bend!-call from the WAT tier
- New WAT import: (import "env" "bend_call"). The host loader supplies
a sync XMLHttpRequest that POSTs the payload to a configured URL
(workers only — sync XHR isn't allowed on main thread).
- New primitive (bend!-call "<payload>") returns the response as a
lumbda string. Works in playground and REPL once the bend URL is
saved in the new top bar.
- bendUrl persists in plain localStorage (not encrypted — it's a
server address, not a secret).
- Tests stub the import with a no-op so unit / integration / functional
suites keep instantiating cleanly.
Playground + REPL zoom 133% by default
- html { zoom: 1.33 } so the styleguide sizes read comfortably without
requiring browser-level zoom.
Free-form default = cross-tier assertion runner in Lisp
- The default editor content for "free form" is now a small assertion
framework matching tests/functional.lsp's PASS/FAIL convention. A
starter the user can extend, runs identically on the three tiers.
C tier + Python tier (bend) are wired through the runner stub; full
bend integration in those tiers comes next once their loaders learn
about setBendUrl.
85 lines
3.4 KiB
JavaScript
85 lines
3.4 KiB
JavaScript
// wasm/asm/lumbda-asm.loader.js
|
|
// Asm tier loader — instantiates lumbda-asm.wasm and exposes evalLisp.
|
|
//
|
|
// Memory layout (mirrors lumbda.wat):
|
|
// 0x10000 output buffer (read after each call)
|
|
// 0x20000 source buffer (write before each call)
|
|
// 0x40000 bend response scratch (host writes here when JS dispatches)
|
|
|
|
async function _bootstrap() {
|
|
const wasmURL = new URL("./lumbda-asm.wasm", import.meta.url).href;
|
|
const resp = await fetch(wasmURL);
|
|
const bytes = await resp.arrayBuffer();
|
|
|
|
// Closure-captured holder so the imported function can see the
|
|
// instance's memory after instantiation completes.
|
|
const refs = { instance: null, bendUrl: null };
|
|
|
|
const importObj = {
|
|
env: {
|
|
// bend_call(src_ptr, src_len, resp_ptr) -> resp_len.
|
|
// Synchronous XHR is the only sync HTTP available in workers;
|
|
// perfect for the WAT tier's blocking eval model.
|
|
bend_call(srcPtr, srcLen, respPtr) {
|
|
if (!refs.bendUrl) {
|
|
const msg = "no bend URL configured";
|
|
const mem = new Uint8Array(refs.instance.exports.memory.buffer);
|
|
mem.set(new TextEncoder().encode(msg), respPtr);
|
|
return msg.length;
|
|
}
|
|
const mem = new Uint8Array(refs.instance.exports.memory.buffer);
|
|
const payload = new TextDecoder().decode(mem.slice(srcPtr, srcPtr + srcLen));
|
|
try {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open("POST", refs.bendUrl, false); // sync
|
|
xhr.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
|
|
xhr.send(payload);
|
|
const respText = xhr.responseText || "";
|
|
const respBytes = new TextEncoder().encode(respText);
|
|
new Uint8Array(refs.instance.exports.memory.buffer).set(respBytes, respPtr);
|
|
return respBytes.length;
|
|
} catch (e) {
|
|
const err = "bend error: " + (e.message || String(e));
|
|
new Uint8Array(refs.instance.exports.memory.buffer)
|
|
.set(new TextEncoder().encode(err), respPtr);
|
|
return err.length;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
const { instance } = await WebAssembly.instantiate(bytes, importObj);
|
|
refs.instance = instance;
|
|
instance.exports.lumbda_init();
|
|
|
|
const enc = new TextEncoder();
|
|
const dec = new TextDecoder();
|
|
const exp = instance.exports;
|
|
|
|
return {
|
|
async evalLisp(src) {
|
|
const srcBytes = enc.encode(src);
|
|
const srcPtr = exp.lumbda_source_ptr();
|
|
const mem = new Uint8Array(exp.memory.buffer);
|
|
mem.set(srcBytes, srcPtr);
|
|
try {
|
|
exp.lumbda_eval(srcBytes.length);
|
|
} catch (e) {
|
|
return `error: ${e.message}`;
|
|
}
|
|
const outPtr = exp.lumbda_output_ptr();
|
|
const outLen = exp.lumbda_output_len();
|
|
return dec.decode(new Uint8Array(exp.memory.buffer, outPtr, outLen));
|
|
},
|
|
setBendUrl(url) { refs.bendUrl = url || null; },
|
|
};
|
|
}
|
|
|
|
// Closure-encapsulated singleton (see lumbda-c.loader.js for rationale).
|
|
export const createAsmTier = (() => {
|
|
let tier = null;
|
|
return async () => {
|
|
if (!tier) tier = await _bootstrap();
|
|
return tier;
|
|
};
|
|
})();
|