54 Commits

Author SHA1 Message Date
John Alanbrook
6de542f0d0 Merge branch 'mach_suite_fix' into bytecode_cleanup 2026-02-12 12:32:06 -06:00
John Alanbrook
6ba4727119 rm call 2026-02-12 11:58:29 -06:00
John Alanbrook
b771b2b5d8 suite passes now with mcode->mach lowering 2026-02-12 09:40:24 -06:00
John Alanbrook
68fb440502 Merge branch 'mach' into bytecode_cleanup 2026-02-12 07:50:09 -06:00
John Alanbrook
e7a2f16004 mcode to mach 2026-02-12 05:23:33 -06:00
John Alanbrook
3a8a17ab60 mcode->mach 2026-02-12 04:28:14 -06:00
John Alanbrook
8a84be65e1 new path 2026-02-11 14:41:37 -06:00
John Alanbrook
c1910ee1db Merge branch 'mcode2' into mach 2026-02-11 13:16:07 -06:00
John Alanbrook
7036cdf2d1 Merge branch 'mach' into bytecode_cleanup 2026-02-11 13:15:20 -06:00
John Alanbrook
fbeec17ce5 simplifications 2026-02-11 13:15:04 -06:00
John Alanbrook
2c55ae8cb2 quiesence exit 2026-02-11 11:50:29 -06:00
John Alanbrook
259bc139fc rm stack usage 2026-02-11 10:17:55 -06:00
John Alanbrook
a252412eca removal of old code 2026-02-11 09:47:30 -06:00
John Alanbrook
b327e16463 rm unused functions 2026-02-11 09:09:40 -06:00
John Alanbrook
da6f096a56 qbe rt 2026-02-10 20:28:51 -06:00
John Alanbrook
1320ef9f47 Merge branch 'mcode2' into mach 2026-02-10 19:04:35 -06:00
John Alanbrook
ed4a5474d5 Merge branch 'mach' into mcode2 2026-02-10 19:04:22 -06:00
John Alanbrook
f52dd80d52 fix compile error 2026-02-10 19:02:42 -06:00
John Alanbrook
504e268b9d run native modules 2026-02-10 18:52:11 -06:00
John Alanbrook
0d47002167 add compile script 2026-02-10 18:35:18 -06:00
John Alanbrook
b65db63447 remove vm_test, update test harness 2026-02-10 17:52:57 -06:00
John Alanbrook
c1ccff5437 fix >256 object literal error 2026-02-10 17:42:58 -06:00
John Alanbrook
2f681fa366 output for parser stages and c runtime doc 2026-02-10 17:38:15 -06:00
John Alanbrook
682b1cf9cf Merge branch 'pitweb' into mcode2 2026-02-10 17:29:03 -06:00
John Alanbrook
ddf3fc1c77 add object literal test 2026-02-10 17:28:59 -06:00
John Alanbrook
f1a5072ff2 fix increment operators on objects 2026-02-10 17:17:36 -06:00
John Alanbrook
f44fb502be string literal error 2026-02-10 17:02:22 -06:00
John Alanbrook
d75ce916d7 compile optimization 2026-02-10 16:37:11 -06:00
John Alanbrook
fe5dc6ecc9 fix fd.c bugs 2026-02-10 14:21:49 -06:00
John Alanbrook
54673e4a04 better disrupt logging; actor exit on crash 2026-02-10 12:38:06 -06:00
John Alanbrook
0d8b5cfb04 bootstrap loads engine 2026-02-10 12:13:18 -06:00
John Alanbrook
3d71f4a363 Merge branch 'mach' into pitweb 2026-02-10 11:15:44 -06:00
John Alanbrook
4deb0e2577 new syntax for internals 2026-02-10 11:03:01 -06:00
John Alanbrook
67b96e1627 add test for multiple declaration 2026-02-10 10:39:23 -06:00
John Alanbrook
4e5f1d8faa fix labeled loops, do-while, shorthand property syntax, and added more tests 2026-02-10 10:32:54 -06:00
John Alanbrook
bd577712d9 fix function shorthand default params 2026-02-10 10:13:46 -06:00
John Alanbrook
6df3b741cf add runtime warnings for stale files 2026-02-10 10:05:27 -06:00
John Alanbrook
178837b88d bootstrap 2026-02-10 09:53:41 -06:00
John Alanbrook
120ce9d30c Merge branch 'mcode2' into pitweb 2026-02-10 09:23:30 -06:00
John Alanbrook
58f185b379 fix merge 2026-02-10 09:21:33 -06:00
John Alanbrook
f7b5252044 core flag 2026-02-10 09:21:21 -06:00
John Alanbrook
ded5f7d74b cell shop env var 2026-02-10 09:13:10 -06:00
John Alanbrook
fe6033d6cb deploy website script 2026-02-10 08:12:51 -06:00
John Alanbrook
60e61eef76 scheduler starts 2026-02-10 08:12:42 -06:00
John Alanbrook
ad863fb89b postfix/prefix operators handled correctly 2026-02-10 08:12:27 -06:00
John Alanbrook
96f8157039 Merge branch 'mach' into mcode2 2026-02-10 07:38:35 -06:00
John Alanbrook
c4ff0bc109 intrinsics rewritten without ++, --, etc 2026-02-10 07:19:45 -06:00
John Alanbrook
877250b1d8 decomposed mcode 2026-02-10 07:12:27 -06:00
John Alanbrook
747227de40 better parse errors 2026-02-10 06:51:26 -06:00
John Alanbrook
3f7e34cd7a more useful parse errors 2026-02-10 06:08:15 -06:00
John Alanbrook
cef5c50169 add is_letter intrinsic 2026-02-10 06:00:47 -06:00
John Alanbrook
0428424ec7 Merge branch 'mach' into mcode2 2026-02-10 05:53:51 -06:00
John Alanbrook
78e64c5067 optimize parse 2026-02-10 05:53:49 -06:00
John Alanbrook
ff11c49c39 optimize tokenize 2026-02-10 05:52:19 -06:00
183 changed files with 10003 additions and 8586 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.mach binary merge=ours

View File

@@ -5,10 +5,16 @@
# or manually build with meson once. # or manually build with meson once.
# #
# The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core # The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core
#
# See BUILDING.md for details on the bootstrap process and .mach files.
CELL_SHOP = $(HOME)/.cell CELL_SHOP = $(HOME)/.cell
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
# .cm sources that compile to .mach bytecode
MACH_SOURCES = tokenize.cm parse.cm fold.cm mcode.cm \
internal/bootstrap.cm internal/engine.cm
maker: install maker: install
makecell: makecell:
@@ -16,7 +22,7 @@ makecell:
cp cell /opt/homebrew/bin/ cp cell /opt/homebrew/bin/
# Install core: symlink this directory to ~/.cell/core # Install core: symlink this directory to ~/.cell/core
install: bootstrap $(CELL_SHOP) install: bootstrap .mach.stamp $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE_PACKAGE)" @echo "Linking cell core to $(CELL_CORE_PACKAGE)"
rm -rf $(CELL_CORE_PACKAGE) rm -rf $(CELL_CORE_PACKAGE)
ln -s $(PWD) $(CELL_CORE_PACKAGE) ln -s $(PWD) $(CELL_CORE_PACKAGE)
@@ -40,6 +46,16 @@ libcell_runtime.dylib: $(CELL_SHOP)/build/dynamic
cell_main: source/main.c libcell_runtime.dylib cell_main: source/main.c libcell_runtime.dylib
cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib
# Regenerate .mach bytecode when any .cm source changes
.mach.stamp: $(MACH_SOURCES)
./cell --core . regen.cm
@touch $@
# Force-regenerate all .mach bytecode files
regen:
./cell --core . regen.cm
@touch .mach.stamp
# Create the cell shop directories # Create the cell shop directories
$(CELL_SHOP): $(CELL_SHOP):
mkdir -p $(CELL_SHOP) mkdir -p $(CELL_SHOP)
@@ -68,7 +84,7 @@ bootstrap:
# Clean build artifacts # Clean build artifacts
clean: clean:
rm -rf $(CELL_SHOP)/build build_bootstrap rm -rf $(CELL_SHOP)/build build_bootstrap
rm -f cell cell_main libcell_runtime.dylib rm -f cell cell_main libcell_runtime.dylib .mach.stamp
# Ensure dynamic build directory exists # Ensure dynamic build directory exists
$(CELL_SHOP)/build/dynamic: $(CELL_SHOP) $(CELL_SHOP)/build/dynamic: $(CELL_SHOP)
@@ -79,4 +95,4 @@ meson:
meson setup build_dbg -Dbuildtype=debugoptimized meson setup build_dbg -Dbuildtype=debugoptimized
meson install -C build_dbg meson install -C build_dbg
.PHONY: cell static bootstrap clean meson install .PHONY: cell static bootstrap clean meson install regen

View File

@@ -319,7 +319,7 @@ JSValue js_reader_list(JSContext *js, JSValue self, int argc, JSValue *argv)
JS_FreeValue(js, arr); JS_FreeValue(js, arr);
return filename; return filename;
} }
JS_SetPropertyUint32(js, arr, arr_index++, filename); JS_SetPropertyNumber(js, arr, arr_index++, filename);
} }
return arr; return arr;

View File

@@ -12,7 +12,8 @@ var files = [
{src: "parse.cm", name: "parse", out: "parse.mach"}, {src: "parse.cm", name: "parse", out: "parse.mach"},
{src: "fold.cm", name: "fold", out: "fold.mach"}, {src: "fold.cm", name: "fold", out: "fold.mach"},
{src: "mcode.cm", name: "mcode", out: "mcode.mach"}, {src: "mcode.cm", name: "mcode", out: "mcode.mach"},
{src: "internal/bootstrap.cm", name: "bootstrap", out: "internal/bootstrap.mach"} {src: "internal/bootstrap.cm", name: "bootstrap", out: "internal/bootstrap.mach"},
{src: "internal/engine.cm", name: "engine", out: "internal/engine.mach"}
] ]
var i = 0 var i = 0

102
compile.ce Normal file
View File

@@ -0,0 +1,102 @@
// compile.ce — compile a .cm module to native .dylib via QBE
//
// Usage:
// cell --core . compile.ce <file.cm>
//
// Produces <file>.dylib in the current directory.
var fd = use('fd')
var os = use('os')
if (length(args) < 1) {
print('usage: cell --core . compile.ce <file.cm>')
return
}
var file = args[0]
var base = file
if (ends_with(base, '.cm')) {
base = text(base, 0, length(base) - 3)
}
var safe = replace(replace(base, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use'
var tmp = '/tmp/qbe_' + safe
var ssa_path = tmp + '.ssa'
var s_path = tmp + '.s'
var o_path = tmp + '.o'
var rt_o_path = '/tmp/qbe_rt.o'
var dylib_path = base + '.dylib'
var cwd = fd.getcwd()
var rc = 0
// Step 1: emit QBE IL
print('emit qbe...')
rc = os.system('cd ' + cwd + ' && ./cell --core . --emit-qbe ' + file + ' > ' + ssa_path)
if (rc != 0) {
print('failed to emit qbe il')
return
}
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper
// Use awk via shell to avoid blob/slurpwrite issues with long strings
print('post-process...')
var awk_cmd = `awk '
need_label && /^[[:space:]]*[^@}]/ && NF > 0 {
print "@_dead_" dead_id; dead_id++; need_label=0
}
/^@/ || /^}/ || NF==0 { need_label=0 }
/^[[:space:]]*ret / || /^[[:space:]]*jmp / { need_label=1; print; next }
{ print }
' ` + ssa_path + ` > ` + tmp + `_fixed.ssa`
rc = os.system(awk_cmd)
if (rc != 0) {
print('post-process failed')
return
}
// Append wrapper function — called as symbol(ctx) by os.dylib_symbol.
// Delegates to cell_rt_module_entry which heap-allocates a frame
// (so closures survive) and calls cell_main.
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
rc = os.system(wrapper_cmd)
if (rc != 0) {
print('wrapper append failed')
return
}
// Step 3: compile QBE IL to assembly
print('qbe compile...')
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa')
if (rc != 0) {
print('qbe compilation failed')
return
}
// Step 4: assemble
print('assemble...')
rc = os.system('cc -c ' + s_path + ' -o ' + o_path)
if (rc != 0) {
print('assembly failed')
return
}
// Step 5: compile runtime stubs (cached — skip if already built)
if (!fd.is_file(rt_o_path)) {
print('compile runtime stubs...')
rc = os.system('cc -c ' + cwd + '/qbe_rt.c -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('runtime stubs compilation failed')
return
}
}
// Step 6: link dylib
print('link...')
rc = os.system('cc -shared -fPIC -undefined dynamic_lookup ' + o_path + ' ' + rt_o_path + ' -o ' + cwd + '/' + dylib_path)
if (rc != 0) {
print('linking failed')
return
}
print('built: ' + dylib_path)

View File

@@ -4,7 +4,7 @@
JSC_CCALL(debug_stack_depth, return number2js(js,js_debugger_stack_depth(js))) JSC_CCALL(debug_stack_depth, return number2js(js,js_debugger_stack_depth(js)))
// Return a backtrace of the current call stack. // Return a backtrace of the current call stack.
JSC_CCALL(debug_build_backtrace, return js_debugger_build_backtrace(js,NULL)) JSC_CCALL(debug_build_backtrace, return js_debugger_build_backtrace(js))
// Return the closure variables for a given function. // Return the closure variables for a given function.
JSC_CCALL(debug_closure_vars, return js_debugger_closure_variables(js,argv[0])) JSC_CCALL(debug_closure_vars, return js_debugger_closure_variables(js,argv[0]))
@@ -21,7 +21,7 @@ JSC_CCALL(debug_local_vars, return js_debugger_local_variables(js, js2number(js,
JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0])) JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0]))
// Return an array of functions in the current backtrace. // Return an array of functions in the current backtrace.
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js,NULL)) JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js))
static const JSCFunctionListEntry js_debug_funcs[] = { static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, stack_depth, 0), MIST_FUNC_DEF(debug, stack_depth, 0),

View File

@@ -11,7 +11,7 @@ type: "docs"
### Variables and Constants ### Variables and Constants
Variables are declared with `var`, constants with `def`. All declarations must be initialized and must appear at the function body level — not inside `if`, `while`, `for`, or bare `{}` blocks. Variables are declared with `var`, constants with `def`. All declarations must be initialized and must appear at the function body level — not inside `if`, `while`, `for`, or `do` blocks.
```javascript ```javascript
var x = 10 var x = 10

296
docs/spec/c-runtime.md Normal file
View File

@@ -0,0 +1,296 @@
---
title: "C Runtime for Native Code"
description: "Minimum C runtime surface for QBE-generated native code"
---
## Overview
QBE-generated native code calls into a C runtime for anything that touches the heap, dispatches dynamically, or requires GC awareness. The design principle: **native code handles control flow and integer math directly; everything else is a runtime call.**
This document defines the runtime boundary — what must be in C, what QBE handles inline, and how to organize the C code to serve both the mcode interpreter and native code cleanly.
## The Boundary
### What native code does inline (no C calls)
These operations compile to straight QBE instructions with no runtime involvement:
- **Integer arithmetic**: `add`, `sub`, `mul` on NaN-boxed ints (shift right 1, operate, shift left 1)
- **Integer comparisons**: extract int with shift, compare, produce tagged bool
- **Control flow**: jumps, branches, labels, function entry/exit
- **Slot access**: load/store to frame slots via `%fp` + offset
- **NaN-box tagging**: integer tagging (`n << 1`), bool constants (`0x03`/`0x23`), null (`0x07`)
- **Type tests**: `JS_IsInt` (LSB check), `JS_IsNumber`, `JS_IsText`, `JS_IsNull` — these are bit tests on the value, no heap access needed
### What requires a C call
Anything that:
1. **Allocates** (arrays, records, strings, frames, function objects)
2. **Touches the heap** (property get/set, array indexing, closure access)
3. **Dispatches on type at runtime** (dynamic load/store, polymorphic arithmetic)
4. **Calls user functions** (frame setup, argument passing, invocation)
5. **Does string operations** (concatenation, comparison, conversion)
## Runtime Functions
### Tier 1: Essential (must exist for any program to run)
These are called by virtually every QBE program.
#### Intrinsic Lookup
```c
// Look up a built-in function by name. Called once per intrinsic per callsite.
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name);
```
Maps name → C function pointer wrapped in JSValue. This is the primary entry point for all built-in functions (`print`, `text`, `length`, `is_array`, etc). The native code never calls intrinsics directly — it always goes through `get_intrinsic``frame``invoke`.
#### Function Calls
```c
// Allocate a call frame with space for nr_args arguments.
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int nr_args);
// Set argument idx in the frame.
void cell_rt_setarg(JSValue frame, int idx, JSValue val);
// Execute the function. Returns the result.
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame);
```
This is the universal calling convention. Every function call — user functions, intrinsics, methods — goes through frame/setarg/invoke. The frame allocates a `JSFrameRegister` on the GC heap, setarg fills slots, invoke dispatches.
**Tail call variants:**
```c
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int nr_args);
void cell_rt_goinvoke(JSContext *ctx, JSValue frame);
```
Same as frame/invoke but reuse the caller's stack position.
### Tier 2: Property Access (needed by any program using records or arrays)
```c
// Record field by constant name.
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name);
void cell_rt_store_field(JSContext *ctx, JSValue obj, JSValue val, const char *name);
// Array element by integer index.
JSValue cell_rt_load_index(JSContext *ctx, JSValue obj, JSValue idx);
void cell_rt_store_index(JSContext *ctx, JSValue obj, JSValue idx, JSValue val);
// Dynamic — type of key unknown at compile time.
JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key);
void cell_rt_store_dynamic(JSContext *ctx, JSValue obj, JSValue key, JSValue val);
```
The typed variants (`load_field`/`load_index`) skip the key-type dispatch that `load_dynamic` must do. When parse and fold provide type information, QBE emit selects the typed variant and the streamline optimizer can narrow dynamic → typed.
**Implementation**: These are thin wrappers around existing `JS_GetPropertyStr`/`JS_GetPropertyNumber`/`JS_GetProperty` and their `Set` counterparts.
### Tier 3: Closures (needed by programs with nested functions)
```c
// Walk depth levels up the frame chain, read slot.
JSValue cell_rt_get_closure(JSContext *ctx, JSValue fp, int depth, int slot);
// Walk depth levels up, write slot.
void cell_rt_put_closure(JSContext *ctx, JSValue fp, JSValue val, int depth, int slot);
```
Closure variables live in outer frames. `depth` is how many `caller` links to follow; `slot` is the register index in that frame.
### Tier 4: Object Construction (needed by programs creating arrays/records/functions)
```c
// Create a function object from a compiled function index.
// The native code loader must maintain a function table.
JSValue cell_rt_make_function(JSContext *ctx, int fn_id);
```
Array and record literals are currently compiled as intrinsic calls (`array(...)`, direct `{...}` construction) which go through the frame/invoke path. A future optimization could add:
```c
// Fast paths (optional, not yet needed)
JSValue cell_rt_new_array(JSContext *ctx, int len);
JSValue cell_rt_new_record(JSContext *ctx);
```
### Tier 5: Collection Operations
```c
// a[] = val (push) and var v = a[] (pop)
void cell_rt_push(JSContext *ctx, JSValue arr, JSValue val);
JSValue cell_rt_pop(JSContext *ctx, JSValue arr);
```
### Tier 6: Error Handling
```c
// Trigger disruption. Jumps to the disrupt handler or unwinds.
void cell_rt_disrupt(JSContext *ctx);
```
### Tier 7: Miscellaneous
```c
JSValue cell_rt_delete(JSContext *ctx, JSValue obj, JSValue key);
JSValue cell_rt_typeof(JSContext *ctx, JSValue val);
```
### Tier 8: String and Float Helpers (called from QBE inline code, not from qbe_emit)
These are called from the QBE IL that `qbe.cm` generates inline for arithmetic and comparison operations. They're not `cell_rt_` prefixed — they're lower-level:
```c
// Float arithmetic (when operands aren't both ints)
JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_neg(JSContext *ctx, JSValue v);
JSValue qbe_float_inc(JSContext *ctx, JSValue v);
JSValue qbe_float_dec(JSContext *ctx, JSValue v);
// Float comparison (returns C int 0/1 for QBE branching)
int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b);
// Bitwise ops on non-int values (convert to int32 first)
JSValue qbe_bnot(JSContext *ctx, JSValue v);
JSValue qbe_bitwise_and(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_bitwise_or(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b);
// String operations
JSValue JS_ConcatString(JSContext *ctx, JSValue a, JSValue b);
int js_string_compare_value(JSContext *ctx, JSValue a, JSValue b, int eq_only);
JSValue JS_NewString(JSContext *ctx, const char *str);
JSValue __JS_NewFloat64(JSContext *ctx, double d);
int JS_ToBool(JSContext *ctx, JSValue v);
// String/number type tests (inline-able but currently calls)
int JS_IsText(JSValue v);
int JS_IsNumber(JSValue v);
// Tolerant equality (== on mixed types)
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b);
// Text ordering comparisons
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b);
```
## What Exists vs What Needs Writing
### Already exists (in qbe_helpers.c)
All `qbe_float_*`, `qbe_bnot`, `qbe_bitwise_*`, `qbe_shift_*`, `qbe_to_bool` — these are implemented and working.
### Already exists (in runtime.c / quickjs.c) but not yet wrapped
The underlying operations exist but aren't exposed with the `cell_rt_` names:
| Runtime function | Underlying implementation |
|---|---|
| `cell_rt_load_field` | `JS_GetPropertyStr(ctx, obj, name)` |
| `cell_rt_load_index` | `JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(idx))` |
| `cell_rt_load_dynamic` | `JS_GetProperty(ctx, obj, key)` |
| `cell_rt_store_field` | `JS_SetPropertyStr(ctx, obj, name, val)` |
| `cell_rt_store_index` | `JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(idx), val)` |
| `cell_rt_store_dynamic` | `JS_SetProperty(ctx, obj, key, val)` |
| `cell_rt_delete` | `JS_DeleteProperty(ctx, obj, key)` |
| `cell_rt_push` | `JS_ArrayPush(ctx, &arr, val)` |
| `cell_rt_pop` | `JS_ArrayPop(ctx, arr)` |
| `cell_rt_typeof` | type tag switch → `JS_NewString` |
| `cell_rt_disrupt` | `JS_Throw(ctx, ...)` |
| `cell_rt_eq_tol` / `cell_rt_ne_tol` | comparison logic in mcode.c `eq_tol`/`ne_tol` handler |
| `cell_rt_lt_text` etc. | `js_string_compare_value` + wrap result |
### Needs new code
| Runtime function | What's needed |
|---|---|
| `cell_rt_get_intrinsic` | Look up intrinsic by name string, return JSValue function. Currently scattered across `js_cell_intrinsic_get` and the mcode handler. Needs a clean single entry point. |
| `cell_rt_frame` | Allocate `JSFrameRegister`, set function slot, set argc. Exists in mcode.c `frame` handler but not as a callable function. |
| `cell_rt_setarg` | Write to frame slot. Trivial: `frame->slots[idx + 1] = val` (slot 0 is `this`). |
| `cell_rt_invoke` | Call the function in the frame. Needs to dispatch: native C function vs mach bytecode vs mcode. This is the critical piece — it must handle all function types. |
| `cell_rt_goframe` / `cell_rt_goinvoke` | Tail call variants. Similar to frame/invoke but reuse caller frame. |
| `cell_rt_make_function` | Create function object from index. Needs a function table (populated by the native loader). |
| `cell_rt_get_closure` / `cell_rt_put_closure` | Walk frame chain. Exists inline in mcode.c `get`/`put` handlers. |
## Recommended C File Organization
```
source/
cell_runtime.c — NEW: all cell_rt_* functions (the native code API)
qbe_helpers.c — existing: float/bitwise/shift helpers for inline QBE
runtime.c — existing: JS_GetProperty, JS_SetProperty, etc.
quickjs.c — existing: core VM, GC, value representation
mcode.c — existing: mcode interpreter (can delegate to cell_runtime.c)
```
**`cell_runtime.c`** is the single file that defines the native code contract. It should:
1. Include `quickjs-internal.h` for access to value representation and heap types
2. Export all `cell_rt_*` functions with C linkage (no `static`)
3. Keep each function thin — delegate to existing `JS_*` functions where possible
4. Handle GC safety: after any allocation (frame, string, array), callers' frames may have moved
### Implementation Priority
**Phase 1** — Get "hello world" running natively:
- `cell_rt_get_intrinsic` (to find `print` and `text`)
- `cell_rt_frame`, `cell_rt_setarg`, `cell_rt_invoke` (to call them)
- A loader that takes QBE output → assembles → links → calls `cell_main`
**Phase 2** — Variables and arithmetic:
- All property access (`load_field`, `load_index`, `store_*`, `load_dynamic`)
- `cell_rt_make_function`, `cell_rt_get_closure`, `cell_rt_put_closure`
**Phase 3** — Full language:
- `cell_rt_push`, `cell_rt_pop`, `cell_rt_delete`, `cell_rt_typeof`
- `cell_rt_disrupt`
- `cell_rt_goframe`, `cell_rt_goinvoke`
- Text comparison wrappers (`cell_rt_lt_text`, etc.)
- Tolerant equality (`cell_rt_eq_tol`, `cell_rt_ne_tol`)
## Calling Convention
All `cell_rt_*` functions follow the same pattern:
- First argument is always `JSContext *ctx`
- Values are passed/returned as `JSValue` (64-bit, by value)
- Frame pointers are `JSValue` (tagged pointer to `JSFrameRegister`)
- String names are `const char *` (pointer to data section label)
- Integer constants (slot indices, arg counts) are `int` / `long`
Native code maintains `%ctx` (JSContext) and `%fp` (current frame pointer) as persistent values across the function body. All slot reads/writes go through `%fp` + offset.
## What Should NOT Be in the C Runtime
These are handled entirely by QBE-generated code:
- **Integer arithmetic and comparisons** — bit operations on NaN-boxed values
- **Control flow** — branches, loops, labels, jumps
- **Boolean logic** — `and`/`or`/`not` on tagged values
- **Constant loading** — integer constants are immediate, strings are data labels
- **Type guard branches** — the `is_int`/`is_text`/`is_null` checks are inline bit tests; the branch to the float or text path is just a QBE `jnz`
The `qbe.cm` macros already handle all of this. The arithmetic path looks like:
```
check both ints? → yes → inline int add → done
→ no → call qbe_float_add (or JS_ConcatString for text)
```
The C runtime is only called on the slow paths (float, text, dynamic dispatch). The fast path (integer arithmetic, comparisons, branching) is fully native.

View File

@@ -154,3 +154,12 @@ struct JSCodeRegister {
``` ```
The constant pool holds all non-immediate values referenced by `LOADK` instructions: strings, large numbers, and other constants. The constant pool holds all non-immediate values referenced by `LOADK` instructions: strings, large numbers, and other constants.
### Constant Pool Index Overflow
Named property instructions (`LOAD_FIELD`, `STORE_FIELD`, `DELETE`) use the iABC format where the constant pool key index occupies an 8-bit field (max 255). When a function references more than 256 unique property names, the serializer automatically falls back to a two-instruction sequence:
1. `LOADK tmp, key_index` — load the key string into a temporary register (iABx, 16-bit index)
2. `LOAD_DYNAMIC` / `STORE_DYNAMIC` / `DELETEINDEX` — use the register-based variant
This is transparent to the mcode compiler and streamline optimizer.

View File

@@ -10,12 +10,44 @@ Mcode is a JSON-based intermediate representation that can be interpreted direct
## Pipeline ## Pipeline
``` ```
Source → Tokenize → Parse (AST) → Mcode (JSON) → Interpret Source → Tokenize → Parse (AST) → Fold → Mcode (JSON) → Streamline → Mach VM (default)
→ Compile to Mach (planned) → Mcode Interpreter
→ Compile to native (planned) → QBE → Native
``` ```
Mcode is produced by the `JS_Mcode` compiler pass, which emits a cJSON tree. The mcode interpreter walks this tree directly, dispatching on instruction name strings. Mcode is produced by `mcode.cm`, which lowers the folded AST to JSON instruction arrays. The streamline optimizer (`streamline.cm`) then eliminates redundant operations. The result is serialized to binary bytecode by the Mach compiler (`mach.c`), interpreted directly by `mcode.c`, or lowered to QBE IL by `qbe_emit.cm` for native compilation. See [Compilation Pipeline](pipeline.md) for the full overview.
### Function Proxy Decomposition
When the compiler encounters a method call `obj.method(args)`, it emits a branching pattern to handle ƿit's function proxy protocol. An arity-2 function used as a proxy target receives the method name and argument array instead of a normal method call:
```json
["is_proxy", check, obj]
["jump_false", check, "record_path"]
// Proxy path: call obj(name, [args...]) with this=null
["access", name_slot, "method"]
["array", args_arr, N, arg0, arg1, ...]
["null", null_slot]
["frame", f, obj, 2]
["setarg", f, 0, null_slot]
["setarg", f, 1, name_slot]
["setarg", f, 2, args_arr]
["invoke", f, dest]
["jump", "done"]
["LABEL", "record_path"]
["load_field", method, obj, "method"]
["frame", f2, method, N]
["setarg", f2, 0, obj]
["setarg", f2, 1, arg0]
...
["invoke", f2, dest]
["LABEL", "done"]
```
The streamline optimizer can eliminate the dead branch when the type of `obj` is statically known.
## JSMCode Structure ## JSMCode Structure
@@ -44,16 +76,37 @@ struct JSMCode {
## Instruction Format ## Instruction Format
Each instruction is a JSON array. The first element is the instruction name (string), followed by operands: Each instruction is a JSON array. The first element is the instruction name (string), followed by operands (typically `[op, dest, ...args, line, col]`):
```json ```json
["LOADK", 0, 42] ["access", 3, 5, 1, 9]
["ADD", 2, 0, 1] ["load_index", 10, 4, 9, 5, 11]
["JMPFALSE", 3, "else_label"] ["store_dynamic", 4, 11, 12, 6, 3]
["CALL", 0, 2, 1] ["frame", 15, 14, 1, 7, 7]
["setarg", 15, 0, 16, 7, 7]
["invoke", 15, 13, 7, 7]
``` ```
The instruction set mirrors the Mach VM opcodes — same operations, same register semantics, but with string dispatch instead of numeric opcodes. ### Typed Load/Store
Memory operations come in typed variants for optimization:
- `load_index dest, obj, idx` — array element by integer index
- `load_field dest, obj, key` — record property by string key
- `load_dynamic dest, obj, key` — unknown; dispatches at runtime
- `store_index obj, val, idx` — array element store
- `store_field obj, val, key` — record property store
- `store_dynamic obj, val, key` — unknown; dispatches at runtime
The compiler selects the appropriate variant based on `type_tag` and `access_kind` annotations from parse and fold.
### Decomposed Calls
Function calls are split into separate instructions:
- `frame dest, fn, argc` — allocate call frame
- `setarg frame, idx, val` — set argument
- `invoke frame, result` — execute the call
## Labels ## Labels

124
docs/spec/pipeline.md Normal file
View File

@@ -0,0 +1,124 @@
---
title: "Compilation Pipeline"
description: "Overview of the compilation stages and optimizations"
---
## Overview
The compilation pipeline transforms source code through several stages, each adding information or lowering the representation toward execution. All backends share the same path through mcode and streamline. There are three execution backends: the Mach register VM (default), the Mcode interpreter (debug), and native code via QBE (experimental).
```
Source → Tokenize → Parse → Fold → Mcode → Streamline → Mach VM (default)
→ Mcode Interpreter
→ QBE → Native
```
## Stages
### Tokenize (`tokenize.cm`)
Splits source text into tokens. Handles string interpolation by re-tokenizing template literal contents. Produces a token array with position information (line, column).
### Parse (`parse.cm`)
Converts tokens into an AST. Also performs semantic analysis:
- **Scope records**: For each scope (global, function), builds a record mapping variable names to their metadata: `make` (var/def/function/input), `function_nr`, `nr_uses`, `closure` flag, and `level`.
- **Type tags**: When the right-hand side of a `def` is a syntactically obvious type, stamps `type_tag` on the scope record entry. Derivable types: `"integer"`, `"number"`, `"text"`, `"array"`, `"record"`, `"function"`, `"logical"`, `"null"`.
- **Intrinsic resolution**: Names used but not locally bound are recorded in `ast.intrinsics`. Name nodes referencing intrinsics get `intrinsic: true`.
- **Access kind**: Subscript (`[`) nodes get `access_kind`: `"index"` for numeric subscripts, `"field"` for string subscripts, omitted otherwise.
- **Tail position**: Return statements where the expression is a call get `tail: true`.
### Fold (`fold.cm`)
Operates on the AST. Performs constant folding and type analysis:
- **Constant folding**: Evaluates arithmetic on known constants at compile time (e.g., `5 + 10` becomes `15`).
- **Constant propagation**: Tracks `def` bindings whose values are known constants.
- **Type propagation**: Extends `type_tag` through operations. When both operands of an arithmetic op have known types, the result type is known. Propagates type tags to reference sites.
- **Intrinsic specialization**: When an intrinsic call's argument types are known, stamps a `hint` on the call node. For example, `length(x)` where x is a known array gets `hint: "array_length"`. Type checks like `is_array(known_array)` are folded to `true`.
- **Purity marking**: Stamps `pure: true` on expressions with no side effects (literals, name references, arithmetic on pure operands).
- **Dead code elimination**: Removes unreachable branches when conditions are known constants.
### Mcode (`mcode.cm`)
Lowers the AST to a JSON-based intermediate representation with explicit operations. Key design principle: **every type check is an explicit instruction** so downstream optimizers can see and eliminate them.
- **Typed load/store**: Emits `load_index` (array by integer), `load_field` (record by string), or `load_dynamic` (unknown) based on type information from fold.
- **Decomposed calls**: Function calls are split into `frame` (create call frame) + `setarg` (set arguments) + `invoke` (execute call).
- **Intrinsic access**: Intrinsic functions are loaded via `access` with an intrinsic marker rather than global lookup.
See [Mcode IR](mcode.md) for instruction format details.
### Streamline (`streamline.cm`)
Optimizes the Mcode IR. Operates per-function:
- **Redundant instruction elimination**: Removes no-op patterns and redundant moves.
- **Dead code removal**: Eliminates instructions whose results are never used.
- **Type-based narrowing**: When type information is available, narrows `load_dynamic`/`store_dynamic` to typed variants.
### QBE Emit (`qbe_emit.cm`)
Lowers optimized Mcode IR to QBE intermediate language for native code compilation. Each Mcode function becomes a QBE function that calls into the cell runtime (`cell_rt_*` functions) for operations that require the runtime (allocation, intrinsic dispatch, etc.).
String constants are interned in a data section. Integer constants are NaN-boxed inline.
### QBE Macros (`qbe.cm`)
Provides operation implementations as QBE IL templates. Each arithmetic, comparison, and type operation is defined as a function that emits the corresponding QBE instructions, handling type dispatch (integer, float, text paths) with proper guard checks.
## Execution Backends
### Mach VM (default)
Binary 32-bit register VM. The Mach serializer (`mach.c`) converts streamlined mcode JSON into compact 32-bit bytecode with a constant pool. Used for production execution and bootstrapping.
```
./cell script.ce
```
Debug the mach bytecode output:
```
./cell --core . --dump-mach script.ce
```
### Mcode Interpreter
JSON-based interpreter. Used for debugging the compilation pipeline.
```
./cell --mcode script.ce
```
### QBE Native (experimental)
Generates QBE IL that can be compiled to native code.
```
./cell --emit-qbe script.ce > output.ssa
```
## Files
| File | Role |
|------|------|
| `tokenize.cm` | Lexer |
| `parse.cm` | Parser + semantic analysis |
| `fold.cm` | Constant folding + type analysis |
| `mcode.cm` | AST → Mcode IR lowering |
| `streamline.cm` | Mcode IR optimizer |
| `qbe_emit.cm` | Mcode IR → QBE IL emitter |
| `qbe.cm` | QBE IL operation templates |
| `internal/bootstrap.cm` | Pipeline orchestrator |
## Test Files
| File | Tests |
|------|-------|
| `parse_test.ce` | Type tags, access_kind, intrinsic resolution |
| `fold_test.ce` | Type propagation, purity, intrinsic hints |
| `mcode_test.ce` | Typed load/store, decomposed calls |
| `streamline_test.ce` | Optimization counts, IR before/after |
| `qbe_test.ce` | End-to-end QBE IL generation |

20
dump_mcode.cm Normal file
View File

@@ -0,0 +1,20 @@
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode")
var streamline = use("streamline")
var name = args[0]
var src = text(fd.slurp(name))
var tok = tokenize(src, name)
var ast = parse(tok.tokens, src, name, tokenize)
var folded = fold(ast)
var compiled = mcode(folded)
var optimized = streamline(compiled)
var out = json.encode(optimized)
var f = fd.open("/tmp/mcode_dump.json", "w")
fd.write(f, out)
fd.close(f)
print("wrote /tmp/mcode_dump.json")

32
fd.c
View File

@@ -504,7 +504,7 @@ JSC_SCALL(fd_readdir,
ret = JS_NewArray(js); ret = JS_NewArray(js);
do { do {
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
JS_ArrayPush(js, ret,JS_NewString(js, ffd.cFileName)); JS_ArrayPush(js, &ret, JS_NewString(js, ffd.cFileName));
} while (FindNextFile(hFind, &ffd) != 0); } while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind); FindClose(hFind);
} }
@@ -516,7 +516,7 @@ JSC_SCALL(fd_readdir,
ret = JS_NewArray(js); ret = JS_NewArray(js);
while ((dir = readdir(d)) != NULL) { while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
JS_ArrayPush(js, ret, JS_NewString(js, dir->d_name)); JS_ArrayPush(js, &ret, JS_NewString(js, dir->d_name));
} }
closedir(d); closedir(d);
} else { } else {
@@ -565,17 +565,21 @@ JSC_CCALL(fd_slurpwrite,
if (!str) return JS_EXCEPTION; if (!str) return JS_EXCEPTION;
int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644); int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { if (fd < 0) {
ret = JS_ThrowInternalError(js, "open failed for %s: %s", str, strerror(errno));
JS_FreeCString(js, str); JS_FreeCString(js, str);
return JS_ThrowInternalError(js, "open failed for %s: %s", str, strerror(errno)); return ret;
} }
ssize_t written = write(fd, data, len); ssize_t written = write(fd, data, len);
close(fd); close(fd);
JS_FreeCString(js, str); if (written != (ssize_t)len) {
ret = JS_ThrowInternalError(js, "write failed for %s: %s", str, strerror(errno));
JS_FreeCString(js, str);
return ret;
}
if (written != (ssize_t)len) JS_FreeCString(js, str);
return JS_ThrowInternalError(js, "write failed for %s: %s", str, strerror(errno));
return JS_NULL; return JS_NULL;
) )
@@ -598,7 +602,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, ffd.cFileName); strcpy(item_rel, ffd.cFileName);
} }
JS_SetPropertyUint32(js, results, (*result_count)++, JS_NewString(js, item_rel)); JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) { if (recurse) {
struct stat st; struct stat st;
@@ -623,7 +627,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, dir->d_name); strcpy(item_rel, dir->d_name);
} }
JS_SetPropertyUint32(js, results, (*result_count)++, JS_NewString(js, item_rel)); JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) { if (recurse) {
struct stat st; struct stat st;
@@ -664,17 +668,21 @@ JSC_CCALL(fd_realpath,
#ifdef _WIN32 #ifdef _WIN32
char resolved[PATH_MAX]; char resolved[PATH_MAX];
DWORD len = GetFullPathNameA(path, PATH_MAX, resolved, NULL); DWORD len = GetFullPathNameA(path, PATH_MAX, resolved, NULL);
JS_FreeCString(js, path);
if (len == 0 || len >= PATH_MAX) { if (len == 0 || len >= PATH_MAX) {
return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno)); JSValue err = JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
JS_FreeCString(js, path);
return err;
} }
JS_FreeCString(js, path);
return JS_NewString(js, resolved); return JS_NewString(js, resolved);
#else #else
char *resolved = realpath(path, NULL); char *resolved = realpath(path, NULL);
JS_FreeCString(js, path);
if (!resolved) { if (!resolved) {
return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno)); JSValue err = JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
JS_FreeCString(js, path);
return err;
} }
JS_FreeCString(js, path);
JSValue result = JS_NewString(js, resolved); JSValue result = JS_NewString(js, resolved);
free(resolved); free(resolved);
return result; return result;

19
fd.cm
View File

@@ -1,4 +1,4 @@
var fd = this var fd = native
var wildstar = use('wildstar') var wildstar = use('wildstar')
function last_pos(str, sep) { function last_pos(str, sep) {
@@ -12,11 +12,11 @@ function last_pos(str, sep) {
// Helper to join paths // Helper to join paths
function join_paths(base, rel) { function join_paths(base, rel) {
base = replace(base, /\/+$/, "") var b = replace(base, /\/+$/, "")
rel = replace(rel, /^\/+/, "") var r = replace(rel, /^\/+/, "")
if (!base) return rel if (!b) return r
if (!rel) return base if (!r) return b
return base + "/" + rel return b + "/" + r
} }
fd.join_paths = join_paths fd.join_paths = join_paths
@@ -39,7 +39,8 @@ fd.stem = function stem(path) {
} }
fd.globfs = function(globs, dir) { fd.globfs = function(globs, dir) {
if (dir == null) dir = "." var _dir = dir
if (_dir == null) _dir = "."
var results = [] var results = []
function check_neg(path) { function check_neg(path) {
@@ -88,9 +89,9 @@ fd.globfs = function(globs, dir) {
}); });
} }
var st = fd.stat(dir) var st = fd.stat(_dir)
if (st && st.isDirectory) { if (st && st.isDirectory) {
visit(dir, "") visit(_dir, "")
} }
return results return results

View File

@@ -324,7 +324,7 @@ static void listfiles_cb(const char *path, void *userdata) {
// Playdate listfiles returns just the name, but sometimes with slash for dir? // Playdate listfiles returns just the name, but sometimes with slash for dir?
// Docs say "names of files". // Docs say "names of files".
JS_SetPropertyUint32(ctx->js, ctx->array, ctx->index++, JS_NewString(ctx->js, path)); JS_SetPropertyNumber(ctx->js, ctx->array, ctx->index++, JS_NewString(ctx->js, path));
} }
JSC_SCALL(fd_readdir, JSC_SCALL(fd_readdir,
@@ -427,7 +427,7 @@ static void enum_cb(const char *name, void *userdata) {
strcpy(item_rel, name); strcpy(item_rel, name);
} }
JS_SetPropertyUint32(ctx->js, ctx->results, (*ctx->count)++, JS_NewString(ctx->js, item_rel)); JS_SetPropertyNumber(ctx->js, ctx->results, (*ctx->count)++, JS_NewString(ctx->js, item_rel));
if (ctx->recurse) { if (ctx->recurse) {
// Check if directory // Check if directory

47
fold.cm
View File

@@ -158,6 +158,7 @@ var fold = function(ast) {
var name = null var name = null
var sv = null var sv = null
var item = null var item = null
var rhs_target = null
while (i < length(stmts)) { while (i < length(stmts)) {
stmt = stmts[i] stmt = stmts[i]
kind = stmt.kind kind = stmt.kind
@@ -169,6 +170,19 @@ var fold = function(ast) {
register_const(fn_nr, name, stmt.right) register_const(fn_nr, name, stmt.right)
} }
} }
if (name != null && stmt.right != null && stmt.right.kind == "(") {
rhs_target = stmt.right.expression
if (rhs_target != null && rhs_target.intrinsic == true) {
sv = scope_var(fn_nr, name)
if (sv != null && sv.type_tag == null) {
if (rhs_target.name == "array") sv.type_tag = "array"
else if (rhs_target.name == "record") sv.type_tag = "record"
else if (rhs_target.name == "text") sv.type_tag = "text"
else if (rhs_target.name == "number") sv.type_tag = "number"
else if (rhs_target.name == "blob") sv.type_tag = "blob"
}
}
}
} else if (kind == "function") { } else if (kind == "function") {
name = stmt.name name = stmt.name
if (name != null && stmt.arity != null) { if (name != null && stmt.arity != null) {
@@ -320,6 +334,8 @@ var fold = function(ast) {
var ar = null var ar = null
var akey = null var akey = null
var tv = null var tv = null
var att = null
var arg = null
// Recurse into children first (bottom-up) // Recurse into children first (bottom-up)
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" ||
@@ -385,6 +401,10 @@ var fold = function(ast) {
return copy_loc(expr, {kind: lit.kind, value: lit.value, number: lit.number}) return copy_loc(expr, {kind: lit.kind, value: lit.value, number: lit.number})
} }
} }
sv = scope_var(fn_nr, expr.name)
if (sv != null && sv.type_tag != null) {
expr.type_tag = sv.type_tag
}
return expr return expr
} }
@@ -497,7 +517,7 @@ var fold = function(ast) {
return expr return expr
} }
// Call: stamp arity // Call: stamp arity and fold intrinsic type checks
if (k == "(") { if (k == "(") {
target = expr.expression target = expr.expression
if (target != null && target.kind == "name" && target.level == 0) { if (target != null && target.kind == "name" && target.level == 0) {
@@ -506,6 +526,30 @@ var fold = function(ast) {
if (fn_arities[akey] != null) ar = fn_arities[akey][target.name] if (fn_arities[akey] != null) ar = fn_arities[akey][target.name]
if (ar != null) expr.arity = ar if (ar != null) expr.arity = ar
} }
if (target != null && target.intrinsic == true && length(expr.list) == 1) {
arg = expr.list[0]
att = null
if (arg.type_tag != null) {
att = arg.type_tag
} else if (arg.kind == "name" && arg.level == 0) {
sv = scope_var(fn_nr, arg.name)
if (sv != null) att = sv.type_tag
}
if (att != null) {
if (target.name == "is_array") return make_bool(att == "array", expr)
if (target.name == "is_text") return make_bool(att == "text", expr)
if (target.name == "is_number") return make_bool(att == "number" || att == "integer", expr)
if (target.name == "is_integer") return make_bool(att == "integer", expr)
if (target.name == "is_function") return make_bool(att == "function", expr)
if (target.name == "is_logical") return make_bool(att == "logical", expr)
if (target.name == "is_null") return make_bool(att == "null", expr)
if (target.name == "is_object") return make_bool(att == "record", expr)
if (target.name == "length") {
if (att == "array") expr.hint = "array_length"
else if (att == "text") expr.hint = "text_length"
}
}
}
return expr return expr
} }
@@ -525,6 +569,7 @@ var fold = function(ast) {
if (k == "var" || k == "def") { if (k == "var" || k == "def") {
stmt.right = fold_expr(stmt.right, fn_nr) stmt.right = fold_expr(stmt.right, fn_nr)
if (is_pure(stmt.right)) stmt.pure = true
return stmt return stmt
} }
if (k == "var_list") { if (k == "var_list") {

BIN
fold.mach

Binary file not shown.

BIN
fold_new.mach Normal file

Binary file not shown.

View File

@@ -1,4 +1,6 @@
// Hidden vars (os, args, core_path, use_mcode) come from env // Hidden vars come from env:
// CLI mode (cell_init): os, args, core_path, shop_path, emit_qbe, dump_mach
// Actor spawn (script_startup): os, json, nota, wota, actorsym, init, core_path, shop_path
// args[0] = script name, args[1..] = user args // args[0] = script name, args[1..] = user args
var load_internal = os.load_internal var load_internal = os.load_internal
function use_embed(name) { function use_embed(name) {
@@ -22,28 +24,69 @@ function use_basic(path) {
return result return result
} }
// Load a module from .mach bytecode, falling back to .ast.json // Load a module from .mach bytecode (bootstrap modules have no source fallback)
function boot_load(name, env) { function boot_load(name, env) {
var mach_path = name + ".mach" var mach_path = core_path + '/' + name + ".mach"
var data = null var data = null
if (fd.is_file(mach_path)) { if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path) data = fd.slurp(mach_path)
return mach_load(data, env) return mach_load(data, env)
} }
data = text(fd.slurp(name + ".ast.json")) print("error: missing bootstrap bytecode: " + mach_path + "\n")
return mach_eval_ast(name, data, env) disrupt
} }
var boot_env = {use: use_basic} var boot_env = {use: use_basic}
var tokenize_mod = boot_load("tokenize", boot_env) var tokenize_mod = boot_load("tokenize", boot_env)
var parse_mod = boot_load("parse", boot_env) var parse_mod = boot_load("parse", boot_env)
var fold_mod = boot_load("fold", boot_env) var fold_mod = boot_load("fold", boot_env)
use_cache['tokenize'] = tokenize_mod
use_cache['parse'] = parse_mod
use_cache['fold'] = fold_mod
// Optionally load mcode compiler module // Always load mcode compiler module
var mcode_mod = null var mcode_mod = boot_load("mcode", boot_env)
if (use_mcode) { use_cache['mcode'] = mcode_mod
mcode_mod = boot_load("mcode", boot_env) var streamline_mod = null
var qbe_emit_mod = null
// Warn if any .cm source is newer than its .mach bytecode
function check_mach_stale() {
var pairs = [
["tokenize.cm", "tokenize.mach"],
["parse.cm", "parse.mach"],
["fold.cm", "fold.mach"],
["mcode.cm", "mcode.mach"],
["streamline.cm", "streamline.mach"],
["qbe.cm", "qbe.mach"],
["qbe_emit.cm", "qbe_emit.mach"],
["internal/bootstrap.cm", "internal/bootstrap.mach"],
["internal/engine.cm", "internal/engine.mach"]
]
var stale = []
var _i = 0
var cm_path = null
var mach_path = null
var cm_stat = null
var mach_stat = null
while (_i < length(pairs)) {
cm_path = core_path + '/' + pairs[_i][0]
mach_path = core_path + '/' + pairs[_i][1]
if (fd.is_file(cm_path) && fd.is_file(mach_path)) {
cm_stat = fd.stat(cm_path)
mach_stat = fd.stat(mach_path)
if (cm_stat.mtime > mach_stat.mtime) {
push(stale, pairs[_i][0])
}
}
_i = _i + 1
}
if (length(stale) > 0) {
print("warning: bytecode is stale for: " + text(stale, ", ") + "\n")
print("run 'make regen' or './cell --core . regen.cm' to update\n")
}
} }
check_mach_stale()
// analyze: tokenize + parse, check for errors // analyze: tokenize + parse, check for errors
function analyze(src, filename) { function analyze(src, filename) {
@@ -80,33 +123,86 @@ function analyze(src, filename) {
return ast return ast
} }
// Run AST through either mcode or mach pipeline // Load a module from .mach bytecode, falling back to source compilation
function run_ast(name, ast, env) { function load_module(name, env) {
var mach_path = core_path + '/' + name + ".mach"
var data = null
var src_path = null
var src = null
var ast = null
var compiled = null var compiled = null
if (use_mcode) { var optimized = null
compiled = mcode_mod(ast) if (fd.is_file(mach_path)) {
return mcode_run(name, json.encode(compiled), env) data = fd.slurp(mach_path)
return mach_load(data, env)
} }
return mach_eval_ast(name, json.encode(ast), env) src_path = core_path + '/' + name + ".cm"
src = text(fd.slurp(src_path))
ast = analyze(src, src_path)
compiled = mcode_mod(ast)
optimized = streamline_mod(compiled)
return mach_eval_mcode(name, json.encode(optimized), env)
}
// Load optimization pipeline modules (needs analyze to be defined)
var qbe_macros = null
streamline_mod = load_module("streamline", boot_env)
use_cache['streamline'] = streamline_mod
if (emit_qbe) {
qbe_macros = load_module("qbe", boot_env)
qbe_emit_mod = load_module("qbe_emit", boot_env)
use_cache['qbe'] = qbe_macros
use_cache['qbe_emit'] = qbe_emit_mod
}
// Run AST through mcode pipeline → register VM
function run_ast(name, ast, env) {
var compiled = mcode_mod(ast)
var optimized = streamline_mod(compiled)
var qbe_il = null
if (emit_qbe) {
qbe_il = qbe_emit_mod(optimized, qbe_macros)
print(qbe_il)
return null
}
if (dump_mach) {
mach_dump_mcode(name, json.encode(optimized), env)
return null
}
return mach_eval_mcode(name, json.encode(optimized), env)
} }
// use() with ƿit pipeline for .cm modules // use() with ƿit pipeline for .cm modules
function use(path) { function use_fn(path) {
var file_path = path + '.cm' var file_path = null
var mach_path = null
var data = null
var script = null var script = null
var ast = null var ast = null
var result = null var result = null
if (use_cache[path]) if (use_cache[path])
return use_cache[path] return use_cache[path]
// Check CWD first, then core_path // Try .mach bytecode first (CWD then core_path)
mach_path = path + '.mach'
if (!fd.is_file(mach_path))
mach_path = core_path + '/' + path + '.mach'
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
result = mach_load(data, {use: use_fn})
use_cache[path] = result
return result
}
// Try .cm source (CWD then core_path)
file_path = path + '.cm'
if (!fd.is_file(file_path)) if (!fd.is_file(file_path))
file_path = core_path + '/' + path + '.cm' file_path = core_path + '/' + path + '.cm'
if (fd.is_file(file_path)) { if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path)) script = text(fd.slurp(file_path))
ast = analyze(script, file_path) ast = analyze(script, file_path)
result = run_ast(path, ast, {use: use}) result = run_ast(path, ast, {use: use_fn})
use_cache[path] = result use_cache[path] = result
return result return result
} }
@@ -117,21 +213,71 @@ function use(path) {
return result return result
} }
// Load and run the user's program // Helper to load engine.cm and run it with given env
var program = args[0] function load_engine(env) {
var script_file = program var engine_path = core_path + '/internal/engine.mach'
var data = null
// Add .ce extension if not already present var engine_src = null
if (!ends_with(script_file, '.ce') && !ends_with(script_file, '.cm')) var engine_ast = null
script_file = program + '.ce' if (fd.is_file(engine_path)) {
data = fd.slurp(engine_path)
var user_args = [] return mach_load(data, env)
var _j = 1 }
while (_j < length(args)) { engine_path = core_path + '/internal/engine.cm'
push(user_args, args[_j]) engine_src = text(fd.slurp(engine_path))
_j = _j + 1 engine_ast = analyze(engine_src, engine_path)
return run_ast('engine', engine_ast, env)
} }
var script = text(fd.slurp(script_file)) // Detect mode and route
var ast = analyze(script, script_file) // CLI mode has 'args'; actor spawn mode has 'init'
run_ast(program, ast, {use: use, args: user_args, json: json}) var program = null
var user_args = []
var _j = 0
var script_file = null
var script = null
var ast = null
if (args != null) {
// CLI mode — parse args
program = args[0]
_j = 1
while (_j < length(args)) {
push(user_args, args[_j])
_j = _j + 1
}
// Resolve script file: try .cm then .ce in CWD then core_path
script_file = program
if (!ends_with(script_file, '.ce') && !ends_with(script_file, '.cm'))
script_file = program + '.cm'
if (!fd.is_file(script_file))
script_file = core_path + '/' + program + '.cm'
if (!fd.is_file(script_file))
script_file = program + '.ce'
if (!fd.is_file(script_file))
script_file = core_path + '/' + program + '.ce'
if (ends_with(script_file, '.ce')) {
// Actor script — delegate to engine
load_engine({
os: os, actorsym: actorsym,
init: {program: program, arg: user_args},
core_path: core_path, shop_path: shop_path, json: json,
analyze: analyze, run_ast_fn: run_ast
})
} else {
// Module script — run directly
script = text(fd.slurp(script_file))
ast = analyze(script, script_file)
run_ast(program, ast, {use: use_fn, args: user_args, json: json})
}
} else {
// Actor spawn mode — load engine.cm with full actor env
load_engine({
os: os, actorsym: actorsym, init: init,
core_path: core_path, shop_path: shop_path, json: json, nota: nota, wota: wota,
analyze: analyze, run_ast_fn: run_ast
})
}

Binary file not shown.

View File

@@ -1,21 +1,18 @@
// Hidden vars (os, actorsym, init, core_path) come from env // Hidden vars (os, actorsym, init, core_path, shop_path, analyze, run_ast_fn, json) come from env
// In actor spawn mode, also: nota, wota
var ACTORDATA = actorsym var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__' var SYSYM = '__SYSTEM__'
var _cell = {} var _cell = {}
var need_stop = false var need_stop = false
var dylib_ext
var cases = { var cases = {
Windows: '.dll', Windows: '.dll',
macOS: '.dylib', macOS: '.dylib',
Linux: '.so' Linux: '.so'
} }
print(os.platform()) var dylib_ext = cases[os.platform()]
dylib_ext = cases[os.platform()]
var MOD_EXT = '.cm' var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce' var ACTOR_EXT = '.ce'
@@ -49,52 +46,62 @@ function ends_with(str, suffix) {
return search(str, suffix, -length(suffix)) != null return search(str, suffix, -length(suffix)) != null
} }
var js = use_embed('js')
var fd = use_embed('fd') var fd = use_embed('fd')
var js = use_embed('js')
// Get the shop path from HOME environment // core_path and shop_path come from env (bootstrap.cm passes them through)
var home = os.getenv('HOME') || os.getenv('USERPROFILE') // shop_path may be null if --core was used without --shop
if (!home) { var packages_path = shop_path ? shop_path + '/packages' : null
os.print('Could not determine home directory\n')
os.exit(1)
}
var shop_path = home + '/.cell'
var packages_path = shop_path + '/packages'
var core_path = packages_path + '/core'
if (!fd.is_dir(core_path)) {
os.print('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.\n')
os.exit(1)
}
var use_cache = {} var use_cache = {}
use_cache['core/os'] = os use_cache['core/os'] = os
// Extra env properties added as engine initializes (log, runtime fns, etc.)
var core_extras = {}
// Load a core module from the file system // Load a core module from the file system
function use_core(path) { function use_core(path) {
var cache_key = 'core/' + path var cache_key = 'core/' + path
var env = null
if (use_cache[cache_key]) if (use_cache[cache_key])
return use_cache[cache_key]; return use_cache[cache_key]
var sym = use_embed(replace(path, '/', '_')) var sym = use_embed(replace(path, '/', '_'))
var result = null
var script = null
var ast = null
// Core scripts are in packages/core/ // Build env: merge core_extras, include C embed as 'native' if available
var file_path = core_path + '/' + path + MOD_EXT env = {use: use_core}
arrfor(array(core_extras), function(k) { env[k] = core_extras[k] })
if (sym) env.native = sym
if (fd.is_file(file_path)) { // Check for pre-compiled .mach file first
var script_blob = fd.slurp(file_path) var mach_path = core_path + '/' + path + '.mach'
var script = text(script_blob) if (fd.is_file(mach_path)) {
var mod = `(function setup_module(use){${script}})` result = mach_load(fd.slurp(mach_path), env)
var fn = mach_eval('core:' + path, mod) use_cache[cache_key] = result
var result = call(fn,sym, [use_core]) return result
use_cache[cache_key] = result;
return result;
} }
use_cache[cache_key] = sym; // Fall back to source .cm file — compile at runtime
return sym; var file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path))
ast = analyze(script, file_path)
result = run_ast_fn('core:' + path, ast, env)
use_cache[cache_key] = result
return result
}
// Embedded C module only
use_cache[cache_key] = sym
return sym
} }
// Load full modules via use_core (extends C embeds with .cm additions, and caches)
fd = use_core('fd')
use_core('js')
var blob = use_core('blob') var blob = use_core('blob')
function actor() { function actor() {
@@ -112,22 +119,9 @@ function is_actor(value) {
var ENETSERVICE = 0.1 var ENETSERVICE = 0.1
var REPLYTIMEOUT = 60 // seconds before replies are ignored var REPLYTIMEOUT = 60 // seconds before replies are ignored
function caller_data(depth = 0) function caller_data(depth)
{ {
var file = "nofile" return {file: "nofile", line: 0}
var line = 0
var caller = array(Error().stack, "\n")[1+depth]
if (caller) {
var md = extract(caller, /\((.*)\:/)
var m = md ? md[1] : "SCRIPT"
if (m) file = m
md = extract(caller, /\:(\d*)\)/)
m = md ? md[1] : 0
if (m) line = m
}
return {file,line}
} }
function console_rec(line, file, msg) { function console_rec(line, file, msg) {
@@ -142,9 +136,7 @@ function log(name, args) {
if (name == 'console') { if (name == 'console') {
os.print(console_rec(caller.line, caller.file, msg)) os.print(console_rec(caller.line, caller.file, msg))
} else if (name == 'error') { } else if (name == 'error') {
if (msg == null) msg = Error() if (msg == null) msg = "error"
if (is_proto(msg, Error))
msg = msg.name + ": " + msg.message + "\n" + msg.stack
os.print(console_rec(caller.line, caller.file, msg)) os.print(console_rec(caller.line, caller.file, msg))
} else if (name == 'system') { } else if (name == 'system') {
msg = "[SYSTEM] " + msg msg = "[SYSTEM] " + msg
@@ -156,6 +148,9 @@ function log(name, args) {
function actor_die(err) function actor_die(err)
{ {
var reason = null
var unders = null
if (err && is_function(err.toString)) { if (err && is_function(err.toString)) {
os.print(err.toString()) os.print(err.toString())
os.print("\n") os.print("\n")
@@ -165,14 +160,14 @@ function actor_die(err)
if (overling) { if (overling) {
if (err) { if (err) {
// with an err, this is a forceful disrupt // with an err, this is a forceful disrupt
var reason = (is_proto(err, Error)) ? err.stack : err reason = err
report_to_overling({type:'disrupt', reason}) report_to_overling({type:'disrupt', reason})
} else } else
report_to_overling({type:'stop'}) report_to_overling({type:'stop'})
} }
if (underlings) { if (underlings) {
var unders = array(underlings) unders = array(underlings)
arrfor(unders, function(id, index) { arrfor(unders, function(id, index) {
log.console(`calling on ${id} to disrupt too`) log.console(`calling on ${id} to disrupt too`)
$_.stop(create_actor({id})) $_.stop(create_actor({id}))
@@ -191,14 +186,15 @@ function actor_die(err)
actor_mod.on_exception(actor_die) //actor_mod.on_exception(actor_die)
_cell.args = init != null ? init : {} _cell.args = init != null ? init : {}
_cell.id = "newguy" _cell.id = "newguy"
function create_actor(desc = {id:guid()}) { function create_actor(desc) {
var _desc = desc == null ? {id:guid()} : desc
var actor = {} var actor = {}
actor[ACTORDATA] = desc actor[ACTORDATA] = _desc
return actor return actor
} }
@@ -208,10 +204,12 @@ $_.self = create_actor()
os.use_cache = use_cache os.use_cache = use_cache
os.global_shop_path = shop_path os.global_shop_path = shop_path
os.$_ = $_ os.$_ = $_
os.analyze = analyze
os.run_ast_fn = run_ast_fn
os.json = json
use_cache['core/json'] = json
var shop = use_core('internal/shop') var shop = use_core('internal/shop')
var json = use_core('json')
var time = use_core('time') var time = use_core('time')
var pronto = use_core('pronto') var pronto = use_core('pronto')
@@ -237,6 +235,9 @@ var runtime_env = {
sequence: sequence sequence: sequence
} }
// Make runtime functions available to modules loaded via use_core
arrfor(array(runtime_env), function(k) { core_extras[k] = runtime_env[k] })
// Pass to os for shop to access // Pass to os for shop to access
os.runtime_env = runtime_env os.runtime_env = runtime_env
@@ -296,8 +297,8 @@ $_.time_limit = function(requestor, seconds)
callback(val, reason) callback(val, reason)
}, value) }, value)
} disruption { } disruption {
cancel(Error('requestor failed')) cancel('requestor failed')
callback(null, Error('requestor failed')) callback(null, 'requestor failed')
} }
do_request() do_request()
@@ -345,9 +346,10 @@ REPLYTIMEOUT = config.reply_timeout
} }
*/ */
function guid(bits = 256) function guid(bits)
{ {
var guid = blob(bits, os.random) var _bits = bits == null ? 256 : bits
var guid = blob(_bits, os.random)
stone(guid) stone(guid)
return text(guid,'h') return text(guid,'h')
} }
@@ -429,13 +431,16 @@ $_.portal = function(fn, port) {
} }
function handle_host(e) { function handle_host(e) {
var queue = null
var data = null
if (e.type == "connect") { if (e.type == "connect") {
log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`) log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`)
peers[`${e.peer.address}:${e.peer.port}`] = e.peer peers[`${e.peer.address}:${e.peer.port}`] = e.peer
var queue = peer_queue.get(e.peer) queue = peer_queue.get(e.peer)
if (queue) { if (queue) {
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg))) arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
log.system(`sent ${msg} out of queue`) log.system(`sent queue out of queue`)
peer_queue.delete(e.peer) peer_queue.delete(e.peer)
} }
} else if (e.type == "disconnect") { } else if (e.type == "disconnect") {
@@ -445,27 +450,28 @@ function handle_host(e) {
}) })
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port) log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
} else if (e.type == "receive") { } else if (e.type == "receive") {
var data = nota.decode(e.data) data = nota.decode(e.data)
if (data.replycc && !data.replycc.address) { if (data.replycc && !data.replycc.address) {
data.replycc[ACTORDATA].address = e.peer.address data.replycc[ACTORDATA].address = e.peer.address
data.replycc[ACTORDATA].port = e.peer.port data.replycc[ACTORDATA].port = e.peer.port
} }
function populate_actor_addresses(obj) { if (data.data) populate_actor_addresses(data.data, e)
if (!is_object(obj)) return
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
obj[ACTORDATA].address = e.peer.address
obj[ACTORDATA].port = e.peer.port
}
arrfor(array(obj), function(key, index) {
if (key in obj)
populate_actor_addresses(obj[key])
})
}
if (data.data) populate_actor_addresses(data.data)
turn(data) turn(data)
} }
} }
function populate_actor_addresses(obj, e) {
if (!is_object(obj)) return
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
obj[ACTORDATA].address = e.peer.address
obj[ACTORDATA].port = e.peer.port
}
arrfor(array(obj), function(key, index) {
if (key in obj)
populate_actor_addresses(obj[key], e)
})
}
// takes a callback function, an actor object, and a configuration record for getting information about the status of a connection to the actor. The configuration record is used to request the sort of information that needs to be communicated. This can include latency, bandwidth, activity, congestion, cost, partitions. The callback is given a record containing the requested information. // takes a callback function, an actor object, and a configuration record for getting information about the status of a connection to the actor. The configuration record is used to request the sort of information that needs to be communicated. This can include latency, bandwidth, activity, congestion, cost, partitions. The callback is given a record containing the requested information.
$_.contact = function(callback, record) { $_.contact = function(callback, record) {
send(create_actor(record), record, callback) send(create_actor(record), record, callback)
@@ -514,12 +520,13 @@ $_.unneeded = function unneeded(fn, seconds) {
} }
// schedules the invocation of a function after a specified amount of time. // schedules the invocation of a function after a specified amount of time.
$_.delay = function delay(fn, seconds = 0) { $_.delay = function delay(fn, seconds) {
var _seconds = seconds == null ? 0 : seconds
function delay_turn() { function delay_turn() {
fn() fn()
send_messages() send_messages()
} }
var id = actor_mod.delay(delay_turn, seconds) var id = actor_mod.delay(delay_turn, _seconds)
return function() { actor_mod.removetimer(id) } return function() { actor_mod.removetimer(id) }
} }
@@ -544,6 +551,9 @@ function actor_send_immediate(actor, send) {
} }
function actor_send(actor, message) { function actor_send(actor, message) {
var wota_blob = null
var peer = null
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
return return
@@ -565,8 +575,7 @@ function actor_send(actor, message) {
// message to actor in same flock // message to actor in same flock
if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) { if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
var wota_blob = wota.encode(message) wota_blob = wota.encode(message)
// log.console(`sending wota blob of ${length(wota_blob)/8} bytes`)
actor_mod.mailbox_push(actor[ACTORDATA].id, wota_blob) actor_mod.mailbox_push(actor[ACTORDATA].id, wota_blob)
return return
} }
@@ -577,7 +586,7 @@ function actor_send(actor, message) {
else else
message.type = "contact" message.type = "contact"
var peer = peers[actor[ACTORDATA].address + ":" + actor[ACTORDATA].port] peer = peers[actor[ACTORDATA].address + ":" + actor[ACTORDATA].port]
if (!peer) { if (!peer) {
if (!portal) { if (!portal) {
log.system(`creating a contactor ...`) log.system(`creating a contactor ...`)
@@ -621,6 +630,11 @@ function send_messages() {
var replies = {} var replies = {}
function send(actor, message, reply) { function send(actor, message, reply) {
var send_msg = null
var target = null
var header = null
var id = null
if (!is_object(actor)) { if (!is_object(actor)) {
log.error(`Must send to an actor object. Provided: ${actor}`) log.error(`Must send to an actor object. Provided: ${actor}`)
disrupt disrupt
@@ -630,11 +644,11 @@ function send(actor, message, reply) {
log.error('Message must be an object') log.error('Message must be an object')
disrupt disrupt
} }
var send_msg = {type:"user", data: message} send_msg = {type:"user", data: message}
var target = actor target = actor
if (actor[HEADER] && actor[HEADER].replycc) { if (actor[HEADER] && actor[HEADER].replycc) {
var header = actor[HEADER] header = actor[HEADER]
if (!header.replycc || !is_actor(header.replycc)) { if (!header.replycc || !is_actor(header.replycc)) {
log.error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`) log.error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
disrupt disrupt
@@ -645,7 +659,7 @@ function send(actor, message, reply) {
} }
if (reply) { if (reply) {
var id = guid() id = guid()
replies[id] = reply replies[id] = reply
$_.delay(_ => { $_.delay(_ => {
if (replies[id]) { if (replies[id]) {
@@ -730,18 +744,21 @@ function handle_actor_disconnect(id) {
function handle_sysym(msg) function handle_sysym(msg)
{ {
var from var from = null
var greeter = null
var letter2 = null
if (msg.kind == 'stop') { if (msg.kind == 'stop') {
actor_die("got stop message") actor_die("got stop message")
} else if (msg.kind == 'underling') { } else if (msg.kind == 'underling') {
from = msg.from from = msg.from
var greeter = greeters[from[ACTORDATA].id] greeter = greeters[from[ACTORDATA].id]
if (greeter) greeter(msg.message) if (greeter) greeter(msg.message)
if (msg.message.type == 'disrupt') if (msg.message.type == 'disrupt')
delete underlings[from[ACTORDATA].id] delete underlings[from[ACTORDATA].id]
} else if (msg.kind == 'contact') { } else if (msg.kind == 'contact') {
if (portal_fn) { if (portal_fn) {
var letter2 = msg.data letter2 = msg.data
letter2[HEADER] = msg letter2[HEADER] = msg
delete msg.data delete msg.data
portal_fn(letter2) portal_fn(letter2)
@@ -758,13 +775,16 @@ function handle_sysym(msg)
} }
function handle_message(msg) { function handle_message(msg) {
var letter = null
var fn = null
if (msg[SYSYM]) { if (msg[SYSYM]) {
handle_sysym(msg[SYSYM], msg.from) handle_sysym(msg[SYSYM], msg.from)
return return
} }
if (msg.type == "user") { if (msg.type == "user") {
var letter = msg.data // what the sender really sent letter = msg.data // what the sender really sent
_ObjectDefineProperty(letter, HEADER, { _ObjectDefineProperty(letter, HEADER, {
value: msg, enumerable: false value: msg, enumerable: false
}) })
@@ -773,7 +793,7 @@ function handle_message(msg) {
}) })
if (msg.return) { if (msg.return) {
var fn = replies[msg.return] fn = replies[msg.return]
if (fn) fn(letter) if (fn) fn(letter)
delete replies[msg.return] delete replies[msg.return]
return return
@@ -798,44 +818,62 @@ function enet_check()
actor_mod.setname(_cell.args.program) actor_mod.setname(_cell.args.program)
var prog = _cell.args.program var prog = _cell.args.program
if (ends_with(prog, '.ce')) prog = text(prog, 0, -3)
var package = use_core('package') var package = use_core('package')
var locator = shop.resolve_locator(_cell.args.program + ".ce", null) // Find the .ce file
var prog_path = prog + ".ce"
if (!locator) { var pkg_dir = null
var pkg = package.find_package_dir(_cell.args.program + ".ce") var core_dir = null
locator = shop.resolve_locator(_cell.args.program + ".ce", pkg) if (!fd.is_file(prog_path)) {
pkg_dir = package.find_package_dir(prog_path)
if (pkg_dir)
prog_path = pkg_dir + '/' + prog + '.ce'
} }
if (!fd.is_file(prog_path)) {
if (!locator) { // Check core packages
os.print(`Main program ${_cell.args.program} could not be found\n`) core_dir = core_path
prog_path = core_dir + '/' + prog + '.ce'
}
if (!fd.is_file(prog_path)) {
os.print(`Main program ${prog} could not be found\n`)
os.exit(1) os.exit(1)
} }
$_.clock(_ => { $_.clock(_ => {
// Get capabilities for the main program var file_info = shop.file_info ? shop.file_info(prog_path) : null
var file_info = shop.file_info ? shop.file_info(locator.path) : null
var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : [] var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : []
// Build env object for injection // Build env with runtime functions + capability injections
var env = {} var env = {}
for (var i = 0; i < length(inject); i++) { arrfor(array(runtime_env), function(k) { env[k] = runtime_env[k] })
var key = inject[i] var _ki = 0
var inj = null
var key = null
while (_ki < length(inject)) {
inj = inject[_ki]
key = inj
if (key && key[0] == '$') key = text(key, 1) if (key && key[0] == '$') key = text(key, 1)
if (key == 'fd') env[key] = fd if (key == 'fd') env['$fd'] = fd
else env[key] = $_[key] else env['$' + key] = $_[key]
_ki = _ki + 1
} }
// Create use function bound to the program's package
var pkg = file_info ? file_info.package : null var pkg = file_info ? file_info.package : null
var use_fn = function(path) { return shop.use(path, pkg) } env.use = function(path) {
var ck = 'core/' + path
if (use_cache[ck]) return use_cache[ck]
return shop.use(path, pkg)
}
env.args = _cell.args.arg
env.log = log
// Call with signature: setup_module(args, use, env) var script = text(fd.slurp(prog_path))
// The script wrapper binds $delay, $start, etc. from env var ast = analyze(script, prog_path)
var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env]) var val = run_ast_fn(prog, ast, env)
if (val) {
if (val)
log.error('Program must not return anything') log.error('Program must not return anything')
disrupt disrupt
}
}) })

BIN
internal/engine.mach Normal file

Binary file not shown.

View File

@@ -5,7 +5,6 @@ var fd = use('fd')
var http = use('http') var http = use('http')
var miniz = use('miniz') var miniz = use('miniz')
var time = use('time') var time = use('time')
var js = use('js')
var crypto = use('crypto') var crypto = use('crypto')
var blob = use('blob') var blob = use('blob')
@@ -13,6 +12,10 @@ var pkg_tools = use('package')
var os = use('os') var os = use('os')
var link = use('link') var link = use('link')
var analyze = os.analyze
var run_ast_fn = os.run_ast_fn
var shop_json = os.json
var core = "core" var core = "core"
function pull_from_cache(content) function pull_from_cache(content)
@@ -32,9 +35,10 @@ function ensure_dir(path) {
if (fd.stat(path).isDirectory) return if (fd.stat(path).isDirectory) return
var parts = array(path, '/') var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : '' var current = starts_with(path, '/') ? '/' : ''
for (var i = 0; i < length(parts); i++) { var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue if (parts[i] == '') continue
current += parts[i] + '/' current = current + parts[i] + '/'
if (!fd.stat(current).isDirectory) { if (!fd.stat(current).isDirectory) {
fd.mkdir(current) fd.mkdir(current)
} }
@@ -76,12 +80,12 @@ function get_packages_dir() {
} }
// Get the core directory (in the global shop) // Get the core directory (in the global shop)
var core_package = 'core'
Shop.get_core_dir = function() { Shop.get_core_dir = function() {
return get_packages_dir() + '/' + core_package return get_packages_dir() + '/' + core_package
} }
var core_package = 'core'
// Get the links file path (in the global shop) // Get the links file path (in the global shop)
function get_links_path() { function get_links_path() {
return global_shop_path + '/link.toml' return global_shop_path + '/link.toml'
@@ -116,12 +120,16 @@ function split_explicit_package_import(path)
if (!looks_explicit) return null if (!looks_explicit) return null
// Find the longest prefix that is an installed package // Find the longest prefix that is an installed package
for (var i = length(parts) - 1; i >= 1; i--) { var i = 0
var pkg_candidate = text(array(parts, 0, i), '/') var pkg_candidate = null
var mod_path = text(array(parts, i), '/') var mod_path = null
var candidate_dir = null
for (i = length(parts) - 1; i >= 1; i--) {
pkg_candidate = text(array(parts, 0, i), '/')
mod_path = text(array(parts, i), '/')
if (!mod_path || length(mod_path) == 0) continue if (!mod_path || length(mod_path) == 0) continue
var candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate) candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate)
if (fd.is_file(candidate_dir + '/cell.toml')) if (fd.is_file(candidate_dir + '/cell.toml'))
return {package: pkg_candidate, path: mod_path} return {package: pkg_candidate, path: mod_path}
@@ -142,8 +150,10 @@ function package_in_shop(package) {
function abs_path_to_package(package_dir) function abs_path_to_package(package_dir)
{ {
if (!fd.is_file(package_dir + '/cell.toml')) if (!fd.is_file(package_dir + '/cell.toml')) {
throw Error('Not a valid package directory (no cell.toml): ' + package_dir) print('Not a valid package directory (no cell.toml): ' + package_dir)
disrupt
}
var packages_prefix = get_packages_dir() + '/' var packages_prefix = get_packages_dir() + '/'
var core_dir = packages_prefix + core_package var core_dir = packages_prefix + core_package
@@ -153,8 +163,9 @@ function abs_path_to_package(package_dir)
return 'core' return 'core'
} }
// Also check if core_dir is a symlink pointing to package_dir // Also check if core_dir is a symlink pointing to package_dir
var core_target = null
if (fd.is_link(core_dir)) { if (fd.is_link(core_dir)) {
var core_target = fd.readlink(core_dir) core_target = fd.readlink(core_dir)
if (core_target == package_dir || fd.realpath(core_dir) == package_dir) { if (core_target == package_dir || fd.realpath(core_dir) == package_dir) {
return 'core' return 'core'
} }
@@ -175,13 +186,14 @@ function abs_path_to_package(package_dir)
return package_dir return package_dir
// For local directories (e.g., linked targets), read the package name from cell.toml // For local directories (e.g., linked targets), read the package name from cell.toml
try { var _toml_path = package_dir + '/cell.toml'
var content = text(fd.slurp(package_dir + '/cell.toml')) var content = null
var cfg = toml.decode(content) var cfg = null
if (fd.is_file(_toml_path)) {
content = text(fd.slurp(_toml_path))
cfg = toml.decode(content)
if (cfg.package) if (cfg.package)
return cfg.package return cfg.package
} catch (e) {
// Fall through
} }
return null return null
@@ -299,23 +311,29 @@ Shop.resolve_package_info = function(pkg) {
// Verify if a package name is valid and return status // Verify if a package name is valid and return status
Shop.verify_package_name = function(pkg) { Shop.verify_package_name = function(pkg) {
if (!pkg) throw Error("Empty package name") if (!pkg) { print("Empty package name"); disrupt }
if (pkg == 'local') throw Error("local is not a valid package name") if (pkg == 'local') { print("local is not a valid package name"); disrupt }
if (pkg == 'core') throw Error("core is not a valid package name") if (pkg == 'core') { print("core is not a valid package name"); disrupt }
if (search(pkg, '://') != null) if (search(pkg, '://') != null) {
throw Error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`) print(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
disrupt
}
} }
// Convert module package to download URL // Convert module package to download URL
Shop.get_download_url = function(pkg, commit_hash) { Shop.get_download_url = function(pkg, commit_hash) {
var info = Shop.resolve_package_info(pkg) var info = Shop.resolve_package_info(pkg)
var parts = null
var host = null
var user = null
var repo = null
if (info == 'gitea') { if (info == 'gitea') {
var parts = array(pkg, '/') parts = array(pkg, '/')
var host = parts[0] host = parts[0]
var user = parts[1] user = parts[1]
var repo = parts[2] repo = parts[2]
return 'https://' + host + '/' + user + '/' + repo + '/archive/' + commit_hash + '.zip' return 'https://' + host + '/' + user + '/' + repo + '/archive/' + commit_hash + '.zip'
} }
@@ -326,12 +344,16 @@ Shop.get_download_url = function(pkg, commit_hash) {
// Get the API URL for checking remote git commits // Get the API URL for checking remote git commits
Shop.get_api_url = function(pkg) { Shop.get_api_url = function(pkg) {
var info = Shop.resolve_package_info(pkg) var info = Shop.resolve_package_info(pkg)
var parts = null
var host = null
var user = null
var repo = null
if (info == 'gitea') { if (info == 'gitea') {
var parts = array(pkg, '/') parts = array(pkg, '/')
var host = parts[0] host = parts[0]
var user = parts[1] user = parts[1]
var repo = parts[2] repo = parts[2]
return 'https://' + host + '/api/v1/repos/' + user + '/' + repo + '/branches/' return 'https://' + host + '/api/v1/repos/' + user + '/' + repo + '/branches/'
} }
@@ -378,91 +400,72 @@ Shop.get_script_capabilities = function(path) {
return Shop.script_inject_for(file_info) return Shop.script_inject_for(file_info)
} }
// Build the env object for a module, with runtime fns and $-prefixed capabilities.
// Matches engine.cm's approach: env properties become free variables in the module.
function inject_env(inject) { function inject_env(inject) {
// Start with runtime functions from engine
var env = {} var env = {}
var rt = my$_.os ? my$_.os.runtime_env : null var rt = my$_.os ? my$_.os.runtime_env : null
if (rt) { if (rt) {
for (var k in rt) { arrfor(array(rt), function(k) { env[k] = rt[k] })
env[k] = rt[k]
}
} }
// Add capability injections // Add capability injections with $ prefix
for (var i = 0; i < length(inject); i++) { var i = 0
var inj = inject[i] var inj = null
var key = trim(inj, '$') var key = null
if (key == 'fd') env[key] = fd for (i = 0; i < length(inject); i++) {
else env[key] = my$_[key] inj = inject[i]
key = inj
if (key && key[0] == '$') key = text(key, 1)
if (key == 'fd') env['$fd'] = fd
else env['$' + key] = my$_[key]
} }
return env return env
} }
function inject_bindings_code(inject) { // Lazy-loaded compiler modules for on-the-fly compilation
var lines = [] var _mcode_mod = null
var _streamline_mod = null
// Runtime function bindings // Compile a module and return its bytecode blob.
var runtime_fns = ['logical', 'some', 'every', 'starts_with', 'ends_with', // The bytecode is cached on disk by content hash.
'actor', 'is_actor', 'log', 'send',
'fallback', 'parallel', 'race', 'sequence']
for (var i = 0; i < length(runtime_fns); i++) {
var fn = runtime_fns[i]
push(lines, `var ${fn} = env["${fn}"];`)
}
// Capability bindings ($delay, $start, etc.)
for (var i = 0; i < length(inject); i++) {
var inj = inject[i]
var key = trim(inj, '$')
push(lines, `var $${key} = env["${key}"];`)
}
return text(lines, '\n')
}
// Build the use function for a specific package context
function make_use_fn_code(pkg_arg) {
return `function(path) { return globalThis.use(path, ${pkg_arg}); }`
}
// for script forms, path is the canonical path of the module
var script_form = function(path, script, pkg, inject) {
var pkg_arg = pkg ? `'${pkg}'` : 'null'
var binds = inject_bindings_code(inject)
var fn = `(function setup_module(args, use, env){
def arg = args;
def PACKAGE = ${pkg_arg};
${binds}
${script}
})`
return fn
}
// Resolve module function, hashing it in the process
// path is the exact path to the script file
function resolve_mod_fn(path, pkg) { function resolve_mod_fn(path, pkg) {
if (!fd.is_file(path)) throw Error(`path ${path} is not a file`) if (!fd.is_file(path)) { print(`path ${path} is not a file`); disrupt }
var file_info = Shop.file_info(path)
var file_pkg = file_info.package
var inject = Shop.script_inject_for(file_info)
var content = text(fd.slurp(path)) var content = text(fd.slurp(path))
var script = script_form(path, content, file_pkg, inject); var cached = pull_from_cache(stone(blob(content)))
var ast = null
var compiled = null
var mach_path = null
var mach_blob = null
var ir = null
var optimized = null
var obj = pull_from_cache(stone(blob(script))) // Check cache for pre-compiled .mach blob
if (obj) { if (cached) {
var fn = js.compile_unblob(obj) return cached
return js.integrate(fn, null)
} }
// Compile name is just for debug/stack traces // Check for pre-compiled .mach file alongside .cm source
var compile_name = path if (ends_with(path, '.cm')) {
mach_path = text(path, 0, length(path) - 3) + '.mach'
if (fd.is_file(mach_path)) {
mach_blob = fd.slurp(mach_path)
put_into_cache(stone(blob(content)), mach_blob)
return mach_blob
}
}
var fn = js.compile(compile_name, script) // Compile via full pipeline: analyze → mcode → streamline → serialize
if (!_mcode_mod) _mcode_mod = Shop.use("mcode", null)
if (!_streamline_mod) _streamline_mod = Shop.use("streamline", null)
ast = analyze(content, path)
ir = _mcode_mod(ast)
optimized = _streamline_mod(ir)
compiled = mach_compile_mcode_bin(path, shop_json.encode(optimized))
put_into_cache(stone(blob(content)), compiled)
put_into_cache(stone(blob(script)), js.compile_blob(fn)) return compiled
return js.integrate(fn, null)
} }
// given a path and a package context // given a path and a package context
@@ -470,24 +473,32 @@ function resolve_mod_fn(path, pkg) {
function resolve_locator(path, ctx) function resolve_locator(path, ctx)
{ {
var explicit = split_explicit_package_import(path) var explicit = split_explicit_package_import(path)
var explicit_path = null
var fn = null
var core_dir = null
var core_file_path = null
var is_core = null
var scope = null
var alias_path = null
if (explicit) { if (explicit) {
if (is_internal_path(explicit.path) && ctx && explicit.package != ctx) if (is_internal_path(explicit.path) && ctx && explicit.package != ctx)
explicit = null explicit = null
} }
if (explicit) { if (explicit) {
var explicit_path = get_packages_dir() + '/' + safe_package_path(explicit.package) + '/' + explicit.path explicit_path = get_packages_dir() + '/' + safe_package_path(explicit.package) + '/' + explicit.path
if (fd.is_file(explicit_path)) { if (fd.is_file(explicit_path)) {
var fn = resolve_mod_fn(explicit_path, explicit.package) fn = resolve_mod_fn(explicit_path, explicit.package)
return {path: explicit_path, scope: SCOPE_PACKAGE, symbol: fn} return {path: explicit_path, scope: SCOPE_PACKAGE, symbol: fn}
} }
} }
// 1. If no context, resolve from core only // 1. If no context, resolve from core only
if (!ctx) { if (!ctx) {
var core_dir = Shop.get_core_dir() core_dir = Shop.get_core_dir()
var core_file_path = core_dir + '/' + path core_file_path = core_dir + '/' + path
if (fd.is_file(core_file_path)) { if (fd.is_file(core_file_path)) {
var fn = resolve_mod_fn(core_file_path, 'core') fn = resolve_mod_fn(core_file_path, 'core')
return {path: core_file_path, scope: SCOPE_CORE, symbol: fn} return {path: core_file_path, scope: SCOPE_CORE, symbol: fn}
} }
return null return null
@@ -496,7 +507,7 @@ function resolve_locator(path, ctx)
// check in ctx package // check in ctx package
// If ctx is an absolute path (starts with /), use it directly // If ctx is an absolute path (starts with /), use it directly
// Otherwise, look it up in the packages directory // Otherwise, look it up in the packages directory
var ctx_dir var ctx_dir = null
if (starts_with(ctx, '/')) { if (starts_with(ctx, '/')) {
ctx_dir = ctx ctx_dir = ctx
} else { } else {
@@ -505,10 +516,10 @@ function resolve_locator(path, ctx)
var ctx_path = ctx_dir + '/' + path var ctx_path = ctx_dir + '/' + path
if (fd.is_file(ctx_path)) { if (fd.is_file(ctx_path)) {
var fn = resolve_mod_fn(ctx_path, ctx) fn = resolve_mod_fn(ctx_path, ctx)
// Check if ctx is the core package (either by name or by path) // Check if ctx is the core package (either by name or by path)
var is_core = (ctx == 'core') || (ctx_dir == Shop.get_core_dir()) is_core = (ctx == 'core') || (ctx_dir == Shop.get_core_dir())
var scope = is_core ? SCOPE_CORE : SCOPE_LOCAL scope = is_core ? SCOPE_CORE : SCOPE_LOCAL
return {path: ctx_path, scope: scope, symbol: fn} return {path: ctx_path, scope: scope, symbol: fn}
} }
@@ -518,24 +529,24 @@ function resolve_locator(path, ctx)
// check for aliased dependency // check for aliased dependency
var alias = pkg_tools.split_alias(ctx, path) var alias = pkg_tools.split_alias(ctx, path)
if (alias) { if (alias) {
var alias_path = get_packages_dir() + '/' + safe_package_path(alias.package) + '/' + alias.path alias_path = get_packages_dir() + '/' + safe_package_path(alias.package) + '/' + alias.path
if (fd.is_file(alias_path)) { if (fd.is_file(alias_path)) {
var fn = resolve_mod_fn(alias_path, ctx) fn = resolve_mod_fn(alias_path, ctx)
return {path: alias_path, scope:SCOPE_PACKAGE, symbol:fn} return {path: alias_path, scope:SCOPE_PACKAGE, symbol:fn}
} }
} }
var package_path = get_packages_dir() + '/' + safe_package_path(path) var package_path = get_packages_dir() + '/' + safe_package_path(path)
if (fd.is_file(package_path)) { if (fd.is_file(package_path)) {
var fn = resolve_mod_fn(package_path, ctx) fn = resolve_mod_fn(package_path, ctx)
return {path: package_path, scope: SCOPE_PACKAGE, symbol: fn} return {path: package_path, scope: SCOPE_PACKAGE, symbol: fn}
} }
// 4. Check core as fallback // 4. Check core as fallback
var core_dir = Shop.get_core_dir() core_dir = Shop.get_core_dir()
var core_file_path = core_dir + '/' + path core_file_path = core_dir + '/' + path
if (fd.is_file(core_file_path)) { if (fd.is_file(core_file_path)) {
var fn = resolve_mod_fn(core_file_path, 'core') fn = resolve_mod_fn(core_file_path, 'core')
return {path: core_file_path, scope: SCOPE_CORE, symbol: fn} return {path: core_file_path, scope: SCOPE_CORE, symbol: fn}
} }
@@ -567,7 +578,7 @@ Shop.open_package_dylib = function(pkg) {
var link_target = link.get_target(pkg) var link_target = link.get_target(pkg)
var resolved_pkg = link_target ? link_target : pkg var resolved_pkg = link_target ? link_target : pkg
var pkg_dir; var pkg_dir = null
if (starts_with(resolved_pkg, '/')) { if (starts_with(resolved_pkg, '/')) {
pkg_dir = resolved_pkg pkg_dir = resolved_pkg
} else { } else {
@@ -575,34 +586,23 @@ Shop.open_package_dylib = function(pkg) {
} }
var toml_path = pkg_dir + '/cell.toml' var toml_path = pkg_dir + '/cell.toml'
var content = null
var cfg = null
if (fd.is_file(toml_path)) { if (fd.is_file(toml_path)) {
try { content = text(fd.slurp(toml_path))
var content = text(fd.slurp(toml_path)) cfg = toml.decode(content)
var cfg = toml.decode(content) if (cfg.dependencies) {
if (cfg.dependencies) { arrfor(array(cfg.dependencies), function(alias, i) {
arrfor(array(cfg.dependencies), function(alias, i) { var dep_pkg = cfg.dependencies[alias]
var dep_pkg = cfg.dependencies[alias] Shop.open_package_dylib(dep_pkg)
try { })
Shop.open_package_dylib(dep_pkg)
} catch (dep_e) {
// Dependency dylib load failed, continue with others
}
})
}
} catch (e) {
// Error reading toml, continue
} }
} }
var dl_path = get_lib_path(pkg) var dl_path = get_lib_path(pkg)
if (fd.is_file(dl_path)) { if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) { if (!open_dls[dl_path]) {
try { open_dls[dl_path] = os.dylib_open(dl_path)
open_dls[dl_path] = os.dylib_open(dl_path)
} catch (e) {
dylib_visited[pkg] = false
throw e
}
} }
} }
} }
@@ -613,12 +613,19 @@ Shop.open_package_dylib = function(pkg) {
// Core is never loaded as a dynamic library via dlopen // Core is never loaded as a dynamic library via dlopen
function resolve_c_symbol(path, package_context) { function resolve_c_symbol(path, package_context) {
var explicit = split_explicit_package_import(path) var explicit = split_explicit_package_import(path)
var sym = null
var dl_path = null
var _path = null
var core_sym = null
var canon_pkg = null
var mod_name = null
if (explicit) { if (explicit) {
if (is_internal_path(explicit.path) && package_context && explicit.package != package_context) if (is_internal_path(explicit.path) && package_context && explicit.package != package_context)
explicit = null explicit = null
} }
if (explicit) { if (explicit) {
var sym = make_c_symbol(explicit.package, explicit.path) sym = make_c_symbol(explicit.package, explicit.path)
if (os.internal_exists(sym)) { if (os.internal_exists(sym)) {
return { return {
symbol: function() { return os.load_internal(sym) }, symbol: function() { return os.load_internal(sym) },
@@ -629,7 +636,7 @@ function resolve_c_symbol(path, package_context) {
} }
Shop.open_package_dylib(explicit.package) Shop.open_package_dylib(explicit.package)
var dl_path = get_lib_path(explicit.package) dl_path = get_lib_path(explicit.package)
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) { if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
return { return {
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) }, symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
@@ -642,8 +649,8 @@ function resolve_c_symbol(path, package_context) {
// If no package context, only check core internal symbols // If no package context, only check core internal symbols
if (!package_context || package_context == 'core') { if (!package_context || package_context == 'core') {
path = replace(path, '/', '_') _path = replace(path, '/', '_')
var core_sym = `js_${path}_use` core_sym = `js_${_path}_use`
if (os.internal_exists(core_sym)) { if (os.internal_exists(core_sym)) {
return { return {
symbol: function() { return os.load_internal(core_sym) }, symbol: function() { return os.load_internal(core_sym) },
@@ -655,7 +662,7 @@ function resolve_c_symbol(path, package_context) {
} }
// 1. Check own package first (internal, then dylib) // 1. Check own package first (internal, then dylib)
var sym = make_c_symbol(package_context, path) sym = make_c_symbol(package_context, path)
if (os.internal_exists(sym)) { if (os.internal_exists(sym)) {
return { return {
symbol: function() { return os.load_internal(sym) }, symbol: function() { return os.load_internal(sym) },
@@ -665,7 +672,7 @@ function resolve_c_symbol(path, package_context) {
} }
Shop.open_package_dylib(package_context) Shop.open_package_dylib(package_context)
var dl_path = get_lib_path(package_context) dl_path = get_lib_path(package_context)
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) { if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
return { return {
@@ -681,10 +688,10 @@ function resolve_c_symbol(path, package_context) {
// 2. Check aliased package imports (e.g. 'prosperon/sprite') // 2. Check aliased package imports (e.g. 'prosperon/sprite')
var pkg_alias = get_import_package(path) var pkg_alias = get_import_package(path)
if (pkg_alias) { if (pkg_alias) {
var canon_pkg = get_aliased_package(path, package_context) canon_pkg = get_aliased_package(path, package_context)
if (canon_pkg) { if (canon_pkg) {
var mod_name = get_import_name(path) mod_name = get_import_name(path)
var sym = make_c_symbol(canon_pkg, mod_name) sym = make_c_symbol(canon_pkg, mod_name)
// Check internal first // Check internal first
if (os.internal_exists(sym)) { if (os.internal_exists(sym)) {
@@ -698,7 +705,7 @@ function resolve_c_symbol(path, package_context) {
// Then check dylib // Then check dylib
Shop.open_package_dylib(canon_pkg) Shop.open_package_dylib(canon_pkg)
var dl_path = get_lib_path(canon_pkg) dl_path = get_lib_path(canon_pkg)
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) { if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
return { return {
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) }, symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
@@ -711,7 +718,7 @@ function resolve_c_symbol(path, package_context) {
} }
// 3. Check core internal symbols (core is never a dynamic library) // 3. Check core internal symbols (core is never a dynamic library)
var core_sym = `js_${replace(path, '/', '_')}_use` core_sym = `js_${replace(path, '/', '_')}_use`
if (os.internal_exists(core_sym)) { if (os.internal_exists(core_sym)) {
return { return {
symbol: function() { return os.load_internal(core_sym) }, symbol: function() { return os.load_internal(core_sym) },
@@ -739,13 +746,19 @@ function resolve_module_info(path, package_context) {
if (min_scope == 999) if (min_scope == 999)
return null return null
var cache_key var cache_key = null
var real_path = null
var real_info = null
var pkg_alias = null
var canon_pkg = null
var mod_name = null
if (mod_resolve.scope == SCOPE_CORE) { if (mod_resolve.scope == SCOPE_CORE) {
cache_key = 'core/' + path cache_key = 'core/' + path
} else if (mod_resolve.scope < 900 && mod_resolve.path) { } else if (mod_resolve.scope < 900 && mod_resolve.path) {
var real_path = fd.realpath(mod_resolve.path) real_path = fd.realpath(mod_resolve.path)
if (real_path) { if (real_path) {
var real_info = Shop.file_info(real_path) real_info = Shop.file_info(real_path)
if (real_info.package && real_info.name) if (real_info.package && real_info.name)
cache_key = real_info.package + '/' + real_info.name cache_key = real_info.package + '/' + real_info.name
else else
@@ -759,11 +772,11 @@ function resolve_module_info(path, package_context) {
else if (min_scope == SCOPE_LOCAL && package_context) else if (min_scope == SCOPE_LOCAL && package_context)
cache_key = package_context + '/' + path cache_key = package_context + '/' + path
else if (min_scope == SCOPE_PACKAGE) { else if (min_scope == SCOPE_PACKAGE) {
var pkg_alias = get_import_package(path) pkg_alias = get_import_package(path)
if (pkg_alias) { if (pkg_alias) {
var canon_pkg = get_canonical_package(pkg_alias, package_context) canon_pkg = get_canonical_package(pkg_alias, package_context)
if (canon_pkg) { if (canon_pkg) {
var mod_name = get_import_name(path) mod_name = get_import_name(path)
cache_key = canon_pkg + '/' + mod_name cache_key = canon_pkg + '/' + mod_name
} else } else
cache_key = path cache_key = path
@@ -814,54 +827,50 @@ function execute_module(info)
var c_resolve = info.c_resolve var c_resolve = info.c_resolve
var mod_resolve = info.mod_resolve var mod_resolve = info.mod_resolve
var used var used = null
var file_info = null
var inject = null
var env = null
var pkg = null
if (mod_resolve.scope < 900) { if (mod_resolve.scope < 900) {
var context = null // Build env with runtime fns, capabilities, and use function
file_info = Shop.file_info(mod_resolve.path)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
pkg = file_info.package
env.use = make_use_fn(pkg)
// Add C module as native context if available
if (c_resolve.scope < 900) { if (c_resolve.scope < 900) {
context = call_c_module(c_resolve) env.native = call_c_module(c_resolve)
} }
// Get file info to determine inject list // Load compiled bytecode with env
var file_info = Shop.file_info(mod_resolve.path) used = mach_load(mod_resolve.symbol, env)
var inject = Shop.script_inject_for(file_info)
var env = inject_env(inject)
var pkg = file_info.package
var use_fn = make_use_fn(pkg)
// Call with signature: setup_module(args, use, env)
// args is null for module loading
used = call(mod_resolve.symbol, context, [null, use_fn, env])
} else if (c_resolve.scope < 900) { } else if (c_resolve.scope < 900) {
// C only // C only
used = call_c_module(c_resolve) used = call_c_module(c_resolve)
} else { } else {
throw Error(`Module ${info.path} could not be found`) print(`Module ${info.path} could not be found`); disrupt
} }
// if (is_function(used)) if (!used) { print(`Module ${info} returned null`); disrupt }
// throw Error('C module loader returned a function; did you forget to call it?')
if (!used)
throw Error(`Module ${info} returned null`)
// stone(used)
return used return used
} }
function get_module(path, package_context) { function get_module(path, package_context) {
var info = resolve_module_info(path, package_context) var info = resolve_module_info(path, package_context)
if (!info) if (!info) { print(`Module ${path} could not be found in ${package_context}`); disrupt }
throw Error(`Module ${path} could not be found in ${package_context}`)
return execute_module(info) return execute_module(info)
} }
Shop.use = function use(path, package_context) { Shop.use = function use(path, package_context) {
var info = resolve_module_info(path, package_context) var info = resolve_module_info(path, package_context)
if (!info) if (!info) { print(`Module ${path} could not be found in ${package_context}`); disrupt }
throw Error(`Module ${path} could not be found in ${package_context}`)
if (use_cache[info.cache_key]) if (use_cache[info.cache_key])
return use_cache[info.cache_key] return use_cache[info.cache_key]
@@ -889,13 +898,13 @@ function fetch_remote_hash(pkg) {
if (!api_url) return null if (!api_url) return null
try { var _fetch_hash = function() {
var resp = http.fetch(api_url) var resp = http.fetch(api_url)
return Shop.extract_commit_hash(pkg, text(resp)) return Shop.extract_commit_hash(pkg, text(resp))
} catch (e) { } disruption {
log.console("Warning: Could not check for updates for " + pkg)
return null return null
} }
return _fetch_hash()
} }
// Download a zip for a package at a specific commit and cache it // Download a zip for a package at a specific commit and cache it
@@ -909,14 +918,14 @@ function download_zip(pkg, commit_hash) {
return null return null
} }
try { var _download = function() {
var zip_blob = http.fetch(download_url) var zip_blob = http.fetch(download_url)
fd.slurpwrite(cache_path, zip_blob) fd.slurpwrite(cache_path, zip_blob)
return zip_blob return zip_blob
} catch (e) { } disruption {
log.error("Download failed for " + pkg + ": " + e)
return null return null
} }
return _download()
} }
// Get zip from cache, returns null if not cached // Get zip from cache, returns null if not cached
@@ -952,17 +961,18 @@ Shop.fetch = function(pkg) {
// Check if we have the zip cached // Check if we have the zip cached
var zip_blob = get_cached_zip(pkg, commit) var zip_blob = get_cached_zip(pkg, commit)
var actual_hash = null
if (zip_blob) { if (zip_blob) {
// If we have a hash on record, verify it // If we have a hash on record, verify it
if (expected_hash) { if (expected_hash) {
var actual_hash = text(crypto.blake2(zip_blob), 'h') actual_hash = text(crypto.blake2(zip_blob), 'h')
if (actual_hash == expected_hash) { if (actual_hash == expected_hash) {
return { status: 'cached' } return { status: 'cached' }
} }
log.console("Zip hash mismatch for " + pkg + ", re-fetching...") log.console("Zip hash mismatch for " + pkg + ", re-fetching...")
} else { } else {
// No hash stored yet - compute and store it // No hash stored yet - compute and store it
var actual_hash = text(crypto.blake2(zip_blob), 'h') actual_hash = text(crypto.blake2(zip_blob), 'h')
lock_entry.zip_hash = actual_hash lock_entry.zip_hash = actual_hash
Shop.save_lock(lock) Shop.save_lock(lock)
return { status: 'cached' } return { status: 'cached' }
@@ -1014,10 +1024,12 @@ Shop.extract = function(pkg) {
// Check if already extracted at correct commit // Check if already extracted at correct commit
var lock = Shop.load_lock() var lock = Shop.load_lock()
var lock_entry = lock[pkg] var lock_entry = lock[pkg]
var extracted_commit_file = null
var extracted_commit = null
if (lock_entry && lock_entry.commit) { if (lock_entry && lock_entry.commit) {
var extracted_commit_file = target_dir + '/.cell_commit' extracted_commit_file = target_dir + '/.cell_commit'
if (fd.is_file(extracted_commit_file)) { if (fd.is_file(extracted_commit_file)) {
var extracted_commit = trim(text(fd.slurp(extracted_commit_file))) extracted_commit = trim(text(fd.slurp(extracted_commit_file)))
if (extracted_commit == lock_entry.commit) { if (extracted_commit == lock_entry.commit) {
// Already extracted at this commit, skip // Already extracted at this commit, skip
return true return true
@@ -1028,7 +1040,7 @@ Shop.extract = function(pkg) {
var zip_blob = get_package_zip(pkg) var zip_blob = get_package_zip(pkg)
if (!zip_blob) if (!zip_blob)
throw Error("No zip blob available for " + pkg) print("No zip blob available for " + pkg); disrupt
// Extract zip for remote package // Extract zip for remote package
install_zip(zip_blob, target_dir) install_zip(zip_blob, target_dir)
@@ -1069,6 +1081,7 @@ Shop.update = function(pkg) {
log.console(`checking ${pkg}`) log.console(`checking ${pkg}`)
var new_entry = null
if (info == 'local') { if (info == 'local') {
// Check if local path exists // Check if local path exists
if (!fd.is_dir(pkg)) { if (!fd.is_dir(pkg)) {
@@ -1076,7 +1089,7 @@ Shop.update = function(pkg) {
return null return null
} }
// Local packages always get a lock entry // Local packages always get a lock entry
var new_entry = { new_entry = {
type: 'local', type: 'local',
updated: time.number() updated: time.number()
} }
@@ -1099,7 +1112,7 @@ Shop.update = function(pkg) {
if (local_commit == remote_commit) if (local_commit == remote_commit)
return null return null
var new_entry = { new_entry = {
type: info, type: info,
commit: remote_commit, commit: remote_commit,
updated: time.number() updated: time.number()
@@ -1113,7 +1126,7 @@ Shop.update = function(pkg) {
function install_zip(zip_blob, target_dir) { function install_zip(zip_blob, target_dir) {
var zip = miniz.read(zip_blob) var zip = miniz.read(zip_blob)
if (!zip) throw Error("Failed to read zip archive") if (!zip) { print("Failed to read zip archive"); disrupt }
if (fd.is_link(target_dir)) fd.unlink(target_dir) if (fd.is_link(target_dir)) fd.unlink(target_dir)
if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1) if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1)
@@ -1124,21 +1137,28 @@ function install_zip(zip_blob, target_dir) {
var count = zip.count() var count = zip.count()
var created_dirs = {} var created_dirs = {}
for (var i = 0; i < count; i++) { var i = 0
var filename = null
var slash_pos = null
var rel_path = null
var full_path = null
var dir_path = null
var file_data = null
for (i = 0; i < count; i++) {
if (zip.is_directory(i)) continue if (zip.is_directory(i)) continue
var filename = zip.get_filename(i) filename = zip.get_filename(i)
var slash_pos = search(filename, '/') slash_pos = search(filename, '/')
if (slash_pos == null) continue if (slash_pos == null) continue
if (slash_pos + 1 >= length(filename)) continue if (slash_pos + 1 >= length(filename)) continue
var rel_path = text(filename, slash_pos + 1) rel_path = text(filename, slash_pos + 1)
var full_path = target_dir + '/' + rel_path full_path = target_dir + '/' + rel_path
var dir_path = fd.dirname(full_path) dir_path = fd.dirname(full_path)
if (!created_dirs[dir_path]) { if (!created_dirs[dir_path]) {
ensure_dir(dir_path) ensure_dir(dir_path)
created_dirs[dir_path] = true created_dirs[dir_path] = true
} }
var file_data = zip.slurp(filename) file_data = zip.slurp(filename)
stone(file_data) stone(file_data)
@@ -1161,18 +1181,20 @@ Shop.remove = function(pkg) {
Shop.get = function(pkg) { Shop.get = function(pkg) {
var lock = Shop.load_lock() var lock = Shop.load_lock()
var info = null
var commit = null
if (!lock[pkg]) { if (!lock[pkg]) {
var info = Shop.resolve_package_info(pkg) info = Shop.resolve_package_info(pkg)
if (!info) { if (!info) {
throw Error("Invalid package: " + pkg) print("Invalid package: " + pkg); disrupt
} }
var commit = null commit = null
if (info != 'local') { if (info != 'local') {
commit = fetch_remote_hash(pkg) commit = fetch_remote_hash(pkg)
if (!commit) { if (!commit) {
throw Error("Could not resolve commit for " + pkg) print("Could not resolve commit for " + pkg); disrupt
} }
} }
@@ -1188,8 +1210,6 @@ Shop.get = function(pkg) {
// Compile a module // Compile a module
// List all files in a package // List all files in a package
var debug = use('debug')
Shop.file_reload = function(file) Shop.file_reload = function(file)
{ {
var info = Shop.file_info(file) var info = Shop.file_info(file)
@@ -1229,8 +1249,10 @@ function get_package_scripts(package)
var files = pkg_tools.list_files(package) var files = pkg_tools.list_files(package)
var scripts = [] var scripts = []
for (var i = 0; i < length(files); i++) { var i = 0
var file = files[i] var file = null
for (i = 0; i < length(files); i++) {
file = files[i]
if (ends_with(file, '.cm') || ends_with(file, '.ce')) { if (ends_with(file, '.cm') || ends_with(file, '.ce')) {
push(scripts, file) push(scripts, file)
} }

View File

@@ -4,19 +4,21 @@ var pkg = use('package')
// Check if current directory is a valid cell package // Check if current directory is a valid cell package
function is_valid_package(dir) { function is_valid_package(dir) {
if (!dir) dir = '.' var _dir = dir == null ? '.' : dir
return fd.is_file(dir + '/cell.toml') if (!_dir) _dir = '.'
return fd.is_file(_dir + '/cell.toml')
} }
// Get current package name from cell.toml or null // Get current package name from cell.toml or null
function get_current_package_name() { function get_current_package_name() {
if (!is_valid_package('.')) return null if (!is_valid_package('.')) return null
try { var _load = function() {
var config = pkg.load_config(null) var config = pkg.load_config(null)
return config.package || 'local' return config.package || 'local'
} catch (e) { } disruption {
return 'local' return 'local'
} }
return _load()
} }
// Get the directory for a package // Get the directory for a package
@@ -37,9 +39,10 @@ function ensure_dir(path) {
var parts = array(path, '/') var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : '' var current = starts_with(path, '/') ? '/' : ''
for (var i = 0; i < length(parts); i++) { var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue if (parts[i] == '') continue
current += parts[i] + '/' current = current + parts[i] + '/'
if (!fd.is_dir(current)) { if (!fd.is_dir(current)) {
fd.mkdir(current) fd.mkdir(current)
} }

72
link.cm
View File

@@ -34,9 +34,10 @@ function ensure_dir(path) {
if (fd.stat(path).isDirectory) return if (fd.stat(path).isDirectory) return
var parts = array(path, '/') var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : '' var current = starts_with(path, '/') ? '/' : ''
for (var i = 0; i < length(parts); i++) { var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue if (parts[i] == '') continue
current += parts[i] + '/' current = current + parts[i] + '/'
if (!fd.stat(current).isDirectory) { if (!fd.stat(current).isDirectory) {
fd.mkdir(current) fd.mkdir(current)
} }
@@ -66,14 +67,16 @@ Link.load = function() {
return link_cache return link_cache
} }
try { var _load = function() {
var content = text(fd.slurp(path)) var content = text(fd.slurp(path))
var cfg = toml.decode(content) var cfg = toml.decode(content)
link_cache = cfg.links || {} if (cfg && cfg.links) link_cache = cfg.links
} catch (e) { else link_cache = {}
log.console("Warning: Failed to load link.toml: " + e) } disruption {
print("Warning: Failed to load link.toml\n")
link_cache = {} link_cache = {}
} }
_load()
return link_cache return link_cache
} }
@@ -90,14 +93,16 @@ Link.add = function(canonical, target, shop) {
// Validate canonical package exists in shop // Validate canonical package exists in shop
var lock = shop.load_lock() var lock = shop.load_lock()
if (!lock[canonical]) { if (!lock[canonical]) {
throw Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical) print('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical + '\n')
disrupt
} }
// Validate target is a valid package // Validate target is a valid package
if (starts_with(target, '/')) { if (starts_with(target, '/')) {
// Local path - must have cell.toml // Local path - must have cell.toml
if (!fd.is_file(target + '/cell.toml')) { if (!fd.is_file(target + '/cell.toml')) {
throw Error('Target ' + target + ' is not a valid package (no cell.toml)') print('Target ' + target + ' is not a valid package (no cell.toml)\n')
disrupt
} }
} else { } else {
// Remote package target - ensure it's installed // Remote package target - ensure it's installed
@@ -115,34 +120,36 @@ Link.add = function(canonical, target, shop) {
// Read the target's cell.toml to find its dependencies // Read the target's cell.toml to find its dependencies
var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target) var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target)
var toml_path = target_path + '/cell.toml' var toml_path = target_path + '/cell.toml'
var _install_deps = null
if (fd.is_file(toml_path)) { if (fd.is_file(toml_path)) {
try { _install_deps = function() {
var content = text(fd.slurp(toml_path)) var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content) var cfg = toml.decode(content)
if (cfg.dependencies) { if (cfg && cfg.dependencies) {
arrfor(array(cfg.dependencies), function(alias) { arrfor(array(cfg.dependencies), function(alias) {
var dep_locator = cfg.dependencies[alias] var dep_locator = cfg.dependencies[alias]
// Skip local dependencies that don't exist // Skip local dependencies that don't exist
if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) { if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) {
log.console(" Skipping missing local dependency: " + dep_locator) print(" Skipping missing local dependency: " + dep_locator + "\n")
return return
} }
// Install the dependency if not already in shop // Install the dependency if not already in shop
try { var _get_dep = function() {
shop.get(dep_locator) shop.get(dep_locator)
shop.extract(dep_locator) shop.extract(dep_locator)
} catch (e) { } disruption {
log.console(` Warning: Could not install dependency ${dep_locator}: ${e.message}`) print(` Warning: Could not install dependency ${dep_locator}\n`)
log.error(e)
} }
_get_dep()
}) })
} }
} catch (e) { } disruption {
log.console(` Warning: Could not read dependencies from ${toml_path}`) print(` Warning: Could not read dependencies from ${toml_path}\n`)
} }
_install_deps()
} }
log.console("Linked " + canonical + " -> " + target) print("Linked " + canonical + " -> " + target + "\n")
return true return true
} }
@@ -154,12 +161,12 @@ Link.remove = function(canonical) {
var target_dir = get_package_abs_dir(canonical) var target_dir = get_package_abs_dir(canonical)
if (fd.is_link(target_dir)) { if (fd.is_link(target_dir)) {
fd.unlink(target_dir) fd.unlink(target_dir)
log.console("Removed symlink at " + target_dir) print("Removed symlink at " + target_dir + "\n")
} }
delete links[canonical] delete links[canonical]
Link.save(links) Link.save(links)
log.console("Unlinked " + canonical) print("Unlinked " + canonical + "\n")
return true return true
} }
@@ -174,7 +181,7 @@ Link.clear = function() {
}) })
Link.save({}) Link.save({})
log.console("Cleared all links") print("Cleared all links\n")
return true return true
} }
@@ -218,7 +225,7 @@ Link.sync_all = function(shop) {
arrfor(array(links), function(canonical) { arrfor(array(links), function(canonical) {
var target = links[canonical] var target = links[canonical]
try { var _sync = function() {
// Validate target exists // Validate target exists
var link_target = resolve_link_target(target) var link_target = resolve_link_target(target)
if (!fd.is_dir(link_target)) { if (!fd.is_dir(link_target)) {
@@ -234,10 +241,10 @@ Link.sync_all = function(shop) {
// Install dependencies of the linked package // Install dependencies of the linked package
var toml_path = link_target + '/cell.toml' var toml_path = link_target + '/cell.toml'
try { var _install = function() {
var content = text(fd.slurp(toml_path)) var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content) var cfg = toml.decode(content)
if (cfg.dependencies) { if (cfg && cfg.dependencies) {
arrfor(array(cfg.dependencies), function(alias) { arrfor(array(cfg.dependencies), function(alias) {
var dep_locator = cfg.dependencies[alias] var dep_locator = cfg.dependencies[alias]
// Skip local dependencies that don't exist // Skip local dependencies that don't exist
@@ -245,22 +252,25 @@ Link.sync_all = function(shop) {
return return
} }
// Install the dependency if not already in shop // Install the dependency if not already in shop
try { var _get = function() {
shop.get(dep_locator) shop.get(dep_locator)
shop.extract(dep_locator) shop.extract(dep_locator)
} catch (e) { } disruption {
// Silently continue - dependency may already be installed // Silently continue - dependency may already be installed
} }
_get()
}) })
} }
} catch (e) { } disruption {
// Could not read dependencies - continue anyway // Could not read dependencies - continue anyway
} }
_install()
count++ count = count + 1
} catch (e) { } disruption {
push(errors, canonical + ': ' + e.message) push(errors, canonical + ': sync failed')
} }
_sync()
}) })
return { synced: count, errors: errors } return { synced: count, errors: errors }
@@ -269,7 +279,7 @@ Link.sync_all = function(shop) {
// Check if a package is currently linked // Check if a package is currently linked
Link.is_linked = function(canonical) { Link.is_linked = function(canonical) {
var links = Link.load() var links = Link.load()
return canonical in links return links[canonical] != null
} }
// Get the link target for a package (or null if not linked) // Get the link target for a package (or null if not linked)

View File

@@ -2,10 +2,12 @@ var fd = use("fd")
var json = use("json") var json = use("json")
var tokenize = use("tokenize") var tokenize = use("tokenize")
var parse = use("parse") var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode") var mcode = use("mcode")
var filename = args[0] var filename = args[0]
var src = text(fd.slurp(filename)) var src = text(fd.slurp(filename))
var result = tokenize(src, filename) var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename) var ast = parse(result.tokens, src, filename, tokenize)
var compiled = mcode(ast) var folded = fold(ast)
var compiled = mcode(folded)
print(json.encode(compiled)) print(json.encode(compiled))

704
mcode.cm
View File

@@ -43,6 +43,8 @@ var mcode = function(ast) {
var s_func_counter = 0 var s_func_counter = 0
var s_loop_break = null var s_loop_break = null
var s_loop_continue = null var s_loop_continue = null
var s_label_map = {}
var s_pending_label = null
var s_is_arrow = false var s_is_arrow = false
var s_function_nr = 0 var s_function_nr = 0
var s_scopes = null var s_scopes = null
@@ -51,6 +53,13 @@ var mcode = function(ast) {
var s_cur_col = 0 var s_cur_col = 0
var s_filename = null var s_filename = null
// Shared closure vars for binop helpers (avoids >4 param functions)
var _bp_dest = 0
var _bp_left = 0
var _bp_right = 0
var _bp_ln = null
var _bp_rn = null
// State save/restore for nested function compilation // State save/restore for nested function compilation
var save_state = function() { var save_state = function() {
return { return {
@@ -64,6 +73,7 @@ var mcode = function(ast) {
max_slot: s_max_slot, max_slot: s_max_slot,
loop_break: s_loop_break, loop_break: s_loop_break,
loop_continue: s_loop_continue, loop_continue: s_loop_continue,
label_map: s_label_map,
is_arrow: s_is_arrow, is_arrow: s_is_arrow,
function_nr: s_function_nr, function_nr: s_function_nr,
intrinsic_cache: s_intrinsic_cache, intrinsic_cache: s_intrinsic_cache,
@@ -83,6 +93,7 @@ var mcode = function(ast) {
s_max_slot = saved.max_slot s_max_slot = saved.max_slot
s_loop_break = saved.loop_break s_loop_break = saved.loop_break
s_loop_continue = saved.loop_continue s_loop_continue = saved.loop_continue
s_label_map = saved.label_map
s_is_arrow = saved.is_arrow s_is_arrow = saved.is_arrow
s_function_nr = saved.function_nr s_function_nr = saved.function_nr
s_intrinsic_cache = saved.intrinsic_cache s_intrinsic_cache = saved.intrinsic_cache
@@ -227,20 +238,513 @@ var mcode = function(ast) {
add_instr([op, slot, label]) add_instr([op, slot, label])
} }
// ---- Decomposed op emission helpers ----
// Helper: check if an AST node is a known-int literal
var is_known_int = function(node) {
if (node == null) { return false }
return node.kind == "number" && is_integer(node.number)
}
// Helper: check if an AST node is a known-text literal
var is_known_text = function(node) {
if (node == null) { return false }
return node.kind == "text" || node.kind == "text literal"
}
// Helper: check if an AST node is a known-number literal (int or float)
var is_known_number = function(node) {
if (node == null) { return false }
return node.kind == "number"
}
// Helper: check if an AST node is a known-bool literal
var is_known_bool = function(node) {
if (node == null) { return false }
return node.kind == "true" || node.kind == "false"
}
// Helper: check if an AST node is a known-null literal
var is_known_null = function(node) {
if (node == null) { return false }
return node.kind == "null"
}
// emit_add_decomposed: int path -> text path -> float path -> disrupt
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_add_decomposed = function() {
var dest = _bp_dest
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var left_is_int = is_known_int(_bp_ln)
var left_is_text = is_known_text(_bp_ln)
var left_is_num = is_known_number(_bp_ln)
var right_is_int = is_known_int(_bp_rn)
var right_is_text = is_known_text(_bp_rn)
var right_is_num = is_known_number(_bp_rn)
var not_int = null
var not_text = null
var done = null
var err = null
// Both sides known int
if (left_is_int && right_is_int) {
emit_3("add_int", dest, left, right)
return null
}
// Both sides known text
if (left_is_text && right_is_text) {
emit_3("concat", dest, left, right)
return null
}
// Both sides known number (but not both int)
if (left_is_num && right_is_num) {
if (left_is_int && right_is_int) {
emit_3("add_int", dest, left, right)
} else {
emit_3("add_float", dest, left, right)
}
return null
}
not_int = gen_label("add_ni")
not_text = gen_label("add_nt")
done = gen_label("add_done")
err = gen_label("add_err")
// Int path
t0 = alloc_slot()
if (!left_is_int) {
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_int)
}
t1 = alloc_slot()
if (!right_is_int) {
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_int)
}
emit_3("add_int", dest, left, right)
emit_jump(done)
// Text path
emit_label(not_int)
if (!left_is_text) {
emit_2("is_text", t0, left)
emit_jump_cond("jump_false", t0, not_text)
}
if (!right_is_text) {
emit_2("is_text", t1, right)
emit_jump_cond("jump_false", t1, not_text)
}
emit_3("concat", dest, left, right)
emit_jump(done)
// Float path
emit_label(not_text)
if (!left_is_num) {
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, err)
}
if (!right_is_num) {
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, err)
}
emit_3("add_float", dest, left, right)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null
}
// emit_numeric_binop: int path -> float path -> disrupt
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_numeric_binop = function(int_op, float_op) {
var dest = _bp_dest
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var left_is_int = is_known_int(_bp_ln)
var left_is_num = is_known_number(_bp_ln)
var right_is_int = is_known_int(_bp_rn)
var right_is_num = is_known_number(_bp_rn)
var not_int = null
var done = null
var err = null
// Both sides known int
if (left_is_int && right_is_int) {
emit_3(int_op, dest, left, right)
return null
}
// Both sides known number (but not both int)
if (left_is_num && right_is_num) {
emit_3(float_op, dest, left, right)
return null
}
not_int = gen_label("num_ni")
done = gen_label("num_done")
err = gen_label("num_err")
t0 = alloc_slot()
if (!left_is_int) {
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_int)
}
t1 = alloc_slot()
if (!right_is_int) {
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_int)
}
emit_3(int_op, dest, left, right)
emit_jump(done)
emit_label(not_int)
if (!left_is_num) {
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, err)
}
if (!right_is_num) {
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, err)
}
emit_3(float_op, dest, left, right)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null
}
// emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false)
// reads _bp_dest, _bp_left, _bp_right from closure
var emit_eq_decomposed = function() {
var dest = _bp_dest
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var done = gen_label("eq_done")
var not_int = gen_label("eq_ni")
var not_num = gen_label("eq_nn")
var not_text = gen_label("eq_nt")
var not_null = gen_label("eq_nnl")
var not_bool = gen_label("eq_nb")
// Identical check
emit_3("is_identical", dest, left, right)
emit_jump_cond("jump_true", dest, done)
// Int path
t0 = alloc_slot()
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_int)
t1 = alloc_slot()
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_int)
emit_3("eq_int", dest, left, right)
emit_jump(done)
// Float path
emit_label(not_int)
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, not_num)
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, not_num)
emit_3("eq_float", dest, left, right)
emit_jump(done)
// Text path
emit_label(not_num)
emit_2("is_text", t0, left)
emit_jump_cond("jump_false", t0, not_text)
emit_2("is_text", t1, right)
emit_jump_cond("jump_false", t1, not_text)
emit_3("eq_text", dest, left, right)
emit_jump(done)
// Null path
emit_label(not_text)
emit_2("is_null", t0, left)
emit_jump_cond("jump_false", t0, not_null)
emit_2("is_null", t1, right)
emit_jump_cond("jump_false", t1, not_null)
emit_1("true", dest)
emit_jump(done)
// Bool path
emit_label(not_null)
emit_2("is_bool", t0, left)
emit_jump_cond("jump_false", t0, not_bool)
emit_2("is_bool", t1, right)
emit_jump_cond("jump_false", t1, not_bool)
emit_3("eq_bool", dest, left, right)
emit_jump(done)
// Mismatch -> false
emit_label(not_bool)
emit_1("false", dest)
emit_label(done)
return null
}
// emit_ne_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(true)
// reads _bp_dest, _bp_left, _bp_right from closure
var emit_ne_decomposed = function() {
var dest = _bp_dest
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var done = gen_label("ne_done")
var not_ident = gen_label("ne_nid")
var not_int = gen_label("ne_ni")
var not_num = gen_label("ne_nn")
var not_text = gen_label("ne_nt")
var not_null = gen_label("ne_nnl")
var not_bool = gen_label("ne_nb")
// Identical -> false
emit_3("is_identical", dest, left, right)
emit_jump_cond("jump_true", dest, not_ident)
// If jump_true doesn't fire, dest already holds false, continue to checks
emit_jump(not_int)
emit_label(not_ident)
emit_1("false", dest)
emit_jump(done)
// Int path
emit_label(not_int)
t0 = alloc_slot()
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_num)
t1 = alloc_slot()
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_num)
emit_3("ne_int", dest, left, right)
emit_jump(done)
// Float path
emit_label(not_num)
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, not_text)
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, not_text)
emit_3("ne_float", dest, left, right)
emit_jump(done)
// Text path
emit_label(not_text)
emit_2("is_text", t0, left)
emit_jump_cond("jump_false", t0, not_null)
emit_2("is_text", t1, right)
emit_jump_cond("jump_false", t1, not_null)
emit_3("ne_text", dest, left, right)
emit_jump(done)
// Null path
emit_label(not_null)
emit_2("is_null", t0, left)
emit_jump_cond("jump_false", t0, not_bool)
emit_2("is_null", t1, right)
emit_jump_cond("jump_false", t1, not_bool)
emit_1("false", dest)
emit_jump(done)
// Bool path
var mismatch = gen_label("ne_mis")
emit_label(not_bool)
emit_2("is_bool", t0, left)
emit_jump_cond("jump_false", t0, mismatch)
emit_2("is_bool", t1, right)
emit_jump_cond("jump_false", t1, mismatch)
emit_3("ne_bool", dest, left, right)
emit_jump(done)
// Mismatch -> true (ne of different types is true)
emit_label(mismatch)
emit_1("true", dest)
emit_label(done)
return null
}
// emit_relational: int -> float -> text -> disrupt
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_relational = function(int_op, float_op, text_op) {
var dest = _bp_dest
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var left_is_int = is_known_int(_bp_ln)
var left_is_num = is_known_number(_bp_ln)
var left_is_text = is_known_text(_bp_ln)
var right_is_int = is_known_int(_bp_rn)
var right_is_num = is_known_number(_bp_rn)
var right_is_text = is_known_text(_bp_rn)
var not_int = null
var not_num = null
var done = null
var err = null
// Both known int
if (left_is_int && right_is_int) {
emit_3(int_op, dest, left, right)
return null
}
// Both known number
if (left_is_num && right_is_num) {
emit_3(float_op, dest, left, right)
return null
}
// Both known text
if (left_is_text && right_is_text) {
emit_3(text_op, dest, left, right)
return null
}
not_int = gen_label("rel_ni")
not_num = gen_label("rel_nn")
done = gen_label("rel_done")
err = gen_label("rel_err")
t0 = alloc_slot()
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_int)
t1 = alloc_slot()
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_int)
emit_3(int_op, dest, left, right)
emit_jump(done)
emit_label(not_int)
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, not_num)
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, not_num)
emit_3(float_op, dest, left, right)
emit_jump(done)
emit_label(not_num)
emit_2("is_text", t0, left)
emit_jump_cond("jump_false", t0, err)
emit_2("is_text", t1, right)
emit_jump_cond("jump_false", t1, err)
emit_3(text_op, dest, left, right)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null
}
// emit_neg_decomposed: int path -> float path -> disrupt
var emit_neg_decomposed = function(dest, src, src_node) {
var t0 = 0
var not_int = null
var done = null
var err = null
if (is_known_int(src_node)) {
emit_2("neg_int", dest, src)
return null
}
if (is_known_number(src_node)) {
emit_2("neg_float", dest, src)
return null
}
not_int = gen_label("neg_ni")
done = gen_label("neg_done")
err = gen_label("neg_err")
t0 = alloc_slot()
emit_2("is_int", t0, src)
emit_jump_cond("jump_false", t0, not_int)
emit_2("neg_int", dest, src)
emit_jump(done)
emit_label(not_int)
emit_2("is_num", t0, src)
emit_jump_cond("jump_false", t0, err)
emit_2("neg_float", dest, src)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null
}
// Central router: maps op string to decomposition helper
// Sets _bp_* closure vars then calls helper with reduced args
var emit_binop = function(op_str, dest, left, right) {
_bp_dest = dest
_bp_left = left
_bp_right = right
if (op_str == "add") {
emit_add_decomposed()
} else if (op_str == "subtract") {
emit_numeric_binop("sub_int", "sub_float")
} else if (op_str == "multiply") {
emit_numeric_binop("mul_int", "mul_float")
} else if (op_str == "divide") {
emit_numeric_binop("div_int", "div_float")
} else if (op_str == "modulo") {
emit_numeric_binop("mod_int", "mod_float")
} else if (op_str == "eq") {
emit_eq_decomposed()
} else if (op_str == "ne") {
emit_ne_decomposed()
} else if (op_str == "lt") {
emit_relational("lt_int", "lt_float", "lt_text")
} else if (op_str == "le") {
emit_relational("le_int", "le_float", "le_text")
} else if (op_str == "gt") {
emit_relational("gt_int", "gt_float", "gt_text")
} else if (op_str == "ge") {
emit_relational("ge_int", "ge_float", "ge_text")
} else {
// Passthrough for bitwise, pow, in, etc.
emit_3(op_str, dest, left, right)
}
return null
}
var emit_get_prop = function(dest, obj, prop) { var emit_get_prop = function(dest, obj, prop) {
add_instr(["load", dest, obj, prop]) add_instr(["load_field", dest, obj, prop])
} }
var emit_set_prop = function(obj, prop, val) { var emit_set_prop = function(obj, prop, val) {
add_instr(["store", obj, val, prop]) add_instr(["store_field", obj, val, prop])
} }
var emit_get_elem = function(dest, obj, idx) { var emit_get_elem = function(dest, obj, idx, access_kind) {
emit_3("load", dest, obj, idx) if (access_kind == "index") {
emit_3("load_index", dest, obj, idx)
} else if (access_kind == "field") {
emit_3("load_field", dest, obj, idx)
} else {
emit_3("load_dynamic", dest, obj, idx)
}
} }
var emit_set_elem = function(obj, idx, val) { var emit_set_elem = function(obj, idx, val, access_kind) {
emit_3("store", obj, val, idx) if (access_kind == "index") {
emit_3("store_index", obj, val, idx)
} else if (access_kind == "field") {
emit_3("store_field", obj, val, idx)
} else {
emit_3("store_dynamic", obj, val, idx)
}
} }
var emit_call = function(dest, func_slot, args) { var emit_call = function(dest, func_slot, args) {
@@ -261,23 +765,114 @@ var mcode = function(ast) {
} }
var emit_call_method = function(dest, obj, prop, args) { var emit_call_method = function(dest, obj, prop, args) {
var instr = ["callmethod", dest, obj, prop] var argc = length(args)
var check = alloc_slot()
var record_path = gen_label("record_path")
var done_label = gen_label("call_done")
var _i = 0 var _i = 0
while (_i < length(args)) { var arg_idx = 0
push(instr, args[_i])
// Check if obj is a proxy function (arity 2)
emit_2("is_proxy", check, obj)
emit_jump_cond("jump_false", check, record_path)
// Function proxy path: call obj(prop_name, [args...]) with this=null
var null_slot = alloc_slot()
emit_const_null(null_slot)
var name_str = alloc_slot()
emit_const_str(name_str, prop)
var args_arr = alloc_slot()
var arr_instr = ["array", args_arr, argc]
_i = 0
while (_i < argc) {
push(arr_instr, args[_i])
_i = _i + 1 _i = _i + 1
} }
add_instr(instr) add_instr(arr_instr)
var pf = alloc_slot()
emit_3("frame", pf, obj, 2)
emit_3("setarg", pf, 0, null_slot)
emit_3("setarg", pf, 1, name_str)
emit_3("setarg", pf, 2, args_arr)
emit_2("invoke", pf, dest)
emit_jump(done_label)
// Record path: load method, call with this=obj
emit_label(record_path)
var method_slot = alloc_slot()
add_instr(["load_field", method_slot, obj, prop])
var frame_slot = alloc_slot()
emit_3("frame", frame_slot, method_slot, argc)
emit_3("setarg", frame_slot, 0, obj)
arg_idx = 1
_i = 0
while (_i < argc) {
emit_3("setarg", frame_slot, arg_idx, args[_i])
arg_idx = arg_idx + 1
_i = _i + 1
}
emit_2("invoke", frame_slot, dest)
emit_label(done_label)
} }
var emit_call_method_dyn = function(dest, obj, key_reg, args) { var emit_call_method_dyn = function(dest, obj, key_reg, args) {
var instr = ["callmethod_dyn", dest, obj, key_reg] var argc = length(args)
var check = alloc_slot()
var record_path = gen_label("dyn_record_path")
var done_label = gen_label("dyn_call_done")
var _i = 0 var _i = 0
while (_i < length(args)) { var arg_idx = 0
push(instr, args[_i])
// Check if obj is a proxy function (arity 2)
emit_2("is_proxy", check, obj)
emit_jump_cond("jump_false", check, record_path)
// Function proxy path (dynamic key): must be text
var key_ok = alloc_slot()
var error_path = gen_label("dyn_error")
emit_2("is_text", key_ok, key_reg)
emit_jump_cond("jump_false", key_ok, error_path)
var null_slot = alloc_slot()
emit_const_null(null_slot)
var args_arr = alloc_slot()
var arr_instr = ["array", args_arr, argc]
_i = 0
while (_i < argc) {
push(arr_instr, args[_i])
_i = _i + 1 _i = _i + 1
} }
add_instr(instr) add_instr(arr_instr)
var pf = alloc_slot()
emit_3("frame", pf, obj, 2)
emit_3("setarg", pf, 0, null_slot)
emit_3("setarg", pf, 1, key_reg)
emit_3("setarg", pf, 2, args_arr)
emit_2("invoke", pf, dest)
emit_jump(done_label)
// Error path: non-text key on function disrupts
emit_label(error_path)
emit_0("disrupt")
emit_jump(done_label)
// Record path: load method dynamically, call with this=obj
emit_label(record_path)
var method_slot = alloc_slot()
emit_3("load_dynamic", method_slot, obj, key_reg)
var frame_slot = alloc_slot()
emit_3("frame", frame_slot, method_slot, argc)
emit_3("setarg", frame_slot, 0, obj)
arg_idx = 1
_i = 0
while (_i < argc) {
emit_3("setarg", frame_slot, arg_idx, args[_i])
arg_idx = arg_idx + 1
_i = _i + 1
}
emit_2("invoke", frame_slot, dest)
emit_label(done_label)
} }
var emit_go_call = function(func_slot, args) { var emit_go_call = function(func_slot, args) {
@@ -463,7 +1058,9 @@ var mcode = function(ast) {
if (op == null) { if (op == null) {
op = "add" op = "add"
} }
emit_3(op, dest, left_slot, right_slot) _bp_ln = left
_bp_rn = right
emit_binop(op, dest, left_slot, right_slot)
return dest return dest
} }
@@ -515,7 +1112,9 @@ var mcode = function(ast) {
} }
right_slot = gen_expr(right, -1) right_slot = gen_expr(right, -1)
dest = alloc_slot() dest = alloc_slot()
emit_3(op, dest, left_slot, right_slot) _bp_ln = null
_bp_rn = right
emit_binop(op, dest, left_slot, right_slot)
if (level == 0) { if (level == 0) {
local = find_var(name) local = find_var(name)
if (local >= 0) { if (local >= 0) {
@@ -538,7 +1137,9 @@ var mcode = function(ast) {
emit_get_prop(old_val, obj_slot, prop) emit_get_prop(old_val, obj_slot, prop)
right_slot = gen_expr(right, -1) right_slot = gen_expr(right, -1)
dest = alloc_slot() dest = alloc_slot()
emit_3(op, dest, old_val, right_slot) _bp_ln = null
_bp_rn = right
emit_binop(op, dest, old_val, right_slot)
emit_set_prop(obj_slot, prop, dest) emit_set_prop(obj_slot, prop, dest)
return dest return dest
} else if (left_kind == "[") { } else if (left_kind == "[") {
@@ -547,11 +1148,13 @@ var mcode = function(ast) {
obj_slot = gen_expr(obj, -1) obj_slot = gen_expr(obj, -1)
idx_slot = gen_expr(idx_expr, -1) idx_slot = gen_expr(idx_expr, -1)
old_val = alloc_slot() old_val = alloc_slot()
emit_get_elem(old_val, obj_slot, idx_slot) emit_get_elem(old_val, obj_slot, idx_slot, left.access_kind)
right_slot = gen_expr(right, -1) right_slot = gen_expr(right, -1)
dest = alloc_slot() dest = alloc_slot()
emit_3(op, dest, old_val, right_slot) _bp_ln = null
emit_set_elem(obj_slot, idx_slot, dest) _bp_rn = right
emit_binop(op, dest, old_val, right_slot)
emit_set_elem(obj_slot, idx_slot, dest, left.access_kind)
return dest return dest
} }
return -1 return -1
@@ -624,7 +1227,7 @@ var mcode = function(ast) {
idx_expr = left.right idx_expr = left.right
obj_slot = gen_expr(obj, -1) obj_slot = gen_expr(obj, -1)
idx_slot = gen_expr(idx_expr, -1) idx_slot = gen_expr(idx_expr, -1)
emit_set_elem(obj_slot, idx_slot, val_slot) emit_set_elem(obj_slot, idx_slot, val_slot, left.access_kind)
} }
return val_slot return val_slot
} }
@@ -679,6 +1282,7 @@ var mcode = function(ast) {
var arith_op = null var arith_op = null
var operand_kind = null var operand_kind = null
var one_slot = 0 var one_slot = 0
var one_node = null
var old_slot = 0 var old_slot = 0
var local = 0 var local = 0
var new_slot = 0 var new_slot = 0
@@ -843,7 +1447,7 @@ var mcode = function(ast) {
obj_slot = gen_expr(obj, -1) obj_slot = gen_expr(obj, -1)
idx_slot = gen_expr(idx, -1) idx_slot = gen_expr(idx, -1)
slot = alloc_slot() slot = alloc_slot()
emit_get_elem(slot, obj_slot, idx_slot) emit_get_elem(slot, obj_slot, idx_slot, expr.access_kind)
return slot return slot
} }
@@ -899,7 +1503,9 @@ var mcode = function(ast) {
a0 = gen_expr(args_list[0], -1) a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1) a1 = gen_expr(args_list[1], -1)
d = alloc_slot() d = alloc_slot()
emit_3(mop, d, a0, a1) _bp_ln = args_list[0]
_bp_rn = args_list[1]
emit_binop(mop, d, a0, a1)
return d return d
} }
@@ -946,7 +1552,7 @@ var mcode = function(ast) {
if (kind == "-unary") { if (kind == "-unary") {
operand_slot = gen_expr(expr.expression, -1) operand_slot = gen_expr(expr.expression, -1)
slot = alloc_slot() slot = alloc_slot()
emit_2("neg", slot, operand_slot) emit_neg_decomposed(slot, operand_slot, expr.expression)
return slot return slot
} }
if (kind == "+unary") { if (kind == "+unary") {
@@ -961,6 +1567,7 @@ var mcode = function(ast) {
operand_kind = operand.kind operand_kind = operand.kind
one_slot = alloc_slot() one_slot = alloc_slot()
emit_2("int", one_slot, 1) emit_2("int", one_slot, 1)
one_node = {kind: "number", number: 1}
if (operand_kind == "name") { if (operand_kind == "name") {
name = operand.name name = operand.name
@@ -983,7 +1590,9 @@ var mcode = function(ast) {
emit_access_intrinsic(old_slot, name) emit_access_intrinsic(old_slot, name)
} }
new_slot = alloc_slot() new_slot = alloc_slot()
emit_3(arith_op, new_slot, old_slot, one_slot) _bp_ln = null
_bp_rn = one_node
emit_binop(arith_op, new_slot, old_slot, one_slot)
if (level == 0) { if (level == 0) {
local = find_var(name) local = find_var(name)
if (local >= 0) { if (local >= 0) {
@@ -1003,7 +1612,9 @@ var mcode = function(ast) {
old_slot = alloc_slot() old_slot = alloc_slot()
emit_get_prop(old_slot, obj_slot, prop) emit_get_prop(old_slot, obj_slot, prop)
new_slot = alloc_slot() new_slot = alloc_slot()
emit_3(arith_op, new_slot, old_slot, one_slot) _bp_ln = null
_bp_rn = one_node
emit_binop(arith_op, new_slot, old_slot, one_slot)
emit_set_prop(obj_slot, prop, new_slot) emit_set_prop(obj_slot, prop, new_slot)
return postfix ? old_slot : new_slot return postfix ? old_slot : new_slot
} else if (operand_kind == "[") { } else if (operand_kind == "[") {
@@ -1012,10 +1623,12 @@ var mcode = function(ast) {
obj_slot = gen_expr(obj, -1) obj_slot = gen_expr(obj, -1)
idx_slot = gen_expr(idx_expr, -1) idx_slot = gen_expr(idx_expr, -1)
old_slot = alloc_slot() old_slot = alloc_slot()
emit_get_elem(old_slot, obj_slot, idx_slot) emit_get_elem(old_slot, obj_slot, idx_slot, operand.access_kind)
new_slot = alloc_slot() new_slot = alloc_slot()
emit_3(arith_op, new_slot, old_slot, one_slot) _bp_ln = null
emit_set_elem(obj_slot, idx_slot, new_slot) _bp_rn = one_node
emit_binop(arith_op, new_slot, old_slot, one_slot)
emit_set_elem(obj_slot, idx_slot, new_slot, operand.access_kind)
return postfix ? old_slot : new_slot return postfix ? old_slot : new_slot
} }
} }
@@ -1281,6 +1894,13 @@ var mcode = function(ast) {
return null return null
} }
if (kind == "label") {
s_pending_label = stmt.name
gen_statement(stmt.statement)
s_pending_label = null
return null
}
if (kind == "while") { if (kind == "while") {
cond = stmt.expression cond = stmt.expression
stmts = stmt.statements stmts = stmt.statements
@@ -1290,6 +1910,10 @@ var mcode = function(ast) {
old_continue = s_loop_continue old_continue = s_loop_continue
s_loop_break = end_label s_loop_break = end_label
s_loop_continue = start_label s_loop_continue = start_label
if (s_pending_label != null) {
s_label_map[s_pending_label] = {break_target: end_label, continue_target: start_label}
s_pending_label = null
}
emit_label(start_label) emit_label(start_label)
cond_slot = gen_expr(cond, -1) cond_slot = gen_expr(cond, -1)
emit_jump_cond("jump_false", cond_slot, end_label) emit_jump_cond("jump_false", cond_slot, end_label)
@@ -1315,6 +1939,10 @@ var mcode = function(ast) {
old_continue = s_loop_continue old_continue = s_loop_continue
s_loop_break = end_label s_loop_break = end_label
s_loop_continue = cond_label s_loop_continue = cond_label
if (s_pending_label != null) {
s_label_map[s_pending_label] = {break_target: end_label, continue_target: cond_label}
s_pending_label = null
}
emit_label(start_label) emit_label(start_label)
_i = 0 _i = 0
while (_i < length(stmts)) { while (_i < length(stmts)) {
@@ -1342,6 +1970,10 @@ var mcode = function(ast) {
old_continue = s_loop_continue old_continue = s_loop_continue
s_loop_break = end_label s_loop_break = end_label
s_loop_continue = update_label s_loop_continue = update_label
if (s_pending_label != null) {
s_label_map[s_pending_label] = {break_target: end_label, continue_target: update_label}
s_pending_label = null
}
if (init != null) { if (init != null) {
init_kind = init.kind init_kind = init.kind
if (init_kind == "var" || init_kind == "def") { if (init_kind == "var" || init_kind == "def") {
@@ -1417,14 +2049,18 @@ var mcode = function(ast) {
} }
if (kind == "break") { if (kind == "break") {
if (s_loop_break != null) { if (stmt.name != null && s_label_map[stmt.name] != null) {
emit_jump(s_label_map[stmt.name].break_target)
} else if (s_loop_break != null) {
emit_jump(s_loop_break) emit_jump(s_loop_break)
} }
return null return null
} }
if (kind == "continue") { if (kind == "continue") {
if (s_loop_continue != null) { if (stmt.name != null && s_label_map[stmt.name] != null) {
emit_jump(s_label_map[stmt.name].continue_target)
} else if (s_loop_continue != null) {
emit_jump(s_loop_continue) emit_jump(s_loop_continue)
} }
return null return null
@@ -1452,7 +2088,9 @@ var mcode = function(ast) {
case_expr = case_node.expression case_expr = case_node.expression
case_val = gen_expr(case_expr, -1) case_val = gen_expr(case_expr, -1)
cmp_slot = alloc_slot() cmp_slot = alloc_slot()
emit_3("eq", cmp_slot, switch_val, case_val) _bp_ln = null
_bp_rn = case_expr
emit_binop("eq", cmp_slot, switch_val, case_val)
emit_jump_cond("jump_true", cmp_slot, case_label) emit_jump_cond("jump_true", cmp_slot, case_label)
push(case_labels, case_label) push(case_labels, case_label)
} }
@@ -1548,6 +2186,7 @@ var mcode = function(ast) {
s_intrinsic_cache = [] s_intrinsic_cache = []
s_loop_break = null s_loop_break = null
s_loop_continue = null s_loop_continue = null
s_label_map = {}
s_is_arrow = is_arrow s_is_arrow = is_arrow
@@ -1741,6 +2380,7 @@ var mcode = function(ast) {
s_func_counter = 0 s_func_counter = 0
s_loop_break = null s_loop_break = null
s_loop_continue = null s_loop_continue = null
s_label_map = {}
s_function_nr = 0 s_function_nr = 0
// Scan scope // Scan scope

Binary file not shown.

View File

@@ -46,7 +46,6 @@ src += [ # core
'miniz.c', 'miniz.c',
'runtime.c', 'runtime.c',
'mach.c', 'mach.c',
'mcode.c',
'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c' 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'
] ]

View File

@@ -14,11 +14,11 @@ static void js_enet_host_finalizer(JSRuntime *rt, JSValue val)
if (host) enet_host_destroy(host); if (host) enet_host_destroy(host);
} }
static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) //static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
{ //{
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id); // ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
JS_MarkValue(rt, *(JSValue*)peer->data, mark_func); // JS_MarkValue(rt, *(JSValue*)peer->data, mark_func);
} //}
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val) static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val)
{ {
@@ -62,7 +62,7 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar
enet_uint32 outgoing_bandwidth = 0; enet_uint32 outgoing_bandwidth = 0;
JSValue obj; JSValue obj;
if (argc < 1 || !JS_IsObject(argv[0])) { if (argc < 1 || !JS_IsRecord(argv[0])) {
host = enet_host_create(NULL, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth); host = enet_host_create(NULL, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
if (!host) return JS_ThrowInternalError(ctx, "Failed to create ENet client host"); if (!host) return JS_ThrowInternalError(ctx, "Failed to create ENet client host");
goto wrap; goto wrap;
@@ -414,7 +414,7 @@ static JSClassDef enet_host = {
static JSClassDef enet_peer_class = { static JSClassDef enet_peer_class = {
"ENetPeer", "ENetPeer",
.finalizer = js_enet_peer_finalizer, .finalizer = js_enet_peer_finalizer,
.gc_mark = js_enet_peer_mark // .gc_mark = js_enet_peer_mark
}; };
JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue *argv) JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue *argv)

View File

@@ -132,7 +132,7 @@ JSC_CCALL(socket_getaddrinfo,
// Store the addrinfo pointer as an internal property // Store the addrinfo pointer as an internal property
// We'll need to handle this differently since we can't wrap it // We'll need to handle this differently since we can't wrap it
// For now, we'll skip storing the raw addrinfo // For now, we'll skip storing the raw addrinfo
JS_SetPropertyUint32(js, ret, idx++, info); JS_SetPropertyNumber(js, ret, idx++, info);
} }
freeaddrinfo(res); freeaddrinfo(res);

31
num_torture.cm Normal file
View File

@@ -0,0 +1,31 @@
// num_torture.cm — integer math torture test
// Pure integer arithmetic so it stays on the fast int path.
// Returns the final checksum so the caller can verify correctness.
var n = 5000000
var sum = 0
var i = 0
var a = 0
var b = 0
while (i < n) {
a = (i * 7 + 13) % 10007
b = (a * a) % 10007
sum = (sum + b) % 1000000007
i = i + 1
}
return function(n) {
var i = 0
var a = 0
var b = 0
var sum = 0
while (i < n) {
a = (i * 7 + 13) % 10007
b = (a * a) % 10007
sum = (sum + b) % 1000000007
i = i + 1
}
return sum
}

View File

@@ -51,7 +51,7 @@ package.load_config = function(name)
return config_cache[config_path] return config_cache[config_path]
if (!fd.is_file(config_path)) { if (!fd.is_file(config_path)) {
throw Error(`${config_path} does not exist`) print(`${config_path} does not exist`); disrupt
} }
var content = text(fd.slurp(config_path)) var content = text(fd.slurp(config_path))
@@ -101,11 +101,12 @@ package.alias_to_package = function(name, alias)
} }
// alias is optional // alias is optional
package.add_dependency = function(name, locator, alias = locator) package.add_dependency = function(name, locator, alias)
{ {
var _alias = alias == null ? locator : alias
var config = package.load_config(name) var config = package.load_config(name)
if (!config.dependencies) config.dependencies = {} if (!config.dependencies) config.dependencies = {}
config.dependencies[alias] = locator config.dependencies[_alias] = locator
package.save_config(name, config) package.save_config(name, config)
} }
@@ -115,10 +116,11 @@ package.remove_dependency = function(name, locator)
var config = package.load_config(name) var config = package.load_config(name)
if (!config.dependencies) return if (!config.dependencies) return
var alias = null
if (config.dependencies[locator]) if (config.dependencies[locator])
delete config.dependencies[locator] delete config.dependencies[locator]
else { else {
var alias = package.find_alias(name, locator) alias = package.find_alias(name, locator)
if (alias) if (alias)
delete config.dependencies[alias] delete config.dependencies[alias]
} }
@@ -133,8 +135,9 @@ package.find_package_dir = function(file)
if (fd.is_file(dir)) if (fd.is_file(dir))
dir = fd.dirname(dir) dir = fd.dirname(dir)
var toml_path = null
while (dir && length(dir) > 0) { while (dir && length(dir) > 0) {
var toml_path = dir + '/cell.toml' toml_path = dir + '/cell.toml'
if (fd.is_file(toml_path)) { if (fd.is_file(toml_path)) {
return dir return dir
} }
@@ -158,21 +161,23 @@ package.split_alias = function(name, path)
var parts = array(path, '/') var parts = array(path, '/')
var first_part = parts[0] var first_part = parts[0]
try { var _split = function() {
var config = package.load_config(name) var config = package.load_config(name)
if (!config) return null if (!config) return null
var deps = config.dependencies var deps = config.dependencies
var dep_locator = null
var remaining_path = null
if (deps && deps[first_part]) { if (deps && deps[first_part]) {
var dep_locator = deps[first_part] dep_locator = deps[first_part]
var remaining_path = text(array(parts, 1), '/') remaining_path = text(array(parts, 1), '/')
return { package: dep_locator, path: remaining_path } return { package: dep_locator, path: remaining_path }
} }
} catch (e) { return null
// Config doesn't exist or couldn't be loaded } disruption {
return null
} }
return _split()
return null
} }
package.gather_dependencies = function(name) package.gather_dependencies = function(name)
@@ -209,17 +214,22 @@ package.list_files = function(pkg) {
var list = fd.readdir(current_dir) var list = fd.readdir(current_dir)
if (!list) return if (!list) return
for (var i = 0; i < length(list); i++) { var i = 0
var item = list[i] var item = null
var full_path = null
var rel_path = null
var st = null
for (i = 0; i < length(list); i++) {
item = list[i]
if (item == '.' || item == '..') continue if (item == '.' || item == '..') continue
if (starts_with(item, '.')) continue if (starts_with(item, '.')) continue
// Skip build directories in root // Skip build directories in root
var full_path = current_dir + "/" + item full_path = current_dir + "/" + item
var rel_path = current_prefix ? current_prefix + "/" + item : item rel_path = current_prefix ? current_prefix + "/" + item : item
var st = fd.stat(full_path) st = fd.stat(full_path)
if (st.isDirectory) { if (st.isDirectory) {
walk(full_path, rel_path) walk(full_path, rel_path)
} else { } else {
@@ -237,7 +247,8 @@ package.list_files = function(pkg) {
package.list_modules = function(name) { package.list_modules = function(name) {
var files = package.list_files(name) var files = package.list_files(name)
var modules = [] var modules = []
for (var i = 0; i < length(files); i++) { var i = 0
for (i = 0; i < length(files); i++) {
if (ends_with(files[i], '.cm')) { if (ends_with(files[i], '.cm')) {
push(modules, text(files[i], 0, -3)) push(modules, text(files[i], 0, -3))
} }
@@ -248,7 +259,8 @@ package.list_modules = function(name) {
package.list_programs = function(name) { package.list_programs = function(name) {
var files = package.list_files(name) var files = package.list_files(name)
var programs = [] var programs = []
for (var i = 0; i < length(files); i++) { var i = 0
for (i = 0; i < length(files); i++) {
if (ends_with(files[i], '.ce')) { if (ends_with(files[i], '.ce')) {
push(programs, text(files[i], 0, -3)) push(programs, text(files[i], 0, -3))
} }
@@ -265,14 +277,16 @@ package.get_flags = function(name, flag_type, target) {
var flags = [] var flags = []
// Base flags // Base flags
var base = null
var target_flags = null
if (config.compilation && config.compilation[flag_type]) { if (config.compilation && config.compilation[flag_type]) {
var base = config.compilation[flag_type] base = config.compilation[flag_type]
flags = array(flags, filter(array(base, /\s+/), function(f) { return length(f) > 0 })) flags = array(flags, filter(array(base, /\s+/), function(f) { return length(f) > 0 }))
} }
// Target-specific flags // Target-specific flags
if (target && config.compilation && config.compilation[target] && config.compilation[target][flag_type]) { if (target && config.compilation && config.compilation[target] && config.compilation[target][flag_type]) {
var target_flags = config.compilation[target][flag_type] target_flags = config.compilation[target][flag_type]
flags = array(flags, filter(array(target_flags, /\s+/), function(f) { return length(f) > 0 })) flags = array(flags, filter(array(target_flags, /\s+/), function(f) { return length(f) > 0 }))
} }
@@ -290,23 +304,36 @@ package.get_c_files = function(name, target, exclude_main) {
// Group files by their base name (without target suffix) // Group files by their base name (without target suffix)
var groups = {} // base_key -> { generic: file, variants: { target: file } } var groups = {} // base_key -> { generic: file, variants: { target: file } }
for (var i = 0; i < length(files); i++) { var i = 0
var file = files[i] var file = null
var ext = null
var base = null
var name_part = null
var dir_part = null
var dir = null
var is_variant = null
var variant_target = null
var generic_name = null
var t = 0
var suffix = null
var group_key = null
for (i = 0; i < length(files); i++) {
file = files[i]
if (!ends_with(file, '.c') && !ends_with(file, '.cpp')) continue if (!ends_with(file, '.c') && !ends_with(file, '.cpp')) continue
var ext = ends_with(file, '.cpp') ? '.cpp' : '.c' ext = ends_with(file, '.cpp') ? '.cpp' : '.c'
var base = text(file, 0, -length(ext)) base = text(file, 0, -length(ext))
var name_part = fd.basename(base) name_part = fd.basename(base)
var dir_part = fd.dirname(base) dir_part = fd.dirname(base)
var dir = (dir_part && dir_part != '.') ? dir_part + '/' : '' dir = (dir_part && dir_part != '.') ? dir_part + '/' : ''
// Check for target suffix // Check for target suffix
var is_variant = false is_variant = false
var variant_target = null variant_target = null
var generic_name = name_part generic_name = name_part
for (var t = 0; t < length(known_targets); t++) { for (t = 0; t < length(known_targets); t++) {
var suffix = '_' + known_targets[t] suffix = '_' + known_targets[t]
if (ends_with(name_part, suffix)) { if (ends_with(name_part, suffix)) {
is_variant = true is_variant = true
variant_target = known_targets[t] variant_target = known_targets[t]
@@ -315,7 +342,7 @@ package.get_c_files = function(name, target, exclude_main) {
} }
} }
var group_key = dir + generic_name + ext group_key = dir + generic_name + ext
if (!groups[group_key]) { if (!groups[group_key]) {
groups[group_key] = { generic: null, variants: {} } groups[group_key] = { generic: null, variants: {} }
} }
@@ -332,6 +359,7 @@ package.get_c_files = function(name, target, exclude_main) {
arrfor(array(groups), function(key) { arrfor(array(groups), function(key) {
var group = groups[key] var group = groups[key]
var selected = null var selected = null
var basename = null
// Prefer target-specific variant if available // Prefer target-specific variant if available
if (target && group.variants[target]) { if (target && group.variants[target]) {
@@ -343,7 +371,7 @@ package.get_c_files = function(name, target, exclude_main) {
if (selected) { if (selected) {
// Skip main.c if requested // Skip main.c if requested
if (exclude_main) { if (exclude_main) {
var basename = fd.basename(selected) basename = fd.basename(selected)
if (basename == 'main.c' || starts_with(basename, 'main_')) return if (basename == 'main.c' || starts_with(basename, 'main_')) return
} }
push(result, selected) push(result, selected)

View File

@@ -1,8 +1,9 @@
var fd = use("fd") var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize") var tokenize = use("tokenize")
var parse = use("parse") var parse = use("parse")
var filename = args[0] var filename = args[0]
var src = text(fd.slurp(filename)) var src = text(fd.slurp(filename))
var result = tokenize(src, filename) var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename) var ast = parse(result.tokens, src, filename, tokenize)
print(json.encode(ast)) print(json.encode(ast))

401
parse.cm
View File

@@ -1,18 +1,5 @@
def CP_SLASH = 47
def CP_BSLASH = 92
var is_alpha = function(c) {
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122)
}
var parse = function(tokens, src, filename, tokenizer) { var parse = function(tokens, src, filename, tokenizer) {
var _src_len = length(src) var _src_len = length(src)
var cp = []
var _i = 0
while (_i < _src_len) {
push(cp, codepoint(src[_i]))
_i = _i + 1
}
// ============================================================ // ============================================================
// Parser Cursor // Parser Cursor
@@ -22,6 +9,9 @@ var parse = function(tokens, src, filename, tokenizer) {
var tok = null var tok = null
var got_lf = false var got_lf = false
var prev_tok = null var prev_tok = null
var _control_depth = 0
var _control_type = null
var _expecting_body = false
var advance = function() { var advance = function() {
var t = null var t = null
@@ -75,7 +65,7 @@ var parse = function(tokens, src, filename, tokenizer) {
var errors = [] var errors = []
var error_count = 0 var error_count = 0
var function_nr = 1 var fn_counter = 1
var ast_node = function(kind, token) { var ast_node = function(kind, token) {
return { return {
@@ -103,14 +93,18 @@ var parse = function(tokens, src, filename, tokenizer) {
}) })
} }
var _keywords = {
"if": true, in: true, "do": true, go: true,
"var": true, def: true, "for": true,
"else": true, "this": true, "null": true, "true": true,
"false": true, "while": true, "break": true,
"return": true, "delete": true,
disrupt: true, "function": true, "continue": true,
disruption: true
}
var is_keyword = function(kind) { var is_keyword = function(kind) {
return kind == "if" || kind == "in" || kind == "do" || kind == "go" || return _keywords[kind] == true
kind == "var" || kind == "def" || kind == "for" ||
kind == "else" || kind == "this" || kind == "null" || kind == "true" ||
kind == "false" || kind == "while" || kind == "break" ||
kind == "return" || kind == "delete" ||
kind == "disrupt" || kind == "function" || kind == "continue" ||
kind == "disruption"
} }
// ============================================================ // ============================================================
@@ -165,17 +159,18 @@ var parse = function(tokens, src, filename, tokenizer) {
var params = null var params = null
var param = null var param = null
var rpos = 0 var rpos = 0
var pattern_str = "" var pattern_parts = null
var flags = "" var flags_parts = null
var tv = null var tv = null
var has_interp = false var has_interp = false
var ti = 0 var ti = 0
var tpl_list = null var tpl_list = null
var fmt = null var fmt_parts = null
var idx = 0 var idx = 0
var tvi = 0 var tvi = 0
var tvlen = 0 var tvlen = 0
var depth = 0 var depth = 0
var expr_parts = null
var expr_str = null var expr_str = null
var tc = null var tc = null
var tq = null var tq = null
@@ -184,6 +179,9 @@ var parse = function(tokens, src, filename, tokenizer) {
var sub_ast = null var sub_ast = null
var sub_stmt = null var sub_stmt = null
var sub_expr = null var sub_expr = null
var meth_old_cd = 0
var meth_old_ct = null
var meth_old_eb = false
if (k == "number") { if (k == "number") {
node = ast_node("number", start) node = ast_node("number", start)
@@ -218,52 +216,53 @@ var parse = function(tokens, src, filename, tokenizer) {
node = ast_node("text literal", start) node = ast_node("text literal", start)
tpl_list = [] tpl_list = []
node.list = tpl_list node.list = tpl_list
fmt = "" fmt_parts = []
idx = 0 idx = 0
tvi = 0 tvi = 0
tvlen = length(tv) tvlen = length(tv)
while (tvi < tvlen) { while (tvi < tvlen) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) { if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
esc_ch = tv[tvi + 1] esc_ch = tv[tvi + 1]
if (esc_ch == "n") { fmt = fmt + "\n" } if (esc_ch == "n") { push(fmt_parts, "\n") }
else if (esc_ch == "t") { fmt = fmt + "\t" } else if (esc_ch == "t") { push(fmt_parts, "\t") }
else if (esc_ch == "r") { fmt = fmt + "\r" } else if (esc_ch == "r") { push(fmt_parts, "\r") }
else if (esc_ch == "\\") { fmt = fmt + "\\" } else if (esc_ch == "\\") { push(fmt_parts, "\\") }
else if (esc_ch == "`") { fmt = fmt + "`" } else if (esc_ch == "`") { push(fmt_parts, "`") }
else if (esc_ch == "$") { fmt = fmt + "$" } else if (esc_ch == "$") { push(fmt_parts, "$") }
else if (esc_ch == "0") { fmt = fmt + character(0) } else if (esc_ch == "0") { push(fmt_parts, character(0)) }
else { fmt = fmt + esc_ch } else { push(fmt_parts, esc_ch) }
tvi = tvi + 2 tvi = tvi + 2
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") { } else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
tvi = tvi + 2 tvi = tvi + 2
depth = 1 depth = 1
expr_str = "" expr_parts = []
while (tvi < tvlen && depth > 0) { while (tvi < tvlen && depth > 0) {
tc = tv[tvi] tc = tv[tvi]
if (tc == "{") { depth = depth + 1; expr_str = expr_str + tc; tvi = tvi + 1 } if (tc == "{") { depth = depth + 1; push(expr_parts, tc); tvi = tvi + 1 }
else if (tc == "}") { else if (tc == "}") {
depth = depth - 1 depth = depth - 1
if (depth > 0) { expr_str = expr_str + tc } if (depth > 0) { push(expr_parts, tc) }
tvi = tvi + 1 tvi = tvi + 1
} }
else if (tc == "'" || tc == "\"" || tc == "`") { else if (tc == "'" || tc == "\"" || tc == "`") {
tq = tc tq = tc
expr_str = expr_str + tc push(expr_parts, tc)
tvi = tvi + 1 tvi = tvi + 1
while (tvi < tvlen && tv[tvi] != tq) { while (tvi < tvlen && tv[tvi] != tq) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) { if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
expr_str = expr_str + tv[tvi] push(expr_parts, tv[tvi])
tvi = tvi + 1 tvi = tvi + 1
} }
expr_str = expr_str + tv[tvi] push(expr_parts, tv[tvi])
tvi = tvi + 1 tvi = tvi + 1
} }
if (tvi < tvlen) { expr_str = expr_str + tv[tvi]; tvi = tvi + 1 } if (tvi < tvlen) { push(expr_parts, tv[tvi]); tvi = tvi + 1 }
} else { } else {
expr_str = expr_str + tc push(expr_parts, tc)
tvi = tvi + 1 tvi = tvi + 1
} }
} }
expr_str = text(expr_parts)
expr_tokens = tokenizer(expr_str, "<template>").tokens expr_tokens = tokenizer(expr_str, "<template>").tokens
sub_ast = parse(expr_tokens, expr_str, "<template>", tokenizer) sub_ast = parse(expr_tokens, expr_str, "<template>", tokenizer)
if (sub_ast != null && sub_ast.statements != null && length(sub_ast.statements) > 0) { if (sub_ast != null && sub_ast.statements != null && length(sub_ast.statements) > 0) {
@@ -276,14 +275,16 @@ var parse = function(tokens, src, filename, tokenizer) {
} }
push(tpl_list, sub_expr) push(tpl_list, sub_expr)
} }
fmt = fmt + "{" + text(idx) + "}" push(fmt_parts, "{")
push(fmt_parts, text(idx))
push(fmt_parts, "}")
idx = idx + 1 idx = idx + 1
} else { } else {
fmt = fmt + tv[tvi] push(fmt_parts, tv[tvi])
tvi = tvi + 1 tvi = tvi + 1
} }
} }
node.value = fmt node.value = text(fmt_parts)
advance() advance()
ast_node_end(node) ast_node_end(node)
return node return node
@@ -390,7 +391,7 @@ var parse = function(tokens, src, filename, tokenizer) {
ast_node_end(param) ast_node_end(param)
if (tok.kind == "=" || tok.kind == "|") { if (tok.kind == "=" || tok.kind == "|") {
advance() advance()
param.expression = parse_expr() param.expression = parse_assign_expr()
} }
push(params, param) push(params, param)
} else { } else {
@@ -404,6 +405,12 @@ var parse = function(tokens, src, filename, tokenizer) {
else if (tok.kind == "eof") parse_error(tok, "unterminated method parameter list") else if (tok.kind == "eof") parse_error(tok, "unterminated method parameter list")
if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters") if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters")
fn.arity = length(params) fn.arity = length(params)
meth_old_cd = _control_depth
meth_old_ct = _control_type
meth_old_eb = _expecting_body
_control_depth = 0
_control_type = null
_expecting_body = false
if (tok.kind == "{") { if (tok.kind == "{") {
advance() advance()
fn.statements = parse_block_statements() fn.statements = parse_block_statements()
@@ -412,11 +419,19 @@ var parse = function(tokens, src, filename, tokenizer) {
} else { } else {
parse_error(tok, "expected '{' for method body") parse_error(tok, "expected '{' for method body")
} }
fn.function_nr = function_nr _control_depth = meth_old_cd
function_nr = function_nr + 1 _control_type = meth_old_ct
_expecting_body = meth_old_eb
fn.function_nr = fn_counter
fn_counter = fn_counter + 1
ast_node_end(fn) ast_node_end(fn)
pair.right = fn pair.right = fn
} else if (!(is_ident && (tok.kind == "," || tok.kind == "}"))) { } else if (is_ident && (tok.kind == "," || tok.kind == "}")) {
right = ast_node("name", pair.left)
right.name = pair.left.name
ast_node_end(right)
pair.right = right
} else {
parse_error(tok, "expected ':' after property name") parse_error(tok, "expected ':' after property name")
} }
push(list, pair) push(list, pair)
@@ -445,24 +460,25 @@ var parse = function(tokens, src, filename, tokenizer) {
if (k == "/") { if (k == "/") {
node = ast_node("regexp", start) node = ast_node("regexp", start)
rpos = tok.at + 1 rpos = tok.at + 1
pattern_str = "" pattern_parts = []
flags = "" flags_parts = []
while (rpos < _src_len && cp[rpos] != CP_SLASH) { while (rpos < _src_len && src[rpos] != "/") {
if (cp[rpos] == CP_BSLASH && rpos + 1 < _src_len) { if (src[rpos] == "\\" && rpos + 1 < _src_len) {
pattern_str = pattern_str + character(cp[rpos]) + character(cp[rpos + 1]) push(pattern_parts, src[rpos])
push(pattern_parts, src[rpos + 1])
rpos = rpos + 2 rpos = rpos + 2
} else { } else {
pattern_str = pattern_str + character(cp[rpos]) push(pattern_parts, src[rpos])
rpos = rpos + 1 rpos = rpos + 1
} }
} }
if (rpos < _src_len) rpos = rpos + 1 if (rpos < _src_len) rpos = rpos + 1
while (rpos < _src_len && is_alpha(cp[rpos])) { while (rpos < _src_len && is_letter(src[rpos])) {
flags = flags + character(cp[rpos]) push(flags_parts, src[rpos])
rpos = rpos + 1 rpos = rpos + 1
} }
node.pattern = pattern_str node.pattern = text(pattern_parts)
if (length(flags) > 0) node.flags = flags if (length(flags_parts) > 0) node.flags = text(flags_parts)
// Skip all tokens consumed by the regex re-scan // Skip all tokens consumed by the regex re-scan
while (true) { while (true) {
advance() advance()
@@ -488,6 +504,9 @@ var parse = function(tokens, src, filename, tokenizer) {
var index = null var index = null
var arg = null var arg = null
var args_list = null var args_list = null
var one_node = null
var binop_node = null
var op = null
if (node == null) return null if (node == null) return null
while (true) { while (true) {
start = tok start = tok
@@ -563,6 +582,10 @@ var parse = function(tokens, src, filename, tokenizer) {
var node = null var node = null
var expr = null var expr = null
var k = tok.kind var k = tok.kind
var operand = null
var one_node = null
var binop_node = null
var op = null
if (k == "!") { if (k == "!") {
advance() advance()
node = ast_node("!", start) node = ast_node("!", start)
@@ -591,19 +614,22 @@ var parse = function(tokens, src, filename, tokenizer) {
ast_node_end(node) ast_node_end(node)
return node return node
} }
if (k == "++") { if (k == "++" || k == "--") {
advance() advance()
node = ast_node("++", start) operand = parse_unary()
node.expression = parse_unary() one_node = ast_node("number", start)
node.postfix = false one_node.number = 1
ast_node_end(node) one_node.value = "1"
return node ast_node_end(one_node)
} op = "+"
if (k == "--") { if (k == "--") op = "-"
advance() binop_node = ast_node(op, start)
node = ast_node("--", start) binop_node.left = operand
node.expression = parse_unary() binop_node.right = one_node
node.postfix = false ast_node_end(binop_node)
node = ast_node("assign", start)
node.left = operand
node.right = binop_node
ast_node_end(node) ast_node_end(node)
return node return node
} }
@@ -688,6 +714,13 @@ var parse = function(tokens, src, filename, tokenizer) {
"&&=": "&&=", "||=": "||=" "&&=": "&&=", "||=": "||="
} }
var compound_binop = {
"+=": "+", "-=": "-", "*=": "*", "/=": "/", "%=": "%",
"<<=": "<<", ">>=": ">>", ">>>=": ">>>",
"&=": "&", "^=": "^", "|=": "|", "**=": "**",
"&&=": "&&", "||=": "||"
}
parse_assign = function(unused) { parse_assign = function(unused) {
var left_node = parse_ternary() var left_node = parse_ternary()
var start = null var start = null
@@ -696,6 +729,8 @@ var parse = function(tokens, src, filename, tokenizer) {
var node = null var node = null
var left_kind = null var left_kind = null
var right_kind = null var right_kind = null
var binop = null
var binop_node = null
if (left_node == null) return null if (left_node == null) return null
start = tok start = tok
kind = assign_ops[tok.kind] kind = assign_ops[tok.kind]
@@ -708,12 +743,23 @@ var parse = function(tokens, src, filename, tokenizer) {
advance() advance()
right_node = parse_assign() right_node = parse_assign()
node = ast_node(kind, start)
node.left = left_node
node.right = right_node
if (left_node.kind == "[" && left_node.right == null) node.push = true binop = compound_binop[kind]
if (right_node != null && right_node.kind == "[" && right_node.right == null) node.pop = true if (binop != null) {
binop_node = ast_node(binop, start)
binop_node.left = left_node
binop_node.right = right_node
ast_node_end(binop_node)
node = ast_node("assign", start)
node.left = left_node
node.right = binop_node
} else {
node = ast_node(kind, start)
node.left = left_node
node.right = right_node
if (left_node.kind == "[" && left_node.right == null) node.push = true
if (right_node != null && right_node.kind == "[" && right_node.right == null) node.pop = true
}
ast_node_end(node) ast_node_end(node)
return node return node
@@ -791,9 +837,10 @@ var parse = function(tokens, src, filename, tokenizer) {
var param = null var param = null
var prev_names = null var prev_names = null
var pname = null var pname = null
var dup = false
var j = 0
var old_dis = 0 var old_dis = 0
var old_cd = _control_depth
var old_ct = _control_type
var old_eb = _expecting_body
if (in_disruption) { if (in_disruption) {
parse_error(tok, "cannot define function inside disruption clause") parse_error(tok, "cannot define function inside disruption clause")
@@ -815,13 +862,7 @@ var parse = function(tokens, src, filename, tokenizer) {
param = ast_node("name", tok) param = ast_node("name", tok)
param.name = tok.value param.name = tok.value
pname = tok.value pname = tok.value
dup = false if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
j = 0
while (j < length(prev_names)) {
if (prev_names[j] == pname) { dup = true; break }
j = j + 1
}
if (dup) parse_error(tok, "duplicate parameter name '" + pname + "'")
push(prev_names, pname) push(prev_names, pname)
advance() advance()
ast_node_end(param) ast_node_end(param)
@@ -846,6 +887,9 @@ var parse = function(tokens, src, filename, tokenizer) {
if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters") if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters")
node.arity = length(params) node.arity = length(params)
_control_depth = 0
_control_type = null
_expecting_body = false
if (tok.kind == "{") { if (tok.kind == "{") {
advance() advance()
stmts = parse_block_statements() stmts = parse_block_statements()
@@ -871,8 +915,11 @@ var parse = function(tokens, src, filename, tokenizer) {
} }
} }
node.function_nr = function_nr _control_depth = old_cd
function_nr = function_nr + 1 _control_type = old_ct
_expecting_body = old_eb
node.function_nr = fn_counter
fn_counter = fn_counter + 1
ast_node_end(node) ast_node_end(node)
return node return node
} }
@@ -887,8 +934,9 @@ var parse = function(tokens, src, filename, tokenizer) {
var expr = null var expr = null
var prev_names = null var prev_names = null
var pname = null var pname = null
var dup = false var old_cd = _control_depth
var j = 0 var old_ct = _control_type
var old_eb = _expecting_body
node.arrow = true node.arrow = true
if (in_disruption) { if (in_disruption) {
@@ -911,13 +959,7 @@ var parse = function(tokens, src, filename, tokenizer) {
param = ast_node("name", tok) param = ast_node("name", tok)
param.name = tok.value param.name = tok.value
pname = tok.value pname = tok.value
dup = false if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
j = 0
while (j < length(prev_names)) {
if (prev_names[j] == pname) { dup = true; break }
j = j + 1
}
if (dup) parse_error(tok, "duplicate parameter name '" + pname + "'")
push(prev_names, pname) push(prev_names, pname)
advance() advance()
ast_node_end(param) ast_node_end(param)
@@ -945,6 +987,9 @@ var parse = function(tokens, src, filename, tokenizer) {
advance() advance()
} }
_control_depth = 0
_control_type = null
_expecting_body = false
if (tok.kind == "{") { if (tok.kind == "{") {
advance() advance()
stmts = parse_block_statements() stmts = parse_block_statements()
@@ -960,8 +1005,11 @@ var parse = function(tokens, src, filename, tokenizer) {
node.statements = stmts node.statements = stmts
} }
node.function_nr = function_nr _control_depth = old_cd
function_nr = function_nr + 1 _control_type = old_ct
_expecting_body = old_eb
node.function_nr = fn_counter
fn_counter = fn_counter + 1
ast_node_end(node) ast_node_end(node)
return node return node
} }
@@ -991,8 +1039,25 @@ var parse = function(tokens, src, filename, tokenizer) {
var elif = null var elif = null
var p1_tok = null var p1_tok = null
var labeled_stmt = null var labeled_stmt = null
var depth = 0
var saved_ct = null
var saved_cd = 0
var saved_eb = false
if (k == "{") { if (k == "{") {
if (!_expecting_body) {
parse_error(start, "bare block '{ ... }' is not a valid statement; use a function, if, while, or for instead")
advance()
depth = 1
while (tok.kind != "eof" && depth > 0) {
if (tok.kind == "{") depth = depth + 1
else if (tok.kind == "}") depth = depth - 1
if (depth > 0) advance()
}
if (tok.kind == "}") advance()
return null
}
_expecting_body = false
node = ast_node("block", start) node = ast_node("block", start)
advance() advance()
stmts = parse_block_statements() stmts = parse_block_statements()
@@ -1003,6 +1068,9 @@ var parse = function(tokens, src, filename, tokenizer) {
} }
if (k == "var" || k == "def") { if (k == "var" || k == "def") {
if (_control_depth > 0) {
parse_error(start, "'" + k + "' declarations must appear at function body level, not inside '" + _control_type + "'; move this declaration before the '" + _control_type + "' statement")
}
kind_name = k kind_name = k
is_def = (k == "def") is_def = (k == "def")
advance() advance()
@@ -1029,6 +1097,8 @@ var parse = function(tokens, src, filename, tokenizer) {
} }
} else if (is_def) { } else if (is_def) {
parse_error(start, "missing initializer for constant '" + var_name + "'") parse_error(start, "missing initializer for constant '" + var_name + "'")
} else {
parse_error(start, "'var' declarations must be initialized; use 'var " + var_name + " = null' if no value is needed")
} }
ast_node_end(node) ast_node_end(node)
push(decls, node) push(decls, node)
@@ -1057,6 +1127,11 @@ var parse = function(tokens, src, filename, tokenizer) {
else parse_error(tok, "expected ')' after if condition") else parse_error(tok, "expected ')' after if condition")
then_stmts = [] then_stmts = []
node.then = then_stmts node.then = then_stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "if"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement() body = parse_statement()
if (body != null) push(then_stmts, body) if (body != null) push(then_stmts, body)
else_ifs = [] else_ifs = []
@@ -1064,15 +1139,22 @@ var parse = function(tokens, src, filename, tokenizer) {
if (tok.kind == "else") { if (tok.kind == "else") {
advance() advance()
if (tok.kind == "if") { if (tok.kind == "if") {
_control_depth = saved_cd
_control_type = saved_ct
elif = parse_statement() elif = parse_statement()
if (elif != null) push(else_ifs, elif) if (elif != null) push(else_ifs, elif)
ast_node_end(node)
return node
} else { } else {
else_stmts = [] else_stmts = []
node.else = else_stmts node.else = else_stmts
_expecting_body = true
body = parse_statement() body = parse_statement()
if (body != null) push(else_stmts, body) if (body != null) push(else_stmts, body)
} }
} }
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node) ast_node_end(node)
return node return node
} }
@@ -1088,8 +1170,15 @@ var parse = function(tokens, src, filename, tokenizer) {
else parse_error(tok, "expected ')' after while condition") else parse_error(tok, "expected ')' after while condition")
stmts = [] stmts = []
node.statements = stmts node.statements = stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "while"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement() body = parse_statement()
if (body != null) push(stmts, body) if (body != null) push(stmts, body)
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node) ast_node_end(node)
return node return node
} }
@@ -1099,8 +1188,15 @@ var parse = function(tokens, src, filename, tokenizer) {
advance() advance()
stmts = [] stmts = []
node.statements = stmts node.statements = stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "do"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement() body = parse_statement()
if (body != null) push(stmts, body) if (body != null) push(stmts, body)
_control_depth = saved_cd
_control_type = saved_ct
if (tok.kind == "while") advance() if (tok.kind == "while") advance()
else parse_error(tok, "expected 'while' after do body") else parse_error(tok, "expected 'while' after do body")
if (tok.kind == "(") advance() if (tok.kind == "(") advance()
@@ -1121,6 +1217,7 @@ var parse = function(tokens, src, filename, tokenizer) {
else parse_error(tok, "expected '(' after for") else parse_error(tok, "expected '(' after for")
if (tok.kind != ";") { if (tok.kind != ";") {
if (tok.kind == "var" || tok.kind == "def") { if (tok.kind == "var" || tok.kind == "def") {
parse_error(tok, "'" + tok.kind + "' declarations cannot appear in the for initializer; declare variables before the for loop")
init = parse_statement() init = parse_statement()
node.init = init node.init = init
} else { } else {
@@ -1144,8 +1241,15 @@ var parse = function(tokens, src, filename, tokenizer) {
else parse_error(tok, "expected ')' after for clauses") else parse_error(tok, "expected ')' after for clauses")
stmts = [] stmts = []
node.statements = stmts node.statements = stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "for"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement() body = parse_statement()
if (body != null) push(stmts, body) if (body != null) push(stmts, body)
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node) ast_node_end(node)
return node return node
} }
@@ -1216,6 +1320,36 @@ var parse = function(tokens, src, filename, tokenizer) {
} }
if (k == "name") { if (k == "name") {
if (tok.value == "try" || tok.value == "catch" || tok.value == "finally") {
parse_error(start, "'" + tok.value + "' is not supported; use disrupt/disruption instead")
sync_to_statement()
return null
}
if (tok.value == "throw") {
parse_error(start, "'throw' is not supported; use disrupt instead")
sync_to_statement()
return null
}
if (tok.value == "class") {
parse_error(start, "'class' is not supported; use meme()/proto() instead")
sync_to_statement()
return null
}
if (tok.value == "new") {
parse_error(start, "'new' is not supported; use meme()/proto() instead")
sync_to_statement()
return null
}
if (tok.value == "switch" || tok.value == "case") {
parse_error(start, "'" + tok.value + "' is not supported; use if/else instead")
sync_to_statement()
return null
}
if (tok.value == "let" || tok.value == "const") {
parse_error(start, "'" + tok.value + "' is not supported; use var/def instead")
sync_to_statement()
return null
}
p1_tok = peek_ahead(1) p1_tok = peek_ahead(1)
if (p1_tok.kind == ":") { if (p1_tok.kind == ":") {
node = ast_node("label", start) node = ast_node("label", start)
@@ -1345,12 +1479,7 @@ var parse = function(tokens, src, filename, tokenizer) {
} }
var sem_add_intrinsic = function(name) { var sem_add_intrinsic = function(name) {
var i = 0 if (find(intrinsics, name) == null) push(intrinsics, name)
while (i < length(intrinsics)) {
if (intrinsics[i] == name) return null
i = i + 1
}
push(intrinsics, name)
} }
var functino_names = { var functino_names = {
@@ -1364,12 +1493,31 @@ var parse = function(tokens, src, filename, tokenizer) {
return functino_names[name] == true return functino_names[name] == true
} }
var sem_propagate_vars = function(parent, child) { var derive_type_tag = function(expr) {
var i = 0 if (expr == null) return null
while (i < length(child.vars)) { var k = expr.kind
push(parent.vars, child.vars[i]) if (k == "array") return "array"
i = i + 1 if (k == "record") return "record"
if (k == "function") return "function"
if (k == "text" || k == "text literal") return "text"
if (k == "number") {
if (is_integer(expr.number)) return "integer"
return "number"
} }
if (k == "true" || k == "false") return "logical"
if (k == "null") return "null"
return null
}
var _assign_kinds = {
assign: true, "+=": true, "-=": true, "*=": true, "/=": true, "%=": true,
"<<=": true, ">>=": true, ">>>=": true,
"&=": true, "^=": true, "|=": true, "**=": true,
"&&=": true, "||=": true
}
var sem_propagate_vars = function(parent, child) {
parent.vars = array(parent.vars, child.vars)
} }
var sem_build_scope_record = function(scope) { var sem_build_scope_record = function(scope) {
@@ -1385,7 +1533,8 @@ var parse = function(tokens, src, filename, tokenizer) {
function_nr: v.function_nr, function_nr: v.function_nr,
nr_uses: v.nr_uses, nr_uses: v.nr_uses,
closure: v.closure == 1, closure: v.closure == 1,
level: 0 level: 0,
type_tag: v.type_tag
} }
slots = slots + 1 slots = slots + 1
if (v.closure) close_slots = close_slots + 1 if (v.closure) close_slots = close_slots + 1
@@ -1487,10 +1636,7 @@ var parse = function(tokens, src, filename, tokenizer) {
var def_val = null var def_val = null
var sr = null var sr = null
if (kind == "assign" || kind == "+=" || kind == "-=" || kind == "*=" || if (_assign_kinds[kind] == true) {
kind == "/=" || kind == "%=" || kind == "<<=" || kind == ">>=" ||
kind == ">>>=" || kind == "&=" || kind == "^=" || kind == "|=" ||
kind == "**=" || kind == "&&=" || kind == "||=") {
sem_check_assign_target(scope, expr.left) sem_check_assign_target(scope, expr.left)
sem_check_expr(scope, expr.right) sem_check_expr(scope, expr.right)
return null return null
@@ -1515,6 +1661,21 @@ var parse = function(tokens, src, filename, tokenizer) {
operand.level = -1 operand.level = -1
} }
} }
} else if (operand != null) {
sem_check_assign_target(scope, operand)
}
return null
}
if (kind == "[") {
sem_check_expr(scope, expr.left)
sem_check_expr(scope, expr.right)
if (expr.right != null) {
if (expr.right.kind == "number" && is_integer(expr.right.number)) {
expr.access_kind = "index"
} else if (expr.right.kind == "text") {
expr.access_kind = "field"
}
} }
return null return null
} }
@@ -1525,7 +1686,7 @@ var parse = function(tokens, src, filename, tokenizer) {
kind == "&&" || kind == "||" || kind == "&" || kind == "&&" || kind == "||" || kind == "&" ||
kind == "|" || kind == "^" || kind == "<<" || kind == ">>" || kind == "|" || kind == "^" || kind == "<<" || kind == ">>" ||
kind == ">>>" || kind == "**" || kind == "in" || kind == ">>>" || kind == "**" || kind == "in" ||
kind == "." || kind == "[") { kind == ".") {
sem_check_expr(scope, expr.left) sem_check_expr(scope, expr.left)
sem_check_expr(scope, expr.right) sem_check_expr(scope, expr.right)
return null return null
@@ -1634,6 +1795,7 @@ var parse = function(tokens, src, filename, tokenizer) {
if (r.level > 0) r.v.closure = 1 if (r.level > 0) r.v.closure = 1
} else { } else {
expr.level = -1 expr.level = -1
expr.intrinsic = true
sem_add_intrinsic(name) sem_add_intrinsic(name)
} }
} }
@@ -1657,6 +1819,7 @@ var parse = function(tokens, src, filename, tokenizer) {
var pname = null var pname = null
var def_val = null var def_val = null
var sr = null var sr = null
var tt = null
if (kind == "var_list") { if (kind == "var_list") {
i = 0 i = 0
@@ -1696,6 +1859,13 @@ var parse = function(tokens, src, filename, tokenizer) {
} }
} }
sem_check_expr(scope, stmt.right) sem_check_expr(scope, stmt.right)
if (name != null) {
tt = derive_type_tag(stmt.right)
if (tt != null) {
existing = sem_find_var(scope, name)
if (existing != null) existing.type_tag = tt
}
}
return null return null
} }
@@ -1773,6 +1943,9 @@ var parse = function(tokens, src, filename, tokenizer) {
if (kind == "return" || kind == "go") { if (kind == "return" || kind == "go") {
sem_check_expr(scope, stmt.expression) sem_check_expr(scope, stmt.expression)
if (stmt.expression != null && stmt.expression.kind == "(") {
stmt.tail = true
}
return null return null
} }

Binary file not shown.

View File

@@ -128,7 +128,7 @@ struct listfiles_ctx {
static void listfiles_cb(const char *path, void *userdata) { static void listfiles_cb(const char *path, void *userdata) {
struct listfiles_ctx *ctx = (struct listfiles_ctx*)userdata; struct listfiles_ctx *ctx = (struct listfiles_ctx*)userdata;
JS_SetPropertyUint32(ctx->js, ctx->array, ctx->index++, JS_NewString(ctx->js, path)); JS_SetPropertyNumber(ctx->js, ctx->array, ctx->index++, JS_NewString(ctx->js, path));
} }
JSC_SCALL(file_listfiles, JSC_SCALL(file_listfiles,

View File

@@ -79,7 +79,7 @@ static void json_decode_array_value(json_decoder *decoder, int pos, json_value v
case kJSONString: jsval = JS_NewString(ctx->js, value.data.stringval); break; case kJSONString: jsval = JS_NewString(ctx->js, value.data.stringval); break;
default: jsval = JS_NULL; break; default: jsval = JS_NULL; break;
} }
JS_SetPropertyUint32(ctx->js, container, pos, jsval); JS_SetPropertyNumber(ctx->js, container, pos, jsval);
} }
// --- JSON Encoder Context --- // --- JSON Encoder Context ---
@@ -128,7 +128,7 @@ static void encode_js_array(json_encoder *enc, JSContext *js, JSValue arr) {
JS_FreeValue(js, lenVal); JS_FreeValue(js, lenVal);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
enc->addArrayMember(enc); enc->addArrayMember(enc);
JSValue val = JS_GetPropertyUint32(js, arr, i); JSValue val = JS_GetPropertyNumber(js, arr, i);
encode_js_value(enc, js, val); encode_js_value(enc, js, val);
JS_FreeValue(js, val); JS_FreeValue(js, val);
} }

View File

@@ -57,7 +57,7 @@ static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) {
boards->boards[i].boardID ? JS_NewString(g_scoreboard_js, boards->boards[i].boardID) : JS_NULL); boards->boards[i].boardID ? JS_NewString(g_scoreboard_js, boards->boards[i].boardID) : JS_NULL);
JS_SetPropertyStr(g_scoreboard_js, board, "name", JS_SetPropertyStr(g_scoreboard_js, board, "name",
boards->boards[i].name ? JS_NewString(g_scoreboard_js, boards->boards[i].name) : JS_NULL); boards->boards[i].name ? JS_NewString(g_scoreboard_js, boards->boards[i].name) : JS_NULL);
JS_SetPropertyUint32(g_scoreboard_js, arr, i, board); JS_SetPropertyNumber(g_scoreboard_js, arr, i, board);
} }
args[0] = arr; args[0] = arr;
} else { } else {
@@ -83,7 +83,7 @@ static void scores_cb(PDScoresList *scores, const char *errorMessage) {
JS_SetPropertyStr(g_scoreboard_js, obj, "limit", JS_NewInt32(g_scoreboard_js, scores->limit)); JS_SetPropertyStr(g_scoreboard_js, obj, "limit", JS_NewInt32(g_scoreboard_js, scores->limit));
JSValue arr = JS_NewArray(g_scoreboard_js); JSValue arr = JS_NewArray(g_scoreboard_js);
for (unsigned int i = 0; i < scores->count; i++) { for (unsigned int i = 0; i < scores->count; i++) {
JS_SetPropertyUint32(g_scoreboard_js, arr, i, score_to_js(g_scoreboard_js, &scores->scores[i])); JS_SetPropertyNumber(g_scoreboard_js, arr, i, score_to_js(g_scoreboard_js, &scores->scores[i]));
} }
JS_SetPropertyStr(g_scoreboard_js, obj, "scores", arr); JS_SetPropertyStr(g_scoreboard_js, obj, "scores", arr);
args[0] = obj; args[0] = obj;

206
pronto.cm
View File

@@ -4,9 +4,9 @@
// Time is in seconds. // Time is in seconds.
function make_reason(factory, excuse, evidence) { function make_reason(factory, excuse, evidence) {
def reason = Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`) var msg = 'pronto.' + factory
reason.evidence = evidence if (excuse) msg = msg + ': ' + excuse
return reason return { message: msg, evidence: evidence }
} }
function is_requestor(fn) { function is_requestor(fn) {
@@ -14,21 +14,27 @@ function is_requestor(fn) {
} }
function check_requestors(list, factory) { function check_requestors(list, factory) {
if (!is_array(list) || some(list, r => !is_requestor(r))) if (!is_array(list) || some(list, r => !is_requestor(r))) {
throw make_reason(factory, 'Bad requestor array.', list) print(make_reason(factory, 'Bad requestor array.', list).message + '\n')
disrupt
}
} }
function check_callback(cb, factory) { function check_callback(cb, factory) {
if (!is_function(cb) || length(cb) != 2) if (!is_function(cb) || length(cb) != 2) {
throw make_reason(factory, 'Not a callback.', cb) print(make_reason(factory, 'Not a callback.', cb).message + '\n')
disrupt
}
} }
// fallback(requestor_array) // fallback(requestor_array)
// Tries each requestor in order until one succeeds. // Tries each requestor in order until one succeeds.
function fallback(requestor_array) { function fallback(requestor_array) {
def factory = 'fallback' def factory = 'fallback'
if (!is_array(requestor_array) || length(requestor_array) == 0) if (!is_array(requestor_array) || length(requestor_array) == 0) {
throw make_reason(factory, 'Empty requestor array.') print(make_reason(factory, 'Empty requestor array.').message + '\n')
disrupt
}
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
return function fallback_requestor(callback, value) { return function fallback_requestor(callback, value) {
@@ -38,9 +44,11 @@ function fallback(requestor_array) {
var cancelled = false var cancelled = false
function cancel(reason) { function cancel(reason) {
var _c = null
cancelled = true cancelled = true
if (current_cancel) { if (current_cancel) {
try { current_cancel(reason) } catch (_) {} _c = function() { current_cancel(reason) } disruption {}
_c()
current_cancel = null current_cancel = null
} }
} }
@@ -53,9 +61,9 @@ function fallback(requestor_array) {
} }
def requestor = requestor_array[index] def requestor = requestor_array[index]
index += 1 index = index + 1
try { var _run = function() {
current_cancel = requestor(function(val, reason) { current_cancel = requestor(function(val, reason) {
if (cancelled) return if (cancelled) return
current_cancel = null current_cancel = null
@@ -65,9 +73,10 @@ function fallback(requestor_array) {
try_next() try_next()
} }
}, value) }, value)
} catch (ex) { } disruption {
try_next() try_next()
} }
_run()
} }
try_next() try_next()
@@ -79,25 +88,32 @@ function fallback(requestor_array) {
// Runs requestors in parallel, collecting all results. // Runs requestors in parallel, collecting all results.
function parallel(requestor_array, throttle, need) { function parallel(requestor_array, throttle, need) {
def factory = 'parallel' def factory = 'parallel'
if (!is_array(requestor_array)) if (!is_array(requestor_array)) {
throw make_reason(factory, 'Not an array.', requestor_array) print(make_reason(factory, 'Not an array.', requestor_array).message + '\n')
disrupt
}
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
def length = length(requestor_array) def len = length(requestor_array)
if (length == 0) if (len == 0)
return function(callback, value) { callback([]) } return function(callback, value) { callback([]) }
if (need == null) need = length var _need = need
if (!is_number(need) || need < 0 || need > length) if (_need == null) _need = len
throw make_reason(factory, 'Bad need.', need) if (!is_number(_need) || _need < 0 || _need > len) {
print(make_reason(factory, 'Bad need.', _need).message + '\n')
disrupt
}
if (throttle != null && (!is_number(throttle) || throttle < 1)) if (throttle != null && (!is_number(throttle) || throttle < 1)) {
throw make_reason(factory, 'Bad throttle.', throttle) print(make_reason(factory, 'Bad throttle.', throttle).message + '\n')
disrupt
}
return function parallel_requestor(callback, value) { return function parallel_requestor(callback, value) {
check_callback(callback, factory) check_callback(callback, factory)
def results = array(length) def results = array(len)
def cancel_list = array(length) def cancel_list = array(len)
var next_index = 0 var next_index = 0
var successes = 0 var successes = 0
var failures = 0 var failures = 0
@@ -107,33 +123,34 @@ function parallel(requestor_array, throttle, need) {
if (finished) return if (finished) return
finished = true finished = true
arrfor(cancel_list, c => { arrfor(cancel_list, c => {
try { if (is_function(c)) c(reason) } catch (_) {} var _c = function() { if (is_function(c)) c(reason) } disruption {}
_c()
}) })
} }
function start_one() { function start_one() {
if (finished || next_index >= length) return if (finished || next_index >= len) return
def idx = next_index def idx = next_index
next_index += 1 next_index = next_index + 1
def requestor = requestor_array[idx] def requestor = requestor_array[idx]
try { var _run = function() {
cancel_list[idx] = requestor(function(val, reason) { cancel_list[idx] = requestor(function(val, reason) {
if (finished) return if (finished) return
cancel_list[idx] = null cancel_list[idx] = null
if (val != null) { if (val != null) {
results[idx] = val results[idx] = val
successes += 1 successes = successes + 1
if (successes >= need) { if (successes >= _need) {
finished = true finished = true
cancel(make_reason(factory, 'Finished.')) cancel(make_reason(factory, 'Finished.'))
callback(results) callback(results)
return return
} }
} else { } else {
failures += 1 failures = failures + 1
if (failures > length - need) { if (failures > len - _need) {
cancel(reason) cancel(reason)
callback(null, reason || make_reason(factory, 'Too many failures.')) callback(null, reason || make_reason(factory, 'Too many failures.'))
return return
@@ -142,20 +159,21 @@ function parallel(requestor_array, throttle, need) {
start_one() start_one()
}, value) }, value)
} catch (ex) { } disruption {
failures += 1 failures = failures + 1
if (failures > length - need) { if (failures > len - _need) {
cancel(ex) cancel(make_reason(factory, 'Requestor threw.'))
callback(null, ex) callback(null, make_reason(factory, 'Requestor threw.'))
return return
} }
start_one() start_one()
} }
_run()
} }
def concurrent = throttle ? min(throttle, len) : len
def concurrent = throttle ? min(throttle, length) : length var i = 0
for (var i = 0; i < concurrent; i++) start_one() while (i < concurrent) { start_one(); i = i + 1 }
return cancel return cancel
} }
@@ -165,22 +183,29 @@ function parallel(requestor_array, throttle, need) {
// Runs requestors in parallel, returns first success(es). // Runs requestors in parallel, returns first success(es).
function race(requestor_array, throttle, need) { function race(requestor_array, throttle, need) {
def factory = 'race' def factory = 'race'
if (!is_array(requestor_array) || length(requestor_array) == 0) if (!is_array(requestor_array) || length(requestor_array) == 0) {
throw make_reason(factory, 'Empty requestor array.') print(make_reason(factory, 'Empty requestor array.').message + '\n')
disrupt
}
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
def length = length(requestor_array) def len = length(requestor_array)
if (need == null) need = 1 var _need = need
if (!is_number(need) || need < 1 || need > length) if (_need == null) _need = 1
throw make_reason(factory, 'Bad need.', need) if (!is_number(_need) || _need < 1 || _need > len) {
print(make_reason(factory, 'Bad need.', _need).message + '\n')
disrupt
}
if (throttle != null && (!is_number(throttle) || throttle < 1)) if (throttle != null && (!is_number(throttle) || throttle < 1)) {
throw make_reason(factory, 'Bad throttle.', throttle) print(make_reason(factory, 'Bad throttle.', throttle).message + '\n')
disrupt
}
return function race_requestor(callback, value) { return function race_requestor(callback, value) {
check_callback(callback, factory) check_callback(callback, factory)
def results = array(length) def results = array(len)
def cancel_list = array(length) def cancel_list = array(len)
var next_index = 0 var next_index = 0
var successes = 0 var successes = 0
var failures = 0 var failures = 0
@@ -190,27 +215,28 @@ function race(requestor_array, throttle, need) {
if (finished) return if (finished) return
finished = true finished = true
arrfor(cancel_list, c => { arrfor(cancel_list, c => {
try { if (is_function(c)) c(reason) } catch (_) {} var _c = function() { if (is_function(c)) c(reason) } disruption {}
_c()
}) })
} }
function start_one() { function start_one() {
if (finished || next_index >= length) return if (finished || next_index >= len) return
def idx = next_index def idx = next_index
next_index += 1 next_index = next_index + 1
def requestor = requestor_array[idx] def requestor = requestor_array[idx]
try { var _run = function() {
cancel_list[idx] = requestor(function(val, reason) { cancel_list[idx] = requestor(function(val, reason) {
if (finished) return if (finished) return
cancel_list[idx] = null cancel_list[idx] = null
if (val != null) { if (val != null) {
results[idx] = val results[idx] = val
successes += 1 successes = successes + 1
if (successes >= need) { if (successes >= _need) {
cancel(make_reason(factory, 'Winner.')) cancel(make_reason(factory, 'Winner.'))
if (need == 1) { if (_need == 1) {
callback(val) callback(val)
} else { } else {
callback(results) callback(results)
@@ -218,8 +244,8 @@ function race(requestor_array, throttle, need) {
return return
} }
} else { } else {
failures += 1 failures = failures + 1
if (failures > length - need) { if (failures > len - _need) {
cancel(reason) cancel(reason)
callback(null, reason || make_reason(factory, 'All failed.')) callback(null, reason || make_reason(factory, 'All failed.'))
return return
@@ -228,19 +254,21 @@ function race(requestor_array, throttle, need) {
start_one() start_one()
}, value) }, value)
} catch (ex) { } disruption {
failures += 1 failures = failures + 1
if (failures > length - need) { if (failures > len - _need) {
cancel(ex) cancel(make_reason(factory, 'Requestor threw.'))
callback(null, ex) callback(null, make_reason(factory, 'Requestor threw.'))
return return
} }
start_one() start_one()
} }
_run()
} }
def concurrent = throttle ? min(throttle, length) : length def concurrent = throttle ? min(throttle, len) : len
for (var i = 0; i < concurrent; i++) start_one() var i = 0
while (i < concurrent) { start_one(); i = i + 1 }
return cancel return cancel
} }
@@ -250,8 +278,10 @@ function race(requestor_array, throttle, need) {
// Runs requestors one at a time, passing result to next. // Runs requestors one at a time, passing result to next.
function sequence(requestor_array) { function sequence(requestor_array) {
def factory = 'sequence' def factory = 'sequence'
if (!is_array(requestor_array)) if (!is_array(requestor_array)) {
throw make_reason(factory, 'Not an array.', requestor_array) print(make_reason(factory, 'Not an array.', requestor_array).message + '\n')
disrupt
}
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
if (length(requestor_array) == 0) if (length(requestor_array) == 0)
@@ -264,9 +294,11 @@ function sequence(requestor_array) {
var cancelled = false var cancelled = false
function cancel(reason) { function cancel(reason) {
var _c = null
cancelled = true cancelled = true
if (current_cancel) { if (current_cancel) {
try { current_cancel(reason) } catch (_) {} _c = function() { current_cancel(reason) } disruption {}
_c()
current_cancel = null current_cancel = null
} }
} }
@@ -279,9 +311,9 @@ function sequence(requestor_array) {
} }
def requestor = requestor_array[index] def requestor = requestor_array[index]
index += 1 index = index + 1
try { var _run = function() {
current_cancel = requestor(function(result, reason) { current_cancel = requestor(function(result, reason) {
if (cancelled) return if (cancelled) return
current_cancel = null current_cancel = null
@@ -291,9 +323,10 @@ function sequence(requestor_array) {
run_next(result) run_next(result)
} }
}, val) }, val)
} catch (ex) { } disruption {
callback(null, ex) callback(null, make_reason(factory, 'Requestor threw.'))
} }
_run()
} }
run_next(value) run_next(value)
@@ -305,26 +338,29 @@ function sequence(requestor_array) {
// Converts a unary function into a requestor. // Converts a unary function into a requestor.
function requestorize(unary) { function requestorize(unary) {
def factory = 'requestorize' def factory = 'requestorize'
if (!is_function(unary)) if (!is_function(unary)) {
throw make_reason(factory, 'Not a function.', unary) print(make_reason(factory, 'Not a function.', unary).message + '\n')
disrupt
}
return function requestorized(callback, value) { return function requestorized(callback, value) {
check_callback(callback, factory) check_callback(callback, factory)
try { var _run = function() {
def result = unary(value) def result = unary(value)
callback(result == null ? true : result) callback(result == null ? true : result)
} catch (ex) { } disruption {
callback(null, ex) callback(null, make_reason(factory, 'Function threw.'))
} }
_run()
} }
} }
return { return {
fallback, fallback: fallback,
parallel, parallel: parallel,
race, race: race,
sequence, sequence: sequence,
requestorize, requestorize: requestorize,
is_requestor, is_requestor: is_requestor,
check_callback check_callback: check_callback
} }

18
qbe.ce Normal file
View File

@@ -0,0 +1,18 @@
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode")
var streamline = use("streamline")
var qbe_macros = use("qbe")
var qbe_emit = use("qbe_emit")
var filename = args[0]
var src = text(fd.slurp(filename))
var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename, tokenize)
var folded = fold(ast)
var compiled = mcode(folded)
var optimized = streamline(compiled)
var il = qbe_emit(optimized, qbe_macros)
print(il)

364
qbe.cm
View File

@@ -13,6 +13,11 @@ def js_true = 35
def js_exception = 15 def js_exception = 15
def js_empty_text = 27 def js_empty_text = 27
// Shared closure vars for functions with >4 params
var _qop = null
var _qop2 = null
var _qflags = null
def int32_min = -2147483648 def int32_min = -2147483648
def int32_max = 2147483647 def int32_max = 2147483647
def mantissa_mask = 4503599627370495 def mantissa_mask = 4503599627370495
@@ -398,18 +403,20 @@ var mod = function(p, ctx, a, b) {
// ============================================================ // ============================================================
// Helper: generate comparison for a given op string and int comparison QBE op // Helper: generate comparison for a given op string and int comparison QBE op
// null_true: whether null==null returns true (eq, le, ge) or false (ne, lt, gt) // reads _qflags = {int_cmp_op, float_id, is_eq, is_ne, null_true} from closure
var cmp = function(p, ctx, a, b, int_cmp_op, float_cmp_op_id, is_eq, is_ne, null_true) { var cmp = function(p, ctx, a, b) {
var int_cmp_op = _qflags.int_cmp_op
var float_cmp_op_id = _qflags.float_id
var eq_only = 0 var eq_only = 0
if (is_eq || is_ne) { var mismatch_val = js_false
var null_val = js_false
if (_qflags.is_eq || _qflags.is_ne) {
eq_only = 1 eq_only = 1
} }
var mismatch_val = js_false if (_qflags.is_ne) {
if (is_ne) {
mismatch_val = js_true mismatch_val = js_true
} }
var null_val = js_false if (_qflags.null_true) {
if (null_true) {
null_val = js_true null_val = js_true
} }
return `@${p}.start return `@${p}.start
@@ -485,27 +492,32 @@ var cmp = function(p, ctx, a, b, int_cmp_op, float_cmp_op_id, is_eq, is_ne, null
// MACH_EQ=0, NEQ=1, LT=2, LE=3, GT=4, GE=5 // MACH_EQ=0, NEQ=1, LT=2, LE=3, GT=4, GE=5
// null_true: eq, le, ge return true for null==null; ne, lt, gt return false // null_true: eq, le, ge return true for null==null; ne, lt, gt return false
var eq = function(p, ctx, a, b) { var eq = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "ceqw", 0, true, false, true) _qflags = {int_cmp_op: "ceqw", float_id: 0, is_eq: true, is_ne: false, null_true: true}
return cmp(p, ctx, a, b)
} }
var ne = function(p, ctx, a, b) { var ne = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "cnew", 1, false, true, false) _qflags = {int_cmp_op: "cnew", float_id: 1, is_eq: false, is_ne: true, null_true: false}
return cmp(p, ctx, a, b)
} }
var lt = function(p, ctx, a, b) { var lt = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "csltw", 2, false, false, false) _qflags = {int_cmp_op: "csltw", float_id: 2, is_eq: false, is_ne: false, null_true: false}
return cmp(p, ctx, a, b)
} }
var le = function(p, ctx, a, b) { var le = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "cslew", 3, false, false, true) _qflags = {int_cmp_op: "cslew", float_id: 3, is_eq: false, is_ne: false, null_true: true}
return cmp(p, ctx, a, b)
} }
var gt = function(p, ctx, a, b) { var gt = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "csgtw", 4, false, false, false) _qflags = {int_cmp_op: "csgtw", float_id: 4, is_eq: false, is_ne: false, null_true: false}
return cmp(p, ctx, a, b)
} }
var ge = function(p, ctx, a, b) { var ge = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "csgew", 5, false, false, true) _qflags = {int_cmp_op: "csgew", float_id: 5, is_eq: false, is_ne: false, null_true: true}
} }
// ============================================================ // ============================================================
@@ -627,7 +639,9 @@ var bnot = function(p, ctx, v) {
// Both operands must be numeric. Int fast path, float -> convert to int32. // Both operands must be numeric. Int fast path, float -> convert to int32.
// ============================================================ // ============================================================
var bitwise_op = function(p, ctx, a, b, qbe_op) { // reads _qop from closure
var bitwise_op = function(p, ctx, a, b) {
var qbe_op = _qop
return `@${p}.start return `@${p}.start
%${p}.at =l and ${a}, 1 %${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1 %${p}.bt =l and ${b}, 1
@@ -654,19 +668,24 @@ var bitwise_op = function(p, ctx, a, b, qbe_op) {
} }
var band = function(p, ctx, a, b) { var band = function(p, ctx, a, b) {
return bitwise_op(p, ctx, a, b, "and") _qop = "and"
return bitwise_op(p, ctx, a, b)
} }
var bor = function(p, ctx, a, b) { var bor = function(p, ctx, a, b) {
return bitwise_op(p, ctx, a, b, "or") _qop = "or"
return bitwise_op(p, ctx, a, b)
} }
var bxor = function(p, ctx, a, b) { var bxor = function(p, ctx, a, b) {
return bitwise_op(p, ctx, a, b, "xor") _qop = "xor"
return bitwise_op(p, ctx, a, b)
} }
// Shift ops: mask shift amount to 5 bits (& 31) // Shift ops: mask shift amount to 5 bits (& 31)
var shift_op = function(p, ctx, a, b, qbe_op) { // reads _qop from closure
var shift_op = function(p, ctx, a, b) {
var qbe_op = _qop
return `@${p}.start return `@${p}.start
%${p}.at =l and ${a}, 1 %${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1 %${p}.bt =l and ${b}, 1
@@ -694,15 +713,274 @@ var shift_op = function(p, ctx, a, b, qbe_op) {
} }
var shl = function(p, ctx, a, b) { var shl = function(p, ctx, a, b) {
return shift_op(p, ctx, a, b, "shl") _qop = "shl"
return shift_op(p, ctx, a, b)
} }
var shr = function(p, ctx, a, b) { var shr = function(p, ctx, a, b) {
return shift_op(p, ctx, a, b, "sar") _qop = "sar"
return shift_op(p, ctx, a, b)
} }
var ushr = function(p, ctx, a, b) { var ushr = function(p, ctx, a, b) {
return shift_op(p, ctx, a, b, "shr") _qop = "shr"
return shift_op(p, ctx, a, b)
}
// ============================================================
// Decomposed per-type-path operations
// These map directly to the new IR ops emitted by mcode.cm.
// ============================================================
// --- Arithmetic (int path) ---
// add_int: assume both operands are tagged ints. Overflow -> float.
var add_int = function(p, ctx, a, b) {
return ` %${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.sum =l add %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.sum, ${int32_min}
%${p}.hi =w csgtl %${p}.sum, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.ov, @${p}.ok
@${p}.ok
%${p}.rw =w copy %${p}.sum
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.ov
%${p}.fd =d sltof %${p}.sum
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
@${p}.done
`
}
var sub_int = function(p, ctx, a, b) {
return ` %${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.diff =l sub %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.diff, ${int32_min}
%${p}.hi =w csgtl %${p}.diff, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.ov, @${p}.ok
@${p}.ok
%${p}.rw =w copy %${p}.diff
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.ov
%${p}.fd =d sltof %${p}.diff
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
@${p}.done
`
}
var mul_int = function(p, ctx, a, b) {
return ` %${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.prod =l mul %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.prod, ${int32_min}
%${p}.hi =w csgtl %${p}.prod, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.ov, @${p}.ok
@${p}.ok
%${p}.rw =w copy %${p}.prod
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.ov
%${p}.fd =d sltof %${p}.prod
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
@${p}.done
`
}
var div_int = function(p, ctx, a, b) {
return ` %${p}.ia =w copy 0
%${p}.tmp =l sar ${a}, 1
%${p}.ia =w copy %${p}.tmp
%${p}.ib =w copy 0
%${p}.tmp2 =l sar ${b}, 1
%${p}.ib =w copy %${p}.tmp2
%${p}.div0 =w ceqw %${p}.ib, 0
jnz %${p}.div0, @${p}.null, @${p}.chk
@${p}.null
%${p} =l copy ${js_null}
jmp @${p}.done
@${p}.chk
%${p}.rem =w rem %${p}.ia, %${p}.ib
%${p}.exact =w ceqw %${p}.rem, 0
jnz %${p}.exact, @${p}.idiv, @${p}.fdiv
@${p}.idiv
%${p}.q =w div %${p}.ia, %${p}.ib
%${p}.qext =l extuw %${p}.q
%${p} =l shl %${p}.qext, 1
jmp @${p}.done
@${p}.fdiv
%${p}.da =d swtof %${p}.ia
%${p}.db =d swtof %${p}.ib
%${p}.dr =d div %${p}.da, %${p}.db
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.dr)
@${p}.done
`
}
var mod_int = function(p, ctx, a, b) {
return ` %${p}.ia =w copy 0
%${p}.tmp =l sar ${a}, 1
%${p}.ia =w copy %${p}.tmp
%${p}.ib =w copy 0
%${p}.tmp2 =l sar ${b}, 1
%${p}.ib =w copy %${p}.tmp2
%${p}.div0 =w ceqw %${p}.ib, 0
jnz %${p}.div0, @${p}.null, @${p}.do_mod
@${p}.null
%${p} =l copy ${js_null}
jmp @${p}.done
@${p}.do_mod
%${p}.r =w rem %${p}.ia, %${p}.ib
%${p}.rext =l extuw %${p}.r
%${p} =l shl %${p}.rext, 1
@${p}.done
`
}
var neg_int = function(p, ctx, v) {
return ` %${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
jnz %${p}.is_min, @${p}.ov, @${p}.ok
@${p}.ov
%${p}.fd =d swtof %${p}.iw
%${p}.fdn =d neg %${p}.fd
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fdn)
jmp @${p}.done
@${p}.ok
%${p}.ni =w sub 0, %${p}.iw
%${p}.niext =l extuw %${p}.ni
%${p} =l shl %${p}.niext, 1
@${p}.done
`
}
// --- Arithmetic (float path) ---
var add_float = function(p, ctx, a, b) {
return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
`
}
var sub_float = function(p, ctx, a, b) {
return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
`
}
var mul_float = function(p, ctx, a, b) {
return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
`
}
var div_float = function(p, ctx, a, b) {
return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
`
}
var mod_float = function(p, ctx, a, b) {
return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
`
}
var neg_float = function(p, ctx, v) {
return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
`
}
// --- Text concat ---
var concat = function(p, ctx, a, b) {
return ` %${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
`
}
// --- Comparisons (int path) ---
var cmp_int = function(p, a, b, qbe_op) {
return ` %${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.iaw =w copy %${p}.ia
%${p}.ibw =w copy %${p}.ib
%${p}.cr =w ${qbe_op} %${p}.iaw, %${p}.ibw
%${p}.crext =l extuw %${p}.cr
%${p}.sh =l shl %${p}.crext, 5
%${p} =l or %${p}.sh, 3
`
}
var eq_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "ceqw") }
var ne_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cnew") }
var lt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csltw") }
var le_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cslew") }
var gt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgtw") }
var ge_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgew") }
// --- Comparisons (float path) ---
// reads _qop from closure (op_id)
var cmp_float = function(p, ctx, a, b) {
var op_id = _qop
return ` %${p}.fcr =w call $qbe_float_cmp(l ${ctx}, w ${op_id}, l ${a}, l ${b})
%${p}.fcrext =l extuw %${p}.fcr
%${p}.fsh =l shl %${p}.fcrext, 5
%${p} =l or %${p}.fsh, 3
`
}
var eq_float = function(p, ctx, a, b) { _qop = 0; return cmp_float(p, ctx, a, b) }
var ne_float = function(p, ctx, a, b) { _qop = 1; return cmp_float(p, ctx, a, b) }
var lt_float = function(p, ctx, a, b) { _qop = 2; return cmp_float(p, ctx, a, b) }
var le_float = function(p, ctx, a, b) { _qop = 3; return cmp_float(p, ctx, a, b) }
var gt_float = function(p, ctx, a, b) { _qop = 4; return cmp_float(p, ctx, a, b) }
var ge_float = function(p, ctx, a, b) { _qop = 5; return cmp_float(p, ctx, a, b) }
// --- Comparisons (text path) ---
// reads _qop (qbe_op) and _qop2 (eq_only) from closure
var cmp_text = function(p, ctx, a, b) {
var qbe_op = _qop
var eq_only = _qop2
return ` %${p}.scmp =w call $js_string_compare_value(l ${ctx}, l ${a}, l ${b}, w ${eq_only})
%${p}.tcr =w ${qbe_op} %${p}.scmp, 0
%${p}.tcrext =l extuw %${p}.tcr
%${p}.tsh =l shl %${p}.tcrext, 5
%${p} =l or %${p}.tsh, 3
`
}
var eq_text = function(p, ctx, a, b) { _qop = "ceqw"; _qop2 = 1; return cmp_text(p, ctx, a, b) }
var ne_text = function(p, ctx, a, b) { _qop = "cnew"; _qop2 = 1; return cmp_text(p, ctx, a, b) }
var lt_text = function(p, ctx, a, b) { _qop = "csltw"; _qop2 = 0; return cmp_text(p, ctx, a, b) }
var le_text = function(p, ctx, a, b) { _qop = "cslew"; _qop2 = 0; return cmp_text(p, ctx, a, b) }
var gt_text = function(p, ctx, a, b) { _qop = "csgtw"; _qop2 = 0; return cmp_text(p, ctx, a, b) }
var ge_text = function(p, ctx, a, b) { _qop = "csgew"; _qop2 = 0; return cmp_text(p, ctx, a, b) }
// --- Comparisons (bool path) ---
var eq_bool = function(p, a, b) {
return ` %${p}.cr =w ceql ${a}, ${b}
%${p}.crext =l extuw %${p}.cr
%${p}.sh =l shl %${p}.crext, 5
%${p} =l or %${p}.sh, 3
`
}
var ne_bool = function(p, a, b) {
return ` %${p}.cr =w cnel ${a}, ${b}
%${p}.crext =l extuw %${p}.cr
%${p}.sh =l shl %${p}.crext, 5
%${p} =l or %${p}.sh, 3
`
}
// --- Type guard: is_identical ---
var is_identical = function(p, a, b) {
return ` %${p}.cr =w ceql ${a}, ${b}
%${p}.crext =l extuw %${p}.cr
%${p}.sh =l shl %${p}.crext, 5
%${p} =l or %${p}.sh, 3
`
} }
// ============================================================ // ============================================================
@@ -760,5 +1038,47 @@ return {
bxor: bxor, bxor: bxor,
shl: shl, shl: shl,
shr: shr, shr: shr,
ushr: ushr ushr: ushr,
// decomposed arithmetic (int path)
add_int: add_int,
sub_int: sub_int,
mul_int: mul_int,
div_int: div_int,
mod_int: mod_int,
neg_int: neg_int,
// decomposed arithmetic (float path)
add_float: add_float,
sub_float: sub_float,
mul_float: mul_float,
div_float: div_float,
mod_float: mod_float,
neg_float: neg_float,
// text concat
concat: concat,
// decomposed comparisons (int)
eq_int: eq_int,
ne_int: ne_int,
lt_int: lt_int,
le_int: le_int,
gt_int: gt_int,
ge_int: ge_int,
// decomposed comparisons (float)
eq_float: eq_float,
ne_float: ne_float,
lt_float: lt_float,
le_float: le_float,
gt_float: gt_float,
ge_float: ge_float,
// decomposed comparisons (text)
eq_text: eq_text,
ne_text: ne_text,
lt_text: lt_text,
le_text: le_text,
gt_text: gt_text,
ge_text: ge_text,
// decomposed comparisons (bool)
eq_bool: eq_bool,
ne_bool: ne_bool,
// type guard
is_identical: is_identical
} }

BIN
qbe.mach Normal file

Binary file not shown.

757
qbe_emit.cm Normal file
View File

@@ -0,0 +1,757 @@
// qbe_emit.cm — mcode IR → QBE IL compiler
// Takes mcode IR (from mcode.cm) and uses qbe.cm macros to produce
// a complete QBE IL program ready for the qbe compiler.
// qbe module is passed via env as 'qbe'
var qbe_emit = function(ir, qbe) {
var out = []
var data_out = []
var str_table = {}
var str_id = 0
var uid = 0
// ============================================================
// Output helpers
// ============================================================
var emit = function(s) {
push(out, s)
}
var fresh = function() {
uid = uid + 1
return "u" + text(uid)
}
var s = function(n) {
return "%s" + text(n)
}
var sanitize = function(lbl) {
var r = replace(lbl, ".", "_")
r = replace(r, "-", "_")
r = replace(r, " ", "_")
r = replace(r, "/", "_")
r = replace(r, "<", "")
r = replace(r, ">", "")
r = replace(r, "(", "")
r = replace(r, ")", "")
return r
}
// ============================================================
// String interning — emit data section entries
// ============================================================
var intern_str = function(val) {
if (str_table[val] != null) return str_table[val]
var label = "$d_str_" + text(str_id)
str_id = str_id + 1
var escaped = replace(val, "\\", "\\\\")
escaped = replace(escaped, "\"", "\\\"")
var line = "data " + label + ' = ' + '{ b "' + escaped + '", b 0 }'
push(data_out, line)
str_table[val] = label
return label
}
// ============================================================
// Extract property name from mcode operand
// ============================================================
var prop_name = function(a) {
if (is_text(a)) return a
if (is_object(a)) {
if (a.name != null) return a.name
if (a.value != null) return a.value
}
return null
}
// ============================================================
// Compile one function's instructions
// ============================================================
var compile_fn = function(fn, fn_idx, is_main) {
var instrs = fn.instructions
var nr_slots = fn.nr_slots
var nr_args = fn.nr_args
var name = is_main ? "cell_main" : "cell_fn_" + text(fn_idx)
name = sanitize(name)
var i = 0
var instr = null
var op = null
var a1 = null
var a2 = null
var a3 = null
var a4 = null
var p = null
var pn = null
var sl = null
var fop_id = 0
var nr_elems = 0
var ei = 0
var elem_slot = 0
// Function signature: (ctx, frame_ptr) → JSValue
emit(`export function l $${name}(l %ctx, l %fp) {`)
emit("@entry")
// Load all slots from frame into SSA variables
// Each slot is a JSValue (8 bytes) at fp + slot*8
var off = 0
i = 0
while (i < nr_slots) {
off = i * 8
emit(` %p${text(i)} =l add %fp, ${text(off)}`)
emit(` ${s(i)} =l loadl %p${text(i)}`)
i = i + 1
}
// Write-back: store SSA var to frame slot so closures see updates
var wb = function(slot) {
emit(` storel ${s(slot)}, %p${text(slot)}`)
}
// Walk instructions
i = 0
while (i < length(instrs)) {
instr = instrs[i]
i = i + 1
// Labels are plain strings
if (is_text(instr)) {
emit("@" + sanitize(instr))
continue
}
op = instr[0]
a1 = instr[1]
a2 = instr[2]
a3 = instr[3]
// --- Constants ---
if (op == "int") {
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
wb(a1)
continue
}
if (op == "null") {
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
wb(a1)
continue
}
if (op == "true") {
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
wb(a1)
continue
}
if (op == "false") {
emit(` ${s(a1)} =l copy ${text(qbe.js_false)}`)
wb(a1)
continue
}
if (op == "access") {
if (is_number(a2)) {
if (is_integer(a2)) {
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
} else {
emit(` ${s(a1)} =l call $__JS_NewFloat64(l %ctx, d d_${text(a2)})`)
}
} else if (is_text(a2)) {
sl = intern_str(a2)
emit(` ${s(a1)} =l call $JS_NewString(l %ctx, l ${sl})`)
} else if (is_object(a2)) {
if (a2.make == "intrinsic") {
sl = intern_str(a2.name)
emit(` ${s(a1)} =l call $cell_rt_get_intrinsic(l %ctx, l ${sl})`)
} else if (a2.kind == "number") {
if (a2.number != null && is_integer(a2.number)) {
emit(` ${s(a1)} =l copy ${text(a2.number * 2)}`)
} else if (a2.number != null) {
emit(` ${s(a1)} =l call $__JS_NewFloat64(l %ctx, d d_${text(a2.number)})`)
} else {
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
}
} else if (a2.kind == "text") {
sl = intern_str(a2.value)
emit(` ${s(a1)} =l call $JS_NewString(l %ctx, l ${sl})`)
} else if (a2.kind == "true") {
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
} else if (a2.kind == "false") {
emit(` ${s(a1)} =l copy ${text(qbe.js_false)}`)
} else if (a2.kind == "null") {
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
} else {
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
}
} else {
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
}
wb(a1)
continue
}
// --- Movement ---
if (op == "move") {
emit(` ${s(a1)} =l copy ${s(a2)}`)
wb(a1)
continue
}
// --- Arithmetic (int path) — use qbe.cm macros ---
if (op == "add_int") {
p = fresh()
emit(qbe.add_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "sub_int") {
p = fresh()
emit(qbe.sub_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "mul_int") {
p = fresh()
emit(qbe.mul_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "div_int") {
p = fresh()
emit(qbe.div_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "mod_int") {
p = fresh()
emit(qbe.mod_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
// --- Arithmetic (float path) ---
if (op == "add_float") {
p = fresh()
emit(qbe.add_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "sub_float") {
p = fresh()
emit(qbe.sub_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "mul_float") {
p = fresh()
emit(qbe.mul_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "div_float") {
p = fresh()
emit(qbe.div_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "mod_float") {
p = fresh()
emit(qbe.mod_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
// --- String concat ---
if (op == "concat") {
p = fresh()
emit(qbe.concat(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
// --- Type checks — use qbe.cm macros ---
if (op == "is_int") {
p = fresh()
emit(qbe.is_int(p, s(a2)))
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_text") {
p = fresh()
emit(qbe.is_imm_text(p, s(a2)))
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_num") {
p = fresh()
emit(qbe.is_number(p, s(a2)))
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_bool") {
p = fresh()
emit(qbe.is_bool(p, s(a2)))
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_null") {
p = fresh()
emit(qbe.is_null(p, s(a2)))
emit(qbe.new_bool(p + ".r", "%" + p))
emit(` ${s(a1)} =l copy %${p}.r`)
wb(a1)
continue
}
if (op == "is_identical") {
p = fresh()
emit(qbe.is_identical(p, s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
// --- Comparisons (int path) ---
if (op == "eq_int") {
p = fresh()
emit(qbe.eq_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "ne_int") {
p = fresh()
emit(qbe.ne_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "lt_int") {
p = fresh()
emit(qbe.lt_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "gt_int") {
p = fresh()
emit(qbe.gt_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "le_int") {
p = fresh()
emit(qbe.le_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "ge_int") {
p = fresh()
emit(qbe.ge_int(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
// --- Comparisons (float/text/bool) ---
if (op == "eq_float") {
p = fresh()
emit(qbe.eq_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "ne_float") {
p = fresh()
emit(qbe.ne_float(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "lt_float" || op == "gt_float" || op == "le_float" || op == "ge_float") {
p = fresh()
fop_id = 0
if (op == "lt_float") fop_id = 2
else if (op == "le_float") fop_id = 3
else if (op == "gt_float") fop_id = 4
else if (op == "ge_float") fop_id = 5
emit(qbe.cmp_float != null ? qbe.cmp_float(p, "%ctx", s(a2), s(a3), fop_id) : ` %${p} =l call $qbe_float_cmp(l %ctx, w ${text(fop_id)}, l ${s(a2)}, l ${s(a3)})`)
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "eq_text") {
p = fresh()
emit(qbe.eq_text(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "ne_text") {
p = fresh()
emit(qbe.ne_text(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") {
p = fresh()
emit(` ${s(a1)} =l call $cell_rt_${op}(l %ctx, l ${s(a2)}, l ${s(a3)})`)
wb(a1)
continue
}
if (op == "eq_bool") {
p = fresh()
emit(qbe.eq_bool(p, s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "ne_bool") {
p = fresh()
emit(qbe.ne_bool(p, s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "eq_tol" || op == "ne_tol") {
emit(` ${s(a1)} =l call $cell_rt_${op}(l %ctx, l ${s(a2)}, l ${s(a3)})`)
wb(a1)
continue
}
// --- Boolean ops ---
if (op == "not") {
p = fresh()
emit(qbe.lnot(p, "%ctx", s(a2)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "and") {
emit(` ${s(a1)} =l and ${s(a2)}, ${s(a3)}`)
wb(a1)
continue
}
if (op == "or") {
emit(` ${s(a1)} =l or ${s(a2)}, ${s(a3)}`)
wb(a1)
continue
}
// --- Bitwise ops — use qbe.cm macros ---
if (op == "bitnot") {
p = fresh()
emit(qbe.bnot(p, "%ctx", s(a2)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "bitand") {
p = fresh()
emit(qbe.band(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "bitor") {
p = fresh()
emit(qbe.bor(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "bitxor") {
p = fresh()
emit(qbe.bxor(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "shl") {
p = fresh()
emit(qbe.shl(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "shr") {
p = fresh()
emit(qbe.shr(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
if (op == "ushr") {
p = fresh()
emit(qbe.ushr(p, "%ctx", s(a2), s(a3)))
emit(` ${s(a1)} =l copy %${p}`)
wb(a1)
continue
}
// --- Property access — runtime calls ---
if (op == "load_field") {
pn = prop_name(a3)
if (pn != null) {
sl = intern_str(pn)
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`)
} else {
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
}
wb(a1)
continue
}
if (op == "load_index") {
emit(` ${s(a1)} =l call $cell_rt_load_index(l %ctx, l ${s(a2)}, l ${s(a3)})`)
wb(a1)
continue
}
if (op == "load_dynamic") {
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
wb(a1)
continue
}
if (op == "store_field") {
// IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name)
pn = prop_name(a3)
if (pn != null) {
sl = intern_str(pn)
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
} else {
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
}
continue
}
if (op == "store_index") {
// IR: ["store_index", obj, val, idx] → C: (ctx, val, obj, idx)
emit(` call $cell_rt_store_index(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
continue
}
if (op == "store_dynamic") {
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key)
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
continue
}
// --- Closure access ---
if (op == "get") {
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a2)}, l ${text(a3)})`)
wb(a1)
continue
}
if (op == "put") {
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${s(a1)}, l ${text(a2)}, l ${text(a3)})`)
continue
}
// --- Control flow ---
if (op == "jump") {
emit(` jmp @${sanitize(a1)}`)
continue
}
if (op == "jump_true") {
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_f`)
emit(`@${p}_f`)
continue
}
if (op == "jump_false") {
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
emit(` jnz %${p}, @${p}_t, @${sanitize(a2)}`)
emit(`@${p}_t`)
continue
}
if (op == "jump_null") {
p = fresh()
emit(` %${p} =w ceql ${s(a1)}, ${text(qbe.js_null)}`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_nn`)
emit(`@${p}_nn`)
continue
}
if (op == "jump_not_null") {
p = fresh()
emit(` %${p} =w cnel ${s(a1)}, ${text(qbe.js_null)}`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_n`)
emit(`@${p}_n`)
continue
}
if (op == "wary_true") {
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_f`)
emit(`@${p}_f`)
continue
}
if (op == "wary_false") {
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
emit(` jnz %${p}, @${p}_t, @${sanitize(a2)}`)
emit(`@${p}_t`)
continue
}
// --- Function calls ---
if (op == "frame") {
emit(` ${s(a1)} =l call $cell_rt_frame(l %ctx, l ${s(a2)}, l ${text(a3)})`)
wb(a1)
continue
}
if (op == "setarg") {
emit(` call $cell_rt_setarg(l ${s(a1)}, l ${text(a2)}, l ${s(a3)})`)
continue
}
if (op == "invoke") {
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
wb(a2)
continue
}
if (op == "goframe") {
emit(` ${s(a1)} =l call $cell_rt_goframe(l %ctx, l ${s(a2)}, l ${text(a3)})`)
wb(a1)
continue
}
if (op == "goinvoke") {
emit(` call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`)
continue
}
// --- Function object creation ---
if (op == "function") {
emit(` ${s(a1)} =l call $cell_rt_make_function(l %ctx, l ${text(a2)}, l %fp)`)
wb(a1)
continue
}
// --- Record/Array creation ---
if (op == "record") {
emit(` ${s(a1)} =l call $JS_NewObject(l %ctx)`)
wb(a1)
continue
}
if (op == "array") {
nr_elems = a2 != null ? a2 : 0
emit(` ${s(a1)} =l call $JS_NewArray(l %ctx)`)
ei = 0
while (ei < nr_elems) {
elem_slot = instr[3 + ei]
emit(` call $JS_SetPropertyNumber(l %ctx, l ${s(a1)}, l ${text(ei)}, l ${s(elem_slot)})`)
ei = ei + 1
}
wb(a1)
continue
}
// --- Array push/pop ---
if (op == "push") {
emit(` call $cell_rt_push(l %ctx, l ${s(a1)}, l ${s(a2)})`)
continue
}
if (op == "pop") {
emit(` ${s(a1)} =l call $cell_rt_pop(l %ctx, l ${s(a2)})`)
wb(a1)
continue
}
// --- Misc ---
if (op == "return") {
emit(` ret ${s(a1)}`)
continue
}
if (op == "disrupt") {
emit(` call $cell_rt_disrupt(l %ctx)`)
emit(` ret ${text(qbe.js_null)}`)
continue
}
if (op == "delete") {
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
wb(a1)
continue
}
if (op == "typeof") {
emit(` ${s(a1)} =l call $cell_rt_typeof(l %ctx, l ${s(a2)})`)
wb(a1)
continue
}
// --- Unknown opcode ---
emit(` # unknown: ${op}`)
}
emit("}")
emit("")
}
// ============================================================
// Main: compile all functions then main
// ============================================================
var fi = 0
while (fi < length(ir.functions)) {
compile_fn(ir.functions[fi], fi, false)
fi = fi + 1
}
compile_fn(ir.main, -1, true)
// Assemble: data section first, then function bodies
var result = []
var di = 0
while (di < length(data_out)) {
push(result, data_out[di])
di = di + 1
}
if (length(data_out) > 0) push(result, "")
di = 0
while (di < length(out)) {
push(result, out[di])
di = di + 1
}
return text(result, "\n")
}
return qbe_emit

BIN
qbe_emit.mach Normal file

Binary file not shown.

82
qbe_rt.c Normal file
View File

@@ -0,0 +1,82 @@
/*
* qbe_rt.c - Non-inline wrappers for QBE-compiled ƿit modules
*
* Provides non-inline versions of static-inline quickjs functions
* (which QBE-generated code calls as external symbols).
*
* All cell_rt_* runtime functions are implemented in source/qbe_helpers.c
* (compiled into the cell binary) and resolved via -undefined dynamic_lookup.
*/
#include <stdint.h>
#include <string.h>
#include <math.h>
typedef uint64_t JSValue;
typedef struct JSContext JSContext;
#define JS_TAG_SHORT_FLOAT 5
#define JS_TAG_NULL 7
#define JS_VAL_NULL 7
/* ============================================================
Non-inline wrappers for static-inline quickjs functions
============================================================ */
/*
* __JS_NewFloat64 — encode double as tagged JSValue
* Short float: [sign:1][exp:8][mantissa:52][tag:3]
* Returns tagged int if value is an exact integer in int32 range
*/
JSValue __JS_NewFloat64(JSContext *ctx, double d) {
union { double d; uint64_t u; } u;
u.d = d;
uint64_t sign = u.u >> 63;
int exp = (u.u >> 52) & 0x7FF;
uint64_t mantissa = u.u & ((1ULL << 52) - 1);
/* Zero */
if (exp == 0 && mantissa == 0)
return JS_TAG_SHORT_FLOAT;
/* NaN/Inf → null */
if (exp == 0x7FF)
return JS_VAL_NULL;
/* Subnormals → zero */
if (exp == 0)
return (sign << 63) | JS_TAG_SHORT_FLOAT;
int short_exp = exp - 1023 + 127;
if (short_exp < 1 || short_exp > 254)
return JS_VAL_NULL;
/* Prefer integer if exact */
if (d >= (double)(-2147483647 - 1) && d <= (double)2147483647) {
int32_t i = (int32_t)d;
if ((double)i == d)
return (uint64_t)(uint32_t)i << 1;
}
return (sign << 63)
| ((uint64_t)short_exp << 55)
| (mantissa << 3)
| JS_TAG_SHORT_FLOAT;
}
/*
* JS_IsNumber — check if value is tagged int or short float
*/
int JS_IsNumber(JSValue v) {
int is_int = (v & 1) == 0;
int is_float = (v & 7) == JS_TAG_SHORT_FLOAT;
return is_int || is_float;
}
/*
* JS_NewString — create string from C string (wraps JS_NewStringLen)
*/
extern JSValue JS_NewStringLen(JSContext *ctx, const char *str, size_t len);
JSValue JS_NewString(JSContext *ctx, const char *str) {
return JS_NewStringLen(ctx, str, strlen(str));
}

2
qop.c
View File

@@ -267,7 +267,7 @@ static JSValue js_qop_list(JSContext *js, JSValue self, int argc, JSValue *argv)
JSValue str = JS_NewStringLen(js, path, len - 1); // -1 for null terminator JSValue str = JS_NewStringLen(js, path, len - 1); // -1 for null terminator
js_free(js, path); js_free(js, path);
JS_SetPropertyUint32(js, arr, count++, str); JS_SetPropertyNumber(js, arr, count++, str);
} }
return arr; return arr;

75
regen.cm Normal file
View File

@@ -0,0 +1,75 @@
// regen.cm — regenerate .mach bytecode files
// Run with: ./cell --core . regen.cm
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode")
var streamline = use("streamline")
var files = [
{src: "tokenize.cm", name: "tokenize", out: "tokenize.mach"},
{src: "parse.cm", name: "parse", out: "parse.mach"},
{src: "fold.cm", name: "fold", out: "fold.mach"},
{src: "mcode.cm", name: "mcode", out: "mcode.mach"},
{src: "streamline.cm", name: "streamline", out: "streamline.mach"},
{src: "qbe.cm", name: "qbe", out: "qbe.mach"},
{src: "qbe_emit.cm", name: "qbe_emit", out: "qbe_emit.mach"},
{src: "internal/bootstrap.cm", name: "bootstrap", out: "internal/bootstrap.mach"},
{src: "internal/engine.cm", name: "engine", out: "internal/engine.mach"}
]
var i = 0
var entry = null
var src = null
var tok_result = null
var ast = null
var folded = null
var compiled = null
var optimized = null
var mcode_json = null
var bytecode = null
var f = null
var errs = null
var ei = 0
var e = null
var had_errors = false
while (i < length(files)) {
entry = files[i]
src = text(fd.slurp(entry.src))
tok_result = tokenize(src, entry.src)
ast = parse(tok_result.tokens, src, entry.src, tokenize)
// Check for parse/semantic errors
errs = ast.errors
if (errs != null && length(errs) > 0) {
ei = 0
while (ei < length(errs)) {
e = errs[ei]
if (e.line != null) {
print(`${entry.src}:${text(e.line)}:${text(e.column)}: error: ${e.message}`)
} else {
print(`${entry.src}: error: ${e.message}`)
}
ei = ei + 1
}
had_errors = true
i = i + 1
continue
}
folded = fold(ast)
compiled = mcode(folded)
optimized = streamline(compiled)
mcode_json = json.encode(optimized)
bytecode = mach_compile_mcode_bin(entry.name, mcode_json)
f = fd.open(entry.out, "w")
fd.write(f, bytecode)
fd.close(f)
print(`wrote ${entry.out}`)
i = i + 1
}
if (had_errors) {
print("regen aborted: fix errors above")
}

91
run_native.ce Normal file
View File

@@ -0,0 +1,91 @@
// run_native.ce — load a module both interpreted and native, compare speed
//
// Usage:
// cell --core . run_native.ce <module>
//
// Loads <module>.cm via use() (interpreted) and <module>.dylib (native),
// runs both and compares results and timing.
var os = use('os')
if (length(args) < 1) {
print('usage: cell --core . run_native.ce <module>')
print(' e.g. cell --core . run_native.ce num_torture')
return
}
var name = args[0]
if (ends_with(name, '.cm')) {
name = text(name, 0, length(name) - 3)
}
var safe = replace(replace(name, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use'
var dylib_path = './' + name + '.dylib'
var fd = use('fd')
// --- Test argument for function-returning modules ---
var test_arg = 5000000
if (length(args) > 1) {
test_arg = number(args[1])
}
// --- Interpreted run ---
print('--- interpreted ---')
var t1 = os.now()
var mod_interp = use(name)
var t2 = os.now()
var result_interp = null
if (is_function(mod_interp)) {
print('module returns a function, calling with ' + text(test_arg))
t1 = os.now()
result_interp = mod_interp(test_arg)
t2 = os.now()
}
result_interp = result_interp != null ? result_interp : mod_interp
var ms_interp = (t2 - t1) / 1000000
print('result: ' + text(result_interp))
print('time: ' + text(ms_interp) + ' ms')
// --- Native run ---
if (!fd.is_file(dylib_path)) {
print('\nno ' + dylib_path + ' found — run compile.ce first')
return
}
print('\n--- native ---')
var t3 = os.now()
var lib = os.dylib_open(dylib_path)
var t4 = os.now()
var mod_native = os.dylib_symbol(lib, symbol)
var t5 = os.now()
var result_native = null
if (is_function(mod_native)) {
print('module returns a function, calling with ' + text(test_arg))
t4 = os.now()
result_native = mod_native(test_arg)
t5 = os.now()
}
result_native = result_native != null ? result_native : mod_native
var ms_load = (t4 - t3) / 1000000
var ms_exec = (t5 - t4) / 1000000
var ms_native = (t5 - t3) / 1000000
print('result: ' + text(result_native))
print('load: ' + text(ms_load) + ' ms')
print('exec: ' + text(ms_exec) + ' ms')
print('total: ' + text(ms_native) + ' ms')
// --- Comparison ---
print('\n--- comparison ---')
var match = result_interp == result_native
var speedup = 0
var speedup_exec = 0
print('match: ' + text(match))
if (ms_native > 0) {
speedup = ms_interp / ms_native
print('speedup: ' + text(speedup) + 'x (total)')
}
if (ms_exec > 0) {
speedup_exec = ms_interp / ms_exec
print('speedup: ' + text(speedup_exec) + 'x (exec only)')
}

View File

@@ -12,7 +12,7 @@
#include "cJSON.h" #include "cJSON.h"
#define BOOTSTRAP_MACH "internal/bootstrap.mach" #define BOOTSTRAP_MACH "internal/bootstrap.mach"
#define BOOTSTRAP_AST "internal/bootstrap.ast.json" #define BOOTSTRAP_SRC "internal/bootstrap.cm"
#define CELL_SHOP_DIR ".cell" #define CELL_SHOP_DIR ".cell"
#define CELL_CORE_DIR "packages/core" #define CELL_CORE_DIR "packages/core"
@@ -26,6 +26,7 @@ int run_c_test_suite(JSContext *ctx);
static int run_test_suite(size_t heap_size); static int run_test_suite(size_t heap_size);
cell_rt *root_cell = NULL; cell_rt *root_cell = NULL;
static char *shop_path = NULL;
static char *core_path = NULL; static char *core_path = NULL;
static JSRuntime *g_runtime = NULL; static JSRuntime *g_runtime = NULL;
@@ -38,31 +39,57 @@ static const char* get_home_dir(void) {
return home; return home;
} }
// Find and verify the cell shop at ~/.cell // Resolve shop_path and core_path
int find_cell_shop(void) // core: --core flag > CELL_CORE env > derived from shop
// shop: --shop flag > CELL_SHOP env > ~/.cell
int find_cell_shop(const char *shop_override, const char *core_override)
{ {
const char *home = get_home_dir(); // Resolve shop_path
if (!home) { if (shop_override) {
printf("ERROR: Could not determine home directory. Set HOME environment variable.\n"); shop_path = strdup(shop_override);
return 0; } else {
const char *env = getenv("CELL_SHOP");
if (env) {
shop_path = strdup(env);
} else {
const char *home = get_home_dir();
if (!home && !core_override && !getenv("CELL_CORE")) {
printf("ERROR: Could not determine home directory. Set HOME environment variable.\n");
return 0;
}
if (home) {
size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR) + 1;
shop_path = malloc(path_len);
if (shop_path)
snprintf(shop_path, path_len, "%s/" CELL_SHOP_DIR, home);
}
}
}
// Resolve core_path
if (core_override) {
core_path = strdup(core_override);
} else {
const char *env = getenv("CELL_CORE");
if (env) {
core_path = strdup(env);
} else if (shop_path) {
size_t core_len = strlen(shop_path) + strlen("/" CELL_CORE_DIR) + 1;
core_path = malloc(core_len);
if (core_path)
snprintf(core_path, core_len, "%s/" CELL_CORE_DIR, shop_path);
}
} }
// Build path to ~/.cell/core
size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR "/" CELL_CORE_DIR) + 1;
core_path = malloc(path_len);
if (!core_path) { if (!core_path) {
printf("ERROR: Could not allocate memory for core path\n"); printf("ERROR: No core path. Use --core <path> or set CELL_CORE.\n");
return 0; return 0;
} }
snprintf(core_path, path_len, "%s/" CELL_SHOP_DIR "/" CELL_CORE_DIR, home);
// Check if the core directory exists // Check if the core directory exists
struct stat st; struct stat st;
if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) { if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
printf("ERROR: Cell shop not found at %s/" CELL_SHOP_DIR "\n", home); printf("ERROR: Core not found at %s\n", core_path);
printf("Run 'cell install' to set up the cell environment.\n");
free(core_path);
core_path = NULL;
return 0; return 0;
} }
@@ -151,14 +178,9 @@ void script_startup(cell_rt *prt)
cell_rt *crt = JS_GetContextOpaque(js); cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js)); JS_FreeValue(js, js_blob_use(js));
// Load pre-compiled bootstrap bytecode (.mach), fall back to AST JSON // Load pre-compiled bootstrap bytecode (.mach)
size_t boot_size; size_t boot_size;
int boot_is_bin = 1;
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size); char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
if (!boot_data) {
boot_is_bin = 0;
boot_data = load_core_file(BOOTSTRAP_AST, &boot_size);
}
if (!boot_data) { if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s!\n", core_path); printf("ERROR: Could not load bootstrap from %s!\n", core_path);
return; return;
@@ -183,26 +205,21 @@ void script_startup(cell_rt *prt)
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL); JS_SetPropertyStr(js, hidden_env, "init", JS_NULL);
} }
if (core_path) { // Set args to null for actor spawn (not CLI mode)
JS_SetPropertyStr(js, hidden_env, "args", JS_NULL);
if (core_path)
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
} JS_SetPropertyStr(js, hidden_env, "shop_path",
shop_path ? JS_NewString(js, shop_path) : JS_NULL);
// Stone the environment // Stone the environment
hidden_env = JS_Stone(js, hidden_env); hidden_env = JS_Stone(js, hidden_env);
// Run through MACH VM // Run through MACH VM
crt->state = ACTOR_RUNNING; crt->state = ACTOR_RUNNING;
JSValue v; JSValue v = JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env);
if (boot_is_bin) { free(boot_data);
v = JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env);
free(boot_data);
} else {
cJSON *ast = cJSON_Parse(boot_data);
free(boot_data);
if (!ast) { printf("ERROR: Failed to parse bootstrap AST\n"); return; }
v = JS_RunMachTree(js, ast, hidden_env);
cJSON_Delete(ast);
}
uncaught_exception(js, v); uncaught_exception(js, v);
crt->state = ACTOR_IDLE; crt->state = ACTOR_IDLE;
set_actor_state(crt); set_actor_state(crt);
@@ -255,9 +272,15 @@ static void print_usage(const char *prog)
printf("Usage: %s [options] <script> [args...]\n\n", prog); printf("Usage: %s [options] <script> [args...]\n\n", prog);
printf("Run a cell script (.ce actor or .cm module).\n\n"); printf("Run a cell script (.ce actor or .cm module).\n\n");
printf("Options:\n"); printf("Options:\n");
printf(" --mcode <script> [args] Run through mcode compilation pipeline\n"); printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
printf(" --emit-qbe Emit QBE IL (for native compilation)\n");
printf(" --dump-mach Dump MACH bytecode disassembly\n");
printf(" --test [heap_size] Run C test suite\n"); printf(" --test [heap_size] Run C test suite\n");
printf(" -h, --help Show this help message\n"); printf(" -h, --help Show this help message\n");
printf("\nEnvironment:\n");
printf(" CELL_CORE Core path (default: <shop>/packages/core)\n");
printf(" CELL_SHOP Shop path (default: ~/.cell)\n");
printf("\nRecompile after changes: make\n"); printf("\nRecompile after changes: make\n");
printf("Bootstrap from scratch: make bootstrap\n"); printf("Bootstrap from scratch: make bootstrap\n");
} }
@@ -284,46 +307,108 @@ int cell_init(int argc, char **argv)
} }
/* Default: run script through bootstrap pipeline */ /* Default: run script through bootstrap pipeline */
int use_mcode = 0; int emit_qbe = 0;
int dump_mach = 0;
int arg_start = 1; int arg_start = 1;
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) { const char *shop_override = NULL;
use_mcode = 1; const char *core_override = NULL;
arg_start = 2;
// Parse flags (order-independent)
while (arg_start < argc && argv[arg_start][0] == '-') {
if (strcmp(argv[arg_start], "--mcode") == 0) {
/* --mcode is now always on; accept and ignore for compat */
arg_start++;
} else if (strcmp(argv[arg_start], "--emit-qbe") == 0) {
emit_qbe = 1;
arg_start++;
} else if (strcmp(argv[arg_start], "--dump-mach") == 0) {
dump_mach = 1;
arg_start++;
} else if (strcmp(argv[arg_start], "--shop") == 0) {
if (arg_start + 1 >= argc) {
printf("ERROR: --shop requires a path argument\n");
return 1;
}
shop_override = argv[arg_start + 1];
arg_start += 2;
} else if (strcmp(argv[arg_start], "--core") == 0) {
if (arg_start + 1 >= argc) {
printf("ERROR: --core requires a path argument\n");
return 1;
}
core_override = argv[arg_start + 1];
arg_start += 2;
} else {
break;
}
} }
if (!find_cell_shop()) return 1; if (!find_cell_shop(shop_override, core_override)) return 1;
actor_initialize();
size_t boot_size; size_t boot_size;
int boot_is_bin = 1;
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size); char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
if (!boot_data) {
boot_is_bin = 0;
boot_data = load_core_file(BOOTSTRAP_AST, &boot_size);
}
if (!boot_data) { if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s\n", core_path); printf("ERROR: Could not load bootstrap from %s\n", core_path);
return 1; return 1;
} }
JSRuntime *rt = JS_NewRuntime(); g_runtime = JS_NewRuntime();
if (!rt) { if (!g_runtime) {
printf("Failed to create JS runtime\n"); printf("Failed to create JS runtime\n");
free(boot_data); free(boot_data);
return 1; return 1;
} }
JSContext *ctx = JS_NewContextWithHeapSize(rt, 16 * 1024 * 1024); JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, 16 * 1024 * 1024);
if (!ctx) { if (!ctx) {
printf("Failed to create JS context\n"); printf("Failed to create JS context\n");
free(boot_data); JS_FreeRuntime(rt); free(boot_data); JS_FreeRuntime(g_runtime);
return 1; return 1;
} }
/* Create a cell_rt for the CLI context so JS-C bridge functions work */
cell_rt *cli_rt = calloc(sizeof(*cli_rt), 1);
cli_rt->mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(cli_rt->mutex, &mattr);
cli_rt->msg_mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(cli_rt->msg_mutex, &mattr);
pthread_mutexattr_destroy(&mattr);
cli_rt->context = ctx;
JS_SetContextOpaque(ctx, cli_rt);
JS_SetInterruptHandler(ctx, (JSInterruptHandler *)actor_interrupt_cb, cli_rt);
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
JS_AddGCRef(ctx, &cli_rt->on_exception_ref);
JS_AddGCRef(ctx, &cli_rt->message_handle_ref);
JS_AddGCRef(ctx, &cli_rt->unneeded_ref);
JS_AddGCRef(ctx, &cli_rt->actor_sym_ref);
cli_rt->idx_buffer_ref.val = JS_NULL;
cli_rt->on_exception_ref.val = JS_NULL;
cli_rt->message_handle_ref.val = JS_NULL;
cli_rt->unneeded_ref.val = JS_NULL;
cli_rt->actor_sym_ref.val = JS_NewObject(ctx);
root_cell = cli_rt;
JS_FreeValue(ctx, js_blob_use(ctx)); JS_FreeValue(ctx, js_blob_use(ctx));
JSValue hidden_env = JS_NewObject(ctx); JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx)); JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path)); JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JS_SetPropertyStr(ctx, hidden_env, "use_mcode", JS_NewBool(ctx, use_mcode)); JS_SetPropertyStr(ctx, hidden_env, "shop_path",
shop_path ? JS_NewString(ctx, shop_path) : JS_NULL);
JS_SetPropertyStr(ctx, hidden_env, "emit_qbe", JS_NewBool(ctx, emit_qbe));
JS_SetPropertyStr(ctx, hidden_env, "dump_mach", JS_NewBool(ctx, dump_mach));
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "wota", js_wota_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL);
JSValue args_arr = JS_NewArray(ctx); JSValue args_arr = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) { for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]); JSValue str = JS_NewString(ctx, argv[i]);
@@ -332,17 +417,8 @@ int cell_init(int argc, char **argv)
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr); JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
hidden_env = JS_Stone(ctx, hidden_env); hidden_env = JS_Stone(ctx, hidden_env);
JSValue result; JSValue result = JS_RunMachBin(ctx, (const uint8_t *)boot_data, boot_size, hidden_env);
if (boot_is_bin) { free(boot_data);
result = JS_RunMachBin(ctx, (const uint8_t *)boot_data, boot_size, hidden_env);
free(boot_data);
} else {
cJSON *ast = cJSON_Parse(boot_data);
free(boot_data);
if (!ast) { printf("Failed to parse bootstrap AST\n"); JS_FreeContext(ctx); JS_FreeRuntime(rt); return 1; }
result = JS_RunMachTree(ctx, ast, hidden_env);
cJSON_Delete(ast);
}
int exit_code = 0; int exit_code = 0;
if (JS_IsException(result)) { if (JS_IsException(result)) {
@@ -356,8 +432,33 @@ int cell_init(int argc, char **argv)
} }
} }
if (scheduler_actor_count() > 0) {
scheduler_enable_quiescence();
actor_loop();
exit_handler();
exit(0);
}
/* No actors spawned — clean up CLI context */
JS_DeleteGCRef(ctx, &cli_rt->idx_buffer_ref);
JS_DeleteGCRef(ctx, &cli_rt->on_exception_ref);
JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref);
JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref);
JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref);
JS_SetInterruptHandler(ctx, NULL, NULL);
pthread_mutex_destroy(cli_rt->mutex);
free(cli_rt->mutex);
pthread_mutex_destroy(cli_rt->msg_mutex);
free(cli_rt->msg_mutex);
free(cli_rt);
root_cell = NULL;
JS_FreeContext(ctx); JS_FreeContext(ctx);
JS_FreeRuntime(rt); JS_FreeRuntime(g_runtime);
g_runtime = NULL;
exit_handler();
return exit_code; return exit_code;
} }

View File

@@ -54,6 +54,7 @@ typedef struct cell_rt {
double ar_secs; // time for unneeded double ar_secs; // time for unneeded
int disrupt; int disrupt;
int is_quiescent; // tracked by scheduler for quiescence detection
int main_thread_only; int main_thread_only;
int affinity; int affinity;
@@ -81,6 +82,8 @@ int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt);
void actor_loop(); void actor_loop();
void actor_initialize(void); void actor_initialize(void);
void actor_free(cell_rt *actor); void actor_free(cell_rt *actor);
int scheduler_actor_count(void);
void scheduler_enable_quiescence(void);
uint64_t cell_ns(); uint64_t cell_ns();
void cell_sleep(double seconds); void cell_sleep(double seconds);

View File

@@ -1327,9 +1327,6 @@ static int re_parse_nested_class(REParseState *s, REStringList *cr, const uint8_
REStringList cr1_s, *cr1 = &cr1_s; REStringList cr1_s, *cr1 = &cr1_s;
BOOL invert, is_first; BOOL invert, is_first;
if (lre_check_stack_overflow(s->opaque, 0))
return re_parse_error(s, "stack overflow");
re_string_list_init(s, cr); re_string_list_init(s, cr);
p = *pp; p = *pp;
p++; /* skip '[' */ p++; /* skip '[' */
@@ -2356,9 +2353,6 @@ static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir)
{ {
int start, len, pos; int start, len, pos;
if (lre_check_stack_overflow(s->opaque, 0))
return re_parse_error(s, "stack overflow");
start = s->byte_code.size; start = s->byte_code.size;
if (re_parse_alternative(s, is_backward_dir)) if (re_parse_alternative(s, is_backward_dir))
return -1; return -1;
@@ -3205,11 +3199,6 @@ const char *lre_get_groupnames(const uint8_t *bc_buf)
#ifdef TEST #ifdef TEST
BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size)
{
return FALSE;
}
void *lre_realloc(void *opaque, void *ptr, size_t size) void *lre_realloc(void *opaque, void *ptr, size_t size)
{ {
return realloc(ptr, size); return realloc(ptr, size);

View File

@@ -52,8 +52,6 @@ int lre_exec(uint8_t **capture,
int lre_parse_escape(const uint8_t **pp, int allow_utf16); int lre_parse_escape(const uint8_t **pp, int allow_utf16);
/* must be provided by the user, return non zero if overflow */
int lre_check_stack_overflow(void *opaque, size_t alloca_size);
/* must be provided by the user, return non zero if time out */ /* must be provided by the user, return non zero if time out */
int lre_check_timeout(void *opaque); int lre_check_timeout(void *opaque);
void *lre_realloc(void *opaque, void *ptr, size_t size); void *lre_realloc(void *opaque, void *ptr, size_t size);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -192,3 +192,258 @@ JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
JS_ToInt32(ctx, &ib, b); JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
} }
/* ============================================================
cell_rt_* — Runtime support for QBE-compiled code
============================================================ */
#include <dlfcn.h>
#include <stdio.h>
/* --- Property access --- */
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
return JS_GetPropertyStr(ctx, obj, name);
}
void cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj,
const char *name) {
JS_SetPropertyStr(ctx, obj, name, val);
}
JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) {
if (JS_IsInt(key))
return JS_GetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key));
return JS_GetProperty(ctx, obj, key);
}
void cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
JSValue key) {
if (JS_IsInt(key))
JS_SetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key), val);
else
JS_SetProperty(ctx, obj, key, val);
}
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
if (JS_IsInt(idx))
return JS_GetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx));
return JS_GetProperty(ctx, arr, idx);
}
void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
JSValue idx) {
if (JS_IsInt(idx))
JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val);
else
JS_SetProperty(ctx, arr, idx, val);
}
/* --- Intrinsic/global lookup --- */
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
return JS_GetPropertyStr(ctx, ctx->global_obj, name);
}
/* --- Closure access ---
Slot 511 in each frame stores a pointer to the enclosing frame.
Walking depth levels up the chain gives the target frame. */
#define QBE_FRAME_OUTER_SLOT 511
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
int64_t slot) {
JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) {
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
if (!outer) return JS_NULL;
frame = (JSValue *)outer;
}
return frame[slot];
}
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
int64_t slot) {
JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) {
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
if (!outer) return;
frame = (JSValue *)outer;
}
frame[slot] = val;
}
/* --- Function creation and calling --- */
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
/* Table mapping fn_idx → outer_fp at creation time.
Valid for single-threaded, non-recursive closure patterns. */
#define MAX_QBE_FUNCTIONS 256
static void *g_outer_fp[MAX_QBE_FUNCTIONS];
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
int argc, JSValue *argv, int magic) {
char name[64];
snprintf(name, sizeof(name), "cell_fn_%d", magic);
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, name);
if (!fn)
return JS_ThrowTypeError(ctx, "native function %s not found", name);
/* Allocate frame: slot 0 = this, slots 1..argc = args */
JSValue frame[512];
memset(frame, 0, sizeof(frame));
frame[0] = this_val;
for (int i = 0; i < argc && i < 510; i++)
frame[1 + i] = argv[i];
/* Link to outer frame for closure access */
if (magic >= 0 && magic < MAX_QBE_FUNCTIONS)
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_outer_fp[magic];
return fn(ctx, frame);
}
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
if (fn_idx >= 0 && fn_idx < MAX_QBE_FUNCTIONS)
g_outer_fp[fn_idx] = outer_fp;
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
255, JS_CFUNC_generic_magic, (int)fn_idx);
}
/* --- Frame-based function calling --- */
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
if (!JS_IsFunction(fn)) {
JS_ThrowTypeError(ctx, "not a function");
return JS_EXCEPTION;
}
int nr_slots = (int)nargs + 2;
JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots);
if (!new_frame) return JS_EXCEPTION;
new_frame->function = fn;
return JS_MKPTR(new_frame);
}
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fr->slots[idx] = val;
}
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
int nr_slots = (int)objhdr_cap56(fr->hdr);
int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0;
/* Copy args to C stack */
JSValue args[c_argc > 0 ? c_argc : 1];
for (int i = 0; i < c_argc; i++)
args[i] = fr->slots[i + 1];
JSValue result = JS_Call(ctx, fr->function, fr->slots[0], c_argc, args);
if (JS_IsException(result))
return JS_EXCEPTION;
return result;
}
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
return cell_rt_frame(ctx, fn, nargs);
}
void cell_rt_goinvoke(JSContext *ctx, JSValue frame_val) {
cell_rt_invoke(ctx, frame_val);
}
/* --- Array push/pop --- */
void cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {
JS_ArrayPush(ctx, &arr, val);
}
JSValue cell_rt_pop(JSContext *ctx, JSValue arr) {
return JS_ArrayPop(ctx, arr);
}
/* --- Delete --- */
JSValue cell_rt_delete(JSContext *ctx, JSValue obj, JSValue key) {
int ret = JS_DeleteProperty(ctx, obj, key);
return JS_NewBool(ctx, ret >= 0);
}
/* --- Typeof --- */
JSValue cell_rt_typeof(JSContext *ctx, JSValue val) {
if (JS_IsNull(val)) return JS_NewString(ctx, "null");
if (JS_IsInt(val) || JS_IsNumber(val)) return JS_NewString(ctx, "number");
if (JS_IsBool(val)) return JS_NewString(ctx, "logical");
if (JS_IsText(val)) return JS_NewString(ctx, "text");
if (JS_IsFunction(val)) return JS_NewString(ctx, "function");
if (JS_IsArray(val)) return JS_NewString(ctx, "array");
if (JS_IsRecord(val)) return JS_NewString(ctx, "object");
return JS_NewString(ctx, "unknown");
}
/* --- Text comparison stubs (called from QBE type-dispatch branches) --- */
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) < 0 : 0;
return JS_NewBool(ctx, r);
}
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) > 0 : 0;
return JS_NewBool(ctx, r);
}
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) <= 0 : 0;
return JS_NewBool(ctx, r);
}
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) {
const char *sa = JS_ToCString(ctx, a);
const char *sb = JS_ToCString(ctx, b);
int r = (sa && sb) ? strcmp(sa, sb) >= 0 : 0;
return JS_NewBool(ctx, r);
}
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, a == b);
}
JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, a != b);
}
/* --- Disruption --- */
void cell_rt_disrupt(JSContext *ctx) {
JS_ThrowTypeError(ctx, "type error in native code");
}
/* --- Module entry point ---
Called as symbol(ctx) by os.dylib_symbol. Looks up cell_main
in the loaded dylib, builds a heap-allocated frame (so closures
can reference it after the module returns), and runs the module body. */
JSValue cell_rt_module_entry(JSContext *ctx) {
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, "cell_main");
if (!fn)
return JS_ThrowTypeError(ctx, "cell_main not found in loaded dylib");
/* Heap-allocate so closures created in cell_main can reference
this frame after the module entry returns. */
JSValue *frame = calloc(512, sizeof(JSValue));
if (!frame)
return JS_ThrowTypeError(ctx, "frame allocation failed");
return fn(ctx, frame);
}

View File

@@ -54,14 +54,6 @@
#include "nota.h" #include "nota.h"
#include "wota.h" #include "wota.h"
#define OPTIMIZE 1
#define SHORT_OPCODES 1
#if defined(EMSCRIPTEN)
#define DIRECT_DISPATCH 0
#else
#define DIRECT_DISPATCH 1
#endif
#if !defined(_WIN32) #if !defined(_WIN32)
/* define it if printf uses the RNDN rounding mode instead of RNDNA */ /* define it if printf uses the RNDN rounding mode instead of RNDNA */
#define CONFIG_PRINTF_RNDN #define CONFIG_PRINTF_RNDN
@@ -154,7 +146,6 @@ typedef struct JSCode JSCode;
/* Extract pointer (clear low bits) */ /* Extract pointer (clear low bits) */
#define JS_VALUE_GET_PTR(v) ((void *)((v) & ~((JSValue)(JSW - 1)))) #define JS_VALUE_GET_PTR(v) ((void *)((v) & ~((JSValue)(JSW - 1))))
static inline JS_BOOL JS_VALUE_IS_TEXT (JSValue v) { static inline JS_BOOL JS_VALUE_IS_TEXT (JSValue v) {
int tag = JS_VALUE_GET_TAG (v); int tag = JS_VALUE_GET_TAG (v);
return tag == JS_TAG_STRING_IMM || (JS_IsPtr(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_TEXT); return tag == JS_TAG_STRING_IMM || (JS_IsPtr(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_TEXT);
@@ -361,70 +352,21 @@ typedef struct BuddyAllocator {
/* Forward declarations for buddy allocator functions */ /* Forward declarations for buddy allocator functions */
static void buddy_destroy (BuddyAllocator *b); static void buddy_destroy (BuddyAllocator *b);
/* controls a host of contexts, handing out memory and scheduling */
struct JSRuntime { struct JSRuntime {
const char *rt_info;
size_t malloc_limit; size_t malloc_limit;
/* Buddy allocator for actor memory blocks */
BuddyAllocator buddy; BuddyAllocator buddy;
/* see JS_SetStripInfo() */
uint8_t strip_flags;
/* User data */
void *user_opaque;
}; };
struct JSClass { struct JSClass {
const char *class_name; const char *class_name;
JSClassFinalizer *finalizer; JSClassFinalizer *finalizer;
JSClassGCMark *gc_mark;
uint32_t class_id; /* 0 means free entry */ uint32_t class_id; /* 0 means free entry */
}; };
#define JS_MODE_BACKTRACE_BARRIER \ #define JS_MODE_BACKTRACE_BARRIER \
(1 << 3) /* stop backtrace before this frame */ (1 << 3) /* stop backtrace before this frame */
typedef struct JSStackFrame {
struct JSStackFrame *prev_frame; /* NULL if first stack frame */
JSValue cur_func; /* current function, JS_NULL if the frame is detached */
JSValue *arg_buf; /* arguments */
JSValue *var_buf; /* variables */
const uint8_t *cur_pc; /* only used in bytecode functions : PC of the
instruction after the call */
int arg_count;
int js_mode; /* not supported for C functions */
JSValue js_frame; /* GC-managed JSFrame (use JS_VALUE_GET_FRAME to access) */
JSValue *stack_buf; /* operand stack base (for GC scanning) */
JSValue **p_sp; /* pointer to current sp (for GC scanning) */
} JSStackFrame;
/* Heap-allocated VM frame for trampoline execution */
struct VMFrame {
struct JSFunctionBytecode *b; /* current function bytecode */
JSContext *ctx; /* execution context / realm */
const uint8_t *pc; /* program counter */
/* Offset-based storage (safe with realloc) */
int value_stack_base; /* base index into ctx->value_stack */
int sp_offset; /* sp offset from base */
int arg_buf_offset; /* arg buffer offset from base (or -1 if aliased) */
int var_buf_offset; /* var buffer offset from base */
/* Continuation info for return */
const uint8_t *ret_pc; /* where to resume in caller */
int ret_sp_offset; /* caller's sp before call (for cleanup) */
int call_argc; /* number of args to clean up */
int call_has_this; /* whether call had this (method call) */
JSValue cur_func; /* current function object */
JSValue this_obj; /* this binding */
int arg_count;
int js_mode;
int stack_size_allocated; /* total size allocated for this frame */
};
typedef struct JSFrameRegister { typedef struct JSFrameRegister {
objhdr_t hdr; // capacity in this is the total number of words of the object, including the 4 words of overhead and all slots objhdr_t hdr; // capacity in this is the total number of words of the object, including the 4 words of overhead and all slots
JSValue function; // JSFunction, function object being invoked JSValue function; // JSFunction, function object being invoked
@@ -464,6 +406,8 @@ typedef struct { uint16_t line; uint16_t col; } MachLineEntry;
#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8) #define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8)
typedef enum MachOpcode { typedef enum MachOpcode {
/* === Legacy opcodes (used by existing .mach files) === */
/* Constants & Loading */ /* Constants & Loading */
MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */ MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */
MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */ MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */
@@ -474,7 +418,7 @@ typedef enum MachOpcode {
/* Movement */ /* Movement */
MACH_MOVE, /* R(A) = R(B) */ MACH_MOVE, /* R(A) = R(B) */
/* Arithmetic (ABC) */ /* Generic arithmetic (ABC) — used by legacy .mach */
MACH_ADD, /* R(A) = R(B) + R(C) */ MACH_ADD, /* R(A) = R(B) + R(C) */
MACH_SUB, /* R(A) = R(B) - R(C) */ MACH_SUB, /* R(A) = R(B) - R(C) */
MACH_MUL, /* R(A) = R(B) * R(C) */ MACH_MUL, /* R(A) = R(B) * R(C) */
@@ -485,7 +429,7 @@ typedef enum MachOpcode {
MACH_INC, /* R(A) = R(B) + 1 */ MACH_INC, /* R(A) = R(B) + 1 */
MACH_DEC, /* R(A) = R(B) - 1 */ MACH_DEC, /* R(A) = R(B) - 1 */
/* Comparison (ABC) */ /* Generic comparison (ABC) — used by legacy .mach */
MACH_EQ, /* R(A) = (R(B) == R(C)) */ MACH_EQ, /* R(A) = (R(B) == R(C)) */
MACH_NEQ, /* R(A) = (R(B) != R(C)) */ MACH_NEQ, /* R(A) = (R(B) != R(C)) */
MACH_LT, /* R(A) = (R(B) < R(C)) */ MACH_LT, /* R(A) = (R(B) < R(C)) */
@@ -493,7 +437,7 @@ typedef enum MachOpcode {
MACH_GT, /* R(A) = (R(B) > R(C)) */ MACH_GT, /* R(A) = (R(B) > R(C)) */
MACH_GE, /* R(A) = (R(B) >= R(C)) */ MACH_GE, /* R(A) = (R(B) >= R(C)) */
/* Logical/Bitwise */ /* Logical/Bitwise — used by legacy .mach */
MACH_LNOT, /* R(A) = !R(B) */ MACH_LNOT, /* R(A) = !R(B) */
MACH_BNOT, /* R(A) = ~R(B) */ MACH_BNOT, /* R(A) = ~R(B) */
MACH_BAND, /* R(A) = R(B) & R(C) */ MACH_BAND, /* R(A) = R(B) & R(C) */
@@ -503,7 +447,7 @@ typedef enum MachOpcode {
MACH_SHR, /* R(A) = R(B) >> R(C) */ MACH_SHR, /* R(A) = R(B) >> R(C) */
MACH_USHR, /* R(A) = R(B) >>> R(C) */ MACH_USHR, /* R(A) = R(B) >>> R(C) */
/* Property access */ /* Property access — used by legacy .mach */
MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */ MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */
MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */ MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */
MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */ MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */
@@ -524,12 +468,11 @@ typedef enum MachOpcode {
MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */ MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */
MACH_JMPNULL, /* if R(A)==null: pc += sBx */ MACH_JMPNULL, /* if R(A)==null: pc += sBx */
/* Function calls — Lua-style consecutive registers */ /* Function calls — Lua-style consecutive registers (legacy .mach) */
MACH_CALL, /* Call R(A) with B args R(A+1)..R(A+B), C=0 discard, C=1 keep result in R(A) */
MACH_RETURN, /* Return R(A) */ MACH_RETURN, /* Return R(A) */
MACH_RETNIL, /* Return null */ MACH_RETNIL, /* Return null */
/* Object/array creation */ /* Object/array creation — legacy .mach */
MACH_NEWOBJECT, /* R(A) = {} */ MACH_NEWOBJECT, /* R(A) = {} */
MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */ MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */
MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */ MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */
@@ -544,17 +487,119 @@ typedef enum MachOpcode {
MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */ MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */
MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */ MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */
MACH_CALLMETHOD, /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key */
MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */ MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */
MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */ MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */
MACH_NOP, MACH_NOP,
/* === New mcode-derived opcodes (1:1 mapping to mcode IR) === */
/* Typed integer arithmetic (ABC) */
MACH_ADD_INT, /* R(A) = R(B) + R(C) — int, overflow → float */
MACH_SUB_INT, /* R(A) = R(B) - R(C) — int */
MACH_MUL_INT, /* R(A) = R(B) * R(C) — int */
MACH_DIV_INT, /* R(A) = R(B) / R(C) — int */
MACH_MOD_INT, /* R(A) = R(B) % R(C) — int */
MACH_NEG_INT, /* R(A) = -R(B) — int (AB) */
/* Typed float arithmetic (ABC) */
MACH_ADD_FLOAT, /* R(A) = R(B) + R(C) — float */
MACH_SUB_FLOAT, /* R(A) = R(B) - R(C) — float */
MACH_MUL_FLOAT, /* R(A) = R(B) * R(C) — float */
MACH_DIV_FLOAT, /* R(A) = R(B) / R(C) — float */
MACH_MOD_FLOAT, /* R(A) = R(B) % R(C) — float */
MACH_NEG_FLOAT, /* R(A) = -R(B) — float (AB) */
/* Text */
MACH_CONCAT, /* R(A) = R(B) ++ R(C) — string concatenation */
/* Typed integer comparisons (ABC) */
MACH_EQ_INT, /* R(A) = (R(B) == R(C)) — int */
MACH_NE_INT, /* R(A) = (R(B) != R(C)) — int */
MACH_LT_INT, /* R(A) = (R(B) < R(C)) — int */
MACH_LE_INT, /* R(A) = (R(B) <= R(C)) — int */
MACH_GT_INT, /* R(A) = (R(B) > R(C)) — int */
MACH_GE_INT, /* R(A) = (R(B) >= R(C)) — int */
/* Typed float comparisons (ABC) */
MACH_EQ_FLOAT, /* R(A) = (R(B) == R(C)) — float */
MACH_NE_FLOAT, /* R(A) = (R(B) != R(C)) — float */
MACH_LT_FLOAT, /* R(A) = (R(B) < R(C)) — float */
MACH_LE_FLOAT, /* R(A) = (R(B) <= R(C)) — float */
MACH_GT_FLOAT, /* R(A) = (R(B) > R(C)) — float */
MACH_GE_FLOAT, /* R(A) = (R(B) >= R(C)) — float */
/* Typed text comparisons (ABC) */
MACH_EQ_TEXT, /* R(A) = (R(B) == R(C)) — text */
MACH_NE_TEXT, /* R(A) = (R(B) != R(C)) — text */
MACH_LT_TEXT, /* R(A) = (R(B) < R(C)) — text */
MACH_LE_TEXT, /* R(A) = (R(B) <= R(C)) — text */
MACH_GT_TEXT, /* R(A) = (R(B) > R(C)) — text */
MACH_GE_TEXT, /* R(A) = (R(B) >= R(C)) — text */
/* Typed bool comparisons (ABC) */
MACH_EQ_BOOL, /* R(A) = (R(B) == R(C)) — bool */
MACH_NE_BOOL, /* R(A) = (R(B) != R(C)) — bool */
/* Special comparisons */
MACH_IS_IDENTICAL, /* R(A) = (R(B) === R(C)) — identity check (ABC) */
/* Type checks (AB) */
MACH_IS_INT, /* R(A) = is_int(R(B)) */
MACH_IS_NUM, /* R(A) = is_num(R(B)) */
MACH_IS_TEXT, /* R(A) = is_text(R(B)) */
MACH_IS_BOOL, /* R(A) = is_bool(R(B)) */
MACH_IS_NULL, /* R(A) = is_null(R(B)) */
MACH_TYPEOF, /* R(A) = typeof(R(B)) */
/* Logical (mcode-style) */
MACH_NOT, /* R(A) = !R(B) — boolean not (AB) */
MACH_AND, /* R(A) = R(B) && R(C) (ABC) */
MACH_OR, /* R(A) = R(B) || R(C) (ABC) */
/* Bitwise (mcode names) */
MACH_BITNOT, /* R(A) = ~R(B) (AB) */
MACH_BITAND, /* R(A) = R(B) & R(C) (ABC) */
MACH_BITOR, /* R(A) = R(B) | R(C) (ABC) */
MACH_BITXOR, /* R(A) = R(B) ^ R(C) (ABC) */
/* Property access (mcode names) */
MACH_LOAD_FIELD, /* R(A) = R(B).K(C) — named property (ABC) */
MACH_STORE_FIELD, /* R(A).K(B) = R(C) — named property (ABC) */
MACH_LOAD_INDEX, /* R(A) = R(B)[R(C)] — integer index (ABC) */
MACH_STORE_INDEX, /* R(A)[R(B)] = R(C) — integer index (ABC) */
MACH_LOAD_DYNAMIC, /* R(A) = R(B)[R(C)] — dynamic key (ABC) */
MACH_STORE_DYNAMIC, /* R(A)[R(B)] = R(C) — dynamic key (ABC) */
/* Object/Array creation (mcode names) */
MACH_NEWRECORD, /* R(A) = {} — new empty record (A only) */
/* Decomposed function calls (mcode-style) */
MACH_FRAME, /* R(A) = frame(R(B), C) — alloc call frame (ABC) */
MACH_SETARG, /* frame R(A)[B] = R(C) — set arg in frame (ABC) */
MACH_INVOKE, /* R(B) = invoke(R(A)) — call frame, result in R(B) (AB) */
MACH_GOFRAME, /* R(A) = goframe(R(B), C) — async frame (ABC) */
MACH_GOINVOKE, /* goinvoke(R(A)) — async invoke, no result (A only) */
/* Control flow */
MACH_JMPNOTNULL, /* if R(A)!=null: pc += sBx (iAsBx) */
/* Error handling */
MACH_DISRUPT, /* trigger disruption (A only) */
/* Variable storage */
MACH_SET_VAR, /* env/global[K(Bx)] = R(A) — store to var (ABx) */
/* Misc */
MACH_IN, /* R(A) = (R(B) in R(C)) — has property (ABC) */
MACH_IS_FUNC, /* R(A) = is_function(R(B)) */
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
MACH_OP_COUNT MACH_OP_COUNT
} MachOpcode; } MachOpcode;
static const char *mach_opcode_names[MACH_OP_COUNT] = { static const char *mach_opcode_names[MACH_OP_COUNT] = {
/* Legacy */
[MACH_LOADK] = "loadk", [MACH_LOADK] = "loadk",
[MACH_LOADI] = "loadi", [MACH_LOADI] = "loadi",
[MACH_LOADNULL] = "loadnull", [MACH_LOADNULL] = "loadnull",
@@ -597,7 +642,6 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_JMPTRUE] = "jmptrue", [MACH_JMPTRUE] = "jmptrue",
[MACH_JMPFALSE] = "jmpfalse", [MACH_JMPFALSE] = "jmpfalse",
[MACH_JMPNULL] = "jmpnull", [MACH_JMPNULL] = "jmpnull",
[MACH_CALL] = "call",
[MACH_RETURN] = "return", [MACH_RETURN] = "return",
[MACH_RETNIL] = "retnil", [MACH_RETNIL] = "retnil",
[MACH_NEWOBJECT] = "newobject", [MACH_NEWOBJECT] = "newobject",
@@ -610,10 +654,75 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_DELETEINDEX] = "deleteindex", [MACH_DELETEINDEX] = "deleteindex",
[MACH_HASPROP] = "hasprop", [MACH_HASPROP] = "hasprop",
[MACH_REGEXP] = "regexp", [MACH_REGEXP] = "regexp",
[MACH_CALLMETHOD] = "callmethod",
[MACH_EQ_TOL] = "eq_tol", [MACH_EQ_TOL] = "eq_tol",
[MACH_NEQ_TOL] = "neq_tol", [MACH_NEQ_TOL] = "neq_tol",
[MACH_NOP] = "nop", [MACH_NOP] = "nop",
/* Mcode-derived */
[MACH_ADD_INT] = "add_int",
[MACH_SUB_INT] = "sub_int",
[MACH_MUL_INT] = "mul_int",
[MACH_DIV_INT] = "div_int",
[MACH_MOD_INT] = "mod_int",
[MACH_NEG_INT] = "neg_int",
[MACH_ADD_FLOAT] = "add_float",
[MACH_SUB_FLOAT] = "sub_float",
[MACH_MUL_FLOAT] = "mul_float",
[MACH_DIV_FLOAT] = "div_float",
[MACH_MOD_FLOAT] = "mod_float",
[MACH_NEG_FLOAT] = "neg_float",
[MACH_CONCAT] = "concat",
[MACH_EQ_INT] = "eq_int",
[MACH_NE_INT] = "ne_int",
[MACH_LT_INT] = "lt_int",
[MACH_LE_INT] = "le_int",
[MACH_GT_INT] = "gt_int",
[MACH_GE_INT] = "ge_int",
[MACH_EQ_FLOAT] = "eq_float",
[MACH_NE_FLOAT] = "ne_float",
[MACH_LT_FLOAT] = "lt_float",
[MACH_LE_FLOAT] = "le_float",
[MACH_GT_FLOAT] = "gt_float",
[MACH_GE_FLOAT] = "ge_float",
[MACH_EQ_TEXT] = "eq_text",
[MACH_NE_TEXT] = "ne_text",
[MACH_LT_TEXT] = "lt_text",
[MACH_LE_TEXT] = "le_text",
[MACH_GT_TEXT] = "gt_text",
[MACH_GE_TEXT] = "ge_text",
[MACH_EQ_BOOL] = "eq_bool",
[MACH_NE_BOOL] = "ne_bool",
[MACH_IS_IDENTICAL] = "is_identical",
[MACH_IS_INT] = "is_int",
[MACH_IS_NUM] = "is_num",
[MACH_IS_TEXT] = "is_text",
[MACH_IS_BOOL] = "is_bool",
[MACH_IS_NULL] = "is_null",
[MACH_TYPEOF] = "typeof",
[MACH_NOT] = "not",
[MACH_AND] = "and",
[MACH_OR] = "or",
[MACH_BITNOT] = "bitnot",
[MACH_BITAND] = "bitand",
[MACH_BITOR] = "bitor",
[MACH_BITXOR] = "bitxor",
[MACH_LOAD_FIELD] = "load_field",
[MACH_STORE_FIELD] = "store_field",
[MACH_LOAD_INDEX] = "load_index",
[MACH_STORE_INDEX] = "store_index",
[MACH_LOAD_DYNAMIC] = "load_dynamic",
[MACH_STORE_DYNAMIC] = "store_dynamic",
[MACH_NEWRECORD] = "newrecord",
[MACH_FRAME] = "frame",
[MACH_SETARG] = "setarg",
[MACH_INVOKE] = "invoke",
[MACH_GOFRAME] = "goframe",
[MACH_GOINVOKE] = "goinvoke",
[MACH_JMPNOTNULL] = "jmpnotnull",
[MACH_DISRUPT] = "disrupt",
[MACH_SET_VAR] = "set_var",
[MACH_IN] = "in",
[MACH_IS_FUNC] = "is_func",
[MACH_IS_PROXY] = "is_proxy",
}; };
/* Compiled register-based code (off-heap, never GC'd). /* Compiled register-based code (off-heap, never GC'd).
@@ -644,28 +753,6 @@ typedef struct JSCodeRegister {
uint16_t disruption_pc; /* start of disruption handler (0 = none) */ uint16_t disruption_pc; /* start of disruption handler (0 = none) */
} JSCodeRegister; } JSCodeRegister;
/* Pre-parsed MCODE for a single function (off-heap, never GC'd).
Created by jsmcode_parse from cJSON MCODE output.
Instructions remain as cJSON pointers for string-based dispatch. */
typedef struct JSMCode {
uint16_t nr_args;
uint16_t nr_slots;
/* Pre-flattened instruction array (cJSON array items → C array for O(1) access) */
cJSON **instrs;
uint32_t instr_count;
/* Label map: label string → instruction index */
struct { const char *name; uint32_t index; } *labels;
uint32_t label_count;
/* Nested function definitions (indexes into top-level functions array) */
struct JSMCode **functions;
uint32_t func_count;
/* Keep root cJSON alive (owns all the cJSON nodes instrs[] point into) */
cJSON *json_root;
MachLineEntry *line_table; /* [instr_count], parallel to instrs[] */
const char *name; /* function name (points into cJSON tree) */
const char *filename; /* source filename (points into cJSON tree) */
uint16_t disruption_pc; /* start of disruption handler (0 = none) */
} JSMCode;
/* Frame for closures - used by link-time relocation model where closures /* Frame for closures - used by link-time relocation model where closures
reference outer frames via (depth, slot) addressing. reference outer frames via (depth, slot) addressing.
@@ -675,7 +762,7 @@ typedef struct JSFrame {
JSValue function; /* JSValue for GC safety (use JS_VALUE_GET_FUNCTION) */ JSValue function; /* JSValue for GC safety (use JS_VALUE_GET_FUNCTION) */
JSValue caller; /* JSValue for GC safety (unused currently) */ JSValue caller; /* JSValue for GC safety (unused currently) */
uint32_t return_pc; uint32_t return_pc;
JSValue slots[]; /* args, captured, locals, temps */ JSValue slots[]; /* [this][args][captured][locals][temps] */
} JSFrame; } JSFrame;
/* Execution state returned by vm_execute_frame */ /* Execution state returned by vm_execute_frame */
@@ -957,31 +1044,19 @@ struct JSContext {
int trace_type; int trace_type;
void *trace_data; void *trace_data;
/* Trampoline VM stacks (per actor/context) */
struct VMFrame *frame_stack; /* array of frames */
int frame_stack_top; /* current frame index (-1 = empty) */
int frame_stack_capacity; /* allocated capacity */
JSValue *value_stack; /* array of JSValues for locals/operands */
int value_stack_top; /* current top index */
int value_stack_capacity; /* allocated capacity */
/* Register VM frame root (updated by GC when frame moves) */ /* Register VM frame root (updated by GC when frame moves) */
JSValue reg_current_frame; /* current JSFrameRegister being executed */ JSValue reg_current_frame; /* current JSFrameRegister being executed */
uint32_t current_register_pc; /* PC at exception time */ uint32_t current_register_pc; /* PC at exception time */
/* Execution state (moved from JSRuntime — per-actor) */
JSValue current_exception;
struct JSStackFrame *current_stack_frame;
BOOL current_exception_is_uncatchable : 8;
BOOL in_out_of_memory : 8;
JSInterruptHandler *interrupt_handler; JSInterruptHandler *interrupt_handler;
void *interrupt_opaque; void *interrupt_opaque;
JSValue current_exception;
/* Stack overflow protection */ /* Stack overflow protection */
size_t stack_size; // todo: want this, but should be a simple increment/decrement counter while frames are pushed
const uint8_t *stack_top; size_t stack_depth;
const uint8_t *stack_limit; size_t stack_limit;
/* Parser state (for GC to scan cpool during parsing) */ /* Parser state (for GC to scan cpool during parsing) */
struct JSFunctionDef *current_parse_fd; struct JSFunctionDef *current_parse_fd;
@@ -1138,7 +1213,6 @@ typedef enum {
JS_FUNC_KIND_BYTECODE, JS_FUNC_KIND_BYTECODE,
JS_FUNC_KIND_C_DATA, JS_FUNC_KIND_C_DATA,
JS_FUNC_KIND_REGISTER, /* register-based VM function */ JS_FUNC_KIND_REGISTER, /* register-based VM function */
JS_FUNC_KIND_MCODE, /* MCODE JSON interpreter */
} JSFunctionKind; } JSFunctionKind;
typedef struct JSFunction { typedef struct JSFunction {
@@ -1162,11 +1236,6 @@ typedef struct JSFunction {
JSValue env_record; /* stone record, module environment */ JSValue env_record; /* stone record, module environment */
JSValue outer_frame; /* JSFrame JSValue, for closures */ JSValue outer_frame; /* JSFrame JSValue, for closures */
} reg; } reg;
struct {
JSMCode *code; /* pre-parsed MCODE (off-heap) */
JSValue outer_frame; /* lexical parent frame for closures */
JSValue env_record; /* module env or JS_NULL */
} mcode;
} u; } u;
} JSFunction; } JSFunction;
@@ -1325,42 +1394,10 @@ typedef struct JSProperty {
#define JS_ARRAY_MAX_CAP ((word_t)((1UL << 24) - 1)) #define JS_ARRAY_MAX_CAP ((word_t)((1UL << 24) - 1))
#endif #endif
typedef enum OPCodeFormat {
#define FMT(f) OP_FMT_##f,
#define DEF(id, size, n_pop, n_push, f)
#include "quickjs-opcode.h"
#undef DEF
#undef FMT
} OPCodeFormat;
enum OPCodeEnum {
#define FMT(f)
#define DEF(id, size, n_pop, n_push, f) OP_##id,
#define def(id, size, n_pop, n_push, f)
#include "quickjs-opcode.h"
#undef def
#undef DEF
#undef FMT
OP_COUNT, /* excluding temporary opcodes */
/* temporary opcodes : overlap with the short opcodes */
OP_TEMP_START = OP_nop + 1,
OP___dummy = OP_TEMP_START - 1,
#define FMT(f)
#define DEF(id, size, n_pop, n_push, f)
#define def(id, size, n_pop, n_push, f) OP_##id,
#include "quickjs-opcode.h"
#undef def
#undef DEF
#undef FMT
OP_TEMP_END,
};
JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv); JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
JSValue js_call_bound_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv); JSValue js_call_bound_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags); JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags);
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame); JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
int argc, JSValue *argv, JSValue outer_frame);
int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop); int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop);
JSValue __attribute__ ((format (printf, 2, 3))) JSValue __attribute__ ((format (printf, 2, 3)))
JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...); JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...);
@@ -1399,8 +1436,6 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue
static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
@@ -1544,22 +1579,6 @@ static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) {
*pval = new_val; *pval = new_val;
} }
#if !defined(CONFIG_STACK_CHECK)
static inline uintptr_t js_get_stack_pointer (void) { return 0; }
static inline BOOL js_check_stack_overflow (JSContext *ctx, size_t alloca_size) {
return FALSE;
}
#else
static inline uintptr_t js_get_stack_pointer (void) {
return (uintptr_t)__builtin_frame_address (0);
}
static inline BOOL js_check_stack_overflow (JSContext *ctx, size_t alloca_size) {
uintptr_t sp;
sp = js_get_stack_pointer () - alloca_size;
return unlikely (sp < (uintptr_t)ctx->stack_limit);
}
#endif
void JS_ThrowInterrupted (JSContext *ctx); void JS_ThrowInterrupted (JSContext *ctx);
static no_inline __exception int __js_poll_interrupts (JSContext *ctx) { static no_inline __exception int __js_poll_interrupts (JSContext *ctx) {
@@ -1692,13 +1711,6 @@ typedef struct {
} GetLineColCache; } GetLineColCache;
/* === MachVarInfo (shared by mach.c and mcode.c) === */
typedef struct MachVarInfo {
char *name;
int slot;
int is_const; /* 1 for def, function args; 0 for var */
int is_closure; /* 1 if captured by a nested function */
} MachVarInfo;
/* === PPretext (parser pretext, system-malloc, used by cell_js.c parser) === */ /* === PPretext (parser pretext, system-malloc, used by cell_js.c parser) === */
typedef struct PPretext { typedef struct PPretext {
@@ -1721,8 +1733,6 @@ int get_line_col_cached (GetLineColCache *s, int *pcol_num, const uint8_t *ptr);
/* runtime.c exports */ /* runtime.c exports */
JSValue JS_ThrowStackOverflow (JSContext *ctx); JSValue JS_ThrowStackOverflow (JSContext *ctx);
JSValue JS_ThrowSyntaxErrorVarRedeclaration (JSContext *ctx, JSValue prop);
JSValue JS_ThrowReferenceErrorUninitialized (JSContext *ctx, JSValue name);
int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name); int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name);
int JS_DefineObjectNameComputed (JSContext *ctx, JSValue obj, JSValue str); int JS_DefineObjectNameComputed (JSContext *ctx, JSValue obj, JSValue str);
int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue name, int flags, JSValue home_obj); int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue name, int flags, JSValue home_obj);
@@ -1736,12 +1746,9 @@ int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key);
void *js_realloc_rt (void *ptr, size_t size); void *js_realloc_rt (void *ptr, size_t size);
char *js_strdup_rt (const char *str); char *js_strdup_rt (const char *str);
JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2); JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2);
no_inline __exception int js_binary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
no_inline __exception int js_unary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
__exception int js_post_inc_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op); __exception int js_post_inc_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
no_inline int js_not_slow (JSContext *ctx, JSValue *sp); no_inline int js_not_slow (JSContext *ctx, JSValue *sp);
no_inline int js_relational_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op); no_inline int js_relational_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
no_inline int js_strict_eq_slow (JSContext *ctx, JSValue *sp, BOOL is_neq);
__exception int js_operator_in (JSContext *ctx, JSValue *sp); __exception int js_operator_in (JSContext *ctx, JSValue *sp);
__exception int js_operator_delete (JSContext *ctx, JSValue *sp); __exception int js_operator_delete (JSContext *ctx, JSValue *sp);
JSText *pretext_init (JSContext *ctx, int capacity); JSText *pretext_init (JSContext *ctx, int capacity);
@@ -1893,8 +1900,5 @@ JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
cJSON *mach_find_scope_record(cJSON *scopes, int function_nr); cJSON *mach_find_scope_record(cJSON *scopes, int function_nr);
int reg_vm_check_interrupt(JSContext *ctx); int reg_vm_check_interrupt(JSContext *ctx);
/* mcode.c exports */
JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, int argc, JSValue *argv, JSValue outer_frame);
#endif /* QUICKJS_INTERNAL_H */ #endif /* QUICKJS_INTERNAL_H */

View File

@@ -1,296 +0,0 @@
/*
* QuickJS opcode definitions
*
* Copyright (c) 2017-2018 Fabrice Bellard
* Copyright (c) 2017-2018 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifdef FMT
FMT(none)
FMT(none_int)
FMT(none_loc)
FMT(none_arg)
FMT(u8)
FMT(i8)
FMT(loc8)
FMT(const8)
FMT(label8)
FMT(u16)
FMT(i16)
FMT(label16)
FMT(npop)
FMT(npopx)
FMT(npop_u16)
FMT(loc)
FMT(arg)
FMT(u32)
FMT(i32)
FMT(const)
FMT(label)
FMT(label_u16)
FMT(key)
FMT(key_u8)
FMT(key_u16)
FMT(key_label_u16)
FMT(u8_u16) /* 1 byte + 2 bytes for upvalue access */
#undef FMT
#endif /* FMT */
#ifdef DEF
#ifndef def
#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f)
#endif
DEF(invalid, 1, 0, 0, none) /* never emitted */
/* push values */
DEF( push_i32, 5, 0, 1, i32)
DEF( push_const, 5, 0, 1, const)
DEF( fclosure, 5, 0, 1, const) /* must follow push_const */
DEF( null, 1, 0, 1, none)
DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */
DEF( push_false, 1, 0, 1, none)
DEF( push_true, 1, 0, 1, none)
DEF( object, 1, 0, 1, none)
DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */
DEF( drop, 1, 1, 0, none) /* a -> */
DEF( nip, 1, 2, 1, none) /* a b -> b */
DEF( nip1, 1, 3, 2, none) /* a b c -> b c */
DEF( dup, 1, 1, 2, none) /* a -> a a */
DEF( dup1, 1, 2, 3, none) /* a b -> a a b */
DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */
DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */
DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */
DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */
DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */
DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */
DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */
DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */
DEF( swap, 1, 2, 2, none) /* a b -> b a */
DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */
DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */
DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */
DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */
DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */
DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */
DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */
DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */
DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */
DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */
DEF( return, 1, 1, 0, none)
DEF( return_undef, 1, 0, 0, none)
DEF( throw, 1, 1, 0, none)
DEF( throw_error, 6, 0, 0, key_u8)
DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
bytecode string */
/* Global variable access - resolved by linker to get/set_global_slot */
DEF( check_var, 5, 0, 1, key) /* check if a variable exists - resolved by linker */
DEF( get_var_undef, 5, 0, 1, key) /* resolved by linker to get_global_slot */
DEF( get_var, 5, 0, 1, key) /* resolved by linker to get_global_slot */
DEF( put_var, 5, 1, 0, key) /* resolved by linker to set_global_slot */
DEF( put_var_init, 5, 1, 0, key) /* resolved by linker to set_global_slot */
DEF( put_var_strict, 5, 2, 0, key) /* resolved by linker to set_global_slot */
/* Global variable opcodes - resolved by linker to get/set_global_slot */
DEF( define_var, 6, 0, 0, key_u8)
DEF(check_define_var, 6, 0, 0, key_u8)
DEF( define_func, 6, 1, 0, key_u8)
DEF( get_field, 5, 1, 1, key)
DEF( get_field2, 5, 1, 2, key)
DEF( put_field, 5, 2, 0, key)
DEF( get_array_el, 1, 2, 1, none)
DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */
DEF( get_array_el3, 1, 2, 3, none) /* obj prop -> obj prop1 value */
DEF( put_array_el, 1, 3, 0, none)
DEF( define_field, 5, 2, 1, key)
DEF( set_name, 5, 1, 1, key)
DEF(set_name_computed, 1, 2, 2, none)
DEF(define_array_el, 1, 3, 2, none)
DEF(copy_data_properties, 2, 3, 3, u8)
DEF( define_method, 6, 2, 1, key_u8)
DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */
DEF( define_class, 6, 2, 2, key_u8) /* parent ctor -> ctor proto */
DEF( define_class_computed, 6, 3, 3, key_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */
DEF( get_loc, 3, 0, 1, loc)
DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */
DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */
DEF( get_arg, 3, 0, 1, arg)
DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */
DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */
DEF(set_loc_uninitialized, 3, 0, 0, loc)
DEF( get_loc_check, 3, 0, 1, loc)
DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */
DEF( put_loc_check_init, 3, 1, 0, loc)
DEF(get_loc_checkthis, 3, 0, 1, loc)
DEF( if_false, 5, 1, 0, label)
DEF( if_true, 5, 1, 0, label) /* must come after if_false */
DEF( goto, 5, 0, 0, label) /* must come after if_true */
DEF( catch, 5, 0, 1, label)
DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
DEF( to_propkey, 1, 1, 1, none)
/* arithmetic/logic operations */
DEF( neg, 1, 1, 1, none)
DEF( plus, 1, 1, 1, none)
DEF( dec, 1, 1, 1, none)
DEF( inc, 1, 1, 1, none)
DEF( post_dec, 1, 1, 2, none)
DEF( post_inc, 1, 1, 2, none)
DEF( dec_loc, 2, 0, 0, loc8)
DEF( inc_loc, 2, 0, 0, loc8)
DEF( add_loc, 2, 1, 0, loc8)
DEF( not, 1, 1, 1, none)
DEF( lnot, 1, 1, 1, none)
DEF( delete, 1, 2, 1, none)
DEF( delete_var, 5, 0, 1, key) /* deprecated - global object is immutable */
DEF( mul, 1, 2, 1, none)
DEF( mul_float, 1, 2, 1, none)
DEF( div, 1, 2, 1, none)
DEF( div_float, 1, 2, 1, none)
DEF( mod, 1, 2, 1, none)
DEF( add, 1, 2, 1, none)
DEF( add_float, 1, 2, 1, none)
DEF( sub, 1, 2, 1, none)
DEF( sub_float, 1, 2, 1, none)
DEF( pow, 1, 2, 1, none)
DEF( shl, 1, 2, 1, none)
DEF( sar, 1, 2, 1, none)
DEF( shr, 1, 2, 1, none)
DEF( lt, 1, 2, 1, none)
DEF( lte, 1, 2, 1, none)
DEF( gt, 1, 2, 1, none)
DEF( gte, 1, 2, 1, none)
DEF( in, 1, 2, 1, none)
DEF( strict_eq, 1, 2, 1, none)
DEF( strict_neq, 1, 2, 1, none)
DEF( and, 1, 2, 1, none)
DEF( xor, 1, 2, 1, none)
DEF( or, 1, 2, 1, none)
/* format template - format_string_cpool_idx(u32), expr_count(u16)
Note: n_push=2 ensures stack has room for temp [format_str, arr] pair,
even though we only leave 1 value (the result) on the stack. */
DEF(format_template, 7, 0, 1, npop_u16)
/* Upvalue access (closures via outer_frame chain) */
DEF( get_up, 4, 0, 1, u8_u16) /* depth:u8, slot:u16 -> value */
DEF( set_up, 4, 1, 0, u8_u16) /* value, depth:u8, slot:u16 -> */
/* Name resolution with bytecode patching */
DEF( get_name, 5, 0, 1, const) /* cpool_idx -> value, patches itself */
DEF( get_env_slot, 3, 0, 1, u16) /* slot -> value (patched from get_name) */
DEF( set_env_slot, 3, 1, 0, u16) /* value -> slot (patched from put_var) */
DEF(get_global_slot, 3, 0, 1, u16) /* slot -> value (patched from get_var) */
DEF(set_global_slot, 3, 1, 0, u16) /* value -> slot (patched from put_var) */
/* must be the last non short and non temporary opcode */
DEF( nop, 1, 0, 0, none)
/* temporary opcodes: never emitted in the final bytecode */
def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */
def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */
def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */
/* the following opcodes must be in the same order as the 'with_x' and
get_var_undef, get_var and put_var opcodes */
def(scope_get_var_undef, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_get_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_put_var, 7, 1, 0, key_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_delete_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_put_var_init, 7, 0, 2, key_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_get_var_checkthis, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
def(get_field_opt_chain, 5, 1, 1, key) /* emitted in phase 1, removed in phase 2 */
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */
#if SHORT_OPCODES
DEF( push_minus1, 1, 0, 1, none_int)
DEF( push_0, 1, 0, 1, none_int)
DEF( push_1, 1, 0, 1, none_int)
DEF( push_2, 1, 0, 1, none_int)
DEF( push_3, 1, 0, 1, none_int)
DEF( push_4, 1, 0, 1, none_int)
DEF( push_5, 1, 0, 1, none_int)
DEF( push_6, 1, 0, 1, none_int)
DEF( push_7, 1, 0, 1, none_int)
DEF( push_i8, 2, 0, 1, i8)
DEF( push_i16, 3, 0, 1, i16)
DEF( push_const8, 2, 0, 1, const8)
DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */
DEF(push_empty_string, 1, 0, 1, none)
DEF( get_loc8, 2, 0, 1, loc8)
DEF( put_loc8, 2, 1, 0, loc8)
DEF( set_loc8, 2, 1, 1, loc8)
DEF( get_loc0, 1, 0, 1, none_loc)
DEF( get_loc1, 1, 0, 1, none_loc)
DEF( get_loc2, 1, 0, 1, none_loc)
DEF( get_loc3, 1, 0, 1, none_loc)
DEF( put_loc0, 1, 1, 0, none_loc)
DEF( put_loc1, 1, 1, 0, none_loc)
DEF( put_loc2, 1, 1, 0, none_loc)
DEF( put_loc3, 1, 1, 0, none_loc)
DEF( set_loc0, 1, 1, 1, none_loc)
DEF( set_loc1, 1, 1, 1, none_loc)
DEF( set_loc2, 1, 1, 1, none_loc)
DEF( set_loc3, 1, 1, 1, none_loc)
DEF( get_arg0, 1, 0, 1, none_arg)
DEF( get_arg1, 1, 0, 1, none_arg)
DEF( get_arg2, 1, 0, 1, none_arg)
DEF( get_arg3, 1, 0, 1, none_arg)
DEF( put_arg0, 1, 1, 0, none_arg)
DEF( put_arg1, 1, 1, 0, none_arg)
DEF( put_arg2, 1, 1, 0, none_arg)
DEF( put_arg3, 1, 1, 0, none_arg)
DEF( set_arg0, 1, 1, 1, none_arg)
DEF( set_arg1, 1, 1, 1, none_arg)
DEF( set_arg2, 1, 1, 1, none_arg)
DEF( set_arg3, 1, 1, 1, none_arg)
DEF( if_false8, 2, 1, 0, label8)
DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */
DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */
DEF( goto16, 3, 0, 0, label16)
DEF( call0, 1, 1, 1, npopx)
DEF( call1, 1, 1, 1, npopx)
DEF( call2, 1, 1, 1, npopx)
DEF( call3, 1, 1, 1, npopx)
DEF( is_null, 1, 1, 1, none)
#endif
#undef DEF
#undef def
#endif /* DEF */

View File

@@ -55,13 +55,13 @@ enum mist_obj_type {
OBJ_FORWARD = 7 OBJ_FORWARD = 7
}; };
typedef uint64_t JSValue;
#define OBJHDR_S_BIT 3u #define OBJHDR_S_BIT 3u
#define OBJHDR_P_BIT 4u #define OBJHDR_P_BIT 4u
#define OBJHDR_A_BIT 5u #define OBJHDR_A_BIT 5u
#define OBJHDR_R_BIT 7u #define OBJHDR_R_BIT 7u
#define OBJHDR_FLAG(bit) ((objhdr_t)1ull << (bit)) #define OBJHDR_FLAG(bit) ((objhdr_t)1ull << (bit))
#define OBJHDR_S_MASK OBJHDR_FLAG (OBJHDR_S_BIT) #define OBJHDR_S_MASK OBJHDR_FLAG (OBJHDR_S_BIT)
#define OBJHDR_P_MASK OBJHDR_FLAG (OBJHDR_P_BIT) #define OBJHDR_P_MASK OBJHDR_FLAG (OBJHDR_P_BIT)
@@ -115,39 +115,24 @@ struct JSGCRef;
============================================================ */ ============================================================ */
#if INTPTR_MAX >= INT64_MAX
#define JS_PTR64
#define JS_PTR64_DEF(a) a
typedef uint64_t JSValue;
#define JSW 8
#else
typedef uint32_t JSValue;
#define JSW 4
#define JS_PTR64_DEF(a)
#endif
#define JSValue JSValue
/* JSValueConst is just JSValue (const is not needed in value semantics) */ /* JSValueConst is just JSValue (const is not needed in value semantics) */
typedef JSValue JSValueConst; typedef JSValue JSValueConst;
#define JSW 8
/* LSB-based tags */ /* LSB-based tags */
enum { enum {
/* Primary tags (low bits) */ /* Primary tags (low bits) */
JS_TAG_INT = 0, /* LSB = 0 */ JS_TAG_INT = 0, /* LSB = 0 */
JS_TAG_PTR = 1, /* LSB = 01 */ JS_TAG_PTR = 1, /* LSB = 01 */
#ifdef JS_PTR64
JS_TAG_SHORT_FLOAT = 5, /* LSB = 101 */ JS_TAG_SHORT_FLOAT = 5, /* LSB = 101 */
#endif
JS_TAG_SPECIAL = 3, /* LSB = 11 */ JS_TAG_SPECIAL = 3, /* LSB = 11 */
/* Special subtypes (5 bits: xxxx11) */ /* Special subtypes (5 bits: xxxx11) */
JS_TAG_BOOL = 0x03, /* 00011 */ JS_TAG_BOOL = 0x03, /* 00011 */
JS_TAG_NULL = 0x07, /* 00111 */ JS_TAG_NULL = 0x07, /* 00111 */
JS_TAG_EXCEPTION = 0x0F, /* 01111 */ JS_TAG_EXCEPTION = 0x0F, /* 01111 */
JS_TAG_UNINITIALIZED = 0x17, /* 10111 */ JS_TAG_STRING_IMM = 0x0B, /* 01011 - immediate ASCII (up to 7 chars) */
JS_TAG_STRING_IMM = 0x1B, /* 11011 - immediate ASCII (up to 7 chars) */
JS_TAG_CATCH_OFFSET = 0x1F, /* 11111 */
}; };
/* Compatibility tag aliases for external code */ /* Compatibility tag aliases for external code */
@@ -180,16 +165,10 @@ void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref);
/* Get primary tag (low 2-3 bits) */ /* Get primary tag (low 2-3 bits) */
static inline int static inline int
JS_VALUE_GET_TAG (JSValue v) { JS_VALUE_GET_TAG (JSValue v) {
#ifdef JS_PTR64
if ((v & 1) == 0) return JS_TAG_INT; if ((v & 1) == 0) return JS_TAG_INT;
if ((v & 7) == JS_TAG_SHORT_FLOAT) return JS_TAG_SHORT_FLOAT; if ((v & 7) == JS_TAG_SHORT_FLOAT) return JS_TAG_SHORT_FLOAT;
if ((v & 3) == JS_TAG_PTR) return JS_TAG_PTR; if ((v & 3) == JS_TAG_PTR) return JS_TAG_PTR;
return (int)(v & 0x1F); /* special tag */ return (int)(v & 0x1F); /* special tag */
#else
if ((v & 1) == 0) return JS_TAG_INT;
if ((v & 3) == JS_TAG_PTR) return JS_TAG_PTR;
return (int)(v & 0x1F);
#endif
} }
#define JS_VALUE_GET_NORM_TAG(v) JS_VALUE_GET_TAG (v) #define JS_VALUE_GET_NORM_TAG(v) JS_VALUE_GET_TAG (v)
@@ -220,7 +199,6 @@ static inline JSValue _JS_MkVal (int tag, int32_t val) {
Out of range → JS_NULL Out of range → JS_NULL
============================================================ */ ============================================================ */
#ifdef JS_PTR64
static inline JSValue static inline JSValue
__JS_NewFloat64 (JSContext *ctx, double d) { __JS_NewFloat64 (JSContext *ctx, double d) {
union { union {
@@ -280,17 +258,6 @@ static inline double JS_VALUE_GET_FLOAT64 (JSValue v) {
#define JS_TAG_IS_FLOAT64(tag) ((tag) == JS_TAG_SHORT_FLOAT) #define JS_TAG_IS_FLOAT64(tag) ((tag) == JS_TAG_SHORT_FLOAT)
#define JS_NAN JS_MKVAL (JS_TAG_NULL, 0) #define JS_NAN JS_MKVAL (JS_TAG_NULL, 0)
#else /* 32-bit: no short float, use boxed double */
static inline JSValue __JS_NewFloat64 (JSContext *ctx,
double d); /* forward decl */
static inline double JS_VALUE_GET_FLOAT64 (JSValue v);
#define JS_TAG_IS_FLOAT64(tag) (0)
#define JS_NAN JS_MKVAL (JS_TAG_NULL, 0)
#endif /* JS_PTR64 */
/* ============================================================ /* ============================================================
Type Checks Type Checks
============================================================ */ ============================================================ */
@@ -299,14 +266,10 @@ static inline JS_BOOL JS_IsInt (JSValue v) { return (v & 1) == 0; }
static inline JS_BOOL JS_IsPtr (JSValue v) { return (v & 7) == JS_TAG_PTR; } static inline JS_BOOL JS_IsPtr (JSValue v) { return (v & 7) == JS_TAG_PTR; }
static inline JS_BOOL JS_IsSpecial (JSValue v) { return (v & 3) == JS_TAG_SPECIAL; } static inline JS_BOOL JS_IsSpecial (JSValue v) { return (v & 3) == JS_TAG_SPECIAL; }
#ifdef JS_PTR64
static inline JS_BOOL static inline JS_BOOL
JS_IsShortFloat (JSValue v) { JS_IsShortFloat (JSValue v) {
return (v & 7) == JS_TAG_SHORT_FLOAT; return (v & 7) == JS_TAG_SHORT_FLOAT;
} }
#endif
#define JS_VALUE_IS_BOTH_INT(v1, v2) (((v1) & 1) == 0 && ((v2) & 1) == 0) #define JS_VALUE_IS_BOTH_INT(v1, v2) (((v1) & 1) == 0 && ((v2) & 1) == 0)
#define JS_VALUE_IS_BOTH_FLOAT(v1, v2) \ #define JS_VALUE_IS_BOTH_FLOAT(v1, v2) \
@@ -320,7 +283,6 @@ JS_IsShortFloat (JSValue v) {
#define JS_FALSE ((JSValue)JS_TAG_BOOL) #define JS_FALSE ((JSValue)JS_TAG_BOOL)
#define JS_TRUE ((JSValue)(JS_TAG_BOOL | (1 << 5))) #define JS_TRUE ((JSValue)(JS_TAG_BOOL | (1 << 5)))
#define JS_EXCEPTION ((JSValue)JS_TAG_EXCEPTION) #define JS_EXCEPTION ((JSValue)JS_TAG_EXCEPTION)
#define JS_UNINITIALIZED ((JSValue)JS_TAG_UNINITIALIZED)
/* flags for object properties - simplified model: /* flags for object properties - simplified model:
- No per-property writable/configurable (use stone() for immutability) - No per-property writable/configurable (use stone() for immutability)
@@ -344,27 +306,10 @@ typedef JSValue JSCFunctionData (JSContext *ctx, JSValue this_val,
int argc, JSValue *argv, int magic, int argc, JSValue *argv, int magic,
JSValue *data); JSValue *data);
typedef struct JSMallocState {
size_t malloc_count;
size_t malloc_size;
size_t malloc_limit;
void *opaque; /* user opaque */
} JSMallocState;
typedef struct JSMallocFunctions {
void *(*js_malloc) (JSMallocState *s, size_t size);
void (*js_free) (JSMallocState *s, void *ptr);
void *(*js_realloc) (JSMallocState *s, void *ptr, size_t size);
size_t (*js_malloc_usable_size) (const void *ptr);
} JSMallocFunctions;
typedef struct JSGCObjectHeader JSGCObjectHeader;
JSValue JS_Stone (JSContext *ctx, JSValue this_val); JSValue JS_Stone (JSContext *ctx, JSValue this_val);
JSRuntime *JS_NewRuntime (void); JSRuntime *JS_NewRuntime (void);
/* info lifetime must exceed that of rt */ /* info lifetime must exceed that of rt */
void JS_SetRuntimeInfo (JSRuntime *rt, const char *info);
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit); void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
/* use 0 to disable maximum stack size check */ /* use 0 to disable maximum stack size check */
void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size); void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
@@ -372,11 +317,6 @@ void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
used to check stack overflow. */ used to check stack overflow. */
void JS_UpdateStackTop (JSContext *ctx); void JS_UpdateStackTop (JSContext *ctx);
void JS_FreeRuntime (JSRuntime *rt); void JS_FreeRuntime (JSRuntime *rt);
void *JS_GetRuntimeOpaque (JSRuntime *rt);
void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque);
typedef void JS_MarkFunc (JSRuntime *rt, JSGCObjectHeader *gp);
/* JS_MarkValue is a no-op with copying GC (values are traced from roots) */
void JS_MarkValue (JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func);
JS_BOOL JS_IsLiveObject (JSRuntime *rt, JSValue obj); JS_BOOL JS_IsLiveObject (JSRuntime *rt, JSValue obj);
JSContext *JS_NewContext (JSRuntime *rt); JSContext *JS_NewContext (JSRuntime *rt);
@@ -410,8 +350,6 @@ void JS_ComputeMemoryUsage (JSRuntime *rt, JSMemoryUsage *s);
void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt);
typedef void JSClassFinalizer (JSRuntime *rt, JSValue val); typedef void JSClassFinalizer (JSRuntime *rt, JSValue val);
typedef void JSClassGCMark (JSRuntime *rt, JSValue val,
JS_MarkFunc *mark_func);
typedef JSValue JSClassCall (JSContext *ctx, JSValue func_obj, typedef JSValue JSClassCall (JSContext *ctx, JSValue func_obj,
JSValue this_val, int argc, JSValue this_val, int argc,
JSValue *argv, int flags); JSValue *argv, int flags);
@@ -419,8 +357,6 @@ typedef JSValue JSClassCall (JSContext *ctx, JSValue func_obj,
typedef struct JSClassDef { typedef struct JSClassDef {
const char *class_name; const char *class_name;
JSClassFinalizer *finalizer; JSClassFinalizer *finalizer;
JSClassGCMark *gc_mark;
/* if call != NULL, the object is a function */
JSClassCall *call; JSClassCall *call;
} JSClassDef; } JSClassDef;
@@ -459,11 +395,6 @@ JS_NewInt32 (JSContext *ctx, int32_t val) {
return JS_MKVAL (JS_TAG_INT, val); return JS_MKVAL (JS_TAG_INT, val);
} }
static inline JSValue
JS_NewCatchOffset (JSContext *ctx, int32_t val) {
return JS_MKVAL (JS_TAG_CATCH_OFFSET, val);
}
static inline JSValue static inline JSValue
JS_NewInt64 (JSContext *ctx, int64_t val) { JS_NewInt64 (JSContext *ctx, int64_t val) {
JSValue v; JSValue v;
@@ -521,10 +452,6 @@ static inline JS_BOOL JS_IsException (JSValue v) {
return (JS_VALUE_GET_TAG (v) == JS_TAG_EXCEPTION); return (JS_VALUE_GET_TAG (v) == JS_TAG_EXCEPTION);
} }
static inline JS_BOOL JS_IsUninitialized (JSValue v) {
return (JS_VALUE_GET_TAG (v) == JS_TAG_UNINITIALIZED);
}
/* Immediate String Helpers */ /* Immediate String Helpers */
#define MIST_ASCII_MAX_LEN 7 #define MIST_ASCII_MAX_LEN 7
@@ -538,13 +465,11 @@ MIST_GetImmediateASCIILen (JSValue v) {
return (int)((v >> 5) & 0x7); return (int)((v >> 5) & 0x7);
} }
static inline int static inline int MIST_GetImmediateASCIIChar (JSValue v, int idx) {
MIST_GetImmediateASCIIChar (JSValue v, int idx) {
return (int)((v >> (8 + idx * 8)) & 0xFF); return (int)((v >> (8 + idx * 8)) & 0xFF);
} }
static inline JSValue static inline JSValue MIST_TryNewImmediateASCII (const char *str, size_t len) {
MIST_TryNewImmediateASCII (const char *str, size_t len) {
if (len > MIST_ASCII_MAX_LEN) return JS_NULL; if (len > MIST_ASCII_MAX_LEN) return JS_NULL;
JSValue v = (JSValue)JS_TAG_STRING_IMM | ((JSValue)len << 5); JSValue v = (JSValue)JS_TAG_STRING_IMM | ((JSValue)len << 5);
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
@@ -555,20 +480,10 @@ MIST_TryNewImmediateASCII (const char *str, size_t len) {
return v; return v;
} }
static inline JS_BOOL JS_IsInteger (JSValue v) {
return JS_VALUE_GET_TAG (v) == JS_TAG_INT;
}
static inline JS_BOOL JS_IsObject (JSValue v) {
return JS_IsPtr (v);
}
JS_BOOL JS_IsArray(JSValue v); JS_BOOL JS_IsArray(JSValue v);
JS_BOOL JS_IsRecord(JSValue v); JS_BOOL JS_IsRecord(JSValue v);
#define JS_IsObject JS_IsRecord
JS_BOOL JS_IsFunction(JSValue v); JS_BOOL JS_IsFunction(JSValue v);
JS_BOOL JS_IsCode(JSValue v);
JS_BOOL JS_IsForwarded(JSValue v);
JS_BOOL JS_IsFrame(JSValue v);
JS_BOOL JS_IsBlob(JSValue v); JS_BOOL JS_IsBlob(JSValue v);
JS_BOOL JS_IsText(JSValue v); JS_BOOL JS_IsText(JSValue v);
static JS_BOOL JS_IsStone(JSValue v); static JS_BOOL JS_IsStone(JSValue v);
@@ -591,6 +506,7 @@ JSValue __js_printf_like (2, 3)
JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...); JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...);
JSValue JS_ThrowOutOfMemory (JSContext *ctx); JSValue JS_ThrowOutOfMemory (JSContext *ctx);
// TODO: rename this to just "eq"
JS_BOOL JS_StrictEq (JSContext *ctx, JSValue op1, JSValue op2); JS_BOOL JS_StrictEq (JSContext *ctx, JSValue op1, JSValue op2);
int JS_ToBool (JSContext *ctx, JSValue val); /* return -1 for JS_EXCEPTION */ int JS_ToBool (JSContext *ctx, JSValue val); /* return -1 for JS_EXCEPTION */
@@ -625,7 +541,6 @@ JSValue JS_NewObject (JSContext *ctx);
JSValue JS_NewArray (JSContext *ctx); JSValue JS_NewArray (JSContext *ctx);
JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len); JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len);
/* GC-safe push: takes pointer to array, updates it if array grows */
int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val); int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val);
JSValue JS_ArrayPop (JSContext *ctx, JSValue obj); JSValue JS_ArrayPop (JSContext *ctx, JSValue obj);
@@ -690,24 +605,17 @@ void JS_PrintTextLn (JSContext *ctx, JSValue val);
void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values); void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values);
JSValue JS_GetProperty (JSContext *ctx, JSValue this_obj, JSValue prop); JSValue JS_GetProperty (JSContext *ctx, JSValue this_obj, JSValue prop);
int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val);
// For records // For records
JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop); JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop);
int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSValue val); int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSValue val);
// Set property on the global object
int JS_SetGlobalStr (JSContext *ctx, const char *prop, JSValue val);
int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val);
JSValue JS_GetPrototype (JSContext *ctx, JSValue val);
// Must be an array // Must be an array
JSValue JS_GetPropertyNumber (JSContext *ctx, JSValue this_obj, int idx); JSValue JS_GetPropertyNumber (JSContext *ctx, JSValue this_obj, int idx);
JSValue JS_SetPropertyNumber (JSContext *ctx, JSValue obj, int idx, JSValue val); JSValue JS_SetPropertyNumber (JSContext *ctx, JSValue obj, int idx, JSValue val);
// Indexed property access (works with arrays and objects) JSValue JS_GetPrototype (JSContext *ctx, JSValue val);
JSValue JS_GetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx);
int JS_SetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx, JSValue val);
int JS_SetPropertyInt64 (JSContext *ctx, JSValue this_obj, int64_t idx, JSValue val);
/* Get property keys as array of text */ /* Get property keys as array of text */
JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj); JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj);
@@ -732,13 +640,6 @@ JSValue JS_JSONStringify (JSContext *ctx, JSValue obj,
typedef int JSInterruptHandler (JSRuntime *rt, void *opaque); typedef int JSInterruptHandler (JSRuntime *rt, void *opaque);
void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb, void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
void *opaque); void *opaque);
/* select which debug info is stripped from the compiled code */
#define JS_STRIP_SOURCE (1 << 0) /* strip source code */
#define JS_STRIP_DEBUG \
(1 << 1) /* strip all debug info including source code */
void JS_SetStripInfo (JSRuntime *rt, int flags);
int JS_GetStripInfo (JSRuntime *rt);
/* C function definition */ /* C function definition */
typedef enum JSCFunctionEnum { typedef enum JSCFunctionEnum {
@@ -1046,12 +947,12 @@ typedef void (*js_hook) (JSContext *, int type, js_debug *dbg, void *user);
void js_debug_sethook (JSContext *ctx, js_hook, int type, void *user); void js_debug_sethook (JSContext *ctx, js_hook, int type, void *user);
uint32_t js_debugger_stack_depth (JSContext *ctx); uint32_t js_debugger_stack_depth (JSContext *ctx);
JSValue js_debugger_backtrace_fns (JSContext *ctx, const uint8_t *cur_pc); JSValue js_debugger_backtrace_fns (JSContext *ctx);
JSValue js_debugger_closure_variables (JSContext *ctx, JSValue fn); JSValue js_debugger_closure_variables (JSContext *ctx, JSValue fn);
JSValue js_debugger_local_variables (JSContext *ctx, int stack_index); JSValue js_debugger_local_variables (JSContext *ctx, int stack_index);
void js_debugger_set_closure_variable (JSContext *js, JSValue fn, void js_debugger_set_closure_variable (JSContext *js, JSValue fn,
JSValue var_name, JSValue val); JSValue var_name, JSValue val);
JSValue js_debugger_build_backtrace (JSContext *ctx, const uint8_t *cur_pc); JSValue js_debugger_build_backtrace (JSContext *ctx);
JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn); JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn);
JSValue js_debugger_fn_bytecode (JSContext *js, JSValue fn); JSValue js_debugger_fn_bytecode (JSContext *js, JSValue fn);
void *js_debugger_val_address (JSContext *js, JSValue val); void *js_debugger_val_address (JSContext *js, JSValue val);
@@ -1091,30 +992,14 @@ MachCode *JS_DeserializeMachCode(const uint8_t *data, size_t size);
/* Load compiled MachCode into a JSContext, materializing JSValues. */ /* Load compiled MachCode into a JSContext, materializing JSValues. */
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env); struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);
/* Dump MACH bytecode to stdout. Takes AST cJSON tree. */
void JS_DumpMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
/* Dump MACH bytecode to stdout. Takes AST JSON string. */
void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Compile and execute MACH bytecode from AST cJSON tree. */
JSValue JS_RunMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
/* Compile and execute MACH bytecode from AST JSON string. */
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Deserialize and execute pre-compiled MACH binary bytecode. */ /* Deserialize and execute pre-compiled MACH binary bytecode. */
JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env); JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env);
/* Execute MCODE from cJSON tree. Takes ownership of root. */ /* Dump disassembly of pre-compiled MACH binary bytecode. */
JSValue JS_CallMcodeTree (JSContext *ctx, struct cJSON *root); void JS_DumpMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env);
/* Execute MCODE from cJSON tree with hidden env. Takes ownership of root. */ /* Compile mcode JSON IR to MachCode binary. */
JSValue JS_CallMcodeTreeEnv (JSContext *ctx, struct cJSON *root, JSValue env); MachCode *mach_compile_mcode(struct cJSON *mcode_json);
/* Parse and execute MCODE JSON string.
Returns result of execution, or JS_EXCEPTION on error. */
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
/* Get stack trace as cJSON array of frame objects. /* Get stack trace as cJSON array of frame objects.
Returns NULL if no register VM frame is active. Returns NULL if no register VM frame is active.

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,8 @@ static struct {
actor_node *main_tail; // Main Thread Queue Tail actor_node *main_tail; // Main Thread Queue Tail
int shutting_down; int shutting_down;
int quiescence_enabled; // set after bootstrap, before actor_loop
_Atomic int quiescent_count; // actors idle with no messages and no timers
pthread_t *worker_threads; pthread_t *worker_threads;
int num_workers; int num_workers;
@@ -258,6 +260,10 @@ void actor_initialize(void) {
void actor_free(cell_rt *actor) void actor_free(cell_rt *actor)
{ {
if (actor->is_quiescent) {
actor->is_quiescent = 0;
atomic_fetch_sub(&engine.quiescent_count, 1);
}
lockless_shdel(actors, actor->id); lockless_shdel(actors, actor->id);
// Do not go forward with actor destruction until the actor is completely free // Do not go forward with actor destruction until the actor is completely free
@@ -303,10 +309,41 @@ void actor_free(cell_rt *actor)
free(actor); free(actor);
int actor_count = lockless_shlen(actors); int actor_count = lockless_shlen(actors);
if (actor_count == 0) exit(0); if (actor_count == 0) {
fprintf(stderr, "all actors are dead\n");
pthread_mutex_lock(&engine.lock);
engine.shutting_down = 1;
pthread_cond_broadcast(&engine.wake_cond);
pthread_cond_broadcast(&engine.timer_cond);
pthread_cond_broadcast(&engine.main_cond);
pthread_mutex_unlock(&engine.lock);
}
}
int scheduler_actor_count(void) {
return (int)lockless_shlen(actors);
}
void scheduler_enable_quiescence(void) {
engine.quiescence_enabled = 1;
// Check if all actors are already quiescent
int qc = atomic_load(&engine.quiescent_count);
int total = (int)lockless_shlen(actors);
if (qc >= total && total > 0) {
pthread_mutex_lock(&engine.lock);
engine.shutting_down = 1;
pthread_cond_broadcast(&engine.wake_cond);
pthread_cond_broadcast(&engine.timer_cond);
pthread_cond_broadcast(&engine.main_cond);
pthread_mutex_unlock(&engine.lock);
}
} }
void exit_handler(void) { void exit_handler(void) {
static int already_exiting = 0;
if (already_exiting) return;
already_exiting = 1;
pthread_mutex_lock(&engine.lock); pthread_mutex_lock(&engine.lock);
engine.shutting_down = 1; engine.shutting_down = 1;
pthread_cond_broadcast(&engine.wake_cond); pthread_cond_broadcast(&engine.wake_cond);
@@ -330,8 +367,6 @@ void exit_handler(void) {
free(actors_mutex); free(actors_mutex);
arrfree(timer_heap); arrfree(timer_heap);
exit(0);
} }
int actor_exists(const char *id) int actor_exists(const char *id)
@@ -357,6 +392,10 @@ void set_actor_state(cell_rt *actor)
case ACTOR_IDLE: case ACTOR_IDLE:
if (arrlen(actor->letters)) { if (arrlen(actor->letters)) {
if (actor->is_quiescent) {
actor->is_quiescent = 0;
atomic_fetch_sub(&engine.quiescent_count, 1);
}
actor->state = ACTOR_READY; actor->state = ACTOR_READY;
actor->ar = 0; actor->ar = 0;
@@ -384,21 +423,46 @@ void set_actor_state(cell_rt *actor)
} }
pthread_mutex_unlock(&engine.lock); pthread_mutex_unlock(&engine.lock);
} else if (!arrlen(actor->letters) && !hmlen(actor->timers)) { } else if (!hmlen(actor->timers)) {
// Schedule remove timer // No messages AND no timers
static uint32_t global_timer_id = 1; // Only count as quiescent if no $unneeded callback registered
uint32_t id = global_timer_id++; int has_unneeded = !JS_IsNull(actor->unneeded_ref.val);
actor->ar = id; if (!actor->is_quiescent && actor->id && !has_unneeded) {
actor->is_quiescent = 1;
uint64_t now = cell_ns(); int qc = atomic_fetch_add(&engine.quiescent_count, 1) + 1;
uint64_t execute_at = now + (uint64_t)(actor->ar_secs * 1e9); int total = (int)lockless_shlen(actors);
if (qc >= total && total > 0 && engine.quiescence_enabled) {
pthread_mutex_lock(&engine.lock); pthread_mutex_lock(&engine.lock);
heap_push(execute_at, actor, id, TIMER_NATIVE_REMOVE); engine.shutting_down = 1;
if (timer_heap[0].timer_id == id) { pthread_cond_broadcast(&engine.wake_cond);
pthread_cond_signal(&engine.timer_cond); pthread_cond_broadcast(&engine.timer_cond);
pthread_cond_broadcast(&engine.main_cond);
pthread_mutex_unlock(&engine.lock);
}
}
if (!engine.shutting_down) {
// Schedule remove timer
static uint32_t global_timer_id = 1;
uint32_t id = global_timer_id++;
actor->ar = id;
uint64_t now = cell_ns();
uint64_t execute_at = now + (uint64_t)(actor->ar_secs * 1e9);
pthread_mutex_lock(&engine.lock);
heap_push(execute_at, actor, id, TIMER_NATIVE_REMOVE);
if (timer_heap[0].timer_id == id) {
pthread_cond_signal(&engine.timer_cond);
}
pthread_mutex_unlock(&engine.lock);
}
} else {
// Has timers but no letters — waiting, not quiescent
if (actor->is_quiescent) {
actor->is_quiescent = 0;
atomic_fetch_sub(&engine.quiescent_count, 1);
} }
pthread_mutex_unlock(&engine.lock);
} }
break; break;
} }
@@ -520,11 +584,17 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
actor->main_thread_only = mainthread; actor->main_thread_only = mainthread;
actor->id = strdup(id); actor->id = strdup(id);
actor->ar_secs = ar; actor->ar_secs = ar;
int added = lockless_shput_unique(actors, id, actor); int added = lockless_shput_unique(actors, actor->id, actor);
if (!added) { if (!added) {
free(actor->id); free(actor->id);
return "Actor with given ID already exists."; return "Actor with given ID already exists.";
} }
// Now that actor is in the registry, track its quiescent state
if (actor->state == ACTOR_IDLE && !arrlen(actor->letters)
&& !hmlen(actor->timers) && JS_IsNull(actor->unneeded_ref.val)) {
actor->is_quiescent = 1;
atomic_fetch_add(&engine.quiescent_count, 1);
}
return NULL; return NULL;
} }
@@ -584,11 +654,13 @@ void actor_turn(cell_rt *actor)
JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size); JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size);
blob_destroy(l.blob_data); blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg); result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result); if (!uncaught_exception(actor->context, result))
actor->disrupt = 1;
JS_FreeValue(actor->context, arg); JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) { } else if (l.type == LETTER_CALLBACK) {
result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL); result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL);
uncaught_exception(actor->context, result); if (!uncaught_exception(actor->context, result))
actor->disrupt = 1;
JS_FreeValue(actor->context, l.callback); JS_FreeValue(actor->context, l.callback);
} }
@@ -600,6 +672,14 @@ void actor_turn(cell_rt *actor)
if (actor->trace_hook) if (actor->trace_hook)
actor->trace_hook(actor->name, CELL_HOOK_EXIT); actor->trace_hook(actor->name, CELL_HOOK_EXIT);
if (actor->disrupt) {
/* Actor must die. Unlock before freeing so actor_free can
lock/unlock/destroy the mutex without use-after-free. */
pthread_mutex_unlock(actor->mutex);
actor_free(actor);
return;
}
set_actor_state(actor); set_actor_state(actor);
pthread_mutex_unlock(actor->mutex); pthread_mutex_unlock(actor->mutex);

View File

@@ -21,7 +21,6 @@ static const char *js_type_name(JSValue v) {
if (JS_IsText(v)) return "string"; if (JS_IsText(v)) return "string";
if (JS_IsArray(v)) return "array"; if (JS_IsArray(v)) return "array";
if (JS_IsRecord(v)) return "object"; if (JS_IsRecord(v)) return "object";
if (JS_IsObject(v)) return "object";
return "unknown"; return "unknown";
} }
@@ -310,7 +309,6 @@ TEST(string_heap_to_cstring) {
TEST(object_create) { TEST(object_create) {
JSValue obj = JS_NewObject(ctx); JSValue obj = JS_NewObject(ctx);
ASSERT(JS_IsObject(obj));
ASSERT(JS_IsRecord(obj)); ASSERT(JS_IsRecord(obj));
return 1; return 1;
} }
@@ -435,7 +433,6 @@ TEST(object_many_properties_resize) {
TEST(array_create) { TEST(array_create) {
JSValue arr = JS_NewArray(ctx); JSValue arr = JS_NewArray(ctx);
ASSERT(JS_IsObject(arr));
ASSERT(JS_IsArray(arr)); ASSERT(JS_IsArray(arr));
return 1; return 1;
} }
@@ -465,9 +462,9 @@ TEST(array_get_by_index) {
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 200)); JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 200));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 300)); JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 300));
JSValue v0 = JS_GetPropertyUint32(ctx, arr_ref.val, 0); JSValue v0 = JS_GetPropertyNumber(ctx, arr_ref.val, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr_ref.val, 1); JSValue v1 = JS_GetPropertyNumber(ctx, arr_ref.val, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr_ref.val, 2); JSValue v2 = JS_GetPropertyNumber(ctx, arr_ref.val, 2);
JS_PopGCRef(ctx, &arr_ref); JS_PopGCRef(ctx, &arr_ref);
ASSERT_INT(v0, 100); ASSERT_INT(v0, 100);
@@ -486,12 +483,12 @@ TEST(array_set_by_index) {
/* Create values first, then read arr_ref.val */ /* Create values first, then read arr_ref.val */
JSValue v55 = JS_NewInt32(ctx, 55); JSValue v55 = JS_NewInt32(ctx, 55);
JS_SetPropertyUint32(ctx, arr_ref.val, 0, v55); JS_SetPropertyNumber(ctx, arr_ref.val, 0, v55);
JSValue v66 = JS_NewInt32(ctx, 66); JSValue v66 = JS_NewInt32(ctx, 66);
JS_SetPropertyUint32(ctx, arr_ref.val, 1, v66); JS_SetPropertyNumber(ctx, arr_ref.val, 1, v66);
JSValue v0 = JS_GetPropertyUint32(ctx, arr_ref.val, 0); JSValue v0 = JS_GetPropertyNumber(ctx, arr_ref.val, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr_ref.val, 1); JSValue v1 = JS_GetPropertyNumber(ctx, arr_ref.val, 1);
JS_PopGCRef(ctx, &arr_ref); JS_PopGCRef(ctx, &arr_ref);
ASSERT_INT(v0, 55); ASSERT_INT(v0, 55);
@@ -525,7 +522,7 @@ TEST(array_out_of_bounds_is_null) {
arr_ref.val = JS_NewArray(ctx); arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 1)); JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 1));
JSValue val = JS_GetPropertyUint32(ctx, arr_ref.val, 999); JSValue val = JS_GetPropertyNumber(ctx, arr_ref.val, 999);
JS_PopGCRef(ctx, &arr_ref); JS_PopGCRef(ctx, &arr_ref);
ASSERT(JS_IsNull(val)); ASSERT(JS_IsNull(val));
return 1; return 1;
@@ -544,10 +541,10 @@ TEST(array_mixed_types) {
JS_ArrayPush(ctx, &arr_ref.val, JS_TRUE); JS_ArrayPush(ctx, &arr_ref.val, JS_TRUE);
JS_ArrayPush(ctx, &arr_ref.val, JS_NULL); JS_ArrayPush(ctx, &arr_ref.val, JS_NULL);
JSValue v0 = JS_GetPropertyUint32(ctx, arr_ref.val, 0); JSValue v0 = JS_GetPropertyNumber(ctx, arr_ref.val, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, arr_ref.val, 1); JSValue v1 = JS_GetPropertyNumber(ctx, arr_ref.val, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, arr_ref.val, 2); JSValue v2 = JS_GetPropertyNumber(ctx, arr_ref.val, 2);
JSValue v3 = JS_GetPropertyUint32(ctx, arr_ref.val, 3); JSValue v3 = JS_GetPropertyNumber(ctx, arr_ref.val, 3);
JS_PopGCRef(ctx, &str_ref); JS_PopGCRef(ctx, &str_ref);
JS_PopGCRef(ctx, &arr_ref); JS_PopGCRef(ctx, &arr_ref);
@@ -571,9 +568,9 @@ TEST(array_many_elements_resize) {
JS_GetLength(ctx, arr_ref.val, &len); JS_GetLength(ctx, arr_ref.val, &len);
/* Verify some values */ /* Verify some values */
JSValue v0 = JS_GetPropertyUint32(ctx, arr_ref.val, 0); JSValue v0 = JS_GetPropertyNumber(ctx, arr_ref.val, 0);
JSValue v500 = JS_GetPropertyUint32(ctx, arr_ref.val, 500); JSValue v500 = JS_GetPropertyNumber(ctx, arr_ref.val, 500);
JSValue v999 = JS_GetPropertyUint32(ctx, arr_ref.val, 999); JSValue v999 = JS_GetPropertyNumber(ctx, arr_ref.val, 999);
/* Pop BEFORE assertions */ /* Pop BEFORE assertions */
JS_PopGCRef(ctx, &arr_ref); JS_PopGCRef(ctx, &arr_ref);
@@ -716,9 +713,9 @@ TEST(array_slice_basic) {
JS_GetLength(ctx, sliced, &len); JS_GetLength(ctx, sliced, &len);
ASSERT(len == 3); ASSERT(len == 3);
JSValue v0 = JS_GetPropertyUint32(ctx, sliced, 0); JSValue v0 = JS_GetPropertyNumber(ctx, sliced, 0);
JSValue v1 = JS_GetPropertyUint32(ctx, sliced, 1); JSValue v1 = JS_GetPropertyNumber(ctx, sliced, 1);
JSValue v2 = JS_GetPropertyUint32(ctx, sliced, 2); JSValue v2 = JS_GetPropertyNumber(ctx, sliced, 2);
ASSERT_INT(v0, 10); ASSERT_INT(v0, 10);
ASSERT_INT(v1, 20); ASSERT_INT(v1, 20);
ASSERT_INT(v2, 30); ASSERT_INT(v2, 30);
@@ -747,10 +744,10 @@ TEST(array_concat_basic) {
JS_GetLength(ctx, result, &len); JS_GetLength(ctx, result, &len);
ASSERT(len == 4); ASSERT(len == 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 0), 1); ASSERT_INT(JS_GetPropertyNumber(ctx, result, 0), 1);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 1), 2); ASSERT_INT(JS_GetPropertyNumber(ctx, result, 1), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 2), 3); ASSERT_INT(JS_GetPropertyNumber(ctx, result, 2), 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, result, 3), 4); ASSERT_INT(JS_GetPropertyNumber(ctx, result, 3), 4);
return 1; return 1;
} }
@@ -772,11 +769,11 @@ TEST(array_sort_numbers) {
JS_GetLength(ctx, sorted, &len); JS_GetLength(ctx, sorted, &len);
ASSERT(len == 5); ASSERT(len == 5);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 0), 10); ASSERT_INT(JS_GetPropertyNumber(ctx, sorted, 0), 10);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 1), 20); ASSERT_INT(JS_GetPropertyNumber(ctx, sorted, 1), 20);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 2), 30); ASSERT_INT(JS_GetPropertyNumber(ctx, sorted, 2), 30);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 3), 40); ASSERT_INT(JS_GetPropertyNumber(ctx, sorted, 3), 40);
ASSERT_INT(JS_GetPropertyUint32(ctx, sorted, 4), 50); ASSERT_INT(JS_GetPropertyNumber(ctx, sorted, 4), 50);
return 1; return 1;
} }
@@ -840,8 +837,8 @@ TEST(array_filter_basic) {
JS_GetLength(ctx, filtered, &len); JS_GetLength(ctx, filtered, &len);
ASSERT(len == 5); /* 6, 7, 8, 9, 10 */ ASSERT(len == 5); /* 6, 7, 8, 9, 10 */
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 0), 6); ASSERT_INT(JS_GetPropertyNumber(ctx, filtered, 0), 6);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 4), 10); ASSERT_INT(JS_GetPropertyNumber(ctx, filtered, 4), 10);
return 1; return 1;
} }
@@ -865,11 +862,11 @@ TEST(array_filter_even) {
JS_GetLength(ctx, filtered, &len); JS_GetLength(ctx, filtered, &len);
ASSERT(len == 5); /* 2, 4, 6, 8, 10 */ ASSERT(len == 5); /* 2, 4, 6, 8, 10 */
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 0), 2); ASSERT_INT(JS_GetPropertyNumber(ctx, filtered, 0), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 1), 4); ASSERT_INT(JS_GetPropertyNumber(ctx, filtered, 1), 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 2), 6); ASSERT_INT(JS_GetPropertyNumber(ctx, filtered, 2), 6);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 3), 8); ASSERT_INT(JS_GetPropertyNumber(ctx, filtered, 3), 8);
ASSERT_INT(JS_GetPropertyUint32(ctx, filtered, 4), 10); ASSERT_INT(JS_GetPropertyNumber(ctx, filtered, 4), 10);
return 1; return 1;
} }
@@ -893,9 +890,9 @@ TEST(array_map_double) {
JS_GetLength(ctx, mapped, &len); JS_GetLength(ctx, mapped, &len);
ASSERT(len == 3); ASSERT(len == 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, mapped, 0), 2); ASSERT_INT(JS_GetPropertyNumber(ctx, mapped, 0), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, mapped, 1), 4); ASSERT_INT(JS_GetPropertyNumber(ctx, mapped, 1), 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, mapped, 2), 6); ASSERT_INT(JS_GetPropertyNumber(ctx, mapped, 2), 6);
return 1; return 1;
} }
@@ -1356,9 +1353,9 @@ TEST(cell_reverse_array) {
JSValue reversed = JS_CellReverse(ctx, arr_ref.val); JSValue reversed = JS_CellReverse(ctx, arr_ref.val);
JS_PopGCRef(ctx, &arr_ref); JS_PopGCRef(ctx, &arr_ref);
ASSERT(JS_IsArray(reversed)); ASSERT(JS_IsArray(reversed));
ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 0), 3); ASSERT_INT(JS_GetPropertyNumber(ctx, reversed, 0), 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 1), 2); ASSERT_INT(JS_GetPropertyNumber(ctx, reversed, 1), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, reversed, 2), 1); ASSERT_INT(JS_GetPropertyNumber(ctx, reversed, 2), 1);
return 1; return 1;
} }
@@ -1408,9 +1405,9 @@ TEST(parse_json_array) {
int64_t len; int64_t len;
JS_GetLength(ctx, arr, &len); JS_GetLength(ctx, arr, &len);
ASSERT(len == 3); ASSERT(len == 3);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 0), 1); ASSERT_INT(JS_GetPropertyNumber(ctx, arr, 0), 1);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 1), 2); ASSERT_INT(JS_GetPropertyNumber(ctx, arr, 1), 2);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 2), 3); ASSERT_INT(JS_GetPropertyNumber(ctx, arr, 2), 3);
return 1; return 1;
} }
@@ -1565,12 +1562,12 @@ TEST(property_type_restrictions) {
/* Setting numeric properties on non-arrays should throw */ /* Setting numeric properties on non-arrays should throw */
JSValue v100 = JS_NewInt32(ctx, 100); JSValue v100 = JS_NewInt32(ctx, 100);
int ret1 = JS_SetPropertyUint32(ctx, obj_ref.val, 0, v100); int ret1 = JS_IsException(JS_SetPropertyNumber(ctx, obj_ref.val, 0, v100)) ? -1 : 0;
int has_exc1 = JS_HasException(ctx); int has_exc1 = JS_HasException(ctx);
JS_GetException(ctx); /* Clear the exception */ JS_GetException(ctx); /* Clear the exception */
/* Getting numeric properties on objects should return null */ /* Getting numeric properties on objects should return null */
JSValue v0 = JS_GetPropertyUint32(ctx, obj_ref.val, 0); JSValue v0 = JS_GetPropertyNumber(ctx, obj_ref.val, 0);
int v0_is_null = JS_IsNull(v0); int v0_is_null = JS_IsNull(v0);
/* Getting text keys from arrays should return null */ /* Getting text keys from arrays should return null */
@@ -1646,8 +1643,8 @@ TEST(new_array_from) {
int64_t len; int64_t len;
JS_GetLength(ctx, arr, &len); JS_GetLength(ctx, arr, &len);
ASSERT(len == 4); ASSERT(len == 4);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 0), 10); ASSERT_INT(JS_GetPropertyNumber(ctx, arr, 0), 10);
ASSERT_INT(JS_GetPropertyUint32(ctx, arr, 3), 40); ASSERT_INT(JS_GetPropertyNumber(ctx, arr, 3), 40);
return 1; return 1;
} }
@@ -1833,9 +1830,7 @@ TEST(is_function_check) {
TEST(is_integer_vs_number) { TEST(is_integer_vs_number) {
JSValue i = JS_NewInt32(ctx, 42); JSValue i = JS_NewInt32(ctx, 42);
JSValue f = JS_NewFloat64(ctx, 3.14); JSValue f = JS_NewFloat64(ctx, 3.14);
ASSERT(JS_IsInteger(i));
ASSERT(JS_IsInt(i)); ASSERT(JS_IsInt(i));
ASSERT(!JS_IsInteger(f));
ASSERT(!JS_IsInt(f)); ASSERT(!JS_IsInt(f));
ASSERT(JS_IsNumber(i)); ASSERT(JS_IsNumber(i));
ASSERT(JS_IsNumber(f)); ASSERT(JS_IsNumber(f));
@@ -1983,9 +1978,9 @@ TEST(wota_encode_nested_array) {
int is_arr = JS_IsArray(decoded); int is_arr = JS_IsArray(decoded);
int64_t len; int64_t len;
JS_GetLength(ctx, decoded, &len); JS_GetLength(ctx, decoded, &len);
JSValue v0 = JS_GetPropertyUint32(ctx, decoded, 0); JSValue v0 = JS_GetPropertyNumber(ctx, decoded, 0);
JSValue v2 = JS_GetPropertyUint32(ctx, decoded, 2); JSValue v2 = JS_GetPropertyNumber(ctx, decoded, 2);
JSValue inner = JS_GetPropertyUint32(ctx, decoded, 1); JSValue inner = JS_GetPropertyNumber(ctx, decoded, 1);
int inner_is_arr = JS_IsArray(inner); int inner_is_arr = JS_IsArray(inner);
int64_t inner_len; int64_t inner_len;
JS_GetLength(ctx, inner, &inner_len); JS_GetLength(ctx, inner, &inner_len);

15
streamline.ce Normal file
View File

@@ -0,0 +1,15 @@
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode")
var streamline = use("streamline")
var filename = args[0]
var src = text(fd.slurp(filename))
var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename, tokenize)
var folded = fold(ast)
var compiled = mcode(folded)
var optimized = streamline(compiled)
print(json.encode(optimized))

351
streamline.cm Normal file
View File

@@ -0,0 +1,351 @@
// streamline.cm — mcode IR optimizer
// Single forward pass: type inference + strength reduction
var streamline = function(ir) {
// Type constants
var T_UNKNOWN = "unknown"
var T_INT = "int"
var T_FLOAT = "float"
var T_NUM = "num"
var T_TEXT = "text"
var T_BOOL = "bool"
var T_NULL = "null"
// Integer arithmetic ops that produce integer results
var int_result_ops = {
add_int: true, sub_int: true, mul_int: true,
div_int: true, mod_int: true
}
// Float arithmetic ops that produce float results
var float_result_ops = {
add_float: true, sub_float: true, mul_float: true,
div_float: true, mod_float: true
}
// Comparison ops that produce bool results
var bool_result_ops = {
eq_int: true, ne_int: true, lt_int: true, gt_int: true,
le_int: true, ge_int: true,
eq_float: true, ne_float: true, lt_float: true, gt_float: true,
le_float: true, ge_float: true,
eq_text: true, ne_text: true, lt_text: true, gt_text: true,
le_text: true, ge_text: true,
eq_bool: true, ne_bool: true,
eq_tol: true, ne_tol: true,
not: true, and: true, or: true,
is_int: true, is_text: true, is_num: true,
is_bool: true, is_null: true, is_identical: true
}
// Type check opcodes and what type they verify
var type_check_map = {
is_int: T_INT,
is_text: T_TEXT,
is_num: T_NUM,
is_bool: T_BOOL,
is_null: T_NULL
}
// Determine the type of an access literal value
var access_value_type = function(val) {
if (is_number(val)) {
if (is_integer(val)) {
return T_INT
}
return T_FLOAT
}
if (is_text(val)) {
return T_TEXT
}
return T_UNKNOWN
}
// Update slot_types for an instruction (shared tracking logic)
var track_types = function(slot_types, instr) {
var op = instr[0]
var src_type = null
if (op == "access") {
slot_types[text(instr[1])] = access_value_type(instr[2])
} else if (op == "int") {
slot_types[text(instr[1])] = T_INT
} else if (op == "true" || op == "false") {
slot_types[text(instr[1])] = T_BOOL
} else if (op == "null") {
slot_types[text(instr[1])] = T_NULL
} else if (op == "move") {
src_type = slot_types[text(instr[2])]
if (src_type != null) {
slot_types[text(instr[1])] = src_type
} else {
slot_types[text(instr[1])] = T_UNKNOWN
}
} else if (int_result_ops[op] == true) {
slot_types[text(instr[1])] = T_INT
} else if (float_result_ops[op] == true) {
slot_types[text(instr[1])] = T_FLOAT
} else if (op == "concat") {
slot_types[text(instr[1])] = T_TEXT
} else if (bool_result_ops[op] == true) {
slot_types[text(instr[1])] = T_BOOL
} else if (op == "load_field" || op == "load_index" || op == "load_dynamic") {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "invoke") {
slot_types[text(instr[2])] = T_UNKNOWN
} else if (op == "pop" || op == "get" || op == "function") {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "typeof") {
slot_types[text(instr[1])] = T_TEXT
} else if (op == "neg_int") {
slot_types[text(instr[1])] = T_INT
} else if (op == "neg_float") {
slot_types[text(instr[1])] = T_FLOAT
} else if (op == "bitnot" || op == "bitand" || op == "bitor" ||
op == "bitxor" || op == "shl" || op == "shr" || op == "ushr") {
slot_types[text(instr[1])] = T_INT
}
return null
}
// Check if a slot has a known type (with T_NUM subsumption)
var slot_is = function(slot_types, slot, typ) {
var known = slot_types[text(slot)]
if (known == null) {
return false
}
if (known == typ) {
return true
}
if (typ == T_NUM && (known == T_INT || known == T_FLOAT)) {
return true
}
return false
}
// Optimize a single function's instructions
var optimize_function = function(func) {
var instructions = func.instructions
var num_instr = 0
var slot_types = null
var nop_counter = 0
var i = 0
var instr = null
var op = null
var dest = 0
var src = 0
var checked_type = null
var next = null
var next_op = null
var target_label = null
var src_known = null
var jlen = 0
var j = 0
var peek = null
if (instructions == null || length(instructions) == 0) {
return null
}
num_instr = length(instructions)
slot_types = {}
// Peephole optimization pass: type tracking + strength reduction
i = 0
while (i < num_instr) {
instr = instructions[i]
// Labels are join points: clear all type info (conservative)
if (is_text(instr)) {
slot_types = {}
i = i + 1
continue
}
if (!is_array(instr)) {
i = i + 1
continue
}
op = instr[0]
// --- Peephole: type-check + jump where we know the type ---
if (type_check_map[op] != null && i + 1 < num_instr) {
dest = instr[1]
src = instr[2]
checked_type = type_check_map[op]
next = instructions[i + 1]
if (is_array(next)) {
next_op = next[0]
// Pattern: is_<type> t, x -> jump_false t, label
if (next_op == "jump_false" && next[1] == dest) {
target_label = next[2]
if (slot_is(slot_types, src, checked_type)) {
// Known match: check always true, never jumps — eliminate both
nop_counter = nop_counter + 1
instructions[i] = "_nop_" + text(nop_counter)
nop_counter = nop_counter + 1
instructions[i + 1] = "_nop_" + text(nop_counter)
slot_types[text(dest)] = T_BOOL
i = i + 2
continue
}
src_known = slot_types[text(src)]
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
// Check for T_NUM subsumption: INT and FLOAT match T_NUM
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
// Actually matches — eliminate both
nop_counter = nop_counter + 1
instructions[i] = "_nop_" + text(nop_counter)
nop_counter = nop_counter + 1
instructions[i + 1] = "_nop_" + text(nop_counter)
slot_types[text(dest)] = T_BOOL
i = i + 2
continue
}
// Known mismatch: always jumps — nop the check, rewrite jump
nop_counter = nop_counter + 1
instructions[i] = "_nop_" + text(nop_counter)
jlen = length(next)
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
slot_types[text(dest)] = T_UNKNOWN
i = i + 2
continue
}
// Unknown: can't eliminate, but narrow type on fallthrough
slot_types[text(dest)] = T_BOOL
slot_types[text(src)] = checked_type
i = i + 2
continue
}
// Pattern: is_<type> t, x -> jump_true t, label
if (next_op == "jump_true" && next[1] == dest) {
target_label = next[2]
if (slot_is(slot_types, src, checked_type)) {
// Known match: always true, always jumps — nop check, rewrite to jump
nop_counter = nop_counter + 1
instructions[i] = "_nop_" + text(nop_counter)
jlen = length(next)
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
slot_types[text(dest)] = T_BOOL
i = i + 2
continue
}
src_known = slot_types[text(src)]
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
// Actually matches T_NUM — always jumps
nop_counter = nop_counter + 1
instructions[i] = "_nop_" + text(nop_counter)
jlen = length(next)
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
slot_types[text(dest)] = T_BOOL
i = i + 2
continue
}
// Known mismatch: never jumps — eliminate both
nop_counter = nop_counter + 1
instructions[i] = "_nop_" + text(nop_counter)
nop_counter = nop_counter + 1
instructions[i + 1] = "_nop_" + text(nop_counter)
slot_types[text(dest)] = T_BOOL
i = i + 2
continue
}
// Unknown: can't optimize
slot_types[text(dest)] = T_BOOL
i = i + 2
continue
}
}
// Standalone type check (no jump following): just track the result
slot_types[text(dest)] = T_BOOL
i = i + 1
continue
}
// --- Strength reduction: load_dynamic / store_dynamic ---
if (op == "load_dynamic") {
if (slot_is(slot_types, instr[3], T_TEXT)) {
instr[0] = "load_field"
} else if (slot_is(slot_types, instr[3], T_INT)) {
instr[0] = "load_index"
}
slot_types[text(instr[1])] = T_UNKNOWN
i = i + 1
continue
}
if (op == "store_dynamic") {
if (slot_is(slot_types, instr[3], T_TEXT)) {
instr[0] = "store_field"
} else if (slot_is(slot_types, instr[3], T_INT)) {
instr[0] = "store_index"
}
i = i + 1
continue
}
// --- Standard type tracking ---
track_types(slot_types, instr)
i = i + 1
}
// Second pass: remove dead jumps (jump to the immediately next label)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_array(instr) && instr[0] == "jump") {
target_label = instr[1]
// Check if the very next non-nop item is that label
j = i + 1
while (j < num_instr) {
peek = instructions[j]
if (is_text(peek)) {
if (peek == target_label) {
nop_counter = nop_counter + 1
instructions[i] = "_nop_" + text(nop_counter)
}
break
}
if (is_array(peek)) {
break
}
j = j + 1
}
}
i = i + 1
}
return null
}
// Process main function
if (ir.main != null) {
optimize_function(ir.main)
}
// Process all sub-functions
var fi = 0
if (ir.functions != null) {
fi = 0
while (fi < length(ir.functions)) {
optimize_function(ir.functions[fi])
fi = fi + 1
}
}
return ir
}
return streamline

BIN
streamline.mach Normal file

Binary file not shown.

382
test.ce
View File

@@ -8,7 +8,7 @@ var dbg = use('js')
// run gc with dbg.gc() // run gc with dbg.gc()
if (!args) args = [] var _args = args == null ? [] : args
var target_pkg = null // null = current package var target_pkg = null // null = current package
var target_test = null // null = all tests, otherwise specific test file var target_test = null // null = all tests, otherwise specific test file
@@ -22,19 +22,20 @@ var actor_test_results = []
// Check if current directory is a valid cell package // Check if current directory is a valid cell package
function is_valid_package(dir) { function is_valid_package(dir) {
if (!dir) dir = '.' var _dir = dir == null ? '.' : dir
return fd.is_file(dir + '/cell.toml') return fd.is_file(_dir + '/cell.toml')
} }
// Get current package name from cell.toml or null // Get current package name from cell.toml or null
function get_current_package_name() { function get_current_package_name() {
if (!is_valid_package('.')) return null if (!is_valid_package('.')) return null
try { var _load = function() {
var config = pkg.load_config(null) var config = pkg.load_config(null)
return config.package || 'local' return config.package || 'local'
} catch (e) { } disruption {
return 'local' return 'local'
} }
return _load()
} }
// Parse arguments // Parse arguments
@@ -48,16 +49,21 @@ function get_current_package_name() {
function parse_args() { function parse_args() {
var cleaned_args = [] var cleaned_args = []
for (var i = 0; i < length(args); i++) { var i = 0
if (args[i] == '-g') { var name = null
var lock = null
var resolved = null
var test_path = null
for (i = 0; i < length(_args); i++) {
if (_args[i] == '-g') {
gc_after_each_test = true gc_after_each_test = true
} else { } else {
push(cleaned_args, args[i]) push(cleaned_args, _args[i])
} }
} }
args = cleaned_args _args = cleaned_args
if (length(args) == 0) { if (length(_args) == 0) {
// cell test - run all tests for current package // cell test - run all tests for current package
if (!is_valid_package('.')) { if (!is_valid_package('.')) {
log.console('No cell.toml found in current directory') log.console('No cell.toml found in current directory')
@@ -67,7 +73,7 @@ function parse_args() {
return true return true
} }
if (args[0] == 'all') { if (_args[0] == 'all') {
// cell test all - run all tests for current package // cell test all - run all tests for current package
if (!is_valid_package('.')) { if (!is_valid_package('.')) {
log.console('No cell.toml found in current directory') log.console('No cell.toml found in current directory')
@@ -77,14 +83,14 @@ function parse_args() {
return true return true
} }
if (args[0] == 'package') { if (_args[0] == 'package') {
if (length(args) < 2) { if (length(_args) < 2) {
log.console('Usage: cell test package <name> [test]') log.console('Usage: cell test package <name> [test]')
log.console(' cell test package all') log.console(' cell test package all')
return false return false
} }
if (args[1] == 'all') { if (_args[1] == 'all') {
// cell test package all - run tests from all packages // cell test package all - run tests from all packages
all_pkgs = true all_pkgs = true
log.console('Testing all packages...') log.console('Testing all packages...')
@@ -92,18 +98,19 @@ function parse_args() {
} }
// cell test package <name> [test] // cell test package <name> [test]
var name = args[1] name = _args[1]
// Check if package exists in lock or is a local path // Check if package exists in lock or is a local path
var lock = shop.load_lock() lock = shop.load_lock()
if (lock[name]) { if (lock[name]) {
target_pkg = name target_pkg = name
} else if (starts_with(name, '/') && is_valid_package(name)) { } else if (starts_with(name, '/') && is_valid_package(name)) {
target_pkg = name target_pkg = name
} else { } else {
// Try to resolve as dependency alias from current package // Try to resolve as dependency alias from current package
resolved = null
if (is_valid_package('.')) { if (is_valid_package('.')) {
var resolved = pkg.alias_to_package(null, name) resolved = pkg.alias_to_package(null, name)
if (resolved) { if (resolved) {
target_pkg = resolved target_pkg = resolved
} else { } else {
@@ -116,9 +123,9 @@ function parse_args() {
} }
} }
if (length(args) >= 3) { if (length(_args) >= 3) {
// cell test package <name> <test> // cell test package <name> <test>
target_test = args[2] target_test = _args[2]
} }
log.console(`Testing package: ${target_pkg}`) log.console(`Testing package: ${target_pkg}`)
@@ -126,7 +133,7 @@ function parse_args() {
} }
// cell test tests/suite or cell test <path> - specific test file // cell test tests/suite or cell test <path> - specific test file
var test_path = args[0] test_path = _args[0]
// Normalize path - add tests/ prefix if not present and doesn't start with / // Normalize path - add tests/ prefix if not present and doesn't start with /
if (!starts_with(test_path, 'tests/') && !starts_with(test_path, '/')) { if (!starts_with(test_path, 'tests/') && !starts_with(test_path, '/')) {
@@ -160,9 +167,10 @@ function ensure_dir(path) {
var parts = array(path, '/') var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : '' var current = starts_with(path, '/') ? '/' : ''
for (var i = 0; i < length(parts); i++) { var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue if (parts[i] == '') continue
current += parts[i] + '/' current = current + parts[i] + '/'
if (!fd.is_dir(current)) if (!fd.is_dir(current))
fd.mkdir(current) fd.mkdir(current)
} }
@@ -189,23 +197,29 @@ function collect_actor_tests(package_name, specific_test) {
var files = pkg.list_files(package_name) var files = pkg.list_files(package_name)
var actor_tests = [] var actor_tests = []
for (var i = 0; i < length(files); i++) { var i = 0
var f = files[i] var f = null
var test_name = null
var match_name = null
var test_base = null
var match_base = null
for (i = 0; i < length(files); i++) {
f = files[i]
// Check if file is in tests/ folder and is a .ce actor // Check if file is in tests/ folder and is a .ce actor
if (starts_with(f, "tests/") && ends_with(f, ".ce")) { if (starts_with(f, "tests/") && ends_with(f, ".ce")) {
// If specific test requested, filter // If specific test requested, filter
if (specific_test) { if (specific_test) {
var test_name = text(f, 0, -3) // remove .ce test_name = text(f, 0, -3) // remove .ce
var match_name = specific_test match_name = specific_test
if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name
if (!ends_with(match_name, '.ce')) match_name = match_name if (!ends_with(match_name, '.ce')) match_name = match_name
// Match without extension // Match without extension
var test_base = test_name test_base = test_name
var match_base = ends_with(match_name, '.ce') ? text(match_name, 0, -3) : match_name match_base = ends_with(match_name, '.ce') ? text(match_name, 0, -3) : match_name
if (test_base != match_base) continue if (test_base != match_base) continue
} }
push(actor_tests,{ push(actor_tests, {
package: package_name || "local", package: package_name || "local",
file: f, file: f,
path: prefix + '/' + f path: prefix + '/' + f
@@ -229,19 +243,19 @@ function spawn_actor_test(test_info) {
actor: null actor: null
} }
try { var _spawn = function() {
// Spawn the actor test - it should send back results
var actor_path = text(test_info.path, 0, -3) // remove .ce var actor_path = text(test_info.path, 0, -3) // remove .ce
entry.actor = $start(actor_path) entry.actor = $start(actor_path)
push(pending_actor_tests, entry) push(pending_actor_tests, entry)
} catch (e) { } disruption {
entry.status = "failed" entry.status = "failed"
entry.error = { message: `Failed to spawn actor: ${e}` } entry.error = { message: `Failed to spawn actor` }
entry.duration_ns = 0 entry.duration_ns = 0
push(actor_test_results, entry) push(actor_test_results, entry)
log.console(` FAIL ${test_name}: `) log.console(` FAIL ${test_name}: `)
log.error(e) log.error()
} }
_spawn()
} }
function run_tests(package_name, specific_test) { function run_tests(package_name, specific_test) {
@@ -260,17 +274,24 @@ function run_tests(package_name, specific_test) {
var files = pkg.list_files(package_name) var files = pkg.list_files(package_name)
var test_files = [] var test_files = []
for (var i = 0; i < length(files); i++) { var i = 0
var f = files[i] var f = null
var test_name = null
var match_name = null
var match_base = null
var mod_path = null
var file_result = null
for (i = 0; i < length(files); i++) {
f = files[i]
// Check if file is in tests/ folder and is a .cm module (not .ce - those are actor tests) // Check if file is in tests/ folder and is a .cm module (not .ce - those are actor tests)
if (starts_with(f, "tests/") && ends_with(f, ".cm")) { if (starts_with(f, "tests/") && ends_with(f, ".cm")) {
// If specific test requested, filter // If specific test requested, filter
if (specific_test) { if (specific_test) {
var test_name = text(f, 0, -3) // remove .cm test_name = text(f, 0, -3) // remove .cm
var match_name = specific_test match_name = specific_test
if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name
// Match without extension // Match without extension
var match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (test_name != match_base) continue if (test_name != match_base) continue
} }
push(test_files, f) push(test_files, f)
@@ -282,106 +303,138 @@ function run_tests(package_name, specific_test) {
else log.console(`Running tests for local package`) else log.console(`Running tests for local package`)
} }
for (var i = 0; i < length(test_files); i++) { var _load_file = null
var f = test_files[i] var load_error = false
var mod_path = text(f, 0, -3) // remove .cm var err_entry = null
for (i = 0; i < length(test_files); i++) {
f = test_files[i]
mod_path = text(f, 0, -3) // remove .cm
load_error = false
var file_result = { file_result = {
name: f, name: f,
tests: [], tests: [],
passed: 0, passed: 0,
failed: 0 failed: 0
} }
try { _load_file = function() {
var test_mod var test_mod = null
// For local packages (null), use the current directory as package context
var use_pkg = package_name ? package_name : fd.realpath('.') var use_pkg = package_name ? package_name : fd.realpath('.')
test_mod = shop.use(mod_path, use_pkg) test_mod = shop.use(mod_path, use_pkg)
var tests = [] var tests = []
var j = 0
var t = null
var test_entry = null
var start_time = null
var _test_error = null
var end_time = null
var _run_one = null
var all_keys = null
var fn_count = 0
var null_count = 0
var other_count = 0
var first_null_key = null
var first_other_key = null
if (is_function(test_mod)) { if (is_function(test_mod)) {
push(tests, {name: 'main', fn: test_mod}) push(tests, {name: 'main', fn: test_mod})
} else if (is_object(test_mod)) { } else if (is_object(test_mod)) {
arrfor(array(test_mod), function(k) { all_keys = array(test_mod)
log.console(` Found ${length(all_keys)} test entries`)
arrfor(all_keys, function(k) {
if (is_function(test_mod[k])) { if (is_function(test_mod[k])) {
fn_count = fn_count + 1
push(tests, {name: k, fn: test_mod[k]}) push(tests, {name: k, fn: test_mod[k]})
} else if (is_null(test_mod[k])) {
null_count = null_count + 1
if (!first_null_key) first_null_key = k
} else {
other_count = other_count + 1
if (!first_other_key) first_other_key = k
} }
}) })
log.console(` functions=${fn_count} nulls=${null_count} other=${other_count}`)
if (first_other_key) {
log.console(` first other key: ${first_other_key}`)
log.console(` is_number=${is_number(test_mod[first_other_key])} is_text=${is_text(test_mod[first_other_key])} is_logical=${is_logical(test_mod[first_other_key])} is_object=${is_object(test_mod[first_other_key])}`)
}
} }
if (length(tests) > 0) { if (length(tests) > 0) {
log.console(` ${f}`) log.console(` ${f}`)
for (var j = 0; j < length(tests); j++) { for (j = 0; j < length(tests); j++) {
var t = tests[j] t = tests[j]
var test_entry = { test_entry = {
package: pkg_result.package, package: pkg_result.package,
test: t.name, test: t.name,
status: "pending", status: "pending",
duration_ns: 0 duration_ns: 0
} }
var start_time = time.number() start_time = time.number()
try { _test_error = null
_run_one = function() {
var ret = t.fn() var ret = t.fn()
if (is_text(ret)) { if (is_text(ret)) {
throw Error(ret) _test_error = ret
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) { disrupt
throw ret } else if (ret && is_text(ret.message)) {
_test_error = ret.message
disrupt
} }
test_entry.status = "passed" test_entry.status = "passed"
log.console(` PASS ${t.name}`) log.console(` PASS ${t.name}`)
pkg_result.passed++ } disruption {
file_result.passed++ var e = _test_error
} catch (e) {
test_entry.status = "failed" test_entry.status = "failed"
test_entry.error = { test_entry.error = {
message: e, message: e,
stack: e.stack || "" stack: (e && e.stack) ? e.stack : ""
} }
if (e.name) test_entry.error.name = e.name if (e && e.name) test_entry.error.name = e.name
if (is_object(e) && e.message) { if (is_object(e) && e.message) {
test_entry.error.message = e.message test_entry.error.message = e.message
} }
log.console(` FAIL ${t.name} ${test_entry.error.message}`) log.console(` FAIL ${t.name} ${test_entry.error.message}`)
if (test_entry.error.stack) { if (test_entry.error.stack) {
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`) log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
} }
pkg_result.failed++
file_result.failed++
} }
var end_time = time.number() _run_one()
end_time = time.number()
test_entry.duration_ns = round((end_time - start_time) * 1000000000) test_entry.duration_ns = round((end_time - start_time) * 1000000000)
// Update counters at _load_file level (not inside _run_one)
if (test_entry.status == "passed") {
pkg_result.passed = pkg_result.passed + 1
file_result.passed = file_result.passed + 1
} else {
pkg_result.failed = pkg_result.failed + 1
file_result.failed = file_result.failed + 1
}
push(file_result.tests, test_entry) push(file_result.tests, test_entry)
pkg_result.total++ pkg_result.total = pkg_result.total + 1
if (gc_after_each_test) { if (gc_after_each_test) {
dbg.gc() dbg.gc()
} }
} }
} }
} catch (e) { } disruption {
log.console(` Error loading ${f}: ${e}`) load_error = true
var test_entry = { }
package: pkg_result.package, _load_file()
test: "load_module", if (load_error) {
status: "failed", log.console(" Error loading " + f)
duration_ns: 0, pkg_result.failed = pkg_result.failed + 1
error: { message: `Error loading module: ${e}` } file_result.failed = file_result.failed + 1
} pkg_result.total = pkg_result.total + 1
push(file_result.tests, test_entry)
pkg_result.failed++
file_result.failed++
pkg_result.total++
if (gc_after_each_test) {
dbg.gc()
}
} }
push(pkg_result.files, file_result) push(pkg_result.files, file_result)
} }
@@ -390,6 +443,8 @@ function run_tests(package_name, specific_test) {
var all_results = [] var all_results = []
var all_actor_tests = [] var all_actor_tests = []
var packages = null
var i = 0
if (all_pkgs) { if (all_pkgs) {
// Run local first if we're in a valid package // Run local first if we're in a valid package
@@ -399,8 +454,8 @@ if (all_pkgs) {
} }
// Then all packages in lock // Then all packages in lock
var packages = shop.list_packages() packages = shop.list_packages()
for (var i = 0; i < length(packages); i++) { for (i = 0; i < length(packages); i++) {
push(all_results, run_tests(packages[i], null)) push(all_results, run_tests(packages[i], null))
all_actor_tests = array(all_actor_tests, collect_actor_tests(packages[i], null)) all_actor_tests = array(all_actor_tests, collect_actor_tests(packages[i], null))
} }
@@ -412,7 +467,7 @@ if (all_pkgs) {
// Spawn actor tests if any // Spawn actor tests if any
if (length(all_actor_tests) > 0) { if (length(all_actor_tests) > 0) {
log.console(`Running ${length(all_actor_tests)} actor test(s)...`) log.console(`Running ${length(all_actor_tests)} actor test(s)...`)
for (var i = 0; i < length(all_actor_tests); i++) { for (i = 0; i < length(all_actor_tests); i++) {
spawn_actor_test(all_actor_tests[i]) spawn_actor_test(all_actor_tests[i])
} }
} }
@@ -421,7 +476,10 @@ if (length(all_actor_tests) > 0) {
function handle_actor_message(msg) { function handle_actor_message(msg) {
var sender = msg.$sender var sender = msg.$sender
var found_idx = -1 var found_idx = -1
for (var i = 0; i < length(pending_actor_tests); i++) { var i = 0
var res = null
var entry = null
for (i = 0; i < length(pending_actor_tests); i++) {
if (pending_actor_tests[i].actor == sender) { if (pending_actor_tests[i].actor == sender) {
found_idx = i found_idx = i
break break
@@ -445,9 +503,9 @@ function handle_actor_message(msg) {
results = [msg] results = [msg]
} }
for (var i = 0; i < length(results); i++) { for (i = 0; i < length(results); i++) {
var res = results[i] || {} res = results[i] || {}
var entry = { entry = {
package: base_entry.package, package: base_entry.package,
file: base_entry.file, file: base_entry.file,
test: res.test || base_entry.test + (length(results) > 1 ? `#${i+1}` : ""), test: res.test || base_entry.test + (length(results) > 1 ? `#${i+1}` : ""),
@@ -481,18 +539,22 @@ function handle_actor_message(msg) {
function check_timeouts() { function check_timeouts() {
var now = time.number() var now = time.number()
var timed_out = [] var timed_out = []
var i = 0
var entry = null
var elapsed_ms = null
var idx = null
for (var i = length(pending_actor_tests) - 1; i >= 0; i--) { for (i = length(pending_actor_tests) - 1; i >= 0; i--) {
var entry = pending_actor_tests[i] entry = pending_actor_tests[i]
var elapsed_ms = (now - entry.start_time) * 1000 elapsed_ms = (now - entry.start_time) * 1000
if (elapsed_ms > ACTOR_TEST_TIMEOUT) { if (elapsed_ms > ACTOR_TEST_TIMEOUT) {
push(timed_out, i) push(timed_out, i)
} }
} }
for (var i = 0; i < length(timed_out); i++) { for (i = 0; i < length(timed_out); i++) {
var idx = timed_out[i] idx = timed_out[i]
var entry = pending_actor_tests[idx] entry = pending_actor_tests[idx]
pending_actor_tests = array(array(pending_actor_tests, 0, idx), array(pending_actor_tests, idx + 1)) pending_actor_tests = array(array(pending_actor_tests, 0, idx), array(pending_actor_tests, idx + 1))
entry.status = "failed" entry.status = "failed"
@@ -519,11 +581,17 @@ function check_completion() {
} }
function finalize_results() { function finalize_results() {
var i = 0
var j = 0
var r = null
var pkg_result = null
var file_result = null
// Add actor test results to all_results // Add actor test results to all_results
for (var i = 0; i < length(actor_test_results); i++) { for (i = 0; i < length(actor_test_results); i++) {
var r = actor_test_results[i] r = actor_test_results[i]
var pkg_result = null pkg_result = null
for (var j = 0; j < length(all_results); j++) { for (j = 0; j < length(all_results); j++) {
if (all_results[j].package == r.package) { if (all_results[j].package == r.package) {
pkg_result = all_results[j] pkg_result = all_results[j]
break break
@@ -534,8 +602,8 @@ function finalize_results() {
push(all_results, pkg_result) push(all_results, pkg_result)
} }
var file_result = null file_result = null
for (var j = 0; j < length(pkg_result.files); j++) { for (j = 0; j < length(pkg_result.files); j++) {
if (pkg_result.files[j].name == r.file) { if (pkg_result.files[j].name == r.file) {
file_result = pkg_result.files[j] file_result = pkg_result.files[j]
break break
@@ -547,22 +615,22 @@ function finalize_results() {
} }
push(file_result.tests, r) push(file_result.tests, r)
pkg_result.total++ pkg_result.total = pkg_result.total + 1
if (r.status == "passed") { if (r.status == "passed") {
pkg_result.passed++ pkg_result.passed = pkg_result.passed + 1
file_result.passed++ file_result.passed = file_result.passed + 1
} else { } else {
pkg_result.failed++ pkg_result.failed = pkg_result.failed + 1
file_result.failed++ file_result.failed = file_result.failed + 1
} }
} }
// Calculate totals // Calculate totals
var totals = { total: 0, passed: 0, failed: 0 } var totals = { total: 0, passed: 0, failed: 0 }
for (var i = 0; i < length(all_results); i++) { for (i = 0; i < length(all_results); i++) {
totals.total += all_results[i].total totals.total = totals.total + all_results[i].total
totals.passed += all_results[i].passed totals.passed = totals.passed + all_results[i].passed
totals.failed += all_results[i].failed totals.failed = totals.failed + all_results[i].failed
} }
log.console(`----------------------------------------`) log.console(`----------------------------------------`)
@@ -573,13 +641,13 @@ function finalize_results() {
} }
// If no actor tests, finalize immediately // If no actor tests, finalize immediately
var totals var totals = null
if (length(all_actor_tests) == 0) { if (length(all_actor_tests) == 0) {
totals = { total: 0, passed: 0, failed: 0 } totals = { total: 0, passed: 0, failed: 0 }
for (var i = 0; i < length(all_results); i++) { for (i = 0; i < length(all_results); i++) {
totals.total += all_results[i].total totals.total = totals.total + all_results[i].total
totals.passed += all_results[i].passed totals.passed = totals.passed + all_results[i].passed
totals.failed += all_results[i].failed totals.failed = totals.failed + all_results[i].failed
} }
log.console(`----------------------------------------`) log.console(`----------------------------------------`)
@@ -593,6 +661,16 @@ function generate_reports(totals) {
var timestamp = text(floor(time.number())) var timestamp = text(floor(time.number()))
var report_dir = shop.get_reports_dir() + '/test_' + timestamp var report_dir = shop.get_reports_dir() + '/test_' + timestamp
ensure_dir(report_dir) ensure_dir(report_dir)
var i = 0
var j = 0
var k = 0
var pkg_res = null
var f = null
var status = null
var t = null
var dur = null
var pkg_tests = null
var json_path = null
var txt_report = `TEST REPORT var txt_report = `TEST REPORT
Date: ${time.text(time.number())} Date: ${time.text(time.number())}
@@ -600,53 +678,53 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
=== SUMMARY === === SUMMARY ===
` `
for (var i = 0; i < length(all_results); i++) { for (i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i] pkg_res = all_results[i]
if (pkg_res.total == 0) continue if (pkg_res.total == 0) continue
txt_report += `Package: ${pkg_res.package}\n` txt_report = txt_report + `Package: ${pkg_res.package}\n`
for (var j = 0; j < length(pkg_res.files); j++) { for (j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j] f = pkg_res.files[j]
var status = f.failed == 0 ? "PASS" : "FAIL" status = f.failed == 0 ? "PASS" : "FAIL"
txt_report += ` [${status}] ${f.name} (${f.passed}/${length(f.tests)})\n` txt_report = txt_report + ` [${status}] ${f.name} (${f.passed}/${length(f.tests)})\n`
} }
} }
txt_report += `\n=== FAILURES ===\n` txt_report = txt_report + `\n=== FAILURES ===\n`
var has_failures = false var has_failures = false
for (var i = 0; i < length(all_results); i++) { for (i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i] pkg_res = all_results[i]
for (var j = 0; j < length(pkg_res.files); j++) { for (j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j] f = pkg_res.files[j]
for (var k = 0; k < length(f.tests); k++) { for (k = 0; k < length(f.tests); k++) {
var t = f.tests[k] t = f.tests[k]
if (t.status == "failed") { if (t.status == "failed") {
has_failures = true has_failures = true
txt_report += `FAIL: ${pkg_res.package} :: ${f.name} :: ${t.test}\n` txt_report = txt_report + `FAIL: ${pkg_res.package} :: ${f.name} :: ${t.test}\n`
if (t.error) { if (t.error) {
txt_report += ` Message: ${t.error.message}\n` txt_report = txt_report + ` Message: ${t.error.message}\n`
if (t.error.stack) { if (t.error.stack) {
txt_report += ` Stack:\n${text(array(array(t.error.stack, '\n'), l => ` ${l}`), '\n')}\n` txt_report = txt_report + ` Stack:\n${text(array(array(t.error.stack, '\n'), l => ` ${l}`), '\n')}\n`
} }
} }
txt_report += `\n` txt_report = txt_report + `\n`
} }
} }
} }
} }
if (!has_failures) txt_report += `None\n` if (!has_failures) txt_report = txt_report + `None\n`
txt_report += `\n=== DETAILED RESULTS ===\n` txt_report = txt_report + `\n=== DETAILED RESULTS ===\n`
for (var i = 0; i < length(all_results); i++) { for (i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i] pkg_res = all_results[i]
if (pkg_res.total == 0) continue if (pkg_res.total == 0) continue
for (var j = 0; j < length(pkg_res.files); j++) { for (j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j] f = pkg_res.files[j]
for (var k = 0; k < length(f.tests); k++) { for (k = 0; k < length(f.tests); k++) {
var t = f.tests[k] t = f.tests[k]
var dur = `${t.duration_ns || 0}ns` dur = `${t.duration_ns || 0}ns`
var status = t.status == "passed" ? "PASS" : "FAIL" status = t.status == "passed" ? "PASS" : "FAIL"
txt_report += `[${status}] ${pkg_res.package} ${t.test} (${dur})\n` txt_report = txt_report + `[${status}] ${pkg_res.package} ${t.test} (${dur})\n`
} }
} }
} }
@@ -655,19 +733,19 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
log.console(`Report written to ${report_dir}/test.txt`) log.console(`Report written to ${report_dir}/test.txt`)
// Generate JSON per package // Generate JSON per package
for (var i = 0; i < length(all_results); i++) { for (i = 0; i < length(all_results); i++) {
var pkg_res = all_results[i] pkg_res = all_results[i]
if (pkg_res.total == 0) continue if (pkg_res.total == 0) continue
var pkg_tests = [] pkg_tests = []
for (var j = 0; j < length(pkg_res.files); j++) { for (j = 0; j < length(pkg_res.files); j++) {
var f = pkg_res.files[j] f = pkg_res.files[j]
for (var k = 0; k < length(f.tests); k++) { for (k = 0; k < length(f.tests); k++) {
push(pkg_tests, f.tests[k]) push(pkg_tests, f.tests[k])
} }
} }
var json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json` json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
fd.slurpwrite(json_path, stone(blob(json.encode(pkg_tests)))) fd.slurpwrite(json_path, stone(blob(json.encode(pkg_tests))))
} }
} }

42
test_parse_boot.cm Normal file
View File

@@ -0,0 +1,42 @@
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var src = text(fd.slurp("internal/bootstrap.cm"))
var tok_result = tokenize(src, "bootstrap.cm")
var ast = parse(tok_result.tokens, src, "bootstrap.cm", tokenize)
var i = 0
var folded = null
var ast_json = null
var f = null
var bytecode = null
var has_errors = ast.errors != null && length(ast.errors) > 0
if (has_errors) {
print("PARSE ERRORS:")
while (i < length(ast.errors)) {
print(text(ast.errors[i].line) + ":" + text(ast.errors[i].column) + " " + ast.errors[i].message)
i = i + 1
}
} else {
print("Parse OK")
print(" statements: " + text(length(ast.statements)))
if (ast.functions != null) {
print(" functions: " + text(length(ast.functions)))
} else {
print(" functions: null")
}
folded = fold(ast)
ast_json = json.encode(folded)
f = fd.open("/tmp/bootstrap_ast.json", "w")
fd.write(f, ast_json)
fd.close(f)
print("Wrote AST to /tmp/bootstrap_ast.json")
bytecode = mach_compile_ast("bootstrap", ast_json)
print("Bytecode size: " + text(length(bytecode)))
f = fd.open("/tmp/bootstrap_test.mach", "w")
fd.write(f, bytecode)
fd.close(f)
print("Wrote bytecode to /tmp/bootstrap_test.mach")
}

165
tests/decl_restrictions.ce Normal file
View File

@@ -0,0 +1,165 @@
// Declaration restriction tests
// Run: ./cell tests/decl_restrictions.ce
var tokenize = use("tokenize")
var parse = use("parse")
var passed = 0
var failed = 0
var error_names = []
var error_reasons = []
var fail_msg = ""
var _i = 0
for (_i = 0; _i < 20; _i++) {
error_names[] = null
error_reasons[] = null
}
var fail = function(msg) {
fail_msg = msg
disrupt
}
var run = function(name, fn) {
fail_msg = ""
fn()
passed = passed + 1
} disruption {
error_names[failed] = name
error_reasons[failed] = fail_msg == "" ? "disruption" : fail_msg
failed = failed + 1
}
var parse_snippet = function(src) {
var result = tokenize(src, "<test>")
var ast = parse(result.tokens, src, "<test>", tokenize)
return ast
}
var has_error = function(ast, substring) {
if (ast.errors == null) return false
var i = 0
while (i < length(ast.errors)) {
if (search(ast.errors[i].message, substring) != null) return true
i = i + 1
}
return false
}
var has_no_errors = function(ast) {
return ast.errors == null || length(ast.errors) == 0
}
// === BARE BLOCK ===
run("bare block rejected", function() {
var ast = parse_snippet("{ var x = 1 }")
if (!has_error(ast, "bare block")) fail("expected 'bare block' error, got: " + text(ast.errors))
})
// === VAR IN IF (braces) ===
run("var in if braces", function() {
var ast = parse_snippet("if (true) { var x = 1 }")
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
})
// === VAR IN IF (no braces) ===
run("var in if no braces", function() {
var ast = parse_snippet("if (true) var x = 1")
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
})
// === VAR IN WHILE ===
run("var in while", function() {
var ast = parse_snippet("while (true) { var x = 1; break }")
if (!has_error(ast, "not inside 'while'")) fail("expected 'not inside while' error, got: " + text(ast.errors))
})
// === VAR IN FOR INIT ===
run("var in for init", function() {
var ast = parse_snippet("for (var i = 0; i < 1; i++) {}")
if (!has_error(ast, "for initializer")) fail("expected 'for initializer' error, got: " + text(ast.errors))
})
// === VAR IN FOR BODY ===
run("var in for body", function() {
var ast = parse_snippet("var i = 0; for (i = 0; i < 1; i++) { var x = 1 }")
if (!has_error(ast, "not inside 'for'")) fail("expected 'not inside for' error, got: " + text(ast.errors))
})
// === VAR IN DO ===
run("var in do", function() {
var ast = parse_snippet("do { var x = 1; break } while (true)")
if (!has_error(ast, "not inside 'do'")) fail("expected 'not inside do' error, got: " + text(ast.errors))
})
// === DEF IN IF ===
run("def in if", function() {
var ast = parse_snippet("if (true) { def x = 1 }")
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
})
// === UNINITIALIZED VAR ===
run("uninitialized var", function() {
var ast = parse_snippet("var x")
if (!has_error(ast, "must be initialized")) fail("expected 'must be initialized' error, got: " + text(ast.errors))
})
// === NESTED: VAR IN IF INSIDE WHILE ===
run("nested var in if inside while", function() {
var ast = parse_snippet("while (true) { if (true) { var x = 1 }; break }")
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
})
// === VALID: NESTED FUNCTION ===
run("valid nested fn in control flow", function() {
var ast = parse_snippet("if (true) { var fn = function() { var x = 1; return x } }")
// The var inside the function is fine; only the var fn = ... inside if should error
if (!has_error(ast, "not inside 'if'")) fail("expected error for var fn inside if")
// But there should NOT be an error about var x inside the function body
var i = 0
var bad = false
if (ast.errors != null) {
while (i < length(ast.errors)) {
if (search(ast.errors[i].message, "var x") != null) bad = true
i = i + 1
}
}
if (bad) fail("should not error on var inside nested function")
})
// === VALID: NORMAL VAR ===
run("valid normal var", function() {
var ast = parse_snippet("var x = 1")
if (!has_no_errors(ast)) fail("expected no errors, got: " + text(ast.errors))
})
// === VALID: VAR IN FUNCTION BODY ===
run("valid var in function body", function() {
var ast = parse_snippet("var fn = function() { var x = 1; return x }")
if (!has_no_errors(ast)) fail("expected no errors, got: " + text(ast.errors))
})
// === SUMMARY ===
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
var _j = 0
if (failed > 0) {
print("")
for (_j = 0; _j < failed; _j++) {
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
}
}

File diff suppressed because it is too large Load Diff

344
time.cm
View File

@@ -1,204 +1,212 @@
// epoch = 0000-01-01 00:00:00 +0000 // epoch = 0000-01-01 00:00:00 +0000
var time = this; var time = native
/* -------- host helpers -------------------------------------------------- */ var now = time.now
var now = time.now; // seconds since Misty epoch (C stub) var computer_zone = time.computer_zone
var computer_zone = time.computer_zone; // integral hours, no DST var computer_dst = time.computer_dst
var computer_dst = time.computer_dst; // true ↔ DST in effect
delete time.now; delete time.now
delete time.computer_zone; delete time.computer_zone
delete time.computer_dst; delete time.computer_dst
/* -------- units & static tables ----------------------------------------- */ time.second = 1
time.minute = 60
time.second = 1; time.hour = 3600
time.minute = 60; time.day = 86400
time.hour = 3_600; time.week = 604800
time.day = 86_400;
time.week = 604_800;
time.weekdays = [ time.weekdays = [
"Sunday", "Monday", "Tuesday", "Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday" "Wednesday", "Thursday", "Friday", "Saturday"
]; ]
time.monthstr = [ time.monthstr = [
"January", "February", "March", "April", "May", "June", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" "July", "August", "September", "October", "November", "December"
]; ]
time.epoch = 1970; time.epoch = 1970
/* ratios (kept K&R-style) */ time.hour2minute = function() { return time.hour / time.minute }
time.hour2minute = function() { return time.hour / time.minute; }; time.day2hour = function() { return time.day / time.hour }
time.day2hour = function() { return time.day / time.hour; }; time.minute2second = function() { return time.minute / time.second }
time.minute2second = function() { return time.minute / time.second; }; time.week2day = function() { return time.week / time.day }
time.week2day = function() { return time.week / time.day; };
/* leap-year helpers */ time.yearsize = function yearsize(y) {
time.yearsize = function yearsize(y) if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) return 366
{ return 365
if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) return 366; }
return 365; time.isleap = function(y) { return time.yearsize(y) == 366 }
};
time.isleap = function(y) { return time.yearsize(y) == 366; };
/* timecode utility */ time.timecode = function(t) {
time.timecode = function(t, fps = 24) var fps = 24
{ var s = whole(t)
var s = whole(t); var frac = t - s
var frac = t - s; return `${s}:${whole(frac * fps)}`
return `${s}:${whole(frac * fps)}`;
};
/* per-month day counts (non-leap) */
time.monthdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// convert seconds-since-epoch → broken-down record
function time_record(num = now(),
zone = computer_zone(),
dst = computer_dst())
{
if (is_object(num)) return num;
var monthdays = array(time.monthdays);
var rec = {
second : 0, minute : 0, hour : 0,
yday : 0, year : 0,
weekday: 0, month : 0, day : 0,
zone : zone, dst : !!dst,
ce : "AD"
};
/* include DST in the effective offset */
var offset = zone + (dst ? 1 : 0);
num += offset * time.hour;
/* split into day + seconds-of-day */
var hms = num % time.day;
var day = floor(num / time.day);
if (hms < 0) { hms += time.day; day--; }
rec.second = hms % time.minute;
var tmp = floor(hms / time.minute);
rec.minute = tmp % time.minute;
rec.hour = floor(tmp / time.minute);
rec.weekday = (day + 4_503_599_627_370_496 + 2) % 7; /* 2 → 1970-01-01 was Thursday */
/* year & day-of-year */
var y = time.epoch;
if (day >= 0) {
for (y = time.epoch; day >= time.yearsize(y); y++)
day -= time.yearsize(y);
} else {
for (y = time.epoch; day < 0; y--)
day += time.yearsize(y - 1);
}
rec.year = y;
rec.ce = (y <= 0) ? "BC" : "AD";
rec.yday = day;
/* month & month-day */
if (time.yearsize(y) == 366) monthdays[1] = 29;
var m = 0;
for (; day >= monthdays[m]; m++) day -= monthdays[m];
rec.month = m;
rec.day = day + 1;
return rec;
} }
function time_number(rec = now()) time.monthdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
{
if (is_number(rec)) return rec;
log.console(rec) // convert seconds-since-epoch -> broken-down record
log.console(rec.minute) function time_record(_num, _zone, _dst) {
var n = _num
var z = _zone
var d = _dst
if (n == null) n = now()
if (z == null) z = computer_zone()
if (d == null) d = computer_dst()
var c = 0; if (is_object(n)) return n
var year = rec.year || 0;
var hour = rec.hour || 0;
var minute = rec.minute || 0;
var second = rec.second || 0;
var zone = rec.zone || 0;
var dst = rec.dst ? 1 : 0;
var yday = rec.yday || 0;
if (year > time.epoch) { var monthdays = array(time.monthdays)
for (var i = time.epoch; i < year; i++) var rec = {
c += time.day * time.yearsize(i); second: 0, minute: 0, hour: 0,
} else if (year < time.epoch) { yday: 0, year: 0,
for (var i = time.epoch - 1; i > year; i--) weekday: 0, month: 0, day: 0,
c += time.day * time.yearsize(i); zone: z, dst: !!d,
c += (time.yearsize(year) - yday - 1) * time.day; ce: "AD"
c += (time.day2hour() - hour - 1) * time.hour;
c += (time.hour2minute() - minute - 1) * time.minute;
c += time.minute2second() - second;
c += (zone + dst) * time.hour;
return -c; /* BCE */
} }
c = second; var offset = z + (d ? 1 : 0)
c += minute * time.minute; n = n + offset * time.hour
c += hour * time.hour;
c += yday * time.day;
c -= (zone + dst) * time.hour;
return c; var hms = n % time.day
var day = floor(n / time.day)
if (hms < 0) { hms = hms + time.day; day = day - 1 }
rec.second = hms % time.minute
var tmp = floor(hms / time.minute)
rec.minute = tmp % time.minute
rec.hour = floor(tmp / time.minute)
rec.weekday = (day + 4503599627370496 + 2) % 7
var y = time.epoch
if (day >= 0) {
y = time.epoch
while (day >= time.yearsize(y)) {
day = day - time.yearsize(y)
y = y + 1
}
} else {
y = time.epoch
while (day < 0) {
y = y - 1
day = day + time.yearsize(y)
}
}
rec.year = y
rec.ce = (y <= 0) ? "BC" : "AD"
rec.yday = day
if (time.yearsize(y) == 366) monthdays[1] = 29
var m = 0
while (day >= monthdays[m]) {
day = day - monthdays[m]
m = m + 1
}
rec.month = m
rec.day = day + 1
return rec
}
function time_number(_rec) {
var r = _rec
if (r == null) r = now()
if (is_number(r)) return r
var c = 0
var year = r.year || 0
var hour = r.hour || 0
var minute = r.minute || 0
var second = r.second || 0
var zone = r.zone || 0
var dst = r.dst ? 1 : 0
var yday = r.yday || 0
var i = 0
if (year > time.epoch) {
i = time.epoch
while (i < year) {
c = c + time.day * time.yearsize(i)
i = i + 1
}
} else if (year < time.epoch) {
i = time.epoch - 1
while (i > year) {
c = c + time.day * time.yearsize(i)
i = i - 1
}
c = c + (time.yearsize(year) - yday - 1) * time.day
c = c + (time.day2hour() - hour - 1) * time.hour
c = c + (time.hour2minute() - minute - 1) * time.minute
c = c + time.minute2second() - second
c = c + (zone + dst) * time.hour
return -c
}
c = second
c = c + minute * time.minute
c = c + hour * time.hour
c = c + yday * time.day
c = c - (zone + dst) * time.hour
return c
} }
// text formatting // text formatting
var default_fmt = "vB mB d hh:nn:ss a z y c"; /* includes new DST token */ var default_fmt = "vB mB d hh:nn:ss a z y c"
function time_text(num = now(), function time_text(_num, _fmt, _zone, _dst) {
fmt = default_fmt, var n = _num
zone = computer_zone(), var f = _fmt
dst = computer_dst()) var z = _zone
{ var d = _dst
var rec = is_number(num) ? time_record(num, zone, dst) : num; if (n == null) n = now()
zone = rec.zone; if (f == null) f = default_fmt
dst = rec.dst; if (z == null) z = computer_zone()
if (d == null) d = computer_dst()
/* am/pm */ var rec = is_number(n) ? time_record(n, z, d) : n
if (search(fmt, "a") != null) { z = rec.zone
if (rec.hour >= 13) { rec.hour -= 12; fmt = replace(fmt, "a", "PM"); } d = rec.dst
else if (rec.hour == 12) { fmt = replace(fmt, "a", "PM"); }
else if (rec.hour == 0) { rec.hour = 12; fmt = replace(fmt, "a", "AM"); } if (search(f, "a") != null) {
else fmt = replace(fmt, "a", "AM"); if (rec.hour >= 13) { rec.hour = rec.hour - 12; f = replace(f, "a", "PM") }
else if (rec.hour == 12) { f = replace(f, "a", "PM") }
else if (rec.hour == 0) { rec.hour = 12; f = replace(f, "a", "AM") }
else f = replace(f, "a", "AM")
} }
/* BCE/CE */ var year = rec.year > 0 ? rec.year : rec.year - 1
var year = rec.year > 0 ? rec.year : rec.year - 1; if (search(f, "c") != null) {
if (search(fmt, "c") != null) { if (year < 0) { year = abs(year); f = replace(f, "c", "BC") }
if (year < 0) { year = abs(year); fmt = replace(fmt, "c", "BC"); } else f = replace(f, "c", "AD")
else fmt = replace(fmt, "c", "AD");
} }
/* substitutions */ var full_offset = z + (d ? 1 : 0)
var full_offset = zone + (dst ? 1 : 0); f = replace(f, "yyyy", text(year, "i4"))
fmt = replace(fmt, "yyyy", text(year, "i4")) f = replace(f, "y", year)
fmt = replace(fmt, "y", year); f = replace(f, "eee", rec.yday + 1)
fmt = replace(fmt, "eee", rec.yday + 1); f = replace(f, "dd", text(rec.day, "i2"))
fmt = replace(fmt, "dd", text(rec.day, "i2")) f = replace(f, "d", rec.day)
fmt = replace(fmt, "d", rec.day); f = replace(f, "hh", text(rec.hour, "i2"))
fmt = replace(fmt, "hh", text(rec.hour, "i2")); f = replace(f, "h", rec.hour)
fmt = replace(fmt, "h", rec.hour); f = replace(f, "nn", text(rec.minute, "i2"))
fmt = replace(fmt, "nn", text(rec.minute, "i2")); f = replace(f, "n", rec.minute)
fmt = replace(fmt, "n", rec.minute); f = replace(f, "ss", text(rec.second, "i2"))
fmt = replace(fmt, "ss", text(rec.second, "i2")); f = replace(f, "s", rec.second)
fmt = replace(fmt, "s", rec.second); f = replace(f, "x", d ? "DST" : "")
fmt = replace(fmt, "x", dst ? "DST" : ""); /* new */ f = replace(f, "z", (full_offset >= 0 ? "+" : "") + text(full_offset))
fmt = replace(fmt, "z", (full_offset >= 0 ? "+" : "") + text(full_offset)); f = replace(f, /mm[^bB]/g, rec.month + 1)
fmt = replace(fmt, /mm[^bB]/g, rec.month + 1); f = replace(f, /m[^bB]/g, rec.month + 1)
fmt = replace(fmt, /m[^bB]/g, rec.month + 1); f = replace(f, /v[^bB]/g, rec.weekday)
fmt = replace(fmt, /v[^bB]/g, rec.weekday); f = replace(f, "mb", text(time.monthstr[rec.month], 0, 3))
fmt = replace(fmt, "mb", text(time.monthstr[rec.month], 0, 3)); f = replace(f, "mB", time.monthstr[rec.month])
fmt = replace(fmt, "mB", time.monthstr[rec.month]); f = replace(f, "vB", time.weekdays[rec.weekday])
fmt = replace(fmt, "vB", time.weekdays[rec.weekday]); f = replace(f, "vb", text(time.weekdays[rec.weekday], 0, 3))
fmt = replace(fmt, "vb", text(time.weekdays[rec.weekday], 0, 3));
return fmt; return f
} }
return { record: time_record, number: time_number, text: time_text }; return { record: time_record, number: time_number, text: time_text }

View File

@@ -1,4 +1,5 @@
var fd = use("fd") var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize") var tokenize = use("tokenize")
var filename = args[0] var filename = args[0]
var src = text(fd.slurp(filename)) var src = text(fd.slurp(filename))

View File

@@ -1,11 +1,6 @@
var tokenize = function(src, filename) { var tokenize = function(src, filename) {
var len = length(src) var len = length(src)
var cp = [] var cp = array(array(src), codepoint)
var _i = 0
while (_i < len) {
push(cp, codepoint(src[_i]))
_i = _i + 1
}
var pos = 0 var pos = 0
var row = 0 var row = 0
@@ -148,46 +143,45 @@ var tokenize = function(src, filename) {
} }
var substr = function(start, end) { var substr = function(start, end) {
var s = "" return text(src, start, end)
var i = start
while (i < end) {
s = s + character(cp[i])
i = i + 1
}
return s
} }
var read_string = function(quote_cp) { var read_string = function(quote_cp) {
var start = pos var start = pos
var start_row = row var start_row = row
var start_col = col var start_col = col
var value = "" var parts = []
var run_start = 0
var esc = 0 var esc = 0
adv() // skip opening quote adv() // skip opening quote
run_start = pos
while (pos < len && pk() != quote_cp) { while (pos < len && pk() != quote_cp) {
if (pk() == CP_BSLASH) { if (pk() == CP_BSLASH) {
if (pos > run_start) push(parts, text(src, run_start, pos))
adv() adv()
esc = adv() esc = adv()
if (esc == CP_n) { value = value + "\n" } if (esc == CP_n) { push(parts, "\n") }
else if (esc == CP_t) { value = value + "\t" } else if (esc == CP_t) { push(parts, "\t") }
else if (esc == CP_r) { value = value + "\r" } else if (esc == CP_r) { push(parts, "\r") }
else if (esc == CP_BSLASH) { value = value + "\\" } else if (esc == CP_BSLASH) { push(parts, "\\") }
else if (esc == CP_SQUOTE) { value = value + "'" } else if (esc == CP_SQUOTE) { push(parts, "'") }
else if (esc == CP_DQUOTE) { value = value + "\"" } else if (esc == CP_DQUOTE) { push(parts, "\"") }
else if (esc == CP_0) { value = value + character(0) } else if (esc == CP_0) { push(parts, character(0)) }
else if (esc == CP_BACKTICK) { value = value + "`" } else if (esc == CP_BACKTICK) { push(parts, "`") }
else if (esc == CP_u) { value = value + read_unicode_escape() } else if (esc == CP_u) { push(parts, read_unicode_escape()) }
else { value = value + character(esc) } else { push(parts, character(esc)) }
run_start = pos
} else { } else {
value = value + character(adv()) adv()
} }
} }
if (pos > run_start) push(parts, text(src, run_start, pos))
if (pos < len) adv() // skip closing quote if (pos < len) adv() // skip closing quote
push(tokens, { push(tokens, {
kind: "text", at: start, kind: "text", at: start,
from_row: start_row, from_column: start_col, from_row: start_row, from_column: start_col,
to_row: row, to_column: col, to_row: row, to_column: col,
value: value value: text(parts)
}) })
} }
@@ -195,49 +189,54 @@ var tokenize = function(src, filename) {
var start = pos var start = pos
var start_row = row var start_row = row
var start_col = col var start_col = col
var value = "" var parts = []
var run_start = 0
var depth = 0 var depth = 0
var tc = 0 var tc = 0
var q = 0 var q = 0
var interp_start = 0
adv() // skip opening backtick adv() // skip opening backtick
run_start = pos
while (pos < len && pk() != CP_BACKTICK) { while (pos < len && pk() != CP_BACKTICK) {
if (pk() == CP_BSLASH && pos + 1 < len) { if (pk() == CP_BSLASH && pos + 1 < len) {
value = value + character(adv()) if (pos > run_start) push(parts, text(src, run_start, pos))
value = value + character(adv()) push(parts, text(src, pos, pos + 2))
adv(); adv()
run_start = pos
} else if (pk() == CP_DOLLAR && pos + 1 < len && pk_at(1) == CP_LBRACE) { } else if (pk() == CP_DOLLAR && pos + 1 < len && pk_at(1) == CP_LBRACE) {
value = value + character(adv()) // $ if (pos > run_start) push(parts, text(src, run_start, pos))
value = value + character(adv()) // { interp_start = pos
adv(); adv() // $ {
depth = 1 depth = 1
while (pos < len && depth > 0) { while (pos < len && depth > 0) {
tc = pk() tc = pk()
if (tc == CP_LBRACE) { depth = depth + 1; value = value + character(adv()) } if (tc == CP_LBRACE) { depth = depth + 1; adv() }
else if (tc == CP_RBRACE) { else if (tc == CP_RBRACE) {
depth = depth - 1 depth = depth - 1
if (depth > 0) { value = value + character(adv()) } adv()
else { value = value + character(adv()) }
} }
else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) { else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) {
q = adv() q = adv()
value = value + character(q)
while (pos < len && pk() != q) { while (pos < len && pk() != q) {
if (pk() == CP_BSLASH && pos + 1 < len) { if (pk() == CP_BSLASH && pos + 1 < len) adv()
value = value + character(adv()) adv()
}
value = value + character(adv())
} }
if (pos < len) { value = value + character(adv()) } if (pos < len) adv()
} else { value = value + character(adv()) } } else { adv() }
} }
push(parts, text(src, interp_start, pos))
run_start = pos
} else { } else {
value = value + character(adv()) adv()
} }
} }
if (pos > run_start) push(parts, text(src, run_start, pos))
if (pos < len) adv() // skip closing backtick if (pos < len) adv() // skip closing backtick
push(tokens, { push(tokens, {
kind: "text", at: start, kind: "text", at: start,
from_row: start_row, from_column: start_col, from_row: start_row, from_column: start_col,
to_row: row, to_column: col, to_row: row, to_column: col,
value: value value: text(parts)
}) })
} }
@@ -344,14 +343,13 @@ var tokenize = function(src, filename) {
var start = pos var start = pos
var start_row = row var start_row = row
var start_col = col var start_col = col
var val = ""
var i = 0 var i = 0
while (i < count) { val = val + character(adv()); i = i + 1 } while (i < count) { adv(); i = i + 1 }
push(tokens, { push(tokens, {
kind: "name", at: start, kind: "name", at: start,
from_row: start_row, from_column: start_col, from_row: start_row, from_column: start_col,
to_row: row, to_column: col, to_row: row, to_column: col,
value: val value: text(src, start, pos)
}) })
} }

Binary file not shown.

127
toml.cm
View File

@@ -3,21 +3,16 @@
function toml_unescape(s) { function toml_unescape(s) {
if (!is_text(s)) return null if (!is_text(s)) return null
// Order matters: var r = replace(s, '\\"', '"')
// "\\\"" (backslash + quote) should become "\"", not just '"' r = replace(r, '\\\\', '\\')
// So: unescape \" first, then unescape \\. return r
s = replace(s, '\\"', '"')
s = replace(s, '\\\\', '\\')
return s
} }
function toml_escape(s) { function toml_escape(s) {
if (!is_text(s)) return null if (!is_text(s)) return null
// Order matters: var r = replace(s, '\\', '\\\\')
// escape backslashes first, otherwise escaping quotes introduces new backslashes that would get double-escaped. r = replace(r, '"', '\\"')
s = replace(s, '\\', '\\\\') return r
s = replace(s, '"', '\\"')
return s
} }
function parse_toml(toml_text) { function parse_toml(toml_text) {
@@ -31,23 +26,33 @@ function parse_toml(toml_text) {
var current_section = result var current_section = result
var current_section_name = '' var current_section_name = ''
for (var i = 0; i < length(lines); i++) { var i = 0
var line = trim(lines[i]) var line = null
var inner = null
var section_path = null
var j = 0
var key = null
var eq_index = null
var key_part = null
var value = null
var unquoted = null
for (i = 0; i < length(lines); i++) {
line = trim(lines[i])
if (line == null) line = lines[i] if (line == null) line = lines[i]
// Skip empty lines and comments // Skip empty lines and comments
if (!line || starts_with(line, '#')) continue if (!line || starts_with(line, '#')) continue
// Section header // Section header
if (starts_with(line, '[') && ends_with(line, ']')) { if (starts_with(line, '[') && ends_with(line, ']')) {
var inner = text(line, 1, -1) inner = text(line, 1, -1)
var section_path = parse_key_path(inner) section_path = parse_key_path(inner)
if (section_path == null) return null if (section_path == null) return null
current_section = result current_section = result
current_section_name = text(section_path, '.') current_section_name = text(section_path, '.')
for (var j = 0; j < length(section_path); j++) { for (j = 0; j < length(section_path); j++) {
var key = section_path[j] key = section_path[j]
// Only treat null as "missing"; do not clobber false/0/"" // Only treat null as "missing"; do not clobber false/0/""
if (current_section[key] == null) { if (current_section[key] == null) {
@@ -63,18 +68,18 @@ function parse_toml(toml_text) {
} }
// Key-value pair // Key-value pair
var eq_index = search(line, '=') eq_index = search(line, '=')
if (eq_index != null && eq_index > 0) { if (eq_index != null && eq_index > 0) {
var key_part = trim(text(line, 0, eq_index)) key_part = trim(text(line, 0, eq_index))
var value = trim(text(line, eq_index + 1)) value = trim(text(line, eq_index + 1))
if (key_part == null) key_part = trim(text(line, 0, eq_index)) if (key_part == null) key_part = trim(text(line, 0, eq_index))
if (value == null) value = trim(text(line, eq_index + 1)) if (value == null) value = trim(text(line, eq_index + 1))
var key = parse_key(key_part) key = parse_key(key_part)
if (key == null) return null if (key == null) return null
if (starts_with(value, '"') && ends_with(value, '"')) { if (starts_with(value, '"') && ends_with(value, '"')) {
var unquoted = text(value, 1, -1) unquoted = text(value, 1, -1)
current_section[key] = toml_unescape(unquoted) current_section[key] = toml_unescape(unquoted)
if (current_section[key] == null) return null if (current_section[key] == null) return null
} else if (starts_with(value, '[') && ends_with(value, ']')) { } else if (starts_with(value, '[') && ends_with(value, ']')) {
@@ -96,9 +101,9 @@ function parse_toml(toml_text) {
function parse_key(str) { function parse_key(str) {
if (!is_text(str)) return null if (!is_text(str)) return null
var inner = null
if (starts_with(str, '"') && ends_with(str, '"')) { if (starts_with(str, '"') && ends_with(str, '"')) {
var inner = text(str, 1, -1) inner = text(str, 1, -1)
return toml_unescape(inner) return toml_unescape(inner)
} }
return str return str
@@ -112,18 +117,21 @@ function parse_key_path(str) {
var current = '' var current = ''
var in_quote = false var in_quote = false
for (var i = 0; i < length(str); i++) { var i = 0
var c = str[i] var c = null
var piece = null
for (i = 0; i < length(str); i++) {
c = str[i]
if (c == '"' && (i == 0 || str[i - 1] != '\\')) { if (c == '"' && (i == 0 || str[i - 1] != '\\')) {
in_quote = !in_quote in_quote = !in_quote
} else if (c == '.' && !in_quote) { } else if (c == '.' && !in_quote) {
var piece = trim(current) piece = trim(current)
if (piece == null) piece = trim(current) if (piece == null) piece = trim(current)
push(parts, parse_key(piece)) push(parts, parse_key(piece))
current = '' current = ''
continue continue
} }
current += c current = current + c
} }
var tail = trim(current) var tail = trim(current)
@@ -137,27 +145,30 @@ function parse_array(str) {
if (!is_text(str)) return null if (!is_text(str)) return null
// Remove brackets and trim // Remove brackets and trim
str = text(str, 1, -1) var s = text(str, 1, -1)
str = trim(str) s = trim(s)
if (!str) return [] if (!s) return []
var items = [] var items = []
var current = '' var current = ''
var in_quotes = false var in_quotes = false
for (var i = 0; i < length(str); i++) { var i = 0
var ch = str[i] var ch = null
var piece = null
for (i = 0; i < length(s); i++) {
ch = s[i]
if (ch == '"' && (i == 0 || str[i - 1] != '\\')) { if (ch == '"' && (i == 0 || s[i - 1] != '\\')) {
in_quotes = !in_quotes in_quotes = !in_quotes
current += ch current = current + ch
} else if (ch == ',' && !in_quotes) { } else if (ch == ',' && !in_quotes) {
var piece = trim(current) piece = trim(current)
if (piece == null) piece = trim(current) if (piece == null) piece = trim(current)
push(items, parse_value(piece)) push(items, parse_value(piece))
current = '' current = ''
} else { } else {
current += ch current = current + ch
} }
} }
@@ -186,12 +197,14 @@ function encode_toml(obj) {
var result = [] var result = []
function encode_value(value) { function encode_value(value) {
var items = null
var i = 0
if (is_text(value)) return '"' + toml_escape(value) + '"' if (is_text(value)) return '"' + toml_escape(value) + '"'
if (is_logical(value)) return value ? 'true' : 'false' if (is_logical(value)) return value ? 'true' : 'false'
if (is_number(value)) return text(value) if (is_number(value)) return text(value)
if (is_array(value)) { if (is_array(value)) {
var items = [] items = []
for (var i = 0; i < length(value); i++) push(items, encode_value(value[i])) for (i = 0; i < length(value); i++) push(items, encode_value(value[i]))
return '[' + text(items, ', ') + ']' return '[' + text(items, ', ') + ']'
} }
return text(value) return text(value)
@@ -206,29 +219,41 @@ function encode_toml(obj) {
// First pass: encode top-level simple values // First pass: encode top-level simple values
var keys = array(obj) var keys = array(obj)
for (var i = 0; i < length(keys); i++) { var i = 0
var key = keys[i] var key = null
var value = obj[key] var value = null
for (i = 0; i < length(keys); i++) {
key = keys[i]
value = obj[key]
if (!is_object(value)) push(result, quote_key(key) + ' = ' + encode_value(value)) if (!is_object(value)) push(result, quote_key(key) + ' = ' + encode_value(value))
} }
// Second pass: encode nested objects // Second pass: encode nested objects
function encode_section(o, path) { function encode_section(o, path) {
var keys = array(o) var keys = array(o)
for (var i = 0; i < length(keys); i++) { var i = 0
var key = keys[i] var key = null
var value = o[key] var value = null
var quoted = null
var section_path = null
var section_keys = null
var j = 0
var sk = null
var sv = null
for (i = 0; i < length(keys); i++) {
key = keys[i]
value = o[key]
if (is_object(value)) { if (is_object(value)) {
var quoted = quote_key(key) quoted = quote_key(key)
var section_path = path ? path + '.' + quoted : quoted section_path = path ? path + '.' + quoted : quoted
push(result, '[' + section_path + ']') push(result, '[' + section_path + ']')
// Direct properties // Direct properties
var section_keys = array(value) section_keys = array(value)
for (var j = 0; j < length(section_keys); j++) { for (j = 0; j < length(section_keys); j++) {
var sk = section_keys[j] sk = section_keys[j]
var sv = value[sk] sv = value[sk]
if (!is_object(sv)) push(result, quote_key(sk) + ' = ' + encode_value(sv)) if (!is_object(sv)) push(result, quote_key(sk) + ' = ' + encode_value(sv))
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
var f = x => { return x }; f(1)

View File

@@ -1 +0,0 @@
var f = (x = 10) => x; f()

View File

@@ -1 +0,0 @@
var f = x => x * 2; f(5)

View File

@@ -1 +0,0 @@
var f = (a, b) => a + b; f(2, 3)

View File

@@ -1 +0,0 @@
var f = () => 42; f()

View File

@@ -1 +0,0 @@
var x = 5; x += 3; x

View File

@@ -1 +0,0 @@
var x = 7; x &= 3; x

View File

@@ -1 +0,0 @@
var x = 6; x /= 2; x

View File

@@ -1 +0,0 @@
var x = 5; x &&= 10; x

View File

@@ -1 +0,0 @@
var x = 0; x ||= 10; x

View File

@@ -1 +0,0 @@
var x = 7; x %= 3; x

View File

@@ -1 +0,0 @@
var x = 5; x *= 3; x

View File

@@ -1 +0,0 @@
var x = null; x ??= 10; x

View File

@@ -1 +0,0 @@
var x = 5; x |= 2; x

View File

@@ -1 +0,0 @@
var x = 2; x **= 3; x

View File

@@ -1 +0,0 @@
var x = 2; x <<= 3; x

View File

@@ -1 +0,0 @@
var x = 8; x >>= 2; x

View File

@@ -1 +0,0 @@
var x = -8; x >>>= 2; x

View File

@@ -1 +0,0 @@
var x = 5; x -= 3; x

View File

@@ -1 +0,0 @@
var x = 5; x ^= 3; x

View File

@@ -1 +0,0 @@
var x, y; x = y = 5; x + y

View File

@@ -1 +0,0 @@
var f = function(x) { return function() { return x } }; f(5)()

View File

@@ -1,11 +0,0 @@
var counter = function() {
var n = 0
return function() {
n = n + 1
return n
}
}
var c = counter()
c()
c()
c()

View File

@@ -1,3 +0,0 @@
// simple test that comments work
var x = 5
// other comment

View File

@@ -1 +0,0 @@
/* comment */ 5

Some files were not shown because too many files have changed in this diff Show More