--- title: "Reader 2" id: 71241 type: "computer_media" slug: "reader-2" url: "http://localhost/computer_media/reader-2/" markdown_url: "http://localhost/computer_media/reader-2.md" published_at: "2026-08-30T20:01:28+00:00" modified_at: "2026-08-30T20:01:29+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/08/reader-2.png" alt: "Reader 1 screen" excerpt: "Peek behind the curtain of BASIC itself — this memory walker decodes your program's raw bytes, revealing line numbers, lengths, tokens, and hidden five-byte number constants." 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: "Programming" slug: "programming" taxonomy: "genre" url: "http://localhost/type/programming/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Reader%202%20%28198x%29%28-%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2026/08/reader-2.png" alt: "Reader 1 screen" media_type_tags: "Programming" --- # Reader 2 Reader 2 walks the BASIC program area in memory and displays a byte-by-byte listing of the stored program, annotating each byte’s address, raw value, and decoded meaning. It reads the PROG pointer from system variable address 23635/23636 and the VARS pointer from 23627/23628 to determine the start and end of the program area. The program handles the five-byte floating-point number format (token 14) as a special case, printing descriptive labels sourced from a DATA statement. Line number and line length fields are decoded separately, with unprintable token bytes flagged explicitly. *** ### Program Structure The program is organized into a main loop (lines 10–75), a shared print subroutine at line 100, and a DATA block at line 200. Execution begins by capturing two system-variable pointers, then enters a sequential decode loop that never needs a line index — it simply advances the pointer variable `p` byte by byte through the BASIC area. 1. **Lines 10–15**: Initialize `p` (PROG, start of BASIC) and `v` (VARS, end of BASIC) from system variables. 2. **Lines 20–35**: Decode a two-byte line number and a two-byte line-length field, printing address, raw value, and a label via the shared subroutine. 3. **Line 40**: Compute the end address of the current line (`le = p + le`). 4. **Lines 45–75**: Main token loop — read one byte, branch on token value, advance `p`, and loop back. 5. **Line 100**: Shared display subroutine — prints address, PEEK value, and positions the cursor for the caller’s annotation. 6. **Line 200**: DATA strings labeling the five bytes of the embedded floating-point number format. ### System Variable Usage | Address | System Variable | Role in Program | | --- | --- | --- | | 23635/23636 | PROG | Start address of the BASIC program area; initial value of `p` | | 23627/23628 | VARS | Start of the variables area; signals end of BASIC when `p` reaches it | ### Line Header Decoding The TS2068 stores each BASIC line as: two-byte line number (high byte first), two-byte length (low byte first), then the tokenized statement bytes. The program reads the line number at line 20 as `256 * PEEK p + PEEK (p+1)` (big-endian) and the length at line 30 as `PEEK p + 256 * PEEK (p+1)` (little-endian), correctly reflecting the actual on-disk format. ### Token 14 — Embedded Floating-Point Numbers Token value 14 (decimal) is the marker for an in-line five-byte binary floating-point constant that follows every numeric literal in tokenized BASIC. Lines 50 handles this case: it issues a `RESTORE` and then reads five labels from the DATA statement at line 200 (`"FIVE"`, `"BYTE"`, `"EQUIV."`, `"OF"`, `"NUMBER"`), one per byte, printing them as annotations. This makes the otherwise opaque five bytes human-readable in the output. ### Unprintable Token Handling Line 55 checks whether the current byte is a control or keyword token in the range 0–15 or 24–255 — specifically, tokens 16–23 are color/attribute control codes that would corrupt the display if PRINTed directly. Any byte outside the range 16–23 is printed as `CHR$ o`; bytes within that range trigger the `"(unprintable)"` label at line 60. ### Shared Subroutine and Output Layout The subroutine at line 100 uses `TAB` stops to create a three-column layout: address at column 0, raw PEEK value at column 8, and the caller-supplied annotation starting at column 16. The trailing colon after `TAB 16;` and the `RETURN` leave the print position at column 16 so the calling line can immediately append its own text without an extra PRINT statement. ### Loop Termination and Line Boundary Detection Two checks guard the inner byte loop: at line 65, if `p` equals `v` (the VARS pointer), execution halts with `STOP`; at line 70, if `p` equals the precomputed line-end address `le`, control jumps back to line 20 to decode the next line header. This avoids any separate counter for bytes-remaining within a line. ### Bugs and Anomalies - **Line length decoded before pointer advance**: At line 30, `le` is read from the current `p`, which still points at the first byte of the length field. Line 40 then sets `le = p + le` using the *pre-advance* value of `p`. By the time the inner loop begins, `p` has been incremented twice more (once per length byte in line 35), so `le` correctly points to the first byte past the end of the statement bytes — this is correct but relies on those two increments happening exactly. ## Source Code ``` 10 LET p= PEEK 23635+256* PEEK 23636 15 LET v= PEEK 23627+256* PEEK 23628 20 LET n=256* PEEK p+ PEEK (p+1) 25 GO SUB 100:PRINT n; TAB 21;"( LINE ":LET p=p+1:GO SUB 100:PRINT TAB 21;"( NUMBER":LET P=P+1 30 LET le= PEEK p+256* PEEK (p+1) 35 GO SUB 100:PRINT le; TAB 21;"( LINE ":LET p=p+1:GO SUB 100:PRINT TAB 21;"( LENGTH":LET P=P+1 40 LET le=p+le 45 LET o= PEEK p 50 GO SUB 100:IF o=14 THEN RESTORE :PRINT TAB 21;"NUMBER:":FOR j=1 TO 5:LET p=p+1:GO SUB 100:READ n$:PRINT TAB 21;n$:NEXT j:GO TO 65 55 IF o<16 OR o>23 THEN PRINT TAB 16; CHR$ o:GO TO 65 60 PRINT TAB 16;"(unprintable)" 65 LET p=p+1:IF p=v THEN STOP 70 IF p=le THEN GO TO 20 75 GO TO 45 100 PRINT p; TAB 8; PEEK p; TAB 16;:RETURN 200 DATA "FIVE","BYTE","EQUIV.","OF","NUMBER" 9000 SAVE "READER 2" LINE 0 ```