1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
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
|