Forth.js is a minimal interpreter for the Forth programming language written in modern JavaScript and designed to be run in-browser. You can checkout the live demo.
-
Implements a basic Forth virtual machine with:
- Stack operations:
DUP,DROP,SWAP,OVER,NIP,TUCK, ect. Full list can be found here. - Arithmetic operations:
+,-,*,/,MOD,/MOD,1+,1-,ABS,MIN,MAX,NEGATE - Colon definitions: Define or redefine words with
: ... ; - 64-bit Cell width for the data stack
- Comment handling
- Stack operations:
-
Robust error handling:
StackErrorParseErrorOperationError
-
Extensible design:
- Core words are modular and easy to extend
- Supports easy addition of new Forth words
Words are plain JavaScript functions stored in objects (see forth/words/core.js, forth/words/core-ext.js, ect). When a word is executed, its callback is invoked with this bound to the Fvm instance:
// src/words/example.js
import { Cell } from '../types/cell.js';
// Inside a word callback, 'this' is the Fvm instance
export const myWords = {
myWordName: function() {
const u = new Cell(42); // Only push Cell objects on to the dataStack
this.dataStack.push(u); // Push to the VM's stack
}
};
// src/words/index.js
// Add file to index.js export
import { myWords } from './examples.js';
export (
// ... ,
myWords
)
// src/forth.js
export class Fvm {
constructor() {
// ...
this.words = {
...words.core,
...words.coreExt,
...words.misc,
...words.myWords
}
}
// don't forget to add your words to resetWords()
resetWords() {
this.words = {
...words.core,
...words.coreExt,
...words.misc,
...words.myWords
}
}
}Important: Always use this to access the VM state (stack, status, etc.) rather than closing over a VM reference. This ensures consistency if the VM is reset or if multiple instances exist.
The Fvm constructor merges words from multiple sources:
this.words = {...words.core, ...words.coreExt, ...}Words from words.core override any identically-named words from words.coreExt and so on. If you add a new word module, ensure it is exported from forth/words/index.js and merged in the desired order.
Forth.js uses 64-bit cells stored as JavaScript BigInt values to represent data on the stack, matching traditional Forth implementations. This differs from JavaScript's native 53-bit safe integer precision and has important implications:
Why Cells?
- ANS Forth specifies that all stack values are stored as fixed-width cells (32-bit or 64-bit)
- Forth.js chooses 64-bit cells for broader numeric range while maintaining predictable overflow behavior
- Using
BigIntensures consistent two's complement arithmetic and bit operations
Key Differences from JavaScript Numbers:
- JavaScript numbers use IEEE 754 double-precision (53-bit integer precision)
- Forth cells use full 64-bit unsigned integers internally with signed/unsigned interpretation
- Operations exceeding 64-bit range wrap around (modular arithmetic) rather than becoming
Infinity
Implementation Details:
// All stack values are Cell objects
import { Cell } from './types/cell.js';
const c = new Cell(42);
c.toNumber() // → 42 (convert to JavaScript number)
c.toSigned() // → 42n (as signed BigInt)
c.toUnsigned() // → 42n (as unsigned BigInt)This design prioritizes Forth compatibility and predictable semantics over raw JavaScript performance.
- Implement control flow words (
IF,ELSE,THEN, loops) - Support return stack for recursion and more advanced Forth features
- Full ANS Forth compilence
ISC License © Christopher R. Martinez