/** * tree.js — Runtime tree representation. * * The JS tree uses a simple array representation matching the * TypeScript reference evaluator: * * Leaf = [] * Stem = [child] (array length === 1) * Fork = [right, left] (array length === 2) * * This is a "flattened stack" representation: when reduced, terms * become arrays and the evaluator pops three elements at a time. */ /** * Check if a value is a Leaf (empty array). */ export function isLeaf(t) { return Array.isArray(t) && t.length === 0; } /** * Check if a value is a Stem (single element array). */ export function isStem(t) { return Array.isArray(t) && t.length === 1; } /** * Check if a value is a Fork (two element array). */ export function isFork(t) { return Array.isArray(t) && t.length === 2; } /** * Check if a value is a valid tree calculus value (Leaf, Stem, or Fork). */ export function isTree(t) { return isLeaf(t) || isStem(t) || isFork(t); } /** * Triage a tree: classify it as Leaf/Stem/Fork. * The tree must be in normal form (no reducible redexes). * * Returns { kind: "leaf"|"stem"|"fork", ...rest } */ export function triage(t) { if (!Array.isArray(t)) { throw new Error("not a tree (not an array)"); } if (t.length === 0) return { kind: "leaf" }; if (t.length === 1) return { kind: "stem", child: t[0] }; if (t.length === 2) return { kind: "fork", right: t[0], left: t[1] }; throw new Error(`not a value/binary tree: length ${t.length}`); } /** * Apply the Tree Calculus apply rules. * * apply(a, b) computes the application of term a to term b. * * Rules: * apply(Fork(Leaf, a), _) = a * apply(Fork(Stem(a), b), c) = apply(apply(a, c), apply(b, c)) * apply(Fork(Fork, _, _), Leaf) = left of inner Fork * apply(Fork(Fork, _, _), Stem) = right of inner Fork * apply(Fork(Fork, _, _), Fork) = apply(apply(c, u), v) where c=Fork(u,v) * apply(Leaf, b) = Stem(b) * apply(Stem(a), b) = Fork(a, b) * * For Fork, the inner structure is [right, left], so: * a = right, b = left */ export function apply(a, b) { // apply(Fork(Leaf, a), _) = a // Fork = [right, left] = [Leaf, a] → left child is Leaf if (isFork(a) && isLeaf(a[1])) { return a[0]; // return right child } // apply(Fork(Stem(a), b), c) if (isFork(a) && isStem(a[1])) { const stemChild = a[1][0]; // left child of fork const right = a[0]; // right child of fork const innerA = stemChild; const innerB = right; const appliedA = apply(innerA, b); const appliedB = apply(innerB, b); return apply(appliedA, appliedB); } // apply(Fork(Fork, _, _), Leaf) if (isFork(a) && isFork(a[1]) && isLeaf(b)) { return a[1][0]; // right child of inner fork (which is left child) } // apply(Fork(Fork, _, _), Stem) if (isFork(a) && isFork(a[1]) && isStem(b)) { return a[1][1]; // left child of inner fork } // apply(Fork(Fork, _, _), Fork) if (isFork(a) && isFork(a[1]) && isFork(b)) { // b = Fork(u, v) = [v, u] const u = b[0]; const v = b[1]; // apply(apply(c, u), v) where c = inner fork const applied = apply(apply(a[1], u), v); return applied; } // apply(Leaf, b) = Stem(b) if (isLeaf(a)) { return [b]; } // apply(Stem(a), b) = Fork(a, b) if (isStem(a)) { return [b, a[0]]; // [right, left] } throw new Error("apply: undefined reduction for terms"); }