about summary refs log tree commit diff stats
path: root/js/baba-yaga/experimental/parity.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/baba-yaga/experimental/parity.js')
-rw-r--r--js/baba-yaga/experimental/parity.js137
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();
+
+