136 lines
3.6 KiB
JavaScript
136 lines
3.6 KiB
JavaScript
/**
|
|
* codecs.js — Minimal codecs for decoding tree results.
|
|
*
|
|
* Implements: decodeResult (from Research.hs)
|
|
* - Leaf → "t"
|
|
* - Numbers: toNumber
|
|
* - Strings: toString
|
|
* - Lists: toList
|
|
* - Fallback: raw tree format
|
|
*/
|
|
|
|
// ── toNumber ────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Decode a tree as a binary number (big-endian).
|
|
* Leaf = 0, Fork(Leaf, rest) = 2*n, Fork(Stem Leaf, rest) = 2*n+1.
|
|
*/
|
|
export function toNumber(t) {
|
|
if (!Array.isArray(t)) return null;
|
|
if (t.length === 0) return 0; // Leaf = 0
|
|
if (t.length !== 2) return null; // must be Fork
|
|
|
|
const [right, left] = t;
|
|
// Fork structure: [right, left]
|
|
// left child determines bit: Leaf = 0, Stem(Leaf) = 1
|
|
let bit;
|
|
if (Array.isArray(left) && left.length === 0) {
|
|
bit = 0; // Leaf
|
|
} else if (Array.isArray(left) && left.length === 1) {
|
|
const child = left[0];
|
|
if (Array.isArray(child) && child.length === 0) {
|
|
bit = 1; // Stem(Leaf) = 1
|
|
} else {
|
|
return null; // Stem of something other than Leaf
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
const rest = toNumber(right);
|
|
if (rest === null) return null;
|
|
|
|
return bit + 2 * rest;
|
|
}
|
|
|
|
// ── toString ────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Decode a tree as a list of numbers (characters).
|
|
* Fork(x, rest) = x : list.
|
|
*/
|
|
export function toList(t) {
|
|
if (!Array.isArray(t)) return null;
|
|
if (t.length === 0) return []; // Leaf = empty list
|
|
if (t.length !== 2) return null; // must be Fork
|
|
|
|
const [right, left] = t;
|
|
const rest = toList(right);
|
|
if (rest === null) return null;
|
|
|
|
return [left, ...rest];
|
|
}
|
|
|
|
/**
|
|
* Decode a tree as a string.
|
|
*/
|
|
export function toString(t) {
|
|
const list = toList(t);
|
|
if (list === null) return null;
|
|
try {
|
|
return list.map((ch) => String.fromCharCode(ch)).join("");
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ── decodeResult ────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Decode a tree result using multiple strategies:
|
|
* 1. Leaf → "t"
|
|
* 2. String (if all chars are printable)
|
|
* 3. Number
|
|
* 4. List
|
|
* 5. Raw tree format
|
|
*/
|
|
export function decodeResult(t) {
|
|
if (!Array.isArray(t)) {
|
|
return String(t);
|
|
}
|
|
|
|
// Leaf
|
|
if (t.length === 0) {
|
|
return "t";
|
|
}
|
|
|
|
// Try string first (list of char codes)
|
|
const list = toList(t);
|
|
if (list !== null && list.length > 0) {
|
|
const str = list.map((n) => {
|
|
if (n < 32 || n > 126) return null;
|
|
return String.fromCharCode(n);
|
|
}).join("");
|
|
if (str) return `"${str}"`;
|
|
}
|
|
|
|
// Try number
|
|
const num = toNumber(t);
|
|
if (num !== null) {
|
|
return String(num);
|
|
}
|
|
|
|
// Try list (elements are trees)
|
|
if (t.length === 2) {
|
|
const elements = toList(t);
|
|
if (elements !== null) {
|
|
const decoded = elements.map((e) => decodeResult(e));
|
|
return `[${decoded.join(", ")}]`;
|
|
}
|
|
}
|
|
|
|
// Raw tree format
|
|
return formatTree(t);
|
|
}
|
|
|
|
/**
|
|
* Format a tree as a parenthesized expression.
|
|
*/
|
|
export function formatTree(t) {
|
|
if (!Array.isArray(t)) return String(t);
|
|
if (t.length === 0) return "Leaf";
|
|
if (t.length === 1) return `Stem(${formatTree(t[0])})`;
|
|
if (t.length === 2) return `Fork(${formatTree(t[1])}, ${formatTree(t[0])})`;
|
|
return `[${t.map(formatTree).join(", ")}]`;
|
|
}
|