This commit is contained in:
2025-12-03 06:41:18 -06:00
parent 0c5609c4e9
commit 814ee7c0db
8 changed files with 428 additions and 716 deletions

View File

@@ -9,619 +9,58 @@ cell.os = null
var dylib_ext
function mod_scriptor(name, script)
{
return `(function setup_${name}_module($_){${script}})`
}
switch(os.platform()) {
case 'Windows': dylib_ext = '.dll'; break;
case 'macOS': dylib_ext = '.dylib'; break;
case 'Linux': dylib_ext = '.so'; break;
}
var load_internal = os.load_internal
function use_embed(name) {
return load_internal(`js_${name}_use`)
}
var actor_mod = use_embed('actor')
var wota = use_embed('wota')
var nota = use_embed('nota')
var enet = use_embed('enet')
var fd = use_embed('fd')
var ENETSERVICE = 0.1
var REPLYTIMEOUT = 60 // seconds before replies are ignored
var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce'
globalThis.pi = 3.1415926535897932
var open_dl = {}
function get_import_package(name) {
var parts = name.split('/')
if (parts.length > 1)
return parts[0]
return null
}
function get_import_dl(name) {
var pkg = get_import_package(name)
if (!pkg) return null
if (open_dl[pkg]) return open_dl[pkg]
var dlpath = `.cell/modules/${pkg}/${pkg}${dylib_ext}`
var dl = os.dylib_open(dlpath)
if (dl) {
open_dl[pkg] = dl
return dl
}
return null
}
function get_c_symbol(name) {
var dl = get_import_dl(name)
var symname = `js_${name.replace('/', '_')}_use`
if (dl)
return os.dylib_symbol(dl, symname)
else
return load_internal(symname)
}
function caller_data(depth = 0)
{
var file = "nofile"
var line = 0
var caller = new Error().stack.split("\n")[1+depth]
if (caller) {
var md = caller.match(/\((.*)\:/)
var m = md ? md[1] : "SCRIPT"
if (m) file = m
md = caller.match(/\:(\d*)\)/)
m = md ? md[1] : 0
if (m) line = m
}
return {file,line}
}
cell.args = cell.hidden.init
cell.args ??= {}
cell.id ??= "newguy"
function console_rec(line, file, msg) {
return `[${cell.id.slice(0,5)}] [${file}:${line}]: ${msg}\n`
// time: [${time.text("mb d yyyy h:nn:ss")}]
}
globalThis.log = {}
log.console = function(msg)
{
var caller = caller_data(1)
os.print(console_rec(caller.line, caller.file, msg))
}
log.error = function(msg = new Error())
{
var caller = caller_data(1)
if (msg instanceof Error)
msg = msg.name + ": " + msg.message + "\n" + msg.stack
os.print(console_rec(caller.line,caller.file,msg))
}
log.system = function(msg) {
msg = "[SYSTEM] " + msg
log.console(msg)
}
var shop_path = '.cell'
if (!fd.stat('.cell').isDirectory) {
log.console("No cell directory found. Make one.\n");
os.exit(1);
}
function write_file(path, blob) {
var fd_handle = fd.open(path, 'w')
fd.write(fd_handle, blob)
fd.close(fd_handle)
}
function mkdir_p(dir) {
if (dir == '' || dir == '.') return
var st = null
try { st = fd.stat(dir) } catch {}
if (!st || !st.isDirectory) {
mkdir_p(dir.substring(0, dir.lastIndexOf('/')))
try { fd.mkdir(dir) } catch {}
}
var load_internal = os.load_internal
function use_embed(name) {
return load_internal(`js_${name}_use`)
}
var qop = use_embed('qop')
var core_qop = qop.open(hidden.core_qop_blob)
var utf8 = use_embed('utf8')
function disrupt(err)
{
if (overling) {
if (err) {
// with an err, this is a forceful disrupt
var reason = (err instanceof Error) ? err.stack : err
report_to_overling({type:'disrupt', reason})
} else
report_to_overling({type:'stop'})
}
if (underlings) {
for (var id of underlings) {
log.console(`calling on ${id} to disrupt too`)
$_.stop(create_actor({id}))
}
}
if (err) {
log.console(err);
if (err.stack)
log.console(err.stack)
}
actor_mod.disrupt()
}
actor_mod.on_exception(disrupt)
var js = use_embed('js')
var use_cache = {}
var BASEPATH = 'base' + MOD_EXT
var script = utf8.decode(core_qop.read(BASEPATH))
var fnname = "base"
script = `(function ${fnname}() { ${script}; })`
js.eval(BASEPATH, script)()
// Three domains:
// core
// local
// module scope
var inProgress = {}
var loadingStack = []
function use_core(path) {
var cache_path = `2:${path}`;
if (use_cache[cache_path])
return use_cache[cache_path];
// Track current package context for nested use() calls
var current_package = null
// Get package name from a resolved path
function get_package_from_path(path) {
if (!path) return null
var modules_prefix = '.cell/modules/'
if (path.startsWith(modules_prefix)) {
var rest = path.substring(modules_prefix.length)
var slash_idx = rest.indexOf('/')
if (slash_idx > 0) {
return rest.substring(0, slash_idx)
var sym = use_embed(path)
var script = core_qop.read(path + MOD_EXT);
if (script) {
script = utf8.decode(script)
var mod = mod_scriptor(path, script)
var fn = js.eval(path, mod)
var result = fn.call(sym);
use_cache[cache_path] = result;
return result;
}
}
return null
use_cache[cache_path] = sym;
return sym;
}
var config = null
// Scope definitions
var SCOPE_LOCAL = 0
var SCOPE_DEPENDENCY = 1
var SCOPE_CORE = 2
function resolve_path(requested, pkg_context, ext) {
if (requested.endsWith(ext)) ext = ''
// Check for underscore prefix in imports - not allowed except for local access
if (requested.includes('/')) {
var parts = requested.split('/')
if (parts.some(part => part.startsWith('_'))) {
throw new Error(`Cannot import modules with underscore prefix: ${requested}`)
}
} else if (requested.startsWith('_')) {
throw new Error(`Cannot import modules with underscore prefix: ${requested}`)
}
var dependencies = (config && config.dependencies) ? config.dependencies : {}
// Helper to check file existence and return result object
function check(path, pkg, isCore, scope) {
if (isCore) {
try {
var blob = core_qop.read(path)
if (blob) {
return { path: path, package_name: null, isCore: true, scope: scope }
}
return null
} catch (e) { return null }
}
// Check for private files if accessing from a different package
if (pkg && current_package && pkg != current_package) {
var filename = path.substring(path.lastIndexOf('/') + 1)
if (filename.startsWith('_')) return null
}
if (fd.is_file(path)) {
return { path: path, package_name: pkg, isCore: false, scope: scope }
}
return null
}
// Step 1: current package (Local)
if (pkg_context) {
var pkg_path = '.cell/modules/' + pkg_context + '/' + requested + ext
var underscore_pkg_path = '.cell/modules/' + pkg_context + '/_' + requested + ext
var res = check(pkg_path, pkg_context, false, SCOPE_LOCAL)
var underscore_res = check(underscore_pkg_path, pkg_context, false, SCOPE_LOCAL)
// If both regular and underscore versions exist, throw error
if (res && underscore_res) {
throw new Error(`Module conflict: both '${requested}' and '_${requested}' exist in package '${pkg_context}'`)
}
// Prefer regular version, but allow underscore version if no regular exists
if (res) return res
if (underscore_res) return underscore_res
// Check if package is locally replaced
if (config && config.replace && config.replace[pkg_context]) {
var replace_path = config.replace[pkg_context]
var full_path = replace_path + '/' + requested + ext
var underscore_full_path = replace_path + '/_' + requested + ext
res = check(full_path, pkg_context, false, SCOPE_LOCAL)
underscore_res = check(underscore_full_path, pkg_context, false, SCOPE_LOCAL)
if (res && underscore_res) {
throw new Error(`Module conflict: both '${requested}' and '_${requested}' exist in replaced package '${pkg_context}'`)
}
if (res) return res
if (underscore_res) return underscore_res
}
} else {
// Top-level local
var project_path = requested + ext
var underscore_project_path = '_' + requested + ext
var res = check(project_path, null, false, SCOPE_LOCAL)
var underscore_res = check(underscore_project_path, null, false, SCOPE_LOCAL)
// If both regular and underscore versions exist, throw error
if (res && underscore_res) {
throw new Error(`Module conflict: both '${requested}' and '_${requested}' exist locally`)
}
// Prefer regular version, but allow underscore version if no regular exists
if (res) return res
if (underscore_res) return underscore_res
}
// Step 2: dependencies (explicit alias first) and replace directives
if (requested.includes('/')) {
var parts = requested.split('/')
var pkg_alias = parts[0]
var sub_path = parts.slice(1).join('/')
// Check for replace directive first
if (config && config.replace && config.replace[pkg_alias]) {
var replace_path = config.replace[pkg_alias]
var full_path = replace_path + '/' + (sub_path || pkg_alias) + ext
var res = check(full_path, pkg_alias, false, SCOPE_DEPENDENCY)
if (res) return res
} else if (dependencies[pkg_alias]) {
var dep_path = '.cell/modules/' + pkg_alias + '/' + sub_path + ext
var res = check(dep_path, pkg_alias, false, SCOPE_DEPENDENCY)
if (res) return res
}
// Also check if it's just a module in .cell/modules even if not in dependencies (implicit)
var implicit_path = '.cell/modules/' + requested + ext
var res = check(implicit_path, pkg_alias, false, SCOPE_DEPENDENCY)
if (res) return res
} else {
// Check replace directives for simple names
if (config && config.replace && config.replace[requested]) {
var replace_path = config.replace[requested]
var full_path = replace_path + '/' + requested + ext
var res = check(full_path, requested, false, SCOPE_DEPENDENCY)
if (res) return res
}
// Check dependencies for simple names
for (var alias in dependencies) {
if (alias == requested) {
var dep_simple = '.cell/modules/' + alias + '/' + requested + ext
var res = check(dep_simple, alias, false, SCOPE_DEPENDENCY)
if (res) return res
}
}
// Implicit check
var implicit_path = '.cell/modules/' + requested + '/' + requested + ext
var res = check(implicit_path, requested, false, SCOPE_DEPENDENCY)
if (res) return res
}
// Step 3: core
var core_res = check(requested + ext, null, true, SCOPE_CORE)
return core_res
}
function get_compiled_path(resolved) {
var build_base = '.cell/build/'
if (resolved.isCore) {
return build_base + 'core/' + resolved.path + '.o'
} else if (resolved.package_name) {
// If it's in .cell/modules/<pkg>/...
var prefix = '.cell/modules/' + resolved.package_name + '/'
if (resolved.path.startsWith(prefix)) {
return build_base + 'modules/' + resolved.package_name + '/' + resolved.path.substring(prefix.length) + '.o'
}
// This seems correct for the "modules" case.
log.console(json.encode(resolved))
return build_base + 'modules/' + resolved.package_name + '/' + resolved.path.substring(resolved.path.lastIndexOf('/') + 1) + '.o'
} else {
// Local project file
return build_base + 'local/' + resolved.path + '.o'
}
}
var open_dl = {}
function get_c_symbol(requested, pkg_context) {
// Construct symbol name: js_x_y_z_use
var symname = `js_${requested.replace(/\//g, '_')}_use`
// 1. Check Local
if (!pkg_context) {
// Check local dylib: .cell/local/local.dylib
var local_dl_path = '.cell/local/local' + dylib_ext
if (fd.is_file(local_dl_path)) {
var dl = open_dl['local']
if (!dl) {
try {
dl = os.dylib_open(local_dl_path)
open_dl['local'] = dl
} catch(e) {}
}
if (dl) {
try {
// For local symbols, use js_local_<name>_use
var local_symname = `js_local_${requested.replace(/\//g, '_')}_use`
var sym = os.dylib_symbol(dl, local_symname)
if (sym) {
return { symbol: sym, scope: SCOPE_LOCAL }
} else {
}
} catch(e) {
}
}
}
}
// 2. Check Modules
// Determine package name from requested path
var pkg_name = null
if (pkg_context) pkg_name = pkg_context
else if (requested.includes('/')) pkg_name = requested.split('/')[0]
else pkg_name = requested
if (pkg_name) {
var mod_dl_path = `.cell/modules/${pkg_name}/${pkg_name}${dylib_ext}`
if (fd.is_file(mod_dl_path)) {
var dl = open_dl[pkg_name]
if (!dl) {
try {
dl = os.dylib_open(mod_dl_path)
open_dl[pkg_name] = dl
} catch(e) {}
}
if (dl) {
try {
var sym = os.dylib_symbol(dl, symname)
if (sym) return { symbol: sym, scope: SCOPE_DEPENDENCY }
} catch(e) {}
}
}
}
// 3. Check Core (Internal)
var internal_sym = load_internal(symname)
if (internal_sym) return { symbol: internal_sym, scope: SCOPE_CORE }
return null
}
function get_module(name, pkg_context) {
return resolve_path(name, pkg_context, MOD_EXT)
}
function get_actor_script(name, pkg_context) {
return resolve_path(name, pkg_context, ACTOR_EXT)
}
globalThis.use = function use(file, ...args) {
var requested = file
// Find C symbol
var c_res = get_c_symbol(requested, current_package)
// Find Module
var mod_res = get_module(requested, current_package)
var c_mod = null
var resolved = null
// Decision logic
if (c_res && mod_res) {
if (c_res.scope < mod_res.scope) {
// C symbol is more specific
c_mod = c_res.symbol
resolved = null
} else if (mod_res.scope < c_res.scope) {
// Module is more specific
c_mod = null
resolved = mod_res
} else {
// Same scope - use both (C symbol as context)
c_mod = c_res.symbol
resolved = mod_res
}
} else if (c_res) {
c_mod = c_res.symbol
} else if (mod_res) {
resolved = mod_res
} else {
// Try embed as fallback for core
var embed_mod = use_embed(requested)
if (embed_mod) c_mod = embed_mod
}
if (!c_mod && !resolved)
throw new Error(`Module ${file} could not be found (package context: ${current_package || 'none'})`)
// Generate cache key
var cache_key = resolved
? (resolved.isCore ? 'core:' + resolved.path : resolved.path)
: (c_mod ? 'c:' + requested : requested)
if (use_cache[cache_key]) return use_cache[cache_key]
// log.console(`cache miss: ${cache_key}`)
// If we have a C module, use it as context
var context = {}
if (c_mod) {
context = c_mod
}
// If we have a script, run it
var ret = c_mod
if (resolved) {
var path = resolved.path
var isCore = resolved.isCore
var module_package = resolved.package_name
// Check for circular dependencies
if (path && loadingStack.includes(path)) {
let cycleIndex = loadingStack.indexOf(path)
let cyclePath = loadingStack.slice(cycleIndex).concat(path)
throw new Error(`Circular dependency: ${cyclePath.join(" -> ")}`)
}
inProgress[path] = true
loadingStack.push(path)
var prev_package = current_package
current_package = module_package
var compiledPath = get_compiled_path(resolved)
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
var useCompiled = false
var srcStat = fd.stat(path)
var compiledStat = fd.stat(compiledPath)
// Always compile from source - never use precompiled for regular modules
// if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) {
// useCompiled = true
// }
var fn
var mod_name = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'))
if (useCompiled) {
var compiledBlob = fd.slurp(compiledPath)
fn = js.compile_unblob(compiledBlob)
fn = js.eval_compile(fn)
log.console("use: using compiled version " + compiledPath)
} else {
if (isCore) {
var script = utf8.decode(core_qop.read(path))
var mod_script = `(function setup_${requested.replace(/[^a-zA-Z0-9_]/g, '_')}_module(arg, $_){${script};})`
fn = js.compile(path, mod_script)
// Save compiled version
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
var compiled = js.compile_blob(fn)
write_file(compiledPath, compiled)
fn = js.eval_compile(fn)
} else {
var script = utf8.decode(fd.slurp(path))
var mod_script = `(function setup_${mod_name}_module(arg, $_){${script};})`
fn = js.compile(path, mod_script)
// Save compiled version to .cell directory
var compiled = js.compile_blob(fn)
write_file(compiledPath, compiled)
fn = js.eval_compile(fn)
}
}
ret = fn.call(context, args, $_)
current_package = prev_package
loadingStack.pop()
delete inProgress[path]
}
if (!ret && c_mod) {
log.console("use: script returned nothing, using c_mod")
ret = c_mod
}
else if (!ret) throw new Error(`Use must be used with a module, but ${file} doesn't return a value`)
use_cache[cache_key] = ret
return ret
}
globalThis.json = use('json')
var time = use('time')
var st_now = time.number()
var shop = use('shop')
config = shop.load_config()
var default_config = {
ar_timer: 60,
actor_memory:0,
net_service:0.1,
reply_timeout:60,
main: false,
}
config ??= {}
config.system ??= {}
config.system.__proto__ = default_config
cell.config = config
ENETSERVICE = config.system.net_service
REPLYTIMEOUT = config.system.reply_timeout
globalThis.text = use('text')
// Load actor-specific configuration
function load_actor_config(program) {
// Extract actor name from program path
// e.g., "prosperon/_sdl_video" or "extramath/spline"
var actor_name = program
if (program.includes('/')) {
actor_name = program
}
if (config.actors && config.actors[actor_name]) {
for (var key in config.actors[actor_name])
cell.args[key] = config.actors[actor_name][key]
}
}
globalThis.use = use_core
var blob = use('blob')
var blob_stone = blob.prototype.stone
@@ -657,6 +96,140 @@ stone.p = function(object)
return Object.isFrozen(object)
}
var actor_mod = use_embed('actor')
var wota = use_embed('wota')
var nota = use_embed('nota')
var fd = use_embed('fd')
globalThis.text = use('text')
var ENETSERVICE = 0.1
var REPLYTIMEOUT = 60 // seconds before replies are ignored
globalThis.pi = 3.1415926535897932
function caller_data(depth = 0)
{
var file = "nofile"
var line = 0
var caller = new Error().stack.split("\n")[1+depth]
if (caller) {
var md = caller.match(/\((.*)\:/)
var m = md ? md[1] : "SCRIPT"
if (m) file = m
md = caller.match(/\:(\d*)\)/)
m = md ? md[1] : 0
if (m) line = m
}
return {file,line}
}
function console_rec(line, file, msg) {
return `[${cell.id.slice(0,5)}] [${file}:${line}]: ${msg}\n`
// time: [${time.text("mb d yyyy h:nn:ss")}]
}
globalThis.log = {}
log.console = function(msg)
{
var caller = caller_data(1)
os.print(console_rec(caller.line, caller.file, msg))
}
log.error = function(msg = new Error())
{
var caller = caller_data(1)
if (msg instanceof Error)
msg = msg.name + ": " + msg.message + "\n" + msg.stack
os.print(console_rec(caller.line,caller.file,msg))
}
log.system = function(msg) {
msg = "[SYSTEM] " + msg
log.console(msg)
}
function disrupt(err)
{
if (overling) {
if (err) {
// with an err, this is a forceful disrupt
var reason = (err instanceof Error) ? err.stack : err
report_to_overling({type:'disrupt', reason})
} else
report_to_overling({type:'stop'})
}
if (underlings) {
for (var id of underlings) {
log.console(`calling on ${id} to disrupt too`)
$_.stop(create_actor({id}))
}
}
if (err) {
log.console(err);
if (err.stack)
log.console(err.stack)
}
actor_mod.disrupt()
}
actor_mod.on_exception(disrupt)
cell.args = cell.hidden.init
cell.args ??= {}
cell.id ??= "newguy"
var shop = use('shop')
os.core_qop = core_qop
os.use_cache = use_cache
shop.set_os(os)
globalThis.use = shop.use
var config = shop.load_config()
// Get package name from a resolved path
function get_package_from_path(path) {
if (!path) return null
var modules_prefix = '.cell/modules/'
if (path.startsWith(modules_prefix)) {
var rest = path.substring(modules_prefix.length)
var slash_idx = rest.indexOf('/')
if (slash_idx > 0) {
return rest.substring(0, slash_idx)
}
}
return null
}
globalThis.json = use('json')
var time = use('time')
var st_now = time.number()
var default_config = {
ar_timer: 60,
actor_memory:0,
net_service:0.1,
reply_timeout:60,
main: false,
}
config ??= {}
config.system ??= {}
config.system.__proto__ = default_config
cell.config = config
ENETSERVICE = config.system.net_service
REPLYTIMEOUT = config.system.reply_timeout
/*
When handling a message, the message appears like this:
{
@@ -831,12 +404,6 @@ $_.receiver = function receiver(fn) {
$_.start = function start(cb, program, ...args) {
if (!program) return
// Resolve the actor program path with package awareness
var resolved_program = resolve_path(program, current_package, ACTOR_EXT)
if (!resolved_program) {
throw new Error(`Actor program ${program} could not be found (package context: ${current_package || 'none'})`)
}
var id = guid()
if (args.length == 1 && Array.isArray(args[0])) args = args[0]
var startup = {
@@ -844,8 +411,7 @@ $_.start = function start(cb, program, ...args) {
overling: $_,
root,
arg: args,
program: resolved_program.path,
package_context: resolved_program.package_name // Pass package context to new actor
program,
}
greeters[id] = cb
message_queue.push({ startup })
@@ -885,6 +451,8 @@ $_.delay = function delay(fn, seconds = 0) {
return function() { actor_mod.removetimer(id) }
}
var enet = use_embed('enet')
// causes this actor to stop when another actor stops.
var couplings = new Set()
$_.couple = function couple(actor) {
@@ -1029,8 +597,6 @@ function turn(msg)
send_messages()
}
load_actor_config(cell.args.program)
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer)
if (config.system.actor_memory)
@@ -1043,18 +609,6 @@ overling = cell.args.overling
root = cell.args.root
root ??= $_
// Set package context from parent actor (if spawned from a package)
if (cell.args.package_context) {
current_package = cell.args.package_context
log.console(`Actor initialized with package context: ${current_package}`)
} else {
// Infer package context from program path
current_package = get_package_from_path(cell.args.program)
if (current_package) {
log.console(`Actor inferred package context from path: ${current_package}`)
}
}
if (overling) {
$_.couple(overling) // auto couple to overling
@@ -1166,58 +720,17 @@ actor_mod.setname(cell.args.program)
var prog = cell.args.program
// Resolve the main program path
var resolved_prog = resolve_path(cell.args.program, current_package, ACTOR_EXT)
var resolved_prog = shop.resolve_locator(cell.args.program, ACTOR_EXT, null)
if (!resolved_prog) {
throw new Error(`Main program ${cell.args.program} could not be found`)
}
prog = resolved_prog.path
var startfn
var compiledPath = get_compiled_path(resolved_prog)
var useCompiled = false
// Always compile from source - never use precompiled for main program
// if (resolved_prog.isCore) {
// // For core, we check if we have a compiled version, else we compile it
// if (fd.is_file(compiledPath)) {
// useCompiled = true
// }
// } else {
// // For local/modules, check timestamps
// var srcStat = fd.stat(prog)
// var compiledStat = fd.stat(compiledPath)
// if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) {
// useCompiled = true
// }
// }
if (useCompiled) {
var compiledBlob = fd.slurp(compiledPath)
var fn = js.compile_unblob(compiledBlob)
startfn = js.eval_compile(fn)
log.console("main: using compiled version " + compiledPath)
} else {
var progContent
if (resolved_prog.isCore) {
progContent = utf8.decode(core_qop.read(prog))
} else {
progContent = utf8.decode(fd.slurp(prog))
}
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })`
// Compile and save
var fn = js.compile(cell.args.program, prog_script)
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
var compiled = js.compile_blob(fn)
write_file(compiledPath, compiled)
startfn = js.eval_compile(fn)
}
var prog_script = `(function start($_, arg) { var args = arg; ${resolved_prog.script} ; })`
var fn = js.eval(cell.args.program, prog_script)
$_.clock(_ => {
var val = startfn($_, cell.args.arg);
var val = fn($_, cell.args.arg);
if (val)
throw new Error('Program must not return anything');

View File

@@ -444,16 +444,20 @@ JSC_CCALL(fd_is_dir,
)
JSC_SCALL(fd_slurpwrite,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
size_t len;
const char *data = js_get_blob_data(js, &len, argv[1]);
if (!data)
return JS_ThrowTypeError(js, "blob expected");
if (write(fd, data, len) != len)
int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
return JS_ThrowInternalError(js, "open failed: %s", strerror(errno));
ssize_t written = write(fd, data, len);
close(fd);
if (written != (ssize_t)len)
return JS_ThrowInternalError(js, "write failed: %s", strerror(errno));
return JS_NULL;

View File

@@ -1,26 +1,20 @@
var json = {}
var utf8 = use('utf8')
var JS = JSON
JSON = null
// Produce a JSON text from a Javascript object. If a record value, at any level, contains a json() method, it will be called, and the value it returns (usually a simpler record) will be JSONified.
// If the record does not have a json() method, and if whitelist is a record, then only the keys that are associated with true in the whitelist are included.
// If the space input is true, then line breaks and extra whitespace will be included in the text.
json.encode = function encode(val,replacer,space = 1,whitelist)
{
return JS.stringify(val, replacer, space)
return JSON.stringify(val, replacer, space)
}
// The text text is parsed, and the resulting value (usually a record or an array) is returned.
// The optional reviver input is a method that will be called for every key and value at every level of the result. Each value will be replaced by the result of the reviver function. This can be used to reform data-only records into method-bearing records, or to transform date strings into seconds.
json.decode = function decode(text,reviver)
{
if (typeof text != 'string') {
text = utf8.decode(text)
if (!text) throw new Error("couldn't parse text: not a string")
}
return JS.parse(text,reviver)
if (typeof json_in != 'string')
throw new Error("couldn't parse text: not a string")
return JSON.parse(text,reviver)
}

View File

@@ -3,16 +3,107 @@
var toml = use('toml')
var json = use('json')
var fd = use('fd')
var utf8 = use('utf8')
var http = use('http')
var miniz = use('miniz')
var time = use('time')
var js = use('js')
var qop
var core_qop
// a locator is what is used to import a module, like prosperon/sprite
// in prosperon/sprite, sprite is the module, and prosperon is the package (usually, an alias)
// a canonical package name relates prosperon to its source, like gitea.pockle.world/john/prosperon
var Shop = {}
var SCOPE_LOCAL = 0
var SCOPE_PACKAGE = 1
var SCOPE_CORE = 2
var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce'
var os
var use_cache
Shop.set_os = function(o)
{
os = o
qop = os.load_internal('js_qop_use')
core_qop = os.core_qop
use_cache = os.use_cache
}
var config = null
var shop_path = '.cell/cell.toml'
var lock_path = '.cell/lock.toml'
var open_dl = {}
function get_import_package(name) {
var parts = name.split('/')
if (parts.length > 1)
return parts[0]
return null
}
function get_import_name(path)
{
var parts = path.split('/')
if (parts.length < 2) return null
return parts.slice(1).join('/')
}
// given a path, get a full package import
// ie, 'prosperon/sprite' would return 'gitea.pockle.world/john/prosperon/sprite'
// if prosperon were a dependency
function get_package_from_path(path, ctx)
{
var pkg = get_import_package(path)
var locator = get_import_name(path)
if (!pkg) return null
var pck = get_normalized_module(pkg, ctx)
return pck + "/" + locator
}
function get_normalized_package(path, ctx)
{
var pkg = get_import_package(path)
if (!pkg) return null
return get_normalized_module(pkg, ctx)
}
// taking the package into account, find the canonical name
function get_normalized_module(mod, ctx) {
var deps = Shop.load_config(ctx);
return deps.dependencies[mod]
}
function get_import_dl(name) {
var pkg = get_import_package(name)
if (!pkg) return null
if (open_dl[pkg]) return open_dl[pkg]
var dlpath = `.cell/modules/${pkg}/${pkg}${dylib_ext}`
var dl = os.dylib_open(dlpath)
if (dl) {
open_dl[pkg] = dl
return dl
}
return null
}
Shop.get_c_symbol = function get_c_symbol(name) {
var dl = get_import_dl(name)
var symname = `js_${name.replace('/', '_')}_use`
if (dl)
return os.dylib_symbol(dl, symname)
else
return os.load_internal(symname)
}
function slurpwrite(path, content) {
var f = fd.open(path)
fd.write(f, content)
@@ -35,12 +126,25 @@ function ensure_dir(path) {
}
// Load cell.toml configuration
Shop.load_config = function() {
if (!fd.stat(shop_path).isFile)
// module given in canonical format (e.g., "gitea.pockle.world/john/prosperon")
// If module is null, loads the root cell.toml
// If module is provided, loads module/cell.toml
Shop.load_config = function(module) {
var content
if (!module) {
if (!fd.is_file(shop_path))
return null
var content = utf8.decode(fd.slurp(shop_path))
return toml.decode(content)
content = fd.slurp(shop_path)
} else {
var module_path = `.cell/modules/${module}/cell.toml`
if (!fd.stat(module_path).isFile)
return null
content = fd.slurp(module_path)
}
return toml.decode(text(content))
}
// Save cell.toml configuration
@@ -53,7 +157,7 @@ Shop.load_lock = function() {
if (!fd.stat(lock_path).isFile)
return {}
var content = utf8.decode(fd.slurp(lock_path))
var content = text(fd.slurp(lock_path))
return toml.decode(content) || {}
}
@@ -285,7 +389,7 @@ Shop.install = function(alias) {
try {
log.console("Fetching info from " + api_url)
var resp = http.fetch(api_url)
var resp_text = utf8.decode(resp)
var resp_text = text(resp)
commit_hash = Shop.extract_commit_hash(locator, resp_text)
log.console("Resolved commit: " + commit_hash)
} catch (e) {
@@ -376,6 +480,135 @@ Shop.verify = function() {
return all_ok
}
var open_dls = {}
function get_locator_module_path(locator)
{
var canon_pkg = get_package_from_path(locator);
var path = `.cell/modules/${canon_pkg}`;
if (fd.is_directory(path))
return path;
return null
}
function resolve_locator(path, ext, ctx)
{
var deps = Shop.load_config(ctx).dependencies || {}
var local_path
if (ctx)
local_path = `.cell/modules/${ctx}/${path}${ext}`
else
local_path = path + ext
if (fd.is_file(local_path))
return {path: local_path, scope: SCOPE_LOCAL, script: text(fd.slurp(local_path))};
var mod_path = `.cell/modules/${get_package_from_path(path, ctx)}${ext}`
if (fd.is_file(mod_path))
return {path: mod_path, scope: SCOPE_PACKAGE, script: text(fd.slurp(mod_path))};
var core = core_qop.read(path + ext)
if (core)
return {path, scope: SCOPE_CORE, script: text(core)};
return null;
}
function resolve_c_symbol(path, package_ctx)
{
var local_path = package_ctx ? package_ctx : 'local'
var local = `js_${local_path}_${path.replace('/', '_')}_use`
var local_dl_name = `.cell/build/${local_path}/cellmod.dylib`
if (fd.is_file(local_dl_name)) {
if (!open_dls[local_dl_name])
open_dls[local_dl_name] = os.dylib_open(local_dl_name);
if (open_dls[local_dl_name]) {
var local_addr = os.dylib_symbol(open_dls[local_dl_name], local);
if (local_addr) return {symbol: local_addr, scope: SCOPE_LOCAL};
}
}
var static_local_addr = os.load_internal(`js_${local_path}_${path.replace('/', '_')}_use`);
if (static_local_addr) return {symbol: static_local_addr, scope: SCOPE_LOCAL};
var pkg = get_package_from_path(path, package_ctx)
if (pkg) {
var package_sym = `js_${pkg.replace('/', '_')}_use`
var canon_pkg = get_normalized_package(path, package_ctx)
var package_dl_name = `.cell/build/${canon_pkg}/cellmod.dylib`;
if (canon_pkg && fd.is_file(package_dl_name)) {
if (!open_dls[package_dl_name])
open_dls[package_dl_name] = os.dylib_open(package_dl_name);
if (open_dls[package_dl_name]) {
var package_addr = os.dylib_symbol(open_dls[package_dl_name], package_sym);
if (package_addr)
return {symbol:package_addr, scope: SCOPE_PACKAGE, package: canon_pkg};
}
}
var static_package_addr = os.load_internal(`js_${pkg.replace('/', '_')}_use`);
if (static_package_addr)
return {symbol:static_package_addr, scope: SCOPE_PACKAGE, package: canon_pkg};
}
var core_addr = os.load_internal(`js_${path.replace('/', '_')}_use`);
if (core_addr)
return {symbol:core_addr, scope: SCOPE_CORE};
return null
}
function mod_scriptor(name, script)
{
return `(function setup_${name}_module($_){${script}})`
}
function eval_mod(path, script, c_sym)
{
var content = mod_scriptor(path, script)
var fn = js.eval(path, content)
return fn.call(c_sym)
}
// first looks in local
// then in dependencies
// then in core
// package_context: optional package context to resolve relative paths within
Shop.use = function(path, package_context) {
var c_resolve = resolve_c_symbol(path, package_context) || {scope:999}
var mod_resolve = resolve_locator(path, '.cm', package_context) || {scope:999}
var min_scope = Math.min(c_resolve.scope, mod_resolve.scope)
if (min_scope == 999) {
throw new Error(`Module ${path} could not be found`)
}
var cache_key = `${text(min_scope)}::${path}`
if (use_cache[cache_key])
return use_cache[cache_key]
if (c_resolve.scope < mod_resolve.scope)
use_cache[cache_key] = c_resolve.symbol
else if (mod_resolve.scope < c_resolve.scope)
use_cache[cache_key] = eval_mod(mod_resolve.path, mod_resolve.script)
else
use_cache[cache_key] = eval_mod(mod_resolve.path, mod_resolve.script, c_resolve.symbol)
return use_cache[cache_key]
}
Shop.resolve_locator = resolve_locator
// Check for updates
Shop.update = function() {
var config = Shop.load_config()
@@ -390,7 +623,7 @@ Shop.update = function() {
if (api_url) {
try {
var resp = http.fetch(api_url)
var resp_text = utf8.decode(resp)
var resp_text = text(resp)
var remote_hash = Shop.extract_commit_hash(locator, resp_text)
var local_hash = lock[alias] ? lock[alias].commit : null
@@ -508,28 +741,4 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
return null
}
// Get the package name from a file path
// e.g., '.cell/modules/extramath/spline.cm' -> 'extramath'
// e.g., 'myfile.cm' -> null
Shop.get_package_from_path = function(path) {
if (!path) return null
var modules_prefix = '.cell/modules/'
if (path.startsWith(modules_prefix)) {
var rest = path.substring(modules_prefix.length)
// This logic is tricky with nested paths like gitea.pockle.world/john/prosperon
// We probably need to reverse map from path to alias using config
var config = Shop.load_config()
if (config && config.dependencies) {
for (var alias in config.dependencies) {
var locator = config.dependencies[alias]
var parsed = Shop.parse_locator(locator)
if (rest.startsWith(parsed.path + '/')) {
return alias
}
}
}
}
return null
}
return Shop

View File

@@ -408,10 +408,4 @@ function format_number(num, format) {
return null;
}
text.base32_to_blob = that.base32_to_blob
text.base64_to_blob = that.base64_to_blob
text.base64url_to_blob = that.base64url_to_blob
text.blob_to_base64 = that.blob_to_base64
text.blob_to_base64url = that.blob_to_base64url
return text;

View File

@@ -2,7 +2,6 @@
var time = this;
/* -------- host helpers -------------------------------------------------- */
var now = time.now; // seconds since Misty epoch (C stub)
var computer_zone = time.computer_zone; // integral hours, no DST
var computer_dst = time.computer_dst; // true ↔ DST in effect

View File

@@ -74,7 +74,6 @@ const char *send_message(const char *id, void *msg);
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 script_startup(cell_rt *rt);
cell_rt *create_actor(void *wota);
int uncaught_exception(JSContext *js, JSValue v);
int actor_exists(const char *id);

View File

@@ -1,4 +1,4 @@
log.console('hello')
var count = 0
function loop()
{