This program is a TS2068 variable area inspector that decodes and displays the contents of the system variable area starting at STKBOT (addresses 23627–23628). It walks through each variable record, identifying type by examining the high three bits of the first byte (dividing by 32), and dispatches to subroutines that handle single-letter numeric variables, multi-character numeric variable names, numeric arrays, FOR–NEXT loop control variables, strings, and string arrays. Numeric values are decoded from the Spectrum’s five-byte floating-point format in subroutine 800, correctly handling both the integer shortcut (byte 1 = 0) and full floating-point mantissa/exponent reconstruction. The array subroutine at line 900 reads dimension counts and element sizes, computing total element count as a product of all dimensions.
Program Analysis
Program Structure
The program is organized as a dispatcher loop anchored at line 20. It reads successive bytes from the variable area (whose start address is obtained via the system variables at addresses 23627–23628, pointing to VARS), then branches to one of six type-handling blocks based on the upper three bits of the first byte of each variable record.
| Lines | Role |
|---|---|
| 1–5 | Setup: screen colors, dimension working array b(5) |
| 10–70 | Main dispatch loop: read type byte, branch on p2=INT(p1/32) |
| 100–120 | Type 3 — single-letter numeric variable |
| 200–270 | Type 5 — multi-character numeric variable name |
| 300–330 | Type 4 — numeric array |
| 400–465 | Type 6 (bits 110) — FOR–NEXT loop control variable |
| 500–550 | Type 2 — string variable |
| 600–650 | Type 6 (bits 110, lower) — string array (dispatched as p2=6) |
| 800–875 | Subroutine: decode five-byte float/integer into n |
| 900–945 | Subroutine: parse array header (dimensions, total element count t) |
| 9998 | SAVE with auto-run |
Variable Area Traversal
The pointer p is initialized from the two-byte system variable VARS (23627 low byte, 23628 high byte). The main loop at line 20 peeks p into p1, checks for the sentinel value 128 (end-of-variables marker), and otherwise dispatches. Each type handler is responsible for advancing p past the record before looping back to line 20 via GO TO 20. The subroutine at line 800 always advances p by 5 bytes as a side effect, which is relied upon by all numeric decoding callers.
Type Detection
The Spectrum encodes variable type in the upper three bits of the first byte of each record. The program recovers this with LET p2=INT(p1/32), equivalent to a right-shift by 5 bits. The mapping is:
p2=3(bits 011x xxxx) — single-letter number, name in low 5 bitsp2=5(bits 101x xxxx) — multi-character number namep2=4(bits 100x xxxx) — numeric arrayp2=7(bits 111x xxxx) — FOR loop variablep2=2(bits 010x xxxx) — stringp2=6(bits 110x xxxx) — string array
Five-Byte Floating-Point Decoder (Lines 800–875)
The Spectrum stores numbers in a five-byte format: byte 1 is the exponent (biased by 128), bytes 2–5 are the mantissa with the sign in bit 7 of byte 2. The integer shortcut uses byte 1 = 0, with byte 2 acting as a sign byte (0 = positive, 255 = negative) and bytes 3–4 holding a 16-bit value.
- If
b(1)=0, use integer path:n=b(3)+256*b(4); ifb(2)=255, subtract 65536 for negative values. - Otherwise, record the sign from
b(2) >= 128, force bit 7 ofb(2)to 1 (the implicit leading 1 of the mantissa), accumulate mantissa asb(2)/1 + b(3)/256 + b(4)/65536 + b(5)/16777216(loop from j=2 to 5, dividing by256^(j-1)), then apply the exponent:n=n*2^(b(1)-128). - Negate if the sign flag
mwas set.
Note a subtle issue: the mantissa loop at line 855 uses b(j)/256^(j-1) starting at j=2, which gives b(2)/256 + b(3)/65536 + ... — this is off by a factor of 256 compared to the canonical reconstruction (which starts dividing from j=1 with b(2) as the most significant byte). This is compensated by the exponent term in line 865: the effective result is still mathematically correct because the exponent is adjusted implicitly by the off-by-one scaling. However, the reconstruction is non-standard and could accumulate floating-point rounding errors for large mantissa values.
Array Header Parser (Lines 900–945)
Called for both numeric and string arrays, this subroutine reads the two-byte length field at p+1, advances past it and the dimension count byte, then loops over each dimension, reading its two-byte element count. It multiplies all dimensions together into t (total elements) and prints each dimension size. The closing LET p=p-1 at line 940 backs up one byte so the caller’s subsequent increment keeps p aligned correctly to the first element.
String Array Handler (Lines 600–650)
The string array handler at line 600 is notably simplified: it prints only a single character per element (line 630: CHR$ PEEK p), suggesting it only handles arrays of single characters or is incomplete for multi-character string arrays. Full Spectrum string arrays store each element as a fixed-length string whose length equals the last dimension; this nuance is not reflected in the display logic.
Notable Bugs and Anomalies
- Line 550:
LET p=p+juses the final value ofjafter theNEXT jloop exits. In Spectrum BASIC,jafter the loop equalsl+1, so the pointer advances byl+1instead of the intendedl. This is a classic off-by-one error that will misalignpfor subsequent variables. - Line 60: Comment says “string aray” (typo for “array”).
- Line 450: FOR loop variable includes line number (2 bytes) and statement number (1 byte) stored after the three five-byte numeric fields; the pointer is advanced by 4 here but the three prior
GO SUB 800calls already moved it by 15, so the total per-record advance is correct. - Line 1:
INK 9selects “transparent ink” on the Spectrum, meaning character ink color inherits from the existing screen attribute — an unusual choice that relies on the display state at startup.
Key BASIC Idioms
- Using
PEEK 23627+256*PEEK 23628to read a two-byte little-endian system variable address is standard Spectrum BASIC practice. TABis used throughout for columnar alignment of output without needing a separate formatting routine.- The subroutine at line 800 advances the global pointer
pas a side effect, making it a stateful iterator rather than a pure function — a common pattern in memory-walking BASIC utilities.
Content
Source Code
1 PAPER 0: BORDER 0: INK 9
5 DIM b(5)
10 LET p=PEEK 23627+256*PEEK 23628
20 PRINT p;TAB 6;: LET p1=PEEK p
25 IF p1=128 THEN PRINT TAB 7;"END OF VARIABLE AREA": STOP
30 LET p2=INT (p1/32)
35 IF p2=3 THEN GO TO 100: REM number (single-letter name)
40 IF p2=5 THEN GO TO 200: REM number (multi-character name)
45 IF p2=4 THEN GO TO 300: REM number array
50 IF p2=7 THEN GO TO 400: REM loop control
55 IF p2=2 THEN GO TO 500: REM string
60 IF p2=6 THEN GO TO 600: REM string aray
70 STOP : REM error if this line is reached
100 REM number:-
105 PRINT CHR$ p1;
110 GO SUB 800
115 PRINT TAB 16;n
120 LET p=p+1: GO TO 20
200 PRINT CHR$ (p1-64);: REM first letter
210 LET p=p+1
220 LET p1=PEEK p
230 IF p1>=128 THEN GO TO 260
240 PRINT CHR$ p1;: REM middle letters (if any)
250 GO TO 210
260 PRINT CHR$ (p1-128);: REM last letter
270 GO TO 110
300 PRINT CHR$ (p1-32);"(";
305 GO SUB 900
310 FOR k=1 TO t
315 GO SUB 800
320 PRINT TAB 3;"element #";k;TAB 16;n
325 NEXT k
330 LET p=p+1: GO TO 20
400 PRINT CHR$ (p1-128);
410 GO SUB 800
420 PRINT TAB 9;"(LOOP)";TAB 16;n;TAB 22;"Current"
425 GO SUB 800
430 PRINT TAB 16;n;TAB 22;"Limit (TO)"
435 GO SUB 800
440 PRINT TAB 16;n;TAB 22;"STEP"
450 LET l=PEEK (p+1)+256*PEEK (p+2): LET s=PEEK (p+3): LET p=p+4
455 PRINT TAB 16;l;TAB 22;"Line No."
460 PRINT TAB 16;s;TAB 22;"Statement"
465 GO TO 20
500 PRINT CHR$ p1;"$";TAB 16;
510 LET l=PEEK (p+1)+256*PEEK (p+2): LET p=p+2
520 FOR j=1 TO l
530 PRINT CHR$ PEEK (p+j);
540 NEXT j: PRINT
550 LET p=p+j: GO TO 20
600 PRINT CHR$ (p1-128);"$(";
605 GO SUB 900
610 FOR k=1 TO t
620 LET p=p+1
630 PRINT TAB 3;"element #";k;TAB 16;CHR$ PEEK p
640 NEXT k
650 LET p=p+1: GO TO 20
800 REM integer
805 FOR j=1 TO 5
810 LET p=p+1: LET b(j)=PEEK p
815 NEXT j
820 IF b(1) THEN GO TO 845
825 LET n=b(3)+256*b(4): IF b(2)=255 THEN LET n=n-65536
830 RETURN
845 REM floating point
850 LET m=1: IF b(2)<128 THEN LET m=0: LET b(2)=b(2)+128
855 LET n=0: FOR j=2 TO 5: LET n=n+b(j)/256^(j-1): NEXT j
860 LET b(1)=b(1)-128
865 LET n=n*2^b(1)
870 IF m THEN LET n=-n
875 RETURN
900 REM arrays
905 LET p1=PEEK (p+1)+256*PEEK (p+2): LET p=p+3
910 LET d=PEEK p: LET p=p+1
915 LET t=1: FOR j=1 TO d
920 LET el=PEEK p+256*PEEK (p+1): LET t=t*el: PRINT el
925 IF d<>1 AND j<>d THEN PRINT ",";
930 LET p=p+2: NEXT j: PRINT ")"
940 LET p=p-1
945 RETURN
9998 SAVE "VARIABLE A" LINE 1
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
