Internals - vroby65/MiniForthConsole GitHub Wiki
This document explains how the Mini Forth Console works under the hood โ its architecture, interpreter design, and core components.
The console is entirely client-side and written in vanilla HTML, CSS, and JavaScript.
-
HTML: Provides the structure (a
<textarea>
for input/output) - CSS: Dark-theme terminal aesthetic, responsive
- JavaScript: Implements the full Forth-like interpreter
No frameworks or external dependencies are used (except for the Fira Code font).
A single JavaScript array called stack
simulates the Forth data stack.
let stack = [];
All operations manipulate this array.
There are two dictionaries:
-
words
: built-in words (native JavaScript functions) -
userWords
: user-defined words (stored as token arrays)
const words = { '+': () => { ... }, 'dup': () => { ... } }
const userWords = { 'square': ['dup', '*'] }
Words are case-insensitive, except for quoted strings.
Variables are stored in a dictionary:
const variables = {
x: 42,
myarr: [1, 2, 3]
}
Array access uses @
/ !
with index:
0 myarr @ .
123 1 myarr !
The function interpretLine(line)
parses the input and executes it token by token.
- Tokens are split using a regex that preserves quoted strings
- Each token is:
- a number โ pushed on the stack
- a string literal โ pushed as-is
- a built-in word โ executed
- a user word โ expanded and re-interpreted
- unknown โ pushed as-is (assumed variable name or JS string)
Words are defined using :
and ;
. Tokens between them are collected:
: square dup * ;
This is stored as:
userWords['square'] = ['dup', '*']
-
if ... else ... then
is interpreted inline, collecting branches -
do ... loop
andfor ... next
are implemented with actual JavaScript loops -
leave
throws an exception to exit loops early
Nested control flow is handled by re-invoking interpretLine
with the selected branch tokens.
Words and variables are saved as JSON in:
-
localStorage
(auto-loaded on page refresh) - downloadable file (
save
) - restorable via file upload (
load
)
-
.*
โ loop: executes the following tokens until the stack is empty -
$
โ capture output instead of printing it (useful for pushing back to stack) -
js
โ executes raw JavaScript in browser context viaeval()
- A
<textarea>
is both the display and input - Input is processed on
keydown
(Enter/ArrowUp/ArrowDown) - Command history is managed manually in a
commandHistory[]
array
3 4 + .
-
3
โ number โ pushed โ[3]
-
4
โ number โ pushed โ[3, 4]
-
+
โ word โ popped 4,3 โ pushed 7 โ[7]
-
.
โ word โ popped 7 โ printedโ 7 OK
The js
command uses eval()
and runs in the page context. It has full access to the DOM and JS environment. Use it carefully.
You can add new words by extending the words
dictionary:
words['square'] = () => {
const x = stack.pop();
stack.push(x * x);
};
Or add user words with:
: square dup * ;
- Use
.$
and.v
to inspect state - Use
clr
to clear the screen - Use
reset
before testing new definitions
- Fully client-side, no build step
- Self-contained interpreter
- Persistent and extensible
- Ideal for experimentation and education
Feel free to modify the source and customize commands or UI to suit your needs!