I've decided that Claude and I need to have a conversation about the ABORT function, and why it doesn't make sense.
Q: What does the ABORT word do?
Claude: Looking at the code we wrote, ABORT is defined as a colon definition in primitives.s:
Honestly that stub is incomplete — it pushes PSP_INIT onto the stack but never actually resets the stack pointer. The ANS Forth standard definition of ABORT should:
1. Empty the parameter stack — reset PSP (X register) to its initial value
2. Empty the return stack — reset RSP (S register) to its initial value
3. Set STATE to 0 — drop back to interpret mode if compiling
4. Transfer control to QUIT — restart the outer interpreter loop
A correct implementation would be:
Code: Select all
HEADER "ABORT", ABORT_CFA, 0, BYE_CFA
CODEPTR ABORT_CODE
.proc ABORT_CODE
.a16
.i16
; Reset parameter stack
LDX #$03FF ; PSP_INIT
; Reset return stack
LDA #$01FF ; RSP_INIT
TAS
; Set STATE = 0 (interpret)
LDA UP
CLC
ADC #U_STATE
STA SCRATCH0
STZ (SCRATCH0)
; Jump to QUIT body directly
; (can't JSR — return stack was just wiped)
LDA #<QUIT_BODY ; Load address of QUIT's body
TAY ; IP = QUIT body
NEXT
.endproc
The key subtlety is that because ABORT wipes the return stack, it cannot use DOCOL/EXIT to call QUIT — there's no valid return address to push. Instead it directly loads QUIT_BODY into IP (Y) and dispatches with NEXT, effectively jumping into QUIT's body as if it were already executing.
A few things I find interesting about this:
1. It abandoned a colon definition for an assembly primitive.
2. Claude loves em dashes.
3. It's still using the invalid instruction stz (SCRATCH)
4. It's not using the ldy instruction!
I decided to ignore Claude and write the function as follows:
Code: Select all
;------------------------------------------------------------------------------
; ABORT ( -- ) The ANS Forth standard definition of ABORT should:
; 1. Empty the parameter stack — reset PSP (X register) to its initial value
; 2. Empty the return stack — reset RSP (S register) to its initial value
; 3. Set STATE to 0 — drop back to interpret mode if compiling
; 4. Transfer control to QUIT — restart the outer interpreter loop
;------------------------------------------------------------------------------
HEADER "ABORT", ABORT_CFA, 0, BYE_CFA
CODEPTR ABORT_CODE
.proc ABORT_CODE
ldx PSP_SAVE ; Reset parameter stack to entry point values
lda RSP_SAVE ; Reset return stack to entry point values
tas
lda UP
sta SCRATCH0
ldy #U_STATE
lda #0000
sta (SCRATCH0),Y ; Set STATE = 0 (interpret)
ldy #QUIT_BODY ; IP = QUIT body
NEXT
I have the corrected code to Claude and it
Claude: That's a clean solution — using Y as an offset into the user area via (SCRATCH0),Y indirect indexed addressing, which the 65816 fully supports. And reusing Y for the IP load right after is efficient since you're done with it as an index at that point. Let me find all the STZ ( occurrences in the files and fix them all consistently.
Perhaps I should have asked Claude to fix its other errors.