From d18ea1b330e5dd15f0426eb00984bb29576f7458 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sun, 8 Feb 2026 08:24:49 -0600 Subject: [PATCH] update engine.cm --- internal/engine.cm | 351 ++++++++++++++++++++++++--------------------- source/cell.c | 18 ++- source/quickjs.c | 35 +++++ 3 files changed, 231 insertions(+), 173 deletions(-) diff --git a/internal/engine.cm b/internal/engine.cm index e5f4d530..d49c9589 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -1,17 +1,21 @@ -(function engine() { // Hidden vars (os, actorsym, init, core_path) come from env var ACTORDATA = actorsym var SYSYM = '__SYSTEM__' var _cell = {} +var need_stop = false var dylib_ext -switch(os.platform()) { - case 'Windows': dylib_ext = '.dll'; break; - case 'macOS': dylib_ext = '.dylib'; break; - case 'Linux': dylib_ext = '.so'; break; -} +var cases = { + Windows: '.dll', + macOS: '.dylib', + Linux: '.so' +} + +print(os.platform()) + +dylib_ext = cases[os.platform()] var MOD_EXT = '.cm' var ACTOR_EXT = '.ce' @@ -51,14 +55,16 @@ var fd = use_embed('fd') // Get the shop path from HOME environment var home = os.getenv('HOME') || os.getenv('USERPROFILE') if (!home) { - throw Error('Could not determine home directory') + os.print('Could not determine home directory\n') + os.exit(1) } var shop_path = home + '/.cell' var packages_path = shop_path + '/packages' var core_path = packages_path + '/core' if (!fd.is_dir(core_path)) { - throw Error('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.') + os.print('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.\n') + os.exit(1) } var use_cache = {} @@ -79,7 +85,7 @@ function use_core(path) { var script_blob = fd.slurp(file_path) var script = text(script_blob) var mod = `(function setup_module(use){${script}})` - var fn = js.eval('core:' + path, mod) + var fn = mach_eval('core:' + path, mod) var result = call(fn,sym, [use_core]) use_cache[cache_key] = result; return result; @@ -133,32 +139,27 @@ function log(name, args) { var caller = caller_data(1) var msg = args[0] - switch(name) { - case 'console': - os.print(console_rec(caller.line, caller.file, msg)) - break - case 'error': - msg = msg ?? Error() - if (is_proto(msg, Error)) - msg = msg.name + ": " + msg.message + "\n" + msg.stack - os.print(console_rec(caller.line, caller.file, msg)) - break - case 'system': - msg = "[SYSTEM] " + msg - os.print(console_rec(caller.line, caller.file, msg)) - break - default: - log.console(`unknown log type: ${name}`) - break + if (name == 'console') { + os.print(console_rec(caller.line, caller.file, msg)) + } else if (name == 'error') { + if (msg == null) msg = Error() + if (is_proto(msg, Error)) + msg = msg.name + ": " + msg.message + "\n" + msg.stack + os.print(console_rec(caller.line, caller.file, msg)) + } else if (name == 'system') { + msg = "[SYSTEM] " + msg + os.print(console_rec(caller.line, caller.file, msg)) + } else { + log.console(`unknown log type: ${name}`) } } -function disrupt(err) +function actor_die(err) { - if (is_function(err.toString)) { - os.print(err.toString()) - os.print("\n") - os.print(err.stack) + if (err && is_function(err.toString)) { + os.print(err.toString()) + os.print("\n") + if (err.stack) os.print(err.stack) } if (overling) { @@ -185,14 +186,14 @@ function disrupt(err) log.console(err.stack) } - actor_mod.disrupt() + actor_mod["disrupt"]() } -actor_mod.on_exception(disrupt) +actor_mod.on_exception(actor_die) -_cell.args = init ?? {} +_cell.args = init != null ? init : {} _cell.id = "newguy" function create_actor(desc = {id:guid()}) { @@ -241,11 +242,15 @@ os.runtime_env = runtime_env $_.time_limit = function(requestor, seconds) { - if (!pronto.is_requestor(requestor)) - throw Error('time_limit: first argument must be a requestor'); - if (!is_number(seconds) || seconds <= 0) - throw Error('time_limit: seconds must be a positive number'); - + if (!pronto.is_requestor(requestor)) { + log.error('time_limit: first argument must be a requestor') + disrupt + } + if (!is_number(seconds) || seconds <= 0) { + log.error('time_limit: seconds must be a positive number') + disrupt + } + return function time_limit_requestor(callback, value) { pronto.check_callback(callback, 'time_limit') var finished = false @@ -260,7 +265,14 @@ $_.time_limit = function(requestor, seconds) timer_cancel = null } if (requestor_cancel) { - try { pronto.requestor_cancel(reason) } catch (_) {} + requestor_cancel(reason) + requestor_cancel = null + } + } + + function safe_cancel_requestor(reason) { + if (requestor_cancel) { + requestor_cancel(reason) requestor_cancel = null } } @@ -268,15 +280,12 @@ $_.time_limit = function(requestor, seconds) timer_cancel = $_.delay(function() { if (finished) return def reason = make_reason(factory, 'Timeout.', seconds) - if (requestor_cancel) { - try { requestor_cancel(reason) } catch (_) {} - requestor_cancel = null - } + safe_cancel_requestor(reason) finished = true callback(null, reason) }, seconds) - try { + function do_request() { requestor_cancel = requestor(function(val, reason) { if (finished) return finished = true @@ -286,16 +295,14 @@ $_.time_limit = function(requestor, seconds) } callback(val, reason) }, value) - } catch (ex) { - cancel(ex) - callback(null, ex) + } disruption { + cancel(Error('requestor failed')) + callback(null, Error('requestor failed')) } + do_request() return function(reason) { - if (requestor_cancel) { - try { requestor_cancel(reason) } catch (_) {} - requestor_cancel = null - } + safe_cancel_requestor(reason) } } } @@ -408,52 +415,54 @@ var portal_fn = null // takes a function input value that will eventually be called with the current time in number form. $_.portal = function(fn, port) { - if (portal) throw Error(`Already started a portal listening on ${portal.port}`) - if (!port) throw Error("Requires a valid port.") + if (portal) { + log.error(`Already started a portal listening on ${portal.port}`) + disrupt + } + if (!port) { + log.error("Requires a valid port.") + disrupt + } log.system(`starting a portal on port ${port}`) portal = enet.create_host({address: "any", port}) portal_fn = fn } function handle_host(e) { - switch (e.type) { - case "connect": - log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`) - peers[`${e.peer.address}:${e.peer.port}`] = e.peer - var queue = peer_queue.get(e.peer) - if (queue) { - arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg))) - log.system(`sent ${msg} out of queue`) - peer_queue.delete(e.peer) - } - break - case "disconnect": + if (e.type == "connect") { + log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`) + peers[`${e.peer.address}:${e.peer.port}`] = e.peer + var queue = peer_queue.get(e.peer) + if (queue) { + arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg))) + log.system(`sent ${msg} out of queue`) peer_queue.delete(e.peer) - arrfor(array(peers), function(id, index) { - if (peers[id] == e.peer) delete peers[id] + } + } else if (e.type == "disconnect") { + peer_queue.delete(e.peer) + arrfor(array(peers), function(id, index) { + if (peers[id] == e.peer) delete peers[id] + }) + log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port) + } else if (e.type == "receive") { + var data = nota.decode(e.data) + if (data.replycc && !data.replycc.address) { + data.replycc[ACTORDATA].address = e.peer.address + data.replycc[ACTORDATA].port = e.peer.port + } + function populate_actor_addresses(obj) { + if (!is_object(obj)) return + if (obj[ACTORDATA] && !obj[ACTORDATA].address) { + obj[ACTORDATA].address = e.peer.address + obj[ACTORDATA].port = e.peer.port + } + arrfor(array(obj), function(key, index) { + if (key in obj) + populate_actor_addresses(obj[key]) }) - log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port) - break - case "receive": - var data = nota.decode(e.data) - if (data.replycc && !data.replycc.address) { - data.replycc[ACTORDATA].address = e.peer.address - data.replycc[ACTORDATA].port = e.peer.port - } - function populate_actor_addresses(obj) { - if (!is_object(obj)) return - if (obj[ACTORDATA] && !obj[ACTORDATA].address) { - obj[ACTORDATA].address = e.peer.address - obj[ACTORDATA].port = e.peer.port - } - arrfor(array(obj), function(key, index) { - if (key in obj) - populate_actor_addresses(obj[key]) - }) - } - if (data.data) populate_actor_addresses(data.data) - turn(data) - break + } + if (data.data) populate_actor_addresses(data.data) + turn(data) } } @@ -487,10 +496,14 @@ $_.stop = function stop(actor) { need_stop = true return } - if (!is_actor(actor)) - throw Error('Can only call stop on an actor.') - if (is_null(underlings[actor[ACTORDATA].id])) - throw Error('Can only call stop on an underling or self.') + if (!is_actor(actor)) { + log.error('Can only call stop on an actor.') + disrupt + } + if (is_null(underlings[actor[ACTORDATA].id])) { + log.error('Can only call stop on an underling or self.') + disrupt + } sys_msg(actor, {kind:"stop"}) } @@ -527,20 +540,22 @@ function actor_prep(actor, send) { // Send a message immediately without queuing function actor_send_immediate(actor, send) { - try { - actor_send(actor, send); - } catch (err) { - log.error("Failed to send immediate message:", err); - } + actor_send(actor, send) } function actor_send(actor, message) { if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop return - if (!is_actor(actor) && !is_actor(actor.replycc)) throw Error(`Must send to an actor object. Attempted send to ${actor}`) - - if (!is_object(message)) throw Error('Must send an object record.') + if (!is_actor(actor) && !is_actor(actor.replycc)) { + log.error(`Must send to an actor object. Attempted send to ${actor}`) + disrupt + } + + if (!is_object(message)) { + log.error('Must send an object record.') + disrupt + } // message to self if (actor[ACTORDATA].id == _cell.id) { @@ -583,12 +598,10 @@ function actor_send(actor, message) { // Holds all messages queued during the current turn. var message_queue = [] -var need_stop = false - - function send_messages() { +function send_messages() { // if we've been flagged to stop, bail out before doing anything if (need_stop) { - disrupt() + actor_die() message_queue = [] return } @@ -608,19 +621,26 @@ var need_stop = false var replies = {} function send(actor, message, reply) { - if (!is_object(actor)) - throw Error(`Must send to an actor object. Provided: ${actor}`); + if (!is_object(actor)) { + log.error(`Must send to an actor object. Provided: ${actor}`) + disrupt + } - if (!is_object(message)) - throw Error('Message must be an object') + if (!is_object(message)) { + log.error('Message must be an object') + disrupt + } var send_msg = {type:"user", data: message} + var target = actor if (actor[HEADER] && actor[HEADER].replycc) { var header = actor[HEADER] - if (!header.replycc || !is_actor(header.replycc)) - throw Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`) + if (!header.replycc || !is_actor(header.replycc)) { + log.error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`) + disrupt + } - actor = header.replycc + target = header.replycc send_msg.return = header.reply } @@ -638,7 +658,7 @@ function send(actor, message, reply) { } // Instead of sending immediately, queue it - actor_prep(actor, send_msg); + actor_prep(target, send_msg); } stone(send) @@ -669,7 +689,7 @@ overling = _cell.args.overling $_.overling = overling root = _cell.args.root -root ??= $_.self +if (root == null) root = $_.self if (overling) { $_.couple(overling) // auto couple to overling @@ -705,36 +725,35 @@ function handle_actor_disconnect(id) { delete greeters[id] } log.system(`actor ${id} disconnected`) - if (!is_null(couplings[id])) disrupt("coupled actor died") // couplings now disrupts instead of stop + if (!is_null(couplings[id])) actor_die("coupled actor died") // couplings now disrupts instead of stop } function handle_sysym(msg) { var from - switch(msg.kind) { - case 'stop': - disrupt("got stop message") - break - case 'underling': - from = msg.from - var greeter = greeters[from[ACTORDATA].id] - if (greeter) greeter(msg.message) - if (msg.message.type == 'disrupt') - delete underlings[from[ACTORDATA].id] - break - case 'contact': - if (portal_fn) { - var letter2 = msg.data - letter2[HEADER] = msg - delete msg.data - portal_fn(letter2) - } else throw Error('Got a contact message, but no portal is established.') - break - case 'couple': // from must be notified when we die - from = msg.from - underlings[from[ACTORDATA].id] = true - log.system(`actor ${from} is coupled to me`) - break + if (msg.kind == 'stop') { + actor_die("got stop message") + } else if (msg.kind == 'underling') { + from = msg.from + var greeter = greeters[from[ACTORDATA].id] + if (greeter) greeter(msg.message) + if (msg.message.type == 'disrupt') + delete underlings[from[ACTORDATA].id] + } else if (msg.kind == 'contact') { + if (portal_fn) { + var letter2 = msg.data + letter2[HEADER] = msg + delete msg.data + portal_fn(letter2) + } else { + log.error('Got a contact message, but no portal is established.') + disrupt + } + } else if (msg.kind == 'couple') { + // from must be notified when we die + from = msg.from + underlings[from[ACTORDATA].id] = true + log.system(`actor ${from} is coupled to me`) } } @@ -744,30 +763,27 @@ function handle_message(msg) { return } - switch (msg.type) { - case "user": - var letter = msg.data // what the sender really sent - _ObjectDefineProperty(letter, HEADER, { - value: msg, enumerable: false - }) - _ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true - value: { reply: msg.reply }, enumerable: false - }) - - if (msg.return) { - var fn = replies[msg.return] - if (fn) fn(letter) - delete replies[msg.return] - return - } - - if (receive_fn) receive_fn(letter) + if (msg.type == "user") { + var letter = msg.data // what the sender really sent + _ObjectDefineProperty(letter, HEADER, { + value: msg, enumerable: false + }) + _ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true + value: { reply: msg.reply }, enumerable: false + }) + + if (msg.return) { + var fn = replies[msg.return] + if (fn) fn(letter) + delete replies[msg.return] return - case "stopped": - handle_actor_disconnect(msg.id) - break + } + + if (receive_fn) receive_fn(letter) + } else if (msg.type == "stopped") { + handle_actor_disconnect(msg.id) } -}; +} function enet_check() { @@ -792,8 +808,10 @@ if (!locator) { locator = shop.resolve_locator(_cell.args.program + ".ce", pkg) } -if (!locator) - throw Error(`Main program ${_cell.args.program} could not be found`) +if (!locator) { + os.print(`Main program ${_cell.args.program} could not be found\n`) + os.exit(1) +} $_.clock(_ => { // Get capabilities for the main program @@ -818,7 +836,6 @@ $_.clock(_ => { var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env]) if (val) - throw Error('Program must not return anything'); + log.error('Program must not return anything') + disrupt }) - -})() \ No newline at end of file diff --git a/source/cell.c b/source/cell.c index 411b6184..ff53442a 100644 --- a/source/cell.c +++ b/source/cell.c @@ -175,7 +175,7 @@ void script_startup(cell_rt *prt) cell_rt *crt = JS_GetContextOpaque(js); JS_FreeValue(js, js_blob_use(js)); - // Load and compile engine.cm + // Load and parse engine.cm to AST size_t engine_size; char *data = load_core_file(ENGINE, &engine_size); if (!data) { @@ -183,10 +183,15 @@ void script_startup(cell_rt *prt) return; } - JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE); + cJSON *ast = JS_ASTTree(data, engine_size, ENGINE); free(data); - if (JS_IsException(bytecode)) { - uncaught_exception(js, bytecode); + if (!ast) { + printf("ERROR: Failed to parse %s\n", ENGINE); + return; + } + + if (print_tree_errors(ast)) { + cJSON_Delete(ast); return; } @@ -216,9 +221,10 @@ void script_startup(cell_rt *prt) // Stone the environment hidden_env = JS_Stone(js, hidden_env); - // Integrate and run + // Run through MACH VM crt->state = ACTOR_RUNNING; - JSValue v = JS_Integrate(js, bytecode, hidden_env); + JSValue v = JS_RunMachTree(js, ast, hidden_env); + cJSON_Delete(ast); uncaught_exception(js, v); crt->state = ACTOR_IDLE; set_actor_state(crt); diff --git a/source/quickjs.c b/source/quickjs.c index a60c2b1d..fd44da6a 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -2074,6 +2074,7 @@ static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); @@ -25239,6 +25240,39 @@ static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue return result; } +/* ============================================================================ + * mach_eval() function - compile and execute via MACH VM + * ============================================================================ + */ + +/* mach_eval(name, source) - parse to AST and run through MACH VM */ +static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1])) + return JS_ThrowTypeError (ctx, "mach_eval requires (name, source) text arguments"); + + const char *name = JS_ToCString (ctx, argv[0]); + if (!name) return JS_EXCEPTION; + + const char *source = JS_ToCString (ctx, argv[1]); + if (!source) { + JS_FreeCString (ctx, name); + return JS_EXCEPTION; + } + + cJSON *ast = JS_ASTTree (source, strlen (source), name); + JS_FreeCString (ctx, source); + + if (!ast) { + JS_FreeCString (ctx, name); + return JS_ThrowSyntaxError (ctx, "mach_eval: failed to parse AST"); + } + + JSValue result = JS_RunMachTree (ctx, ast, JS_NULL); + cJSON_Delete (ast); + JS_FreeCString (ctx, name); + return result; +} + /* ============================================================================ * stone() function - deep freeze with blob support * ============================================================================ @@ -26373,6 +26407,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) { /* Core functions - using GC-safe helper */ js_set_global_cfunc(ctx, "eval", js_cell_eval, 2); + js_set_global_cfunc(ctx, "mach_eval", js_mach_eval, 2); js_set_global_cfunc(ctx, "stone", js_cell_stone, 1); js_set_global_cfunc(ctx, "length", js_cell_length, 1); js_set_global_cfunc(ctx, "call", js_cell_call, 3);