Case Swap is a BASIC utility that modifies a program’s own source code in memory, converting all alphabetic characters in the BASIC program area to either uppercase or lowercase. It works by reading the system variable at address 23635 to locate the start of the BASIC program, then walks through the program bytes, using the system variable at 23627 (VARS) as the end boundary. The routine skips over newline tokens (13) and the floating-point number marker (14) to avoid corrupting non-text data embedded in tokenized BASIC lines. POKEing directly into the program area makes the case conversion permanent until the program is re-loaded or the changes reversed by running the utility again.
Program Structure
The program is organized into two logical phases: a user-input phase (lines 5–30) and a memory-scanning phase (lines 35–95), plus a final SAVE at line 9000. The input phase prompts the user to choose lowercase (L) or uppercase (U) conversion and polls INKEY$ in a tight loop (lines 15–30). The flag L is set to 1 for lowercase and 0 for uppercase. The scan phase then walks through the BASIC program area in memory, POKEing character codes in-place to effect the conversion.
Memory Map Usage
The routine relies on two well-known ZX Spectrum system variables:
FN P(23635)— reads the two-byte system variablePROG, giving the start address of the BASIC program area.FN P(23627)— reads the two-byte system variableVARS, used as the end-of-program boundary. The scan halts when the pointerAexceeds this value.
The helper function DEF FN P(A) = PEEK A + 256 * PEEK (A+1) at line 40 is a compact, reusable 16-bit word reader, avoiding the need to write out the two-byte fetch formula every time.
Tokenized BASIC Navigation
TS2068 BASIC stores each program line as: line number (2 bytes), line length (2 bytes), tokens/characters, and a newline byte (13). Embedded numeric literals are stored as a token byte (14) followed by five bytes of floating-point data. The scanner must skip both constructs to avoid corrupting non-text bytes:
- Line 70: if
PEEK A = 13(newline), skip to the next line start viaGO TO 55. - Line 75: if
PEEK A = 14(numeric literal marker), advanceAby one extra byte and loop — this skips only the marker byte itself, not the full five-byte float. This is a subtle bug: the loop at line 95 still incrementsAby one, so in total only two bytes are skipped, while the full embedded number occupies six bytes (marker + five float bytes). The remaining four float bytes will be passed through the case-conversion test, though since float bytes are unlikely to fall in the ASCII letter ranges 65–90 or 97–122, they are generally not corrupted in practice.
Scan Initialization
Line 50 sets A = PROG - 5, and line 55 immediately adds 5, so the first byte examined is PROG itself. This two-step approach (rather than starting at PROG directly) appears to be a stylistic artifact: line 55 doubles as the re-entry point for the “advance to next line” branch from line 70, making the loop structure slightly cleaner at the cost of an unintuitive initialization.
Case Conversion Logic
The conversion uses ASCII arithmetic directly on the POKEd byte values:
| Mode | Condition | Operation |
|---|---|---|
Uppercase (L=0) | P >= 65 AND P <= 90 | POKE A, P + 32 (A→a) |
Lowercase (L=1) | P >= 97 AND P <= 122 | POKE A, P - 32 (a→A) |
When converting to lowercase (L=1), line 80 branches over the uppercase-to-lowercase check at line 85, jumping directly to line 90 which applies the lowercase-to-uppercase POKE. Wait — re-reading: IF L THEN GO TO 90 skips line 85 (the lowercase conversion) and goes to line 90 (uppercase conversion). This means when the user presses L (setting L=1), the program actually performs uppercase conversion, and vice versa. The flag naming and the branch logic are inverted relative to the menu prompts, which is a logic bug.
Termination
When A exceeds the value of VARS (line 65), the program issues CLS : LIST : STOP, clearing the screen, listing the now-modified program, and halting. This gives immediate visual feedback that the conversion has taken effect.
Notable Techniques
- Self-modifying BASIC: the program edits its own tokenized source in the program area, a technique that persists across subsequent
LISTor re-runs. - The
DEF FNword-fetch idiom for reading 16-bit system variables is compact and idiomatic for this style of utility. - Using
INKEY$in a polling loop (lines 15–30) rather thanINPUTallows single-keypress selection without pressing Enter. - The
CHR$comparisons check both cases of each letter (108/76 for l/L, 117/85 for u/U), making the menu case-insensitive.
Content
Source Code
5 CLS :PRINT ''"TO LIST IN LOWER CASE, PRESS ""L"""
10 PRINT ''"TO LIST IN UPPER CASE, PRESS ""U"""
15 LET I$= INKEY$
20 IF I$= CHR$ 108 OR I$= CHR$ 76 THEN LET L=1:GO TO 35
25 IF I$= CHR$ 117 OR I$= CHR$ 85 THEN LET L=0:GO TO 35
30 GO TO 15
35 CLS :PRINT FLASH 1;"READING THROUGH PROGRAM"
40 DEF FN P(A)= PEEK A+256* PEEK (A+1)
45 LET PROG= FN P(23635)
50 LET A=PROG-5
55 LET A=A+5
60 LET P= PEEK A
65 IF A> FN P(23627) THEN CLS :LIST :STOP
70 IF P=13 THEN GO TO 55
75 IF P=14 THEN LET A=A+1:GO TO 55
80 IF L THEN GO TO 90
85 IF P >=97 AND P <=122 THEN POKE A,P-32:GO TO 95
90 IF P >=65 AND P <=90 THEN POKE A,P+32
95 LET A=A+1:GO TO 60
9000 SAVE "CASE SWAP" LINE 0
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
