Named let creates a loop continuation but set! inside the loop body does not mutate bindings in the enclosing let scope. Affects both the WASM kernel and native OCaml CEK evaluator. 6 failing Node tests cover: - set! counter (simplest case) - set! counter with named let params - set! list accumulator via append - append! + set! counter combo - set! string concatenation - nested named let set! 3 baselines pass: plain let set!, functional named let, plain append! Also adds spec/tests/test-named-let-set.sx (7 assertions, first fails and aborts — confirms bug exists in spec test suite too). This is the root cause of empty source code blocks on all example pages: tokenize-sx uses set! in named let → empty tokens → highlight returns "(<> )" → empty <code> blocks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83 lines
2.9 KiB
JavaScript
83 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* test-named-let-set.js — WASM kernel tests for set! inside named let loops.
|
|
*
|
|
* FAILING: set! inside named let body does not persist mutations to
|
|
* the enclosing scope. This breaks tokenize-sx → highlight → source display.
|
|
*/
|
|
const { createSxEnv } = require('./sx-harness');
|
|
|
|
let passed = 0, failed = 0;
|
|
const origLog = console.log;
|
|
const origErr = console.error;
|
|
|
|
function test(name, expr, expected) {
|
|
let env;
|
|
try {
|
|
// Fresh env per test to avoid cross-contamination
|
|
// But creating env is slow, so we'll reuse one below
|
|
} catch(e) {}
|
|
}
|
|
|
|
async function main() {
|
|
origLog('=== Named Let + set! Tests (WASM kernel) ===\n');
|
|
const t0 = Date.now();
|
|
|
|
const env = await createSxEnv({});
|
|
|
|
function check(name, expr, expected) {
|
|
const result = env.eval(expr);
|
|
const expectedStr = JSON.stringify(expected);
|
|
const resultVal = result?.items ? result.items : result;
|
|
const resultStr = JSON.stringify(resultVal);
|
|
if (resultStr === expectedStr) {
|
|
passed++;
|
|
origLog(` \u2713 ${name}`);
|
|
} else {
|
|
failed++;
|
|
origErr(` \u2717 ${name}: expected ${expectedStr}, got ${resultStr}`);
|
|
}
|
|
}
|
|
|
|
// ---- Baselines (should pass) ----
|
|
origLog('Baselines:');
|
|
check('set! in plain let',
|
|
'(let ((x 0)) (set! x 42) x)', 42);
|
|
|
|
check('named let without set! (functional accumulator)',
|
|
'(trampoline (let loop ((i 0) (acc 0)) (if (< i 5) (loop (+ i 1) (+ acc i)) acc)))', 10);
|
|
|
|
check('append! on outer binding (no named let)',
|
|
'(let ((xs (list))) (append! xs 1) (append! xs 2) xs)', [1, 2]);
|
|
|
|
// ---- Named let + set! (the broken cases) ----
|
|
origLog('\nNamed let + set!:');
|
|
check('set! counter in named let loop',
|
|
'(let ((x 0)) (let loop () (when (< x 5) (set! x (+ x 1)) (loop))) x)', 5);
|
|
|
|
check('set! counter in named let loop (with param)',
|
|
'(let ((total 0)) (let loop ((i 1)) (when (<= i 10) (set! total (+ total i)) (loop (+ i 1)))) total)', 55);
|
|
|
|
check('set! list via append in named let',
|
|
'(let ((acc (list))) (let loop ((i 0)) (when (< i 3) (set! acc (append acc (list i))) (loop (+ i 1)))) acc)', [0, 1, 2]);
|
|
|
|
check('append! in named let',
|
|
'(let ((acc (list)) (i 0)) (let loop () (when (< i 3) (append! acc i) (set! i (+ i 1)) (loop))) acc)', [0, 1, 2]);
|
|
|
|
check('set! string concat in named let',
|
|
'(let ((result "")) (let loop ((i 0)) (when (< i 3) (set! result (str result (str i))) (loop (+ i 1)))) result)', "012");
|
|
|
|
// ---- Nested named let ----
|
|
origLog('\nNested:');
|
|
check('set! in nested named let',
|
|
'(let ((count 0)) (let outer ((i 0)) (when (< i 3) (let inner ((j 0)) (when (< j 2) (set! count (+ count 1)) (inner (+ j 1)))) (outer (+ i 1)))) count)', 6);
|
|
|
|
env.close();
|
|
|
|
const dt = Date.now() - t0;
|
|
origLog(`\n=== ${passed} passed, ${failed} failed (${dt}ms) ===`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
main().catch(e => { origErr(e); process.exit(1); });
|