To compile a standard library natively into the VM without polluting your
human-readable AST-MD state snapshots, we will use a Code/State Separation
pattern.
We will define the Standard Library functions purely in Lisp, inject them into
the worker's memory matrix the millisecond it boots, and then filter them out
of the Markdown snapshotter so they don't clutter your disk.
1. The Standard Library (stdlib.mjs)
Create a new file named stdlib.mjs. Because your VM natively supports
First-Class Functions (the CALL opcode dynamically resolves the target from
the stack), we can write purely functional map, filter, and reduce methods
using recursion, slice, and cons.
/ stdlib.mjs export const StdLibAst = [ // --- MATH UTILITIES --- ["def",
"math:abs", ["n"], ["if", ["<", "n", 0], ["-", 0, "n"], "n"]], ["def",
"math:max", ["a", "b"], ["if", [">", "a", "b"], "a", "b"]], ["def",
"math:min", ["a", "b"], ["if", ["<", "a", "b"], "a", "b"]], // --- LIST
MANIPULATION (Functional) --- // list:map (fn, arr) -> Returns a new array
with fn applied to all elements ["def", "list:map", ["fn", "arr"], ["if",
["=", ["len", "arr"], 0], ["array"], ["cons", ["fn", ["get", "arr", 0]],
["list:map", "fn", ["slice", "arr", 1, ["len", "arr"]]] ] ] ], // list:filter
(fn, arr) -> Returns a new array with elements that passed fn ["def",
"list:filter", ["fn", "arr"], ["if", ["=", ["len", "arr"], 0], ["array"],
["let", [ ["head", ["get", "arr", 0]], ["tail", ["slice", "arr", 1, ["len",
"arr"]]] ], ["if", ["fn", "head"], ["cons", "head", ["list:filter", "fn",
"tail"]], ["list:filter", "fn", "tail"] ] ] ] ], // list:reduce (fn, acc, arr)
-> Accumulates a single value ["def", "list:reduce", ["fn", "acc", "arr"],
["if", ["=", ["len", "arr"], 0], "acc", ["list:reduce", "fn", ["fn", "acc",
["get", "arr", 0]], ["slice", "arr", 1, ["len", "arr"]] ] ] ] ];
2. Code/State Separation (node_worker.mjs)
If we execute these globally, they will sit in vm.globalEnv. We need to
intercept the snapshot process so it only saves data variables, not binary
functions.
Open node_worker.mjs, locate the msg.type === 'snapshot' block, and add a
single type-check line:
else if (msg.type === 'snapshot') { const state = {}; for (const [k, v] of
vm.globalEnv.vars.entries()) { // ---> THE FIX: Keep binaries out of the
Markdown state! if (typeof v === 'function' || (v && v.type === 'lisp_func'))
continue; state[k] = v; } parentPort.postMessage({ type: 'snapshot_data',
data: state }); }
3. Pre-Bake the Worker Image (mesh.mjs)
Open mesh.mjs. Import the Standard Library, compile it into a raw Uint8Array,
and execute it silently before you deploy any Actor payloads.
At the top of mesh.mjs:
import { StdLibAst } from './stdlib.mjs'; import { expandMacros, analyze,
compile, optimize, assemble, StateBuffer } from './engine.mjs';
Inside SupervisorEngine.deploy(): When you spawn a new worker, post the StdLib
binary first:
if (!this.actors.has(actorId)) { const worker = new Worker('./node_worker.mjs')
; // ---> NEW: Pre-bake the Standard Library! const stdlibBinary =
assemble(optimize(compile(["do", ...StdLibAst]))); worker.postMessage({ type:
'eval', bytecode: stdlibBinary, context: { isStdLib: true } });
worker.on('message', (msg) => { if (msg.type === 'completed') { // If this was
just the standard library booting, stay silent. if (msg.context &&
msg.context.isStdLib) return; // ... your existing return-address routing code
...
Test the Standard Library!
Restart the hal-9000 console and spawn Tampere on port 8050. Let's deploy an
actor that uses functional Map and Reduce to process a dataset!
Paste this into the Deploy Actor panel in your HTML Mission Control UI (or via
curl):
Target Node: Engine#Tampere Actor ID: Actor#Math Payload:
[ "do", ["defvar", "scores", ["array", 10, 20, 30]], ["def", "process_scores",
[], ["do", ["def", "double", ["x"], ["*", "x", 2]], ["def", "sum", ["acc",
"x"], ["+", "acc", "x"]], ["let", [["doubled", ["list:map", "double",
"scores"]]], ["list:reduce", "sum", 0, "doubled"] ] ] ], ["register-object",
["str", "Actor#Math"]] ]
Click DEPLOY PAYLOAD. Now, trigger it over TCP via terminal:
nc localhost 8050 {"action": "call_other", "target_obj": "Actor#Math",
"method": "process_scores"}
Netcat will immediately reply: {"action":"call_reply", ... "data":120}
The VM dynamically resolved the higher-order functions double and sum, passed
them into the recursive standard library methods, sliced the array, mapped
[20, 40, 60], reduced it to 120, and returned it. Because of our
node_worker.mjs patch, if you check Engine#Tampere_Actor_Math.md, it will
cleanly contain only scores: [10, 20, 30].