A stack-based virtual machine implementing Forth-like semantics for the REI framework.
REIVM is the execution engine at the heart of REI. It provides a simple, observable, and clonable virtual machine that makes computation transparent and explorable.
- ✅ Stack-based execution - All computation flows through observable data stack
- ✅ 30 CORE words - Stack operations, arithmetic, strings, console, and DOM
- ✅ User-defined words - Extend the VM with custom compiled words
- ✅ Session persistence - Serialize and restore VM state
- ✅ Branching support - Clone VMs to explore multiple paths
- ✅ Browser compatible - No Node.js dependencies
- ✅ Type-safe - Full TypeScript support with strict mode
- ✅ Well-tested - 235 tests with 100% pass rate
bun installThis project requires Bun v1.3.5 or later.
import { bootstrapVM } from './src/bootstrap.ts';
// Create a VM with CORE words loaded
const vm = bootstrapVM();
// Execute REI code
vm.run('5 3 +');
console.log(vm.dataStack.pop()); // 8
// Chain operations
vm.run('5 3 + DUP *'); // (5+3)² = 64
console.log(vm.dataStack.pop()); // 64Everything in REIVM revolves around the data stack. Values are pushed onto the stack, and words operate on stack values.
const vm = bootstrapVM();
// Push values
vm.dataStack.push(5);
vm.dataStack.push(3);
// Execute a word
vm.execute('+'); // Pops 5 and 3, pushes 8
// Check the result
console.log(vm.dataStack.peek()); // 8A word is the fundamental unit of execution. Words can be:
- Native words - Implemented as JavaScript functions
- Compiled words - Arrays of other word names
// Define a compiled word
vm.dictionary.define('SQUARE', {
name: 'SQUARE',
stackEffect: '( n -- n² )',
body: ['DUP', '*'], // Duplicate, then multiply
immediate: false,
protected: false,
category: 'USER',
metadata: { defined: new Date(), usageCount: 0 }
});
vm.run('8 SQUARE');
console.log(vm.dataStack.pop()); // 64The run() method parses and executes a string of REI code:
vm.run('10 20 + 2 /'); // Average of 10 and 20
console.log(vm.dataStack.pop()); // 15Tokens are processed left to right:
- Numbers are pushed onto the stack
- Words are looked up and executed
REIVM includes 30 built-in CORE words:
| Word | Stack Effect | Description |
|---|---|---|
DUP |
( n -- n n ) |
Duplicate top value |
DROP |
( n -- ) |
Remove top value |
SWAP |
( a b -- b a ) |
Swap top two values |
OVER |
( a b -- a b a ) |
Copy second value to top |
ROT |
( a b c -- b c a ) |
Rotate top three values |
2DUP |
( a b -- a b a b ) |
Duplicate top two values |
2DROP |
( a b -- ) |
Remove top two values |
2SWAP |
( a b c d -- c d a b ) |
Swap top two pairs |
| Word | Stack Effect | Description |
|---|---|---|
+ |
( a b -- sum ) |
Add two numbers |
- |
( a b -- difference ) |
Subtract b from a |
* |
( a b -- product ) |
Multiply two numbers |
/ |
( a b -- quotient ) |
Divide a by b |
MOD |
( a b -- remainder ) |
Modulo operation |
| Word | Stack Effect | Description |
|---|---|---|
= |
( a b -- bool ) |
Test equality |
< |
( a b -- bool ) |
Test less than |
> |
( a b -- bool ) |
Test greater than |
<= |
( a b -- bool ) |
Test less than or equal |
>= |
( a b -- bool ) |
Test greater than or equal |
| Word | Stack Effect | Description |
|---|---|---|
CONCAT |
( str1 str2 -- str ) |
Concatenate two strings |
LENGTH |
( str -- n ) |
Get string length |
SUBSTRING |
( str start end -- substr ) |
Extract substring |
TO-STRING |
( value -- str ) |
Convert value to string |
| Word | Stack Effect | Description |
|---|---|---|
. |
( value -- ) [prints] |
Print any value with newline |
EMIT |
( char-code -- ) [prints] |
Print single character from code |
Note: Other console words like CR (newline), SPACE, SPACES can be defined as user words using these primitives.
| Word | Stack Effect | Description |
|---|---|---|
CREATE-ELEMENT |
( tag-name -- element ) |
Create DOM element |
APPEND-CHILD |
( parent child -- parent ) |
Append child to parent |
SET-ATTRIBUTE |
( element key value -- element ) |
Set element attribute |
SET-TEXT |
( element text -- element ) |
Set text content |
QUERY-SELECTOR |
( selector -- element|null ) |
Find element by selector |
GET-ATTRIBUTE |
( element key -- value ) |
Get attribute value |
Create a new VM instance with CORE words loaded.
const vm = bootstrapVM();
const vm = bootstrapVM({ stackDepth: 1024 });Parse and execute a code string.
vm.run('5 3 + DUP *');Execute a single word.
vm.execute('DUP');
vm.execute(myWord);Serialize VM state for persistence.
const state = vm.serialize();
localStorage.setItem('session', JSON.stringify(state));Restore VM state from serialized data.
const state = JSON.parse(localStorage.getItem('session'));
vm.deserialize(state);Create an independent copy of the VM.
const vm2 = vm1.clone();
vm2.run('DUP *'); // Changes don't affect vm1const vm = bootstrapVM();
// Simple calculation
vm.run('10 20 +');
console.log(vm.dataStack.pop()); // 30
// Expression: (5 + 3) * 2
vm.run('5 3 + 2 *');
console.log(vm.dataStack.pop()); // 16const vm = bootstrapVM();
// Define a word to calculate area
vm.dictionary.define('AREA', {
name: 'AREA',
stackEffect: '( width height -- area )',
body: ['*'],
immediate: false,
protected: false,
category: 'USER',
metadata: { defined: new Date(), usageCount: 0 }
});
vm.run('10 5 AREA');
console.log(vm.dataStack.pop()); // 50const vm = bootstrapVM();
// Celsius to Fahrenheit: (C * 9 / 5) + 32
vm.dictionary.define('C->F', {
name: 'C->F',
stackEffect: '( celsius -- fahrenheit )',
body: ['9', '*', '5', '/', '32', '+'],
immediate: false,
protected: false,
category: 'USER',
metadata: { defined: new Date(), usageCount: 0 }
});
vm.run('100 C->F');
console.log(vm.dataStack.pop()); // 212
vm.run('0 C->F');
console.log(vm.dataStack.pop()); // 32// Save session
const vm1 = bootstrapVM();
vm1.run('5 3 +');
vm1.dictionary.define('DOUBLE', {
name: 'DOUBLE',
stackEffect: '( n -- 2n )',
body: ['DUP', '+'],
immediate: false,
protected: false,
category: 'USER',
metadata: { defined: new Date(), usageCount: 0 }
});
const state = vm1.serialize();
localStorage.setItem('my-session', JSON.stringify(state));
// Restore session later
const savedState = JSON.parse(localStorage.getItem('my-session'));
const vm2 = bootstrapVM();
vm2.deserialize(savedState);
console.log(vm2.dataStack.peek()); // 8
vm2.run('DOUBLE');
console.log(vm2.dataStack.peek()); // 16const base = bootstrapVM();
base.run('5 3'); // Stack: [5, 3]
// Explore addition path
const addPath = base.clone();
addPath.run('+');
console.log('Add result:', addPath.dataStack.peek()); // 8
// Explore multiplication path
const mulPath = base.clone();
mulPath.run('*');
console.log('Mul result:', mulPath.dataStack.peek()); // 15
// Original unchanged
console.log('Base stack:', base.dataStack.snapshot()); // [5, 3]const vm = bootstrapVM();
// Build a greeting
vm.dataStack.push('Hello');
vm.dataStack.push(' ');
vm.execute('CONCAT');
vm.dataStack.push('World');
vm.execute('CONCAT');
console.log(vm.dataStack.pop()); // "Hello World"
// Get length
vm.dataStack.push('Hello World');
vm.execute('LENGTH');
console.log(vm.dataStack.pop()); // 11
// Extract substring
vm.dataStack.push('Hello World');
vm.run('0 5 SUBSTRING');
console.log(vm.dataStack.pop()); // "Hello"const vm = bootstrapVM();
// Print a value
vm.run('42 .'); // Prints: 42
// Print character
vm.run('65 EMIT'); // Prints: A
// Define CR (newline) as user word
vm.dictionary.define('CR', {
name: 'CR',
stackEffect: '( -- )',
body: (vm) => {
vm.dataStack.push(10);
vm.execute('EMIT');
},
immediate: false,
protected: false,
category: 'USER',
metadata: { defined: new Date(), usageCount: 0 }
});// In browser environment
const vm = bootstrapVM();
// Get body element
vm.dataStack.push('body');
vm.execute('QUERY-SELECTOR');
// Create a button as child
vm.dataStack.push('button');
vm.execute('CREATE-ELEMENT');
vm.dataStack.push('id');
vm.dataStack.push('my-button');
vm.execute('SET-ATTRIBUTE');
vm.dataStack.push('Click me!');
vm.execute('SET-TEXT');
// Append to body
vm.execute('APPEND-CHILD');
vm.execute('DROP');
// Result: <button id="my-button">Click me!</button> added to bodyRun the test suite:
bun testRun tests in watch mode:
bun test --watchRun type checking:
bun run type-check- 235 tests across 9 test files
- 100% pass rate
- Tests cover:
- Stack operations
- Dictionary operations
- All CORE words (arithmetic, strings, console, DOM)
- VM execution
- Serialization/deserialization
- Cloning
- Integration scenarios
- Browser DOM operations (using jsdom)
REIVM/
├── specs/
│ ├── SPEC.md # Initial implementation specification
│ ├── SESSION_ARCHITECTURE.md # Session & branching design
│ └── SPEC_v0.1.1.md # v0.1.1 specification
├── src/
│ ├── index.ts # Main export
│ ├── vm.ts # VM class
│ ├── stack.ts # Stack implementation
│ ├── dictionary.ts # Dictionary implementation
│ ├── word.ts # Word type definitions
│ ├── errors.ts # Error classes
│ ├── bootstrap.ts # VM initialization
│ └── core/
│ ├── index.ts # Core words export
│ ├── stack-ops.ts # Stack manipulation words
│ ├── arithmetic.ts # Arithmetic & comparison words
│ ├── strings.ts # String operations (v0.1.1)
│ ├── console.ts # Console I/O (v0.1.1)
│ └── dom.ts # DOM primitives (v0.1.1, browser only)
├── tests/
│ ├── stack.test.ts
│ ├── dictionary.test.ts
│ ├── vm.test.ts
│ ├── integration.test.ts
│ ├── strings.test.ts # String operations tests
│ ├── console.test.ts # Console I/O tests
│ ├── dom.test.ts # DOM operations tests
│ └── core/
│ ├── stack-ops.test.ts
│ └── arithmetic.test.ts
├── examples/
│ └── browser-test.html # Browser testing example
├── package.json
├── tsconfig.json
└── README.md # This file
REIVM follows the REI Design Principles:
- Prefer boring over clever
- Make it work correctly first, optimize never (unless needed)
- When in doubt, do less
- All computation flows through the data stack
- Stack is observable at every step
- No hidden state transformations
- CORE words are
protected: true- cannot be modified or forgotten - USER words can be freely redefined
- Previous definitions are stored for introspection
REIVM implements the virtual machine as specified in the REI Framework.
Required reading for contributors:
- REI Manifesto - Philosophical foundation
- Design Principles - Decision-making framework
- Technical Specifications - Requirements
- REIVM Component Spec - Component requirements
New in this release:
- ✅ String operations (4 words) - CONCAT, LENGTH, SUBSTRING, TO-STRING
- ✅ Console I/O (2 words) -
.(print), EMIT - ✅ DOM primitives (6 words, browser only) - CREATE-ELEMENT, APPEND-CHILD, SET-ATTRIBUTE, SET-TEXT, QUERY-SELECTOR, GET-ATTRIBUTE
- ✅ Browser test example - Interactive HTML test page
- ✅ 235 tests with 100% pass rate
- ✅ jsdom integration for testing DOM operations
Foundation release:
- ✅ Complete stack implementation
- ✅ Complete dictionary implementation
- ✅ 18 CORE words (stack ops, arithmetic, comparisons)
- ✅ VM execution engine (run, execute, parse)
- ✅ Bootstrap function
- ✅ Error handling (all error types)
- ✅ Serialization/deserialization
- ✅ Cloning support
- ✅ ESM build for Bun and browser
- ✅ TypeScript type definitions
These features are planned for future versions:
- Control flow (IF/THEN/BEGIN/UNTIL) - v0.2.0
- Source file parsing (REILOADER component) - Future
- Advanced dictionary operations - Future
- Tracing support - Optional enhancement
- REPL interface (that's REIMON's job)
REIVM targets modern browsers:
- Chrome (last 2 versions)
- Firefox (last 2 versions)
- Safari (last 2 versions)
- Edge (last 2 versions)
No Node.js APIs used - works in any modern JavaScript environment.
When implementing features or fixing bugs:
- Read the REI Design Principles
- Check SPEC.md for implementation requirements
- Ensure all tests pass (
bun test) - Ensure type checking passes (
bun run type-check) - Follow the existing code style
- Add tests for new features
See the REI Framework for license information.
Built with Bun - a fast all-in-one JavaScript runtime.
REIVM v0.1.1 - Simple. Observable. Explorable.