Search › A-Z › P › Piet Interpreter
Piet Interpreter
A clean, from-scratch Piet interpreter written for emulators.org and running entirely in your browser. Piet (David Morgan-Mar, 2002) is a visual stack esolang whose program is a bitmap of colour blocks. A Direction Pointer and a Codel Chooser walk the image, and the change in hue and lightness between the block left and the block entered encodes each command, from push and add to pointer, roll and output.
The view is the whole machine at once: the colour-block image with the current codel ringed and the Direction Pointer arrow shown, the stack, and the output line. It boots running the public-domain "Hello world!" image, and plugs into the shared debugger so you can pause, single-step one codel-transition at a time, set breakpoints on any codel, and watch the stack build each letter.
Runs on: Web browser
Piet Interpreter Online Emulator
Play Piet Interpreter using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Hello world! | Piet Interpreter | open | Open ⛶ |
Chips
Notes
Embedding
Piet is authored here from scratch, so embedding is just three small files: the VM (piet.js), the canvas view (piet-render.js), and a boot shim that owns the loop. There is no CPU and no ROM. The "machine" is a bitmap of colour blocks plus one integer stack, a Direction Pointer and a Codel Chooser.
Boot. Create the VM, load a decoded program image, wire a renderer, and run your own loop built on vm.step() (perform exactly one interpreter attempt):
var vm = new Piet();
vm.load({ w: 30, h: 29, rows: rows }); // the "Hello world!" image
var view = new PietRender(canvas, vm);
(function loop(){
for (var i = 0; i < 120 && !vm.halted; i++) vm.step(); // one attempt each
view.draw();
if (!vm.halted) requestAnimationFrame(loop);
})();
The machine is plain objects. Everything the debugger needs is a live field:
| Member | Kind | What it does |
|---|---|---|
vm.step() | method | One interpreter attempt: find the exit codel for the current DP and CC, try to move one codel in the DP direction, run the colour-transition command, or toggle CC / rotate DP when blocked. The single-step primitive; returns {moved}, {blocked}, {waiting} or {halted}. |
vm.grid | field | The program image as a Uint8Array of codel indices (row-major, y*W + x): 0 to 17 are the six hues in three lightnesses, 18 is white, 19 is black. |
vm.stack | field | The integer stack (a JS array; the top is the last element). |
vm.cx / vm.cy | field | The current codel. vm.dp is the Direction Pointer (0 to 3), vm.cc the Codel Chooser (0 left, 1 right). |
vm.blockSize | field | The size of the current colour block, which push puts onto the stack. |
vm.queueInput(v) | method | Feed stdin for in(char) and in(number): pass a string or a number. |
vm.load(prog) / vm.reset() | method | Parse a decoded image into the grid / clear the machine to its initial state. |
Debugger integration
Because the VM is ordinary JavaScript and vm.step() is one interpreter attempt, the debugger drives it directly. The boot shim publishes window.EMU_BOOT with a transport whose stepInsn(n) runs n attempts, and a run loop that checks the current codel against a breakpoint set every step:
function runBatch(){
for (var i = 0; i < speed; i++){
var idx = vm.cy * vm.W + vm.cx; // linear codel address
if (bps.has(idx) && !justResumed) return 'bp'; // break when the codel is reached
var ev = vm.step();
if (ev.halted) return 'halt';
if (ev.waiting) return 'wait'; // blocked on in(char) / in(number)
}
}
Breakpoints are codels, stored as the linear address y*W + x - the same address pc() returns and the disasm / hex views use, so clicking the gutter next to a codel sets a breakpoint there. Watchpoints cover the stack (any push or pop). The "registers" are the real machine state: the current codel CX / CY, the Direction Pointer DP, the Codel Chooser CC, the current BLOCK size and the stack depth.
The disassembler is unusual: Piet has no linear instruction stream, and a command is really the transition between two blocks, decided live by the DP and CC. So /debugger/src/cpus/piet.js decodes each grid codel as its colour (hue, lightness, or white / black) to annotate the image-as-memory view, while the live transition op is shown in the register panel where it has meaning.
Architecture
Piet (David Morgan-Mar, 2002) is named after the painter Piet Mondrian. A program is a picture:
- The image - a grid of codels. Contiguous same-colour codels form a colour block; the block the pointer is on is the unit it works with.
- The pointers - a Direction Pointer picks one of four travel directions, and a Codel Chooser picks the left or right edge. Together they choose which codel the pointer exits a block from.
- The palette - 18 colours are six hues in three lightnesses. White is a free zone the pointer slides across; black and the canvas edge are walls.
- Commands - the step from one block to the next encodes an operation as the change in hue and lightness: push, pop, add, subtract, multiply, divide, mod, not, greater, pointer, switch, duplicate, roll, in and out.
pushputs the size of the block just left onto the stack. - Flow - when a move is blocked, the interpreter toggles the Codel Chooser, then rotates the Direction Pointer, trying all eight combinations before it halts.
This interpreter is a faithful clean-room implementation of that public specification. The default image is the well-known public-domain "Hello world!" sample. There is no external code to trust: the whole machine is a few hundred lines of readable JavaScript, which is exactly what makes it a clear debugging target.