--- title: "Renum" id: 55718 type: "computer_media" slug: "renum" url: "http://localhost/computer_media/renum/" markdown_url: "http://localhost/computer_media/renum.md" published_at: "2024-07-05T00:18:47+00:00" modified_at: "2026-03-30T21:44:56+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A two-pass BASIC renumbering utility that patches both line headers and embedded floating-point GO TO targets directly in memory — no ROM calls needed." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Renumber" slug: "renumber" taxonomy: "genre" url: "http://localhost/type/renumber/" - name: "Utility" slug: "utility" taxonomy: "genre" url: "http://localhost/type/utility/" media_contents: - id: 55711 title: "ISTUG Public Domain Library 8" type: "computer_media" url: "http://localhost/computer_media/istug-public-domain-library-8/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Renum%20%28198x%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" media_type_tags: "Renumber, Utility" --- # Renum This program renumbers a BASIC program stored in memory, reassigning line numbers starting from 2000 in steps of 10. It works by scanning the program’s tokenized bytecode directly via PEEK, using two DEF FN helpers to read little-endian and big-endian 16-bit words from memory. The routine first performs a pass to collect all GO TO and GO SUB targets (token codes 236 and 237) along with their in-memory addresses into a string T$, encoding each reference as a 9-character record. A second pass then rewrites the line-number header bytes of each program line and patches the numeric literals embedded after GO TO/GO SUB keywords — including the five-byte floating-point representation — using a manual logarithm-based binary exponent calculation to reconstruct the Spectrum’s internal float format. *** ## Program Analysis ### Program Structure The program is divided into two major phases, both beginning at the PROGRAM_START pointer read from system variable address `23635` (PROG): 1. **Pass 1 (lines 9959–9975):** Walks every line of the BASIC program looking for GO TO (`236`) and GO SUB (`237`) tokens. For each one found, it records the in-memory address of the token and the target line number into the accumulator string `T$`, using 9-character fixed-width records. 2. **Pass 2 (lines 9976–9999):** Walks the program again. For each line below 9958, it rewrites the two-byte line-number header to the new sequential number starting at `BASE=2000` with step 10, and calls subroutine 9990 to patch any GO TO/GO SUB references that targeted the old line number. Line `9958` is a sentinel `STOP` that prevents the renumberer from processing itself; both passes terminate when they encounter a line number ≥ 9958. ### Memory Layout and DEF FN Helpers Two `DEF FN` functions abstract 16-bit memory reads: | Function | Definition | Purpose | | --- | --- | --- | | `FN A(X)` | `PEEK X + 256*PEEK(X+1)` | Little-endian 16-bit read (used for line length and PROG pointer) | | `FN B(S)` | `256*PEEK S + PEEK(S+1)` | Big-endian 16-bit read (used for line numbers in the program area) | BASIC line headers on the Spectrum are stored as: 2 bytes big-endian line number, 2 bytes little-endian line length, then the tokenized statement bytes. `FN B` reads the line number and `FN A` reads the length, allowing the walker to advance by `LENGTH+4` bytes per line. ### Pass 1: Collecting GO TO/GO SUB References Lines `9965`–`9968` scan each line’s token bytes. A line whose first token byte is `234` (the `REM` token) is skipped entirely, since a REM line’s content is not executable. For every other token equal to `236` (GO TO) or `237` (GO SUB), subroutine `9971` is called. Subroutine `9971`–`9975` checks that the number following the keyword has its five-byte float embedded inline (indicated by the number-marker byte `14` at offset `I+5`). If not, it prints a “NON-STANDARD COMMAND” error and halts. Otherwise it appends a 9-character record to `T$`: - Characters 1–5: `STR$ I` — the memory address of the token, left as a decimal string padded to 5 characters via `STR$`. - Characters 6–9: bytes at `I+1` through `I+4` encoded as `CHR$` — the four ASCII digit characters of the target line number as it appears in the source text. This means the “line number” stored in the record is the printed decimal digits of the target, not a binary integer — retrieved later with `VAL T$(I+5 TO I+8)`. ### Pass 2: Rewriting Line Headers and Patching References Pass 2 reuses the PROG pointer walk. For each line, the loop at `9980`–`9982` scans `T$` in steps of 9 to find any reference records whose target line number matches the current `LINE`. If found, subroutine `9990` patches the reference. The line-header rewrite at `9983`–`9984` stores the new `BASE` value as a big-endian 16-bit pair directly into the two header bytes at address `S`. ### Floating-Point Patching Subroutine (9990–9999) After a GO TO or GO SUB token, the Spectrum stores the target line number twice: once as ASCII digit characters and once as a five-byte internal float (preceded by the marker byte `14`). The subroutine must update both representations. Lines `9991` patches the four ASCII digit bytes using `CODE(STR$ BASE)(J)` — extracting the J-th character code of the decimal string of `BASE`. This works correctly only when `BASE` is exactly a 4-digit number; it would malfunction for 1-, 2-, 3-, or 5-digit targets, which is a known limitation given the fixed step-10 scheme starting at 2000. Lines `9993`–`9998` reconstruct the Spectrum’s five-byte float manually: - `BYTE1` = exponent byte: `128 + floor(log₂(BASE) + 1)` - `BYTE2` = the mantissa scaled to a 16-bit value - The high byte of the mantissa has its sign bit cleared by subtracting 128 (`INT(BYTE2/256)-128`), relying on the fact that the number is positive. - Bytes 4 and 5 of the float (the low mantissa bytes) are left at zero, which is valid for integers up to 16 bits since their contribution is negligible for line-number values. ### Notable Techniques and Idioms - Fixed-width string packing in `T$` (9 bytes per record) allows O(n) scanning with `STEP 9` and direct field extraction by substring offset. - The use of `VAL T$(I TO I+4)` to recover the stored memory address from its decimal string representation is a common Spectrum BASIC idiom for storing integers in strings without binary encoding. - All program lines from 9958 onward are excluded from renumbering, protecting the utility itself. - The manual float reconstruction avoids any ROM calls, operating purely in BASIC arithmetic. ### Bugs and Limitations - The ASCII digit patching at line `9991` assumes the new line number is always exactly 4 digits. Line numbers below 1000 or above 9999 would write incorrect bytes or cause an error. - The float reconstruction only fills 3 of the 5 float bytes; bytes at offsets `MEMORY+9` and `MEMORY+10` (the two lowest mantissa bytes) are never updated. For the range 2000–9950 this is harmless since those bits are zero, but it is architecturally incomplete. - Multi-statement lines containing more than one GO TO or GO SUB are handled correctly by the `FOR I` loop, but a line beginning with `REM` is skipped in its entirety, meaning a GO TO inside a multi-statement line that starts with REM would be missed. - The renumber step is hardcoded to 10 and the base to 2000; there are no input prompts to customize these values. ## Source Code ``` 9958 STOP 9959 LET T$="": LET X=23635 9960 DEF FN A(X)=PEEK X+256*PEEK (X+1) 9961 DEF FN B(S)=256*PEEK S+PEEK (S+1) 9962 LET S=FN A(X) 9963 LET LINE=FN B(S): IF LINE>=9958 THEN GO TO 9976 9964 LET LENGTH=FN A(S+2) 9965 IF PEEK (S+4)=234 THEN GO TO 9969 9966 FOR I=S+4 TO S+LENGTH+2 9967 IF PEEK I=236 OR PEEK I=237 THEN GO SUB 9971 9968 NEXT I 9969 LET S=S+LENGTH+4 9970 GO TO 9963 9971 IF PEEK (I+5)=14 THEN GO TO 9974 9972 PRINT "NON-STANDARD COMMAND, LINE ";LINE 9973 STOP 9974 LET T$=T$+STR$ I+CHR$ PEEK (I+1)+CHR$ PEEK (I+2)+CHR$ PEEK (I+3)+CHR$ PEEK (I+4) 9975 RETURN 9976 LET BASE=2000: LET X=23635 9977 LET S=FN A(X) 9978 LET LINE=FN B(S): IF LINE>=9958 THEN STOP 9979 LET LENGTH=FN A(S+2) 9980 FOR I=1 TO LEN T$ STEP 9 9981 IF VAL T$(I+5 TO I+8)=LINE THEN GO SUB 9990 9982 NEXT I 9983 POKE S,INT (BASE/256) 9984 POKE S+1,BASE-256*INT (BASE/256) 9985 LET BASE=BASE+10 9986 LET S=S+LENGTH+4 9987 GO TO 9978 9990 FOR J=1 TO 4 9991 POKE (VAL T$(I TO I+4)+J),CODE (STR$ BASE)(J) 9992 NEXT J 9993 LET BYTE1=128+INT (LN BASE/LN 2+1) 9994 LET BYTE2=BASE*65536/(2^(BYTE1-128)) 9995 LET MEMORY=VAL T$(I TO I+4) 9996 POKE MEMORY+6,BYTE1 9997 POKE MEMORY+7,INT (BYTE2/256)-128 9998 POKE MEMORY+8,BYTE2-256*INT (BYTE2/256) 9999 RETURN ```