diff options
Diffstat (limited to 'js/baba-yaga/experimental/parity.js')
-rw-r--r-- | js/baba-yaga/experimental/parity.js | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/js/baba-yaga/experimental/parity.js b/js/baba-yaga/experimental/parity.js new file mode 100644 index 0000000..2beedf3 --- /dev/null +++ b/js/baba-yaga/experimental/parity.js @@ -0,0 +1,137 @@ +// parity.js +// Simple parity harness: runs a Baba Yaga program via interpreter and compiled JS, compares results and outputs. +// Usage: node parity.js <file1.baba> [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(); + + |