Last time we got into a fight with Claude, but ended up being able to see the results of basic operations in our interactive AntForth interpreter.
What wasn't apparent from the demo I showed was that a stack underflow, or any other exception caused the interpreter to simply
Last time we got the outer interpreter running and we were able to type stuff into it, but we lacked the facility to display any results.
In this sprint, we'll be building:
* ., U. and .R to let us fetch and view values on the stack
* .S to let
OK, this is the big one! Story 2.2 will introduce the outer interpreter, which means that AntForth will be interactive for the first time! Probably won't be able to actually do much, but we will be able to interact!
In this sprint, we'll be building:
We're now starting Epic 2, which is all about getting us to a functional AntForth interpreter.
The first story (2.1) is about the dictionary and has table. The dictionary is where all our Forth word definitions are stored, so that they can be looked up when they&
On to the next BMAD task: 1.5 - console I/O primitives
In this sprint, we'll be building:
* output primitives EMIT, CR, SPACE and SPACES
* input primitives KEY and KEY?
These should all be trivial wrappers around BDOS calls to do the actual work. These functions will
On to the next BMAD task: 1.4 - arithmetic, logic and relational operators.
In this sprint, we'll be building:
* arithmetic operators: +, -, *, /, MOD, /MOD
* logical operators: AND, OR, XOR, INVERT, LSHIFT, RSHIFT
* relational operators: =, <, >, 0=, 0<, U<
That's a lot of
On to the next BMAD task: 1.3 - stack and memory primitives.
In this sprint, we will acquire the parameter stack manipulation primitives DUP, DROP, SWAP, OVER, ROT, PICK, ROLL, and DEPTH. We'll also get the return stack primitives >R and R> - PUSH and
On to the next BMAD task: 1.2 - inner interpreter and threading.
We /bmad-bmm-create-story 1-2 to create the next story that will guide Claude's development. We got story 1-2 for free last time as part of the initial planning process, but from here on in we generate
DTC-NEXT: LD A,(DE) (7) (IP)->W, increment IP
LD L,A (4)
INC DE (6)
LD A,(DE) (7)
LD H,A (4)
INC DE (6)
JP (HL) (4) jump to address in W
alternate version (same number of clock cycles):
DTC-NEXT: EX DE,HL (4) (IP)->W, increment IP
NEXT-HL: LD E,(HL) (7)
INC HL (6)
LD D,(HL) (7)
INC HL (6)
EX DE,HL (4)
JP (HL) (4) jump to address in W
We're loading the next "word" to be executed from where our "instruction pointer" (IP) is pointing, and we're executing it (by JumPing to it). The HL register does double duty here: it's the most flexible z80 register, and here we're using its ability to do memory load indirection and an indirect jump. Hence all the EXing so we're using HL in the right place at the right time.
In direct-threaded code the CFA contains either machine code (for primitives) or a JP DOCOL instruction (for colon words). Jumping through W means primitives run their own body directly.
The rest of macro.asm contains some interesting looking chicanery using sjasmplus' Lua interpreter. This is to allow us to use macros to define Forth words whilst using the fancy hash-based dictionary algorithm that we've chosen. It all looks terribly exciting, but I wouldn't be surprised if this is a source of friction later.
In inner_interpreter.asm is our definition of DOCOL:
; === DOCOL — Enter colon definition ===
; Push IP (DE) onto return stack (IX), set IP to body (following JP DOCOL)
DOCOL:
; Push current IP onto return stack
DEC IX
DEC IX
LD (IX+0), E
LD (IX+1), D
; W (HL) points to code field (the JP DOCOL), body = HL+3
INC HL
INC HL
INC HL
EX DE, HL ; DE = IP = body address
NEXT
We can compare this with the final DTC word ENTER in Moving Forth - part2. Straight away we can see we've got an issue: we EX DE,HL and then call NEXT, and the first thing NEXT does is EX DE, HL so we've got a NOP. This is what Brad is hinting with his NEXTHL alternate entrypoint, and we can ask Claude to make that change too.
First /bmad-agent-bmm-dev to start up a generic dev agent, then we ask him:
❯ in src/inner_interpreter.asm we have a DOCOL macro, this ends with `EX DE, HL: NEXT` and the first line of NEXT (defined in macros.asm) is another `EX DE, HL`. We should have another macro NEXTHL which is the last instructions of NEXT. Then words like DOCOL can skip the `EX DE, HL` and call HLNEXT directly. NEXT can also use NEXTHL.
Then /bmad-bmm-code-review and ask it to review the recent changes. Et voila:
Verdict: Changes are correct and beneficial. The NEXTHL optimization is sound — saves 8 T-states per DOCOL invocation with no behavioral change. No bugs found. Ready to commit.
Claude has made a basic project scaffold for us, but it's mostly stub files at this point. What we do have though, is some interesting stuff going on in macros.asm and inner_interpreter.asm.
Our register allocation is:
* HL = W (working register — points to the current word&