actor files now .ce; module files now .cm; add toml encoder/decoder and test
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -13,20 +13,16 @@ Jenkinsfile
|
||||
*.gz
|
||||
*.tar
|
||||
.nova/
|
||||
packer*
|
||||
primum
|
||||
sokol-shdc*
|
||||
source/shaders/*.h
|
||||
core.cdb
|
||||
primum.exe
|
||||
core.cdb.h
|
||||
jsc
|
||||
.DS_Store
|
||||
*.html
|
||||
.vscode
|
||||
*.icns
|
||||
game.zip
|
||||
icon.ico
|
||||
steam/
|
||||
subprojects/*/
|
||||
build_dbg/
|
||||
.cell
|
||||
|
||||
10
CLAUDE.md
10
CLAUDE.md
@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
## Build Commands
|
||||
|
||||
### Build variants
|
||||
- `make debug` - Build debug version (uses meson debug configuration)
|
||||
- `make` - Make and install debug version. Usually all that's needed.
|
||||
- `make fast` - Build optimized version
|
||||
- `make release` - Build release version with LTO and optimizations
|
||||
- `make small` - Build minimal size version
|
||||
@@ -13,10 +13,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
- `make crosswin` - Cross-compile for Windows using mingw32
|
||||
|
||||
### Testing
|
||||
- `meson test -C build_dbg` - Run all tests in debug build
|
||||
- `meson test -C build_<variant>` - Run tests in specific build variant
|
||||
- `./build_dbg/prosperon tests/<testname>.js` - Run specific test
|
||||
- Available tests: `spawn_actor`, `empty`, `nota`, `wota`, `portalspawner`, `overling`, `send`, `delay`
|
||||
After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
|
||||
|
||||
## Scripting language
|
||||
This is called "cell", but it is is a variant of javascript and extremely similar.
|
||||
|
||||
### Common development commands
|
||||
- `meson setup build_<variant>` - Configure build directory
|
||||
|
||||
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
||||
debug: FORCE
|
||||
meson setup build_dbg -Dbuildtype=debug
|
||||
meson install -C build_dbg
|
||||
meson install --only-changed -C build_dbg
|
||||
|
||||
fast: FORCE
|
||||
meson setup build_fast
|
||||
|
||||
14
meson.build
14
meson.build
@@ -339,18 +339,6 @@ cell_dep = declare_dependency(
|
||||
link_with: cell
|
||||
)
|
||||
|
||||
copy_tests = custom_target(
|
||||
'copy_tests',
|
||||
output: 'tests',
|
||||
command: [
|
||||
'cp', '-rf',
|
||||
join_paths(meson.project_source_root(), 'tests'),
|
||||
meson.project_build_root()
|
||||
],
|
||||
build_always_stale: true,
|
||||
build_by_default: true
|
||||
)
|
||||
|
||||
tests = [
|
||||
'spawn_actor',
|
||||
'empty',
|
||||
@@ -363,5 +351,5 @@ tests = [
|
||||
]
|
||||
|
||||
foreach file : tests
|
||||
test(file, cell, args:['tests/' + file + '.js'], depends:copy_tests)
|
||||
test(file, cell, args:['tests/' + file])
|
||||
endforeach
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
(function engine() {
|
||||
globalThis.cell = prosperon
|
||||
cell.hidden.console.print("HELLO\n")
|
||||
cell.DOC = cell.hidden.DOCSYM
|
||||
|
||||
var MOD_EXT = '.cm'
|
||||
var ACTOR_EXT = '.ce'
|
||||
|
||||
globalThis.pi = 3.1415926535897932
|
||||
|
||||
function caller_data(depth = 0)
|
||||
@@ -22,7 +24,7 @@ function caller_data(depth = 0)
|
||||
|
||||
return {file,line}
|
||||
}
|
||||
|
||||
cell.id ??= "newguy"
|
||||
function console_rec(line, file, msg) {
|
||||
return `[${cell.id.slice(0,5)}] [${file}:${line}]: ${msg}\n`
|
||||
|
||||
@@ -97,17 +99,10 @@ if (!io.exists('.cell')) {
|
||||
os.exit(1);
|
||||
}
|
||||
|
||||
io.mount("scripts")
|
||||
|
||||
var RESPATH = 'scripts/resources.js'
|
||||
var canonical = io.realdir(RESPATH) + 'resources.js'
|
||||
var content = io.slurp(RESPATH)
|
||||
var resources = js.eval(RESPATH, `(function setup_resources(io){${content}})`).call({}, io)
|
||||
io.mount(".cell/modules", "")
|
||||
|
||||
var use_cache = {}
|
||||
|
||||
use_cache['resources'] = resources
|
||||
|
||||
function print_api(obj) {
|
||||
for (var prop in obj) {
|
||||
if (!obj.hasOwnProperty(prop)) continue
|
||||
@@ -122,7 +117,7 @@ function print_api(obj) {
|
||||
|
||||
var res_cache = {}
|
||||
|
||||
var BASEPATH = 'scripts/base.js'
|
||||
var BASEPATH = 'scripts/base' + MOD_EXT
|
||||
var script = io.slurp(BASEPATH)
|
||||
var fnname = "base"
|
||||
script = `(function ${fnname}() { ${script}; })`
|
||||
@@ -132,19 +127,9 @@ var inProgress = {}
|
||||
var loadingStack = []
|
||||
|
||||
globalThis.use = function use(file, ...args) {
|
||||
// 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 cache first
|
||||
if (use_cache[file]) {
|
||||
return use_cache[file]
|
||||
}
|
||||
|
||||
// Check for circular dependencies
|
||||
@@ -159,11 +144,13 @@ globalThis.use = function use(file, ...args) {
|
||||
)
|
||||
}
|
||||
|
||||
// Try to find the script file
|
||||
var path = resources.find_script(request_name)
|
||||
var path = null
|
||||
if (io.exists(file + MOD_EXT) && !io.is_directory(file + MOD_EXT)) {
|
||||
path = file + MOD_EXT
|
||||
}
|
||||
|
||||
// Check if there's an embedded module
|
||||
var embed_mod = use_embed(request_name)
|
||||
var embed_mod = use_embed(file)
|
||||
|
||||
// If no script and no embedded module, error
|
||||
if (!path && !embed_mod) {
|
||||
@@ -173,28 +160,26 @@ globalThis.use = function use(file, ...args) {
|
||||
// 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}"`)
|
||||
throw new Error(
|
||||
`Circular dependency detected while loading "${file}".\n` +
|
||||
`Module chain: ${loadingStack.join(" -> ")}\n` +
|
||||
`Cycle specifically: ${cyclePath.join(" -> ")}`
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
inProgress[path] = true
|
||||
loadingStack.push(file)
|
||||
|
||||
// Determine the compiled file path in .cell directory
|
||||
var compiledPath = path + '.o'
|
||||
var compiledPath = ".cell/build/" + io.realdir(path) + "/" + path + '.o'
|
||||
|
||||
// Ensure .cell directory exists
|
||||
if (!io.exists('.cell')) {
|
||||
io.mkdir('.cell')
|
||||
}
|
||||
io.mkdir(compiledPath.dir())
|
||||
|
||||
// Check if compiled version exists and is newer than source
|
||||
var useCompiled = false
|
||||
@@ -210,7 +195,6 @@ globalThis.use = function use(file, ...args) {
|
||||
var mod_name = path.name()
|
||||
|
||||
if (useCompiled) {
|
||||
// Load compiled bytecode
|
||||
var compiledBlob = io.slurpbytes(compiledPath)
|
||||
fn = js.compile_unblob(compiledBlob)
|
||||
fn = js.eval_compile(fn)
|
||||
@@ -245,13 +229,8 @@ globalThis.use = function use(file, ...args) {
|
||||
loadingStack.pop()
|
||||
delete inProgress[path]
|
||||
|
||||
// Cache under all possible keys
|
||||
use_cache[path] = ret
|
||||
// Cache the result
|
||||
use_cache[file] = ret
|
||||
use_cache[request_name] = ret
|
||||
if (file !== request_name && !file.endsWith('.js')) {
|
||||
use_cache[request_name + '.js'] = ret
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -286,7 +265,7 @@ stone.p = function(object)
|
||||
return Object.isFrozen(object)
|
||||
}
|
||||
|
||||
var DOCPATH = 'scripts/doc.js'
|
||||
var DOCPATH = 'scripts/doc' + MOD_EXT
|
||||
var script = io.slurp(DOCPATH)
|
||||
var fnname = "doc"
|
||||
script = `(function ${fnname}() { ${script}; })`
|
||||
@@ -648,11 +627,6 @@ if (overling) actor_prep(overling, {type:'greet', actor: $_})
|
||||
if (!cell.args.program)
|
||||
os.exit(1)
|
||||
|
||||
if (typeof cell.args.program !== 'string')
|
||||
cell.args.program = 'main.js';
|
||||
|
||||
actor_mod.setname(cell.args.program)
|
||||
|
||||
function destroyself() {
|
||||
log.spam(`Got the message to destroy self.`)
|
||||
dying = true
|
||||
@@ -731,9 +705,31 @@ function enet_check()
|
||||
//enet_check();
|
||||
|
||||
// Finally, run the program
|
||||
var prog = resources.find_script(cell.args.program)
|
||||
prog = io.slurp(prog)
|
||||
var prog_script = `(function ${cell.args.program.name()}($_) { ${prog} })`
|
||||
actor_mod.setname(cell.args.program)
|
||||
|
||||
var prog = null
|
||||
var progPath = cell.args.program
|
||||
|
||||
if (io.exists(progPath + ACTOR_EXT) && !io.is_directory(progPath + ACTOR_EXT)) {
|
||||
prog = progPath + ACTOR_EXT
|
||||
} else if (io.exists(progPath) && io.is_directory(progPath)) {
|
||||
var mainPath = progPath + '/main' + ACTOR_EXT
|
||||
if (io.exists(mainPath) && !io.is_directory(mainPath)) {
|
||||
prog = mainPath
|
||||
}
|
||||
}
|
||||
|
||||
if (!prog)
|
||||
throw new Error(cell.args.program + " not found.");
|
||||
|
||||
// Mount the directory containing the program
|
||||
var progDir = prog.substring(0, prog.lastIndexOf('/'))
|
||||
if (progDir && progDir !== '.') {
|
||||
io.mount(progDir, "")
|
||||
}
|
||||
|
||||
var progContent = io.slurp(prog)
|
||||
var prog_script = `(function ${cell.args.program.name()}($_) { ${progContent} })`
|
||||
var val = js.eval(cell.args.program, prog_script)($_)
|
||||
if (val)
|
||||
throw new Error('Program must not return anything');
|
||||
169
scripts/toml.cm
Normal file
169
scripts/toml.cm
Normal file
@@ -0,0 +1,169 @@
|
||||
// Simple TOML parser for shop.toml
|
||||
// Supports basic TOML features needed for the module system
|
||||
|
||||
function parse_toml(text) {
|
||||
var lines = text.split('\n')
|
||||
var result = {}
|
||||
var current_section = result
|
||||
var current_section_name = ''
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim()
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (!line || line.startsWith('#')) continue
|
||||
|
||||
// Section header
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
var section_path = line.slice(1, -1).split('.')
|
||||
current_section = result
|
||||
current_section_name = section_path.join('.')
|
||||
|
||||
for (var j = 0; j < section_path.length; j++) {
|
||||
var key = section_path[j]
|
||||
if (!current_section[key]) {
|
||||
current_section[key] = {}
|
||||
}
|
||||
current_section = current_section[key]
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Key-value pair
|
||||
var eq_index = line.indexOf('=')
|
||||
if (eq_index > 0) {
|
||||
var key = line.substring(0, eq_index).trim()
|
||||
var value = line.substring(eq_index + 1).trim()
|
||||
|
||||
// Parse value
|
||||
if (value.startsWith('"') && value.endsWith('"')) {
|
||||
// String - unescape quotes
|
||||
current_section[key] = value.slice(1, -1).replace(/\\"/g, '"')
|
||||
} else if (value.startsWith('[') && value.endsWith(']')) {
|
||||
// Array
|
||||
current_section[key] = parse_array(value)
|
||||
} else if (value === 'true' || value === 'false') {
|
||||
// Boolean
|
||||
current_section[key] = value === 'true'
|
||||
} else if (!isNaN(Number(value))) {
|
||||
// Number
|
||||
current_section[key] = Number(value)
|
||||
} else {
|
||||
// Unquoted string
|
||||
current_section[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function parse_array(str) {
|
||||
// Remove brackets
|
||||
str = str.slice(1, -1).trim()
|
||||
if (!str) return []
|
||||
|
||||
var items = []
|
||||
var current = ''
|
||||
var in_quotes = false
|
||||
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var char = str[i]
|
||||
|
||||
if (char === '"' && (i === 0 || str[i-1] !== '\\')) {
|
||||
in_quotes = !in_quotes
|
||||
current += char
|
||||
} else if (char === ',' && !in_quotes) {
|
||||
items.push(parse_value(current.trim()))
|
||||
current = ''
|
||||
} else {
|
||||
current += char
|
||||
}
|
||||
}
|
||||
|
||||
if (current.trim()) {
|
||||
items.push(parse_value(current.trim()))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
function parse_value(str) {
|
||||
if (str.startsWith('"') && str.endsWith('"')) {
|
||||
return str.slice(1, -1).replace(/\\"/g, '"')
|
||||
} else if (str === 'true' || str === 'false') {
|
||||
return str === 'true'
|
||||
} else if (!isNaN(Number(str))) {
|
||||
return Number(str)
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
function encode_toml(obj) {
|
||||
var result = []
|
||||
|
||||
function encode_value(value) {
|
||||
if (typeof value === 'string') {
|
||||
return '"' + value.replace(/"/g, '\\"') + '"'
|
||||
} else if (typeof value === 'boolean') {
|
||||
return value ? 'true' : 'false'
|
||||
} else if (typeof value === 'number') {
|
||||
return String(value)
|
||||
} else if (Array.isArray(value)) {
|
||||
var items = []
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
items.push(encode_value(value[i]))
|
||||
}
|
||||
return '[' + items.join(', ') + ']'
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// First pass: encode top-level simple values
|
||||
var keys = Object.keys(obj)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var value = obj[key]
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
||||
result.push(key + ' = ' + encode_value(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: encode nested objects
|
||||
function encode_section(obj, path) {
|
||||
var keys = Object.keys(obj)
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var value = obj[key]
|
||||
|
||||
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
||||
// Nested object - create section
|
||||
var section_path = path ? path + '.' + key : key
|
||||
result.push('[' + section_path + ']')
|
||||
|
||||
// First encode direct properties of this section
|
||||
var section_keys = Object.keys(value)
|
||||
for (var j = 0; j < section_keys.length; j++) {
|
||||
var sk = section_keys[j]
|
||||
var sv = value[sk]
|
||||
if (sv === null || typeof sv !== 'object' || Array.isArray(sv)) {
|
||||
result.push(sk + ' = ' + encode_value(sv))
|
||||
}
|
||||
}
|
||||
|
||||
// Then encode nested sections
|
||||
encode_section(value, section_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encode_section(obj, '')
|
||||
return result.join('\n')
|
||||
}
|
||||
|
||||
return {
|
||||
decode: parse_toml,
|
||||
encode: encode_toml
|
||||
}
|
||||
3
scripts/vendor.ce
Normal file
3
scripts/vendor.ce
Normal file
@@ -0,0 +1,3 @@
|
||||
log.console(`not implemented.sorry.`)
|
||||
|
||||
$_.stop()
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
int tracy_profiling_enabled = 0;
|
||||
|
||||
#define ENGINE "engine.js"
|
||||
#define ENGINE "engine.cm"
|
||||
|
||||
static cell_rt **ready_queue = NULL;
|
||||
static cell_rt **main_ready_queue = NULL;
|
||||
@@ -202,7 +202,10 @@ int prosperon_mount_core(void)
|
||||
eocd_pos = i;
|
||||
break;
|
||||
}
|
||||
if (eocd_pos < 0) return fprintf(stderr, "EOCD not found\n"), free(zip_buffer_global), 0;
|
||||
if (eocd_pos < 0) {
|
||||
free(zip_buffer_global);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t comment_length = zip_buffer_global[eocd_pos + 20] | (zip_buffer_global[eocd_pos + 21] << 8);
|
||||
int eocd_size = 22 + comment_length;
|
||||
|
||||
@@ -154,6 +154,7 @@ JSC_CCALL(os_unneeded,
|
||||
JSC_CCALL(os_destroy,
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
rt->need_stop = 1;
|
||||
set_actor_state(rt);
|
||||
)
|
||||
|
||||
JSC_SCALL(actor_setname,
|
||||
|
||||
@@ -10,7 +10,7 @@ function load_comment_from_api_requestor(id) {
|
||||
|
||||
$_.receiver(tree => {
|
||||
var child_reqs = tree.children.map(child => cb => {
|
||||
$_.start(e => send(e.actor, child, cb), "tests/comments.js")
|
||||
$_.start(e => send(e.actor, child, cb), "tests/comments")
|
||||
})
|
||||
|
||||
var job = parseq.par_all({
|
||||
@@ -45,5 +45,5 @@ $_.start(e => {
|
||||
// Start status checking after a small delay
|
||||
$_.delay(checkin, 0.01)
|
||||
}
|
||||
}, "examples/http_download_actor.js")
|
||||
}, "examples/http_download_actor")
|
||||
|
||||
1
tests/mod1.cm
Normal file
1
tests/mod1.cm
Normal file
@@ -0,0 +1 @@
|
||||
return "ok!"
|
||||
@@ -10,4 +10,4 @@ $_.start(e => {
|
||||
|
||||
$_.stop(e.actor)
|
||||
}
|
||||
}, "tests/underling.js");
|
||||
}, "tests/underling");
|
||||
@@ -31,5 +31,5 @@ $_.start(e => {
|
||||
|
||||
log.console(`took ${os.now()-st} secs`)
|
||||
});
|
||||
}, "tests/comments.js")
|
||||
}, "tests/comments")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Creates a portal and a separate actor to contact
|
||||
|
||||
//os.createprocess(["./prosperon", "tests/portal.js"])
|
||||
//os.createprocess(["./prosperon", "tests/portal"])
|
||||
|
||||
//$_.delay(_ => {
|
||||
// os.createprocess(["./prosperon", "tests/contact.js"])
|
||||
// os.createprocess(["./prosperon", "tests/contact"])
|
||||
// $_.stop()
|
||||
// $_.delay($_.stop, 0.1)
|
||||
//}, 0.2)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user