Created
February 16, 2026 22:20
-
-
Save prescod/571c4dcaa53f5829788351107d9efed2 to your computer and use it in GitHub Desktop.
BechML Runner JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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