Skip to content

Instantly share code, notes, and snippets.

@JamesNewton
Last active September 20, 2025 04:10
Show Gist options
  • Select an option

  • Save JamesNewton/432dc2f45ae0c239b2d9fcbfe411c9f8 to your computer and use it in GitHub Desktop.

Select an option

Save JamesNewton/432dc2f45ae0c239b2d9fcbfe411c9f8 to your computer and use it in GitHub Desktop.
RET FORTH
;1000 years ago, when I was 17, I wrote a FORTH for the Z80 on a Trash 80.
;It was unique for the time, because it used the machine language RET as NEXT for asm words.
;e.g. the machine stack pointed NOT at the data stack or return stack but instead
; at the list of word addresses being executed aka the thread.
;That meant that asm words could be strung together even faster than a standard asm program
;because there was no asm CALL the RET was both the return from the current asm word AND
;the call to the next one.
;The address of the data and return stacks were kept elsewhere...
;one of the other registers or a memory location (I don't remember, code is long lost).
;I think it was IX and IY.
;Every non-asm word started with an asm call to a routine that saved SP to the return stack pointer,
;set the SP to the value on the stack, and then did a RET. If I remember, that's called THREAD?
;Then last thread address was to an asm word that pulled the return stack TOS back into SP and RET.
;I seem to remember that is called NEXT. So for asm words, NEXT is RET and for higher level words,
;NEXT was a few asm instructions.
;
;Advantages: /screaming/ fast low level words, not much slower high level words.
;
;Disadvantages:
;- low level words couldn't really use the stack, because they would overwrite a thread.
; Of course, you could save and restore SP...
; but I seem to remember doing most manipulation of the stack with other registers.
; e.g. LD (IX+0),B; LD (IX+1),C; INC IX; INC IX; Or maybe it was DEC, I don't remember.
;
;- NO INTERRUPTS during asm words! LOL. Can't save PC to TOS because it would over write a thread.
; I got around that somewhat by adding an EI (enable interrupts) in the NEXT code while the SP was
; pointed at the return stack, doing a NOP and then DI. LOOP also had that in it because it was asm.
; Just for the fun of it, I put this together from the original hello world at:
; https://8bitworkshop.com/v3.12.0/?platform=zx&file=hello.asm#
; https://gist.github.com/JamesNewton/432dc2f45ae0c239b2d9fcbfe411c9f8#file-hello-asm
CHAN_OPEN equ 5633
PRINT equ 8252
org 0x5ccb
call thread
;ld a, 2 ; 3E 02
;call CHAN_OPEN ; CD 01 16
;ld de, text ; 11 0E 7F
;ld bc, textend-text ; 01 0E 00
;call PRINT ; C3 3C 20
loop
jmp loop
;words. Better examples would be dup and all that rot
op1
ld a, 2
ret
LOAD_BCDE
pop bc
pop de
ret
LOAD_DATA
pop bc
PUSH_BC
inc ix
inc ix
ld (ix+0),b
ld (ix+1),c
ret
DUP
ld b,(ix+0)
ld c,(ix+1)
jmp PUSH_BC
DROP
dec ix
dec ix
ret
PLUS
ld b,(ix-2)
ld c,(ix-1)
ld h,(ix+0)
ld l,(ix+1)
add hl,bc
ld (ix-2),h
ld (ix-1),l
jmp DROP
;the thread
thread
ld ix,data
ld (return),sp ;save the stack
ld sp,start ;stack is now start
ret ;ret runs the words
start
dw LOAD_DATA
dw 1
dw LOAD_DATA
dw 2
dw PLUS
dw op1 ;a word is just the address of the asm
dw CHAN_OPEN ;which can be anything that doesn't push or pop
dw LOAD_BCDE
dw textend-text
dw text
dw PRINT
dw stop
stop
ld sp,(return)
ret
data
defb ' '
return
defb ' '
text defb 'Hello, world!' ; 48 65 6C 6C 6F 2C 20 57
; 6F 72 6C 64 21
defb 13 ; 0D
textend
org 0xff57
defb 00h
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment