summaryrefslogtreecommitdiff
path: root/ipl/packs/itweak/itweak.icn
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/packs/itweak/itweak.icn')
-rw-r--r--ipl/packs/itweak/itweak.icn830
1 files changed, 830 insertions, 0 deletions
diff --git a/ipl/packs/itweak/itweak.icn b/ipl/packs/itweak/itweak.icn
new file mode 100644
index 0000000..47324ef
--- /dev/null
+++ b/ipl/packs/itweak/itweak.icn
@@ -0,0 +1,830 @@
+############################################################################
+#
+# File: itweak.icn
+#
+# Subject: Icon interactive debugging.
+# Tweaks a ucode file ('.u1') to invoke a debugging procedure.
+#
+# Author: Hakan Soderstrom
+#
+# Revision: $Revision: 2.21 $
+#
+###########################################################################
+#
+# Copyright (c) 1994 Hakan Soderstrom and
+# Soderstrom Programvaruverkstad AB, Sweden
+#
+# Permission to use, copy, modify, distribute, and sell this software
+# and its documentation for any purpose is hereby granted without fee,
+# provided that the above copyright notice and this permission notice
+# appear in all copies of the software and related documentation.
+#
+# THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+# WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+#
+# IN NO EVENT SHALL HAKAN SODERSTROM OR SODERSTROM PROGRAMVARUVERKSTAD
+# AB BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL
+# DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY
+# OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+###########################################################################
+
+#
+#-------- Record types --------
+#
+
+record l_decl (d_type, d_serial, d_code, d_name, d_displ, ld_cserial, ld_dbg)
+# Holds a 'local' declaration.
+# 'd_type' must be the declaration type (integer), in this case,
+$define D_LOCAL 1
+# 'd_serial' must be the serial number of the declaration (integer).
+# 'd_code' must be the bitfield that further characterizes the declaration.
+# It is stored as the integer obtained by interpreting the octal coded
+# bitfield as a decimal number.
+# 'd_name' must be the source name of the declared entity.
+# 'd_displ' must be non-null to indicate that this declaration is to be
+# passed to the debug procedure.
+# 'ld_cserial' may be a constant serial number (integer), or null.
+# If integer then the name of this local exists as a constant in the current
+# procedure, which means we include it among the visible variables.
+# 'ld_dbg' is non-null if the declaration has been added by this program.
+
+record c_decl (d_type, d_serial, d_code, d_name, d_displ)
+# Holds a constant declaration added by the program.
+# Like 'l_decl', except 'd_type' must be
+$define D_CONST 2
+
+record fmap (fm_ucode, fm_source)
+# Holds the mapping between an ucode file name and a source file name.
+# 'fm_ucode' must be the root of an ucode file name (string).
+# I.e. the file name without the trailing '.u?'.
+# 'fm_source' must be the name of the source file from which the ucode
+# file originates (string).
+
+global file_map
+# Set containing mapping between ucode and source files (set of record fmap).
+
+global file_root, uin, uout, ulno
+# The current root file name (i.e. file name without '.u?').
+# The current ucode input file.
+# The current ucode output file.
+# The current line number in the current ucode input file.
+
+global init_file
+# Output file name: init file.
+
+global msgout
+# Message output file.
+
+global proc_hil
+# Table containing the "high label" of each procedure in a ucode file.
+# Entry key is a procedure name (string).
+# Entry value is the numeric part of the highest existing label before
+# debugification (integer).
+
+global white
+# This program's definition of white space.
+
+#
+#-------- Constants --------
+#
+
+# Version of this program, variable for holding it.
+$define PROGRAM_VERSION "$Revision: 2.21 $"
+$define PROG_VERSION_VAR "__dbg_itweak_ver"
+
+# DEBUGGING IDENTIFIERS.
+# List holding breakpoints for one source file; two parts.
+# The root file name should be spliced in between.
+$define DBG_BRKP1 "__dbg_file_"
+$define DBG_BRKP2 "_brkp"
+# Global variable holding source/ucode file map.
+# Note: any change affects 'dbg.icn' as well.
+$define DBG_FILE_MAP "__dbg_file_map"
+# Procedure for initializing debugging globals.
+$define DBG_INIT "__dbg_init"
+# Local variable: trapped line number.
+$define DBG_LINE "__dbg_line"
+# List containing names of interesting local variables.
+$define DBG_NAME "__dbg_name"
+# Procedure to call on break.
+$define DBG_PROC "__dbg_proc"
+# Procedure deciding on break.
+$define DBG_TEST "__dbg_test"
+
+# Name of variable whose presence is taken as assurance that an ucode
+# file has been tweaked.
+$define DBG_SENTINEL DBG_LINE
+
+# Default file name for writing the debug initialization code.
+$define DBG_INIT_FILE "dbg_init.icn"
+
+# File name for the debugging run-time.
+$define DBG_RUN_TIME "dbg_run.u1"
+
+# Ucode 'codes' (bitfields) for local declarations.
+# The values are the octal coded bitfield interpreted as decimal.
+$define LD_GLOBAL 0
+$define LD_LOCAL 20
+$define LD_PARM 1000
+$define LD_STATIC 40
+
+# Ucode 'codes' (bitfields) for constant declarations.
+$define CD_INT 2000
+$define CD_STRING 10000
+
+# Various ucode op-codes.
+$define OP_CONST "con"
+$define OP_DEND "declend"
+$define OP_END "end"
+$define OP_FILEN "filen"
+$define OP_LABEL "lab"
+$define OP_LINE "line"
+$define OP_LOCAL "local"
+$define OP_PROC "proc"
+
+# Op-codes in the '.u2' file.
+$define OP_VERSION "version"
+$define OP_LINK "link"
+$define OP_GLOBAL "global"
+
+# Icon versions for which the program has been tested.
+$define ICON_VER_LO "U8.10.00"
+$define ICON_VER_HI "U9.0.00"
+
+# Prefix used for labels.
+$define ULAB_PREF "L"
+
+$define NALN -1
+# Not A Line Number.
+
+$define PROGNAME "itweak"
+# The name by which the user knows this program.
+
+$define U1 ".u1"
+$define U2 ".u2"
+# Standard ucode file name suffix.
+
+$define U1TMP ".uA"
+$define U2TMP ".uB"
+# Suffix of temporary ucode file.
+
+$define U1OLD ".u1~"
+$define U2OLD ".u2~"
+# Suffix of renamed, original ucode file.
+
+#
+#-------- Main --------
+#
+
+procedure main (argv)
+local file_names, iout, u2count
+ # Initialize globals.
+ file_map := set ()
+ msgout := &errout
+ white := '\t '
+ # Process command line options; leave a list of file names.
+ if argv[1] == "-o" then {
+ get (argv)
+ (init_file := get (argv)) |
+ confl ("'-o' requires a file name")
+ }
+ else
+ init_file := DBG_INIT_FILE
+ file_names := copy (argv)
+ # The number of tweaked '.u2' files.
+ u2count := 0
+ # Do two passes on each file.
+ every file_root := !file_names do {
+ # Allow for 'file.u1' and 'file.u'.
+ file_root := if file_root[-3:0] == ".u1" then
+ file_root[1:-3] else if file_root[-2:0] == ".u" then
+ file_root[1:-2]
+ # Pass 1.
+ (uin := open (file_root || U1, "r")) |
+ confl ("Cannot open '%1%2' for input.", file_root, U1)
+ uout := &null
+ if pass1 () then {
+ close (uin)
+ # Tweak at most one '.u2' file.
+ if u2count = 0 then {
+ (uin := open (file_root || U2, "r")) |
+ confl ("Cannot open '%1%2' for input.", file_root, U2)
+ (uout := open (file_root || U2TMP, "w")) |
+ confl ("Cannot open '%1%2' for output.", file_root,
+ U2TMP)
+ u2tweak ()
+ close (uin)
+ close (uout)
+ u2count +:= 1
+ # Make way for the following rename.
+ remove (file_root || U2OLD)
+ rename (file_root || U2, file_root || U2OLD) |
+ confl ("Cannot rename '%1%2' to '%1%3'.", file_root,
+ U2, U2OLD)
+ rename (file_root || U2TMP, file_root || U2) |
+ confl ("Cannot rename '%1%2' to '%1%3'.", file_root,
+ U2TMP, U2)
+ }
+ # Pass 2.
+ (uin := open (file_root || U1, "r")) |
+ confl ("Cannot open '%1%2' for input.", file_root, U1)
+ (uout := open (file_root || U1TMP, "w")) |
+ confl ("Cannot open '%1%2' for output.", file_root, U1TMP)
+ pass2 ()
+ close (uin)
+ close (uout)
+ # Make way for the following rename.
+ remove (file_root || U1OLD)
+ rename (file_root || U1, file_root || U1OLD) |
+ confl ("Cannot rename '%1%2' to '%1%3'.", file_root, U1, U1OLD)
+ rename (file_root || U1TMP, file_root || U1) |
+ confl ("Cannot rename '%1%2' to '%1%3'.", file_root, U1TMP, U1)
+ }
+ else {
+ close (uin)
+ note ("'%1%2' seems to be tweaked already; left untouched.",
+ file_root, U1)
+ }
+ }
+ # Write initialization code.
+ (iout := open (init_file, "w")) |
+ confl ("Cannot open '%1' for output.", init_file)
+ cre_init (iout)
+ note ("Initialization code written to '%1'.", init_file)
+end
+
+#
+#-------- Pass 1 procedures --------
+#
+
+procedure pass1 ()
+# Performs a first pass over a ucode file, collecting label statistics.
+# RETURNS null normally.
+# FAILS if the first procedure has a local declaration containing the sentinel
+# variable.
+# This is taken to imply that the ucode file is already tweaked.
+# SIDE EFFECT: Updates glocal 'proc_hil' (max labels per proc).
+# Updates 'file_map' (source file name ~ ucode file name).
+local cur_high, cur_proc, labint, line, loc, op, proc_no
+static fn_instr, lc_decl
+initial {
+ fn_instr := [OP_FILEN, OP_LINE, OP_LABEL]
+ lc_decl := [OP_LOCAL, OP_CONST, OP_DEND]
+ }
+ proc_hil := table ()
+ loc := table ()
+ proc_no := 0
+ while op := p1_proclab () do if op[1] == "proc" then {
+ if \cur_proc then {
+ (/proc_hil[cur_proc] := cur_high) |
+ confl ("%1: occurs twice; confusing.", cur_proc)
+ }
+ cur_proc := op[2]
+ cur_high := -1
+
+ # Special treatment of the first procedure in every file.
+ if (proc_no +:= 1) = 1 then {
+ # Borrow some pass 2 code to collect the local declarations.
+ while (op := p2_upto (lc_decl))[1] == OP_LOCAL do
+ p2_getlocal (loc, op[2])
+ # Look for source file name.
+ repeat if (op := p2_upto (fn_instr))[1] == OP_FILEN then {
+ insert (file_map, fmap (file_root, op[2]))
+ break
+ }
+ else if op[1] == OP_LABEL then
+ cur_high <:= integer (op[2][2:0])
+ # Flush buffers.
+ p2_upto ()
+ # Fail if the sentinel is present.
+ if \loc[DBG_SENTINEL] then
+ fail
+ }
+ }
+ else if op[1] == "lab" then {
+ # ASSUME the label consists of one character followed by an integer.
+ (labint := integer (op[2][2:0])) |
+ intern ("pass1: Problem parsing label %1.", image (op[2]))
+ cur_high <:= labint
+ }
+ if \cur_proc then {
+ (/proc_hil[cur_proc] := cur_high) |
+ confl ("%1: occurs twice; confusing.", cur_proc)
+ }
+ else
+ intern ("pass1: No proc found.")
+ return &null
+end
+
+procedure p1_proclab ()
+# Returns the next ucode line containing a "proc" or "lab" instruction.
+# If a matching line is found, RETURNS a two-component list.
+# The first element contains the instruction found (string).
+# The second element contains the second word on the line.
+# FAILS on end-of-file.
+local line, opcode, tail
+static opchar
+initial opchar := &lcase
+ while line := read (uin) do line ? {
+ if (opcode := tab (many (opchar))) == ("proc" | "lab") then {
+ tab (many (white))
+ tail := tab (upto (white) | 0)
+ break
+ }
+ }
+ return [opcode, \tail]
+end
+
+#
+#-------- Pass 2 procedures --------
+#
+
+procedure pass2 ()
+# Performs a second pass over the ucode file, doing the actual tweaking.
+# Writes the new ucode to 'uout'.
+local counter, op
+ counter := 0
+ while op := p2_upto ([OP_PROC]) do
+ p2_proc (trim (op[2]), counter +:= 1)
+end
+
+procedure p2_addbrkp (line, last_lab, dbg_brkp, dbg_label, dbg_line, dbg_test)
+# Adds code for breakpoint testing.
+# 'line' should be the line number associated with the current ucode 'line'
+# instruction.
+# 'ltab' must be a table containing declarations of the current procedure.
+# 'last_lab' must be the previous highest label serial (integer).
+# RETURNS the new highest label serial.
+ write (uout,
+ "\tmark\t", ULAB_PREF, last_lab +:= 1,
+ "\n\tpnull",
+ "\n\tvar\t", dbg_line,
+ "\n\tvar\t", dbg_test,
+ "\n\tvar\t", dbg_brkp,
+ "\n\tkeywd\tline\n\tinvoke\t2\n\tasgn\n\tgoto\t", dbg_label,
+ "\n\tunmark\nlab ", ULAB_PREF, last_lab)
+ return last_lab
+end
+
+procedure p2_addcall (ltab, dbg_label, init_label, end_label, dbg_line, dbg_name,
+ dbg_proc, pname_decl)
+# Adds code for invoking the debug procedure.
+local decl, pname_var, vlist
+ # Make vlist an alphabetically sorted list of identifiers: the names of
+ # the variables which should be passed to the debugging procedure.
+ vlist := []
+ every \(decl := !ltab).d_displ do
+ put (vlist, decl.d_name)
+ vlist := sort (vlist)
+ # Begin writing the code.
+ write (uout,
+ "\tgoto\t", end_label,
+ "\nlab ", dbg_label,
+ "\n\tinit\t", init_label,
+ "\n\tmark\t", init_label,
+ "\n\tpnull\n\tvar\t", dbg_name,
+ "\n\tpnull")
+ every write (uout, "\tstr\t", (ltab[!vlist]).ld_cserial)
+ pname_var := if pname_decl.d_type = D_LOCAL then
+ pname_decl.ld_cserial else pname_decl.d_serial
+ write (uout,
+ "\tllist\t", *vlist,
+ "\n\tasgn\n\tunmark\nlab ", init_label,
+ "\n\tmark0\n\tvar\t", dbg_proc,
+ "\n\tkeywd\tfile\n\tvar\t", dbg_line,
+ "\n\tstr\t", pname_var,
+ "\n\tvar\t", dbg_name)
+ every write (uout, "\tvar\t", (ltab[!vlist]).d_serial)
+ write (uout,
+ "\tinvoke\t", 4 + *vlist,
+ "\n\tunmark\nlab ", end_label,
+ "\n\tpfail")
+end
+
+procedure p2_addconst (decl, last_ser)
+# Adds a string constant declaration containing the name of a local or constant
+# declaration.
+# 'decl' must be the declaration (record l_decl or c_decl).
+# 'last_ser' must be the previous highest constant serial in this procedure.
+# RETURNS the serial of the new constant.
+# SIDE EFFECT: Updates 'decl'.
+# Writes the new constant to the ucode output file.
+# NOTE: This version does not add the name if the declaration is a global and
+# is known to be a procedure.
+local serial
+ # Omit variables which have been added by this program.
+ (decl.d_type = D_CONST) | (/decl.ld_dbg & decl.d_code ~= LD_GLOBAL) |
+ fail
+ (decl.d_type = D_CONST) | (decl.d_displ := 1)
+ serial := last_ser + 1
+ if decl.d_type = D_LOCAL then
+ decl.ld_cserial := serial
+ else
+ decl.d_serial := serial
+ writes (uout, "\tcon\t", serial, ",",
+ right (CD_STRING, 6, "0"), ",", *decl.d_name)
+ every writes (uout, ",", octal (ord (!decl.d_name)))
+ write (uout)
+ return serial
+end
+
+procedure p2_addinit (ltab, init_label)
+ write (uout,
+ "\tinit\t", init_label,
+ "\n\tmark\t", init_label,
+ "\n\tvar\t", ltab[DBG_INIT].d_serial,
+ "\n\tinvoke\t0\n\tunmark\nlab ", init_label)
+end
+
+procedure p2_addlocal (pname, ltab, serial, code, name, dbg)
+# Adds a local declaration to a table.
+# 'pname' must be the current procedure name.
+# 'ltab' must be the table where the new declaration is stored.
+# See 'p2_getlocal' for details.
+# 'serial' must be the serial to assign to the new declaration.
+# 'code' must be the code,
+# 'name' must be the name of the new declaration.
+# 'dbg' may be non-null to indicate something different from a normal variable
+# declaration.
+# RETURNS the new declaration (record l_decl).
+# SIDE EFFECT: Writes code for the new declaration to the ucode output file.
+# Creates a new entry in 'ltab'.
+local decl, old_d
+ # Check if the declaration already is there.
+ if old_d := \ltab[name] then {
+ # Check that the existing declaration is equivalent to the new.
+ (old_d.d_code = code) |
+ confl ("%1: conflicting declarations in procedure %2.", name, pname)
+ return old_d
+ }
+ decl := l_decl (D_LOCAL)
+ decl.d_serial := serial
+ decl.d_code := code
+ decl.ld_dbg := 1
+ ltab[decl.d_name := name] := decl
+ write (uout, "\tlocal\t", serial, ",", right (code, 6, "0"), ",", name)
+ return decl
+end
+
+procedure p2_brkp ()
+# Scans the ucode input file for the next breakpoint location.
+# Ucode 'line' instructions are considered suitable breakpoint locations.
+# If there are several 'line' instructions with the same line number only the
+# last one is considered suitable.
+# If a location is found, RETURNS the line number of the current location.
+# FAILS if no suitable location is found.
+# This means that an 'end' instruction has been reached
+# When the procedure returns the 'line' instruction has been copied to the ucode
+# output file.
+# When the procedure encounters an 'end' instruction this instruction is not
+# copied to the ucode output file.
+local last_lno, line, opcode
+static cur_lno, opchar
+initial {
+ cur_lno := NALN
+ opchar := &lcase ++ '01'
+ }
+ repeat {
+ # Read and copy until the next 'line' or 'end' instruction is found.
+ repeat {
+ (line := read (uin)) |
+ intern ("p2_brkp: unexpected end of file.")
+ line ? if tab (many (white)) &
+ (opcode := tab (many (opchar))) then {
+ (opcode ~== OP_END) | {
+ last_lno := NALN
+ break
+ }
+ write (uout, line)
+ (opcode ~== OP_LINE) | {
+ last_lno := integer (tab (0))
+ break
+ }
+ }
+ else
+ write (uout, line)
+ }
+ if last_lno = NALN then
+ break
+ else case cur_lno of {
+ # Still the same line, try another one.
+ last_lno: next # a little unstructured ...
+ # First line found.
+ NALN: cur_lno := last_lno
+ # OK, this is it, stop here.
+ default: break
+ }
+ }
+ if last_lno = NALN then
+ fail
+ else
+ return cur_lno :=: last_lno
+end
+
+procedure p2_getlocal (ltab, dstring)
+# Gets a local declaration from ucode representation; adds it to a table.
+# 'ltab' must be a table storing declarations.
+# Entry key is the variable name.
+# Entry value is an 'l_decl' record.
+# 'dstring' must be the ucode string defining the local.
+# RETURNS the serial number of the new declaration.
+# SIDE EFFECT: Adds an entry to 'ltab'.
+local decl
+ decl := l_decl (D_LOCAL)
+ dstring ? {
+ decl.d_serial := integer (tab (many (&digits)))
+ =","
+ decl.d_code := integer (tab (many (&digits)))
+ =","
+ decl.d_name := tab (upto (white) | 0)
+ }
+ ltab[decl.d_name] := decl
+ return decl.d_serial
+end
+
+procedure p2_newlocals (pname, ltab, last_ser, main_flag)
+# Adds debugging local declarations to a procedure.
+# 'pname' must be the procedure name (string).
+# 'ltab' must be a table holding local declarations; see 'p2_getlocal'.
+# 'last_ser' must be the last (highest) serial previously assigned.
+# 'main_flag' must be non-null if the current procedure is 'main'.
+# This will add the DBG_INIT procedure.
+# RETURNS the last local declaration serial.
+# SIDE EFFECT: Writes the new declarations to the ucode output file.
+# Adds the new declarations to 'ltab'.
+ # Add the debugging init procedure if this is 'main'.
+ /main_flag |
+ p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL, DBG_INIT)
+ p2_addlocal (pname, ltab, last_ser +:= 1, LD_LOCAL, DBG_LINE)
+ p2_addlocal (pname, ltab, last_ser +:= 1, LD_STATIC, DBG_NAME)
+ p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL, DBG_PROC)
+ p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL, DBG_TEST)
+ p2_addlocal (pname, ltab, last_ser +:= 1, LD_GLOBAL,
+ make_brkp_idf (file_root))
+ return last_ser
+end
+
+procedure p2_proc (pname)
+# Tweaks the ucode of a single procedure.
+# 'pname' must be the name of the procedure.
+# SIDE EFFECT: Writes tweaked ucode to the ucode output file.
+local dbg_brkp, dbg_label, dbg_line, dbg_name, dbg_proc, dbg_test
+local init_label, end_label, pname_decl
+local loc, first_new_const, last_conser, last_label, last_locser, line
+local main_flag, op
+static con_decl, lc_decl
+initial {
+ # This is just a piece of hand optimization.
+ con_decl := [OP_CONST, OP_DEND]
+ lc_decl := [OP_LOCAL, OP_CONST, OP_DEND]
+ }
+ main_flag := pname == "main"
+ # Go through local declarations; add some new.
+ # See 'p2_getlocal' for documentation of the 'loc' table.
+ loc := table ()
+ last_locser := -1
+ while (op := p2_upto (lc_decl))[1] == OP_LOCAL do {
+ last_locser <:= p2_getlocal (loc, op[2])
+ }
+ # Add our own locals, write them to the ucode output file.
+ last_locser := p2_newlocals (pname, loc, last_locser, main_flag)
+ # Go through constant declarations in order to find the maximum serial.
+ last_conser := -1
+ repeat {
+ if op[1] == OP_CONST then
+ last_conser <:= (op[2] ? integer (tab (many (&digits))))
+ else
+ break
+ (op := p2_upto (con_decl)) | break
+ }
+ # Declare a constant for the procedure name.
+ # Note that the procedure name may be hidden by a local!
+ /loc[pname] := c_decl (D_CONST, , CD_STRING, pname)
+ # Add new constant declarations to the ucode file.
+ first_new_const := last_conser + 1
+ every last_conser := p2_addconst (!loc, last_conser)
+ # We will soon need a new label.
+ last_label := proc_hil[pname]
+ # Flush the 'p2_upto' buffer, normally the 'declend' instruction.
+ p2_upto ()
+ # If this is the 'main' procedure insert code for invoking the
+ # initialization procedure.
+ if \main_flag then
+ p2_addinit (loc, ULAB_PREF || (last_label +:= 1))
+ # Insert breakpoint testing code.
+ dbg_brkp := loc[make_brkp_idf (file_root)].d_serial
+ dbg_label := ULAB_PREF || (last_label +:= 1)
+ dbg_line := loc[DBG_LINE].d_serial
+ dbg_test := loc[DBG_TEST].d_serial
+ while last_label := p2_addbrkp (p2_brkp (), last_label,
+ dbg_brkp, dbg_label, dbg_line, dbg_test)
+ # Write the debug invocation code.
+ init_label := ULAB_PREF || (last_label +:= 1)
+ end_label := ULAB_PREF || (last_label +:= 1)
+ dbg_name := loc[DBG_NAME].d_serial
+ dbg_proc := loc[DBG_PROC].d_serial
+ pname_decl := loc[pname]
+ p2_addcall (loc, dbg_label, init_label, end_label, dbg_line, dbg_name,
+ dbg_proc, pname_decl)
+ # Add an 'end' instruction swallowed by 'p2_brkp'.
+ write (uout, "\t", OP_END)
+end
+
+procedure p2_upto (op)
+# Scans the ucode file, looking for the next line containing an interesting
+# op-code.
+# Copies non-matching lines to the new ucode file (if non-null)
+# 'op' must be a list of the interesting op-code(s), or null.
+# If a matching line is found, RETURNS a list of two elements.
+# The first element contains the op-code, the second element the tail of the
+# instruction (excluding any leading white space).
+# FAILS on end-of-file.
+# FLUSHING THE BUFFER:
+# If the procedure is invoked with null 'op' any uncopied lines are written to
+# the ucode output file; the procedure fails.
+# NOTE: The procedure is used occasionally in pass 1, where there is no 'uout'
+# file.
+# This is the reason 'uout' is checked for existence (otherwise ucode will
+# appear on standard output).
+local opcode, tail
+static new_line, opchar, old_line
+initial opchar := &lcase ++ '01'
+ write (\uout, \new_line)
+ new_line := &null
+ \op | fail
+ repeat {
+ old_line := new_line
+ (new_line := read (uin)) | fail
+ new_line ? {
+ tab (many (white))
+ if (opcode := tab (many (opchar))) == !op then {
+ tab (many (white))
+ tail := tab (0)
+ break
+ }
+ else
+ write (\uout, new_line)
+ }
+ }
+ return [opcode, tail]
+end
+
+#
+#-------- '.u2' tweaking -----------
+#
+
+procedure u2tweak ()
+# Tweaks a '.u2' file, which means:
+# Check the Icon version number;
+# insert 'link' commands to the debugging run-time and to the init procedure.
+local hitcount, op
+ (op := p2_upto ([OP_VERSION])) | {
+ note ("Surprising absence of 'version' in .u2 file...")
+ fail
+ }
+ (ICON_VER_LO <<= op[2] <<= ICON_VER_HI) |
+ note ("WARNING: %1 is tested only for Icon versions '%2'-'%3', found '%4'.",
+ PROGNAME, ICON_VER_LO, ICON_VER_HI, op[2])
+ hitcount := 0
+ while (op := p2_upto ([OP_LINK, OP_GLOBAL]))[1] == OP_LINK do
+ if op[2] == DBG_RUN_TIME then
+ hitcount +:= 1
+ if hitcount = 0 then {
+ write (uout, OP_LINK, "\t", DBG_RUN_TIME)
+ write (uout, OP_LINK, "\t", init_file)
+ }
+ p2_upto ()
+ while write (uout, read (uin))
+end
+
+#
+#-------- General message handling and other utilities --------
+#
+
+procedure confl (msg, parm[])
+# Writes a conflict message and stops the program with nonzero exit code.
+ message ("[CONFLICT] ", subst (msg, parm))
+ message ("*** ", PROGNAME, " stops with failure.")
+ stop ()
+end
+
+procedure cre_init (f)
+# Creates initialization code.
+# 'f' must be a file open for output.
+local map, version
+ version := (PROGRAM_VERSION ? (tab (upto (&digits)),
+ tab (many (&digits++'.'))))
+ every write (f, "global ", (PROG_VERSION_VAR | DBG_TEST | DBG_FILE_MAP))
+ every write (f, "global ", make_brkp_idf ((!file_map).fm_ucode))
+ write (f,
+ "\nprocedure ", DBG_INIT, " ()\n\t",
+ PROG_VERSION_VAR, " := \"", version, "\"\n\t",
+ DBG_TEST, " := member")
+ every write (f,
+ "\t", make_brkp_idf ((!file_map).fm_ucode), " := set ()")
+ write (f, "\t", DBG_FILE_MAP, " := table ()")
+ every map := !file_map do
+ write (f, "\t",
+ DBG_FILE_MAP, "[\"", map.fm_source, "\"] := ",
+ make_brkp_idf (map.fm_ucode))
+ write (f, "\t", DBG_PROC, " ()\nend")
+end
+
+procedure fld_adj (str)
+# Part of 'subst' format string parsing.
+# 'str' must be a parameter string identified by the beginning part of a
+# placeholder ('%n').
+# This procedure checks if the placeholder contains a fixed field width
+# specifier.
+# A fixed field specifier begins with '<' or '<' and continues with the field
+# width expressed as a decimal literal.
+# RETURNS 'str' possibly inserted in a fixed width field.
+local just, init_p, res, wid
+static fwf
+initial fwf := '<>'
+ init_p := &pos
+ if (just := if ="<" then left else if =">" then right) &
+ (wid := integer (tab (many (&digits)))) then
+ res := just (str, wid)
+ else {
+ res := str
+ &pos := init_p
+ }
+ return res
+end
+
+procedure intern (msg, parm[])
+# Writes an internal conflict message and stops the program with nonzero exit
+# code.
+ message ("*** INTERNAL: ", subst (msg, parm))
+ message ("*** ", PROGNAME, " stops with failure.")
+ stop ()
+end
+
+procedure make_brkp_idf (ucode_root)
+# RETURNS an identifier which should be used to hold the breakpoints of an
+# ucode file whose root name is 'ucode_root'.
+ return DBG_BRKP1 || ucode_root || DBG_BRKP2
+end
+
+procedure message (parm[])
+# Writes any number of strings to the message file.
+ every writes (msgout, !parm)
+ write (msgout)
+end
+
+procedure note (msg, parm[])
+# Writes a note message.
+ message ("[NOTE] ", subst (msg, parm))
+end
+
+procedure octal (i)
+# RETURNS the 'i' integer in the form of an octal literal.
+ static digits
+ local s, d
+ initial digits := string (&digits)
+ if i = 0 then return "0"
+ s := ""
+ while i > 0 do {
+ d := i % 8
+ if d > 9 then d := digits[d + 1]
+ s := d || s
+ i /:= 8
+ }
+ return s
+end
+
+procedure subst (msg, parm)
+# Substitutes parameters in a message template.
+# 'msg' must be a message template (string).
+# 'parm' must be a list of parameters (list of string-convertible), or null.
+# It may also be a string.
+local esc, res, sub
+static p_digit
+initial p_digit := '123456789'
+ \parm | return msg
+ parm := [string (parm)]
+ res := ""
+ msg ? until pos (0) do {
+ res ||:= tab (upto ('%\\') | 0)
+ if ="%" then res ||:= {
+ if any (p_digit) then {
+ sub := (\parm[integer (move (1))] | "")
+ fld_adj (sub)
+ }
+ else if any ('%') then
+ move (1)
+ else ""
+ }
+ else if ="\\" then res ||:= case esc := move (1) of {
+ "n": "\n"
+ "t": "\t"
+ default: esc
+ }
+ }
+ return res
+end