// parity.js // Simple parity harness: runs a Baba Yaga program via interpreter and compiled JS, compares results and outputs. // Usage: node parity.js [file2.baba ...] import fs from 'fs'; import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import { createLexer } from '../lexer.js'; import { createParser } from '../parser.js'; import { createInterpreter } from '../interpreter.js'; import { compile } from './experimental/compiler/compiler.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); function deepPlain(value) { if (typeof value === 'function') { try { if (value && value.type === 'NativeFunction') return { type: 'NativeFunction' }; } catch {} return { type: 'Function' }; } if (value && typeof value.value === 'number') return value.value; if (Array.isArray(value)) return value.map(deepPlain); if (value && value.type === 'Object' && value.properties instanceof Map) { const obj = {}; for (const [k, v] of value.properties.entries()) obj[k] = deepPlain(v); return obj; } if (value && value.type === 'Function') return { type: 'Function' }; if (value && value.type === 'NativeFunction') return { type: 'NativeFunction' }; if (value && value.type === 'Result') { return { type: 'Result', variant: value.variant, value: deepPlain(value.value) }; } return value; } function makeHostCapture() { const out = []; const io = { out: (...xs) => { out.push(xs.map((x) => deepPlain(x))); }, in: () => '', }; return { io, out }; } function runWithInterpreter(source) { const lexer = createLexer(source); const tokens = lexer.allTokens(); const parser = createParser(tokens); const ast = parser.parse(); const { io, out } = makeHostCapture(); const { interpret } = createInterpreter(ast, { io }); const result = interpret(); return { result, out }; } async function runWithCompiler(source) { try { const { code } = compile(source, { format: 'esm', mode: 'closure' }); // Attempt to import as ESM via data URL const dataUrl = 'data:text/javascript;base64,' + Buffer.from(code, 'utf8').toString('base64'); const mod = await import(dataUrl); const { io, out } = makeHostCapture(); // Heuristic: prefer `run(host)` if exported, else `main(host)` or `main()` let result; if (typeof mod.run === 'function') { result = await mod.run({ io }); } else if (typeof mod.main === 'function') { try { result = await mod.main({ io }); } catch { result = await mod.main(); } } else if (typeof mod.__main === 'function') { result = await mod.__main({ io }); } else { return { pending: true, note: 'No run/main export in compiled module', out: [], result: undefined }; } return { result, out }; } catch (e) { return { pending: true, note: `Compiled execution unavailable: ${e && e.message ? e.message : String(e)}` }; } } function isDeepEqual(a, b) { return JSON.stringify(a) === JSON.stringify(b); } async function main() { const args = process.argv.slice(2); const files = args.length ? args : ['example.baba']; let pass = 0; let fail = 0; let pending = 0; for (const file of files) { const filePath = path.isAbsolute(file) ? file : path.resolve(__dirname, file); const source = fs.readFileSync(filePath, 'utf8'); const interp = runWithInterpreter(source); const comp = await runWithCompiler(source); const interpValue = deepPlain(interp.result); const compValue = comp.pending ? undefined : deepPlain(comp.result); const interpOut = interp.out; const compOut = comp.pending ? undefined : comp.out; const valueEq = !comp.pending && isDeepEqual(interpValue, compValue); const outEq = !comp.pending && isDeepEqual(interpOut, compOut); if (comp.pending) { pending++; console.log(`[PENDING] ${path.basename(file)} - ${comp.note}`); continue; } if (valueEq && outEq) { pass++; console.log(`[PASS] ${path.basename(file)}`); } else { fail++; console.log(`[FAIL] ${path.basename(file)}`); if (!valueEq) { console.log(' value: interpreter =', JSON.stringify(interpValue), ' compiled =', JSON.stringify(compValue)); } if (!outEq) { console.log(' io.out: interpreter =', JSON.stringify(interpOut), ' compiled =', JSON.stringify(compOut)); } } } console.log(`\nSummary: pass=${pass}, fail=${fail}, pending=${pending}`); if (fail > 0) process.exitCode = 1; } await main();