Merge branch 'improve_compile_error'

This commit is contained in:
2026-02-20 14:39:51 -06:00
65 changed files with 2255 additions and 1376 deletions

144
analyze.cm Normal file
View File

@@ -0,0 +1,144 @@
// analyze.cm — Static analysis over index data.
//
// All functions take an index object (from index.cm) and return structured results.
// Does not depend on streamline — operates purely on source-semantic data.
var analyze = {}
// Find all references to a name, with optional scope filter.
// scope: "top" (enclosing == null), "fn" (enclosing != null), null (all)
analyze.find_refs = function(idx, name, scope) {
var hits = []
var i = 0
var ref = null
while (i < length(idx.references)) {
ref = idx.references[i]
if (ref.name == name) {
if (scope == null) {
hits[] = ref
} else if (scope == "top" && ref.enclosing == null) {
hits[] = ref
} else if (scope == "fn" && ref.enclosing != null) {
hits[] = ref
}
}
i = i + 1
}
return hits
}
// Find all <name>.<property> usage patterns (channel analysis).
// Only counts unshadowed uses (name not declared as local var in scope).
analyze.channels = function(idx, name) {
var channels = {}
var summary = {}
var i = 0
var cs = null
var callee = null
var prop = null
var prefix_dot = name + "."
while (i < length(idx.call_sites)) {
cs = idx.call_sites[i]
callee = cs.callee
if (callee != null && starts_with(callee, prefix_dot)) {
prop = text(callee, length(prefix_dot), length(callee))
if (channels[prop] == null) {
channels[prop] = []
}
channels[prop][] = {span: cs.span}
if (summary[prop] == null) {
summary[prop] = 0
}
summary[prop] = summary[prop] + 1
}
i = i + 1
}
return {channels: channels, summary: summary}
}
// Find declarations by name, with optional kind filter.
// kind: "var", "def", "fn", "param", or null (any)
analyze.find_decls = function(idx, name, kind) {
var hits = []
var i = 0
var sym = null
while (i < length(idx.symbols)) {
sym = idx.symbols[i]
if (sym.name == name) {
if (kind == null || sym.kind == kind) {
hits[] = sym
}
}
i = i + 1
}
return hits
}
// Find intrinsic usage by name.
analyze.find_intrinsic = function(idx, name) {
var hits = []
var i = 0
var ref = null
if (idx.intrinsic_refs == null) return hits
while (i < length(idx.intrinsic_refs)) {
ref = idx.intrinsic_refs[i]
if (ref.name == name) {
hits[] = ref
}
i = i + 1
}
return hits
}
// Call sites with >4 args — always a compile error (max arity is 4).
analyze.excess_args = function(idx) {
var hits = []
var i = 0
var cs = null
while (i < length(idx.call_sites)) {
cs = idx.call_sites[i]
if (cs.args_count > 4) {
hits[] = {span: cs.span, callee: cs.callee, args_count: cs.args_count}
}
i = i + 1
}
return hits
}
// Extract module export shape from index data (for cross-module analysis).
analyze.module_summary = function(idx) {
var exports = {}
var i = 0
var j = 0
var exp = null
var sym = null
var found = false
if (idx.exports == null) return {exports: exports}
while (i < length(idx.exports)) {
exp = idx.exports[i]
found = false
if (exp.symbol_id != null) {
j = 0
while (j < length(idx.symbols)) {
sym = idx.symbols[j]
if (sym.symbol_id == exp.symbol_id) {
if (sym.kind == "fn" && sym.params != null) {
exports[exp.name] = {type: "function", arity: length(sym.params)}
} else {
exports[exp.name] = {type: sym.kind}
}
found = true
break
}
j = j + 1
}
}
if (!found) {
exports[exp.name] = {type: "unknown"}
}
i = i + 1
}
return {exports: exports}
}
return analyze

View File

@@ -6,7 +6,7 @@ var fd = use('fd')
var time = use('time')
var json = use('json')
var blob = use('blob')
var os = use('os')
var os = use('internal/os')
var testlib = use('internal/testlib')
var math = use('math/radians')

View File

@@ -6,7 +6,7 @@
// Compiles (if needed) and benchmarks a module via both VM and native dylib.
// Reports median/mean timing per benchmark + speedup ratio.
var os = use('os')
var os = use('internal/os')
var fd = use('fd')
if (length(args) < 1) {

View File

@@ -1,8 +1,8 @@
// encoders.cm — nota/wota/json encode+decode benchmark
// Isolates per-type bottlenecks across all three serializers.
var nota = use('nota')
var wota = use('wota')
var nota = use('internal/nota')
var wota = use('internal/wota')
var json = use('json')
// --- Test data shapes ---

View File

@@ -1,5 +1,5 @@
var nota = use('nota')
var os = use('os')
var nota = use('internal/nota')
var os = use('internal/os')
var io = use('fd')
var json = use('json')

View File

@@ -1,5 +1,5 @@
var wota = use('wota');
var os = use('os');
var wota = use('internal/wota');
var os = use('internal/os');
var i = 0

View File

@@ -1,8 +1,8 @@
var wota = use('wota');
var nota = use('nota');
var wota = use('internal/wota');
var nota = use('internal/nota');
var json = use('json');
var jswota = use('jswota')
var os = use('os');
var os = use('internal/os');
if (length(arg) != 2) {
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');

View File

@@ -1486,7 +1486,7 @@
["setarg", 11, 1, 9, 10, 16],
["invoke", 11, 9, 10, 16],
["move", 11, 9, 10, 16],
["access", 9, "crypto", 11, 24],
["access", 9, "internal_crypto", 11, 24],
["frame", 12, 2, 1, 11, 14],
["null", 13, 11, 14],
["setarg", 12, 0, 13, 11, 14],

View File

@@ -7,9 +7,9 @@
// Build.build_static(packages, target, output) - Build static binary
var fd = use('fd')
var crypto = use('crypto')
var crypto = use('internal/crypto')
var blob = use('blob')
var os = use('os')
var os = use('internal/os')
var toolchains = use('toolchains')
var shop = use('internal/shop')
var pkg_tools = use('package')
@@ -718,7 +718,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
// Compile QBE runtime stubs if needed
var rc = null
if (!fd.is_file(rt_o_path)) {
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c'
qbe_rt_path = shop.get_package_dir('core') + '/src/qbe_rt.c'
rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('QBE runtime stubs compilation failed'); disrupt
@@ -790,7 +790,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
// Compile QBE runtime stubs if needed
var rc = null
if (!fd.is_file(rt_o_path)) {
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c'
qbe_rt_path = shop.get_package_dir('core') + '/src/qbe_rt.c'
rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('QBE runtime stubs compilation failed'); disrupt

View File

@@ -2,8 +2,8 @@ var cellfs = {}
var fd = use('fd')
var miniz = use('miniz')
var qop = use('qop')
var wildstar = use('wildstar')
var qop = use('internal/qop')
var wildstar = use('internal/wildstar')
var mounts = []

View File

@@ -5,7 +5,7 @@
var build = use('build')
var fd_mod = use('fd')
var os = use('os')
var os = use('internal/os')
var json = use('json')
var time = use('time')

View File

@@ -11,9 +11,9 @@ var time = use('time')
var _args = args == null ? [] : args
var analyze = use('os').analyze
var run_ast_fn = use('os').run_ast_fn
var run_ast_noopt_fn = use('os').run_ast_noopt_fn
var analyze = use('internal/os').analyze
var run_ast_fn = use('internal/os').run_ast_fn
var run_ast_noopt_fn = use('internal/os').run_ast_noopt_fn
if (!run_ast_noopt_fn) {
log.console("error: run_ast_noopt_fn not available (rebuild bootstrap)")

View File

@@ -52,10 +52,13 @@ Where:
Examples:
- `mypackage/math.c` -> `js_mypackage_math_use`
- `gitea.pockle.world/john/lib/render.c` -> `js_gitea_pockle_world_john_lib_render_use`
- `mypackage/internal/helpers.c` -> `js_mypackage_internal_helpers_use`
- `mypackage/game.ce` (AOT actor) -> `js_mypackage_game_program`
Actor files (`.ce`) use the `_program` suffix instead of `_use`.
Internal modules (in `internal/` subdirectories) follow the same convention — the `internal` directory name becomes part of the symbol. For example, `internal/os.c` in the core package has the symbol `js_core_internal_os_use`.
**Note:** Having both a `.cm` and `.c` file with the same stem at the same scope is a build error.
## Required Headers

View File

@@ -72,6 +72,7 @@ cell streamline --stats <file.ce|file.cm> # summary stats per function
cell streamline --ir <file.ce|file.cm> # human-readable IR
cell streamline --check <file.ce|file.cm> # warnings only
cell streamline --types <file.ce|file.cm> # IR with type annotations
cell streamline --diagnose <file.ce|file.cm> # compile-time diagnostics
```
| Flag | Description |
@@ -81,6 +82,7 @@ cell streamline --types <file.ce|file.cm> # IR with type annotations
| `--ir` | Human-readable canonical IR (same format as `ir_report.ce`) |
| `--check` | Warnings only (e.g. `nr_slots > 200` approaching 255 limit) |
| `--types` | Optimized IR with inferred type annotations per slot |
| `--diagnose` | Run compile-time diagnostics (type errors and warnings) |
Flags can be combined.

View File

@@ -5,7 +5,7 @@ weight: 85
type: "docs"
---
Nota is a binary message format developed for use in the Procession Protocol. It provides a compact, JSON-like encoding that supports blobs, text, arrays, records, numbers, and symbols.
Nota is a binary message format developed for use in the Procession Protocol. It provides a compact, JSON-like encoding that supports blobs, text, arrays, records, numbers, and symbols. Nota is an internal module: `use('internal/nota')`.
Nota stands for Network Object Transfer Arrangement.

View File

@@ -230,4 +230,4 @@ If `shop.toml` is missing or has no `[policy]` section, all methods are enabled
| `internal/os.c` | OS intrinsics: dylib ops, internal symbol lookup, embedded modules |
| `package.cm` | Package directory detection, alias resolution, file listing |
| `link.cm` | Development link management (link.toml read/write) |
| `boot/*.cm.mcode` | Pre-compiled pipeline seeds (tokenize, parse, fold, mcode, bootstrap) |
| `boot/*.cm.mcode` | Pre-compiled pipeline seeds (tokenize, parse, fold, mcode, streamline, bootstrap) |

View File

@@ -69,6 +69,7 @@ Optimizes the Mcode IR through a series of independent passes. Operates per-func
6. **Move elimination**: Removes self-moves (`move a, a`).
7. **Unreachable elimination**: Nops dead code after `return` until the next label.
8. **Dead jump elimination**: Removes jumps to the immediately following label.
9. **Compile-time diagnostics** (optional): When `_warn` is set on the mcode input, emits errors for provably wrong operations (storing named property on array, invoking null, etc.) and warnings for suspicious patterns (named property access on array/text). The engine aborts compilation if any error-severity diagnostics are emitted.
See [Streamline Optimizer](streamline.md) for detailed pass descriptions.
@@ -134,6 +135,7 @@ Seeds are used during cold start (empty cache) to compile the pipeline modules f
| `mcode.ce --pretty` | Print raw Mcode IR before streamlining |
| `streamline.ce --types` | Print streamlined IR with type annotations |
| `streamline.ce --stats` | Print IR after streamlining with before/after stats |
| `streamline.ce --diagnose` | Print compile-time diagnostics (type errors and warnings) |
## Test Files
@@ -146,3 +148,4 @@ Seeds are used during cold start (empty cache) to compile the pipeline modules f
| `qbe_test.ce` | End-to-end QBE IL generation |
| `test_intrinsics.cm` | Inlined intrinsic opcodes (is_array, length, push, etc.) |
| `test_backward.cm` | Backward type propagation for parameters |
| `tests/compile.cm` | Compile-time diagnostics (type errors and warnings) |

View File

@@ -178,6 +178,36 @@ Removes `jump L` instructions where `L` is the immediately following label (skip
**Nop prefix:** `_nop_dj_`
### 9. diagnose_function (compile-time diagnostics)
Optional pass that runs when `_warn` is set on the mcode input. Performs a forward type-tracking scan and emits diagnostics for provably wrong operations. Diagnostics are collected in `ir._diagnostics` as `{severity, file, line, col, message}` records.
This pass does not modify instructions — it only emits diagnostics.
**Errors** (compilation is aborted):
| Pattern | Message |
|---------|---------|
| `store_field` on T_ARRAY | storing named property on array |
| `store_index` on T_RECORD | storing numeric index on record |
| `store_field` / `store_index` on T_TEXT | storing property/index on text |
| `push` on T_TEXT / T_RECORD | push on text/record |
| `invoke` on T_NULL / T_INT / T_FLOAT / T_NUM / T_TEXT / T_BOOL / T_ARRAY | invoking null/number/text/bool/array |
| arity mismatch (module imports only) | function expects N arguments, got M |
**Warnings** (compilation continues):
| Pattern | Message |
|---------|---------|
| `load_field` on T_ARRAY | named property access on array |
| `load_field` on T_TEXT | named property access on text |
| `load_dynamic` with T_TEXT key on T_RECORD | text key on record |
| `load_dynamic` with T_RECORD / T_ARRAY / T_BOOL / T_NULL key on T_RECORD | record/array/bool/null key on record |
The engine (`internal/engine.cm`) prints all diagnostics and aborts compilation if any have severity `"error"`. Warnings are printed but do not block compilation.
**Nop prefix:** none (diagnostics only, does not modify instructions)
## Pass Composition
All passes run in sequence in `optimize_function`:
@@ -191,6 +221,7 @@ simplify_booleans
eliminate_moves
eliminate_unreachable
eliminate_dead_jumps
diagnose_function → optional, when _warn is set
```
Each pass is independent and can be commented out for testing or benchmarking.

View File

@@ -192,6 +192,36 @@ Failures saved to tests/fuzz_failures/
Saved failure files are valid `.cm` modules that can be run directly or added to the test suite.
## Compile-Time Diagnostics Tests
The `tests/compile.cm` test suite verifies that the type checker catches provably wrong operations at compile time. It works by compiling source snippets through the pipeline with `_warn` enabled and checking that the expected diagnostics are emitted.
```javascript
var shop = use('internal/shop')
var streamline = use('streamline')
function get_diagnostics(src) {
fd.slurpwrite(tmpfile, stone(blob(src)))
var compiled = shop.mcode_file(tmpfile)
compiled._warn = true
var optimized = streamline(compiled)
if (optimized._diagnostics == null) return []
return optimized._diagnostics
}
```
The suite covers:
- **Store errors**: storing named property on array, numeric index on record, property/index on text, push on text/record
- **Invoke errors**: invoking null, number, text
- **Warnings**: named property access on array/text, record key on record
- **Clean code**: valid operations produce no diagnostics
Run the compile diagnostics tests with:
```bash
pit test compile
```
## Test File Organization
Tests live in the `tests/` directory of a package:

View File

@@ -5,7 +5,7 @@ weight: 86
type: "docs"
---
Wota is a binary message format for local inter-process communication. It is similar to Nota but works at word granularity (64-bit words) rather than byte granularity. Wota arrangements are less compact than Nota but faster to arrange and consume.
Wota is a binary message format for local inter-process communication. It is similar to Nota but works at word granularity (64-bit words) rather than byte granularity. Wota arrangements are less compact than Nota but faster to arrange and consume. Wota is an internal module: `use('internal/wota')`.
Wota stands for Word Object Transfer Arrangement.

2
fd.cm
View File

@@ -1,5 +1,5 @@
var fd = use('internal/fd')
var wildstar = use('wildstar')
var wildstar = use('internal/wildstar')
function last_pos(str, sep) {
var last = null

View File

@@ -13,7 +13,7 @@ var fd = use('fd')
var time = use('time')
var json = use('json')
var os_ref = use('os')
var os_ref = use('internal/os')
var analyze = os_ref.analyze
var run_ast_fn = os_ref.run_ast_fn
var run_ast_noopt_fn = os_ref.run_ast_noopt_fn

View File

@@ -201,7 +201,9 @@ var index_ast = function(ast, tokens, filename) {
if (node.expression.left != null && node.expression.left.kind == "name") {
callee_name = node.expression.left.name
}
if (node.expression.right != null && node.expression.right.name != null) {
if (is_text(node.expression.right)) {
callee_name = (callee_name != null ? callee_name + "." : "") + node.expression.right
} else if (node.expression.right != null && node.expression.right.name != null) {
callee_name = (callee_name != null ? callee_name + "." : "") + node.expression.right.name
}
}

View File

@@ -8,7 +8,7 @@ function use_embed(name) {
var fd = use_embed('internal_fd')
var json_mod = use_embed('json')
var crypto = use_embed('crypto')
var crypto = use_embed('internal_crypto')
function content_hash(content) {
var data = content

View File

@@ -238,7 +238,7 @@ static const JSCFunctionListEntry js_crypto_funcs[] = {
JS_CFUNC_DEF("unlock", 3, js_crypto_unlock),
};
JSValue js_core_crypto_use(JSContext *js)
JSValue js_core_internal_crypto_use(JSContext *js)
{
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));

View File

@@ -25,7 +25,7 @@ function use_embed(name) {
var fd = use_embed('internal_fd')
var js = use_embed('js')
var crypto = use_embed('crypto')
var crypto = use_embed('internal_crypto')
// core_path and shop_path come from env (C runtime passes them through)
// shop_path may be null if --core was used without --shop
@@ -182,9 +182,110 @@ function analyze(src, filename) {
// Lazy-loaded verify_ir module (loaded on first use)
var _verify_ir_mod = null
// Module summary extraction for cross-program analysis.
// Scans mcode IR for use() call patterns and attaches summaries.
// _summary_resolver is set after shop loads (null during bootstrap).
var _summary_resolver = null
function extract_module_summaries(compiled) {
if (_summary_resolver == null) return null
var instrs = null
var summaries = []
var unresolved = []
var i = 0
var j = 0
var n = 0
var instr = null
var prev = null
var op = null
var use_slots = {}
var frame_map = {}
var arg_map = {}
var val_slot = 0
var f_slot = 0
var path = null
var result_slot = 0
var summary = null
var inv_n = 0
if (compiled.main == null) return null
instrs = compiled.main.instructions
if (instrs == null) return null
n = length(instrs)
// Pass 1: find access(slot, {make:"intrinsic", name:"use"})
i = 0
while (i < n) {
instr = instrs[i]
if (is_array(instr) && instr[0] == "access") {
if (is_object(instr[2]) && instr[2].make == "intrinsic" && instr[2].name == "use") {
use_slots[text(instr[1])] = true
}
}
i = i + 1
}
// Pass 2: find frame(frame_slot, use_slot), setarg with string, invoke
i = 0
while (i < n) {
instr = instrs[i]
if (is_array(instr)) {
op = instr[0]
if (op == "frame" || op == "goframe") {
if (use_slots[text(instr[2])] == true) {
frame_map[text(instr[1])] = true
}
} else if (op == "setarg") {
if (frame_map[text(instr[1])] == true) {
val_slot = instr[3]
j = i - 1
while (j >= 0) {
prev = instrs[j]
if (is_array(prev) && prev[0] == "access" && prev[1] == val_slot && is_text(prev[2])) {
arg_map[text(instr[1])] = prev[2]
break
}
j = j - 1
}
}
} else if (op == "invoke" || op == "tail_invoke") {
f_slot = instr[1]
path = arg_map[text(f_slot)]
if (path != null) {
result_slot = instr[2]
summary = _summary_resolver(path)
if (summary != null) {
if (summary._native != true) {
summaries[] = {slot: result_slot, summary: summary}
}
} else {
inv_n = length(instr)
unresolved[] = {path: path, line: instr[inv_n - 2], col: instr[inv_n - 1]}
}
}
}
}
i = i + 1
}
if (length(summaries) > 0 || length(unresolved) > 0) {
return {summaries: summaries, unresolved: unresolved}
}
return null
}
// Run AST through mcode pipeline -> register VM
function run_ast_fn(name, ast, env) {
var compiled = mcode_mod(ast)
var ms = null
var _ui = 0
var _ur = null
var optimized = null
var _di = 0
var _diag = null
var _has_errors = false
var mcode_json = null
var mach_blob = null
if (os._verify_ir) {
if (_verify_ir_mod == null) {
_verify_ir_mod = load_pipeline_module('verify_ir', pipeline_env)
@@ -192,13 +293,45 @@ function run_ast_fn(name, ast, env) {
compiled._verify = true
compiled._verify_mod = _verify_ir_mod
}
var optimized = streamline_mod(compiled)
if (!_no_warn) {
compiled._warn = true
ms = extract_module_summaries(compiled)
if (ms != null) {
if (length(ms.summaries) > 0) {
compiled._module_summaries = ms.summaries
}
if (length(ms.unresolved) > 0) {
compiled._unresolved_imports = ms.unresolved
}
}
}
if (compiled._unresolved_imports != null) {
_ui = 0
while (_ui < length(compiled._unresolved_imports)) {
_ur = compiled._unresolved_imports[_ui]
print(`${name}:${text(_ur.line)}:${text(_ur.col)}: error: cannot resolve module '${_ur.path}'\n`)
_ui = _ui + 1
}
disrupt
}
optimized = streamline_mod(compiled)
if (optimized._verify) {
delete optimized._verify
delete optimized._verify_mod
}
var mcode_json = json.encode(optimized)
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
if (optimized._diagnostics != null && length(optimized._diagnostics) > 0) {
_di = 0
_has_errors = false
while (_di < length(optimized._diagnostics)) {
_diag = optimized._diagnostics[_di]
print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`)
if (_diag.severity == "error") _has_errors = true
_di = _di + 1
}
if (_has_errors) disrupt
}
mcode_json = json.encode(optimized)
mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env)
}
@@ -217,6 +350,52 @@ function compile_to_blob(name, ast) {
return mach_compile_mcode_bin(name, json.encode(optimized))
}
// Compile user program AST to blob with diagnostics
function compile_user_blob(name, ast) {
var compiled = mcode_mod(ast)
var ms = null
var _ui = 0
var _ur = null
var optimized = null
var _di = 0
var _diag = null
var _has_errors = false
if (!_no_warn) {
compiled._warn = true
ms = extract_module_summaries(compiled)
if (ms != null) {
if (length(ms.summaries) > 0) {
compiled._module_summaries = ms.summaries
}
if (length(ms.unresolved) > 0) {
compiled._unresolved_imports = ms.unresolved
}
}
}
if (compiled._unresolved_imports != null) {
_ui = 0
while (_ui < length(compiled._unresolved_imports)) {
_ur = compiled._unresolved_imports[_ui]
print(`${name}:${text(_ur.line)}:${text(_ur.col)}: error: cannot resolve module '${_ur.path}'\n`)
_ui = _ui + 1
}
disrupt
}
optimized = streamline_mod(compiled)
if (optimized._diagnostics != null && length(optimized._diagnostics) > 0) {
_di = 0
_has_errors = false
while (_di < length(optimized._diagnostics)) {
_diag = optimized._diagnostics[_di]
print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`)
if (_diag.severity == "error") _has_errors = true
_di = _di + 1
}
if (_has_errors) disrupt
}
return mach_compile_mcode_bin(name, json.encode(optimized))
}
// If loaded directly by C runtime (not via bootstrap), convert args -> init
var _program = null
var _user_args = []
@@ -227,6 +406,9 @@ var _init = init
if (_init != null && _init.native_mode)
native_mode = true
// Inherit warn mode from init (set by C for --no-warn)
var _no_warn = (_init != null && _init.no_warn) ? true : false
// CLI path: convert args to init record
if (args != null && (_init == null || !_init.program)) {
_program = args[0]
@@ -242,7 +424,7 @@ if (args != null && (_init == null || !_init.program)) {
}
}
use_cache['core/os'] = os
use_cache['core/internal/os'] = os
// Extra env properties added as engine initializes (log, runtime fns, etc.)
var core_extras = {}
@@ -314,8 +496,8 @@ function actor() {
}
var actor_mod = use_core('actor')
var wota = use_core('wota')
var nota = use_core('nota')
var wota = use_core('internal/wota')
var nota = use_core('internal/nota')
var ENETSERVICE = 0.1
@@ -424,6 +606,22 @@ core_extras.native_mode = native_mode
// NOW load shop -- it receives all of the above via env
var shop = use_core('internal/shop')
use_core('build')
// Wire up module summary resolver now that shop is available
_summary_resolver = function(path) {
var info = shop.resolve_import_info(path, null)
if (info == null) return null
if (info.type == 'native') return {_native: true}
var resolved = info.resolved_path
if (resolved == null) return null
var summary_fn = function() {
return shop.summary_file(resolved)
} disruption {
return null
}
return summary_fn()
}
var time = use_core('time')
var toml = use_core('toml')
@@ -1306,7 +1504,7 @@ $_.clock(_ => {
} else {
script = text(source_blob)
ast = analyze(script, prog_path)
mach_blob = compile_to_blob(prog, ast)
mach_blob = compile_user_blob(prog, ast)
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)

View File

@@ -72,7 +72,7 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
MIST_FUNC_DEF(kim, decode, 1),
};
JSValue js_core_kim_use(JSContext *js)
JSValue js_core_internal_kim_use(JSContext *js)
{
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));

431
internal/nota.c Normal file
View File

@@ -0,0 +1,431 @@
#define NOTA_IMPLEMENTATION
#include "quickjs-internal.h"
#include "cell.h"
static int nota_get_arr_len (JSContext *ctx, JSValue arr) {
int64_t len;
JS_GetLength (ctx, arr, &len);
return (int)len;
}
typedef struct NotaVisitedNode {
JSGCRef ref;
struct NotaVisitedNode *next;
} NotaVisitedNode;
typedef struct NotaEncodeContext {
JSContext *ctx;
NotaVisitedNode *visited_list;
NotaBuffer nb;
int cycle;
JSGCRef *replacer_ref; /* pointer to GC-rooted ref */
} NotaEncodeContext;
static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
NotaVisitedNode *node = (NotaVisitedNode *)sys_malloc (sizeof (NotaVisitedNode));
JS_PushGCRef (enc->ctx, &node->ref);
node->ref.val = JS_DupValue (enc->ctx, val);
node->next = enc->visited_list;
enc->visited_list = node;
}
static void nota_stack_pop (NotaEncodeContext *enc) {
NotaVisitedNode *node = enc->visited_list;
enc->visited_list = node->next;
JS_FreeValue (enc->ctx, node->ref.val);
JS_PopGCRef (enc->ctx, &node->ref);
sys_free (node);
}
static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) {
NotaVisitedNode *node = enc->visited_list;
while (node) {
if (JS_StrictEq (enc->ctx, node->ref.val, val))
return 1;
node = node->next;
}
return 0;
}
static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return JS_DupValue (enc->ctx, val);
JSValue args[2] = { JS_DupValue (enc->ctx, key), JS_DupValue (enc->ctx, val) };
JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args);
JS_FreeValue (enc->ctx, args[0]);
JS_FreeValue (enc->ctx, args[1]);
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
return result;
}
static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) {
int type = nota_type (nota);
JSValue ret2;
long long n;
double d;
int b;
char *str;
uint8_t *blob;
switch (type) {
case NOTA_BLOB:
nota = nota_read_blob (&n, (char **)&blob, nota);
*tmp = js_new_blob_stoned_copy (js, blob, n);
sys_free (blob);
break;
case NOTA_TEXT:
nota = nota_read_text (&str, nota);
*tmp = JS_NewString (js, str);
sys_free (str);
break;
case NOTA_ARR:
nota = nota_read_array (&n, nota);
*tmp = JS_NewArrayLen (js, n);
for (int i = 0; i < n; i++) {
nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver);
JS_SetPropertyNumber (js, *tmp, i, ret2);
}
break;
case NOTA_REC:
nota = nota_read_record (&n, nota);
*tmp = JS_NewObject (js);
for (int i = 0; i < n; i++) {
JSGCRef prop_key_ref, sub_val_ref;
JS_PushGCRef (js, &prop_key_ref);
JS_PushGCRef (js, &sub_val_ref);
nota = nota_read_text (&str, nota);
prop_key_ref.val = JS_NewString (js, str);
sub_val_ref.val = JS_NULL;
nota = js_do_nota_decode (js, &sub_val_ref.val, nota, *tmp, prop_key_ref.val, reviver);
JS_SetPropertyStr (js, *tmp, str, sub_val_ref.val);
JS_PopGCRef (js, &sub_val_ref);
JS_PopGCRef (js, &prop_key_ref);
sys_free (str);
}
break;
case NOTA_INT:
nota = nota_read_int (&n, nota);
*tmp = JS_NewInt64 (js, n);
break;
case NOTA_SYM:
nota = nota_read_sym (&b, nota);
if (b == NOTA_PRIVATE) {
JSGCRef inner_ref, obj_ref2;
JS_PushGCRef (js, &inner_ref);
JS_PushGCRef (js, &obj_ref2);
inner_ref.val = JS_NULL;
nota = js_do_nota_decode (js, &inner_ref.val, nota, holder, JS_NULL, reviver);
obj_ref2.val = JS_NewObject (js);
if (!JS_IsNull (js->actor_sym))
JS_SetPropertyKey (js, obj_ref2.val, js->actor_sym, inner_ref.val);
JS_CellStone (js, obj_ref2.val);
*tmp = obj_ref2.val;
JS_PopGCRef (js, &obj_ref2);
JS_PopGCRef (js, &inner_ref);
} else {
switch (b) {
case NOTA_NULL: *tmp = JS_NULL; break;
case NOTA_FALSE: *tmp = JS_NewBool (js, 0); break;
case NOTA_TRUE: *tmp = JS_NewBool (js, 1); break;
default: *tmp = JS_NULL; break;
}
}
break;
default:
case NOTA_FLOAT:
nota = nota_read_float (&d, nota);
*tmp = JS_NewFloat64 (js, d);
break;
}
if (!JS_IsNull (reviver)) {
JSValue args[2] = { JS_DupValue (js, key), JS_DupValue (js, *tmp) };
JSValue revived = JS_Call (js, reviver, holder, 2, args);
JS_FreeValue (js, args[0]);
JS_FreeValue (js, args[1]);
if (!JS_IsException (revived)) {
JS_FreeValue (js, *tmp);
*tmp = revived;
} else {
JS_FreeValue (js, revived);
}
}
return nota;
}
static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) {
JSContext *ctx = enc->ctx;
JSGCRef replaced_ref, keys_ref, elem_ref, prop_ref;
JS_PushGCRef (ctx, &replaced_ref);
replaced_ref.val = nota_apply_replacer (enc, holder, key, val);
int tag = JS_VALUE_GET_TAG (replaced_ref.val);
switch (tag) {
case JS_TAG_INT:
case JS_TAG_FLOAT64: {
double d;
JS_ToFloat64 (ctx, &d, replaced_ref.val);
nota_write_number (&enc->nb, d);
break;
}
case JS_TAG_STRING: {
const char *str = JS_ToCString (ctx, replaced_ref.val);
nota_write_text (&enc->nb, str);
JS_FreeCString (ctx, str);
break;
}
case JS_TAG_BOOL:
if (JS_VALUE_GET_BOOL (replaced_ref.val)) nota_write_sym (&enc->nb, NOTA_TRUE);
else nota_write_sym (&enc->nb, NOTA_FALSE);
break;
case JS_TAG_NULL:
nota_write_sym (&enc->nb, NOTA_NULL);
break;
case JS_TAG_PTR: {
if (JS_IsText (replaced_ref.val)) {
const char *str = JS_ToCString (ctx, replaced_ref.val);
nota_write_text (&enc->nb, str);
JS_FreeCString (ctx, str);
break;
}
if (js_is_blob (ctx, replaced_ref.val)) {
size_t buf_len;
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced_ref.val);
if (buf_data == (void *)-1) {
JS_PopGCRef (ctx, &replaced_ref);
return;
}
nota_write_blob (&enc->nb, (unsigned long long)buf_len * 8, (const char *)buf_data);
break;
}
if (JS_IsArray (replaced_ref.val)) {
if (nota_stack_has (enc, replaced_ref.val)) {
enc->cycle = 1;
break;
}
nota_stack_push (enc, replaced_ref.val);
int arr_len = nota_get_arr_len (ctx, replaced_ref.val);
nota_write_array (&enc->nb, arr_len);
JS_PushGCRef (ctx, &elem_ref);
for (int i = 0; i < arr_len; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, replaced_ref.val, i);
JSValue elem_key = JS_NewInt32 (ctx, i);
nota_encode_value (enc, elem_ref.val, replaced_ref.val, elem_key);
}
JS_PopGCRef (ctx, &elem_ref);
nota_stack_pop (enc);
break;
}
JSValue adata = JS_NULL;
if (!JS_IsNull (ctx->actor_sym)) {
int has = JS_HasPropertyKey (ctx, replaced_ref.val, ctx->actor_sym);
if (has > 0) adata = JS_GetPropertyKey (ctx, replaced_ref.val, ctx->actor_sym);
}
if (!JS_IsNull (adata)) {
nota_write_sym (&enc->nb, NOTA_PRIVATE);
nota_encode_value (enc, adata, replaced_ref.val, JS_NULL);
JS_FreeValue (ctx, adata);
break;
}
JS_FreeValue (ctx, adata);
if (nota_stack_has (enc, replaced_ref.val)) {
enc->cycle = 1;
break;
}
nota_stack_push (enc, replaced_ref.val);
JSValue to_json = JS_GetPropertyStr (ctx, replaced_ref.val, "toJSON");
if (JS_IsFunction (to_json)) {
JSValue result = JS_Call (ctx, to_json, replaced_ref.val, 0, NULL);
if (!JS_IsException (result)) {
nota_encode_value (enc, result, holder, key);
} else {
nota_write_sym (&enc->nb, NOTA_NULL);
}
nota_stack_pop (enc);
break;
}
JS_PushGCRef (ctx, &keys_ref);
keys_ref.val = JS_GetOwnPropertyNames (ctx, replaced_ref.val);
if (JS_IsException (keys_ref.val)) {
nota_write_sym (&enc->nb, NOTA_NULL);
nota_stack_pop (enc);
JS_PopGCRef (ctx, &keys_ref);
break;
}
int64_t plen64;
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
nota_write_sym (&enc->nb, NOTA_NULL);
nota_stack_pop (enc);
JS_PopGCRef (ctx, &keys_ref);
break;
}
uint32_t plen = (uint32_t)plen64;
JS_PushGCRef (ctx, &prop_ref);
JS_PushGCRef (ctx, &elem_ref);
uint32_t non_function_count = 0;
for (uint32_t i = 0; i < plen; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val);
if (!JS_IsFunction (prop_ref.val)) non_function_count++;
}
nota_write_record (&enc->nb, non_function_count);
for (uint32_t i = 0; i < plen; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val);
if (!JS_IsFunction (prop_ref.val)) {
const char *prop_name = JS_ToCString (ctx, elem_ref.val);
nota_write_text (&enc->nb, prop_name ? prop_name : "");
nota_encode_value (enc, prop_ref.val, replaced_ref.val, elem_ref.val);
JS_FreeCString (ctx, prop_name);
}
}
JS_PopGCRef (ctx, &elem_ref);
JS_PopGCRef (ctx, &prop_ref);
JS_PopGCRef (ctx, &keys_ref);
nota_stack_pop (enc);
break;
}
default:
nota_write_sym (&enc->nb, NOTA_NULL);
break;
}
JS_PopGCRef (ctx, &replaced_ref);
}
void *value2nota (JSContext *ctx, JSValue v) {
JSGCRef val_ref, key_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &key_ref);
val_ref.val = v;
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_list = NULL;
enc->cycle = 0;
enc->replacer_ref = NULL;
nota_buffer_init (&enc->nb, 128);
key_ref.val = JS_NewString (ctx, "");
nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val);
if (enc->cycle) {
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &val_ref);
nota_buffer_free (&enc->nb);
return NULL;
}
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &val_ref);
void *data_ptr = enc->nb.data;
enc->nb.data = NULL;
nota_buffer_free (&enc->nb);
return data_ptr;
}
JSValue nota2value (JSContext *js, void *nota) {
if (!nota) return JS_NULL;
JSGCRef holder_ref, key_ref, ret_ref;
JS_PushGCRef (js, &holder_ref);
JS_PushGCRef (js, &key_ref);
JS_PushGCRef (js, &ret_ref);
holder_ref.val = JS_NewObject (js);
key_ref.val = JS_NewString (js, "");
ret_ref.val = JS_NULL;
js_do_nota_decode (js, &ret_ref.val, nota, holder_ref.val, key_ref.val, JS_NULL);
JSValue result = ret_ref.val;
JS_PopGCRef (js, &ret_ref);
JS_PopGCRef (js, &key_ref);
JS_PopGCRef (js, &holder_ref);
return result;
}
static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_RaiseDisrupt (ctx, "nota.encode requires at least 1 argument");
JSGCRef val_ref, replacer_ref, key_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &replacer_ref);
JS_PushGCRef (ctx, &key_ref);
val_ref.val = argv[0];
replacer_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_list = NULL;
enc->cycle = 0;
enc->replacer_ref = &replacer_ref;
nota_buffer_init (&enc->nb, 128);
key_ref.val = JS_NewString (ctx, "");
nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val);
JSValue ret;
if (enc->cycle) {
nota_buffer_free (&enc->nb);
ret = JS_RaiseDisrupt (ctx, "Tried to encode something to nota with a cycle.");
} else {
size_t total_len = enc->nb.size;
void *data_ptr = enc->nb.data;
ret = js_new_blob_stoned_copy (ctx, (uint8_t *)data_ptr, total_len);
nota_buffer_free (&enc->nb);
}
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &replacer_ref);
JS_PopGCRef (ctx, &val_ref);
return ret;
}
static JSValue js_nota_decode (JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
if (argc < 1) return JS_NULL;
size_t len;
unsigned char *nota = js_get_blob_data (js, &len, argv[0]);
if (nota == (unsigned char *)-1) return JS_EXCEPTION;
if (!nota) return JS_NULL;
JSGCRef holder_ref, key_ref, ret_ref, reviver_ref;
JS_PushGCRef (js, &holder_ref);
JS_PushGCRef (js, &key_ref);
JS_PushGCRef (js, &ret_ref);
JS_PushGCRef (js, &reviver_ref);
reviver_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
holder_ref.val = JS_NewObject (js);
key_ref.val = JS_NewString (js, "");
ret_ref.val = JS_NULL;
js_do_nota_decode (js, &ret_ref.val, (char *)nota, holder_ref.val, key_ref.val, reviver_ref.val);
JSValue result = ret_ref.val;
JS_PopGCRef (js, &reviver_ref);
JS_PopGCRef (js, &ret_ref);
JS_PopGCRef (js, &key_ref);
JS_PopGCRef (js, &holder_ref);
return result;
}
static const JSCFunctionListEntry js_nota_funcs[] = {
JS_CFUNC_DEF ("encode", 1, js_nota_encode),
JS_CFUNC_DEF ("decode", 1, js_nota_decode),
};
JSValue js_core_internal_nota_use (JSContext *js) {
JSGCRef export_ref;
JS_PushGCRef (js, &export_ref);
export_ref.val = JS_NewObject (js);
JS_SetPropertyFunctionList (js, export_ref.val, js_nota_funcs, sizeof (js_nota_funcs) / sizeof (JSCFunctionListEntry));
JSValue result = export_ref.val;
JS_PopGCRef (js, &export_ref);
return result;
}

View File

@@ -733,7 +733,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, stack, 1),
};
JSValue js_core_os_use(JSContext *js) {
JSValue js_core_internal_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id);
JS_NewClass(js, js_dylib_class_id, &js_dylib_class);

View File

@@ -449,7 +449,7 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, 0),
};
JSValue js_core_qop_use(JSContext *js) {
JSValue js_core_internal_qop_use(JSContext *js) {
JS_FRAME(js);
JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);

View File

@@ -5,11 +5,11 @@ var fd = use('fd')
var http = use('http')
var miniz = use('miniz')
var time = use('time')
var crypto = use('crypto')
var crypto = use('internal/crypto')
var blob = use('blob')
var pkg_tools = use('package')
var os = use('os')
var os = use('internal/os')
var link = use('link')
// These come from env (via core_extras in engine.cm):
@@ -519,6 +519,7 @@ var _ast_cache = {}
var _analyze_cache = {}
var _compile_cache = {}
var _index_cache = {}
var _summary_cache = {}
var get_tokenize = function() {
if (!_tokenize_mod) _tokenize_mod = use_cache['core/tokenize'] || use_cache['tokenize']
@@ -547,6 +548,14 @@ var get_index = function() {
}
return _index_mod
}
var _analyze_mod = null
var get_analyze = function() {
if (!_analyze_mod) {
_analyze_mod = use_cache['core/analyze'] || use_cache['analyze']
if (!_analyze_mod) _analyze_mod = Shop.use('analyze', 'core')
}
return _analyze_mod
}
Shop.tokenize_file = function(path) {
var src = text(fd.slurp(path))
@@ -611,6 +620,16 @@ Shop.index_file = function(path) {
return idx
}
Shop.summary_file = function(path) {
var src = text(fd.slurp(path))
var key = content_hash(stone(blob(src)))
if (_summary_cache[key]) return _summary_cache[key]
var idx = Shop.index_file(path)
var summary = get_analyze().module_summary(idx)
_summary_cache[key] = summary
return summary
}
Shop.mcode_file = function(path) {
var folded = Shop.analyze_file(path)
return get_mcode()(folded)

View File

@@ -28,7 +28,7 @@ static const JSCFunctionListEntry js_wildstar_funcs[] = {
JS_PROP_INT32_DEF("WM_WILDSTAR", WM_WILDSTAR, 0),
};
JSValue js_core_wildstar_use(JSContext *js) {
JSValue js_core_internal_wildstar_use(JSContext *js) {
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_wildstar_funcs, countof(js_wildstar_funcs));

461
internal/wota.c Normal file
View File

@@ -0,0 +1,461 @@
#define WOTA_IMPLEMENTATION
#include "quickjs-internal.h"
#include "cell.h"
typedef struct ObjectRef {
void *ptr;
struct ObjectRef *next;
} ObjectRef;
typedef struct WotaEncodeContext {
JSContext *ctx;
ObjectRef *visited_stack;
WotaBuffer wb;
int cycle;
JSValue replacer;
} WotaEncodeContext;
static void wota_stack_push (WotaEncodeContext *enc, JSValueConst val) {
(void)enc; (void)val;
/* Cycle detection disabled for performance */
}
static void wota_stack_pop (WotaEncodeContext *enc) {
if (!enc->visited_stack) return;
ObjectRef *top = enc->visited_stack;
enc->visited_stack = top->next;
sys_free (top);
}
static int wota_stack_has (WotaEncodeContext *enc, JSValueConst val) {
(void)enc; (void)val;
return 0;
/* Cycle detection disabled for performance */
}
static void wota_stack_free (WotaEncodeContext *enc) {
while (enc->visited_stack) {
wota_stack_pop (enc);
}
}
static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) {
if (JS_IsNull (enc->replacer)) return JS_DupValue (enc->ctx, val);
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (enc->ctx, key);
JSValue args[2] = { key_val, JS_DupValue (enc->ctx, val) };
JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args);
JS_FreeValue (enc->ctx, args[0]);
JS_FreeValue (enc->ctx, args[1]);
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
return result;
}
static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key);
static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder) {
JSContext *ctx = enc->ctx;
/* Root the input value to protect it during property enumeration */
JSGCRef val_ref, keys_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &keys_ref);
val_ref.val = JS_DupValue (ctx, val);
keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val);
if (JS_IsException (keys_ref.val)) {
wota_write_sym (&enc->wb, WOTA_NULL);
JS_FreeValue (ctx, val_ref.val);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
return;
}
int64_t plen64;
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
JS_FreeValue (ctx, keys_ref.val);
JS_FreeValue (ctx, val_ref.val);
wota_write_sym (&enc->wb, WOTA_NULL);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
return;
}
uint32_t plen = (uint32_t)plen64;
uint32_t non_function_count = 0;
/* Allocate GC-rooted arrays for props and keys */
JSGCRef *prop_refs = sys_malloc (sizeof (JSGCRef) * plen);
JSGCRef *key_refs = sys_malloc (sizeof (JSGCRef) * plen);
for (uint32_t i = 0; i < plen; i++) {
JS_PushGCRef (ctx, &prop_refs[i]);
JS_PushGCRef (ctx, &key_refs[i]);
prop_refs[i].val = JS_NULL;
key_refs[i].val = JS_NULL;
}
for (uint32_t i = 0; i < plen; i++) {
/* Store key into its GCRef slot immediately so it's rooted before
JS_GetProperty can trigger GC and relocate the string. */
key_refs[i].val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
JSValue prop_val = JS_GetProperty (ctx, val_ref.val, key_refs[i].val);
if (!JS_IsFunction (prop_val)) {
if (i != non_function_count) {
key_refs[non_function_count].val = key_refs[i].val;
key_refs[i].val = JS_NULL;
}
prop_refs[non_function_count].val = prop_val;
non_function_count++;
} else {
JS_FreeValue (ctx, prop_val);
JS_FreeValue (ctx, key_refs[i].val);
key_refs[i].val = JS_NULL;
}
}
JS_FreeValue (ctx, keys_ref.val);
wota_write_record (&enc->wb, non_function_count);
for (uint32_t i = 0; i < non_function_count; i++) {
size_t klen;
const char *prop_name = JS_ToCStringLen (ctx, &klen, key_refs[i].val);
wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val);
JS_FreeCString (ctx, prop_name);
JS_FreeValue (ctx, prop_refs[i].val);
JS_FreeValue (ctx, key_refs[i].val);
}
/* Pop all GC refs in reverse order */
for (int i = plen - 1; i >= 0; i--) {
JS_PopGCRef (ctx, &key_refs[i]);
JS_PopGCRef (ctx, &prop_refs[i]);
}
sys_free (prop_refs);
sys_free (key_refs);
JS_FreeValue (ctx, val_ref.val);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
}
static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key) {
JSContext *ctx = enc->ctx;
JSValue replaced;
if (!JS_IsNull (enc->replacer) && !JS_IsNull (key))
replaced = wota_apply_replacer (enc, holder, key, val);
else
replaced = JS_DupValue (enc->ctx, val);
int tag = JS_VALUE_GET_TAG (replaced);
switch (tag) {
case JS_TAG_INT: {
int32_t d;
JS_ToInt32 (ctx, &d, replaced);
wota_write_int_word (&enc->wb, d);
break;
}
case JS_TAG_FLOAT64: {
double d;
if (JS_ToFloat64 (ctx, &d, replaced) < 0) {
wota_write_sym (&enc->wb, WOTA_NULL);
break;
}
wota_write_float_word (&enc->wb, d);
break;
}
case JS_TAG_STRING: {
size_t plen;
const char *str = JS_ToCStringLen (ctx, &plen, replaced);
wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString (ctx, str);
break;
}
case JS_TAG_BOOL:
wota_write_sym (&enc->wb, JS_VALUE_GET_BOOL (replaced) ? WOTA_TRUE : WOTA_FALSE);
break;
case JS_TAG_NULL:
wota_write_sym (&enc->wb, WOTA_NULL);
break;
case JS_TAG_PTR: {
if (JS_IsText (replaced)) {
size_t plen;
const char *str = JS_ToCStringLen (ctx, &plen, replaced);
wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString (ctx, str);
break;
}
if (js_is_blob (ctx, replaced)) {
size_t buf_len;
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced);
if (buf_data == (void *)-1) {
JS_FreeValue (ctx, replaced);
return;
}
if (buf_len == 0) {
wota_write_blob (&enc->wb, 0, "");
} else {
wota_write_blob (&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data);
}
break;
}
if (JS_IsArray (replaced)) {
if (wota_stack_has (enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push (enc, replaced);
int64_t arr_len;
JS_GetLength (ctx, replaced, &arr_len);
wota_write_array (&enc->wb, arr_len);
for (int64_t i = 0; i < arr_len; i++) {
JSValue elem_val = JS_GetPropertyNumber (ctx, replaced, i);
wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i));
JS_FreeValue (ctx, elem_val);
}
wota_stack_pop (enc);
break;
}
JSValue adata = JS_NULL;
if (!JS_IsNull (ctx->actor_sym)) {
int has = JS_HasPropertyKey (ctx, replaced, ctx->actor_sym);
if (has > 0) adata = JS_GetPropertyKey (ctx, replaced, ctx->actor_sym);
}
if (!JS_IsNull (adata)) {
wota_write_sym (&enc->wb, WOTA_PRIVATE);
wota_encode_value (enc, adata, replaced, JS_NULL);
JS_FreeValue (ctx, adata);
break;
}
JS_FreeValue (ctx, adata);
if (wota_stack_has (enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push (enc, replaced);
JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON");
if (JS_IsFunction (to_json)) {
JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL);
JS_FreeValue (ctx, to_json);
if (!JS_IsException (result)) {
wota_encode_value (enc, result, holder, key);
JS_FreeValue (ctx, result);
} else
wota_write_sym (&enc->wb, WOTA_NULL);
wota_stack_pop (enc);
break;
}
JS_FreeValue (ctx, to_json);
encode_object_properties (enc, replaced, holder);
wota_stack_pop (enc);
break;
}
default:
wota_write_sym (&enc->wb, WOTA_NULL);
break;
}
JS_FreeValue (ctx, replaced);
}
static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) {
uint64_t first_word = *(uint64_t *)data_ptr;
int type = (int)(first_word & 0xffU);
switch (type) {
case WOTA_INT: {
long long val;
data_ptr = wota_read_int (&val, data_ptr);
*out_val = JS_NewInt64 (ctx, val);
break;
}
case WOTA_FLOAT: {
double d;
data_ptr = wota_read_float (&d, data_ptr);
*out_val = JS_NewFloat64 (ctx, d);
break;
}
case WOTA_SYM: {
int scode;
data_ptr = wota_read_sym (&scode, data_ptr);
if (scode == WOTA_PRIVATE) {
JSGCRef inner_ref, obj_ref2;
JS_PushGCRef (ctx, &inner_ref);
JS_PushGCRef (ctx, &obj_ref2);
inner_ref.val = JS_NULL;
data_ptr = decode_wota_value (ctx, data_ptr, &inner_ref.val, holder, JS_NULL, reviver);
obj_ref2.val = JS_NewObject (ctx);
if (!JS_IsNull (ctx->actor_sym))
JS_SetPropertyKey (ctx, obj_ref2.val, ctx->actor_sym, inner_ref.val);
JS_CellStone (ctx, obj_ref2.val);
*out_val = obj_ref2.val;
JS_PopGCRef (ctx, &obj_ref2);
JS_PopGCRef (ctx, &inner_ref);
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
else if (scode == WOTA_FALSE) *out_val = JS_NewBool (ctx, 0);
else if (scode == WOTA_TRUE) *out_val = JS_NewBool (ctx, 1);
else *out_val = JS_NULL;
break;
}
case WOTA_BLOB: {
long long blen;
char *bdata = NULL;
data_ptr = wota_read_blob (&blen, &bdata, data_ptr);
*out_val = bdata ? js_new_blob_stoned_copy (ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy (ctx, NULL, 0);
if (bdata) sys_free (bdata);
break;
}
case WOTA_TEXT: {
char *utf8 = NULL;
data_ptr = wota_read_text (&utf8, data_ptr);
*out_val = JS_NewString (ctx, utf8 ? utf8 : "");
if (utf8) sys_free (utf8);
break;
}
case WOTA_ARR: {
long long c;
data_ptr = wota_read_array (&c, data_ptr);
JSGCRef arr_ref;
JS_PushGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen (ctx, c);
for (long long i = 0; i < c; i++) {
JSGCRef elem_ref;
JS_PushGCRef (ctx, &elem_ref);
elem_ref.val = JS_NULL;
JSValue idx_key = JS_NewInt32 (ctx, (int32_t)i);
data_ptr = decode_wota_value (ctx, data_ptr, &elem_ref.val, arr_ref.val, idx_key, reviver);
JS_SetPropertyNumber (ctx, arr_ref.val, i, elem_ref.val);
JS_PopGCRef (ctx, &elem_ref);
}
*out_val = arr_ref.val;
JS_PopGCRef (ctx, &arr_ref);
break;
}
case WOTA_REC: {
long long c;
data_ptr = wota_read_record (&c, data_ptr);
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
size_t key_len;
data_ptr = wota_read_text_len (&key_len, &tkey, data_ptr);
if (!tkey) continue;
JSGCRef prop_key_ref, sub_val_ref;
JS_PushGCRef (ctx, &prop_key_ref);
JS_PushGCRef (ctx, &sub_val_ref);
prop_key_ref.val = JS_NewStringLen (ctx, tkey, key_len);
sub_val_ref.val = JS_NULL;
data_ptr = decode_wota_value (ctx, data_ptr, &sub_val_ref.val, obj_ref.val, prop_key_ref.val, reviver);
JS_SetPropertyStr (ctx, obj_ref.val, tkey, sub_val_ref.val);
JS_PopGCRef (ctx, &sub_val_ref);
JS_PopGCRef (ctx, &prop_key_ref);
sys_free (tkey);
}
*out_val = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
break;
}
default:
data_ptr += 8;
*out_val = JS_NULL;
break;
}
if (!JS_IsNull (reviver)) {
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (ctx, key);
JSValue args[2] = { key_val, JS_DupValue (ctx, *out_val) };
JSValue revived = JS_Call (ctx, reviver, holder, 2, args);
JS_FreeValue (ctx, args[0]);
JS_FreeValue (ctx, args[1]);
if (!JS_IsException (revived)) {
JS_FreeValue (ctx, *out_val);
*out_val = revived;
} else
JS_FreeValue (ctx, revived);
}
return data_ptr;
}
void *value2wota (JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes) {
JSGCRef val_ref, rep_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &rep_ref);
val_ref.val = v;
rep_ref.val = replacer;
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = NULL;
enc->cycle = 0;
enc->replacer = rep_ref.val;
wota_buffer_init (&enc->wb, 16);
wota_encode_value (enc, val_ref.val, JS_NULL, JS_NULL);
if (enc->cycle) {
wota_stack_free (enc);
wota_buffer_free (&enc->wb);
JS_PopGCRef (ctx, &rep_ref);
JS_PopGCRef (ctx, &val_ref);
return NULL;
}
wota_stack_free (enc);
size_t total_bytes = enc->wb.size * sizeof (uint64_t);
void *wota = sys_realloc (enc->wb.data, total_bytes);
if (bytes) *bytes = total_bytes;
JS_PopGCRef (ctx, &rep_ref);
JS_PopGCRef (ctx, &val_ref);
return wota;
}
JSValue wota2value (JSContext *ctx, void *wota) {
JSGCRef holder_ref, result_ref;
JS_PushGCRef (ctx, &holder_ref);
JS_PushGCRef (ctx, &result_ref);
result_ref.val = JS_NULL;
holder_ref.val = JS_NewObject (ctx);
decode_wota_value (ctx, wota, &result_ref.val, holder_ref.val, JS_NULL, JS_NULL);
JSValue result = result_ref.val;
JS_PopGCRef (ctx, &result_ref);
JS_PopGCRef (ctx, &holder_ref);
return result;
}
static JSValue js_wota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_RaiseDisrupt (ctx, "wota.encode requires at least 1 argument");
size_t total_bytes;
void *wota = value2wota (ctx, argv[0], JS_IsFunction (argv[1]) ? argv[1] : JS_NULL, &total_bytes);
JSValue ret = js_new_blob_stoned_copy (ctx, wota, total_bytes);
sys_free (wota);
return ret;
}
static JSValue js_wota_decode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_NULL;
size_t len;
uint8_t *buf = js_get_blob_data (ctx, &len, argv[0]);
if (buf == (uint8_t *)-1) return JS_EXCEPTION;
if (!buf || len == 0) return JS_RaiseDisrupt (ctx, "No blob data present");
JSValue reviver = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
char *data_ptr = (char *)buf;
JSGCRef result_ref, holder_ref, empty_key_ref;
JS_PushGCRef (ctx, &result_ref);
JS_PushGCRef (ctx, &holder_ref);
JS_PushGCRef (ctx, &empty_key_ref);
result_ref.val = JS_NULL;
holder_ref.val = JS_NewObject (ctx);
empty_key_ref.val = JS_NewString (ctx, "");
decode_wota_value (ctx, data_ptr, &result_ref.val, holder_ref.val, empty_key_ref.val, reviver);
JSValue result = result_ref.val;
JS_PopGCRef (ctx, &empty_key_ref);
JS_PopGCRef (ctx, &holder_ref);
JS_PopGCRef (ctx, &result_ref);
return result;
}
static const JSCFunctionListEntry js_wota_funcs[] = {
JS_CFUNC_DEF ("encode", 2, js_wota_encode),
JS_CFUNC_DEF ("decode", 2, js_wota_decode),
};
JSValue js_core_internal_wota_use (JSContext *ctx) {
JSGCRef exports_ref;
JS_PushGCRef (ctx, &exports_ref);
exports_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, exports_ref.val, js_wota_funcs, sizeof (js_wota_funcs) / sizeof (js_wota_funcs[0]));
JSValue result = exports_ref.val;
JS_PopGCRef (ctx, &exports_ref);
return result;
}

67
json.c Normal file
View File

@@ -0,0 +1,67 @@
#include "cell.h"
#include <string.h>
/* JSON uses JS_KEY_empty for reviver — define locally from public constants */
#ifndef JS_KEY_empty
#define JS_KEY_empty ((JSValue)JS_TAG_STRING_IMM)
#endif
static JSValue js_cell_json_encode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1)
return JS_RaiseDisrupt (ctx, "json.encode requires at least 1 argument");
int pretty = argc <= 1 || JS_ToBool (ctx, argv[1]);
JSValue space = pretty ? JS_NewInt32 (ctx, 2) : JS_NULL;
JSValue replacer = JS_NULL;
if (argc > 2 && JS_IsFunction (argv[2]))
replacer = argv[2];
else if (argc > 3 && JS_IsArray (argv[3]))
replacer = argv[3];
JSValue result = JS_JSONStringify (ctx, argv[0], replacer, space, pretty);
return result;
}
static JSValue js_cell_json_decode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1)
return JS_RaiseDisrupt (ctx, "json.decode requires at least 1 argument");
if (!JS_IsText (argv[0]))
return JS_RaiseDisrupt (ctx, "couldn't parse text: not a string");
const char *str = JS_ToCString (ctx, argv[0]);
if (!str) return JS_EXCEPTION;
size_t len = strlen (str);
JSValue result = JS_ParseJSON (ctx, str, len, "<json>");
JS_FreeCString (ctx, str);
/* Apply reviver if provided */
if (argc > 1 && JS_IsFunction (argv[1]) && !JS_IsException (result)) {
/* Create wrapper object to pass to reviver */
JSValue wrapper = JS_NewObject (ctx);
JS_SetPropertyStr (ctx, wrapper, "", result);
JSValue holder = wrapper;
JSValue key = JS_KEY_empty;
JSValue args[2] = { key, JS_GetProperty (ctx, holder, key) };
JSValue final = JS_Call (ctx, argv[1], holder, 2, args);
result = final;
}
return result;
}
static const JSCFunctionListEntry js_cell_json_funcs[] = {
JS_CFUNC_DEF ("encode", 1, js_cell_json_encode),
JS_CFUNC_DEF ("decode", 1, js_cell_json_decode),
};
JSValue js_core_json_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_cell_json_funcs, countof (js_cell_json_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

66
math/cycles.c Normal file
View File

@@ -0,0 +1,66 @@
#include "cell.h"
#include "cell_math.h"
#include <math.h>
#define TWOPI (2.0 * 3.14159265358979323846264338327950288419716939937510)
static JSValue js_math_cyc_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, acos (x) / TWOPI);
}
static JSValue js_math_cyc_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, asin (x) / TWOPI);
}
static JSValue js_math_cyc_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, atan (x) / TWOPI);
}
static JSValue js_math_cyc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, cos (x * TWOPI));
}
static JSValue js_math_cyc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, sin (x * TWOPI));
}
static JSValue js_math_cyc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, tan (x * TWOPI));
}
static const JSCFunctionListEntry js_math_cycles_funcs[]
= { JS_CFUNC_DEF ("arc_cosine", 1, js_math_cyc_arc_cosine),
JS_CFUNC_DEF ("arc_sine", 1, js_math_cyc_arc_sine),
JS_CFUNC_DEF ("arc_tangent", 1, js_math_cyc_arc_tangent),
JS_CFUNC_DEF ("cosine", 1, js_math_cyc_cosine),
JS_CFUNC_DEF ("sine", 1, js_math_cyc_sine),
JS_CFUNC_DEF ("tangent", 1, js_math_cyc_tangent),
JS_CFUNC_DEF ("ln", 1, js_math_ln),
JS_CFUNC_DEF ("log", 1, js_math_log10),
JS_CFUNC_DEF ("log2", 1, js_math_log2),
JS_CFUNC_DEF ("power", 2, js_math_power),
JS_CFUNC_DEF ("root", 2, js_math_root),
JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt),
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_cycles_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_cycles_funcs, countof (js_math_cycles_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

67
math/degrees.c Normal file
View File

@@ -0,0 +1,67 @@
#include "cell.h"
#include "cell_math.h"
#include <math.h>
#define DEG2RAD (3.14159265358979323846264338327950288419716939937510 / 180.0)
#define RAD2DEG (180.0 / 3.14159265358979323846264338327950288419716939937510)
static JSValue js_math_deg_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, acos (x) * RAD2DEG);
}
static JSValue js_math_deg_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, asin (x) * RAD2DEG);
}
static JSValue js_math_deg_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, atan (x) * RAD2DEG);
}
static JSValue js_math_deg_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, cos (x * DEG2RAD));
}
static JSValue js_math_deg_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, sin (x * DEG2RAD));
}
static JSValue js_math_deg_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, tan (x * DEG2RAD));
}
static const JSCFunctionListEntry js_math_degrees_funcs[]
= { JS_CFUNC_DEF ("arc_cosine", 1, js_math_deg_arc_cosine),
JS_CFUNC_DEF ("arc_sine", 1, js_math_deg_arc_sine),
JS_CFUNC_DEF ("arc_tangent", 1, js_math_deg_arc_tangent),
JS_CFUNC_DEF ("cosine", 1, js_math_deg_cosine),
JS_CFUNC_DEF ("sine", 1, js_math_deg_sine),
JS_CFUNC_DEF ("tangent", 1, js_math_deg_tangent),
JS_CFUNC_DEF ("ln", 1, js_math_ln),
JS_CFUNC_DEF ("log", 1, js_math_log10),
JS_CFUNC_DEF ("log2", 1, js_math_log2),
JS_CFUNC_DEF ("power", 2, js_math_power),
JS_CFUNC_DEF ("root", 2, js_math_root),
JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt),
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_degrees_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_degrees_funcs, countof (js_math_degrees_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

64
math/radians.c Normal file
View File

@@ -0,0 +1,64 @@
#include "cell.h"
#include "cell_math.h"
#include <math.h>
static JSValue js_math_rad_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, acos (x));
}
static JSValue js_math_rad_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, asin (x));
}
static JSValue js_math_rad_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, atan (x));
}
static JSValue js_math_rad_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, cos (x));
}
static JSValue js_math_rad_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, sin (x));
}
static JSValue js_math_rad_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, tan (x));
}
static const JSCFunctionListEntry js_math_radians_funcs[]
= { JS_CFUNC_DEF ("arc_cosine", 1, js_math_rad_arc_cosine),
JS_CFUNC_DEF ("arc_sine", 1, js_math_rad_arc_sine),
JS_CFUNC_DEF ("arc_tangent", 1, js_math_rad_arc_tangent),
JS_CFUNC_DEF ("cosine", 1, js_math_rad_cosine),
JS_CFUNC_DEF ("sine", 1, js_math_rad_sine),
JS_CFUNC_DEF ("tangent", 1, js_math_rad_tangent),
JS_CFUNC_DEF ("ln", 1, js_math_ln),
JS_CFUNC_DEF ("log", 1, js_math_log10),
JS_CFUNC_DEF ("log2", 1, js_math_log2),
JS_CFUNC_DEF ("power", 2, js_math_power),
JS_CFUNC_DEF ("root", 2, js_math_root),
JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt),
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_radians_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_radians_funcs, countof (js_math_radians_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

View File

@@ -64,11 +64,18 @@ src += ['qbe_helpers.c']
src += ['qbe_backend.c']
scripts = [
'json.c',
'internal/nota.c',
'internal/wota.c',
'math/radians.c',
'math/degrees.c',
'math/cycles.c',
'src/cell_math.c',
'debug/js.c',
'qop.c',
'wildstar.c',
'internal/qop.c',
'internal/wildstar.c',
'fit.c',
'crypto.c',
'internal/crypto.c',
'internal/kim.c',
'internal/time.c',
'debug/debug.c',
@@ -76,7 +83,6 @@ scripts = [
'internal/fd.c',
'net/http.c',
'net/enet.c',
'wildstar.c',
'archive/miniz.c',
'source/cJSON.c'
]
@@ -86,7 +92,7 @@ foreach file: scripts
endforeach
srceng = 'source'
includes = [srceng, 'internal', 'debug', 'net', 'archive', 'src/qbe']
includes = [srceng, 'internal', 'debug', 'net', 'archive', 'src/qbe', 'src']
foreach file : src
full_path = join_paths(srceng, file)

View File

@@ -1727,6 +1727,13 @@ var parse = function(tokens, src, filename, tokenizer) {
return null
}
if (kind == "this") {
if (scope.function_nr == 0) {
sem_error(expr, "'this' cannot be used at the top level of a program")
}
return null
}
if (kind == "[") {
sem_check_expr(scope, expr.left)
sem_check_expr(scope, expr.right)

View File

@@ -1,7 +1,7 @@
// cell qopconv - Convert QOP archive formats
var fd = use('fd')
var qop = use('qop')
var qop = use('internal/qop')
function print_usage() {
log.console("Usage: qopconv [OPTION...] FILE...")

137
query.ce
View File

@@ -1,29 +1,39 @@
// cell query — Semantic queries across packages.
//
// Usage:
// cell query --this [--top|--fn] [<package>] this references
// cell query --intrinsic <name> [<package>] Find built-in intrinsic usage
// cell query --decl <name> [<package>] Variable declarations by name
// cell query --help Show usage
// cell query <name> Find all references to <name>
// cell query <name> --top Top-level references only
// cell query <name> --fn Inside-function references only
// cell query <name> --channels Show <name>.<prop> usage summary
// cell query --decl <name> Find declarations of <name>
// cell query --decl <name> --fn Only function declarations
// cell query --intrinsic <name> Find intrinsic usage
// cell query --excess-args Find call sites with >4 args
// cell query --help
var shop = use('internal/shop')
var query_mod = use('query')
var analyze_mod = use('analyze')
var fd = use('fd')
var mode = null
var name = null
var this_scope = null
var scope_filter = null
var kind_filter = null
var pkg_filter = null
var show_help = false
var i = 0
for (i = 0; i < length(args); i++) {
if (args[i] == '--this') {
mode = "this"
} else if (args[i] == '--top') {
this_scope = "top"
if (args[i] == '--top') {
scope_filter = "top"
} else if (args[i] == '--fn') {
this_scope = "fn"
if (mode == "decl") {
kind_filter = "fn"
} else {
scope_filter = "fn"
}
} else if (args[i] == '--channels') {
mode = "channels"
} else if (args[i] == '--intrinsic') {
mode = "intrinsic"
if (i + 1 < length(args) && !starts_with(args[i + 1], '-')) {
@@ -42,13 +52,26 @@ for (i = 0; i < length(args); i++) {
log.error('--decl requires a name')
mode = "error"
}
} else if (args[i] == '--excess-args') {
mode = "excess_args"
} else if (args[i] == '--help' || args[i] == '-h') {
show_help = true
} else if (!starts_with(args[i], '-')) {
pkg_filter = args[i]
if (name == null && mode == null) {
name = args[i]
mode = "refs"
} else {
pkg_filter = args[i]
}
}
}
// --channels requires a name from positional arg
if (mode == "channels" && name == null) {
log.error('--channels requires a name (e.g., cell query log --channels)')
mode = "error"
}
var all_files = null
var files = []
var j = 0
@@ -56,6 +79,10 @@ var idx = null
var hits = null
var hit = null
var k = 0
var ch_result = null
var props = null
var prop = null
var parts = null
// Use return pattern to avoid closure-over-object issue with disruption.
var safe_index = function(path) {
@@ -65,21 +92,24 @@ var safe_index = function(path) {
}
if (show_help) {
log.console("Usage: cell query [options] [<package>]")
log.console("Usage: cell query [options] [<name>] [<package>]")
log.console("")
log.console("Semantic queries across packages.")
log.console("")
log.console("Options:")
log.console(" --this All this references")
log.console(" --this --top Top-level this only (not inside functions)")
log.console(" --this --fn this inside functions only")
log.console(" --intrinsic <name> Find built-in intrinsic usage (e.g., print)")
log.console(" --decl <name> Variable declarations by name")
log.console("Commands:")
log.console(" <name> Find all references to <name>")
log.console(" <name> --top Top-level references only")
log.console(" <name> --fn Inside-function references only")
log.console(" <name> --channels Show <name>.<prop> usage summary")
log.console(" --decl <name> Find declarations of <name>")
log.console(" --decl <name> --fn Only function declarations")
log.console(" --intrinsic <name> Find intrinsic usage")
log.console(" --excess-args Find call sites with >4 args")
log.console("")
log.console("Without a package argument, searches all installed packages.")
} else if (mode == null || mode == "error") {
if (mode != "error") {
log.error('Specify --this, --intrinsic, or --decl. Use --help for usage.')
log.error('Specify a name or --decl/--intrinsic/--excess-args. Use --help for usage.')
}
} else {
all_files = shop.all_script_paths()
@@ -98,22 +128,61 @@ if (show_help) {
idx = safe_index(files[j].full_path)
if (idx == null) continue
hits = null
if (mode == "this") {
hits = query_mod.find_this(idx, this_scope)
if (mode == "refs") {
hits = analyze_mod.find_refs(idx, name, scope_filter)
if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.span != null) {
if (hit.enclosing != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name} (in: ${hit.enclosing})`)
} else {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name} (top-level)`)
}
}
}
}
} else if (mode == "channels") {
ch_result = analyze_mod.channels(idx, name)
if (ch_result != null && ch_result.summary != null) {
props = array(ch_result.summary)
if (length(props) > 0) {
parts = []
for (k = 0; k < length(props); k++) {
prop = props[k]
parts[] = `${prop}(${text(ch_result.summary[prop])})`
}
log.console(`${files[j].package}:${files[j].rel_path}: ${text(parts, " ")}`)
}
}
} else if (mode == "intrinsic") {
hits = query_mod.intrinsic(idx, name)
hits = analyze_mod.find_intrinsic(idx, name)
if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.span != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name}`)
}
}
}
} else if (mode == "decl") {
hits = query_mod.find_decl(idx, name, null)
}
if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.span != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name}`)
} else if (hit.decl_span != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.decl_span.from_row)}:${text(hit.decl_span.from_col)}: ${hit.kind} ${hit.name}`)
hits = analyze_mod.find_decls(idx, name, kind_filter)
if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.decl_span != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.decl_span.from_row)}:${text(hit.decl_span.from_col)}: ${hit.kind} ${hit.name}`)
}
}
}
} else if (mode == "excess_args") {
hits = analyze_mod.excess_args(idx)
if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.span != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.callee}() called with ${text(hit.args_count)} args (max 4)`)
}
}
}
}

View File

@@ -1,62 +1,19 @@
// query.cm — Semantic queries over index data.
//
// All functions take an index object (from index.cm) and return arrays of hits.
// query.cm — Backward-compatible wrapper delegating to analyze.cm.
var analyze = use('analyze')
var query = {}
// Find this references. scope: "top" (top-level only), "fn" (in functions), null (all).
query.find_this = function(idx, scope) {
var hits = []
var i = 0
var ref = null
while (i < length(idx.references)) {
ref = idx.references[i]
if (ref.name == "this") {
if (scope == null) {
hits[] = ref
} else if (scope == "top" && ref.enclosing == null) {
hits[] = ref
} else if (scope == "fn" && ref.enclosing != null) {
hits[] = ref
}
}
i = i + 1
}
return hits
return analyze.find_refs(idx, "this", scope)
}
// Intrinsic usage: find refs to a built-in name (e.g., print).
query.intrinsic = function(idx, name) {
var hits = []
var i = 0
var ref = null
if (idx.intrinsic_refs == null) return hits
while (i < length(idx.intrinsic_refs)) {
ref = idx.intrinsic_refs[i]
if (ref.name == name) {
hits[] = ref
}
i = i + 1
}
return hits
return analyze.find_intrinsic(idx, name)
}
// Variable declarations matching a name and optional kind filter.
// kind is one of "var", "def", "fn", "param", or null (any).
query.find_decl = function(idx, name, kind) {
var hits = []
var i = 0
var sym = null
while (i < length(idx.symbols)) {
sym = idx.symbols[i]
if (sym.name == name) {
if (kind == null || sym.kind == kind) {
hits[] = sym
}
}
i = i + 1
}
return hits
return analyze.find_decls(idx, name, kind)
}
return query

View File

@@ -1,6 +1,6 @@
var rnd = {}
var os = use('os')
var os = use('internal/os')
rnd.random = function()
{

View File

@@ -6,7 +6,7 @@
// Loads <module>.cm via use() (interpreted) and via shop.use_native() (native),
// runs both and compares results and timing.
var os = use('os')
var os = use('internal/os')
var fd = use('fd')
var shop = use('internal/shop')

View File

@@ -2,7 +2,7 @@
// Usage: ./cell --dev --seed run_native_seed benches/fibonacci
var fd = use("fd")
var os = use("os")
var os = use("internal/os")
if (length(args) < 1) {
print("usage: cell --dev --seed run_native_seed <module>")

View File

@@ -17,7 +17,7 @@
var fd = use("fd")
var json = use("json")
var os = use("os")
var os = use("internal/os")
var shop = use("internal/shop")
var tokenize = use("tokenize")
var parse = use("parse")

View File

@@ -30,6 +30,7 @@ cell_rt *root_cell = NULL;
static char *shop_path = NULL;
static char *core_path = NULL;
static int native_mode = 0;
static int warn_mode = 1;
static JSRuntime *g_runtime = NULL;
// Compute blake2b hash of data and return hex string (caller must free)
@@ -260,7 +261,7 @@ void actor_disrupt(cell_rt *crt)
actor_free(crt);
}
JSValue js_core_os_use(JSContext *js);
JSValue js_core_internal_os_use(JSContext *js);
JSValue js_core_json_use(JSContext *js);
void script_startup(cell_rt *prt)
@@ -316,7 +317,7 @@ void script_startup(cell_rt *prt)
JS_AddGCRef(js, &boot_env_ref);
boot_env_ref.val = JS_NewObject(js);
JSValue btmp;
btmp = js_core_os_use(js);
btmp = js_core_internal_os_use(js);
JS_SetPropertyStr(js, boot_env_ref.val, "os", btmp);
if (core_path) {
btmp = JS_NewString(js, core_path);
@@ -346,7 +347,7 @@ void script_startup(cell_rt *prt)
JS_AddGCRef(js, &env_ref);
env_ref.val = JS_NewObject(js);
JSValue tmp;
tmp = js_core_os_use(js);
tmp = js_core_internal_os_use(js);
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
tmp = js_core_json_use(js);
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
@@ -523,6 +524,9 @@ int cell_init(int argc, char **argv)
} else if (strcmp(argv[arg_start], "--native") == 0) {
native_mode = 1;
arg_start++;
} else if (strcmp(argv[arg_start], "--no-warn") == 0) {
warn_mode = 0;
arg_start++;
} else {
break;
}
@@ -608,7 +612,7 @@ int cell_init(int argc, char **argv)
JS_AddGCRef(ctx, &boot_env_ref);
boot_env_ref.val = JS_NewObject(ctx);
JSValue btmp;
btmp = js_core_os_use(ctx);
btmp = js_core_internal_os_use(ctx);
JS_SetPropertyStr(ctx, boot_env_ref.val, "os", btmp);
btmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
@@ -652,7 +656,7 @@ int cell_init(int argc, char **argv)
JS_AddGCRef(ctx, &env_ref);
env_ref.val = JS_NewObject(ctx);
JSValue tmp;
tmp = js_core_os_use(ctx);
tmp = js_core_internal_os_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
tmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
@@ -661,11 +665,14 @@ int cell_init(int argc, char **argv)
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
tmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
if (native_mode) {
if (native_mode || !warn_mode) {
JSGCRef init_ref;
JS_AddGCRef(ctx, &init_ref);
init_ref.val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
if (native_mode)
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
if (!warn_mode)
JS_SetPropertyStr(ctx, init_ref.val, "no_warn", JS_NewBool(ctx, 1));
JS_SetPropertyStr(ctx, env_ref.val, "init", init_ref.val);
JS_DeleteGCRef(ctx, &init_ref);
} else {

View File

@@ -1197,6 +1197,8 @@ int js_string_compare_value_nocase (JSContext *ctx, JSValue op1, JSValue op2);
JSValue js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop);
int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key);
JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key);
int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue val);
void *js_realloc_rt (void *ptr, size_t size);
char *js_strdup_rt (const char *str);
JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2);

File diff suppressed because it is too large Load Diff

50
src/cell_math.c Normal file
View File

@@ -0,0 +1,50 @@
#include "cell_math.h"
#include <math.h>
JSValue js_math_e (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double power = 1.0;
if (argc > 0 && !JS_IsNull (argv[0])) {
if (JS_ToFloat64 (ctx, &power, argv[0]) < 0) return JS_EXCEPTION;
}
return JS_NewFloat64 (ctx, exp (power));
}
JSValue js_math_ln (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, log (x));
}
JSValue js_math_log10 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, log10 (x));
}
JSValue js_math_log2 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, log2 (x));
}
JSValue js_math_power (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x, y;
if (argc < 2) return JS_NULL;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
if (JS_ToFloat64 (ctx, &y, argv[1]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, pow (x, y));
}
JSValue js_math_root (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x, n;
if (argc < 2) return JS_NULL;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
if (JS_ToFloat64 (ctx, &n, argv[1]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, pow (x, 1.0 / n));
}
JSValue js_math_sqrt (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, sqrt (x));
}

14
src/cell_math.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef CELL_MATH_H
#define CELL_MATH_H
#include "cell.h"
JSValue js_math_e (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_math_ln (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_math_log10 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_math_log2 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_math_power (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_math_root (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_math_sqrt (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
#endif

View File

View File

@@ -6,6 +6,7 @@
// cell streamline --ir <file> Human-readable IR
// cell streamline --check <file> Warnings only (e.g. high slot count)
// cell streamline --types <file> Optimized IR with type annotations
// cell streamline --diagnose <file> Run diagnostics (type errors/warnings)
var fd = use("fd")
var json = use("json")
@@ -15,8 +16,11 @@ var show_stats = false
var show_ir = false
var show_check = false
var show_types = false
var show_diagnose = false
var filename = null
var i = 0
var di = 0
var diag = null
for (i = 0; i < length(args); i++) {
if (args[i] == '--stats') {
@@ -27,8 +31,10 @@ for (i = 0; i < length(args); i++) {
show_check = true
} else if (args[i] == '--types') {
show_types = true
} else if (args[i] == '--diagnose') {
show_diagnose = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell streamline [--stats] [--ir] [--check] [--types] <file>")
log.console("Usage: cell streamline [--stats] [--ir] [--check] [--types] [--diagnose] <file>")
$stop()
} else if (!starts_with(args[i], '-')) {
filename = args[i]
@@ -36,7 +42,7 @@ for (i = 0; i < length(args); i++) {
}
if (!filename) {
print("usage: cell streamline [--stats] [--ir] [--check] [--types] <file>")
print("usage: cell streamline [--stats] [--ir] [--check] [--types] [--diagnose] <file>")
$stop()
}
@@ -46,10 +52,19 @@ if (show_stats) {
before = json.decode(json.encode(shop.mcode_file(filename)))
}
var optimized = shop.compile_file(filename)
// For --diagnose, compile with _warn enabled to get diagnostics
var optimized = null
var compiled = null
if (show_diagnose) {
compiled = shop.mcode_file(filename)
compiled._warn = true
optimized = use('streamline')(compiled)
} else {
optimized = shop.compile_file(filename)
}
// If no flags, default to full JSON output
if (!show_stats && !show_ir && !show_check && !show_types) {
if (!show_stats && !show_ir && !show_check && !show_types && !show_diagnose) {
print(json.encode(optimized, true))
$stop()
}
@@ -343,4 +358,18 @@ if (show_stats) {
print('---')
}
if (show_diagnose) {
if (optimized._diagnostics != null && length(optimized._diagnostics) > 0) {
di = 0
while (di < length(optimized._diagnostics)) {
diag = optimized._diagnostics[di]
print(`${diag.file}:${text(diag.line)}:${text(diag.col)}: ${diag.severity}: ${diag.message}`)
di = di + 1
}
print(`\n${text(length(optimized._diagnostics))} diagnostic(s)`)
} else {
print("No diagnostics.")
}
}
$stop()

View File

@@ -1829,6 +1829,236 @@ var streamline = function(ir, log) {
return null
}
// =========================================================
// Pass: diagnose_function — emit diagnostics from type info
// Runs after dead code elimination on surviving instructions.
// =========================================================
var diagnose_function = function(func, ctx, ir) {
var param_types = ctx.param_types
var write_types = ctx.write_types
var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0
var num_instr = 0
var base_types = null
var cur_types = null
var i = 0
var j = 0
var instr = null
var op = null
var n = 0
var line = 0
var col = 0
var known = null
var filename = ir.filename != null ? ir.filename : "<unknown>"
var frame_callee = {}
var frame_argc = {}
var callee_slot = null
var obj_type = null
var key_type = null
var module_slots = {}
var slot_arity = {}
var ms_i = 0
var ms = null
var exp_info = null
var f_slot_key = null
var cs = null
var argc = null
var known_arity = null
var load_field_null = false
// Build module_slots map from ir._module_summaries
if (ir._module_summaries != null) {
ms_i = 0
while (ms_i < length(ir._module_summaries)) {
ms = ir._module_summaries[ms_i]
module_slots[text(ms.slot)] = ms.summary
ms_i = ms_i + 1
}
}
if (instructions == null || length(instructions) == 0) return null
num_instr = length(instructions)
// Pre-compute base types from params + write-invariant types
base_types = array(func.nr_slots)
j = 1
while (j <= nr_args) {
if (param_types != null && param_types[j] != null) {
base_types[j] = param_types[j]
}
j = j + 1
}
if (write_types != null) {
j = 0
while (j < length(write_types)) {
if (write_types[j] != null) {
base_types[j] = write_types[j]
}
j = j + 1
}
}
cur_types = array(base_types)
var emit = function(severity, line, col, message) {
ir._diagnostics[] = {
severity: severity,
file: filename,
line: line,
col: col,
message: message
}
}
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_text(instr)) {
// Label — reset types to base
if (!starts_with(instr, "_nop_")) {
cur_types = array(base_types)
}
i = i + 1
continue
}
if (!is_array(instr)) {
i = i + 1
continue
}
op = instr[0]
n = length(instr)
line = instr[n - 2]
col = instr[n - 1]
// Track frame/invoke correlation
if (op == "frame" || op == "goframe") {
frame_callee[text(instr[1])] = instr[2]
if (n > 4) {
frame_argc[text(instr[1])] = instr[3]
}
}
// --- Error checks (proven to always disrupt) ---
if (op == "frame" || op == "goframe") {
callee_slot = instr[2]
known = cur_types[callee_slot]
if (known == T_NULL) {
emit("error", line, col, "invoking null — will always disrupt")
} else if (known != null && known != T_UNKNOWN && known != T_FUNCTION && known != T_RECORD) {
emit("error", line, col, `invoking ${known} — will always disrupt`)
}
}
if (op == "invoke" || op == "tail_invoke") {
f_slot_key = text(instr[1])
cs = frame_callee[f_slot_key]
argc = frame_argc[f_slot_key]
if (cs != null && argc != null) {
known_arity = slot_arity[text(cs)]
if (known_arity != null) {
if (argc > known_arity) {
emit("error", line, col, `function expects ${text(known_arity)} args, called with ${text(argc)}`)
} else if (argc < known_arity) {
emit("warning", line, col, `function expects ${text(known_arity)} args, called with ${text(argc)}`)
}
}
}
}
if (op == "store_field") {
obj_type = cur_types[instr[1]]
if (obj_type == T_TEXT) {
emit("error", line, col, "storing property on text — text is immutable")
} else if (obj_type == T_ARRAY) {
emit("error", line, col, "storing named property on array — use index or push")
}
}
if (op == "store_index") {
obj_type = cur_types[instr[1]]
if (obj_type == T_TEXT) {
emit("error", line, col, "storing index on text — text is immutable")
} else if (obj_type == T_RECORD) {
emit("error", line, col, "storing numeric index on record — use text key")
}
}
if (op == "store_dynamic") {
obj_type = cur_types[instr[1]]
if (obj_type == T_TEXT) {
emit("error", line, col, "storing on text — text is immutable")
}
}
if (op == "push") {
obj_type = cur_types[instr[1]]
if (obj_type != null && obj_type != T_UNKNOWN && obj_type != T_ARRAY) {
emit("error", line, col, `push on ${obj_type} — only arrays support push`)
}
}
// Note: arithmetic (add/subtract/etc), bitwise, and concat ops are NOT
// checked here because the mcode generator emits type-dispatch guards
// before these instructions. The guards ensure correct types at runtime.
// --- Warning checks (likely bug) ---
load_field_null = false
if (op == "load_field") {
obj_type = cur_types[instr[2]]
if (obj_type == T_ARRAY) {
emit("warning", line, col, "named property access on array — always returns null")
load_field_null = true
} else if (obj_type == T_TEXT) {
emit("warning", line, col, "named property access on text — always returns null")
load_field_null = true
}
// Cross-module: check if obj is a module with known exports
ms = module_slots[text(instr[2])]
if (ms != null && ms.exports != null && is_text(instr[3])) {
exp_info = ms.exports[instr[3]]
if (exp_info == null) {
emit("warning", line, col, `module does not export '${instr[3]}'`)
} else if (exp_info.type == "function") {
cur_types[instr[1]] = T_FUNCTION
slot_arity[text(instr[1])] = exp_info.arity
}
}
}
if (op == "load_dynamic") {
obj_type = cur_types[instr[2]]
key_type = cur_types[instr[3]]
if (obj_type == T_ARRAY && key_type == T_TEXT) {
emit("warning", line, col, "text key on array — always returns null")
}
if (obj_type == T_TEXT && key_type != null && key_type != T_UNKNOWN && key_type != T_INT) {
emit("warning", line, col, `${key_type} key on text — requires integer index`)
}
if (obj_type == T_RECORD && key_type != null && key_type != T_UNKNOWN && key_type != T_TEXT) {
emit("warning", line, col, `${key_type} key on record — requires text key`)
}
}
// Update types for this instruction
track_types(cur_types, instr)
// Override: load_field on array/text always returns null
if (load_field_null) {
cur_types[instr[1]] = T_NULL
}
i = i + 1
}
return null
}
// =========================================================
// Compose all passes
// =========================================================
@@ -1905,6 +2135,9 @@ var streamline = function(ir, log) {
}
run_cycle("")
if (ir._warn) {
diagnose_function(func, {param_types: param_types, write_types: write_types}, ir)
}
return null
}
@@ -1914,6 +2147,10 @@ var streamline = function(ir, log) {
// eliminator to mis-optimize comparisons on closure-written variables.
mark_closure_writes(ir)
if (ir._warn) {
ir._diagnostics = []
}
// Process main function
if (ir.main != null) {
optimize_function(ir.main, log)

View File

@@ -19,7 +19,7 @@ var gc_after_each_test = false
var verify_ir = false
var diff_mode = false
var os_ref = use('os')
var os_ref = use('internal/os')
var analyze = os_ref.analyze
var run_ast_fn = os_ref.run_ast_fn
var run_ast_noopt_fn = os_ref.run_ast_noopt_fn

View File

@@ -1,5 +1,5 @@
var Blob = use('blob');
var os = use('os');
var os = use('internal/os');
function assert(condition, message) {
if (!condition) disrupt

129
tests/compile.cm Normal file
View File

@@ -0,0 +1,129 @@
// Compile-time diagnostics tests — verify the type checker catches errors
var fd = use('fd')
var shop = use('internal/shop')
var streamline = use('streamline')
var tmpfile = "/tmp/_cell_compile_test.cm"
function write_source(src) {
fd.slurpwrite(tmpfile, stone(blob(src)))
}
function get_diagnostics(src) {
write_source(src)
var compiled = shop.mcode_file(tmpfile)
compiled._warn = true
var optimized = streamline(compiled)
if (optimized._diagnostics == null) return []
return optimized._diagnostics
}
function has_diagnostic(diags, severity, pattern) {
var i = 0
while (i < length(diags)) {
if (diags[i].severity == severity && search(diags[i].message, pattern) != null) {
return true
}
i = i + 1
}
return false
}
function expect(diags, severity, pattern) {
if (!has_diagnostic(diags, severity, pattern)) {
return `expected ${severity} matching '${pattern}', got ${text(length(diags))} diagnostic(s)`
}
return null
}
function expect_clean(diags) {
if (length(diags) > 0) {
return `expected no diagnostics, got ${text(length(diags))}`
}
return null
}
return {
// === Store errors ===
test_store_field_on_array: function() {
var d = get_diagnostics("var a = []\na[\"x\"] = 1")
return expect(d, "error", "storing named property on array")
},
test_store_index_on_record: function() {
var d = get_diagnostics("var a = {}\na[1] = 1")
return expect(d, "error", "storing numeric index on record")
},
test_store_field_on_text: function() {
var d = get_diagnostics("var s = \"hello\"\ns.x = 1")
return expect(d, "error", "storing property on text")
},
test_store_index_on_text: function() {
var d = get_diagnostics("var s = \"hello\"\ns[0] = 1")
return expect(d, "error", "storing index on text")
},
test_push_on_text: function() {
var d = get_diagnostics("var s = \"hello\"\ns[] = 1")
return expect(d, "error", "push on text")
},
test_push_on_record: function() {
var d = get_diagnostics("var r = {}\nr[] = 1")
return expect(d, "error", "push on record")
},
// === Invoke errors ===
test_invoke_null: function() {
var d = get_diagnostics("var x = null\nx()")
return expect(d, "error", "invoking null")
},
test_invoke_number: function() {
var d = get_diagnostics("var x = 42\nx()")
return expect(d, "error", "invoking")
},
test_invoke_text: function() {
var d = get_diagnostics("var x = \"hello\"\nx()")
return expect(d, "error", "invoking")
},
// === Warnings ===
test_field_on_array_warns: function() {
var d = get_diagnostics("var a = [1, 2]\nvar x = a.name")
return expect(d, "warning", "named property access on array")
},
test_field_on_text_warns: function() {
var d = get_diagnostics("var s = \"hello\"\nvar x = s.name")
return expect(d, "warning", "named property access on text")
},
test_record_key_on_record_warns: function() {
var d = get_diagnostics("var r = {a: 1}\nvar k = {}\nvar x = r[k]")
return expect(d, "warning", "record key on record")
},
// === Clean code produces no diagnostics ===
test_clean_array_ops: function() {
var d = get_diagnostics("var a = [1, 2, 3]\nvar x = a[0]\na[1] = 5\na[] = 4")
return expect_clean(d)
},
test_clean_record_ops: function() {
var d = get_diagnostics("var r = {a: 1, b: 2}\nvar x = r.a\nr.c = 3")
return expect_clean(d)
},
test_clean_function_call: function() {
var d = get_diagnostics("function f(a, b) { return a + b }\nvar x = f(1, 2)")
return expect_clean(d)
}
}

View File

@@ -1,4 +1,4 @@
var kim = use("kim");
var kim = use("internal/kim");
var blob = use('blob')
return {

View File

@@ -1,5 +1,5 @@
var nota = use('nota');
var os = use('os');
var nota = use('internal/nota');
var os = use('internal/os');
var blob = use('blob')
var EPSILON = 1e-12

View File

@@ -1,5 +1,5 @@
var wota = use('wota')
var os = use('os')
var wota = use('internal/wota')
var os = use('internal/os')
var blob = use('blob')
var math = use('math/radians')

View File

@@ -14,7 +14,7 @@
var shop = use('internal/shop')
var build = use('build')
var fd = use('fd')
var os = use('os')
var os = use('internal/os')
var target_pkg = null
var run_build = false

View File

@@ -1927,7 +1927,8 @@ run("array for", function() {
// ============================================================================
run("array string key disrupts", function() {
if (!should_disrupt(function() { var a = []; a["a"] = 1 })) fail("array should not use string as key")
var f = function(a) { a["a"] = 1 }
if (!should_disrupt(function() { f([]) })) fail("array should not use string as key")
})
run("array object key disrupts", function() {
@@ -1947,7 +1948,8 @@ run("array array key disrupts", function() {
})
run("obj number key disrupts", function() {
if (!should_disrupt(function() { var a = {}; a[1] = 1 })) fail("object should not use number as key")
var f = function(a) { a[1] = 1 }
if (!should_disrupt(function() { f({}) })) fail("object should not use number as key")
})
run("obj array key disrupts", function() {