___________ ____ ________ _______ __
/ ____/ __ \/ __ \/_ __/ / / / ____/ |/ /
/ /_ / / / / /_/ / / / / /_/ / __/ | /
/ __/ / /_/ / _, _/ / / / __ / /___ / |
/_/ \____/_/ |_| /_/ /_/ /_/_____//_/|_|
A modern, AST-based Forth interpreter built from scratch in Elixir.
Try it directly in your browser: forthex.fly.dev (Snake controls: WASD)
Forth is a stack-based, procedural programming language using Reverse Polish Notation (RPN).
This began as a university project and grew into a deeper engineering challenge: building a dual-stack VM with simulated memory, a proper AST parser, and clear separation of concerns, all backed by 100% test coverage.
Note: loops in web REPL are intentionally slowed down. For maximum speed use Docker version.
git clone https://github.com/arturz/forthex.git
cd forthex
docker build -t forthex .
docker run -it forthexClick to expand instructions
Prerequisites: Elixir 1.16+ installed.
# Fetch dependencies
mix deps.get
# Start the REPL
mix forthex
# Run a Forth script
mix forthex forth_scripts/calculator.forth
# Run Snake (requires iex for raw terminal mode)
iex -S mix forthex snake| ๐ Snake | Terminal game with WASD controls, collision detection, scoring. |
| ๐ Mandelbrot | Interactive colorful fractal with navigation controls. |
| ๐งฎ Calculator | Interactive calculator with menu UI. |
New to Forth? It's easier than it looks! Forth uses a stack for everything. Imagine a stack of plates: you put numbers on top, and operations (like + or .) take them off.
1. Basic math
Type numbers to push them. Type . to print the top number.
1 2 + .(push 1, push 2, add them, print result -> 3)
2. Stack manipulation
Words operate on the stack.
DUP: Duplicate top number (1->1 1)SWAP: Swap top two numbers (1 2->2 1)DROP: Discard top number
5 DUP * .(5 -> 5 5 -> 25 -> print 25)
3. Defining words
Extend the language by defining new words with : name ... ;.
: SQUARE
DUP * ;
5 SQUARE .(define SQUARE, then use it to square 5)
Tip
The semicolon ; is a token and must be preceded by a space (e.g. : FOO ... ;).
4. Loops
Loops use DO ... LOOP. The limit is exclusive, start is inclusive.
: COUNT-DOWN
11 1 DO
I .
LOOP ;
COUNT-DOWN(define loop word, then run it to print 1 to 10)
graph LR
SC[Source code] --> L[Lexer]
L --> T[Tokens]
T --> P[Parser]
P --> A[AST]
A --> I[Interpreter]
I --> R[Result]
subgraph State [Interpreter state]
I -.-> S[Stack]
I -.-> RS[Return stack]
I -.-> H[Heap]
I -.-> D[Dictionary]
end
| Module | Purpose |
|---|---|
Forthex.Lexer |
Tokenizes source into typed tokens (:word_opening, :if, :call_or_literal, etc.) |
Forthex.Parser |
Recursive descent parser building AST nodes (IfExpression, DoLoop, WordDefinition) |
Forthex.Interpreter |
Stack-based VM with pattern matching on AST nodes |
Forthex.Interpreter.State |
Holds data stack, return stack, dictionary, and heap |
Forthex.Interpreter.Heap |
Simulated memory with address allocation and bounds checking (implemented as an immutable map simulating mutable state) |
Forthex.Interpreter.Dictionary |
Maps word names to either native Elixir functions or user-defined ASTs |
- Data stack - Primary operand stack for computations
- Return stack - Used for loop indices (
I,J) and control flow
lib/forthex/
โโโ lexer.ex # Tokenization
โโโ parser.ex # AST construction
โโโ interpreter.ex # VM execution
โโโ runner.ex # Pipeline orchestration
โโโ ast/ # AST node definitions
โ โโโ if_expression.ex
โ โโโ do_loop.ex
โ โโโ word_definition.ex
โ โโโ ...
โโโ interpreter/
โโโ state.ex # VM state container
โโโ heap.ex # Memory simulation
โโโ dictionary.ex # Word lookup
โโโ dictionary/
โโโ stack_words.ex
โโโ math_words.ex
โโโ io_words.ex
โโโ ...
forth_scripts/ # Example programs
โโโ snake.forth
โโโ mandelbrot.forth
โโโ calculator.forth
DUP DROP SWAP OVER ROT -ROT 2DUP 2DROP 2SWAP 2OVER CLEAR . .S
+ - * / MOD ABS 1-
< > = <> <= >= AND OR NOT 0= TRUE FALSE WITHIN
( inline comment )
\ line commentIF ... THEN
IF ... ELSE ... THEN
DO ... LOOP
?DO ... LOOP ( safe loop - skips if range empty )
BEGIN ... UNTILLoop indices: I (current), J (outer loop)
VARIABLE name ( declare variable )
CREATE name N ALLOT ( allocate N cells )
! ( store: value addr -- )
@ ( fetch: addr -- value )
+! ( add to: n addr -- )
2! 2@ ( store/fetch pairs )
CELLS ( convert to cell units )
HERE ( next free address )
FILL ( fill memory: addr u char -- )
ERASE ( zero memory: addr u -- ). ( print number )
." text" ( print string )
EMIT ( print char by ASCII code )
CR ( newline )
SPACE ( print single space )
SPACES ( print N spaces )
KEY ( blocking read key )
KEY? ( non-blocking key check )
ACCEPT ( read line as number )
PAGE ( clear screen )
AT-XY ( move cursor: x y -- )
MS ( sleep milliseconds )
RESET-TERMINAL ( exit raw (non-canonical) terminal mode )
BANNER ( show startup banner ): name ... ; ( define new word )
INCLUDE file.forth ( load external file )
['] word ( get execution token )
EXECUTE ( run execution token )
PERFORM ( @ EXECUTE )
WORDS ( list all words )
HELP word ( show word info )
RANDOM ( from to -- n )
BYE / EXIT ( quit interpreter )SNAKE ( run snake game )
CALCULATOR ( run calculator )
MANDELBROT ( run mandelbrot )CREATE scores 10 CELLS ALLOT
: INIT-SCORES ( -- )
10 0 DO
I 10 * I CELLS scores + !
LOOP ;
: PRINT-SCORES ( -- )
10 0 DO
I CELLS scores + @ .
LOOP ;
INIT-SCORES PRINT-SCORES ( prints 0 10 20 30 40 50 60 70 80 90 )Unlike classic Forth, this interpreter separates parsing and execution: parsing builds an AST, and word calls and literals are resolved at runtime.
Distributed under the MIT License. See LICENSE for more information.

