home *** CD-ROM | disk | FTP | other *** search
/ ftp.cs.arizona.edu / ftp.cs.arizona.edu.tar / ftp.cs.arizona.edu / icon / historic / v941.tgz / icon.v941src.tar / icon.v941src / ipl / packs / itweak / itweak.icn < prev    next >
Text File  |  2000-07-29  |  26KB  |  831 lines

  1. ############################################################################
  2. #
  3. #    File:     itweak.icn
  4. #
  5. #    Subject:  Icon interactive debugging.
  6. #          Tweaks a ucode file ('.u1') to invoke a debugging procedure.
  7. #
  8. #    Author:   Hakan Soderstrom
  9. #
  10. #    Revision: $Revision: 2.21 $
  11. #
  12. ###########################################################################
  13. #
  14. # Copyright (c) 1994 Hakan Soderstrom and
  15. # Soderstrom Programvaruverkstad AB, Sweden
  16. # Permission to use, copy, modify, distribute, and sell this software
  17. # and its documentation for any purpose is hereby granted without fee,
  18. # provided that the above copyright notice and this permission notice
  19. # appear in all copies of the software and related documentation.
  20. # THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
  21. # EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
  22. # WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
  23. #
  24. # IN NO EVENT SHALL HAKAN SODERSTROM OR SODERSTROM PROGRAMVARUVERKSTAD
  25. # AB BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL
  26. # DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  27. # OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY
  28. # OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
  29. # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  30. #
  31. ###########################################################################
  32.  
  33. #
  34. #-------- Record types --------
  35. #
  36.  
  37. record l_decl (d_type, d_serial, d_code, d_name, d_displ, ld_cserial, ld_dbg)
  38. # Holds a 'local' declaration.
  39. # 'd_type' must be the declaration type (integer), in this case,
  40. $define D_LOCAL        1
  41. # 'd_serial' must be the serial number of the declaration (integer).
  42. # 'd_code' must be the bitfield that further characterizes the declaration.
  43. # It is stored as the integer obtained by interpreting the octal coded
  44. # bitfield as a decimal number.
  45. # 'd_name' must be the source name of the declared entity.
  46. # 'd_displ' must be non-null to indicate that this declaration is to be
  47. # passed to the debug procedure.
  48. # 'ld_cserial' may be a constant serial number (integer), or null.
  49. # If integer then the name of this local exists as a constant in the current
  50. # procedure, which means we include it among the visible variables.
  51. # 'ld_dbg' is non-null if the declaration has been added by this program.
  52.  
  53. record c_decl (d_type, d_serial, d_code, d_name, d_displ)
  54. # Holds a constant declaration added by the program.
  55. # Like 'l_decl', except 'd_type' must be
  56. $define D_CONST        2
  57.  
  58. record fmap (fm_ucode, fm_source)
  59. # Holds the mapping between an ucode file name and a source file name.
  60. # 'fm_ucode' must be the root of an ucode file name (string).
  61. # I.e. the file name without the trailing '.u?'.
  62. # 'fm_source' must be the name of the source file from which the ucode
  63. # file originates (string).
  64.  
  65. global file_map
  66. # Set containing mapping between ucode and source files (set of record fmap).
  67.  
  68. global file_root, uin, uout, ulno
  69. # The current root file name (i.e. file name without '.u?').
  70. # The current ucode input file.
  71. # The current ucode output file.
  72. # The current line number in the current ucode input file.
  73.  
  74. global init_file
  75. # Output file name: init file.
  76.  
  77. global msgout
  78. # Message output file.
  79.  
  80. global proc_hil
  81. # Table containing the "high label" of each procedure in a ucode file.
  82. # Entry key is a procedure name (string).
  83. # Entry value is the numeric part of the highest existing label before
  84. # debugification (integer).
  85.  
  86. global white
  87. # This program's definition of white space.
  88.  
  89. #
  90. #-------- Constants --------
  91. #
  92.  
  93. # Version of this program, variable for holding it.
  94. $define PROGRAM_VERSION    "$Revision: 2.21 $"
  95. $define PROG_VERSION_VAR "__dbg_itweak_ver"
  96.  
  97. # DEBUGGING IDENTIFIERS.
  98. # List holding breakpoints for one source file; two parts.
  99. # The root file name should be spliced in between.
  100. $define DBG_BRKP1    "__dbg_file_"
  101. $define DBG_BRKP2    "_brkp"
  102. # Global variable holding source/ucode file map.
  103. # Note: any change affects 'dbg.icn' as well.
  104. $define DBG_FILE_MAP    "__dbg_file_map"
  105. # Procedure for initializing debugging globals.
  106. $define DBG_INIT    "__dbg_init"
  107. # Local variable: trapped line number.
  108. $define DBG_LINE    "__dbg_line"
  109. # List containing names of interesting local variables.
  110. $define DBG_NAME    "__dbg_name"
  111. # Procedure to call on break.
  112. $define DBG_PROC    "__dbg_proc"
  113. # Procedure deciding on break.
  114. $define DBG_TEST    "__dbg_test"
  115.  
  116. # Name of variable whose presence is taken as assurance that an ucode
  117. # file has been tweaked.
  118. $define DBG_SENTINEL    DBG_LINE
  119.  
  120. # Default file name for writing the debug initialization code.
  121. $define DBG_INIT_FILE    "dbg_init.icn"
  122.  
  123. # File name for the debugging run-time.
  124. $define DBG_RUN_TIME    "dbg_run.u1"
  125.  
  126. # Ucode 'codes' (bitfields) for local declarations.
  127. # The values are the octal coded bitfield interpreted as decimal.
  128. $define LD_GLOBAL    0
  129. $define LD_LOCAL    20
  130. $define LD_PARM        1000
  131. $define LD_STATIC    40
  132.  
  133. # Ucode 'codes' (bitfields) for constant declarations.
  134. $define CD_INT        2000
  135. $define CD_STRING    10000
  136.  
  137. # Various ucode op-codes.
  138. $define OP_CONST    "con"
  139. $define OP_DEND        "declend"
  140. $define OP_END        "end"
  141. $define OP_FILEN    "filen"
  142. $define OP_LABEL    "lab"
  143. $define OP_LINE        "line"
  144. $define OP_LOCAL    "local"
  145. $define OP_PROC        "proc"
  146.  
  147. # Op-codes in the '.u2' file.
  148. $define OP_VERSION    "version"
  149. $define OP_LINK        "link"
  150. $define OP_GLOBAL    "global"
  151.  
  152. # Icon versions for which the program has been tested.
  153. $define ICON_VER_LO    "U8.10.00"
  154. $define ICON_VER_HI    "U9.0.00"
  155.  
  156. # Prefix used for labels.
  157. $define ULAB_PREF    "L"
  158.  
  159. $define NALN        -1
  160. # Not A Line Number.
  161.  
  162. $define PROGNAME    "itweak"
  163. # The name by which the user knows this program.
  164.  
  165. $define U1    ".u1"
  166. $define U2    ".u2"
  167. # Standard ucode file name suffix.
  168.  
  169. $define U1TMP    ".uA"
  170. $define U2TMP    ".uB"
  171. # Suffix of temporary ucode file.
  172.  
  173. $define U1OLD    ".u1~"
  174. $define U2OLD    ".u2~"
  175. # Suffix of renamed, original ucode file.
  176.  
  177. #
  178. #-------- Main --------
  179. #
  180.  
  181. procedure main (argv)
  182. local file_names, iout, u2count
  183.     # Initialize globals.
  184.     file_map := set ()
  185.     msgout := &errout
  186.     white := '\t '
  187.     # Process command line options; leave a list of file names.
  188.     if argv[1] == "-o" then {
  189.         get (argv)
  190.         (init_file := get (argv)) |
  191.         confl ("'-o' requires a file name")
  192.         }
  193.     else
  194.         init_file := DBG_INIT_FILE
  195.     file_names := copy (argv)
  196.     # The number of tweaked '.u2' files.
  197.     u2count := 0
  198.     # Do two passes on each file.
  199.     every file_root := !file_names do {
  200.         # Allow for 'file.u1' and 'file.u'.
  201.         file_root := if file_root[-3:0] == ".u1" then
  202.             file_root[1:-3] else if file_root[-2:0] == ".u" then
  203.             file_root[1:-2]
  204.         # Pass 1.
  205.         (uin := open (file_root || U1, "r")) |
  206.         confl ("Cannot open '%1%2' for input.", file_root, U1)
  207.         uout := &null
  208.         if pass1 () then {
  209.             close (uin)
  210.             # Tweak at most one '.u2' file.
  211.             if u2count = 0 then {
  212.                 (uin := open (file_root || U2, "r")) |
  213.                 confl ("Cannot open '%1%2' for input.", file_root, U2)
  214.                 (uout := open (file_root || U2TMP, "w")) |
  215.                 confl ("Cannot open '%1%2' for output.", file_root,
  216.                     U2TMP)
  217.                 u2tweak ()
  218.                 close (uin)
  219.                 close (uout)
  220.                 u2count +:= 1
  221.                 # Make way for the following rename.
  222.                 remove (file_root || U2OLD)
  223.                 rename (file_root || U2, file_root || U2OLD) |
  224.                 confl ("Cannot rename '%1%2' to '%1%3'.", file_root,
  225.                     U2, U2OLD)
  226.                 rename (file_root || U2TMP, file_root || U2) |
  227.                 confl ("Cannot rename '%1%2' to '%1%3'.", file_root,
  228.                     U2TMP, U2)
  229.                 }
  230.             # Pass 2.
  231.             (uin := open (file_root || U1, "r")) |
  232.             confl ("Cannot open '%1%2' for input.", file_root, U1)
  233.             (uout := open (file_root || U1TMP, "w")) |
  234.             confl ("Cannot open '%1%2' for output.", file_root, U1TMP)
  235.             pass2 ()
  236.             close (uin)
  237.             close (uout)
  238.             # Make way for the following rename.
  239.             remove (file_root || U1OLD)
  240.             rename (file_root || U1, file_root || U1OLD) |
  241.             confl ("Cannot rename '%1%2' to '%1%3'.", file_root, U1, U1OLD)
  242.             rename (file_root || U1TMP, file_root || U1) |
  243.             confl ("Cannot rename '%1%2' to '%1%3'.", file_root, U1TMP, U1)
  244.             }
  245.         else {
  246.             close (uin)
  247.             note ("'%1%2' seems to be tweaked already; left untouched.",
  248.                 file_root, U1)
  249.             }
  250.         }
  251.     # Write initialization code.
  252.     (iout := open (init_file, "w")) |
  253.     confl ("Cannot open '%1' for output.", init_file)
  254.     cre_init (iout)
  255.     note ("Initialization code written to '%1'.", init_file)
  256. end
  257.  
  258. #
  259. #-------- Pass 1 procedures --------
  260. #
  261.  
  262. procedure pass1 ()
  263. # Performs a first pass over a ucode file, collecting label statistics.
  264. # RETURNS null normally.
  265. # FAILS if the first procedure has a local declaration containing the sentinel
  266. # variable.
  267. # This is taken to imply that the ucode file is already tweaked.
  268. # SIDE EFFECT: Updates glocal 'proc_hil' (max labels per proc).
  269. # Updates 'file_map' (source file name ~ ucode file name).
  270. local cur_high, cur_proc, labint, line, loc, op, proc_no
  271. static fn_instr, lc_decl
  272. initial {
  273.     fn_instr := [OP_FILEN, OP_LINE, OP_LABEL]
  274.     lc_decl := [OP_LOCAL, OP_CONST, OP_DEND]
  275.     }
  276.     proc_hil := table ()
  277.     loc := table ()
  278.     proc_no := 0
  279.     while op := p1_proclab () do if op[1] == "proc" then {
  280.         if \cur_proc then {
  281.             (/proc_hil[cur_proc] := cur_high) |
  282.             confl ("%1: occurs twice; confusing.", cur_proc)
  283.             }
  284.         cur_proc := op[2]
  285.         cur_high := -1
  286.  
  287.         # Special treatment of the first procedure in every file.
  288.         if (proc_no +:= 1) = 1 then {
  289.             # Borrow some pass 2 code to collect the local declarations.
  290.             while (op := p2_upto (lc_decl))[1] == OP_LOCAL do
  291.                 p2_getlocal (loc, op[2])
  292.             # Look for source file name.
  293.             repeat if (op := p2_upto (fn_instr))[1] == OP_FILEN then {
  294.                 insert (file_map, fmap (file_root, op[2]))
  295.                 break
  296.                 }
  297.             else if op[1] == OP_LABEL then
  298.                 cur_high <:= integer (op[2][2:0])
  299.             # Flush buffers.
  300.             p2_upto ()
  301.             # Fail if the sentinel is present.
  302.             if \loc[DBG_SENTINEL] then
  303.                 fail
  304.             }
  305.         }
  306.     else if op[1] == "lab" then {
  307.         # ASSUME the label consists of one character followed by an integer.
  308.         (labint := integer (op[2][2:0])) |
  309.         intern ("pass1: Problem parsing label %1.", image (op[2]))
  310.         cur_high <:= labint
  311.         }
  312.     if \cur_proc then {
  313.         (/proc_hil[cur_proc] := cur_high) |
  314.         confl ("%1: occurs twice; confusing.", cur_proc)
  315.         }
  316.     else
  317.         intern ("pass1: No proc found.")
  318.     return &null
  319. end
  320.  
  321. procedure p1_proclab ()
  322. # Returns the next ucode line containing a "proc" or "lab" instruction.
  323. # If a matching line is found, RETURNS a two-component list.
  324. # The first element contains the instruction found (string).
  325. # The second element contains the second word on the line.
  326. # FAILS on end-of-file.
  327. local line, opcode, tail
  328. static opchar
  329. initial opchar := &lcase
  330.     while line := read (uin) do line ? {
  331.         if (opcode := tab (many (opchar))) == ("proc" | "lab") then {
  332.             tab (many (white))
  333.             tail := tab (upto (white) | 0)
  334.             break
  335.             }
  336.         }
  337.     return [opcode, \tail]
  338. end
  339.  
  340. #
  341. #-------- Pass 2 procedures --------
  342. #
  343.  
  344. procedure pass2 ()
  345. # Performs a second pass over the ucode file, doing the actual tweaking.
  346. # Writes the new ucode to 'uout'.
  347. local counter, op
  348.     counter := 0
  349.     while op := p2_upto ([OP_PROC]) do
  350.         p2_proc (trim (op[2]), counter +:= 1)
  351. end
  352.  
  353. procedure p2_addbrkp (line, last_lab, dbg_brkp, dbg_label, dbg_line, dbg_test)
  354. # Adds code for breakpoint testing.
  355. # 'line' should be the line number associated with the current ucode 'line'
  356. # instruction.
  357. # 'ltab' must be a table containing declarations of the current procedure.
  358. # 'last_lab' must be the previous highest label serial (integer).
  359. # RETURNS the new highest label serial.
  360.     write (uout,
  361.         "\tmark\t", ULAB_PREF, last_lab +:= 1,
  362.         "\n\tpnull",
  363.         "\n\tvar\t", dbg_line,
  364.         "\n\tvar\t", dbg_test,
  365.         "\n\tvar\t", dbg_brkp,
  366.         "\n\tkeywd\tline\n\tinvoke\t2\n\tasgn\n\tgoto\t", dbg_label,
  367.         "\n\tunmark\nlab ", ULAB_PREF, last_lab)
  368.     return last_lab
  369. end
  370.  
  371. procedure p2_addcall (ltab, dbg_label, init_label, end_label, dbg_line, dbg_name,
  372.     dbg_proc, pname_decl)
  373. # Adds code for invoking the debug procedure.
  374. local decl, pname_var, vlist
  375.     # Make vlist an alphabetically sorted list of identifiers: the names of
  376.     # the variables which should be passed to the debugging procedure.
  377.     vlist := []
  378.     every \(decl := !ltab).d_displ do
  379.         put (vlist, decl.d_name)
  380.     vlist := sort (vlist)
  381.     # Begin writing the code.
  382.     write (uout,
  383.         "\tgoto\t", end_label,
  384.         "\nlab ", dbg_label,
  385.         "\n\tinit\t", init_label,
  386.         "\n\tmark\t", init_label,
  387.         "\n\tpnull\n\tvar\t", dbg_name,
  388.         "\n\tpnull")
  389.     every write (uout, "\tstr\t", (ltab[!vlist]).ld_cserial)
  390.     pname_var := if pname_decl.d_type = D_LOCAL then
  391.         pname_decl.ld_cserial else pname_decl.d_serial
  392.     write (uout,
  393.         "\tllist\t", *vlist,
  394.         "\n\tasgn\n\tunmark\nlab ", init_label,
  395.         "\n\tmark0\n\tvar\t", dbg_proc,
  396.         "\n\tkeywd\tfile\n\tvar\t", dbg_line,
  397.         "\n\tstr\t", pname_var,
  398.         "\n\tvar\t", dbg_name)
  399.     every write (uout, "\tvar\t", (ltab[!vlist]).d_serial)
  400.     write (uout,
  401.         "\tinvoke\t", 4 + *vlist,
  402.         "\n\tunmark\nlab ", end_label,
  403.         "\n\tpfail")
  404. end
  405.  
  406. procedure p2_addconst (decl, last_ser)
  407. # Adds a string constant declaration containing the name of a local or constant
  408. # declaration.
  409. # 'decl' must be the declaration (record l_decl or c_decl).
  410. # 'last_ser' must be the previous highest constant serial in this procedure.
  411. # RETURNS the serial of the new constant.
  412. # SIDE EFFECT: Updates 'decl'.
  413. # Writes the new constant to the ucode output file.
  414. # NOTE: This version does not add the name if the declaration is a global and
  415. # is known to be a procedure.
  416. local serial
  417.     # Omit variables which have been added by this program.
  418.     (decl.d_type = D_CONST) | (/decl.ld_dbg & decl.d_code ~= LD_GLOBAL) |
  419.         fail
  420.     (decl.d_type = D_CONST) | (decl.d_displ := 1)
  421.     serial := last_ser + 1
  422.     if decl.d_type = D_LOCAL then
  423.         decl.ld_cserial := serial
  424.     else
  425.         decl.d_serial := serial
  426.     writes (uout, "\tcon\t", serial, ",",
  427.         right (CD_STRING, 6, "0"), ",", *decl.d_name)
  428.     every writes (uout, ",", octal (ord (!decl.d_name)))
  429.     write (uout)
  430.     return serial
  431. end
  432.  
  433. procedure p2_addinit (ltab, init_label)
  434.     write (uout,
  435.         "\tinit\t", init_label,
  436.         "\n\tmark\t", init_label,
  437.         "\n\tvar\t", ltab[DBG_INIT].d_serial,
  438.         "\n\tinvoke\t0\n\tunmark\nlab ", init_label)
  439. end
  440.  
  441. procedure p2_addlocal (pname, ltab, serial, code, name, dbg)
  442. # Adds a local declaration to a table.
  443. # 'pname' must be the current procedure name.
  444. # 'ltab' must be the table where the new declaration is stored.
  445. # See 'p2_getlocal' for details.
  446. # 'serial' must be the serial to assign to the new declaration.
  447. # 'code' must be the code,
  448. # 'name' must be the name of the new declaration.
  449. # 'dbg' may be non-null to indicate something different from a normal variable
  450. # declaration.
  451. # RETURNS the new declaration (record l_decl).
  452. # SIDE EFFECT: Writes code for the new declaration to the ucode output file.
  453. # Creates a new entry in 'ltab'.
  454. local decl, old_d
  455.     # Check if the declaration already is there.
  456.     if old_d := \ltab[name] then {
  457.         # Check that the existing declaration is equivalent to the new.
  458.         (old_d.d_code = code) |
  459.         confl ("%1: conflicting declarations in procedure %2.", name, pname)
  460.         return old_d
  461.         }
  462.     decl := l_decl (D_LOCAL)
  463.     decl.d_serial := serial
  464.     decl.d_code := code
  465.     decl.ld_dbg := 1
  466.     ltab[decl.d_name := name] := decl
  467.     write (uout, "\tlocal\t", serial, ",", right (code, 6, "0"), ",", name)
  468.     return decl
  469. end
  470.  
  471. procedure p2_brkp ()
  472. # Scans the ucode input file for the next breakpoint location.
  473. # Ucode 'line' instructions are considered suitable breakpoint locations.
  474. # If there are several 'line' instructions with the same line number only the
  475. # last one is considered suitable.
  476. # If a location is found, RETURNS the line number of the current location.
  477. # FAILS if no suitable location is found.
  478. # This means that an 'end' instruction has been reached
  479. # When the procedure returns the 'line' instruction has been copied to the ucode
  480. # output file.
  481. # When the procedure encounters an 'end' instruction this instruction is not
  482. # copied to the ucode output file.
  483. local last_lno, line, opcode
  484. static cur_lno, opchar
  485. initial {
  486.     cur_lno := NALN
  487.     opchar := &lcase ++ '01'
  488.     }
  489.     repeat {
  490.         # Read and copy until the next 'line' or 'end' instruction is found.
  491.         repeat {
  492.             (line := read (uin)) |
  493.             intern ("p2_brkp: unexpected end of file.")
  494.             line ? if tab (many (white)) &
  495.                 (opcode := tab (many (opchar))) then {
  496.                 (opcode ~== OP_END) | {
  497.                     last_lno := NALN
  498.                     break
  499.                     }
  500.                 write (uout, line)
  501.                 (opcode ~== OP_LINE) | {
  502.                     last_lno := integer (tab (0))
  503.                     break
  504.                     }
  505.                 }
  506.             else
  507.                 write (uout, line)
  508.             }
  509.         if last_lno = NALN then
  510.             break
  511.         else case cur_lno of {
  512.             # Still the same line, try another one.
  513.             last_lno:    next # a little unstructured ...
  514.             # First line found.
  515.             NALN:        cur_lno := last_lno
  516.             # OK, this is it, stop here.
  517.             default:    break
  518.             }
  519.         }
  520.     if last_lno = NALN then
  521.         fail
  522.     else
  523.         return cur_lno :=: last_lno
  524. end
  525.  
  526. procedure p2_getlocal (ltab, dstring)
  527. # Gets a local declaration from ucode representation; adds it to a table.
  528. # 'ltab' must be a table storing declarations.
  529. # Entry key is the variable name.
  530. # Entry value is an 'l_decl' record.
  531. # 'dstring' must be the ucode string defining the local.
  532. # RETURNS the serial number of the new declaration.
  533. # SIDE EFFECT: Adds an entry to 'ltab'.
  534. local decl
  535.     decl := l_decl (D_LOCAL)
  536.     dstring ? {
  537.         decl.d_serial := integer (tab (many (&digits)))
  538.         =","
  539.         decl.d_code := integer (tab (many (&digits)))
  540.         =","
  541.         decl.d_name := tab (upto (white) | 0)
  542.         }
  543.     ltab[decl.d_name] := decl
  544.     return decl.d_serial
  545. end
  546.  
  547. procedure p2_newlocals (pname, ltab, last_ser, main_flag)
  548. # Adds debugging local declarations to a procedure.
  549. # 'pname' must be the procedure name (string).
  550. # 'ltab' must be a table holding local declarations; see 'p2_getlocal'.
  551. # 'last_ser' must be the last (highest) serial previously assigned.
  552. # 'main_flag' must be non-null if the current procedure is 'main'.
  553. # This will add the DBG_INIT procedure.
  554. # RETURNS the last local declaration serial.
  555. # SIDE EFFECT: Writes the new declarations to the ucode output file.
  556. # Adds the new declarations to 'ltab'.
  557.     # Add the debugging init procedure if this is 'main'.
  558.     /main_flag |
  559.     p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL, DBG_INIT)
  560.     p2_addlocal (pname, ltab, last_ser +:= 1, LD_LOCAL, DBG_LINE)
  561.     p2_addlocal (pname, ltab, last_ser +:= 1, LD_STATIC, DBG_NAME)
  562.     p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL, DBG_PROC)
  563.     p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL, DBG_TEST)
  564.     p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL,
  565.         make_brkp_idf (file_root))
  566.     return last_ser
  567. end
  568.     
  569. procedure p2_proc (pname)
  570. # Tweaks the ucode of a single procedure.
  571. # 'pname' must be the name of the procedure.
  572. # SIDE EFFECT: Writes tweaked ucode to the ucode output file.
  573. local dbg_brkp, dbg_label, dbg_line, dbg_name, dbg_proc, dbg_test
  574. local init_label, end_label, pname_decl
  575. local loc, first_new_const, last_conser, last_label, last_locser, line
  576. local main_flag, op
  577. static con_decl, lc_decl
  578. initial {
  579.     # This is just a piece of hand optimization.
  580.     con_decl := [OP_CONST, OP_DEND]
  581.     lc_decl := [OP_LOCAL, OP_CONST, OP_DEND]
  582.     }
  583.     main_flag := pname == "main"
  584.     # Go through local declarations; add some new.
  585.     # See 'p2_getlocal' for documentation of the 'loc' table.
  586.     loc := table ()
  587.     last_locser := -1
  588.     while (op := p2_upto (lc_decl))[1] == OP_LOCAL do {
  589.         last_locser <:= p2_getlocal (loc, op[2])
  590.         }
  591.     # Add our own locals, write them to the ucode output file.
  592.     last_locser := p2_newlocals (pname, loc, last_locser, main_flag)
  593.     # Go through constant declarations in order to find the maximum serial.
  594.     last_conser := -1
  595.     repeat {
  596.         if op[1] == OP_CONST then
  597.             last_conser <:= (op[2] ? integer (tab (many (&digits))))
  598.         else
  599.             break
  600.         (op := p2_upto (con_decl)) | break
  601.         }
  602.     # Declare a constant for the procedure name.
  603.     # Note that the procedure name may be hidden by a local!
  604.     /loc[pname] := c_decl (D_CONST, , CD_STRING, pname)
  605.     # Add new constant declarations to the ucode file.
  606.     first_new_const := last_conser + 1
  607.     every last_conser := p2_addconst (!loc, last_conser)
  608.     # We will soon need a new label.
  609.     last_label := proc_hil[pname]
  610.     # Flush the 'p2_upto' buffer, normally the 'declend' instruction.
  611.     p2_upto ()
  612.     # If this is the 'main' procedure insert code for invoking the
  613.     # initialization procedure.
  614.     if \main_flag then
  615.         p2_addinit (loc, ULAB_PREF || (last_label +:= 1))
  616.     # Insert breakpoint testing code.
  617.     dbg_brkp := loc[make_brkp_idf (file_root)].d_serial
  618.     dbg_label := ULAB_PREF || (last_label +:= 1)
  619.     dbg_line := loc[DBG_LINE].d_serial
  620.     dbg_test := loc[DBG_TEST].d_serial
  621.     while last_label := p2_addbrkp (p2_brkp (), last_label,
  622.         dbg_brkp, dbg_label, dbg_line, dbg_test)
  623.     # Write the debug invocation code.
  624.     init_label := ULAB_PREF || (last_label +:= 1)
  625.     end_label := ULAB_PREF || (last_label +:= 1)
  626.     dbg_name := loc[DBG_NAME].d_serial
  627.     dbg_proc := loc[DBG_PROC].d_serial
  628.     pname_decl := loc[pname]
  629.     p2_addcall (loc, dbg_label, init_label, end_label, dbg_line, dbg_name,
  630.         dbg_proc, pname_decl)
  631.     # Add an 'end' instruction swallowed by 'p2_brkp'.
  632.     write (uout, "\t", OP_END)
  633. end
  634.  
  635. procedure p2_upto (op)
  636. # Scans the ucode file, looking for the next line containing an interesting
  637. # op-code.
  638. # Copies non-matching lines to the new ucode file (if non-null)
  639. # 'op' must be a list of the interesting op-code(s), or null.
  640. # If a matching line is found, RETURNS a list of two elements.
  641. # The first element contains the op-code, the second element the tail of the
  642. # instruction (excluding any leading white space).
  643. # FAILS on end-of-file.
  644. # FLUSHING THE BUFFER:
  645. # If the procedure is invoked with null 'op' any uncopied lines are written to
  646. # the ucode output file; the procedure fails.
  647. # NOTE: The procedure is used occasionally in pass 1, where there is no 'uout'
  648. # file.
  649. # This is the reason 'uout' is checked for existence (otherwise ucode will
  650. # appear on standard output).
  651. local opcode, tail
  652. static new_line, opchar, old_line
  653. initial opchar := &lcase ++ '01'
  654.     write (\uout, \new_line)
  655.     new_line := &null
  656.     \op | fail
  657.     repeat {
  658.         old_line := new_line
  659.         (new_line := read (uin)) | fail
  660.         new_line ? {
  661.             tab (many (white))
  662.             if (opcode := tab (many (opchar))) == !op then {
  663.                 tab (many (white))
  664.                 tail := tab (0)
  665.                 break
  666.                 }
  667.             else
  668.                 write (\uout, new_line)
  669.             }
  670.         }
  671.     return [opcode, tail]
  672. end
  673.  
  674. #
  675. #-------- '.u2' tweaking -----------
  676. #
  677.  
  678. procedure u2tweak ()
  679. # Tweaks a '.u2' file, which means:
  680. # Check the Icon version number;
  681. # insert 'link' commands to the debugging run-time and to the init procedure.
  682. local hitcount, op
  683.     (op := p2_upto ([OP_VERSION])) | {
  684.         note ("Surprising absence of 'version' in .u2 file...")
  685.         fail
  686.         }
  687.     (ICON_VER_LO <<= op[2] <<= ICON_VER_HI) |
  688.     note ("WARNING: %1 is tested only for Icon versions '%2'-'%3', found '%4'.",
  689.             PROGNAME, ICON_VER_LO, ICON_VER_HI, op[2])
  690.     hitcount := 0
  691.     while (op := p2_upto ([OP_LINK, OP_GLOBAL]))[1] == OP_LINK do
  692.         if op[2] == DBG_RUN_TIME then
  693.             hitcount +:= 1
  694.     if hitcount = 0 then {
  695.         write (uout, OP_LINK, "\t", DBG_RUN_TIME)
  696.         write (uout, OP_LINK, "\t", init_file)
  697.         }
  698.     p2_upto ()
  699.     while write (uout, read (uin))
  700. end
  701.  
  702. #
  703. #-------- General message handling and other utilities --------
  704. #
  705.  
  706. procedure confl (msg, parm[])
  707. # Writes a conflict message and stops the program with nonzero exit code.
  708.     message ("[CONFLICT] ", subst (msg, parm))
  709.     message ("*** ", PROGNAME, " stops with failure.")
  710.     stop ()
  711. end
  712.  
  713. procedure cre_init (f)
  714. # Creates initialization code.
  715. # 'f' must be a file open for output.
  716. local map, version
  717.     version := (PROGRAM_VERSION ? (tab (upto (&digits)),
  718.         tab (many (&digits++'.'))))
  719.     every write (f, "global ", (PROG_VERSION_VAR | DBG_TEST | DBG_FILE_MAP))
  720.     every write (f, "global ", make_brkp_idf ((!file_map).fm_ucode))
  721.     write (f,
  722.         "\nprocedure ", DBG_INIT, " ()\n\t",
  723.         PROG_VERSION_VAR, " := \"", version, "\"\n\t",
  724.         DBG_TEST, " := member")
  725.     every write (f,
  726.         "\t", make_brkp_idf ((!file_map).fm_ucode), " := set ()")
  727.     write (f, "\t", DBG_FILE_MAP, " := table ()")
  728.     every map := !file_map do
  729.         write (f, "\t",
  730.             DBG_FILE_MAP, "[\"", map.fm_source, "\"] := ",
  731.             make_brkp_idf (map.fm_ucode))
  732.     write (f, "\t", DBG_PROC, " ()\nend")
  733. end
  734.  
  735. procedure fld_adj (str)
  736. # Part of 'subst' format string parsing.
  737. # 'str' must be a parameter string identified by the beginning part of a
  738. # placeholder ('%n').
  739. # This procedure checks if the placeholder contains a fixed field width
  740. # specifier.
  741. # A fixed field specifier begins with '<' or '<' and continues with the field
  742. # width expressed as a decimal literal.
  743. # RETURNS 'str' possibly inserted in a fixed width field.
  744. local just, init_p, res, wid
  745. static fwf
  746. initial fwf := '<>'
  747.     init_p := &pos
  748.     if (just := if ="<" then left else if =">" then right) &
  749.         (wid := integer (tab (many (&digits)))) then
  750.         res := just (str, wid)
  751.     else {
  752.         res := str
  753.         &pos := init_p
  754.         }
  755.     return res
  756. end
  757.  
  758. procedure intern (msg, parm[])
  759. # Writes an internal conflict message and stops the program with nonzero exit
  760. # code.
  761.     message ("*** INTERNAL: ", subst (msg, parm))
  762.     message ("*** ", PROGNAME, " stops with failure.")
  763.     stop ()
  764. end
  765.  
  766. procedure make_brkp_idf (ucode_root)
  767. # RETURNS an identifier which should be used to hold the breakpoints of an
  768. # ucode file whose root name is 'ucode_root'.
  769.     return DBG_BRKP1 || ucode_root || DBG_BRKP2
  770. end
  771.  
  772. procedure message (parm[])
  773. # Writes any number of strings to the message file.
  774.     every writes (msgout, !parm)
  775.     write (msgout)
  776. end
  777.  
  778. procedure note (msg, parm[])
  779. # Writes a note message.
  780.     message ("[NOTE] ", subst (msg, parm))
  781. end
  782.  
  783. procedure octal (i)
  784. # RETURNS the 'i' integer in the form of an octal literal.
  785.    static digits
  786.    local s, d
  787.    initial digits := string (&digits)
  788.    if i = 0 then return "0"
  789.    s := ""
  790.    while i > 0 do {
  791.       d := i % 8
  792.       if d > 9 then d := digits[d + 1]
  793.       s := d || s
  794.       i /:= 8
  795.       }
  796.    return s
  797. end
  798.  
  799. procedure subst (msg, parm)
  800. # Substitutes parameters in a message template.
  801. # 'msg' must be a message template (string).
  802. # 'parm' must be a list of parameters (list of string-convertible), or null.
  803. # It may also be a string.
  804. local esc, res, sub
  805. static p_digit
  806. initial p_digit := '123456789'
  807.     \parm | return msg
  808.     parm := [string (parm)]
  809.     res := ""
  810.     msg ? until pos (0) do {
  811.         res ||:= tab (upto ('%\\') | 0)
  812.         if ="%" then res ||:= {
  813.             if any (p_digit) then {
  814.                 sub := (\parm[integer (move (1))] | "")
  815.                 fld_adj (sub)    
  816.                 }
  817.             else if any ('%') then
  818.                 move (1)
  819.             else ""
  820.             }
  821.         else if ="\\" then res ||:= case esc := move (1) of {
  822.             "n": "\n"
  823.             "t": "\t"
  824.             default: esc
  825.             }
  826.         }
  827.     return res
  828. end
  829.