3 Commits

Author SHA1 Message Date
John Alanbrook
7469383e66 refactor into multiple files 2026-02-08 16:32:14 -06:00
John Alanbrook
1fee8f9f8b condense jsruntime and jscontext 2026-02-08 10:10:42 -06:00
John Alanbrook
d18ea1b330 update engine.cm 2026-02-08 08:24:49 -06:00
21 changed files with 39080 additions and 38721 deletions

View File

@@ -382,13 +382,13 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
JSValue js_miniz_use(JSContext *js) JSValue js_miniz_use(JSContext *js)
{ {
JS_NewClassID(&js_reader_class_id); JS_NewClassID(&js_reader_class_id);
JS_NewClass(JS_GetRuntime(js), js_reader_class_id, &js_reader_class); JS_NewClass(js, js_reader_class_id, &js_reader_class);
JSValue reader_proto = JS_NewObject(js); JSValue reader_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto); JS_SetClassProto(js, js_reader_class_id, reader_proto);
JS_NewClassID(&js_writer_class_id); JS_NewClassID(&js_writer_class_id);
JS_NewClass(JS_GetRuntime(js), js_writer_class_id, &js_writer_class); JS_NewClass(js, js_writer_class_id, &js_writer_class);
JSValue writer_proto = JS_NewObject(js); JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto); JS_SetClassProto(js, js_writer_class_id, writer_proto);

View File

@@ -1,17 +1,21 @@
(function engine() {
// Hidden vars (os, actorsym, init, core_path) come from env // Hidden vars (os, actorsym, init, core_path) come from env
var ACTORDATA = actorsym var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__' var SYSYM = '__SYSTEM__'
var _cell = {} var _cell = {}
var need_stop = false
var dylib_ext var dylib_ext
switch(os.platform()) { var cases = {
case 'Windows': dylib_ext = '.dll'; break; Windows: '.dll',
case 'macOS': dylib_ext = '.dylib'; break; macOS: '.dylib',
case 'Linux': dylib_ext = '.so'; break; Linux: '.so'
} }
print(os.platform())
dylib_ext = cases[os.platform()]
var MOD_EXT = '.cm' var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce' var ACTOR_EXT = '.ce'
@@ -51,14 +55,16 @@ var fd = use_embed('fd')
// Get the shop path from HOME environment // Get the shop path from HOME environment
var home = os.getenv('HOME') || os.getenv('USERPROFILE') var home = os.getenv('HOME') || os.getenv('USERPROFILE')
if (!home) { 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 shop_path = home + '/.cell'
var packages_path = shop_path + '/packages' var packages_path = shop_path + '/packages'
var core_path = packages_path + '/core' var core_path = packages_path + '/core'
if (!fd.is_dir(core_path)) { 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 = {} var use_cache = {}
@@ -79,7 +85,7 @@ function use_core(path) {
var script_blob = fd.slurp(file_path) var script_blob = fd.slurp(file_path)
var script = text(script_blob) var script = text(script_blob)
var mod = `(function setup_module(use){${script}})` 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]) var result = call(fn,sym, [use_core])
use_cache[cache_key] = result; use_cache[cache_key] = result;
return result; return result;
@@ -133,32 +139,27 @@ function log(name, args) {
var caller = caller_data(1) var caller = caller_data(1)
var msg = args[0] var msg = args[0]
switch(name) { if (name == 'console') {
case 'console': os.print(console_rec(caller.line, caller.file, msg))
os.print(console_rec(caller.line, caller.file, msg)) } else if (name == 'error') {
break if (msg == null) msg = Error()
case 'error': if (is_proto(msg, Error))
msg = msg ?? Error() msg = msg.name + ": " + msg.message + "\n" + msg.stack
if (is_proto(msg, Error)) os.print(console_rec(caller.line, caller.file, msg))
msg = msg.name + ": " + msg.message + "\n" + msg.stack } else if (name == 'system') {
os.print(console_rec(caller.line, caller.file, msg)) msg = "[SYSTEM] " + msg
break os.print(console_rec(caller.line, caller.file, msg))
case 'system': } else {
msg = "[SYSTEM] " + msg log.console(`unknown log type: ${name}`)
os.print(console_rec(caller.line, caller.file, msg))
break
default:
log.console(`unknown log type: ${name}`)
break
} }
} }
function disrupt(err) function actor_die(err)
{ {
if (is_function(err.toString)) { if (err && is_function(err.toString)) {
os.print(err.toString()) os.print(err.toString())
os.print("\n") os.print("\n")
os.print(err.stack) if (err.stack) os.print(err.stack)
} }
if (overling) { if (overling) {
@@ -185,14 +186,14 @@ function disrupt(err)
log.console(err.stack) 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" _cell.id = "newguy"
function create_actor(desc = {id:guid()}) { function create_actor(desc = {id:guid()}) {
@@ -241,11 +242,15 @@ os.runtime_env = runtime_env
$_.time_limit = function(requestor, seconds) $_.time_limit = function(requestor, seconds)
{ {
if (!pronto.is_requestor(requestor)) if (!pronto.is_requestor(requestor)) {
throw Error('time_limit: first argument must be a requestor'); log.error('time_limit: first argument must be a requestor')
if (!is_number(seconds) || seconds <= 0) disrupt
throw Error('time_limit: seconds must be a positive number'); }
if (!is_number(seconds) || seconds <= 0) {
log.error('time_limit: seconds must be a positive number')
disrupt
}
return function time_limit_requestor(callback, value) { return function time_limit_requestor(callback, value) {
pronto.check_callback(callback, 'time_limit') pronto.check_callback(callback, 'time_limit')
var finished = false var finished = false
@@ -260,7 +265,14 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = null timer_cancel = null
} }
if (requestor_cancel) { 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 requestor_cancel = null
} }
} }
@@ -268,15 +280,12 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = $_.delay(function() { timer_cancel = $_.delay(function() {
if (finished) return if (finished) return
def reason = make_reason(factory, 'Timeout.', seconds) def reason = make_reason(factory, 'Timeout.', seconds)
if (requestor_cancel) { safe_cancel_requestor(reason)
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
finished = true finished = true
callback(null, reason) callback(null, reason)
}, seconds) }, seconds)
try { function do_request() {
requestor_cancel = requestor(function(val, reason) { requestor_cancel = requestor(function(val, reason) {
if (finished) return if (finished) return
finished = true finished = true
@@ -286,16 +295,14 @@ $_.time_limit = function(requestor, seconds)
} }
callback(val, reason) callback(val, reason)
}, value) }, value)
} catch (ex) { } disruption {
cancel(ex) cancel(Error('requestor failed'))
callback(null, ex) callback(null, Error('requestor failed'))
} }
do_request()
return function(reason) { return function(reason) {
if (requestor_cancel) { safe_cancel_requestor(reason)
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
} }
} }
} }
@@ -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. // takes a function input value that will eventually be called with the current time in number form.
$_.portal = function(fn, port) { $_.portal = function(fn, port) {
if (portal) throw Error(`Already started a portal listening on ${portal.port}`) if (portal) {
if (!port) throw Error("Requires a valid port.") 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}`) log.system(`starting a portal on port ${port}`)
portal = enet.create_host({address: "any", port}) portal = enet.create_host({address: "any", port})
portal_fn = fn portal_fn = fn
} }
function handle_host(e) { function handle_host(e) {
switch (e.type) { if (e.type == "connect") {
case "connect": log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`)
log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`) peers[`${e.peer.address}:${e.peer.port}`] = e.peer
peers[`${e.peer.address}:${e.peer.port}`] = e.peer var queue = peer_queue.get(e.peer)
var queue = peer_queue.get(e.peer) if (queue) {
if (queue) { arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg))) log.system(`sent ${msg} out of queue`)
log.system(`sent ${msg} out of queue`)
peer_queue.delete(e.peer)
}
break
case "disconnect":
peer_queue.delete(e.peer) 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 if (data.data) populate_actor_addresses(data.data)
case "receive": turn(data)
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
} }
} }
@@ -487,10 +496,14 @@ $_.stop = function stop(actor) {
need_stop = true need_stop = true
return return
} }
if (!is_actor(actor)) if (!is_actor(actor)) {
throw Error('Can only call stop on an actor.') log.error('Can only call stop on an actor.')
if (is_null(underlings[actor[ACTORDATA].id])) disrupt
throw Error('Can only call stop on an underling or self.') }
if (is_null(underlings[actor[ACTORDATA].id])) {
log.error('Can only call stop on an underling or self.')
disrupt
}
sys_msg(actor, {kind:"stop"}) sys_msg(actor, {kind:"stop"})
} }
@@ -527,20 +540,22 @@ function actor_prep(actor, send) {
// Send a message immediately without queuing // Send a message immediately without queuing
function actor_send_immediate(actor, send) { function actor_send_immediate(actor, send) {
try { actor_send(actor, send)
actor_send(actor, send);
} catch (err) {
log.error("Failed to send immediate message:", err);
}
} }
function actor_send(actor, message) { function actor_send(actor, message) {
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
return return
if (!is_actor(actor) && !is_actor(actor.replycc)) throw Error(`Must send to an actor object. Attempted send to ${actor}`) if (!is_actor(actor) && !is_actor(actor.replycc)) {
log.error(`Must send to an actor object. Attempted send to ${actor}`)
if (!is_object(message)) throw Error('Must send an object record.') disrupt
}
if (!is_object(message)) {
log.error('Must send an object record.')
disrupt
}
// message to self // message to self
if (actor[ACTORDATA].id == _cell.id) { if (actor[ACTORDATA].id == _cell.id) {
@@ -583,12 +598,10 @@ function actor_send(actor, message) {
// Holds all messages queued during the current turn. // Holds all messages queued during the current turn.
var message_queue = [] 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 we've been flagged to stop, bail out before doing anything
if (need_stop) { if (need_stop) {
disrupt() actor_die()
message_queue = [] message_queue = []
return return
} }
@@ -608,19 +621,26 @@ var need_stop = false
var replies = {} var replies = {}
function send(actor, message, reply) { function send(actor, message, reply) {
if (!is_object(actor)) if (!is_object(actor)) {
throw Error(`Must send to an actor object. Provided: ${actor}`); log.error(`Must send to an actor object. Provided: ${actor}`)
disrupt
}
if (!is_object(message)) if (!is_object(message)) {
throw Error('Message must be an object') log.error('Message must be an object')
disrupt
}
var send_msg = {type:"user", data: message} var send_msg = {type:"user", data: message}
var target = actor
if (actor[HEADER] && actor[HEADER].replycc) { if (actor[HEADER] && actor[HEADER].replycc) {
var header = actor[HEADER] var header = actor[HEADER]
if (!header.replycc || !is_actor(header.replycc)) if (!header.replycc || !is_actor(header.replycc)) {
throw Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`) 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 send_msg.return = header.reply
} }
@@ -638,7 +658,7 @@ function send(actor, message, reply) {
} }
// Instead of sending immediately, queue it // Instead of sending immediately, queue it
actor_prep(actor, send_msg); actor_prep(target, send_msg);
} }
stone(send) stone(send)
@@ -669,7 +689,7 @@ overling = _cell.args.overling
$_.overling = overling $_.overling = overling
root = _cell.args.root root = _cell.args.root
root ??= $_.self if (root == null) root = $_.self
if (overling) { if (overling) {
$_.couple(overling) // auto couple to overling $_.couple(overling) // auto couple to overling
@@ -705,36 +725,35 @@ function handle_actor_disconnect(id) {
delete greeters[id] delete greeters[id]
} }
log.system(`actor ${id} disconnected`) 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) function handle_sysym(msg)
{ {
var from var from
switch(msg.kind) { if (msg.kind == 'stop') {
case 'stop': actor_die("got stop message")
disrupt("got stop message") } else if (msg.kind == 'underling') {
break from = msg.from
case 'underling': var greeter = greeters[from[ACTORDATA].id]
from = msg.from if (greeter) greeter(msg.message)
var greeter = greeters[from[ACTORDATA].id] if (msg.message.type == 'disrupt')
if (greeter) greeter(msg.message) delete underlings[from[ACTORDATA].id]
if (msg.message.type == 'disrupt') } else if (msg.kind == 'contact') {
delete underlings[from[ACTORDATA].id] if (portal_fn) {
break var letter2 = msg.data
case 'contact': letter2[HEADER] = msg
if (portal_fn) { delete msg.data
var letter2 = msg.data portal_fn(letter2)
letter2[HEADER] = msg } else {
delete msg.data log.error('Got a contact message, but no portal is established.')
portal_fn(letter2) disrupt
} else throw Error('Got a contact message, but no portal is established.') }
break } else if (msg.kind == 'couple') {
case 'couple': // from must be notified when we die // from must be notified when we die
from = msg.from from = msg.from
underlings[from[ACTORDATA].id] = true underlings[from[ACTORDATA].id] = true
log.system(`actor ${from} is coupled to me`) log.system(`actor ${from} is coupled to me`)
break
} }
} }
@@ -744,30 +763,27 @@ function handle_message(msg) {
return return
} }
switch (msg.type) { if (msg.type == "user") {
case "user": var letter = msg.data // what the sender really sent
var letter = msg.data // what the sender really sent _ObjectDefineProperty(letter, HEADER, {
_ObjectDefineProperty(letter, HEADER, { value: msg, enumerable: false
value: msg, enumerable: false })
}) _ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true value: { reply: msg.reply }, enumerable: false
value: { reply: msg.reply }, enumerable: false })
})
if (msg.return) {
if (msg.return) { var fn = replies[msg.return]
var fn = replies[msg.return] if (fn) fn(letter)
if (fn) fn(letter) delete replies[msg.return]
delete replies[msg.return]
return
}
if (receive_fn) receive_fn(letter)
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() function enet_check()
{ {
@@ -792,8 +808,10 @@ if (!locator) {
locator = shop.resolve_locator(_cell.args.program + ".ce", pkg) locator = shop.resolve_locator(_cell.args.program + ".ce", pkg)
} }
if (!locator) if (!locator) {
throw Error(`Main program ${_cell.args.program} could not be found`) os.print(`Main program ${_cell.args.program} could not be found\n`)
os.exit(1)
}
$_.clock(_ => { $_.clock(_ => {
// Get capabilities for the main program // Get capabilities for the main program
@@ -818,7 +836,6 @@ $_.clock(_ => {
var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env]) var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env])
if (val) if (val)
throw Error('Program must not return anything'); log.error('Program must not return anything')
disrupt
}) })
})()

View File

@@ -577,7 +577,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
JSValue js_os_use(JSContext *js) { JSValue js_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id); JS_NewClassID(&js_dylib_class_id);
JS_NewClass(JS_GetRuntime(js), js_dylib_class_id, &js_dylib_class); JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js); JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs)); JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));

View File

@@ -44,7 +44,12 @@ src += [ # core
'wildmatch.c', 'wildmatch.c',
'qjs_actor.c', 'qjs_actor.c',
'miniz.c', 'miniz.c',
'quickjs.c', 'runtime.c',
'cell_js.c',
'tokenize.c',
'parse.c',
'mach.c',
'mcode.c',
'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c' 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'
] ]

View File

@@ -571,13 +571,13 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JSValue js_enet_use(JSContext *ctx) JSValue js_enet_use(JSContext *ctx)
{ {
JS_NewClassID(&enet_host_id); JS_NewClassID(&enet_host_id);
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host); JS_NewClass(ctx, enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx); JSValue host_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs)); JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto); JS_SetClassProto(ctx, enet_host_id, host_proto);
JS_NewClassID(&enet_peer_class_id); JS_NewClassID(&enet_peer_class_id);
JS_NewClass(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class); JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx); JSValue peer_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs)); JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto); JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);

4
qop.c
View File

@@ -458,13 +458,13 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
JSValue js_qop_use(JSContext *js) { JSValue js_qop_use(JSContext *js) {
JS_NewClassID(&js_qop_archive_class_id); JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class); JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js); JSValue archive_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs)); JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto); JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
JS_NewClassID(&js_qop_writer_class_id); JS_NewClassID(&js_qop_writer_class_id);
JS_NewClass(JS_GetRuntime(js), js_qop_writer_class_id, &js_qop_writer_class); JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
JSValue writer_proto = JS_NewObject(js); JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs)); JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs));
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto); JS_SetClassProto(js, js_qop_writer_class_id, writer_proto);

View File

@@ -26,6 +26,7 @@ static int run_test_suite(size_t heap_size);
cell_rt *root_cell = NULL; cell_rt *root_cell = NULL;
static char *core_path = NULL; static char *core_path = NULL;
static JSRuntime *g_runtime = NULL;
// Get the home directory // Get the home directory
static const char* get_home_dir(void) { static const char* get_home_dir(void) {
@@ -165,17 +166,31 @@ JSValue js_wota_use(JSContext *js);
void script_startup(cell_rt *prt) void script_startup(cell_rt *prt)
{ {
JSRuntime *rt = JS_NewRuntime(); if (!g_runtime) {
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt); g_runtime = JS_NewRuntime();
JSContext *js = JS_NewContext(rt); }
JSContext *js = JS_NewContext(g_runtime);
JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt);
JS_SetContextOpaque(js, prt); JS_SetContextOpaque(js, prt);
prt->context = js; prt->context = js;
/* Register all GCRef fields so the Cheney GC can relocate them. */
JS_AddGCRef(js, &prt->idx_buffer_ref);
JS_AddGCRef(js, &prt->on_exception_ref);
JS_AddGCRef(js, &prt->message_handle_ref);
JS_AddGCRef(js, &prt->unneeded_ref);
JS_AddGCRef(js, &prt->actor_sym_ref);
prt->idx_buffer_ref.val = JS_NULL;
prt->on_exception_ref.val = JS_NULL;
prt->message_handle_ref.val = JS_NULL;
prt->unneeded_ref.val = JS_NULL;
prt->actor_sym_ref.val = JS_NULL;
cell_rt *crt = JS_GetContextOpaque(js); cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js)); JS_FreeValue(js, js_blob_use(js));
// Load and compile engine.cm // Load and parse engine.cm to AST
size_t engine_size; size_t engine_size;
char *data = load_core_file(ENGINE, &engine_size); char *data = load_core_file(ENGINE, &engine_size);
if (!data) { if (!data) {
@@ -183,10 +198,15 @@ void script_startup(cell_rt *prt)
return; return;
} }
JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE); cJSON *ast = JS_ASTTree(data, engine_size, ENGINE);
free(data); free(data);
if (JS_IsException(bytecode)) { if (!ast) {
uncaught_exception(js, bytecode); printf("ERROR: Failed to parse %s\n", ENGINE);
return;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
return; return;
} }
@@ -197,8 +217,8 @@ void script_startup(cell_rt *prt)
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js)); JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js)); JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
crt->actor_sym = JS_NewObject(js); crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym)); JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null) // Always set init (even if null)
if (crt->init_wota) { if (crt->init_wota) {
@@ -216,9 +236,10 @@ void script_startup(cell_rt *prt)
// Stone the environment // Stone the environment
hidden_env = JS_Stone(js, hidden_env); hidden_env = JS_Stone(js, hidden_env);
// Integrate and run // Run through MACH VM
crt->state = ACTOR_RUNNING; 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); uncaught_exception(js, v);
crt->state = ACTOR_IDLE; crt->state = ACTOR_IDLE;
set_actor_state(crt); set_actor_state(crt);

View File

@@ -144,7 +144,7 @@ JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYP
#define QJSCLASSPREP_NO_FUNCS(TYPE) \ #define QJSCLASSPREP_NO_FUNCS(TYPE) \
JS_NewClassID(&js_##TYPE##_id);\ JS_NewClassID(&js_##TYPE##_id);\
JS_NewClass(JS_GetRuntime(js), js_##TYPE##_id, &js_##TYPE##_class);\ JS_NewClass(js, js_##TYPE##_id, &js_##TYPE##_class);\
JSValue TYPE##_proto = JS_NewObject(js); \ JSValue TYPE##_proto = JS_NewObject(js); \
JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \

View File

@@ -24,21 +24,26 @@ typedef struct letter {
typedef struct cell_rt { typedef struct cell_rt {
JSContext *context; JSContext *context;
JSValue idx_buffer;
JSValue on_exception; /* JSValues on the GC heap — each paired with a JSGCRef so the
JSValue message_handle; Cheney GC can relocate them during compaction. */
JSGCRef idx_buffer_ref;
JSGCRef on_exception_ref;
JSGCRef message_handle_ref;
JSGCRef unneeded_ref;
JSGCRef actor_sym_ref;
void *init_wota; void *init_wota;
/* Protects JSContext usage */ /* Protects JSContext usage */
pthread_mutex_t *mutex; /* for everything else */ pthread_mutex_t *mutex; /* for everything else */
pthread_mutex_t *msg_mutex; /* For message queue and timers queue */ pthread_mutex_t *msg_mutex; /* For message queue and timers queue */
char *id; char *id;
int idx_count; int idx_count;
/* The mailbox for incoming messages + a dedicated lock for it: */ /* The "mailbox" for incoming messages + a dedicated lock for it: */
letter *letters; letter *letters;
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */ /* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
@@ -47,14 +52,11 @@ typedef struct cell_rt {
int state; int state;
uint32_t ar; // timer for unneeded uint32_t ar; // timer for unneeded
double ar_secs; // time for unneeded double ar_secs; // time for unneeded
JSValue unneeded; // fn to call before unneeded
int disrupt; int disrupt;
int main_thread_only; int main_thread_only;
int affinity; int affinity;
JSValue actor_sym;
const char *name; // human friendly name const char *name; // human friendly name
cell_hook trace_hook; cell_hook trace_hook;
} cell_rt; } cell_rt;
@@ -63,8 +65,6 @@ cell_rt *create_actor(void *wota);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar); const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_disrupt(cell_rt *actor); void actor_disrupt(cell_rt *actor);
JSValue actor_sym(cell_rt *actor);
const char *send_message(const char *id, void *msg); const char *send_message(const char *id, void *msg);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar); const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds); void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);

12740
source/cell_js.c Normal file

File diff suppressed because it is too large Load Diff

3316
source/mach.c Normal file

File diff suppressed because it is too large Load Diff

3461
source/mcode.c Normal file

File diff suppressed because it is too large Load Diff

2087
source/parse.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -74,7 +74,7 @@ JSC_CCALL(os_register_actor,
JS_ToFloat64(js, &ar, argv[3]); JS_ToFloat64(js, &ar, argv[3]);
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar); const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err); if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err);
rt->message_handle = JS_DupValue(js, argv[1]); rt->message_handle_ref.val = argv[1];
rt->context = js; rt->context = js;
JS_FreeCString(js, id); JS_FreeCString(js, id);
) )
@@ -106,8 +106,7 @@ JSC_SCALL(actor_setname,
JSC_CCALL(actor_on_exception, JSC_CCALL(actor_on_exception,
cell_rt *rt = JS_GetContextOpaque(js); cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, rt->on_exception); rt->on_exception_ref.val = argv[0];
rt->on_exception = JS_DupValue(js,argv[0]);
) )
JSC_CCALL(actor_clock, JSC_CCALL(actor_clock,

2007
source/quickjs-internal.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -367,10 +367,10 @@ JSRuntime *JS_NewRuntime (void);
void JS_SetRuntimeInfo (JSRuntime *rt, const char *info); void JS_SetRuntimeInfo (JSRuntime *rt, const char *info);
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit); void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
/* use 0 to disable maximum stack size check */ /* use 0 to disable maximum stack size check */
void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size); void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
/* should be called when changing thread to update the stack top value /* should be called when changing thread to update the stack top value
used to check stack overflow. */ used to check stack overflow. */
void JS_UpdateStackTop (JSRuntime *rt); void JS_UpdateStackTop (JSContext *ctx);
void JS_FreeRuntime (JSRuntime *rt); void JS_FreeRuntime (JSRuntime *rt);
void *JS_GetRuntimeOpaque (JSRuntime *rt); void *JS_GetRuntimeOpaque (JSRuntime *rt);
void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque); void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque);
@@ -429,9 +429,9 @@ JSClassID JS_NewClassID (JSClassID *pclass_id);
/* Returns the class ID if `v` is an object, otherwise returns /* Returns the class ID if `v` is an object, otherwise returns
* JS_INVALID_CLASS_ID. */ * JS_INVALID_CLASS_ID. */
JSClassID JS_GetClassID (JSValue v); JSClassID JS_GetClassID (JSValue v);
int JS_NewClass (JSRuntime *rt, JSClassID class_id, int JS_NewClass (JSContext *ctx, JSClassID class_id,
const JSClassDef *class_def); const JSClassDef *class_def);
int JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id); int JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id);
extern JSClassID js_class_id_alloc; extern JSClassID js_class_id_alloc;
@@ -740,7 +740,7 @@ JSValue JS_JSONStringify (JSContext *ctx, JSValue obj,
/* return != 0 if the JS code needs to be interrupted */ /* return != 0 if the JS code needs to be interrupted */
typedef int JSInterruptHandler (JSRuntime *rt, void *opaque); typedef int JSInterruptHandler (JSRuntime *rt, void *opaque);
void JS_SetInterruptHandler (JSRuntime *rt, JSInterruptHandler *cb, void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
void *opaque); void *opaque);
/* select which debug info is stripped from the compiled code */ /* select which debug info is stripped from the compiled code */
#define JS_STRIP_SOURCE (1 << 0) /* strip source code */ #define JS_STRIP_SOURCE (1 << 0) /* strip source code */

13756
source/runtime.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -266,11 +266,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context; JSContext *js = actor->context;
JS_FreeValue(js, actor->idx_buffer); JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_FreeValue(js, actor->message_handle); JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_FreeValue(js, actor->on_exception); JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_FreeValue(js, actor->unneeded); JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_FreeValue(js, actor->actor_sym); JS_DeleteGCRef(js, &actor->actor_sym_ref);
for (int i = 0; i < hmlen(actor->timers); i++) { for (int i = 0; i < hmlen(actor->timers); i++) {
JS_FreeValue(js, actor->timers[i].value); JS_FreeValue(js, actor->timers[i].value);
@@ -289,10 +289,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters); arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js); JS_SetInterruptHandler(js, NULL, NULL);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_FreeContext(js); JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id); free(actor->id);
pthread_mutex_unlock(actor->mutex); pthread_mutex_unlock(actor->mutex);
@@ -419,8 +417,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1; actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) { if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL); JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret); uncaught_exception(actor->context, ret);
} }
@@ -434,14 +432,13 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
{ {
if (actor->disrupt) return; if (actor->disrupt) return;
JS_FreeValue(actor->context, actor->unneeded);
if (!JS_IsFunction(fn)) { if (!JS_IsFunction(fn)) {
actor->unneeded = JS_NULL; actor->unneeded_ref.val = JS_NULL;
goto END; goto END;
} }
actor->unneeded = JS_DupValue(actor->context, fn); actor->unneeded_ref.val = fn;
actor->ar_secs = seconds; actor->ar_secs = seconds;
END: END:
@@ -493,11 +490,10 @@ cell_rt *create_actor(void *wota)
actor->heap = mi_heap_new(); actor->heap = mi_heap_new();
#endif #endif
actor->init_wota = wota; actor->init_wota = wota;
actor->idx_buffer = JS_NULL; /* GCRef fields are registered after JSContext creation in script_startup.
actor->message_handle = JS_NULL; For now, zero-init from calloc is sufficient (val = 0 = JS_MKVAL(JS_TAG_INT,0),
actor->unneeded = JS_NULL; which is not a pointer so GC-safe). The actual JS_NULL assignment and
actor->on_exception = JS_NULL; JS_AddGCRef happen in script_startup. */
actor->actor_sym = JS_NULL;
arrsetcap(actor->letters, 5); arrsetcap(actor->letters, 5);
@@ -587,7 +583,7 @@ void actor_turn(cell_rt *actor)
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size); JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size);
blob_destroy(l.blob_data); blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg); result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result); uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg); JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) { } else if (l.type == LETTER_CALLBACK) {

View File

@@ -118,11 +118,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context; JSContext *js = actor->context;
JS_FreeValue(js, actor->idx_buffer); JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_FreeValue(js, actor->message_handle); JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_FreeValue(js, actor->on_exception); JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_FreeValue(js, actor->unneeded); JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_FreeValue(js, actor->actor_sym); JS_DeleteGCRef(js, &actor->actor_sym_ref);
/* Free timer callbacks stored in actor */ /* Free timer callbacks stored in actor */
for (int i = 0; i < hmlen(actor->timers); i++) { for (int i = 0; i < hmlen(actor->timers); i++) {
@@ -142,10 +142,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters); arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js); JS_SetInterruptHandler(js, NULL, NULL);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_FreeContext(js); JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id); free(actor->id);
free(actor); free(actor);
@@ -157,14 +155,13 @@ void actor_free(cell_rt *actor)
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
{ {
if (actor->disrupt) return; if (actor->disrupt) return;
JS_FreeValue(actor->context, actor->unneeded);
if (!JS_IsFunction(fn)) {
if (!JS_IsFunction(actor->context, fn)) { actor->unneeded_ref.val = JS_NULL;
actor->unneeded = JS_NULL;
goto END; goto END;
} }
actor->unneeded = JS_DupValue(actor->context, fn); actor->unneeded_ref.val = fn;
actor->ar_secs = seconds; actor->ar_secs = seconds;
END: END:
@@ -257,8 +254,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1; actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) { if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL); JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret); uncaught_exception(actor->context, ret);
} }
@@ -328,11 +325,7 @@ cell_rt *create_actor(void *wota)
{ {
cell_rt *actor = calloc(sizeof(*actor), 1); cell_rt *actor = calloc(sizeof(*actor), 1);
actor->init_wota = wota; actor->init_wota = wota;
actor->idx_buffer = JS_NULL; /* GCRef fields are registered after JSContext creation in script_startup. */
actor->message_handle = JS_NULL;
actor->unneeded = JS_NULL;
actor->on_exception = JS_NULL;
actor->actor_sym = JS_NULL;
arrsetcap(actor->letters, 5); arrsetcap(actor->letters, 5);
@@ -408,7 +401,7 @@ void actor_turn(cell_rt *actor)
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size); JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size);
blob_destroy(l.blob_data); blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg); result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result); uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg); JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) { } else if (l.type == LETTER_CALLBACK) {

1432
source/tokenize.c Normal file

File diff suppressed because it is too large Load Diff