In which I call for immediacy
Implement a way to compile immediate words.
http://beza1e1.tuxen.de/articles/forth.html
As far as I can tell, immediate words are words that are executed immediately, even in compile mode. An example of this is the ;
that ends a word definition.
There are two things needed:
- tell if a compiled word is immediate so we can execute it even when compiling
- mark a word definition as immediate when it is defined
The implementation was quite straightforward. I added a boolean property to CompiledWord
called isImmediate
. For the built in words, this property is hard coded – in any case, it’s false for all of them except .endCompile
. For defined words, the value is delegated to the associated WordList
which has a mutable property of the same name.
Implementation of immediate
then becomes simple. It just sets the property on the last compiled definition.
There was one wrinkle. I found that embedding a defined immediate word within a definition e.g.
: imm 5 . ; immediate
: foo imm 3 ;
didn’t work. The above snippet is meant to print 5, but it didn’t. Instead, things got weird and confused. The reason turned out to be that the when imm
was called in the middle of the definition of foo
, the Forth machine was still in compile mode. This meant that 5
and .
were jut added to the definition of foo
. The answer was to amend the code for executing a definition as follows:
case .definition(let words):
// Flush our cached stack top back to the stack
if let st = stackTop.pop()
{
dataStack.push(st)
}
let savedState = state.state
guard state.transition(to: .interpreting)
else { fatalError("Failed to transition: \(savedState) -> \(State.interpreting)") }
try interpret(words: words, output: &output)
let returnedState = state.state
guard state.transition(to: savedState)
else { throw Error.invalidReturnState("\(returnedState)") }
What does it do? Well, first it flushes the stack top back to the data stack. Then it saves the current machine state and transitions to .interpreting
. The first guard
is unnecessary, actually, because we can only be in the .compiling
or .interpreting
state when we hit this piece of code. Then it interprets the words of the definition and on return, it restores the old state.
To make it work, I had to modify my state machine to make it legal to transition from .interpreting
directly to .compiling
and also to .interpreting
.
The code is tagged blog-1045-1