reduce shop
This commit is contained in:
@@ -35,6 +35,7 @@ all source for every single part of a cell program are located in the cell shop.
|
|||||||
|
|
||||||
the cell shop looks like this:
|
the cell shop looks like this:
|
||||||
.cell
|
.cell
|
||||||
|
shop.toml <---- shop configuration
|
||||||
packages
|
packages
|
||||||
gitea.pockle.world/john/cell <--- this is the root cell
|
gitea.pockle.world/john/cell <--- this is the root cell
|
||||||
gitea.pockle.world/john/prosperon
|
gitea.pockle.world/john/prosperon
|
||||||
@@ -62,3 +63,35 @@ When a module <name/mod> is requested, from a within package <a> ..
|
|||||||
1) cell looks within package <a> for a folder <name> and loads <name/mod>, if present
|
1) cell looks within package <a> for a folder <name> and loads <name/mod>, if present
|
||||||
2) cell looks for a package <name> with a top level <mod>
|
2) cell looks for a package <name> with a top level <mod>
|
||||||
3) cell looks in the core for <name/mod>
|
3) cell looks in the core for <name/mod>
|
||||||
|
|
||||||
|
The core can be set and have something else linked into it, if you want to break your cell build
|
||||||
|
|
||||||
|
## cell build
|
||||||
|
cell build compiles all c into the appropriate binaries. All object files are stored in the .cell/cache
|
||||||
|
|
||||||
|
there are two ways to build a cell program: as a shared library, or as a static binary.
|
||||||
|
|
||||||
|
Cell script files are simply compiled into built objects
|
||||||
|
|
||||||
|
Then, all C files in the package are compiled.
|
||||||
|
|
||||||
|
If you have a file like fd.c and fd_playdate.c, that is a signal to cell to compile fd.c usually, but then for the playdate target, compile fd_playdate.c instead.
|
||||||
|
|
||||||
|
files name "main.c" are not compiled.
|
||||||
|
|
||||||
|
each cell.toml in a package
|
||||||
|
|
||||||
|
### shared library
|
||||||
|
this is more suitable for development. firstly, the cell core must build a shared library for the platform.
|
||||||
|
|
||||||
|
Then, each package compiles, linking to the cell core shared library. Modules are written to include the cell core headers.
|
||||||
|
|
||||||
|
Shared libraries are stored
|
||||||
|
|
||||||
|
### bootstrapping cell
|
||||||
|
after the cell shared libraries are all compiled, the main.c of the cell core is compiled against the cell core shared library, creating a thin cell runner. However, even on subsequent updates to cell, this runner is not updated; only for bootstrapping.
|
||||||
|
|
||||||
|
### static binary
|
||||||
|
For static binaries, rather than going through and compiling each dynamic library for a package, a static binary is created for a particular package, based on its dependencies. All C symbols, plus the main.c runner from the cell core, are packaged into one binary. It works just like the basic cell program.
|
||||||
|
|
||||||
|
However, you can also specify an entry point for this binary, for example, a particular actor. When the binary is run, that particular actor runs.
|
||||||
@@ -9,6 +9,8 @@ cell.os = null
|
|||||||
|
|
||||||
var dylib_ext
|
var dylib_ext
|
||||||
|
|
||||||
|
cell.id ??= "newguy"
|
||||||
|
|
||||||
switch(os.platform()) {
|
switch(os.platform()) {
|
||||||
case 'Windows': dylib_ext = '.dll'; break;
|
case 'Windows': dylib_ext = '.dll'; break;
|
||||||
case 'macOS': dylib_ext = '.dylib'; break;
|
case 'macOS': dylib_ext = '.dylib'; break;
|
||||||
@@ -55,19 +57,15 @@ function use_core(path) {
|
|||||||
if (use_cache[cache_path])
|
if (use_cache[cache_path])
|
||||||
return use_cache[cache_path];
|
return use_cache[cache_path];
|
||||||
|
|
||||||
var sym = use_embed(path)
|
var sym = use_embed(path.replace('/','_'))
|
||||||
// Core scripts are now in .cell/core/scripts
|
|
||||||
var file_path = core_path + '/scripts/' + path + MOD_EXT
|
|
||||||
|
|
||||||
// Fallback check for root (optional, for backward compat if needed, but not strictly necessary if we are consistent)
|
// Core scripts are now in .cell/core/scripts
|
||||||
if (!fd.is_file(file_path)) {
|
var file_path = core_path + '/' + path + MOD_EXT
|
||||||
file_path = core_path + '/' + path + MOD_EXT
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fd.is_file(file_path)) {
|
if (fd.is_file(file_path)) {
|
||||||
var script_blob = fd.slurp(file_path)
|
var script_blob = fd.slurp(file_path)
|
||||||
var script = utf8.decode(script_blob)
|
var script = utf8.decode(script_blob)
|
||||||
var mod = `(function setup_${path}_module($_){${script}})`
|
var mod = `(function setup_module($_){${script}})`
|
||||||
var fn = js.eval(path, mod)
|
var fn = js.eval(path, mod)
|
||||||
var result = fn.call(sym);
|
var result = fn.call(sym);
|
||||||
use_cache[cache_path] = result;
|
use_cache[cache_path] = result;
|
||||||
@@ -114,7 +112,7 @@ stone.p = function(object)
|
|||||||
var actor_mod = use('actor')
|
var actor_mod = use('actor')
|
||||||
var wota = use('wota')
|
var wota = use('wota')
|
||||||
var nota = use('nota')
|
var nota = use('nota')
|
||||||
globalThis.text = use('text')
|
globalThis.text = use('internal/text')
|
||||||
|
|
||||||
var ENETSERVICE = 0.1
|
var ENETSERVICE = 0.1
|
||||||
var REPLYTIMEOUT = 60 // seconds before replies are ignored
|
var REPLYTIMEOUT = 60 // seconds before replies are ignored
|
||||||
@@ -231,7 +229,6 @@ function get_package_from_path(path) {
|
|||||||
|
|
||||||
globalThis.json = use('json')
|
globalThis.json = use('json')
|
||||||
var time = use('time')
|
var time = use('time')
|
||||||
var st_now = time.number()
|
|
||||||
|
|
||||||
var default_config = {
|
var default_config = {
|
||||||
ar_timer: 60,
|
ar_timer: 60,
|
||||||
@@ -653,16 +650,18 @@ if (!program) {
|
|||||||
|
|
||||||
// Find the package containing the program
|
// Find the package containing the program
|
||||||
// The program path is resolved relative to current directory
|
// The program path is resolved relative to current directory
|
||||||
var package_dir = shop.set_current_package(program)
|
// Find the package containing the program
|
||||||
if (package_dir) {
|
// The program path is resolved relative to current directory
|
||||||
// Reload config from the package
|
// var package_dir = shop.set_current_package(program)
|
||||||
config = shop.load_config()
|
// if (package_dir) {
|
||||||
if (config) {
|
// // Reload config from the package
|
||||||
config.system = config.system || {}
|
// config = shop.load_config()
|
||||||
config.system.__proto__ = default_config
|
// if (config) {
|
||||||
cell.config = config
|
// config.system = config.system || {}
|
||||||
}
|
// config.system.__proto__ = default_config
|
||||||
}
|
// cell.config = config
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
function handle_actor_disconnect(id) {
|
function handle_actor_disconnect(id) {
|
||||||
var greeter = greeters[id]
|
var greeter = greeters[id]
|
||||||
@@ -298,7 +298,7 @@ static const JSCFunctionListEntry js_text_funcs[] = {
|
|||||||
MIST_FUNC_DEF(text, base64url_to_blob, 1),
|
MIST_FUNC_DEF(text, base64url_to_blob, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_text_use(JSContext *js)
|
JSValue js_internal_text_use(JSContext *js)
|
||||||
{
|
{
|
||||||
JSValue mod = JS_NewObject(js);
|
JSValue mod = JS_NewObject(js);
|
||||||
JS_SetPropertyFunctionList(js, mod, js_text_funcs, countof(js_text_funcs));
|
JS_SetPropertyFunctionList(js, mod, js_text_funcs, countof(js_text_funcs));
|
||||||
|
|||||||
54
meson.build
54
meson.build
@@ -52,34 +52,33 @@ src += [ # core
|
|||||||
src += ['scheduler.c']
|
src += ['scheduler.c']
|
||||||
|
|
||||||
scripts = [
|
scripts = [
|
||||||
'nota.c',
|
'internal/nota.c',
|
||||||
'js.c',
|
'debug/js.c',
|
||||||
'qop.c',
|
'qop.c',
|
||||||
'wildstar.c',
|
'wildstar.c',
|
||||||
'fit.c',
|
'fit.c',
|
||||||
'crypto.c',
|
'crypto.c',
|
||||||
'text.c',
|
'internal/text.c',
|
||||||
'utf8.c',
|
'utf8.c',
|
||||||
'kim.c',
|
'internal/kim.c',
|
||||||
'time.c',
|
'time.c',
|
||||||
'nota.c',
|
'internal/nota.c',
|
||||||
'debug.c',
|
'debug/debug.c',
|
||||||
'os.c',
|
'internal/os.c',
|
||||||
'fd.c',
|
'fd.c',
|
||||||
'http.c',
|
'net/http.c',
|
||||||
'enet.c',
|
'net/enet.c',
|
||||||
'wildstar.c',
|
'wildstar.c',
|
||||||
'miniz.c',
|
'archive/miniz.c',
|
||||||
'json.c'
|
'internal/json.c'
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach file: scripts
|
foreach file: scripts
|
||||||
full_path = join_paths('scripts', file)
|
sources += files(file)
|
||||||
sources += files(full_path)
|
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
srceng = 'source'
|
srceng = 'source'
|
||||||
includes = [srceng]
|
includes = [srceng, 'internal', 'debug', 'net', 'archive']
|
||||||
|
|
||||||
foreach file : src
|
foreach file : src
|
||||||
full_path = join_paths(srceng, file)
|
full_path = join_paths(srceng, file)
|
||||||
@@ -99,7 +98,6 @@ else
|
|||||||
link += '-Wl,-export_dynamic'
|
link += '-Wl,-export_dynamic'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
cell_so = shared_library(
|
cell_so = shared_library(
|
||||||
'cell_runtime',
|
'cell_runtime',
|
||||||
sources,
|
sources,
|
||||||
@@ -108,14 +106,6 @@ cell_so = shared_library(
|
|||||||
install : true,
|
install : true,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create core.zip from scripts folder
|
|
||||||
qop_target = custom_target('core.qop',
|
|
||||||
output: 'core.qop',
|
|
||||||
command: ['sh', '-c', ' qopconv -d ' + meson.project_source_root() / 'scripts . core.qop'],
|
|
||||||
build_by_default: true,
|
|
||||||
build_always_stale: true
|
|
||||||
)
|
|
||||||
|
|
||||||
# Determine RPATH based on OS
|
# Determine RPATH based on OS
|
||||||
# MacOS uses @loader_path, Linux uses $ORIGIN
|
# MacOS uses @loader_path, Linux uses $ORIGIN
|
||||||
# We include both the current directory (for build) and ../lib (for install)
|
# We include both the current directory (for build) and ../lib (for install)
|
||||||
@@ -127,28 +117,14 @@ elif host_machine.system() == 'linux'
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# Create main executable linked against the cell library
|
# Create main executable linked against the cell library
|
||||||
cell_exe = executable('cell_exe',
|
cell_exe = executable('cell',
|
||||||
'source/main.c',
|
'source/main.c',
|
||||||
dependencies: deps,
|
dependencies: deps,
|
||||||
include_directories: includers,
|
include_directories: includers,
|
||||||
link_with: cell_so,
|
link_with: cell_so,
|
||||||
link_args: link,
|
link_args: link,
|
||||||
build_rpath: ':'.join(rpath_dirs),
|
build_rpath: ':'.join(rpath_dirs),
|
||||||
install: false
|
install: true
|
||||||
)
|
|
||||||
|
|
||||||
# Create final cell executable by appending core.zip to cell_exe
|
|
||||||
cell = custom_target('cell',
|
|
||||||
input: [cell_exe, qop_target],
|
|
||||||
output: 'cell' + exe_ext,
|
|
||||||
command: [
|
|
||||||
'sh', '-c',
|
|
||||||
'cp "$1" "$3" && cat "$2" >> "$3" && chmod +x "$3"',
|
|
||||||
'cell-cat', '@INPUT0@', '@INPUT1@', '@OUTPUT@'
|
|
||||||
],
|
|
||||||
build_by_default: true,
|
|
||||||
install: true,
|
|
||||||
install_dir: get_option('bindir')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Install headers for building dynamic libraries using Cell
|
# Install headers for building dynamic libraries using Cell
|
||||||
|
|||||||
326
shop.cm
326
shop.cm
@@ -10,13 +10,13 @@ var utf8 = use('utf8')
|
|||||||
var blob = use('blob')
|
var blob = use('blob')
|
||||||
var build_utils = use('build')
|
var build_utils = use('build')
|
||||||
|
|
||||||
|
var core = "gitea.pockle.world/john/cell"
|
||||||
|
|
||||||
function content_hash(content)
|
function content_hash(content)
|
||||||
{
|
{
|
||||||
return text(crypto.blake2(utf8.encode(content)), 'h')
|
return text(crypto.blake2(utf8.encode(content)), 'h')
|
||||||
}
|
}
|
||||||
|
|
||||||
log.console(content_hash("hello"))
|
|
||||||
|
|
||||||
// a package string is what is used to import a module, like prosperon/sprite
|
// a package string 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)
|
// 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
|
// a canonical package name relates prosperon to its source, like gitea.pockle.world/john/prosperon
|
||||||
@@ -27,9 +27,6 @@ var Shop = {}
|
|||||||
// Located at ~/.cell on the user's machine
|
// Located at ~/.cell on the user's machine
|
||||||
var global_shop_path = null
|
var global_shop_path = null
|
||||||
|
|
||||||
// Current package context - the package we're running from (resolved from program path)
|
|
||||||
var current_package_path = null
|
|
||||||
|
|
||||||
var SCOPE_LOCAL = 0
|
var SCOPE_LOCAL = 0
|
||||||
var SCOPE_PACKAGE = 1
|
var SCOPE_PACKAGE = 1
|
||||||
var SCOPE_CORE = 2
|
var SCOPE_CORE = 2
|
||||||
@@ -165,21 +162,7 @@ function update_local_lock(abs_path) {
|
|||||||
Shop.ensure_package_link = ensure_package_link
|
Shop.ensure_package_link = ensure_package_link
|
||||||
|
|
||||||
// Set the current package context from a program path
|
// Set the current package context from a program path
|
||||||
Shop.set_current_package = function(program_path) {
|
|
||||||
current_package_path = Shop.find_package_dir(program_path)
|
|
||||||
|
|
||||||
if (current_package_path && current_package_path.startsWith('/')) {
|
|
||||||
// It's a local package, ensure it is linked in the shop
|
|
||||||
ensure_package_link(current_package_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return current_package_path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current package path
|
|
||||||
Shop.get_current_package = function() {
|
|
||||||
return current_package_path
|
|
||||||
}
|
|
||||||
|
|
||||||
Shop.set_os = function(o, $guy)
|
Shop.set_os = function(o, $guy)
|
||||||
{
|
{
|
||||||
@@ -203,9 +186,6 @@ function get_config_path(package_path) {
|
|||||||
if (package_path) {
|
if (package_path) {
|
||||||
return package_path + '/cell.toml'
|
return package_path + '/cell.toml'
|
||||||
}
|
}
|
||||||
if (current_package_path) {
|
|
||||||
return current_package_path + '/cell.toml'
|
|
||||||
}
|
|
||||||
// Fallback to current directory
|
// Fallback to current directory
|
||||||
return 'cell.toml'
|
return 'cell.toml'
|
||||||
}
|
}
|
||||||
@@ -351,6 +331,10 @@ function get_path_in_package(path, ctx)
|
|||||||
return canon_pkg + "/" + mod_name
|
return canon_pkg + "/" + mod_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given a path like 'prosperon/sprite', extract the package alias ('prosperon')
|
||||||
|
// and resolve it to its canonical path using the dependencies in ctx's config.
|
||||||
|
// Returns the canonical package path (e.g., 'gitea.pockle.world/john/prosperon')
|
||||||
|
// or null if the path has no package prefix or the package is not found.
|
||||||
function get_normalized_package(path, ctx)
|
function get_normalized_package(path, ctx)
|
||||||
{
|
{
|
||||||
var pkg = get_import_package(path)
|
var pkg = get_import_package(path)
|
||||||
@@ -773,35 +757,13 @@ Shop.check_cache = function(pkg) {
|
|||||||
var open_dls = {}
|
var open_dls = {}
|
||||||
|
|
||||||
// for script forms, path is the canonical path of the module
|
// for script forms, path is the canonical path of the module
|
||||||
var script_forms = []
|
var script_form = function(path, script, pkg) {
|
||||||
|
|
||||||
// Construct a descriptive compile name with extension preserved.
|
|
||||||
// Formats:
|
|
||||||
// core:<module path>
|
|
||||||
// <package>:<module path> (package is the full canonical package name)
|
|
||||||
// local:<module path>
|
|
||||||
// package:<module path> (fallback when pkg isn't provided but path is under modules)
|
|
||||||
function make_compile_name(path, rel_path, pkg, scope) {
|
|
||||||
if (scope == SCOPE_CORE) return 'core:' + rel_path
|
|
||||||
if (pkg) return pkg + ':' + rel_path
|
|
||||||
var modules_dir = get_modules_dir()
|
|
||||||
if (path && path.startsWith(modules_dir + '/')) return 'package:' + rel_path
|
|
||||||
return 'local:' + rel_path
|
|
||||||
}
|
|
||||||
|
|
||||||
script_forms['.cm'] = function(path, script, pkg) {
|
|
||||||
var pkg_arg = pkg ? `'${pkg}'` : 'null'
|
var pkg_arg = pkg ? `'${pkg}'` : 'null'
|
||||||
var relative_use_fn = `def use = function(path) { return globalThis.use(path, ${pkg_arg});}`
|
var relative_use_fn = `def use = function(path) { return globalThis.use(path, ${pkg_arg});}`
|
||||||
var fn = `(function setup_module($_){ ${relative_use_fn}; ${script}})`
|
var fn = `(function setup_module($_){ ${relative_use_fn}; ${script}})`
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
script_forms['.ce'] = function(path, script, pkg) {
|
|
||||||
var pkg_arg = pkg ? `'${pkg}'` : 'null'
|
|
||||||
var relative_use_fn = `def use = function(path) { return globalThis.use(path, ${pkg_arg});}`
|
|
||||||
return `(function start($_, arg) { ${relative_use_fn}; var args = arg; ${script} ; })`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get flags from config
|
// Get flags from config
|
||||||
function get_flags(config, platform, key) {
|
function get_flags(config, platform, key) {
|
||||||
var flags = ''
|
var flags = ''
|
||||||
@@ -817,136 +779,81 @@ function get_flags(config, platform, key) {
|
|||||||
|
|
||||||
Shop.get_flags = get_flags
|
Shop.get_flags = get_flags
|
||||||
|
|
||||||
function get_build_dir(pkg) {
|
|
||||||
if (!pkg) pkg = current_package_path
|
|
||||||
if (!pkg) return get_global_build_dir() + '/local' // Fallback for non-package scripts
|
|
||||||
|
|
||||||
// If pkg is absolute path, mirror it
|
|
||||||
if (pkg.startsWith('/')) {
|
|
||||||
// Accio folder should be .cell/packages/Users/...
|
|
||||||
// But build should be .cell/build/packages/Users/...
|
|
||||||
return get_global_build_dir() + '/packages' + pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise it's a relative package path (from packages dir)
|
// Resolve module function
|
||||||
return get_global_build_dir() + '/packages/' + pkg
|
function resolve_mod_fn(path, pkg) {
|
||||||
}
|
|
||||||
|
|
||||||
Shop.get_build_dir = get_build_dir
|
|
||||||
Shop.get_global_build_dir = get_global_build_dir
|
|
||||||
|
|
||||||
function get_rel_path(path, pkg) {
|
|
||||||
if (!pkg) {
|
|
||||||
// For local files, strip the current package path prefix if present
|
|
||||||
if (current_package_path && path.startsWith(current_package_path + '/')) {
|
|
||||||
return path.substring(current_package_path.length + 1)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefix
|
|
||||||
if (pkg.startsWith('/')) {
|
|
||||||
prefix = pkg + '/'
|
|
||||||
} else {
|
|
||||||
prefix = get_modules_dir() + '/' + pkg + '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.startsWith(prefix)) {
|
|
||||||
return path.substring(prefix.length)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolve_mod_fn(path, pkg)
|
|
||||||
{
|
|
||||||
if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`)
|
if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`)
|
||||||
var rel_path = get_rel_path(path, pkg)
|
|
||||||
|
|
||||||
// Determine build directory based on context
|
var content = fd.slurp(path)
|
||||||
var build_dir
|
var hash = content_hash(content)
|
||||||
if (pkg) {
|
var hash_path = get_global_build_dir() + '/' + hash
|
||||||
build_dir = get_build_dir(pkg)
|
|
||||||
} else if (current_package_path) {
|
|
||||||
// Local file in current package - use package path (strip leading /)
|
|
||||||
var pkg_id = current_package_path.substring(1) // Remove leading /
|
|
||||||
build_dir = get_global_build_dir() + '/' + pkg_id
|
|
||||||
} else {
|
|
||||||
build_dir = get_build_dir('local')
|
|
||||||
}
|
|
||||||
|
|
||||||
var cache_path = build_dir + '/' + rel_path + '.o'
|
if (fd.is_file(hash_path)) {
|
||||||
|
var obj = fd.slurp(hash_path)
|
||||||
if (fd.is_file(cache_path) && fd.stat(path).mtime <= fd.stat(cache_path).mtime) {
|
|
||||||
var obj = fd.slurp(cache_path)
|
|
||||||
var fn = js.compile_unblob(obj)
|
var fn = js.compile_unblob(obj)
|
||||||
return js.eval_compile(fn)
|
return js.eval_compile(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ext = path.substring(path.lastIndexOf('.'))
|
var form = script_form
|
||||||
var script_form = script_forms[ext]
|
|
||||||
if (!script_form) throw new Error(`No script form for extension ${ext}`)
|
|
||||||
|
|
||||||
var compile_name = make_compile_name(path, rel_path, pkg, pkg ? SCOPE_PACKAGE : SCOPE_LOCAL)
|
// We don't really use pkg scope for compilation anymore since it's just hash based cache
|
||||||
|
// But we need to pass a package context for the 'use' function inside the module
|
||||||
|
var script = form(path, text(content), pkg);
|
||||||
|
|
||||||
|
// Compile name is just for debug/stack traces
|
||||||
|
var compile_name = pkg ? pkg + ':' + path : 'local:' + path
|
||||||
|
|
||||||
var script = script_form(path, text(fd.slurp(path)), pkg)
|
|
||||||
var fn = js.compile(compile_name, script)
|
var fn = js.compile(compile_name, script)
|
||||||
ensure_dir(cache_path.substring(0, cache_path.lastIndexOf('/')))
|
|
||||||
fd.slurpwrite(cache_path, js.compile_blob(fn))
|
// Ensure build dir exists
|
||||||
|
ensure_dir(get_global_build_dir())
|
||||||
|
fd.slurpwrite(hash_path, js.compile_blob(fn))
|
||||||
|
|
||||||
return js.eval_compile(fn)
|
return js.eval_compile(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve and cache a core module
|
// resolve_core_mod_fn is no longer needed as core modules are just modules in a package (or local)
|
||||||
function resolve_core_mod_fn(core_path, rel_path) {
|
|
||||||
var build_dir = get_global_build_dir() + '/core'
|
|
||||||
var cache_path = build_dir + '/' + rel_path + '.o'
|
|
||||||
|
|
||||||
if (fd.is_file(cache_path) && fd.stat(core_path).mtime <= fd.stat(cache_path).mtime) {
|
|
||||||
var obj = fd.slurp(cache_path)
|
|
||||||
var fn = js.compile_unblob(obj)
|
|
||||||
return js.eval_compile(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext = core_path.substring(core_path.lastIndexOf('.'))
|
|
||||||
var form = script_forms[ext]
|
|
||||||
if (!form) throw new Error(`No script form for extension ${ext}`)
|
|
||||||
|
|
||||||
var compile_name = make_compile_name(core_path, rel_path, null, SCOPE_CORE)
|
|
||||||
var script = form(null, text(fd.slurp(core_path)))
|
|
||||||
var fn = js.compile(compile_name, script)
|
|
||||||
ensure_dir(cache_path.substring(0, cache_path.lastIndexOf('/')))
|
|
||||||
fd.slurpwrite(cache_path, js.compile_blob(fn))
|
|
||||||
return js.eval_compile(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolve_locator(path, ext, ctx)
|
function resolve_locator(path, ext, ctx)
|
||||||
{
|
{
|
||||||
// In static_only mode, only look in the embedded pack
|
if (path.endsWith(ext)) ext = ''
|
||||||
var static_only = cell.static_only
|
|
||||||
|
|
||||||
if (!static_only) {
|
// 1. Check local file (relative to current directory if no context, or relative to package 'ctx' if provided)
|
||||||
// First, check if file exists in current package directory
|
// If ctx is provided, it's a package alias or path.
|
||||||
if (current_package_path) {
|
|
||||||
var pkg_local_path = current_package_path + '/' + path + ext
|
|
||||||
if (fd.is_file(pkg_local_path)) {
|
|
||||||
var fn = resolve_mod_fn(pkg_local_path, null)
|
|
||||||
return {path: pkg_local_path, scope: SCOPE_LOCAL, symbol:fn}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check CWD for local file
|
|
||||||
var local_path
|
var local_path
|
||||||
if (ctx)
|
if (ctx) {
|
||||||
local_path = get_modules_dir() + '/' + ctx + '/' + path + ext
|
var mod_dir = get_modules_dir()
|
||||||
else
|
// Check if ctx is an absolute path (local package)
|
||||||
|
if (ctx.startsWith('/')) {
|
||||||
|
local_path = ctx + '/' + path + ext
|
||||||
|
} else {
|
||||||
|
local_path = mod_dir + '/' + ctx + '/' + path + ext
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No context, just check simple local path
|
||||||
local_path = path + ext
|
local_path = path + ext
|
||||||
|
}
|
||||||
|
|
||||||
if (fd.is_file(local_path)) {
|
if (fd.is_file(local_path)) {
|
||||||
var fn = resolve_mod_fn(local_path, ctx)
|
var fn = resolve_mod_fn(local_path, ctx)
|
||||||
return {path: local_path, scope: SCOPE_LOCAL, symbol:fn}
|
return {path: local_path, scope: SCOPE_LOCAL, symbol:fn}
|
||||||
|
} else {
|
||||||
|
// Fallback: check if the path itself is a file (ignoring required extension)
|
||||||
|
// This allows running scripts like 'test.cm' even if engine asks for '.ce'
|
||||||
|
if (ext && path != local_path && fd.is_file(path)) {
|
||||||
|
var fn = resolve_mod_fn(path, ctx)
|
||||||
|
return {path: path, scope: SCOPE_LOCAL, symbol:fn}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check installed packages
|
|
||||||
|
// 2. Check installed packages (if path suggests a package import)
|
||||||
|
// This handles imports like 'prosperon/sprite'
|
||||||
var canonical_pkg = get_normalized_package(path, ctx)
|
var canonical_pkg = get_normalized_package(path, ctx)
|
||||||
|
if (canonical_pkg) {
|
||||||
var pkg_path = get_path_in_package(path, ctx)
|
var pkg_path = get_path_in_package(path, ctx)
|
||||||
var mod_path = get_modules_dir() + '/' + pkg_path + ext
|
var mod_path = get_modules_dir() + '/' + pkg_path + ext
|
||||||
if (fd.is_file(mod_path)) {
|
if (fd.is_file(mod_path)) {
|
||||||
@@ -955,32 +862,25 @@ function resolve_locator(path, ext, ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check core directory for core modules
|
// 3. Check core (as a fallback)
|
||||||
|
// "core" is now just another package, potentially.
|
||||||
|
// But if the user really wants to load "time", "js", etc, which are in core.
|
||||||
|
// We can try to resolve them in the core package.
|
||||||
|
// Ideally 'core' is defined in dependencies if needed, or we hardcode a fallback.
|
||||||
|
// Hardcoded fallback for now to match behavior:
|
||||||
var core_dir = Shop.get_core_dir()
|
var core_dir = Shop.get_core_dir()
|
||||||
|
var core_file_path = core_dir + '/' + path + ext
|
||||||
|
|
||||||
// For packages, try the full package path first in core
|
|
||||||
if (ctx) {
|
|
||||||
var pkg_rel = ctx + '/' + path + ext
|
|
||||||
var pkg_core_path = core_dir + '/' + pkg_rel
|
|
||||||
if (fd.is_file(pkg_core_path)) {
|
|
||||||
var fn = resolve_core_mod_fn(pkg_core_path, pkg_rel)
|
|
||||||
return {path: pkg_rel, scope: SCOPE_CORE, symbol:fn};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check core directory for the module
|
|
||||||
// Core scripts are now in .cell/core/scripts
|
|
||||||
var core_dir = Shop.get_core_dir()
|
|
||||||
var core_file_path = core_dir + '/scripts/' + path + ext
|
|
||||||
if (path == 'text') log.console("Checking core mod: " + core_file_path + " exists: " + fd.is_file(core_file_path))
|
|
||||||
if (fd.is_file(core_file_path)) {
|
if (fd.is_file(core_file_path)) {
|
||||||
var fn = resolve_core_mod_fn(core_file_path, path + ext)
|
// Core is treated as a package now, essentially
|
||||||
|
var fn = resolve_mod_fn(core_file_path, 'core') // using 'core' string as package for now
|
||||||
return {path: path + ext, scope: SCOPE_CORE, symbol:fn};
|
return {path: path + ext, scope: SCOPE_CORE, symbol:fn};
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function c_sym_path(path)
|
function c_sym_path(path)
|
||||||
{
|
{
|
||||||
return path.replace(/\//g, '_').replace(/\\/g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
return path.replace(/\//g, '_').replace(/\\/g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||||
@@ -989,14 +889,16 @@ function c_sym_path(path)
|
|||||||
function resolve_c_symbol(path, package_context)
|
function resolve_c_symbol(path, package_context)
|
||||||
{
|
{
|
||||||
var static_only = cell.static_only
|
var static_only = cell.static_only
|
||||||
var local_path = package_context ? package_context : 'local'
|
|
||||||
var local_sym_base = c_sym_path(path)
|
var local_sym_base = c_sym_path(path)
|
||||||
var local
|
|
||||||
|
|
||||||
|
// Candidates for symbol names
|
||||||
function symbol_candidates(pkg_path, mod_sym) {
|
function symbol_candidates(pkg_path, mod_sym) {
|
||||||
var variants = []
|
var variants = []
|
||||||
|
// if pkg_path is 'gitea.pockle.world/john/prosperon', we want:
|
||||||
|
// js_gitea_pockle_world_john_prosperon_mod_use
|
||||||
|
// and maybe with leading slash?
|
||||||
var paths = [pkg_path]
|
var paths = [pkg_path]
|
||||||
if (!pkg_path.startsWith('/')) paths.push('/' + pkg_path)
|
// if (!pkg_path.startsWith('/')) paths.push('/' + pkg_path) // unlikely to need slash variant for standard pkgs
|
||||||
for (var i = 0; i < paths.length; i++) {
|
for (var i = 0; i < paths.length; i++) {
|
||||||
var candidate = `js_${c_sym_path(paths[i])}_${mod_sym}_use`
|
var candidate = `js_${c_sym_path(paths[i])}_${mod_sym}_use`
|
||||||
if (variants.indexOf(candidate) < 0)
|
if (variants.indexOf(candidate) < 0)
|
||||||
@@ -1005,36 +907,39 @@ function resolve_c_symbol(path, package_context)
|
|||||||
return variants
|
return variants
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!package_context) {
|
// 1. Check internal symbols (statically linked)
|
||||||
local = `js_local_${local_sym_base}_use`
|
// If we have a package context, we check package-prefixed symbols
|
||||||
} else {
|
// If not, we look for 'js_local_...' or 'js_path_use' directly?
|
||||||
local = null // handled via candidates below
|
|
||||||
}
|
|
||||||
|
|
||||||
// First check for statically linked/internal symbols
|
if (package_context) {
|
||||||
var local_candidates = package_context ? symbol_candidates(local_path, local_sym_base) : [local]
|
// Check package internal symbols
|
||||||
for (var li = 0; li < local_candidates.length; li++) {
|
var variants = symbol_candidates(package_context, local_sym_base)
|
||||||
var lc = local_candidates[li]
|
for (var i = 0; i < variants.length; i++) {
|
||||||
if (os.internal_exists(lc))
|
if (os.internal_exists(variants[i])) {
|
||||||
return {
|
return {
|
||||||
symbol: function() { return os.load_internal(lc); },
|
symbol: function() { return os.load_internal(variants[i]); },
|
||||||
scope: SCOPE_LOCAL,
|
scope: SCOPE_PACKAGE,
|
||||||
path: lc
|
path: variants[i]
|
||||||
};
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In static_only mode, skip dynamic library lookups
|
// In static_only mode, skip dynamic library lookups
|
||||||
if (!static_only) {
|
if (!static_only) {
|
||||||
// Then try dynamic library
|
// Then try dynamic library
|
||||||
var build_dir = get_build_dir(package_context)
|
// Use ./libcellmod for local?
|
||||||
var local_dl_name = build_dir + '/libcellmod' + dylib_ext
|
var local_dl_name = './libcellmod' + dylib_ext
|
||||||
|
// Or check package context dir if it's a local package path?
|
||||||
|
if (package_context && package_context.startsWith('/')) {
|
||||||
|
local_dl_name = package_context + '/libcellmod' + dylib_ext
|
||||||
|
}
|
||||||
|
|
||||||
if (fd.is_file(local_dl_name)) {
|
if (fd.is_file(local_dl_name)) {
|
||||||
if (!open_dls[local_dl_name])
|
if (!open_dls[local_dl_name])
|
||||||
open_dls[local_dl_name] = os.dylib_open(local_dl_name);
|
open_dls[local_dl_name] = os.dylib_open(local_dl_name);
|
||||||
|
|
||||||
if (open_dls[local_dl_name]) {
|
if (open_dls[local_dl_name]) {
|
||||||
var locals = package_context ? symbol_candidates(local_path, local_sym_base) : [local]
|
var locals = package_context ? symbol_candidates(package_context, local_sym_base) : [`js_local_${local_sym_base}_use`]
|
||||||
for (var i = 0; i < locals.length; i++) {
|
for (var i = 0; i < locals.length; i++) {
|
||||||
var candidate = locals[i]
|
var candidate = locals[i]
|
||||||
if (os.dylib_has_symbol(open_dls[local_dl_name], candidate))
|
if (os.dylib_has_symbol(open_dls[local_dl_name], candidate))
|
||||||
@@ -1047,8 +952,19 @@ function resolve_c_symbol(path, package_context)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Local context
|
||||||
|
var local_sym = `js_local_${local_sym_base}_use`
|
||||||
|
if (os.internal_exists(local_sym)) {
|
||||||
|
return {
|
||||||
|
symbol: function() { return os.load_internal(local_sym); },
|
||||||
|
scope: SCOPE_LOCAL,
|
||||||
|
path: local_sym
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If 'path' has a package alias (e.g. 'prosperon/sprite'), try to resolve it
|
// 2. Check if valid package import (e.g. 'prosperon/sprite')
|
||||||
var pkg_alias = get_import_package(path)
|
var pkg_alias = get_import_package(path)
|
||||||
if (pkg_alias) {
|
if (pkg_alias) {
|
||||||
var canon_pkg = get_normalized_package(path, package_context)
|
var canon_pkg = get_normalized_package(path, package_context)
|
||||||
@@ -1057,21 +973,20 @@ function resolve_c_symbol(path, package_context)
|
|||||||
var mod_sym = mod_name.replace(/\//g, '_').replace(/-/g, '_').replace(/\./g, '_')
|
var mod_sym = mod_name.replace(/\//g, '_').replace(/-/g, '_').replace(/\./g, '_')
|
||||||
var sym_names = symbol_candidates(canon_pkg, mod_sym)
|
var sym_names = symbol_candidates(canon_pkg, mod_sym)
|
||||||
|
|
||||||
// First check internal/static symbols for package
|
for (var i = 0; i < sym_names.length; i++) {
|
||||||
for (var sii = 0; sii < sym_names.length; sii++) {
|
if (os.internal_exists(sym_names[i])) {
|
||||||
var sym_name = sym_names[sii]
|
|
||||||
if (os.internal_exists(sym_name))
|
|
||||||
return {
|
return {
|
||||||
symbol: function() { return os.load_internal(sym_name) },
|
symbol: function() { return os.load_internal(sym_names[i]); },
|
||||||
scope: SCOPE_PACKAGE,
|
scope: SCOPE_PACKAGE,
|
||||||
package: canon_pkg,
|
package: canon_pkg,
|
||||||
path: sym_name
|
path: sym_names[i]
|
||||||
};
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then try dynamic library for package (skip in static_only mode)
|
// Then try dynamic library for package (skip in static_only mode)
|
||||||
if (!static_only) {
|
if (!static_only) {
|
||||||
var pkg_build_dir = get_build_dir(canon_pkg)
|
// Check package dir for libcellmod
|
||||||
|
var pkg_build_dir = get_modules_dir() + '/' + canon_pkg
|
||||||
var dl_path = pkg_build_dir + '/libcellmod' + dylib_ext
|
var dl_path = pkg_build_dir + '/libcellmod' + dylib_ext
|
||||||
if (fd.is_file(dl_path)) {
|
if (fd.is_file(dl_path)) {
|
||||||
if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path)
|
if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path)
|
||||||
@@ -1092,6 +1007,7 @@ function resolve_c_symbol(path, package_context)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Check Core/General fallback (js_path_use)
|
||||||
var core_sym = `js_${path.replace(/\//g, '_')}_use`;
|
var core_sym = `js_${path.replace(/\//g, '_')}_use`;
|
||||||
if (os.internal_exists(core_sym))
|
if (os.internal_exists(core_sym))
|
||||||
return {
|
return {
|
||||||
@@ -1150,14 +1066,20 @@ function execute_module(info)
|
|||||||
|
|
||||||
var used
|
var used
|
||||||
|
|
||||||
if (c_resolve.scope < mod_resolve.scope)
|
// If we have a script, it always takes precedence (containing the C symbol if available)
|
||||||
|
// This supports "Hybrid" modules where JS wraps C.
|
||||||
|
if (mod_resolve.scope < 900) {
|
||||||
|
var context = null
|
||||||
|
if (c_resolve.scope < 900) {
|
||||||
|
context = c_resolve.symbol(null, $_)
|
||||||
|
}
|
||||||
|
used = mod_resolve.symbol.call(context, $_)
|
||||||
|
} else if (c_resolve.scope < 900) {
|
||||||
|
// C only
|
||||||
used = c_resolve.symbol(null, $_)
|
used = c_resolve.symbol(null, $_)
|
||||||
else if (mod_resolve.scope < c_resolve.scope)
|
} else {
|
||||||
used = mod_resolve.symbol.call(null, $_)
|
throw new Error(`Module ${info.path} could not be found`)
|
||||||
else
|
} if (!used)
|
||||||
used = mod_resolve.symbol.call(c_resolve.symbol(), $_)
|
|
||||||
|
|
||||||
if (!used)
|
|
||||||
throw new Error(`Module ${json.encode(info)} returned null`)
|
throw new Error(`Module ${json.encode(info)} returned null`)
|
||||||
|
|
||||||
return used
|
return used
|
||||||
@@ -1172,10 +1094,6 @@ function get_module(path, package_context) {
|
|||||||
return execute_module(info)
|
return execute_module(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
Shop.use = function(path, package_context) {
|
||||||
var info = resolve_module_info(path, package_context)
|
var info = resolve_module_info(path, package_context)
|
||||||
if (!info)
|
if (!info)
|
||||||
@@ -1585,7 +1503,7 @@ Shop.remove_replacement = function(alias) {
|
|||||||
// List all files in a package
|
// List all files in a package
|
||||||
Shop.list_files = function(pkg) {
|
Shop.list_files = function(pkg) {
|
||||||
var dir
|
var dir
|
||||||
if (!pkg) dir = current_package_path || '.'
|
if (!pkg) dir = '.'
|
||||||
else dir = get_modules_dir() + '/' + pkg
|
else dir = get_modules_dir() + '/' + pkg
|
||||||
|
|
||||||
var files = []
|
var files = []
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
#include "cell_internal.h"
|
#include "cell_internal.h"
|
||||||
|
|
||||||
#define ENGINE "scripts/engine.cm"
|
#define ENGINE "internal/engine.cm"
|
||||||
#define CELL_SHOP_DIR ".cell"
|
#define CELL_SHOP_DIR ".cell"
|
||||||
#define CELL_CORE_DIR "core"
|
#define CELL_CORE_DIR "core"
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ static const JSCFunctionListEntry js_time_funcs[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
JSValue
|
JSValue
|
||||||
js_time_use(JSContext *ctx)
|
js_internal_time_use(JSContext *ctx)
|
||||||
{
|
{
|
||||||
JSValue obj = JS_NewObject(ctx);
|
JSValue obj = JS_NewObject(ctx);
|
||||||
JS_SetPropertyFunctionList(ctx, obj,
|
JS_SetPropertyFunctionList(ctx, obj,
|
||||||
@@ -78,3 +78,9 @@ js_time_use(JSContext *ctx)
|
|||||||
sizeof(js_time_funcs[0]));
|
sizeof(js_time_funcs[0]));
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSValue
|
||||||
|
js_time_use(JSContext *ctx)
|
||||||
|
{
|
||||||
|
return js_internal_time_use(ctx);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user