diff --git a/README.md b/README.md
index 85df5b5..291b35d 100644
--- a/README.md
+++ b/README.md
@@ -11,8 +11,9 @@ TinyAPL (read like *tiny apple*) is a tiny APL dialect and interpreter in Haskel
* Part 5: [Array Notation and Reductions](https://blog.rubenverg.com/tinyapl_5_array_notation_reductions)
* Part 6: [Tests, Docs, Each](https://blog.rubenverg.com/tinyapl_6_tests_docs_each)
* Part 7: [Quads, Key, Index](https://blog.rubenverg.com/tinyapl_7_quads_key_index)
+* Part 8: [All About Rank, and a Web Interface](https://blog.rubenverg.com/tinyapl_8_rank_web)
-Documentation is available [here](https://tinyapl.rubenverg.com)
+Documentation is available [here](https://tinyapl.rubenverg.com) and you can run the latest interpreter [here](https://tinyapl.rubenverg.com/run/latest)
## Features
diff --git a/docs/interpreters/0.5.0/ghc_wasm_jsffi.js b/docs/interpreters/0.5.0/ghc_wasm_jsffi.js
new file mode 100644
index 0000000..7c0dfa1
--- /dev/null
+++ b/docs/interpreters/0.5.0/ghc_wasm_jsffi.js
@@ -0,0 +1,122 @@
+// This file implements the JavaScript runtime logic for Haskell
+// modules that use JSFFI. It is not an ESM module, but the template
+// of one; the post-linker script will copy all contents into a new
+// ESM module.
+
+// Manage a mapping from unique 32-bit ids to actual JavaScript
+// values.
+class JSValManager {
+ #lastk = 0;
+ #kv = new Map();
+
+ constructor() {}
+
+ // Maybe just bump this.#lastk? For 64-bit ids that's sufficient,
+ // but better safe than sorry in the 32-bit case.
+ #allocKey() {
+ let k = this.#lastk;
+ while (true) {
+ if (!this.#kv.has(k)) {
+ this.#lastk = k;
+ return k;
+ }
+ k = (k + 1) | 0;
+ }
+ }
+
+ newJSVal(v) {
+ const k = this.#allocKey();
+ this.#kv.set(k, v);
+ return k;
+ }
+
+ // A separate has() call to ensure we can store undefined as a value
+ // too. Also, unconditionally check this since the check is cheap
+ // anyway, if the check fails then there's a use-after-free to be
+ // fixed.
+ getJSVal(k) {
+ if (!this.#kv.has(k)) {
+ throw new WebAssembly.RuntimeError(`getJSVal(${k})`);
+ }
+ return this.#kv.get(k);
+ }
+
+ // Check for double free as well.
+ freeJSVal(k) {
+ if (!this.#kv.delete(k)) {
+ throw new WebAssembly.RuntimeError(`freeJSVal(${k})`);
+ }
+ }
+}
+
+// A simple & fast setImmediate() implementation for browsers. It's
+// not a drop-in replacement for node.js setImmediate() because:
+// 1. There's no clearImmediate(), and setImmediate() doesn't return
+// anything
+// 2. There's no guarantee that callbacks scheduled by setImmediate()
+// are executed in the same order (in fact it's the opposite lol),
+// but you are never supposed to rely on this assumption anyway
+class SetImmediate {
+ #fs = [];
+ #mc = new MessageChannel();
+
+ constructor() {
+ this.#mc.port1.addEventListener("message", () => {
+ this.#fs.pop()();
+ });
+ this.#mc.port1.start();
+ }
+
+ setImmediate(cb, ...args) {
+ this.#fs.push(() => cb(...args));
+ this.#mc.port2.postMessage(undefined);
+ }
+}
+
+// The actual setImmediate() to be used. This is a ESM module top
+// level binding and doesn't pollute the globalThis namespace.
+let setImmediate;
+if (globalThis.setImmediate) {
+ // node.js, bun
+ setImmediate = globalThis.setImmediate;
+} else {
+ try {
+ // deno
+ setImmediate = (await import("node:timers")).setImmediate;
+ } catch {
+ // browsers
+ const sm = new SetImmediate();
+ setImmediate = (cb, ...args) => sm.setImmediate(cb, ...args);
+ }
+}
+
+export default (__exports) => {
+const __ghc_wasm_jsffi_jsval_manager = new JSValManager();
+const __ghc_wasm_jsffi_finalization_registry = new FinalizationRegistry(sp => __exports.rts_freeStablePtr(sp));
+return {
+newJSVal: (v) => __ghc_wasm_jsffi_jsval_manager.newJSVal(v),
+getJSVal: (k) => __ghc_wasm_jsffi_jsval_manager.getJSVal(k),
+freeJSVal: (k) => __ghc_wasm_jsffi_jsval_manager.freeJSVal(k),
+scheduleWork: () => setImmediate(__exports.rts_schedulerLoop),
+ZC25ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1,$2) => {return [$1, $2];},
+ZC26ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: async ($1,$2) => {await $1($2);},
+ZC27ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: async ($1) => {return await $1();},
+ZC28ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1) => {return $1;},
+ZC29ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1) => {return $1;},
+ZC30ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1,$2) => {const a = $2.slice(); a.unshift($1); return a;},
+ZC31ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: () => {return [];},
+ZC0ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => ($1.reject(new WebAssembly.RuntimeError($2))),
+ZC16ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => ($1.resolve($2)),
+ZC18ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => ($1.resolve($2)),
+ZC19ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1) => ($1.resolve()),
+ZC20ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: () => {let res, rej; const p = new Promise((resolve, reject) => { res = resolve; rej = reject; }); p.resolve = res; p.reject = rej; return p;},
+ZC17ZCghczminternalZCGHCziInternalziWasmziPrimziImportsZC: ($1,$2) => ($1.then(res => __exports.rts_promiseResolveJSVal($2, res), err => __exports.rts_promiseReject($2, err))),
+ZC18ZCghczminternalZCGHCziInternalziWasmziPrimziImportsZC: ($1,$2) => ($1.then(() => __exports.rts_promiseResolveUnit($2), err => __exports.rts_promiseReject($2, err))),
+ZC0ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1) => (`${$1.stack ? $1.stack : $1}`),
+ZC1ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1,$2) => ((new TextDecoder('utf-8', {fatal: true})).decode(new Uint8Array(__exports.memory.buffer, $1, $2))),
+ZC2ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1,$2,$3) => ((new TextEncoder()).encodeInto($1, new Uint8Array(__exports.memory.buffer, $2, $3)).written),
+ZC3ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1) => ($1.length),
+ZC4ZCghczminternalZCGHCziInternalziWasmziPrimziTypesZC: ($1) => {if (!__ghc_wasm_jsffi_finalization_registry.unregister($1)) { throw new WebAssembly.RuntimeError('js_callback_unregister'); }},
+ZC0ZCghczminternalZCGHCziInternalziWasmziPrimziConcziInternalZC: async ($1) => (new Promise(res => setTimeout(res, $1 / 1000))),
+};
+};
diff --git a/docs/interpreters/0.5.0/index.html b/docs/interpreters/0.5.0/index.html
new file mode 100644
index 0000000..77fe67b
--- /dev/null
+++ b/docs/interpreters/0.5.0/index.html
@@ -0,0 +1,81 @@
+
+
+
+ TinyAPL Interpreter
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/interpreters/0.5.0/index.js b/docs/interpreters/0.5.0/index.js
new file mode 100644
index 0000000..c4afb8d
--- /dev/null
+++ b/docs/interpreters/0.5.0/index.js
@@ -0,0 +1,199 @@
+import * as tinyapl from './tinyapl.js';
+
+const buttons = document.querySelector('#buttons');
+const output = document.querySelector('#output');
+/** @type {HTMLInputElement} */
+const input = document.querySelector('#input');
+const highlighted = document.querySelector('#highlighted');
+const button = document.querySelector('#button');
+
+function zip(as, bs) {
+ return [...as, ...bs].slice(0, Math.min(as.length, bs.length)).map((_, idx) => [as[idx], bs[idx]]);
+}
+
+const prefix = { code: 'Backquote', sym: '`' };
+
+const keyboard = [
+ ['Backquote', '`', '~', undefined, '⍨', '⋄', '⌺'],
+ ['Digit1', '1', '!', '¨', undefined, undefined, undefined],
+ ['Digit2', '2', '@', '¯', undefined, undefined, undefined],
+ ['Digit3', '3', '#', undefined, '⍒', undefined, undefined],
+ ['Digit4', '4', '$', '≤', '⍋', '⊴', undefined],
+ ['Digit5', '5', '%', undefined, undefined, undefined, undefined],
+ ['Digit6', '6', '^', '≥', '⍉', '⊵', undefined],
+ ['Digit7', '7', '&', undefined, '⊖', undefined, undefined],
+ ['Digit8', '8', '*', '≠', '⍣', '⍟', '∞'],
+ ['Digit9', '9', '(', '∨', '⍱', undefined, undefined],
+ ['Digit0', '0', ')', '∧', '⍲', '⍬', undefined],
+ ['Minus', '-', '_', '×', '⊗', '⸚', undefined],
+ ['Equal', '=', '+', '÷', '⊕', '⌹', undefined],
+ ['KeyQ', 'q', 'Q', undefined, undefined, undefined, undefined],
+ ['KeyW', 'w', 'W', '⍵', '⍹', undefined, undefined],
+ ['KeyE', 'e', 'E', '∊', '⍷', '⏨', '⋷'],
+ ['KeyR', 'r', 'R', '⍴', '√', 'ϼ', 'ℜ'],
+ ['KeyT', 't', 'T', '⊞', '⍨', undefined, undefined],
+ ['KeyY', 'y', 'Y', '↑', '↟', undefined, undefined],
+ ['KeyU', 'u', 'U', '↓', '↡', undefined, undefined],
+ ['KeyI', 'i', 'I', '⍳', '⍸', '…', 'ℑ'],
+ ['KeyO', 'o', 'O', '○', '⍥', undefined, undefined],
+ ['KeyP', 'p', 'P', undefined, undefined, undefined, undefined],
+ ['BracketLeft', '[', '{', '←', '⟨', undefined, undefined],
+ ['BracketRight', ']', '}', undefined, '⟩', undefined, undefined],
+ ['KeyA', 'a', 'A', '⍺', '⍶', undefined, undefined],
+ ['KeyS', 's', 'S', '⌈', '§', '↾', undefined],
+ ['KeyD', 'd', 'D', '⌊', '⸠', '⇂', undefined],
+ ['KeyF', 'f', 'F', '⍛', '∡', '∠', undefined],
+ ['KeyG', 'g', 'G', '∇', '⍢', undefined, undefined],
+ ['KeyH', 'h', 'H', '∆', '⍙', '⊸', '⟜'],
+ ['KeyJ', 'j', 'J', '∘', '⍤', 'ᴊ', undefined],
+ ['KeyK', 'k', 'K', '⍆', '⌸', '⍅', undefined],
+ ['KeyL', 'l', 'L', '⎕', '⌷', undefined, undefined],
+ ['Semicolon', ';', ':', '⍎', '≡', '⍮', '■'],
+ ['Quote', '\'', '"', '⍕', '≢', '⍘', '⍞'],
+ ['Backslash', '\\', '|', '⊢', '⊣', undefined, undefined],
+ ['KeyZ', 'z', 'Z', '⊂', '⊆', undefined, undefined],
+ ['KeyX', 'x', 'X', '⊃', '⊇', '↗', undefined],
+ ['KeyC', 'c', 'C', '∩', '⍝', '⟃', '⟄'],
+ ['KeyV', 'v', 'V', '∪', '⁖', undefined, undefined],
+ ['KeyB', 'b', 'B', '⊥', undefined, undefined, undefined],
+ ['KeyN', 'n', 'N', '⊤', undefined, undefined, undefined],
+ ['KeyM', 'm', 'M', '«', '»', undefined, undefined],
+ ['Comma', ',', '<', '⍪', 'ᑈ', '⊲', undefined],
+ ['Period', '.', '>', '∙', 'ᐵ', '⊳', undefined],
+ ['Slash', '/', '?', '⌿', undefined, undefined, undefined],
+].map(([code, sym, symS, symP, symPS, symPP, symPPS]) => ({ code, sym, symS, symP, symPS, symPP, symPPS }));
+
+const colors = {
+ other: 'unset',
+ syntax: 'unset',
+ number: '#ea0027',
+ string: '#0079d3',
+ stringEscape: '#0266b3',
+ arrayName: '#ff4500',
+ primArray: '#cc3600',
+ functionName: '#46d160',
+ primFunction: '#349e48',
+ adverbName: '#ff66ac',
+ primAdverb: '#cc5289',
+ conjunctionName: '#ffd635',
+ primConjunction: '#ccac2b',
+ comment: '#014980',
+};
+
+async function highlight() {
+ const code = input.value;
+ const pairs = zip(await tinyapl.splitString(code), await tinyapl.highlight(code));
+ highlighted.innerHTML = '';
+ for (const [t, c] of pairs) {
+ const span = document.createElement('span');
+ span.style.color = colors[tinyapl.colorsInv[c]];
+ span.innerText = t;
+ highlighted.appendChild(span);
+ }
+ highlighted.scrollLeft = input.scrollLeft;
+}
+
+function insertText(str) {
+ input.setRangeText(str, input.selectionStart, input.selectionEnd, "end");
+ input.focus();
+ highlight();
+}
+
+const io = new class IO {
+ #input = [];
+ #output = [];
+ #error = [];
+ rInput(l) { this.#input.push(l); }
+ rOutput(l) { this.#output.push(l); }
+ rError(l) { this.#error.push(l); }
+ done() { this.#input = []; this.#output = []; this.#error = []; }
+ async input() { const i = window.prompt('Input'); for (const l of this.#input) await l(i); return i; }
+ async output(what) { for (const l of this.#output) await l(what); }
+ async error(what) { for (const l of this.#error) await l(what); }
+};
+
+for (const k of ['syntax', 'identifiers', 'arrays', 'functions', 'adverbs', 'conjunctions']) {
+ for (const i of tinyapl.glyphs[k]) {
+ let v, p;
+ if (v = keyboard.find(k => k.symP === i)) p = `${prefix.sym}${v.sym}`;
+ else if (v = keyboard.find(k => k.symPS === i)) p = `${prefix.sym}${v.symS}`;
+ else if (v = keyboard.find(k => k.symPP === i)) p = `${prefix.sym}${prefix.sym}${v.sym}`;
+ else if (v = keyboard.find(k => k.symPPS === i)) p = `${prefix.sym}${prefix.sym}${v.symS}`;
+ const b = document.createElement('button');
+ b.textContent = i;
+ if (p !== undefined) b.title = `Input: ${p}`;
+ b.addEventListener('click', () => { insertText(i); });
+ buttons.appendChild(b);
+ }
+ buttons.appendChild(document.createElement('br'));
+}
+
+const context = await tinyapl.newContext(io.input.bind(io), io.output.bind(io), io.error.bind(io));
+
+function div(cls, contents) {
+ const div = document.createElement('div');
+ div.className = cls;
+ div.textContent = contents;
+ return div;
+}
+
+function clickableDiv(cls, contents, clickedContents = contents) {
+ const d = div(cls, contents);
+ d.addEventListener('click', () => {
+ if (input.value.trim() == '') {
+ input.value = clickedContents;
+ input.focus();
+ highlight();
+ }
+ });
+ return d;
+}
+
+async function runCode(code) {
+ output.appendChild(clickableDiv('code', ' '.repeat(6) + code, code));
+ const d = div('quad', '');
+ output.appendChild(d);
+ io.rInput(what => { d.innerText += what + '\n'; });
+ io.rOutput(what => { d.innerText += what; });
+ io.rError(what => { d.innerText += what; });
+ const [result, success] = await tinyapl.runCode(context, code);
+ io.done();
+ if (d.textContent.trim() === '') output.removeChild(d);
+ if (d.textContent.at(-1) === '\n') d.textContent = d.textContent.slice(0, -1);
+ if (success) output.appendChild(clickableDiv('result', result));
+ else output.appendChild(div('error', result));
+}
+
+async function run() {
+ await runCode(input.value);
+ input.value = '';
+ highlight();
+}
+
+let keyboardState = 0;
+
+button.addEventListener('click', () => run());
+input.addEventListener('keydown', evt => {
+ if (keyboardState < 2 && evt.code == prefix.code && !evt.shiftKey) {
+ keyboardState++
+ evt.preventDefault();
+ } else if (keyboardState !== 0 && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
+ const v = keyboard.find(k => k.code == evt.code);
+ if (v) {
+ const t = keyboardState == 2 ? (evt.shiftKey ? v.symPPS : v.symPP) : (evt.shiftKey ? v.symPS : v.symP);
+ insertText(t ?? evt.key);
+ keyboardState = 0;
+ evt.preventDefault();
+ }
+ } else if (evt.key == 'Enter') {
+ evt.preventDefault();
+ return run();
+ }
+});
+input.addEventListener('input', () => highlight());
+input.addEventListener('scroll', () => highlight());
+
+const search = new URLSearchParams(window.location.search);
+
+for (const line of search.getAll('run')) await runCode(decodeURIComponent(line));
+
diff --git a/docs/interpreters/0.5.0/tinyapl-js.wasm b/docs/interpreters/0.5.0/tinyapl-js.wasm
new file mode 100755
index 0000000..b4dbb3d
Binary files /dev/null and b/docs/interpreters/0.5.0/tinyapl-js.wasm differ
diff --git a/docs/interpreters/0.5.0/tinyapl.js b/docs/interpreters/0.5.0/tinyapl.js
new file mode 100644
index 0000000..f29fd6d
--- /dev/null
+++ b/docs/interpreters/0.5.0/tinyapl.js
@@ -0,0 +1,86 @@
+import { WASI, OpenFile, File, ConsoleStdout } from 'https://esm.run/@bjorn3/browser_wasi_shim@0.3.0';
+import ghc_wasm_jsffi from './ghc_wasm_jsffi.js';
+
+const args = [];
+const env = [];
+const files = [
+ new OpenFile(new File([])), // stdin
+ ConsoleStdout.lineBuffered(msg => console.log(`[WASI] ${msg}`)), // stdout
+ ConsoleStdout.lineBuffered(msg => console.warn(`[WASI] ${msg}`)), // stderr
+];
+const options = {};
+const wasi = new WASI(args, env, files, options);
+
+const instanceExports = {};
+const { instance } = await WebAssembly.instantiateStreaming(fetch("./tinyapl-js.wasm"), {
+ wasi_snapshot_preview1: wasi.wasiImport,
+ ghc_wasm_jsffi: ghc_wasm_jsffi(instanceExports),
+});
+Object.assign(instanceExports, instance.exports);
+
+wasi.initialize(instance);
+
+await instance.exports.hs_start();
+
+/**
+ * Create a new context for TinyAPL code
+ * @param {() => string | Promise} input Function providing standard input
+ * @param {(what: string) => void | Promise} output Function providing standard output
+ * @param {(what: string) => void | Promise} error Function providing standard error
+ * @returns {Promise} Scope ID
+ */
+export async function newContext(input, output, error) {
+ return await instance.exports.tinyapl_newContext(input, output, error);
+}
+
+/**
+ * Run code in a context
+ * @param {number} context Context ID
+ * @param {string} code
+ * @returns {Promise<[string, boolean]>} A pair containing the result of the code or the error and whether running succeeded
+ */
+export async function runCode(context, code) {
+ const [result, success] = await instance.exports.tinyapl_runCode(context, code);
+ return [result, Boolean(success)];
+}
+
+/**
+ * Higlight a piece of code
+ * @param {string} code
+ * @returns {Promise} Colors of each character
+ */
+export async function highlight(code) {
+ return await instance.exports.tinyapl_highlight(code);
+}
+
+/**
+ * Split a string into UTF32 codepoints
+ * @param {string} str
+ * @returns {Promise}
+ */
+export async function splitString(str) {
+ return await instance.exports.tinyapl_splitString(str);
+}
+
+/**
+ * @type {Record}
+ */
+export const glyphs = {
+ syntax: await instance.exports.tinyapl_glyphsSyntax(),
+ identifiers: await instance.exports.tinyapl_glyphsIdentifiers(),
+ arrays: await instance.exports.tinyapl_glyphsArrays(),
+ functions: await instance.exports.tinyapl_glyphsFunctions(),
+ adverbs: await instance.exports.tinyapl_glyphsAdverbs(),
+ conjunctions: await instance.exports.tinyapl_glyphsConjunctions(),
+};
+
+/**
+ * @type {Record}
+ */
+export const colors = Object.fromEntries(await Promise.all(Object.entries(instance.exports).filter(([k]) => k.startsWith('tinyapl_hl')).map(async ([k, v]) => [k['tinyapl_hl'.length].toLowerCase() + k.slice('tinyapl_hl'.length + 1), await v()])));
+
+/**
+ * @type {Record}
+ */
+export const colorsInv = Object.fromEntries(Object.entries(colors).map(([k, v]) => [v, k]));
+
diff --git a/docs/interpreters/latest/ghc_wasm_jsffi.js b/docs/interpreters/latest/ghc_wasm_jsffi.js
index 3e50da8..7c0dfa1 100644
--- a/docs/interpreters/latest/ghc_wasm_jsffi.js
+++ b/docs/interpreters/latest/ghc_wasm_jsffi.js
@@ -98,13 +98,13 @@ newJSVal: (v) => __ghc_wasm_jsffi_jsval_manager.newJSVal(v),
getJSVal: (k) => __ghc_wasm_jsffi_jsval_manager.getJSVal(k),
freeJSVal: (k) => __ghc_wasm_jsffi_jsval_manager.freeJSVal(k),
scheduleWork: () => setImmediate(__exports.rts_schedulerLoop),
-ZC25ZCtinyaplzm0zi4zi2zi0zminplacezmtinyaplzmjsZCMainZC: ($1,$2) => {return [$1, $2];},
-ZC26ZCtinyaplzm0zi4zi2zi0zminplacezmtinyaplzmjsZCMainZC: async ($1,$2) => {await $1($2);},
-ZC27ZCtinyaplzm0zi4zi2zi0zminplacezmtinyaplzmjsZCMainZC: async ($1) => {return await $1();},
-ZC28ZCtinyaplzm0zi4zi2zi0zminplacezmtinyaplzmjsZCMainZC: ($1) => {return $1;},
-ZC29ZCtinyaplzm0zi4zi2zi0zminplacezmtinyaplzmjsZCMainZC: ($1) => {return $1;},
-ZC30ZCtinyaplzm0zi4zi2zi0zminplacezmtinyaplzmjsZCMainZC: ($1,$2) => {const a = $2.slice(); a.unshift($1); return a;},
-ZC31ZCtinyaplzm0zi4zi2zi0zminplacezmtinyaplzmjsZCMainZC: () => {return [];},
+ZC25ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1,$2) => {return [$1, $2];},
+ZC26ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: async ($1,$2) => {await $1($2);},
+ZC27ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: async ($1) => {return await $1();},
+ZC28ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1) => {return $1;},
+ZC29ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1) => {return $1;},
+ZC30ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: ($1,$2) => {const a = $2.slice(); a.unshift($1); return a;},
+ZC31ZCtinyaplzm0zi5zi0zi0zminplacezmtinyaplzmjsZCMainZC: () => {return [];},
ZC0ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => ($1.reject(new WebAssembly.RuntimeError($2))),
ZC16ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => ($1.resolve($2)),
ZC18ZCghczminternalZCGHCziInternalziWasmziPrimziExportsZC: ($1,$2) => ($1.resolve($2)),
diff --git a/docs/interpreters/latest/tinyapl-js.wasm b/docs/interpreters/latest/tinyapl-js.wasm
index faca08f..b4dbb3d 100755
Binary files a/docs/interpreters/latest/tinyapl-js.wasm and b/docs/interpreters/latest/tinyapl-js.wasm differ
diff --git a/tinyapl.cabal b/tinyapl.cabal
index ebea075..a3742e1 100644
--- a/tinyapl.cabal
+++ b/tinyapl.cabal
@@ -1,6 +1,6 @@
cabal-version: 3.0
name: tinyapl
-version: 0.4.2.0
+version: 0.5.0.0
synopsis: TinyAPL is a tiny APL dialect
-- A longer description of the package.