Download Game! Currently 93 players and visitors. Last logged in:VenturusFonkensembleAstraxFelder

Blitzer's Blog >> 72252

Back to blogs index
Posted: 10 Sep 2026 16:06 [ permalink ]
Choosing callbacks is a remarkably clean and explicit path. It avoids the
hidden complexity of blocking threads and aligns perfectly with event-sourced
architectures and asynchronous out-of-band communication. Because every
external response is simply treated as a distinct event triggering a known
function, logging the intent and payload into a history stream becomes
trivial.
Here is how you can implement Continuation-Passing Style (CPS) in your JL-AST
engine.
1. The AST Design
You will need a designated operation for host-level boundaries, such as
syscall or dispatch. The final argument is always the function to invoke when
the host completes the task.
[
  "do",
  [
    "def", "handle_response", ["payload"],
    [
      "if", ["=", ["get", "payload", "status"], 200],
      ["print", ["get", "payload", "data"]],
      ["throw", "Network error"]
    ]
  ],
  ["syscall", "fetch", "https://api.mesh", "handle_response"]
]

2. Modifying the Virtual Machine
To support this, your VM transitions from a single run-to-completion loop into
an Event-Driven Actor.
 * The Syscall Opcode: Add an OP_SYSCALL instruction. When the VM executes
this, it pops the target (e.g., "fetch"), the arguments, and the callback
name.
 * Yielding to Host: The VM writes these values to the SharedArrayBuffer using
a new status (e.g., STATUS.SYSCALL = 5) and immediately yields control,
pausing its while loop.
 * The Main Thread: The main thread polls this status, reads the request, and
executes the actual NodeJS/Browser native code (like an HTTP fetch to an edge
tunnel or an IndexedDB query).
3. The Callback Invocation (The Message Queue)
Once the main thread finishes the external task, it needs a way to inject the
result back into the VM.
 * Instead of just running a single executeVM function on startup, the worker
thread needs a message listener (via parentPort.on('message')).
 * The main thread sends a payload: { type: 'INVOKE', func: 'handle_response',
args: [{status: 200, data: "..."}] }.
 * The worker receives this, looks up handle_response in its globalEnv, pushes
it onto the callStack, and restarts the while(callStack.length > 0) loop.
This effectively turns your VM into a microkernel with its own non-blocking
event loop.
To wire this up, would you like to start by defining the OP_SYSCALL logic in
the compiler, or by modifying the worker's message listener to accept incoming
function invocations?