Variable A

Date: 198x
Type: Program
Platform(s): TS 2068

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.

LinesRole
1–5Setup: screen colors, dimension working array b(5)
10–70Main dispatch loop: read type byte, branch on p2=INT(p1/32)
100–120Type 3 — single-letter numeric variable
200–270Type 5 — multi-character numeric variable name
300–330Type 4 — numeric array
400–465Type 6 (bits 110) — FOR–NEXT loop control variable
500–550Type 2 — string variable
600–650Type 6 (bits 110, lower) — string array (dispatched as p2=6)
800–875Subroutine: decode five-byte float/integer into n
900–945Subroutine: parse array header (dimensions, total element count t)
9998SAVE 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 bits
  • p2=5 (bits 101x xxxx) — multi-character number name
  • p2=4 (bits 100x xxxx) — numeric array
  • p2=7 (bits 111x xxxx) — FOR loop variable
  • p2=2 (bits 010x xxxx) — string
  • p2=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.

  1. If b(1)=0, use integer path: n=b(3)+256*b(4); if b(2)=255, subtract 65536 for negative values.
  2. Otherwise, record the sign from b(2) >= 128, force bit 7 of b(2) to 1 (the implicit leading 1 of the mantissa), accumulate mantissa as b(2)/1 + b(3)/256 + b(4)/65536 + b(5)/16777216 (loop from j=2 to 5, dividing by 256^(j-1)), then apply the exponent: n=n*2^(b(1)-128).
  3. Negate if the sign flag m was 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+j uses the final value of j after the NEXT j loop exits. In Spectrum BASIC, j after the loop equals l+1, so the pointer advances by l+1 instead of the intended l. This is a classic off-by-one error that will misalign p for 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 800 calls already moved it by 15, so the total per-record advance is correct.
  • Line 1: INK 9 selects “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 23628 to read a two-byte little-endian system variable address is standard Spectrum BASIC practice.
  • TAB is used throughout for columnar alignment of output without needing a separate formatting routine.
  • The subroutine at line 800 advances the global pointer p as a side effect, making it a stateful iterator rather than a pure function — a common pattern in memory-walking BASIC utilities.

Content

Related Products

Related Articles

Related Content

Image Gallery

Variable A

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.

People

No people associated with this content.

Scroll to Top