Search › A-Z › W › Whitespace Interpreter
Whitespace Interpreter
A clean, from-scratch Whitespace interpreter written for emulators.org and running entirely in your browser. Whitespace is the imperative stack esolang created by Edwin Brady and Chris Morris in 2003, whose only significant characters are Space, Tab and LineFeed. Every other character is a comment, so a program looks completely blank on the page. Tokens group into an Instruction Modification Parameter and a command that drive a single integer stack and a heap of addressed values.
Because the program is invisible, the view renders it: the significant tokens appear as S, T and L glyphs in a flowing layout, with the current instruction's token span highlighted and a decoded mnemonic line above it. Below sit the stack, a compact heap view and the output line. It boots running an authored "Hello World", and plugs into the shared debugger so you can pause, single-step one instruction at a time, set breakpoints on any token, and watch the stack and heap.
Whitespace language reference ↗
Runs on: Web browser
Whitespace Interpreter Online Emulator
Play Whitespace Interpreter using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Hello World | Whitespace Interpreter | open | Open ⛶ | ||
| Echo input | Whitespace Interpreter | open | Open ⛶ |
Notes
Embedding
Whitespace is authored here from scratch, so embedding is just three small files: the VM (whitespace.js), the canvas view (whitespace-render.js), and a boot shim that owns the loop. There is no CPU and no ROM. The program is a string whose only significant characters are Space, Tab and LineFeed, parsed into a token stream plus an instruction list.
Boot. Create the VM, load a program, wire a renderer, and run your own loop built on vm.step() (execute exactly one instruction):
var vm = new Whitespace();
vm.load(Whitespace.build('Hello World')); // build an invisible program
var view = new WhitespaceRender(canvas, vm);
(function loop(){
for (var i = 0; i < 200 && !vm.halted; i++) vm.step(); // one instruction each
view.draw();
if (!vm.halted) requestAnimationFrame(loop);
})();
The machine is plain objects. Everything the debugger needs is a live field; there is no wasm heap:
| Member | Kind | What it does |
|---|---|---|
vm.step() | method | Execute exactly one instruction, then advance (or jump) the program counter. The single-step primitive; returns {ok}, {halted} or {waiting}. |
vm.tokens | field | The significant tokens as a flat array (0=Space, 1=Tab, 2=LineFeed). This is the program the debugger walks. |
vm.prog | field | The parsed instruction list; each item is {op, arg, start, len, mnem} where start is the token index it begins at. |
vm.stack / vm.heap | field | The integer stack (top is the last element) and the heap (an address→value map; vm.heapKeys() returns the sorted addresses). |
vm.pc / vm.tok | field | The instruction index and the token index of the current instruction (vm.prog[vm.pc].start). |
vm.queueInput(v) | method | Feed stdin for read-char and read-number: pass a string or a number. |
vm.load(text) / vm.reset() | method | Parse a program into tokens and instructions / clear the machine to its initial state. |
Debugger integration
Because the VM is ordinary JavaScript and vm.step() is one instruction, the debugger drives it directly. The boot shim publishes window.EMU_BOOT with a transport whose stepInsn(n) runs n instructions, and a run loop that checks the current instruction's token index against a breakpoint set every instruction:
function runBatch(){
for (var i = 0; i < speed; i++){
var tok = vm.prog[vm.pc].start; // token index of this instruction
if (bps.has(tok) && !justResumed) return 'bp'; // break at this token
var ev = vm.step();
if (ev.halted) return 'halt';
if (ev.waiting) return 'wait'; // blocked on read input
}
}
Breakpoints are token indices - the same address pc() returns and the disasm/hex views use, so clicking the gutter next to a token sets a breakpoint on the instruction that begins there. Watchpoints cover the stack (any push or pop). The "registers" are the real machine state: PC (instruction index), TOK (token index), SP, TOP, call-stack depth, heap size and the HALT / WAIT flags.
The disassembler is unusual: the program is invisible, so /debugger/src/cpus/whitespace.js decodes the token stream one instruction at a time, reading the IMP, the command and any number or label parameter, and reports how many tokens each instruction spans. That lets the disasm view step through the token stream as real Whitespace instructions.
Architecture
Whitespace (Edwin Brady and Chris Morris, 2003) is an imperative stack language whose only significant characters are Space, Tab and LineFeed; everything else is a comment, so a program looks blank:
- The tokens - three symbols group into an Instruction Modification Parameter (IMP) then a command. Space is Stack Manipulation, Tab-Space is Arithmetic, Tab-Tab is Heap Access, Tab-LineFeed is I/O, and LineFeed is Flow Control.
- Numbers - a sign token (Space is positive, Tab is negative), then binary digits (Space is 0, Tab is 1) most significant first, terminated by a LineFeed.
- Labels - an arbitrary Space/Tab bit-string terminated by a LineFeed, used verbatim as a name for marks, jumps and calls.
- The stack and heap - one integer stack for working data and a heap that maps integer addresses to integer values via store and retrieve.
- Flow - mark a label, call and return through a call stack, jump unconditionally, jump if the top is zero, or jump if the top is negative.
- I/O - output a character or a number, and read a character or a number into a heap address.
This interpreter is a faithful clean-room implementation of that public specification. The language spec is public domain. There is no external code to trust; the entire machine is a couple of hundred lines of readable JavaScript, which is exactly what makes it a clear debugging target.