A Forth interpreter that runs in the browser. Supports word definitions, arithmetic, stack operations, memory access, and can be embedded directly in HTML via <script type="application/forth"> tags.
For quick demo, you can find the latest version here: https://ccurl.github.io/JS-Forth
- Copy the 2 files: 'index.html' and 'jsforth.js'.
- Paste them into a folder of your choice.
- Open the 'index.html' file in a browser of your choice.
- NOTE: there is another file (doom-o.js) that demonstrates how to use ray-casting
- Have fun!
This is a complete Forth virtual machine implemented in ~300 lines of JavaScript. It features:
- Data stack and return stack with separate memory regions
- Compiled word definitions stored in shared memory
- Immediate words (executed during compilation)
- Dictionary of primitives and user-defined words
- Efficient string parsing with single-pass tokenization
- Automatic execution of embedded Forth scripts on page load
- Support for defining new words with
:and;
- Memory (
mem): Unified array holding stacks, dictionary, and compiled code- Data Stack: base 0, grows upward (0-59)
- Return Stack: base 60, grows upward (60-119)
- Temp Stack: base 100, grows upward (100-169)
- Loop Stack: base 170, grows upward (170-199)
- BASE is at address 200
- STATE is at address 201
- Addresses 202-255 are free
- HERE (Compiled Code) starts at address 256
- Dictionary (
dictionary[]): Array of word definitions with:- len: length of the word's name
- name: name of the word
- xt: execution token (function for prims, index for colon-words)
- imm: immediate flag
- Input Buffer (
tib,toIn,tibLen): Tokenization state - Program Counter (
pc): Current instruction pointer during execution - Opcode (
op): Current instruction during execution - Compilation Flag (
state): Tracks execution mode (0 = immediate/execute, 1 = compile)
-
Outer Interpreter (
outer()) - Parses tokens from input string- Attempts to parse as number, colon definition, semicolon, or word lookup
- Throws error on unknown word
-
Inner Interpreter (
inner()) - Executes compiled code- Fetches and executes opcodes from memory
- Supports both primitive functions and compiled word addresses
- Manages return stack for nested word calls
- Supports tail-call optimization
-
Word Types
- Immediate: Executed during compilation (e.g.,
:,;) - Primitive: Native functions that manipulate the stack
- Compiled: User-defined sequences of words compiled to memory
- Immediate: Executed during compilation (e.g.,
(skips until the next word that is):begins a word definition, compiling subsequent words to memory;ends compilation and appendsexittoken- Numbers are compiled as
lit(literal) followed by the value - Word references are compiled as their execution token (address or function)
- When compiling,
Comma()stores values athere. Thenhereis incremented.
Blocks are stored in a JavaScript array (blocks[]) and accessed via:
n list- Display block n in the UI textarean load- Load/Execute block n- Blocks are stored in the jsforth.js file - edit them there
The interpreter can update HTML elements dynamically:
html!primitive takes a value and element ID from the stack- Elements are selected by
idattribute - Updates set the element's
textContentproperty - Useful for displaying computed results, version info, block numbers, etc.
version s" hdr" html! \ Update element id="hdr"
5 s" block-num" html! \ Display block numberIn your HTML, add corresponding id attributes:
<h1 id="hdr">??</h1>
<span>Block <pre id="blk-num">??</pre></span>The Forth code then updates these elements when executed.
Embed Forth code directly in HTML using type="application/forth":
<script type="application/forth">
." hello world!"
</script>
<script type="application/forth" src="program.fth"></script>- Inline scripts execute their
innerText - External scripts are fetched via
fetch()and executed - Auto-execution happens on window
loadevent - Browser ignores
application/forthMIME type, preventing unwanted parsing
Call the interpreter directly from JavaScript:
// Single execution (captures output via console.log override)
runForth('5 3 + .'); // Outputs: 8
// Direct parsing
outer('10 2 / .'); // Outputs: 5- Stack effects use
( in--out )to separate inputs from outputs. - True comparison results are
-1; false results are0. - Numeric values can be either integer or floating point unless specified.
| Name | Stack | Description |
|---|---|---|
| dup | ( n--n n ) | Duplicate TOS. |
| drop | ( n-- ) | Remove TOS. |
| swap | ( a b--b a ) | Exchange the top two items. |
| over | ( a b--a b a ) | Copy NOS to the top. |
| .s | ( -- ) | Display the data stack without removing items. |
| + | ( a b--result ) | Add two values. |
| - | ( a b--result ) | Subtract TOS value from NOS. |
| * | ( a b--result ) | Multiply two values. |
| / | ( a b--result ) | Division (float, not truncated). |
| negate | ( n---n ) | Negate TOS. |
| /mod | ( a b--quot rem ) | Integer divide and modulo. |
| mod/ | ( a b--rem quot ) | Integer modulo and divide. |
| < | ( a b--flag ) | Compare whether the NOS is less than TOS. |
| = | ( a b--flag ) | Compare whether NOS and TOS are equal. |
| > | ( a b--flag ) | Compare whether NOS is greater than TOS. |
| 0= | ( n--flag ) | Test whether TOS is zero. |
| 1+ | ( n--n+1 ) | Increment TOS. |
| 1- | ( n--n-1 ) | Decrement NOS. |
| and | ( a b--result ) | Bitwise AND of NOS and TOS. |
| or | ( a b--result ) | Bitwise OR of NOS and TOS. |
| xor | ( a b--result ) | Bitwise XOR of NOS and TOS. |
| com | ( n--result ) | Bitwise complement of TOS. |
| @ | ( addr--value ) | Fetch a memory cell. |
| ! | ( value addr-- ) | Store a value at an address. |
| , | ( n-- ) | Compile a value at the current here address. |
| here | ( --addr ) | Push the current compilation address. |
| allot | ( n-- ) | Allocate memory by incrementing here by n. |
| . | ( n-- ) | Print TOS; numeric values are followed by a space. |
| .NWB | ( n width base-- ) | Print n in base, padded to width digits with leading zeros. |
| type | ( str-- ) | Print and remove a string. |
| emit | ( char-code-- ) | Print the character for a character code. |
| cr | ( -- ) | Print a newline. |
| html! | ( value id-str-- ) | Set an element's textContent. |
| s" | ( -- ) | Compile or push a string. |
| ." | ( -- ) | Compile or print a string. |
| if ... then | ( flag-- ) | Execute the body when the flag is nonzero. |
| begin ... until | ( flag-- ) | Jump to begin while the flag is zero. |
| begin ... while | ( flag-- ) | Jump to begin when the flag is nonzero. |
| begin ... again | ( -- ) | Unconditionally jump back to begin. |
| for ... next | ( n-- ) | Run the body the specified number of times. |
| i | ( --index ) | Push the current loop index, starting at zero. |
| unloop | ( -- ) | Reset to the previous loop context. Does NOT exit the loop. |
| exit | ( -- ) | Exit from the current word. |
| words | ( -- ) | Print the defined word names. |
| see | ( -- ) | Read the next input word and print its definition. |
| version | ( --str ) | Push the current JS-Forth version string. |
| var | ( -- ) | Define the next word as a variable. |
| const | ( n-- ) | Define the next word as a constant for n. |
| immediate | ( -- ) | Mark the most recently defined word as immediate. |
| load | ( block-num-- ) | Execute a stored block. |
| list | ( block-num-- ) | Display a stored block in the UI textarea. |
| a@ | ( --val ) | Push the value of variable a. |
| b@ | ( --val ) | Push the value of variable b. |
| a! | ( val-- ) | Store the TOS in variable a. |
| b! | ( val-- ) | Store the TOS in variable b. |
| >a | ( val-- ) | Save a and set a to val. |
| >b | ( val-- ) | Save b and set b to val. |
| <a | ( -- ) | Restore variable a's previous value. |
| <b | ( -- ) | Restore variable b's previous value. |
| >t | ( val-- ) | Move the TOS to the temporary stack. |
| t@ | ( --val ) | Copy the temporary stack's TOS to the data stack. |
| t! | ( val-- ) | Set the temporary stack's TOS to val. |
| t> | ( --val ) | Pop the temporary stack onto the data stack. |
| <t | ( -- ) | Pop and discard from the temporary stack. |
| timer | ( --time ) | Push the current high-precision time. |
| sin | ( radians--result ) | Push the sine of an angle in radians. |
| cos | ( radians--result ) | Push the cosine of an angle in radians. |
| tan | ( radians--result ) | Push the tangent of an angle in radians. |
| atan | ( n--radians ) | Push the arctangent of a value in radians. |
| floor | ( n--result ) | Round TOS down to the nearest integer. |
| sqrt | ( n--result ) | Push the square root of TOS. |
| base | ( --addr ) | Get the address of the base variabe. |
| state | ( --addr ) | Get the address of the state variable. |
| ttl-fw | ( distance-- ) | Move forward, drawing when the pen is down. |
| ttl-rt | ( angle-- ) | Turn right by an angle in degrees. |
| ttl-lt | ( angle-- ) | Turn left by an angle in degrees. |
| pen-up | ( -- ) | Lift the pen off the writing surface. |
| pen-down | ( -- ) | Lower the pen onto the writing surface. |
| pen-color | ( r g b-- ) | Set the pen (stroke) color. |
| ttl-goto | ( x y-- ) | Move to X/Y, drawing if the pen is down. |
| ttl-square | ( size-- ) | Draw a square of the given side length. |
| ttl-home | ( -- ) | Return to the canvas center and reset heading. |
| canvas-width | ( --n ) | Push the canvas width. |
| canvas-height | ( --n ) | Push the canvas height. |
| canvas-width! | ( n-- ) | Set the canvas width. |
| canvas-height! | ( n-- ) | Set the canvas height. |
| resize-canvas | ( width height-- ) | Set width and height. |
| clear | ( -- ) | Clear the canvas. |
| color | ( r g b-- ) | Set fill color. |
| stroke | ( r g b-- ) | Set line/stroke color. |
| fill-alpha | ( alpha-- ) | Set global alpha from 0 to 255. |
| line-width | ( pixels-- ) | Set stroke width. |
| rect | ( x y width height-- ) | Fill a rectangle. |
| rect-stroke | ( x y width height-- ) | Outline a rectangle. |
| circle | ( x y radius-- ) | Fill a circle. |
| circle-stroke | ( x y radius-- ) | Outline a circle. |
| pi | ( --pi ) | Push the value of pi. |
| begin-path | ( -- ) | Begin a new path. |
| move-to | ( x y-- ) | Set the current path point. |
| line-to | ( x y-- ) | Add a line to the current path. |
| stroke-path | ( -- ) | Stroke the current path. |
| fill-path | ( -- ) | Fill the current path. |
| close-path | ( -- ) | Close the current path. |
| pixel | ( x y-- ) | Draw one pixel. |
| save-state | ( -- ) | Save the canvas drawing state. |
| restore-state | ( -- ) | Restore the previously saved canvas drawing state. |
| translate | ( x y-- ) | Translate the canvas. |
| rotate | ( angle-- ) | Rotate in radians. |
| scale | ( scale-x scale-y-- ) | Scale the canvas. |
| font | ( str-- ) | Set the canvas font. |
| text | ( str x y-- ) | Fill text at a position. |
| text-stroke | ( str x y-- ) | Outline text at a position. |
| image-data | ( x y width height-- ) | Copy a canvas rectangle to the temporary stack. |
| put-image-data | ( x y-- ) | Put the saved image data at a position. |
| load-sprite | ( url-address--index ) | Load an image from a URL and push its sprite index. |
| draw-sprite | ( index x y-- ) | Draw a loaded sprite at native size. |
| draw-sprite-scaled | ( index x y w h-- ) | Draw a loaded sprite at a specified size. |
| sprite-width | ( index--pixels ) | Push a sprite's loaded width. |
| sprite-height | ( index--pixels ) | Push a sprite's loaded height. |
| redraw | ( -- ) | Request a redraw. |
| redraw? | ( --flag ) | Push true (-1) when a redraw is pending, otherwise false (0). |
push(val)- Add value to data stackpop()- Remove and return top of stack (throws error on underflow)TOS()/NOS()- Peek at top/next-on-stack without removingsetTOS(val)/setNOS(val)- Modify top/next-on-stack without poppingComma(x)- Store value atherepointer and incrementnComma(array)- Store multiple values via CommarPush(val)/rPop()- Return stack operationslPush(val)/lPop()- Loop stack operationstPush(val)/tPop()- Temp stack operationscolonWord()- Dispatch handler for colon-defined wordsinner(start)- Execute compiled code starting at address; calls functions directlyouter(source)- Parse and execute/compile source stringaddWord(name, immediate)- Add entry to dictionaryfindWord(name)- Look up word by namefindWordIndex(name)- Find word index in dictionaryfindByXT(xt)- Find word by execution tokenforthPrim(name, fn)- Register a primitive (function) in the dictionaryforthImm(name, fn)- Register an IMMEDIATE primitive in the dictionarynextWord()- Extract next whitespace-delimited token from input bufferdelimWord(delim)- Extract token delimited by a specific characterdoNum(token)- Parse and handle numeric literaldoWord(token)- Look up and execute/compile worddoColon()- Begin word definitiondoSemi()- End word definitiondoLit(n)- Handle numeric literals during compilation or executionlistForthBlock(n)- Display block n in the UI textareasetHTMLValue(id, val)- Update HTML element with given IDdoSee()- Inspect word definition
The window load event handler automatically:
- Finds all
<script type="application/forth">tags - Loads external files via
fetch()ifsrcis set - Executes inline Forth code
- Routes output to console.log (captured and displayed)
- This is accomplished by the
window.addEventListener()code injsforth.js