Skip to content

Instantly share code, notes, and snippets.

@prescod
Created February 16, 2026 22:20
Show Gist options
  • Select an option

  • Save prescod/571c4dcaa53f5829788351107d9efed2 to your computer and use it in GitHub Desktop.

Select an option

Save prescod/571c4dcaa53f5829788351107d9efed2 to your computer and use it in GitHub Desktop.
BechML Runner JS
#!/usr/bin/env node
// BechML runner using WASM module from bechml.github.io
// Requires: Node.js, bechml.wasm and core/ in same directory as this script
// Usage: node bechml-runner.mjs <source.bml>
// Optional: BECHML_WASM_DIR env var overrides location of bechml.wasm and core/
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { WASI } from 'wasi';
import { argv } from 'process';
const baseDir = process.env.BECHML_WASM_DIR || dirname(fileURLToPath(import.meta.url));
const wasmPath = join(baseDir, 'bechml.wasm');
const wasmBuffer = readFileSync(wasmPath);
const wasi = new WASI({
version: 'preview1',
args: [],
env: {},
preopens: {}
});
let instance;
function getMemory() {
return new Uint8Array(instance.exports.memory.buffer);
}
function writeCString(text) {
const encoder = new TextEncoder();
const bytes = encoder.encode(`${text}\0`);
const ptr = instance.exports.malloc(bytes.length);
getMemory().set(bytes, ptr);
return ptr;
}
function readCString(ptr) {
const memory = getMemory();
let end = ptr;
while (memory[end] !== 0) {
end += 1;
}
const decoder = new TextDecoder();
return decoder.decode(memory.subarray(ptr, end));
}
function addFile(path, content) {
const pathPtr = writeCString(path);
const contentPtr = writeCString(content);
const resultPtr = instance.exports.bechml_add_file(pathPtr, contentPtr);
instance.exports.free(pathPtr);
instance.exports.free(contentPtr);
instance.exports.free(resultPtr);
}
function addLib(path) {
const pathPtr = writeCString(path);
const resultPtr = instance.exports.bechml_add_lib(pathPtr);
const result = readCString(resultPtr);
instance.exports.free(pathPtr);
instance.exports.free(resultPtr);
return result;
}
function compileSource(source) {
const inputPtr = writeCString(source);
const resultPtr = instance.exports.bechml_compile(inputPtr);
instance.exports.free(inputPtr);
const payload = readCString(resultPtr);
instance.exports.free(resultPtr);
if (payload.startsWith('OK\n')) {
return { ok: true, js: payload.slice(3) };
}
if (payload.startsWith('ERR\n')) {
return { ok: false, error: payload.slice(4) };
}
return { ok: false, error: 'Unexpected compiler response.' };
}
async function loadCoreLib() {
const coreDir = join(baseDir, 'core');
const coreFiles = {
'core/Core.bml': join(coreDir, 'Core.bml'),
'core/Core/IO.bml': join(coreDir, 'Core', 'IO.bml'),
'core/Core/Int.bml': join(coreDir, 'Core', 'Int.bml'),
'core/Core/Identity.bml': join(coreDir, 'Core', 'Identity.bml'),
'core/Core/List.bml': join(coreDir, 'Core', 'List.bml')
};
for (const [path, filePath] of Object.entries(coreFiles)) {
const content = readFileSync(filePath, 'utf8');
addFile(path, content);
}
const result = addLib('core/Core.bml');
if (!result.startsWith('OK')) {
console.error('Failed to register Core lib:', result);
}
}
async function main() {
const wasmModule = await WebAssembly.compile(wasmBuffer);
instance = await WebAssembly.instantiate(wasmModule, {
wasi_snapshot_preview1: wasi.wasiImport
});
wasi.initialize(instance);
if (instance.exports.hs_init) {
instance.exports.hs_init(0, 0);
}
await loadCoreLib();
const sourceFile = argv[2];
if (!sourceFile) {
console.error('Usage: bechml-runner.mjs <source.bml>');
process.exit(1);
}
const source = readFileSync(sourceFile, 'utf8');
const result = compileSource(source);
if (!result.ok) {
console.error('Compile error:', result.error);
process.exit(1);
}
const bechmlConsole = {
log: (...args) => {
const s = args.map(String).join(' ');
if (s.length === 1 && s !== '\n') {
process.stdout.write(s);
} else {
process.stdout.write(s + (s.endsWith('\n') ? '' : '\n'));
}
},
warn: (...args) => process.stderr.write(args.map(String).join(' ') + '\n'),
error: (...args) => process.stderr.write(args.map(String).join(' ') + '\n')
};
const runner = new Function('console', result.js);
runner(bechmlConsole);
}
main().catch(err => {
console.error(err);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment