This program is a BASIC flow tracer utility designed to be merged into another BASIC program and then activated by running line 9800. It loads 135 bytes of Z80 machine code into RAM at address 65218 onward, which performs the work of scanning BASIC program lines and identifying control flow tokens such as GOTO, GOSUB, and RETURN. The tracer walks through the target program’s lines, printing each line number encountered along with the keyword that transfers control, and correctly handles nested GOSUB calls using a software stack stored in array s(50). Memory locations 65360–65367 serve as a parameter-passing area between the BASIC and machine code layers. The utility stops when it reaches line 9800 or encounters a STOP statement, preventing it from tracing its own code.
Program Analysis
Program Structure
The program is divided into two logical phases. Lines 9799–9870 handle machine code installation: they POKE 135 bytes of Z80 code into addresses 65218–65352 and verify the installation via a sentinel byte check at 65350. Lines 9900–9980 implement the BASIC-level tracer loop that calls into the machine code and interprets the results. Line 9999 saves the program and lists it.
The REM comments at lines 9800, 9823, 9830, 9840, 9850, and 9860 label the machine code DATA blocks by logical section: the main scanner (data A), the token dispatcher (data B), a sub-scanner (S13), a sequence scanner (SEE), and an output helper (OUT). This structure suggests the machine code was originally assembled in named sections.
Machine Code Usage
The machine code is loaded from DATA statements at lines 9825–9865 into the top of RAM starting at address 65218. A guard check at line 9807 tests whether address 65350 contains 201 (the Z80 RET opcode) before attempting a reload, allowing the merged utility to skip reloading if already installed.
Two USR entry points are used:
USR 65218— the main scanner entry point, called at line 9915 to advance from the current line pointer to the next BASIC token of interest.USR 65261— called at line 9916 to retrieve the controlling token byte (stored inc) and the associated address ina.USR 65345— called at line 9980 to continue scanning from a new address after handling a conditional branch.
The machine code communicates with BASIC through a dedicated parameter block at addresses 65360–65367. BASIC writes the current line number (65362–65363) and a return address pointer (65364–65365), and reads back the next line number (65360–65361) and token address (65366–65367).
Key BASIC Idioms
- Line 9912 performs manual 16-bit decomposition:
LET l2=INT(l/256): LET l1=l-l2*256, splitting a line number into low and high bytes for POKEing. - The GOSUB stack is simulated in BASIC using a numeric array
s(50)with a manual stack pointer variables, since the tracer is inspecting — not executing — the target program. lltracks the last printed line number to suppress repeated printing of the same line number when multiple statements on one line are encountered (lines 9921–9922).- Line 9905 uses
OVER 1to draw a decorative underline beneath the title text without erasing it.
Token Interpretation
The tracer identifies Spectrum BASIC keyword token bytes returned in c and dispatches on them:
| Token value (c) | Keyword | Action |
|---|---|---|
| 250 | IF | Enters conditional branch handler at line 9950 |
| 237 | GOSUB | Prints “GOSUB”, pushes return line onto stack |
| 236 | GOTO | Prints “GOTO”, transfers line pointer |
| 254 | RETURN | Prints “RETURN”, pops return line from stack |
| 235 | DEF FN / other | Prints CHR$ c and continues |
| 243 | other | Prints CHR$ c and continues |
| 226 | STOP | Prints “STOP” and halts tracing |
For GOTO and GOSUB, the tracer scans forward through the token’s argument bytes looking for the floating-point number marker byte 14 (line 9933), then reads the two-byte integer value at offset +3 (line 9935) to extract the destination line number.
Conditional Branch Handling
When an IF token is detected, the code at lines 9950–9965 scans forward in the line for byte 203 (the THEN token), then reads the following token. If THEN is followed by GOTO or GOSUB (236 or 237), the destination line is extracted and printed. Otherwise, the token is printed as a character and scanning continues. This handles IF...THEN GOTO n, IF...THEN GOSUB n, and IF...THEN RETURN forms.
Notable Techniques
- The merge-in design (STOP at line 9799, utility code at 9800+) means the utility is invisible to the traced program as long as it uses line numbers below 9800, which is enforced by the check at line 9910.
- The sentinel check (
PEEK 65350=201) at line 9807 is a lightweight way to detect whether the machine code is already present, avoiding redundant loading when the tracer is re-entered. - The software GOSUB stack depth is capped at 50 levels by the
DIM s(50)declaration; no overflow check is performed, which would cause an error on deeply nested programs. - Line 9901 provides a user-friendly error message if machine code is not loaded, guiding the user to run line 9800 first.
Potential Issues
- The GOSUB stack variable and the stack array share the name
s(scalar and array). This is valid BASIC syntax since scalars and arrays occupy separate namespaces, but is potentially confusing to readers. - No bounds checking is performed on the GOSUB stack pointer
s; a program with more than 50 nested GOSUBs would cause a subscript error. - The tracer only follows one branch of an IF statement (the THEN branch), so control flow through the false path is not traced.
Content
Source Code
9799 STOP
9800 REM Merge this program to aBASIC program.GOTO 9800 to tracethe BASIC program
9807 IF PEEK 65350=201 THEN GO TO 9902
9810 CLEAR 65217
9812 PRINT "Please wait..."''"Machine Code Loading"
9815 RESTORE 9825
9820 FOR q=65218 TO 65352: READ a: POKE q,a: NEXT q
9823 REM data A:
9825 DATA 42,80,255,237,91,82,255,167,237,82,0,56,1,201,42,84,255,35,35,35,0,0,62,13,35,190,32,252,35,34,84,255,70,35,78,237,67,80,255,24,215,0,0
9830 REM data B:
9835 DATA 42,84,255,35,35,35,35,126,254,250,40,63,254,235,40,59,254,243,40,55,254,237,40,51,254,254,40,47,254,236,40,43,254,226,40,39,35,126,254,13,40,10,254,58,40,216,254,34,40,15,24,240
9840 REM data S13:
9845 DATA 35,34,84,255,70,35,78,237,67,80,255,24,191
9850 REM data SEE:
9855 DATA 1,0,2,35,62,34,237,177,24,218
9860 REM data OUT:
9865 DATA 6,0,78,34,86,255,201,0,0,42,84,255,24,203,201,0,0
9870 CLS
9900 REM Flow Utility
9901 IF PEEK 65350<>201 THEN PRINT "Machine Code not Loaded..."''"GOTO 9800": STOP
9902 DIM s(50): LET s=0: LET ll=s
9905 PRINT "Basic Program Flow:";AT 0,0; OVER 1;"_________ _________"
9907 INPUT "Starting Line Number? ";l
9910 IF l>=9800 THEN GO TO 9907
9912 LET l2=INT (l/256): LET l1=l-l2*256
9913 POKE 65362,l1: POKE 65363,l2
9914 POKE 65364,86: POKE 65365,104: POKE 65360,PEEK 26711: POKE 65361,PEEK 26710
9915 RANDOMIZE USR 65218: LET a=PEEK 65364+256*PEEK 65365: LET l=PEEK (a+1)+256*PEEK a
9916 LET c=USR 65261
9917 LET l=PEEK 65360+256*PEEK 65361: IF l>=9800 THEN PRINT "END of BASIC": STOP
9918 LET a=PEEK 65366+256*PEEK 65367
9919 IF c=250 THEN GO TO 9950
9921 IF l=ll THEN PRINT ": ";
9922 IF l<>ll THEN PRINT : PRINT l;" ";: LET ll=l
9923 IF c=254 THEN PRINT "RETURN";: LET l=s(s): LET s=s-1: GO TO 9912
9925 IF c=235 OR c=243 THEN PRINT CHR$ c;: GO TO 9980
9927 IF c=226 THEN PRINT "STOP": STOP
9930 LET a=a+1: LET g=PEEK a
9933 IF g<>14 THEN GO TO 9930
9935 LET a=a+3: LET g=PEEK a+256*PEEK (a+1)
9937 IF c=237 THEN PRINT "GOSUB ";: LET l=l+1: LET s=s+1: LET s(s)=l
9940 IF c=236 THEN PRINT "GOTO ";
9945 PRINT g;: LET l=g: GO TO 9912
9950 LET a=a+1: IF PEEK a<>203 THEN GO TO 9950
9952 LET c=PEEK (a+1)
9954 IF l<>ll THEN PRINT : LET ll=l
9955 PRINT l;" IF...THEN ";CHR$ c;: IF c=236 OR c=237 THEN GO TO 9960
9957 LET a=a+1: GO TO 9980
9960 LET a=a+1: LET g=PEEK a
9962 IF g<>14 THEN GO TO 9960
9965 LET a=a+3: LET g=PEEK a+256*PEEK (a+1): PRINT g;: LET a=a+1: GO TO 9980
9980 LET l2=INT (a/256): LET l1=a-l2*256: POKE 65364,l1: POKE 65365,l2: LET c=USR 65345: GO TO 9917
9999 SAVE "tracer": LIST
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
