diff --git a/examples/chess/main.js b/examples/chess/main.js index 3c0761bd..3abf7408 100644 --- a/examples/chess/main.js +++ b/examples/chess/main.js @@ -327,6 +327,7 @@ function joinServer() { var os = use('os') var actor = use('actor') +for (var i in actor) console.log(i) // Set up IO actor subscription var ioguy = { diff --git a/scripts/core/engine.js b/scripts/core/engine.js index 32a2d332..0883a762 100644 --- a/scripts/core/engine.js +++ b/scripts/core/engine.js @@ -23,12 +23,6 @@ prosperon.dispatch = function(type, data) { var os = use_embed('os') var actor_mod = use_embed('actor') -var tracy = use_embed('tracy') - -os.trace = true; - -if (os.trace) - tracy.level(1); var js = use_embed('js') @@ -46,19 +40,6 @@ prosperon.on('SIGSEGV', function() { os.exit(1) }) -Object.defineProperty(Function.prototype, "hashify", { - value: function () { - var hash = new Map() - var fn = this - function hashified(...args) { - var key = args[0] - if (!hash.has(key)) hash.set(key, fn(...args)) - return hash.get(key) - } - return hashified - }, -}) - var io = use_embed('io') globalThis.console = use_embed('console') @@ -70,7 +51,7 @@ var resources = js.eval(RESPATH, `(function setup_resources(){${content}})`).cal var use_cache = {} -use_cache[resources.canonical('resources.js')] = resources +use_cache['resources'] = resources function print_api(obj) { for (var prop in obj) { @@ -86,61 +67,14 @@ function print_api(obj) { prosperon.PATH = [ "/", - "scripts/modules/", - "scripts/modules/ext/", + "scripts/modules/" ] -// path is the path of a module or script to resolve -var script_fn = function script_fn(path, args) { - var parsed = {} - var file = resources.find_script(path) - - if (!file) { - parsed.module_ret = bare_load(path) - if (!parsed.module_ret) throw new Error(`Module ${path} could not be created`) - return parsed - } - - var content = io.slurp(file) - var parsed = parse_file(content, file) - var module_name = file.name() - parsed.module_ret = bare_load(path) - parsed.module_ret ??= {} - - if (parsed.module) { - // Create a context object with args - var context = Object.create(parsed.module_ret) - context.__args__ = args || [] - var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; var arg = this.__args__; ${parsed.module}})` - var module_fn = js.eval(file, mod_script) - parsed.module_ret = module_fn.call(context) - if (parsed.module_ret === undefined || parsed.module_ret === null) - throw new Error(`Module ${module_name} must return a value`) - parsed.module_fn = module_fn - } - - parsed.program ??= "" - var prog_script = `(function use_${module_name}($_) { var self = this; var $ = this.__proto__; ${parsed.program}})` - parsed.prog_fn = js.eval(file, prog_script) - - return parsed -}.hashify() - -function bare_load(file) { - try { - return use_embed(file) - } catch (e) { } - try { - return use_dyn(file + so_ext) - } catch(e) { } - return undefined -} - - - var res_cache = {} function console_rec(category, priority, line, file, msg) { + return `[${file}:${line}: [${category} ${priority}]: ${msg}\n` + var now = time.now() var id = prosperon.name ? prosperon.name : prosperon.id @@ -149,12 +83,7 @@ function console_rec(category, priority, line, file, msg) { return `[${id}] [${time.text(now, "mb d yyyy h:nn:ss")}] ${file}:${line}: [${category} ${priority}]: ${msg}\n` } -io.mkdir('.prosperon') -var logfile //= io.open('.prosperon/log.txt') - function pprint(msg, lvl = 0) { - if (!logfile) return - var file = "nofile" var line = 0 @@ -169,11 +98,6 @@ function pprint(msg, lvl = 0) { } var fmt = console_rec("script", lvl, line, file, msg) console.print(fmt) - - if (logfile) - logfile.write(fmt) - - if (tracy) tracy.message(fmt) } function format_args(...args) { @@ -248,64 +172,28 @@ var fnname = "base" script = `(function ${fnname}() { ${script}; })` js.eval(BASEPATH, script)() -function add_timer(obj, fn, seconds) { - var timers = obj[TIMERS] - - var stop = function () { - if (!timer) return - timers.delete(stop) - timer.fn = undefined - timer = undefined - } - - function execute() { - if (fn) timer.remain = fn(stop.seconds) - if (!timer) return - if (!timer.remain) stop() - else stop.seconds = timer.remain - } - -// var timer = os.make_timer(execute) - timer.remain = seconds - - stop.remain = seconds - stop.seconds = seconds - - timers.push(stop) - return stop -} - -var DEAD = Symbol() -var GARBAGE = Symbol() -var FILE = Symbol() -var TIMERS = Symbol() -var REGGIES = Symbol() -var UNDERLINGS = Symbol() -var OVERLING = Symbol() - -var actor = {} -globalThis.actor = actor - -var so_ext -switch(os.platform()) { - case 'Windows': - so_ext = '.dll' - break - default: - so_ext = '.so' - break -} - -var use_cache = {} var inProgress = {} var loadingStack = [] globalThis.use = function use(file, ...args) { - // If we've already begun loading this file in this chain, show the cycle + // Normalize the request - remove .js extension if present + var request_name = file + if (file.endsWith('.js')) { + request_name = file.substring(0, file.length - 3) + } + + // Check cache first - both 'transform' and 'transform.js' should return same cached value + for (var cached_key in use_cache) { + if (cached_key === file || cached_key === request_name || + cached_key === request_name + '.js' || + (cached_key.endsWith('.js') && cached_key.substring(0, cached_key.length - 3) === request_name)) { + return use_cache[cached_key] + } + } + + // Check for circular dependencies if (loadingStack.includes(file)) { - // Find where in the stack this file first appeared let cycleIndex = loadingStack.indexOf(file) - // Extract just the modules in the cycle let cyclePath = loadingStack.slice(cycleIndex).concat(file) throw new Error( @@ -314,292 +202,81 @@ globalThis.use = function use(file, ...args) { `Cycle specifically: ${cyclePath.join(" -> ")}` ) } - - // Already fully loaded? Return it - if (use_cache[file]) { - return use_cache[file] + + // Try to find the script file + var path = resources.find_script(request_name) + + // Check if there's an embedded module + var embed_mod = use_embed(request_name) + + // If no script and no embedded module, error + if (!path && !embed_mod) { + throw new Error(`Module ${file} could not be found`) } - - // If it's loading (but not on the stack), mark it as a new chain entry - // (This is optional if you just rely on loadingStack. - // But if you'd like a simple “already loading” check, keep 'inProgress'.) - if (inProgress[file]) { + + // If only embedded module exists, return it + if (!path && embed_mod) { + use_cache[file] = embed_mod + use_cache[request_name] = embed_mod + if (file !== request_name) { + use_cache[request_name + '.js'] = embed_mod + } + return embed_mod + } + + // If we have a script path, check for circular dependency + if (inProgress[path]) { throw new Error(`Circular dependency detected while loading "${file}"`) } - inProgress[file] = true - - // Push onto loading stack for chain tracking + + inProgress[path] = true loadingStack.push(file) - - // Actually load the module with arguments - var mod = script_fn(file, args) - - // Done loading, remove from the chain and mark as loaded + + // Load and execute the script + var script = io.slurp(path) + var mod_name = path.name() + + // Create context - if embedded module exists, script extends it + var context = {} + if (embed_mod) + context.__proto__ = embed_mod + + var mod_script = `(function setup_${mod_name}_module(arg){${script};})` + var fn = js.eval(path, mod_script) + + // Call the script - pass embedded module as 'this' if it exists + var ret = fn.call(context, args) + + // If script doesn't return anything, check if we have embedded module + if (!ret && embed_mod) { + ret = embed_mod + } else if (!ret) { + throw new Error(`Use must be used with a module, but ${path} doesn't return a value`) + } + loadingStack.pop() - delete inProgress[file] - - // Cache and return - use_cache[file] = mod.module_ret - return use_cache[file] + delete inProgress[path] + + // Cache under all possible keys + use_cache[path] = ret + use_cache[file] = ret + use_cache[request_name] = ret + if (file !== request_name && !file.endsWith('.js')) { + use_cache[request_name + '.js'] = ret + } + + return ret } globalThis.json = use('json') var time = use('time') -function parse_file(content, file) { - if (!content) return {} - if (!/^\s*---\s*$/m.test(content)) { - var part = content.trim() - if (part.match(/return\s+[^;]+;?\s*$/)) { - return { module: part } - } - return { program: part } - } - var parts = content.split(/\n\s*---\s*\n/) - var module = parts[0] - if (!/\breturn\b/.test(module)) - throw new Error(`Malformed file: ${file}. Module section must end with a return statement.`) - - try { - new Function(module)() - } catch (e) { - throw new Error(`Malformed file: ${file}. Module section must end with a return statement.\n` + e.message) - } - - var pad = '\n'.repeat(module.split('\n').length + 4) - return { - module, - program: pad + parts[1] - } -} - -globalThis.Register = { - registries: [], - - add_cb(name) { - var n = {} - var fns = [] - - n.register = function (fn, oname) { - if (typeof fn !== 'function') return - - var dofn = function (...args) { - fn(...args) - } - Object.defineProperty(dofn, 'name', {value:`do_${oname}`}) - - var left = 0 - var right = fns.length - 1 - dofn.layer = fn.layer - dofn.layer ??= 0 - - while (left <= right) { - var mid = Math.floor((left + right) / 2) - if (fns[mid] === dofn.layer) { - left = mid - break - } else if (fns[mid].layer < dofn.layer) left = mid + 1 - else right = mid - 1 - } - - fns.splice(left, 0, dofn) - return function () { - fns.delete(dofn) - } - } - - prosperon[name] = function (...args) { - fns.forEach(fn => { - fn(...args) - }) - } - Object.defineProperty(prosperon[name], 'name', {value:name}) - prosperon[name].fns = fns - - n.clear = function () { - fns = [] - } - - Register[name] = n - Register.registries[name] = n - return n - }, -} - -Register.pull_registers = function pull_registers(obj) { - var reggies = [] - for (var reg in Register.registries) { - if (typeof obj[reg] === "function") - reggies.push(reg) - } - return reggies -} - -Register.register_obj = function register_obj(obj, reg) { - var fn = obj[reg].bind(obj) - fn.layer = obj[reg].layer - var name = obj.ur ? obj.ur.name : obj.toString() - obj[TIMERS].push(Register.registries[reg].register(fn, name)) - if (!obj[reg].name) Object.defineProperty(obj[reg], 'name', {value:`${obj._file}_${reg}`}) -} - -Register.check_registers = function check_registers(obj) { - if (obj[REGGIES]) { - if (obj[REGGIES].length == 0) return - for (var reg of obj[REGGIES]) - Register.register_obj(obj,reg) - return - } - for (var reg in Register.registries) { - if (typeof obj[reg] === "function") - Register.register_obj(obj,reg) - } -} - -Register.add_cb("appupdate") -Register.add_cb("update").doc = "Called once per frame." -Register.add_cb("physupdate") -Register.add_cb("gui") -Register.add_cb("hud") -Register.add_cb("draw") -Register.add_cb("imgui") -Register.add_cb("app") - -function cant_kill() { - throw Error("Can't kill an object in its spawning code. Move the kill command to awake.") -} - -actor.toString = function() { return this[FILE] } - -actor.spawn = function spawn(script, config, actor_context) { - if (this[DEAD]) throw new Error("Attempting to spawn on a dead actor") - var prog - if (!script) { - prog = {} - prog.module_ret = {} - prog.prog_fn = function() {} - } else { - prog = script_fn(script) - if (!prog.prog_fn) throw new Error(`Script ${script} is not an actor script or has no actor component`) - } - - var underling - prog.module_ret.__proto__ = actor - underling = Object.create(prog.module_ret) - underling[OVERLING] = this - underling[FILE] = script - underling[TIMERS] = [] - underling[UNDERLINGS] = new Set() - - // Make $_ available to the actor (either passed context or the engine's $_) - var actor_dollar = actor_context || $_ - Object.defineProperty(underling, '$_', { - value: actor_dollar, - writable:false, - enumerable:false, - configurable:false - }) - - Object.defineProperty(underling, 'overling', { - get() { return this[OVERLING] }, - enumerable:true, - configurable:false - }) - - Object.defineProperty(underling, 'underlings', { - get() { return new Set(this[UNDERLINGS]) }, - enumerable:true, - configurable:false - }) - - Object.defineProperty(underling, 'spawn', { - value: function(script, config) { - return actor.spawn.call(this, script, config, actor_dollar) - }, - writable:false, - enumerable:true, - configurable:false - }) - - Object.defineProperty(underling, 'kill', { - value: actor.kill, - writable:false, - enumerable:true, - configurable:false - }) - - Object.defineProperty(underling, 'delay', { - value: actor.delay, - writable:false, - enumerable:true, - configurable:false - }) - - try { - // Pass $_ as a parameter to actor scripts - prog.prog_fn.call(underling, actor_dollar) - } catch(e) { throw e; } - - if (underling[DEAD]) return undefined; - - if (typeof config === 'object') Object.assign(underling, config) - - if (!underling[REGGIES]) - underling.__proto__[REGGIES] = Register.pull_registers(underling) - - Register.check_registers(underling) - if (underling.awake) underling.awake() - - this[UNDERLINGS].add(underling) - if (underling.tag) act.tag_add(underling.tag, underling) - - underling[GARBAGE] = underling.garbage - return underling -} - -actor.clear = function actor_clear() { - this[UNDERLINGS].forEach(p => { - p.kill() - }) - this[UNDERLINGS].clear() -} - -var input = use('input') - -actor.kill = function kill() { - if (this[DEAD]) return - this[DEAD] = true - this[TIMERS].slice().forEach(t => t()) - delete this[TIMERS] - input.do_uncontrol(this) - - this.clear() - this[OVERLING][UNDERLINGS].delete(this) - delete this[UNDERLINGS] - - if (typeof this.garbage === "function") this.garbage() - if (typeof this.then === "function") this.then() - - act.tag_clear_guid(this) -} -actor.kill.doc = `Remove this actor and all its underlings from existence.` - -actor.delay = function(fn, seconds) { - if (this[DEAD]) return - add_timer(this, fn, seconds) -} -actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.` - -var act = use('actor') -actor[UNDERLINGS] = new Set() - globalThis.mixin("color") var DOCPATH = 'scripts/core/doc.js' var script = io.slurp(DOCPATH) var fnname = "doc" script = `(function ${fnname}() { ${script}; })` -//js.eval(DOCPATH, script)() /* When handling a message, the message appears like this: @@ -937,10 +614,7 @@ if (!prosperon.args.program) os.exit(1) if (typeof prosperon.args.program !== 'string') - prosperon.args.program = 'main.js'; - -// Spawn the root actor with access to $_ -actor.spawn.call({[UNDERLINGS]: new Set()}, prosperon.args.program, undefined) + prosperon.args.program = 'main.js'; function destroyself() { console.log(`Got the message to destroy self.`) @@ -1023,4 +697,11 @@ function enet_check() send_messages(); enet_check(); +// Finally, run the program +var prog = io.slurp(prosperon.args.program) +var prog_script = `(function ${prosperon.args.program.name()}($_) { ${prog} })` +var val = js.eval(prosperon.args.program, prog_script)($_) +if (val) + throw new Error('Program must not return anything'); + })() \ No newline at end of file diff --git a/scripts/modules/cmd.js b/scripts/modules/cmd.js index fa9708b2..f8c1a0cc 100644 --- a/scripts/modules/cmd.js +++ b/scripts/modules/cmd.js @@ -73,9 +73,6 @@ Cmdline.register_order( else app = actor.spawn("nogame.js") - // rm actor so it can't be tampered - globalThis.actor = undefined - var loop = use('loop') while(1) loop.step(); }, diff --git a/scripts/modules/draw2d.js b/scripts/modules/draw2d.js index 55d4758a..8b6ca884 100644 --- a/scripts/modules/draw2d.js +++ b/scripts/modules/draw2d.js @@ -459,7 +459,6 @@ var sysfont = graphics.get_font('fonts/c64.ttf', 8) draw.text = function text(text, rect, font = sysfont, size = 0, color = Color.white, wrap = 0, pipeline) { if (typeof font === 'string') font = graphics.get_font(font) var mesh = graphics.make_text_buffer(text, rect, 0, color, wrap, font) - console.log(json.encode(mesh)) render.geometry(font, mesh) } draw.text[prosperon.DOC] = ` diff --git a/scripts/modules/ext/transform.js b/scripts/modules/ext/transform.js deleted file mode 100644 index 34c7fc8e..00000000 --- a/scripts/modules/ext/transform.js +++ /dev/null @@ -1,31 +0,0 @@ -return { - get pos() { - if (!this.transform) return - return this.transform.pos; - }, - set pos(x) { - this.transform.pos = x; - }, - get angle() { - return this.transform.angle; - }, - set angle(x) { - this.transform.angle = x; - }, - get scale() { - return this.transform.scale; - }, - set scale(x) { - this.transform.scale = x; - }, - move(vec) { - this.pos = this.pos.add(vec); - }, - rotate(x) { - this.transform.rotate([0, 0, -1],x); - }, - grow(vec) { - if (typeof vec === "number") vec = [vec, vec]; - this.scale = this.scale.map((x, i) => x * vec[i]); - }, -} diff --git a/scripts/modules/moth.js b/scripts/modules/moth.js index f2417dc4..0c34a650 100644 --- a/scripts/modules/moth.js +++ b/scripts/modules/moth.js @@ -19,6 +19,7 @@ var io = use('io'); var render = use('render'); var actor = use('actor'); var transform = use('transform'); + var gameConfig = {}; var gameDir = ""; diff --git a/scripts/modules/resources.js b/scripts/modules/resources.js index ed544e21..c20ecded 100644 --- a/scripts/modules/resources.js +++ b/scripts/modules/resources.js @@ -2,6 +2,19 @@ var io = use_embed('io'); var miniz = use_embed('miniz'); var os = use_embed('os'); +Object.defineProperty(Function.prototype, "hashify", { + value: function () { + var hash = new Map() + var fn = this + function hashified(...args) { + var key = args[0] + if (!hash.has(key)) hash.set(key, fn(...args)) + return hash.get(key) + } + return hashified + }, +}) + // Merge of the old resources.js and packer.js functionalities var Resources = {} diff --git a/scripts/modules/scenetree.js b/scripts/modules/scenetree.js index 7d2f7bb9..9282e4f3 100644 --- a/scripts/modules/scenetree.js +++ b/scripts/modules/scenetree.js @@ -1,4 +1,259 @@ var ex = this + +var input = use('input') + +var DEAD = Symbol() +var GARBAGE = Symbol() +var FILE = Symbol() +var TIMERS = Symbol() +var REGGIES = Symbol() +var UNDERLINGS = Symbol() +var OVERLING = Symbol() + +function add_timer(obj, fn, seconds) { + var timers = obj[TIMERS] + + var stop = function () { + if (!timer) return + timers.delete(stop) + timer.fn = undefined + timer = undefined + } + + function execute() { + if (fn) timer.remain = fn(stop.seconds) + if (!timer) return + if (!timer.remain) stop() + else stop.seconds = timer.remain + } + +// var timer = os.make_timer(execute) + timer.remain = seconds + + stop.remain = seconds + stop.seconds = seconds + + timers.push(stop) + return stop +} + +globalThis.Register = { + registries: [], + + add_cb(name) { + var n = {} + var fns = [] + + n.register = function (fn, oname) { + if (typeof fn !== 'function') return + + var dofn = function (...args) { + fn(...args) + } + Object.defineProperty(dofn, 'name', {value:`do_${oname}`}) + + var left = 0 + var right = fns.length - 1 + dofn.layer = fn.layer + dofn.layer ??= 0 + + while (left <= right) { + var mid = Math.floor((left + right) / 2) + if (fns[mid] === dofn.layer) { + left = mid + break + } else if (fns[mid].layer < dofn.layer) left = mid + 1 + else right = mid - 1 + } + + fns.splice(left, 0, dofn) + return function () { + fns.delete(dofn) + } + } + + prosperon[name] = function (...args) { + fns.forEach(fn => { + fn(...args) + }) + } + Object.defineProperty(prosperon[name], 'name', {value:name}) + prosperon[name].fns = fns + + n.clear = function () { + fns = [] + } + + Register[name] = n + Register.registries[name] = n + return n + }, +} + +Register.pull_registers = function pull_registers(obj) { + var reggies = [] + for (var reg in Register.registries) { + if (typeof obj[reg] === "function") + reggies.push(reg) + } + return reggies +} + +Register.register_obj = function register_obj(obj, reg) { + var fn = obj[reg].bind(obj) + fn.layer = obj[reg].layer + var name = obj.ur ? obj.ur.name : obj.toString() + obj[TIMERS].push(Register.registries[reg].register(fn, name)) + if (!obj[reg].name) Object.defineProperty(obj[reg], 'name', {value:`${obj._file}_${reg}`}) +} + +Register.check_registers = function check_registers(obj) { + if (obj[REGGIES]) { + if (obj[REGGIES].length == 0) return + for (var reg of obj[REGGIES]) + Register.register_obj(obj,reg) + return + } + for (var reg in Register.registries) { + if (typeof obj[reg] === "function") + Register.register_obj(obj,reg) + } +} + +Register.add_cb("appupdate") +Register.add_cb("update").doc = "Called once per frame." +Register.add_cb("physupdate") +Register.add_cb("gui") +Register.add_cb("hud") +Register.add_cb("draw") +Register.add_cb("imgui") +Register.add_cb("app") + + +var actor = {} + +actor.toString = function() { return this[FILE] } + +actor.spawn = function spawn(script, config, actor_context) { + if (this[DEAD]) throw new Error("Attempting to spawn on a dead actor") + var prog + if (!script) { + prog = {} + prog.module_ret = {} + prog.prog_fn = function() {} + } else { + prog = script_fn(script) + if (!prog.prog_fn) throw new Error(`Script ${script} is not an actor script or has no actor component`) + } + + var underling + prog.module_ret.__proto__ = actor + underling = Object.create(prog.module_ret) + underling[OVERLING] = this + underling[FILE] = script + underling[TIMERS] = [] + underling[UNDERLINGS] = new Set() + + // Make $_ available to the actor (either passed context or the engine's $_) + var actor_dollar = actor_context || $_ + Object.defineProperty(underling, '$_', { + value: actor_dollar, + writable:false, + enumerable:false, + configurable:false + }) + + Object.defineProperty(underling, 'overling', { + get() { return this[OVERLING] }, + enumerable:true, + configurable:false + }) + + Object.defineProperty(underling, 'underlings', { + get() { return new Set(this[UNDERLINGS]) }, + enumerable:true, + configurable:false + }) + + Object.defineProperty(underling, 'spawn', { + value: function(script, config) { + return actor.spawn.call(this, script, config, actor_dollar) + }, + writable:false, + enumerable:true, + configurable:false + }) + + Object.defineProperty(underling, 'kill', { + value: actor.kill, + writable:false, + enumerable:true, + configurable:false + }) + + Object.defineProperty(underling, 'delay', { + value: actor.delay, + writable:false, + enumerable:true, + configurable:false + }) + + try { + // Pass $_ as a parameter to actor scripts + prog.prog_fn.call(underling, actor_dollar) + } catch(e) { throw e; } + + if (underling[DEAD]) return undefined; + + if (typeof config === 'object') Object.assign(underling, config) + + if (!underling[REGGIES]) + underling.__proto__[REGGIES] = Register.pull_registers(underling) + + Register.check_registers(underling) + if (underling.awake) underling.awake() + + this[UNDERLINGS].add(underling) + if (underling.tag) act.tag_add(underling.tag, underling) + + underling[GARBAGE] = underling.garbage + return underling +} + +actor.clear = function actor_clear() { + this[UNDERLINGS].forEach(p => { + p.kill() + }) + this[UNDERLINGS].clear() +} + +actor.kill = function kill() { + if (this[DEAD]) return + this[DEAD] = true + this[TIMERS].slice().forEach(t => t()) + delete this[TIMERS] + input.do_uncontrol(this) + + this.clear() + this[OVERLING][UNDERLINGS].delete(this) + delete this[UNDERLINGS] + + if (typeof this.garbage === "function") this.garbage() + if (typeof this.then === "function") this.then() + + act.tag_clear_guid(this) +} +actor.kill.doc = `Remove this actor and all its underlings from existence.` + +actor.delay = function(fn, seconds) { + if (this[DEAD]) return + add_timer(this, fn, seconds) +} +actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.` + +actor[UNDERLINGS] = new Set() + + ex[prosperon.DOC] = ` A set of utilities for iterating over a hierarchy of actor-like objects, as well as managing tag-based lookups. Objects are assumed to have a "objects" property, @@ -75,4 +330,68 @@ ex.objects_with_tag[prosperon.DOC] = ` Retrieve all objects currently tagged with the specified tag. ` +function parse_file(content, file) { + if (!content) return {} + if (content.match() + if (!/^\s*---\s*$/m.test(content)) { + var part = content.trim() + if (part.match(/return\s+[^;]+;?\s*$/)) { + return { module: part } + } + return { program: part } + } + var parts = content.split(/\n\s*---\s*\n/) + var module = parts[0] + if (!/\breturn\b/.test(module)) + throw new Error(`Malformed file: ${file}. Module section must end with a return statement.`) + + try { + new Function(module)() + } catch (e) { + throw new Error(`Malformed file: ${file}. Module section must end with a return statement.\n` + e.message) + } + + var pad = '\n'.repeat(module.split('\n').length + 4) + return { + module, + program: pad + parts[1] + } +} + +// path is the path of a module or script to resolve +var script_fn = function script_fn(path, args) { + var parsed = {} + var file = resources.find_script(path) + + if (!file) { + parsed.module_ret = bare_load(path) + if (!parsed.module_ret) throw new Error(`Module ${path} could not be created`) + return parsed + } + + var content = io.slurp(file) + var parsed = parse_file(content, file) + var module_name = file.name() + parsed.module_ret = bare_load(path) + parsed.module_ret ??= {} + + if (parsed.module) { + // Create a context object with args + var context = Object.create(parsed.module_ret) + context.__args__ = args || [] + var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; var arg = this.__args__; ${parsed.module}})` + var module_fn = js.eval(file, mod_script) + parsed.module_ret = module_fn.call(context) + if (parsed.module_ret === undefined || parsed.module_ret === null) + throw new Error(`Module ${module_name} must return a value`) + parsed.module_fn = module_fn + } + + parsed.program ??= "" + var prog_script = `(function use_${module_name}($_) { var self = this; var $ = this.__proto__; ${parsed.program}})` + parsed.prog_fn = js.eval(file, prog_script) + + return parsed +}.hashify() + return ex diff --git a/source/jsffi.c b/source/jsffi.c index cb94d03e..0df5e08c 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -1906,9 +1906,7 @@ JSC_CCALL(os_guid, return JS_NewString(js,guid_str); ) -JSC_SCALL(console_print, - printf("%s", str); -) +JSC_SCALL(console_print, printf("%s", str); ) static const JSCFunctionListEntry js_console_funcs[] = { MIST_FUNC_DEF(console,print,1), @@ -2762,9 +2760,6 @@ JSC_SCALL(os_use_embed, break; } } - - if (JS_IsUndefined(ret)) - ret = JS_ThrowReferenceError(js,"Library %s could not be found embedded", str); ) JSC_SCALL(os_use_dyn,