ZX-FORTH is a FORTH programming language interpreter implemented in machine code and stored within a REM statement at line 65310. The program launches via RAND USR 17245, transferring execution directly to the machine code payload embedded in the REM statement’s data area. The REM statement contains the complete FORTH interpreter runtime, including dictionary entries, inner interpreter, and I/O primitives encoded as raw bytes. This technique of storing machine code in a high-numbered REM line keeps the BASIC loader compact while placing the binary payload at a predictable memory address.
Program Analysis
Program Structure
The program consists of only two active BASIC lines plus a large data-carrying REM statement:
- Line
10:SAVE "ZXFORTH"— saves the program to tape. - Line
20:RAND USR 17245— transfers execution to the machine code interpreter at address 17245 (decimal), which falls within the REM statement’s data area. - Line
65310: AREMstatement carrying the entire machine code binary of the FORTH interpreter as raw byte data.
Machine Code Delivery via REM
Storing machine code inside a REM statement is a classic technique. The BASIC line header for line 65310 begins at a known offset in RAM, and the bytes following the REM token are the raw Z80 opcodes and data of the FORTH system. The entry point at address 17245 (hex 0x435D) points into this payload. Because REM data is never parsed by the BASIC interpreter, arbitrary byte values — including those that would otherwise be interpreted as BASIC keywords — can be stored safely.
RAND USR Entry Point
RAND USR 17245 on line 20 is the standard idiom for invoking machine code from BASIC. RAND USR n calls the Z80 CALL to address n, and because the FORTH interpreter never returns to BASIC, the RAND return value is irrelevant. Address 17245 decimal is 0x435D, which lies in the region immediately after the system variables and in the program area, consistent with the REM data starting at line 65310‘s data bytes.
FORTH Interpreter Internals (Inferred from REM Data)
The byte content of the REM statement, when interpreted as Z80 machine code and FORTH dictionary data, implements a threaded FORTH interpreter. Visible ASCII fragments within the REM data correspond to FORTH word names embedded in dictionary headers. Identifiable word names and runtime tokens visible in the listing include:
- Standard FORTH words:
DUP,DROP,SWAP,OVER,ROT(inferred from stack-manipulation byte patterns) - BASIC keyword tokens appearing in the REM data (
PRINT,INPUT,LET,INKEY$,PEEK,POKE,USR,RUN,STOP,COPY,FAST,SLOW,PLOT,UNPLOT,CLS,LPRINT,PAUSE,GOSUB,FOR,NEXT,RETURN,NEW,DIM,CLEAR,RAND,SAVE) — these appear because the Z80 byte values of those opcodes coincide with the BASIC keyword token values, not because BASIC is executing them - Mathematical and string functions visible as tokens:
SIN,COS,TAN,ATN,ACS,ASN,LN,EXP,ABS,SGN,RND,PI,STR$,VAL,CHR$,CODE,LEN,TAB
The repeated appearance of RETURN tokens throughout the REM data is consistent with a direct-threaded or indirect-threaded FORTH inner interpreter using RET (opcode 0xC9) instructions — the Z80 RET opcode value happens to equal the BASIC RETURN token, causing the lister to display RETURN throughout the machine code body.
Notable Techniques
- High line number for REM: Line
65310places the REM at the very end of the BASIC program area, so the machine code does not interfere with normal BASIC line numbering and is unlikely to be accidentally renumbered or edited. - Two-line loader: The entire BASIC portion is just
SAVEandRAND USR, minimizing BASIC overhead and RAM consumed by the interpreter stub. - Block graphics and inverse characters in REM: The REM data contains block graphic characters and inverse-video characters, which are legitimate byte values within the Z80 machine code — they appear in the listing as graphical symbols but are raw data bytes to the CPU.
- FASTSTR$ and other compound tokens: Sequences like
FASTSTR$in the REM listing represent consecutive keyword tokens whose byte values happen to produce those token sequences when the lister renders them; they are not actual BASIC statements.
Potential Anomalies
The null bytes (displayed as 0 on lines by themselves near the top of the REM data) and the short numeric sequences (0 0) are legitimate parts of the binary payload — FORTH dictionary entries commonly contain null terminators and 16-bit address fields that render as low numeric values in the BASIC lister. These are not bugs.
Memory Map Summary
| Address (dec) | Content |
|---|---|
| 17245 | FORTH interpreter entry point (inside REM data) |
| BASIC line 65310 | REM statement containing full machine code payload |
| BASIC line 10 | SAVE loader |
| BASIC line 20 | RAND USR launch stub |
Content
Image Gallery
Source Code
10 SAVE "ZXFORT[H]"
20 RAND USR 17245
65310 REM ............................................................................................. FASTSTR$ VAL [R]ASN #INKEY$ RETURN█KM RETURN▒CZ RETURN?CV RETURN#S▝CHR$ 4 RETURNKS= RETURNUK▖CHR$ =/= RETURNINKEY$ S# RETURN#K#CHR$ ./▒5[-]INKEY$ #- ;# RETURN#CWACS [R] PRINT UTRND RETURN▘ATN #INKEY$ LET NOT AT SGN LPRINT TAN E:RNDFUTRNDWMTRND#6:RND RETURN#4(F6:RNDY▝MTRNDUURNDWMURNDQ ##INKEY$ FASTSTR$ VAL PRINT UURND RETURN▀C▌Y#NOT /▀LN :£ LET AT SGN LPRINT TAN CHR$ ~~/[3]CHR$ 8/[.]
0
0 ▘▝▀▖▌▞▛▒,,~~ [£]"[?]£$ [(]()*+,-./:;<=>?["][$] [:][>][)]VAL STR$ FAST▘ █UQ RETURN▜4,,LN M? CLEARQP COPY/▀LN P? GOSUB #9RNDLN [X]▛K LPRINT 5 UNPLOT INKEY$ ;###INKEY$ #########LMNOPKTSRQ#####$####4I##PIUVZJB#[-][*]#RND57[/][;][,][.][0][1][+]▒[2][=][>][<]6DC8[3][4]XFH[5] GYWESTR$ #Y#<= RETURN3S$Y RETURN<= RETURN3S▞Y▘[R]#SGN TAN [J]/ IF PRINT INKEY$ 7/ PLOT LET TAN FASTSTR$ VAL PRINT LN 4#)>#Y#<= RETURN *S PRINT .#[N]4 NEXT LET AT SGN LPRINT TAN FASTSTR$ VAL :▘▞ Y#<= RETURNPEEK COPY*S▖( PRINT / LET 2[=]▞,1<= RETURN*ACS ##S PRINT ( PRINT 4▖ RETURN#K>=ZACS )KNOT #/ACS FASTSTR$ VAL PRINT LN 4#)ACS ▒( RETURN.#[N]4 RAND /[R] FASTSTR$ VAL PRINT #RACS <C1[3] NEW▌LEN ▖#PEEK COPY▞7( RETURNLN CLSPI ▞2( RETURN$4 INPUT [R]( CLEAR/ TO #[▒]PIY#<= RETURN3 CLEARACS V▚Y COPYMBRNDTAN PRINT FASTSTR$ VAL UQ RETURN▜ PRINT ATN C? LET CODE F?AT SGN LPRINT LET TAN PRINT FASTSTR$ VAL UQ RETURN▜ PRINT ATN 4? LET CODE 7?AT SGN LPRINT LET TAN FAST PRINT 5##LN #PILN ABS INKEY$ RETURN$C▖ LET LPRINT RTAN LET LPRINT [R]TAN ##INKEY$ ##4#INKEY$ ######$ NEXT 5##7Q▘Y▘[Y]4▌[J]P[Y]C NEXT F6▄##*# #▝#▘▘▘:[O]#▒ 4# STOP#4#█#3 ######; LET [>]4#2#STR$ FAST CLEARO5TAB [F]#LN #PIC▀▘##~~▀#~~▀##7# FOR DIM ▄##USR [Y]#~~▀#~~▀#/CHR$ ▗######VAL [Q]#ABS # LPRINT />=▚PI#INKEY$ ##COS LEN # STEP ####7#F;##/[M]▗KPI#INKEY$ ##COS STR$ # RUN # LPRINT #[P]C SLOW▀▀/[1]▚C####[D] FOR #"#)▘ E[;]#[~~]#7=+# IF B##[-]#7[2]/▌ STEP #76[;]#▀▀#[1]#▗CF####[D] ###SGN /SIN ▜C##[D]S###E[;]#FFFF6[;]#SGN #7#SGN 7#7##[1]#▟TAN ####E[;]##7#STR$ / INPUT ▐####USR #### LPRINT SGN #CHR$ K IF [2]# RETURN~~ IF [<]#CHR$ ▛ RETURN~~ IF [2]#[X] PAUSE [2]##5▘ #[.]###[0]#▚C####[D]##[H]#SGN LPRINT FAST,[I] NEWZ437<,[I]▗4-K RUN 5▌ ; STOP.,[R] PAUSE CODE ##- 5▘ #[.]#S▞<,[R] PAUSE STR$ #< FOR #7##[N]4ASN LPRINT 5 #[0]#▗######VAL [6]# RUN #SGN LPRINT FAST#) COPY COPYF7<[Y]C CLSSTR$ PRINT STR$ .STR$ #[1]# LET PRINT 7<[Y]C~~STR$ #[1]# LET STR$ <STR$ #[1]#▜###USR FOR #Z#ASN #CODE #[6]#[9]#▄#▄## OR E####AT #[,,]Z######INKEY$ ATN X###5 # LPRINT #▙#ABS #####CHR$ #▐####VAL ######AT SGN STOP/▌[L]4 RUN AT /[8]▙#[E]##[~~]#SGN LPRINT VAL ##LN [A]# FAST###LN [A]#SGN #,,EXP ###AT STR$ #[0]#5 :▒D*K▀;EXP $4 PLOT TAN ▙#[J]▄#[X]#5▖ T##7##AT LPRINT #[)]#[/]S▛5 COPY COPY##/3Y(D* FOR DK▝<[R] FOR 3 PRINT K▌[R] GOSUB PI/▞ GOSUB PIK▝,,.< LET X4 STOPAT FASTSTR$ #[1]#▄INKEY$ #CODE [Q]#▝#SGN LPRINT #[9]##[8]##[0]#▙#ABS IF #=#SGN LPRINT #[P]##
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.