117 Commits

Author SHA1 Message Date
John Alanbrook
a74231563a further loading 2026-02-08 22:26:13 -06:00
John Alanbrook
6799d90a7d engine is at least running 2026-02-08 11:09:01 -06:00
John Alanbrook
1fee8f9f8b condense jsruntime and jscontext 2026-02-08 10:10:42 -06:00
John Alanbrook
d18ea1b330 update engine.cm 2026-02-08 08:24:49 -06:00
John Alanbrook
4de0659474 allow tokens as properties 2026-02-08 00:34:15 -06:00
John Alanbrook
27a9b72b07 functino tests; default args for mach and mcode 2026-02-08 00:31:18 -06:00
John Alanbrook
a3622bd5bd better parser error reporting 2026-02-08 00:23:47 -06:00
John Alanbrook
2f6700415e add functinos 2026-02-07 23:38:39 -06:00
John Alanbrook
243d92f7f3 rm ?? and .? 2026-02-07 22:09:40 -06:00
John Alanbrook
8f9d026b9b use casesensitive json 2026-02-07 17:01:11 -06:00
John Alanbrook
2c9ac8f7b6 no json roundtrip for mcode 2026-02-07 16:29:04 -06:00
John Alanbrook
80f24e131f all suite asan errors fixed for mcode 2026-02-07 16:15:58 -06:00
John Alanbrook
a8f8af7662 Merge branch 'mach' into mcode2 2026-02-07 15:49:38 -06:00
John Alanbrook
f5b3494762 memfree for mcode 2026-02-07 15:49:36 -06:00
John Alanbrook
13a6f6c79d faster mach bytecode generation 2026-02-07 15:49:09 -06:00
John Alanbrook
1a925371d3 faster parsing 2026-02-07 15:38:36 -06:00
John Alanbrook
08d2bacb1f improve ast parsing time 2026-02-07 15:22:18 -06:00
John Alanbrook
7322153e57 Merge branch 'mach' into mcode2 2026-02-07 14:53:41 -06:00
John Alanbrook
cc72c4cb0f fix mem errors for mcode 2026-02-07 14:53:35 -06:00
John Alanbrook
ae1f09a28f fix all memory errors 2026-02-07 14:53:14 -06:00
John Alanbrook
3c842912a1 gc fixing in mach vm 2026-02-07 14:25:04 -06:00
John Alanbrook
7cacf32078 Merge branch 'mach' into mcode2 2026-02-07 14:24:52 -06:00
John Alanbrook
b740612761 gc fixing in mach vm 2026-02-07 14:24:49 -06:00
John Alanbrook
6001c2b4bb Merge branch 'mach' into mcode2 2026-02-07 14:19:19 -06:00
John Alanbrook
98625fa15b mcode fix tests 2026-02-07 14:19:17 -06:00
John Alanbrook
87fafa44c8 fix last error 2026-02-07 13:43:13 -06:00
John Alanbrook
45ce76aef7 fixes 2026-02-07 12:50:46 -06:00
John Alanbrook
32fb44857c 1 test failing now 2026-02-07 12:50:26 -06:00
John Alanbrook
31d67f6710 fix vm suite tests 2026-02-07 12:34:18 -06:00
John Alanbrook
3621b1ef33 Merge branch 'mach' into mcode2 2026-02-07 11:53:44 -06:00
John Alanbrook
836227c8d3 fix mach proxy and templates 2026-02-07 11:53:39 -06:00
John Alanbrook
0ae59705d4 fix errors 2026-02-07 11:53:26 -06:00
John Alanbrook
8e2607b6ca Merge branch 'mcode2' into mach 2026-02-07 10:54:19 -06:00
John Alanbrook
dc73e86d8c handle mcode in callinternal 2026-02-07 10:51:45 -06:00
John Alanbrook
555cceb9d6 fixed text runner 2026-02-07 10:51:27 -06:00
John Alanbrook
fbb7933eb6 Merge branch 'mcode2' into mach 2026-02-07 10:40:20 -06:00
John Alanbrook
0287d6ada4 regex uses C strings now 2026-02-07 10:28:35 -06:00
John Alanbrook
73cd6a255d more test fixing 2026-02-07 07:59:52 -06:00
John Alanbrook
83ea67c01b Merge branch 'mach' into mcode2 2026-02-07 00:10:01 -06:00
John Alanbrook
16059cca4e fix tests 2026-02-07 00:09:58 -06:00
John Alanbrook
9ffe60ebef vm suite 2026-02-07 00:09:41 -06:00
John Alanbrook
2beafec5d9 fix tests 2026-02-07 00:09:21 -06:00
John Alanbrook
aba8eb66bd crash fixes 2026-02-06 23:38:56 -06:00
John Alanbrook
1abcaa92c7 Merge branch 'mach' into mcode2 2026-02-06 23:20:55 -06:00
John Alanbrook
168f7c71d5 fix text header chasing 2026-02-06 23:20:48 -06:00
John Alanbrook
56ed895b6e Merge branch 'mach' into mcode2 2026-02-06 23:15:38 -06:00
John Alanbrook
1e4646999d fix mach crashes 2026-02-06 23:15:33 -06:00
John Alanbrook
68d6c907fe fix mcode compilation 2026-02-06 23:13:13 -06:00
John Alanbrook
8150c64c7d pitcode 2026-02-06 22:58:21 -06:00
John Alanbrook
024d796ca4 add asan error vm stacktrace 2026-02-06 21:49:53 -06:00
John Alanbrook
ea185dbffd rm typeof 2026-02-06 21:26:45 -06:00
John Alanbrook
6571262af0 mach disrupt support 2026-02-06 21:09:18 -06:00
John Alanbrook
77ae133747 Merge branch 'mcode2' into mach 2026-02-06 20:45:57 -06:00
John Alanbrook
142a2d518b Merge branch 'stacktrace' into mach 2026-02-06 20:44:43 -06:00
John Alanbrook
5b65c64fe5 stack traces 2026-02-06 20:44:38 -06:00
John Alanbrook
e985fa5fe1 disrupt/disruption; remove try/catch 2026-02-06 18:40:56 -06:00
John Alanbrook
160ade2410 smarter gc malloc for large allocations 2026-02-06 18:38:23 -06:00
John Alanbrook
e2bc5948c1 fix functions and closures in mach 2026-02-06 18:30:26 -06:00
John Alanbrook
8cf98d8a9e Merge branch 'mcode2' into mach 2026-02-06 15:14:40 -06:00
John Alanbrook
3c38e828e5 context free tokenizing, parsing, compiling 2026-02-06 15:14:18 -06:00
John Alanbrook
af2d296f40 use new parser info 2026-02-06 12:45:25 -06:00
John Alanbrook
0a45394689 fix crash related to allocating in context heap 2026-02-06 12:43:19 -06:00
John Alanbrook
32885a422f bring in mcode 2026-02-06 04:24:14 -06:00
John Alanbrook
8959e53303 Merge branch 'newsyn' into mcode2 2026-02-06 03:55:56 -06:00
John Alanbrook
8a9a02b131 Merge branch 'newsyn' into mach 2026-02-06 03:54:38 -06:00
John Alanbrook
f9d68b2990 fix if/else, chained assignment 2026-02-06 03:54:25 -06:00
John Alanbrook
017a57b1eb use new parser information 2026-02-06 03:44:44 -06:00
John Alanbrook
ff8c68d01c mcode and mcode interpreter 2026-02-06 03:31:31 -06:00
John Alanbrook
9212003401 cannot set unbound 2026-02-06 03:24:01 -06:00
John Alanbrook
f9f8a4db42 Merge branch 'newsyn' into mach 2026-02-06 03:10:14 -06:00
John Alanbrook
8db95c654b more info in AST parser 2026-02-06 03:00:46 -06:00
John Alanbrook
63feabed5d mach vm 2026-02-06 02:50:48 -06:00
John Alanbrook
c814c0e1d8 rm new; rm void 2026-02-06 02:12:19 -06:00
John Alanbrook
bead0c48d4 Merge branch 'mcode' into newsyn 2026-02-06 02:02:46 -06:00
John Alanbrook
98dcab4ba7 comprehensive syntax test; fix multiple default args 2026-02-06 02:02:17 -06:00
John Alanbrook
ae44ce7b4b mcode and mach 2026-02-06 01:56:26 -06:00
John Alanbrook
1c38699b5a fix scope resolution 2026-02-06 01:41:03 -06:00
John Alanbrook
9a70a12d82 object literal 2026-02-05 21:41:34 -06:00
John Alanbrook
a8a271e014 Merge branch 'syntax' into ast 2026-02-05 20:39:56 -06:00
John Alanbrook
91761c03e6 push/pop syntax 2026-02-05 20:39:53 -06:00
John Alanbrook
5a479cc765 function literal in record literal 2026-02-05 20:32:57 -06:00
John Alanbrook
97a003e025 errors 2026-02-05 20:12:06 -06:00
John Alanbrook
20f14abd17 string templates 2026-02-05 19:34:06 -06:00
John Alanbrook
19ba184fec default params for functions 2026-02-05 18:44:40 -06:00
John Alanbrook
7909b11f6b better errors 2026-02-05 18:35:48 -06:00
John Alanbrook
27229c675c add parser and tokenizer errors 2026-02-05 18:14:49 -06:00
John Alanbrook
64d234ee35 Merge branch 'syntax' into ast 2026-02-05 17:45:15 -06:00
John Alanbrook
e861d73eec mkarecord 2026-02-05 17:45:13 -06:00
John Alanbrook
a24331aae5 tokenize 2026-02-05 11:21:34 -06:00
John Alanbrook
c1cb922b64 more comprehensive ast 2026-02-05 10:59:56 -06:00
John Alanbrook
aacb0b48bf more vm tests 2026-02-05 10:44:53 -06:00
John Alanbrook
b38aec95b6 Merge branch 'syntax' into ast 2026-02-05 10:29:29 -06:00
John Alanbrook
b29d3c2fe0 add vm tests 2026-02-05 10:29:09 -06:00
John Alanbrook
1cc3005b68 better jump labels 2026-02-05 10:28:13 -06:00
John Alanbrook
b86cd042fc vm unit tests 2026-02-05 10:21:16 -06:00
John Alanbrook
8b7af0c22a vm bytecode output 2026-02-05 10:14:14 -06:00
John Alanbrook
f71f6a296b register vm 2026-02-05 06:55:45 -06:00
John Alanbrook
9bd764b11b add go 2026-02-05 03:10:06 -06:00
John Alanbrook
058cdfd2e4 groundwork for vm 2026-02-05 02:59:16 -06:00
John Alanbrook
1ef837c6ff rm bound function stuff 2026-02-05 02:36:14 -06:00
John Alanbrook
cd21de3d70 rm realm concept on function 2026-02-05 02:33:50 -06:00
John Alanbrook
a98faa4dbb debugging 2026-02-05 02:27:26 -06:00
John Alanbrook
08559234c4 fix closures 2026-02-05 02:07:18 -06:00
John Alanbrook
c3dc27eac6 machine code 2026-02-04 23:45:51 -06:00
John Alanbrook
7170a9c7eb ast 2026-02-04 22:20:57 -06:00
John Alanbrook
a08ee50f84 serializable bytecode 2026-02-04 20:57:44 -06:00
John Alanbrook
ed7dd91c3f rm global 2026-02-04 18:57:45 -06:00
John Alanbrook
3abe20fee0 merge 2026-02-04 18:38:46 -06:00
John Alanbrook
a92a96118e remove eval parser; consolidate addintrinsic 2026-02-04 17:15:03 -06:00
John Alanbrook
4e407fe301 migrate nota, wota into quickjs.c 2026-02-04 17:03:48 -06:00
John Alanbrook
ab74cdc173 merge warningfix 2026-02-04 16:17:52 -06:00
John Alanbrook
2c9d039271 massive cleanup 2026-02-04 14:26:17 -06:00
John Alanbrook
80d314c58f Merge templatefix branch
Use PPretext for parser string building to avoid GC issues during parsing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:21:25 -06:00
John Alanbrook
611fba2b6f fix regexp parsing 2026-02-04 14:19:39 -06:00
John Alanbrook
f5fad52d47 Rewrite template literals with OP_format_template
Replace complex template literal handling with a simple format-based
approach. Template literals like `hello ${x}` now compile to:
  <push x>
  OP_format_template expr_count=1, cpool_idx=N
where cpool[N] = "hello {0}"

The opcode handler parses the format string, substitutes {N} placeholders
with stringified stack values, and produces the result string.

Key implementation details:
- Uses PPretext (parser pretext) with pjs_malloc to avoid GC issues
- Re-reads b->cpool[cpool_idx] after any GC-triggering operation
- Opcode layout is u16 expr_count followed by u32 cpool_idx - the u16
  must come first because compute_stack_size reads the pop count from
  position 1 for npop_u16 format opcodes

Removed:
- OP_template_concat opcode and handler
- Tagged template literal support (users can use format() directly)
- FuncCallType enum (FUNC_CALL_TEMPLATE case no longer needed)
- Complex template object creation logic in js_parse_template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:18:02 -06:00
John Alanbrook
2fc7d333ad new environment tact for engine 2026-02-04 14:12:57 -06:00
John Alanbrook
d4635f2a75 remove unused vars, fix warnings 2026-02-04 13:49:43 -06:00
146 changed files with 26391 additions and 5305 deletions

View File

@@ -382,13 +382,13 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
JSValue js_miniz_use(JSContext *js)
{
JS_NewClassID(&js_reader_class_id);
JS_NewClass(JS_GetRuntime(js), js_reader_class_id, &js_reader_class);
JS_NewClass(js, js_reader_class_id, &js_reader_class);
JSValue reader_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto);
JS_NewClassID(&js_writer_class_id);
JS_NewClass(JS_GetRuntime(js), js_writer_class_id, &js_writer_class);
JS_NewClass(js, js_writer_class_id, &js_writer_class);
JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto);

View File

@@ -39,20 +39,22 @@ JSC_CCALL(os_calc_mem,
JSC_SSCALL(os_eval,
if (!str2) return JS_ThrowReferenceError(js, "Second argument should be the script.");
if (!str) return JS_ThrowReferenceError(js, "First argument should be the name of the script.");
ret = JS_Eval(js,str2,strlen(str2),str, 0);
JSValue bytecode = JS_Compile(js, str2, strlen(str2), str);
if (JS_IsException(bytecode)) return bytecode;
ret = JS_Integrate(js, bytecode, JS_NULL);
)
// Compile a string of JavaScript code into a function object.
JSC_SSCALL(js_compile,
if (!str2) return JS_ThrowReferenceError(js, "Second argument should be the script.");
if (!str) return JS_ThrowReferenceError(js, "First argument should be the name of the script.");
ret = JS_Eval(js, str2, strlen(str2), str, JS_EVAL_FLAG_COMPILE_ONLY | JS_EVAL_FLAG_BACKTRACE_BARRIER);
ret = JS_Compile(js, str2, strlen(str2), str);
)
// Evaluate a function object in the current QuickJS context.
JSC_CCALL(js_eval_compile,
JS_DupValue(js,argv[0]);
ret = JS_EvalFunction(js, argv[0]);
// Link compiled bytecode with environment and execute.
JSC_CCALL(js_integrate,
JSValue env = (argc > 1 && !JS_IsNull(argv[1])) ? argv[1] : JS_NULL;
ret = JS_Integrate(js, argv[0], env);
)
// Compile a function object into a bytecode blob.
@@ -92,7 +94,7 @@ static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(os, max_stacksize, 1),
MIST_FUNC_DEF(os, eval, 2),
MIST_FUNC_DEF(js, compile, 2),
MIST_FUNC_DEF(js, eval_compile, 1),
MIST_FUNC_DEF(js, integrate, 2),
MIST_FUNC_DEF(js, compile_blob, 1),
MIST_FUNC_DEF(js, compile_unblob, 1),
MIST_FUNC_DEF(js, disassemble, 1),

287
docs/pitcode.md Normal file
View File

@@ -0,0 +1,287 @@
# Pitmachine
A pitmachine is an abstract register machine for executing pitcode.
The pitmachine assembly language is JSON. The instructions field contains an array of labels and instructions. Labels are the targets of branch instructions. They are simple text. Slots are elements in an activation frame. They are designated by a small positive integer. Slots are the general registers of the pitmachine. Slots hold the arguments, variables, and temporaries of a function invocation.
{
"name": "program_name",
"data": {⸳⸳⸳},
"source": "...",
The labels record associates labels with tokens for use by debuggers.
"labels": {
"entry": token,
"beyond": token,
...
},
The instructions array is a list of instructions and labels.
instructions: [
A statement label:
"entry",
go to beyond:
["jump", "beyond"],
assign slot 8: pi / 2
["access", 13, {"kind": "name", "name": "pi", "make": "intrinsic", ⸳⸳⸳}],
["int", 14, 2],
["divide", 8, 13, 14],
⸳⸳⸳
"beyond"
⸳⸳⸳
]
}
## Pitcode instructions
This is a register based machine.
### Arithmetic
Arithmetic instructions perform operations on numbers. All other cases disrupt (except add, which is polymorphic).
"add", dest, left, right // dest, left, and right are numbers designating slots in the current activation frame. Also works on texts (concatenation).
"subtract", dest, left, right
"multiply", dest, left, right
"divide", dest, left, right
"integer_divide", dest, left, right
"modulo", dest, left, right
"remainder", dest, left, right
"max", dest, left, right
"min", dest, left, right
"abs", dest, right
"neg", dest, right
"sign", dest, right
"fraction", dest, right
"integer", dest, right
"ceiling", dest, right, place
"floor", dest, right, place
"round", dest, right, place
"trunc", dest, right, place
### Text
Text instructions perform operations on texts. All other cases disrupt.
"concat", dest, left, right // concat two texts
"concat_space", dest, left, right
"character", dest, right
"codepoint", dest, right
"length", dest, right
"lower", dest, right
"upper", dest, right
"append", pretext, right
Append the right text to the pretext, forwarding and growing its capacity if necessary.
### Comparison
Comparison instructions perform operations on texts or numbers. All other cases disrupt.
"eq", dest, left, right
"ne", dest, left, right
"lt", dest, left, right
"le", dest, left, right
"gt", dest, left, right
"ge", dest, left, right
### Logical
"not", dest, right
### Bitwise
Bitwise instructions convert operands to 32-bit integers. Non-numbers disrupt.
"bitand", dest, left, right
"bitor", dest, left, right
"bitxor", dest, left, right
"bitnot", dest, right
"shl", dest, left, right
"shr", dest, left, right
"ushr", dest, left, right
### Function
"frame", dest, func, nr_args
Prepare to invoke the func object. If the nr_args is too large, disrupt. Allocate the new activation frame. Put the current frame pointer into it.
"goframe", dest, func, nr_args
Same as frame, except that the current frame is reused if it is large enough.
"invoke", frame
Store the next instruction address in the current frame. Make the new frame the current frame. Jump to the entry point.
"goinvoke", frame
"apply", func, array
"return", value
"return_value", dest
"setarg", frame, slot, value // set the slot of frame to value
### Branching
"jump", label
"jump_true", slot, label
If the value in the slot is true, jump to the label. Otherwise, continue with the next instruction.
"jump_false": slot, label
If the value in the slot is false, jump to the label. Otherwise, continue with the next instruction.
"jump_null": slot, label
"jump_empty": slot, label
"wary_true", slot, label
If the value in the slot is true, jump to the label. If the value is false, continue with the next instruction. Otherwise disrupt because of a type error.
"wary_false": slot, label
If the value in the slot is false, jump to the label. If the value is true, continue with the next instruction. Otherwise disrupt because of a type error.
### Sensory
Does the right slot contain a value of the indicated type?
"array?", dest, right
"blob?", dest, right
"character?", dest, right
"data?", dest, right
"digit?", dest, right
"false?", dest, right
"fit?", dest, right
"function?", dest, right
"integer?", dest, right
"letter?", dest, right
"logical?", dest, right
"null?", dest, right
"pattern?", dest, right
"record?", dest, right
"stone?", dest, right
"text?", dest, right
"true?", dest, right
"upper?", dest, right
"whitespace?", dest, right
### Potpourri
"stone", dest, right // stone an object
"true", dest
"false", dest
"null", dest
"move", dest, right
"int", dest, small_int
"access", dest, literal
This is used to access values (numbers, texts) from the program's immutable memory. The literal is a number or text.
"load", dest, object, subscript
This is used to load values from records and arrays.
"store", dest, object, subscript
This is used to store values into records and arrays.
"delete", object, subscript
used to delete a field from a record.
"get", dest, slot, level
This is used to get values from slots in outer frames.
"put", value, slot, level
This is used to store values into slots in outer frames.
"push", array, slot
Append to a mutable array.
"pop", dest, array
Remove the last element of a mutable array, putting the element into dest.
"disrupt"
### Make
"array", dest, nr_elements
"blob", dest, nr_bits
"function", dest, code_name_text
"pretext", dest, nr_characters
A pretext must be converted to text by stone before it can leave the function scope.
"record", dest, nr_elements

View File

@@ -1,24 +1,21 @@
(function engine() {
var _cell = globalThis.cell
delete globalThis.cell
var ACTORDATA = _cell.hidden.actorsym
// Hidden vars (os, actorsym, init, core_path) come from env
var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__'
var hidden = _cell.hidden
var os = hidden.os;
_cell.os = null
var _cell = {}
var need_stop = false
var dylib_ext
_cell.id ??= "newguy"
var cases = {
Windows: '.dll',
macOS: '.dylib',
Linux: '.so'
}
switch(os.platform()) {
case 'Windows': dylib_ext = '.dll'; break;
case 'macOS': dylib_ext = '.dylib'; break;
case 'Linux': dylib_ext = '.so'; break;
}
print(os.platform())
dylib_ext = cases[os.platform()]
var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce'
@@ -28,8 +25,7 @@ function use_embed(name) {
return load_internal("js_" + name + "_use")
}
globalThis.logical = function(val1)
{
function logical(val1) {
if (val1 == 0 || val1 == false || val1 == "false" || val1 == null)
return false;
if (val1 == 1 || val1 == true || val1 == "true")
@@ -37,36 +33,42 @@ globalThis.logical = function(val1)
return null;
}
globalThis.some = function(arr, pred) {
function some(arr, pred) {
return find(arr, pred) != null
}
globalThis.every = function(arr, pred) {
function every(arr, pred) {
return find(arr, x => not(pred(x))) == null
}
globalThis.starts_with = function(str, prefix) {
function starts_with(str, prefix) {
return search(str, prefix) == 0
}
globalThis.ends_with = function(str, suffix) {
function ends_with(str, suffix) {
return search(str, suffix, -length(suffix)) != null
}
print("engine: loading js")
var js = use_embed('js')
print("engine: loading fd")
var fd = use_embed('fd')
print("engine: loaded fd")
print("engine: getting home")
// Get the shop path from HOME environment
var home = os.getenv('HOME') || os.getenv('USERPROFILE')
if (!home) {
throw Error('Could not determine home directory')
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)) {
throw Error('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.')
os.print('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.\n')
os.exit(1)
}
var use_cache = {}
@@ -78,37 +80,51 @@ function use_core(path) {
if (use_cache[cache_key])
return use_cache[cache_key];
print(`engine: use_core('${path}') - embed`)
var sym = use_embed(replace(path, '/', '_'))
// Core scripts are in packages/core/
var file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(file_path)) {
print(`engine: use_core('${path}') - loading script`)
var script_blob = fd.slurp(file_path)
var script = text(script_blob)
var mod = `(function setup_module(use){${script}})`
var fn = js.eval('core:' + path, mod)
var fn = mach_eval('core:' + path, mod)
if (fn == null) {
print(`engine: use_core('${path}') - mach_eval returned null!`)
return null
}
print(`engine: use_core('${path}') - calling (fn type: ${typeof(fn)})`)
var result = call(fn,sym, [use_core])
print(`engine: use_core('${path}') - done`)
use_cache[cache_key] = result;
return result;
}
print(`engine: use_core('${path}') - using embed directly`)
use_cache[cache_key] = sym;
return sym;
}
print("engine: loading blob")
var blob = use_core('blob')
print("engine: loaded blob")
globalThis.actor = function()
{
function actor() {
}
print("engine: loading actor")
var actor_mod = use_core('actor')
print("engine: loading wota")
var wota = use_core('wota')
print("engine: loading nota")
var nota = use_core('nota')
print("engine: loaded nota")
globalThis.is_actor = function(value) {
function is_actor(value) {
return is_object(value) && value[ACTORDATA]
}
@@ -138,36 +154,31 @@ function console_rec(line, file, msg) {
// time: [${time.text("mb d yyyy h:nn:ss")}]
}
globalThis.log = function(name, args) {
function log(name, args) {
var caller = caller_data(1)
var msg = args[0]
switch(name) {
case 'console':
os.print(console_rec(caller.line, caller.file, msg))
break
case 'error':
msg = msg ?? Error()
if (is_proto(msg, Error))
msg = msg.name + ": " + msg.message + "\n" + msg.stack
os.print(console_rec(caller.line, caller.file, msg))
break
case 'system':
msg = "[SYSTEM] " + msg
os.print(console_rec(caller.line, caller.file, msg))
break
default:
log.console(`unknown log type: ${name}`)
break
if (name == 'console') {
os.print(console_rec(caller.line, caller.file, msg))
} else if (name == '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))
} else if (name == 'system') {
msg = "[SYSTEM] " + msg
os.print(console_rec(caller.line, caller.file, msg))
} else {
log.console(`unknown log type: ${name}`)
}
}
function disrupt(err)
function actor_die(err)
{
if (is_function(err.toString)) {
os.print(err.toString())
os.print("\n")
os.print(err.stack)
if (err && is_function(err.toString)) {
os.print(err.toString())
os.print("\n")
if (err.stack) os.print(err.stack)
}
if (overling) {
@@ -194,16 +205,20 @@ function disrupt(err)
log.console(err.stack)
}
actor_mod.disrupt()
actor_mod["disrupt"]()
}
actor_mod.on_exception(disrupt)
print("engine: setting up actor_die")
actor_mod.on_exception(actor_die)
_cell.args = _cell.hidden.init
_cell.args ??= {}
_cell.id ??= "newguy"
print("engine: setting cell args")
print(`engine: init is ${init}`)
_cell.args = init != null ? init : {}
print("engine: args set")
_cell.id = "newguy"
print("engine: id set")
function create_actor(desc = {id:guid()}) {
var actor = {}
@@ -211,31 +226,61 @@ function create_actor(desc = {id:guid()}) {
return actor
}
print("engine: creating $_")
var $_ = {}
print("engine: creating self actor")
$_.self = create_actor()
print("engine: setting os props")
os.use_cache = use_cache
os.global_shop_path = shop_path
os.$_ = $_
print("engine: os props set")
print("engine: loading shop")
var shop = use_core('internal/shop')
print("engine: loaded shop, loading json")
var json = use_core('json')
var time = use_core('time')
print("engine: loading pronto")
var pronto = use_core('pronto')
globalThis.fallback = pronto.fallback
globalThis.parallel = pronto.parallel
globalThis.race = pronto.race
globalThis.sequence = pronto.sequence
print("engine: loaded pronto")
var fallback = pronto.fallback
var parallel = pronto.parallel
var race = pronto.race
var sequence = pronto.sequence
// Create runtime environment for modules
var runtime_env = {
logical: logical,
some: some,
every: every,
starts_with: starts_with,
ends_with: ends_with,
actor: actor,
is_actor: is_actor,
log: log,
send: send,
fallback: fallback,
parallel: parallel,
race: race,
sequence: sequence
}
// Pass to os for shop to access
os.runtime_env = runtime_env
$_.time_limit = function(requestor, seconds)
{
if (!pronto.is_requestor(requestor))
throw Error('time_limit: first argument must be a requestor');
if (!is_number(seconds) || seconds <= 0)
throw Error('time_limit: seconds must be a positive number');
if (!pronto.is_requestor(requestor)) {
log.error('time_limit: first argument must be a requestor')
disrupt
}
if (!is_number(seconds) || seconds <= 0) {
log.error('time_limit: seconds must be a positive number')
disrupt
}
return function time_limit_requestor(callback, value) {
pronto.check_callback(callback, 'time_limit')
var finished = false
@@ -250,7 +295,14 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = null
}
if (requestor_cancel) {
try { pronto.requestor_cancel(reason) } catch (_) {}
requestor_cancel(reason)
requestor_cancel = null
}
}
function safe_cancel_requestor(reason) {
if (requestor_cancel) {
requestor_cancel(reason)
requestor_cancel = null
}
}
@@ -258,15 +310,12 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = $_.delay(function() {
if (finished) return
def reason = make_reason(factory, 'Timeout.', seconds)
if (requestor_cancel) {
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
safe_cancel_requestor(reason)
finished = true
callback(null, reason)
}, seconds)
try {
function do_request() {
requestor_cancel = requestor(function(val, reason) {
if (finished) return
finished = true
@@ -276,16 +325,14 @@ $_.time_limit = function(requestor, seconds)
}
callback(val, reason)
}, value)
} catch (ex) {
cancel(ex)
callback(null, ex)
} disruption {
cancel(Error('requestor failed'))
callback(null, Error('requestor failed'))
}
do_request()
return function(reason) {
if (requestor_cancel) {
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
safe_cancel_requestor(reason)
}
}
}
@@ -398,52 +445,54 @@ var portal_fn = null
// takes a function input value that will eventually be called with the current time in number form.
$_.portal = function(fn, port) {
if (portal) throw Error(`Already started a portal listening on ${portal.port}`)
if (!port) throw Error("Requires a valid port.")
if (portal) {
log.error(`Already started a portal listening on ${portal.port}`)
disrupt
}
if (!port) {
log.error("Requires a valid port.")
disrupt
}
log.system(`starting a portal on port ${port}`)
portal = enet.create_host({address: "any", port})
portal_fn = fn
}
function handle_host(e) {
switch (e.type) {
case "connect":
log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`)
peers[`${e.peer.address}:${e.peer.port}`] = e.peer
var queue = peer_queue.get(e.peer)
if (queue) {
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
log.system(`sent ${msg} out of queue`)
peer_queue.delete(e.peer)
}
break
case "disconnect":
if (e.type == "connect") {
log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`)
peers[`${e.peer.address}:${e.peer.port}`] = e.peer
var queue = peer_queue.get(e.peer)
if (queue) {
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
log.system(`sent ${msg} out of queue`)
peer_queue.delete(e.peer)
arrfor(array(peers), function(id, index) {
if (peers[id] == e.peer) delete peers[id]
}
} else if (e.type == "disconnect") {
peer_queue.delete(e.peer)
arrfor(array(peers), function(id, index) {
if (peers[id] == e.peer) delete peers[id]
})
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
} else if (e.type == "receive") {
var data = nota.decode(e.data)
if (data.replycc && !data.replycc.address) {
data.replycc[ACTORDATA].address = e.peer.address
data.replycc[ACTORDATA].port = e.peer.port
}
function populate_actor_addresses(obj) {
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])
})
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
break
case "receive":
var data = nota.decode(e.data)
if (data.replycc && !data.replycc.address) {
data.replycc[ACTORDATA].address = e.peer.address
data.replycc[ACTORDATA].port = e.peer.port
}
function populate_actor_addresses(obj) {
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)
break
}
if (data.data) populate_actor_addresses(data.data)
turn(data)
}
}
@@ -477,10 +526,14 @@ $_.stop = function stop(actor) {
need_stop = true
return
}
if (!is_actor(actor))
throw Error('Can only call stop on an actor.')
if (is_null(underlings[actor[ACTORDATA].id]))
throw Error('Can only call stop on an underling or self.')
if (!is_actor(actor)) {
log.error('Can only call stop on an actor.')
disrupt
}
if (is_null(underlings[actor[ACTORDATA].id])) {
log.error('Can only call stop on an underling or self.')
disrupt
}
sys_msg(actor, {kind:"stop"})
}
@@ -517,20 +570,22 @@ function actor_prep(actor, send) {
// Send a message immediately without queuing
function actor_send_immediate(actor, send) {
try {
actor_send(actor, send);
} catch (err) {
log.error("Failed to send immediate message:", err);
}
actor_send(actor, send)
}
function actor_send(actor, message) {
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
return
if (!is_actor(actor) && !is_actor(actor.replycc)) throw Error(`Must send to an actor object. Attempted send to ${actor}`)
if (!is_object(message)) throw Error('Must send an object record.')
if (!is_actor(actor) && !is_actor(actor.replycc)) {
log.error(`Must send to an actor object. Attempted send to ${actor}`)
disrupt
}
if (!is_object(message)) {
log.error('Must send an object record.')
disrupt
}
// message to self
if (actor[ACTORDATA].id == _cell.id) {
@@ -573,12 +628,10 @@ function actor_send(actor, message) {
// Holds all messages queued during the current turn.
var message_queue = []
var need_stop = false
function send_messages() {
function send_messages() {
// if we've been flagged to stop, bail out before doing anything
if (need_stop) {
disrupt()
actor_die()
message_queue = []
return
}
@@ -597,21 +650,28 @@ var need_stop = false
var replies = {}
globalThis.send = function send(actor, message, reply) {
if (!is_object(actor))
throw Error(`Must send to an actor object. Provided: ${actor}`);
if (!is_object(message))
throw Error('Message must be an object')
var send = {type:"user", data: message}
function send(actor, message, reply) {
if (!is_object(actor)) {
log.error(`Must send to an actor object. Provided: ${actor}`)
disrupt
}
if (!is_object(message)) {
log.error('Message must be an object')
disrupt
}
var send_msg = {type:"user", data: message}
var target = actor
if (actor[HEADER] && actor[HEADER].replycc) {
var header = actor[HEADER]
if (!header.replycc || !is_actor(header.replycc))
throw Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
if (!header.replycc || !is_actor(header.replycc)) {
log.error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
disrupt
}
actor = header.replycc
send.return = header.reply
target = header.replycc
send_msg.return = header.reply
}
if (reply) {
@@ -623,12 +683,12 @@ globalThis.send = function send(actor, message, reply) {
delete replies[id]
}
}, REPLYTIMEOUT)
send.reply = id
send.replycc = $_.self
send_msg.reply = id
send_msg.replycc = $_.self
}
// Instead of sending immediately, queue it
actor_prep(actor,send);
actor_prep(target, send_msg);
}
stone(send)
@@ -659,7 +719,7 @@ overling = _cell.args.overling
$_.overling = overling
root = _cell.args.root
root ??= $_.self
if (root == null) root = $_.self
if (overling) {
$_.couple(overling) // auto couple to overling
@@ -695,36 +755,35 @@ function handle_actor_disconnect(id) {
delete greeters[id]
}
log.system(`actor ${id} disconnected`)
if (!is_null(couplings[id])) disrupt("coupled actor died") // couplings now disrupts instead of stop
if (!is_null(couplings[id])) actor_die("coupled actor died") // couplings now disrupts instead of stop
}
function handle_sysym(msg)
{
var from
switch(msg.kind) {
case 'stop':
disrupt("got stop message")
break
case 'underling':
from = msg.from
var greeter = greeters[from[ACTORDATA].id]
if (greeter) greeter(msg.message)
if (msg.message.type == 'disrupt')
delete underlings[from[ACTORDATA].id]
break
case 'contact':
if (portal_fn) {
var letter2 = msg.data
letter2[HEADER] = msg
delete msg.data
portal_fn(letter2)
} else throw Error('Got a contact message, but no portal is established.')
break
case 'couple': // from must be notified when we die
from = msg.from
underlings[from[ACTORDATA].id] = true
log.system(`actor ${from} is coupled to me`)
break
if (msg.kind == 'stop') {
actor_die("got stop message")
} else if (msg.kind == 'underling') {
from = msg.from
var greeter = greeters[from[ACTORDATA].id]
if (greeter) greeter(msg.message)
if (msg.message.type == 'disrupt')
delete underlings[from[ACTORDATA].id]
} else if (msg.kind == 'contact') {
if (portal_fn) {
var letter2 = msg.data
letter2[HEADER] = msg
delete msg.data
portal_fn(letter2)
} else {
log.error('Got a contact message, but no portal is established.')
disrupt
}
} else if (msg.kind == 'couple') {
// from must be notified when we die
from = msg.from
underlings[from[ACTORDATA].id] = true
log.system(`actor ${from} is coupled to me`)
}
}
@@ -734,30 +793,27 @@ function handle_message(msg) {
return
}
switch (msg.type) {
case "user":
var letter = msg.data // what the sender really sent
_ObjectDefineProperty(letter, HEADER, {
value: msg, enumerable: false
})
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
value: { reply: msg.reply }, enumerable: false
})
if (msg.return) {
var fn = replies[msg.return]
if (fn) fn(letter)
delete replies[msg.return]
return
}
if (receive_fn) receive_fn(letter)
if (msg.type == "user") {
var letter = msg.data // what the sender really sent
_ObjectDefineProperty(letter, HEADER, {
value: msg, enumerable: false
})
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
value: { reply: msg.reply }, enumerable: false
})
if (msg.return) {
var fn = replies[msg.return]
if (fn) fn(letter)
delete replies[msg.return]
return
case "stopped":
handle_actor_disconnect(msg.id)
break
}
if (receive_fn) receive_fn(letter)
} else if (msg.type == "stopped") {
handle_actor_disconnect(msg.id)
}
};
}
function enet_check()
{
@@ -782,10 +838,10 @@ if (!locator) {
locator = shop.resolve_locator(_cell.args.program + ".ce", pkg)
}
if (!locator)
throw Error(`Main program ${_cell.args.program} could not be found`)
stone(globalThis)
if (!locator) {
os.print(`Main program ${_cell.args.program} could not be found\n`)
os.exit(1)
}
$_.clock(_ => {
// Get capabilities for the main program
@@ -810,7 +866,6 @@ $_.clock(_ => {
var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env])
if (val)
throw Error('Program must not return anything');
log.error('Program must not return anything')
disrupt
})
})()

View File

@@ -75,7 +75,11 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
JSValue js_kim_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs));
return mod;
JSGCRef mod_ref;
JS_PushGCRef(js, &mod_ref);
mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_kim_funcs, countof(js_kim_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
}

View File

@@ -1,394 +0,0 @@
#include "cell.h"
#include "cell_internal.h"
#define NOTA_IMPLEMENTATION
#include "nota.h"
typedef struct NotaEncodeContext {
JSContext *ctx;
JSValue visitedStack;
NotaBuffer nb;
int cycle;
JSValue replacer;
} NotaEncodeContext;
static void nota_stack_push(NotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visitedStack);
JS_SetPropertyInt64(ctx, enc->visitedStack, len, JS_DupValue(ctx, val));
}
static void nota_stack_pop(NotaEncodeContext *enc)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visitedStack);
JS_SetPropertyStr(ctx, enc->visitedStack, "length", JS_NewUint32(ctx, len - 1));
}
static int nota_stack_has(NotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visitedStack);
for (int i = 0; i < len; i++) {
JSValue elem = JS_GetPropertyUint32(ctx, enc->visitedStack, i);
if (JS_IsObject(elem) && JS_IsObject(val)) {
if (JS_StrictEq(ctx, elem, val)) {
JS_FreeValue(ctx, elem);
return 1;
}
}
JS_FreeValue(ctx, elem);
}
return 0;
}
static JSValue apply_replacer(NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
if (JS_IsNull(enc->replacer)) return JS_DupValue(enc->ctx, val);
JSValue args[2] = { JS_DupValue(enc->ctx, key), JS_DupValue(enc->ctx, val) };
JSValue result = JS_Call(enc->ctx, enc->replacer, holder, 2, args);
JS_FreeValue(enc->ctx, args[0]);
JS_FreeValue(enc->ctx, args[1]);
if (JS_IsException(result)) return JS_DupValue(enc->ctx, val);
return result;
}
char *js_do_nota_decode(JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) {
int type = nota_type(nota);
JSValue ret2;
long long n;
double d;
int b;
char *str;
uint8_t *blob;
switch(type) {
case NOTA_BLOB:
nota = nota_read_blob(&n, (char**)&blob, nota);
*tmp = js_new_blob_stoned_copy(js, blob, n);
free(blob);
break;
case NOTA_TEXT:
nota = nota_read_text(&str, nota);
*tmp = JS_NewString(js, str);
free(str);
break;
case NOTA_ARR:
nota = nota_read_array(&n, nota);
*tmp = JS_NewArray(js);
for (int i = 0; i < n; i++) {
nota = js_do_nota_decode(js, &ret2, nota, *tmp, JS_NewInt32(js, i), reviver);
JS_SetPropertyInt64(js, *tmp, i, ret2);
}
break;
case NOTA_REC:
nota = nota_read_record(&n, nota);
*tmp = JS_NewObject(js);
for (int i = 0; i < n; i++) {
nota = nota_read_text(&str, nota);
JSValue prop_key = JS_NewString(js, str);
nota = js_do_nota_decode(js, &ret2, nota, *tmp, prop_key, reviver);
JS_SetPropertyStr(js, *tmp, str, ret2);
JS_FreeValue(js, prop_key);
free(str);
}
break;
case NOTA_INT:
nota = nota_read_int(&n, nota);
*tmp = JS_NewInt64(js, n);
break;
case NOTA_SYM:
nota = nota_read_sym(&b, nota);
if (b == NOTA_PRIVATE) {
JSValue inner;
nota = js_do_nota_decode(js, &inner, nota, holder, JS_NULL, reviver);
JSValue obj = JS_NewObject(js);
cell_rt *crt = JS_GetContextOpaque(js);
// JS_SetProperty(js, obj, crt->actor_sym, inner);
*tmp = obj;
} else {
switch(b) {
case NOTA_NULL: *tmp = JS_NULL; break;
case NOTA_FALSE: *tmp = JS_NewBool(js, 0); break;
case NOTA_TRUE: *tmp = JS_NewBool(js, 1); break;
default: *tmp = JS_NULL; break;
}
}
break;
default:
case NOTA_FLOAT:
nota = nota_read_float(&d, nota);
*tmp = JS_NewFloat64(js, d);
break;
}
if (!JS_IsNull(reviver)) {
JSValue args[2] = { JS_DupValue(js, key), JS_DupValue(js, *tmp) };
JSValue revived = JS_Call(js, reviver, holder, 2, args);
JS_FreeValue(js, args[0]);
JS_FreeValue(js, args[1]);
if (!JS_IsException(revived)) {
JS_FreeValue(js, *tmp);
*tmp = revived;
} else {
JS_FreeValue(js, revived);
}
}
return nota;
}
static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) {
JSContext *ctx = enc->ctx;
JSValue replaced = apply_replacer(enc, holder, key, val);
int tag = JS_VALUE_GET_TAG(replaced);
switch (tag) {
case JS_TAG_INT:
case JS_TAG_FLOAT64: {
double d;
JS_ToFloat64(ctx, &d, replaced);
nota_write_number(&enc->nb, d);
break;
}
case JS_TAG_STRING: {
const char *str = JS_ToCString(ctx, replaced);
nota_write_text(&enc->nb, str);
JS_FreeCString(ctx, str);
break;
}
case JS_TAG_BOOL:
if (JS_VALUE_GET_BOOL(replaced)) nota_write_sym(&enc->nb, NOTA_TRUE);
else nota_write_sym(&enc->nb, NOTA_FALSE);
break;
case JS_TAG_NULL:
nota_write_sym(&enc->nb, NOTA_NULL);
break;
case JS_TAG_PTR: {
if (js_is_blob(ctx, replaced)) {
size_t buf_len;
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
if (buf_data == -1) {
JS_FreeValue(ctx, replaced);
return; // JS_EXCEPTION will be handled by caller
}
nota_write_blob(&enc->nb, (unsigned long long)buf_len * 8, (const char*)buf_data);
break;
}
if (JS_IsArray(replaced)) {
if (nota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;
}
nota_stack_push(enc, replaced);
int arr_len = JS_ArrayLength(ctx, replaced);
nota_write_array(&enc->nb, arr_len);
for (int i = 0; i < arr_len; i++) {
JSValue elem_val = JS_GetPropertyUint32(ctx, replaced, i);
JSValue elem_key = JS_NewInt32(ctx, i);
nota_encode_value(enc, elem_val, replaced, elem_key);
JS_FreeValue(ctx, elem_val);
JS_FreeValue(ctx, elem_key);
}
nota_stack_pop(enc);
break;
}
cell_rt *crt = JS_GetContextOpaque(ctx);
// JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
JSValue adata = JS_NULL;
if (!JS_IsNull(adata)) {
nota_write_sym(&enc->nb, NOTA_PRIVATE);
nota_encode_value(enc, adata, replaced, JS_NULL);
JS_FreeValue(ctx, adata);
break;
}
JS_FreeValue(ctx, adata);
if (nota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;
}
nota_stack_push(enc, replaced);
JSValue to_json = JS_GetPropertyStr(ctx, replaced, "toJSON");
if (JS_IsFunction(to_json)) {
JSValue result = JS_Call(ctx, to_json, replaced, 0, NULL);
JS_FreeValue(ctx, to_json);
if (!JS_IsException(result)) {
nota_encode_value(enc, result, holder, key);
JS_FreeValue(ctx, result);
} else {
nota_write_sym(&enc->nb, NOTA_NULL);
}
nota_stack_pop(enc);
break;
}
JS_FreeValue(ctx, to_json);
JSValue keys = JS_GetOwnPropertyNames(ctx, replaced);
if (JS_IsException(keys)) {
nota_write_sym(&enc->nb, NOTA_NULL);
nota_stack_pop(enc);
break;
}
int64_t plen64;
if (JS_GetLength(ctx, keys, &plen64) < 0) {
JS_FreeValue(ctx, keys);
nota_write_sym(&enc->nb, NOTA_NULL);
nota_stack_pop(enc);
break;
}
uint32_t plen = (uint32_t)plen64;
uint32_t non_function_count = 0;
for (uint32_t i = 0; i < plen; i++) {
JSValue key = JS_GetPropertyUint32(ctx, keys, i);
JSValue prop_val = JS_GetProperty(ctx, replaced, key);
if (!JS_IsFunction(prop_val)) non_function_count++;
JS_FreeValue(ctx, prop_val);
JS_FreeValue(ctx, key);
}
nota_write_record(&enc->nb, non_function_count);
for (uint32_t i = 0; i < plen; i++) {
JSValue key = JS_GetPropertyUint32(ctx, keys, i);
JSValue prop_val = JS_GetProperty(ctx, replaced, key);
if (!JS_IsFunction(prop_val)) {
const char *prop_name = JS_ToCString(ctx, key);
nota_write_text(&enc->nb, prop_name ? prop_name : "");
nota_encode_value(enc, prop_val, replaced, key);
JS_FreeCString(ctx, prop_name);
}
JS_FreeValue(ctx, prop_val);
JS_FreeValue(ctx, key);
}
JS_FreeValue(ctx, keys);
nota_stack_pop(enc);
break;
}
default:
nota_write_sym(&enc->nb, NOTA_NULL);
break;
}
JS_FreeValue(ctx, replaced);
}
void *value2nota(JSContext *ctx, JSValue v) {
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visitedStack = JS_NewArray(ctx);
enc->cycle = 0;
enc->replacer = JS_NULL;
nota_buffer_init(&enc->nb, 128);
nota_encode_value(enc, v, JS_NULL, JS_NewString(ctx, ""));
if (enc->cycle) {
JS_FreeValue(ctx, enc->visitedStack);
nota_buffer_free(&enc->nb);
return NULL;
}
JS_FreeValue(ctx, enc->visitedStack);
void *data_ptr = enc->nb.data;
enc->nb.data = NULL;
nota_buffer_free(&enc->nb);
return data_ptr;
}
JSValue nota2value(JSContext *js, void *nota) {
if (!nota) return JS_NULL;
JSValue ret;
JSValue holder = JS_NewObject(js);
js_do_nota_decode(js, &ret, nota, holder, JS_NewString(js, ""), JS_NULL);
JS_FreeValue(js, holder);
return ret;
}
static JSValue js_nota_tostring(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
size_t len;
void *nota = js_get_blob_data(ctx, &len, this_val);
if (nota == (void*)-1) return JS_EXCEPTION;
if (!nota) return JS_NULL;
JSValue decoded;
JSValue holder = JS_NewObject(ctx);
js_do_nota_decode(ctx, &decoded, (char*)nota, holder, JS_NewString(ctx, ""), JS_NULL);
JS_FreeValue(ctx, holder);
JSValue global = JS_GetGlobalObject(ctx);
JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
JSValue stringify = JS_GetPropertyStr(ctx, json, "stringify");
JSValue args[3];
args[0] = decoded;
args[1] = JS_NULL;
args[2] = JS_NewInt32(ctx, 1);
JSValue result = JS_Call(ctx, stringify, json, 3, args);
JS_FreeValue(ctx, stringify);
JS_FreeValue(ctx, json);
JS_FreeValue(ctx, global);
JS_FreeValue(ctx, decoded);
JS_FreeValue(ctx, args[2]);
return result;
}
static JSValue js_nota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_ThrowTypeError(ctx, "nota.encode requires at least 1 argument");
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visitedStack = JS_NewArray(ctx);
enc->cycle = 0;
enc->replacer = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
nota_buffer_init(&enc->nb, 128);
nota_encode_value(enc, argv[0], JS_NULL, JS_NewString(ctx, ""));
if (enc->cycle) {
JS_FreeValue(ctx, enc->visitedStack);
nota_buffer_free(&enc->nb);
return JS_ThrowReferenceError(ctx, "Tried to encode something to nota with a cycle.");
}
JS_FreeValue(ctx, enc->visitedStack);
size_t total_len = enc->nb.size;
void *data_ptr = enc->nb.data;
JSValue ret = js_new_blob_stoned_copy(ctx, (uint8_t*)data_ptr, total_len);
nota_buffer_free(&enc->nb);
return ret;
}
static JSValue js_nota_decode(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
if (argc < 1) return JS_NULL;
size_t len;
unsigned char *nota = js_get_blob_data(js, &len, argv[0]);
if (nota == -1) return JS_EXCEPTION;
if (!nota) return JS_NULL;
JSValue reviver = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
JSValue ret;
JSValue holder = JS_NewObject(js);
js_do_nota_decode(js, &ret, (char*)nota, holder, JS_NewString(js, ""), reviver);
JS_FreeValue(js, holder);
return ret;
}
static const JSCFunctionListEntry js_nota_funcs[] = {
JS_CFUNC_DEF("encode", 1, js_nota_encode),
JS_CFUNC_DEF("decode", 1, js_nota_decode),
};
JSValue js_nota_use(JSContext *js) {
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry));
return export;
}

View File

@@ -577,9 +577,13 @@ static const JSCFunctionListEntry js_os_funcs[] = {
JSValue js_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id);
JS_NewClass(JS_GetRuntime(js), js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));
return mod;
JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSGCRef mod_ref;
JS_PushGCRef(js, &mod_ref);
mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
}

View File

@@ -172,7 +172,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
};
JSValue js_os_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs));
return mod;
JSGCRef mod_ref;
JS_PushGCRef(js, &mod_ref);
mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
}

View File

@@ -172,7 +172,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
};
JSValue js_os_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs));
return mod;
JSGCRef mod_ref;
JS_PushGCRef(js, &mod_ref);
mod_ref.val = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
JSValue result = mod_ref.val;
JS_PopGCRef(js, &mod_ref);
return result;
}

View File

@@ -1,5 +1,6 @@
print("shop: starting")
var toml = use('toml')
print("shop: loaded toml")
var json = use('json')
var fd = use('fd')
var http = use('http')
@@ -142,8 +143,10 @@ function package_in_shop(package) {
function abs_path_to_package(package_dir)
{
if (!fd.is_file(package_dir + '/cell.toml'))
throw Error('Not a valid package directory (no cell.toml): ' + package_dir)
if (!fd.is_file(package_dir + '/cell.toml')) {
log.error('Not a valid package directory (no cell.toml): ' + package_dir)
disrupt
}
var packages_prefix = get_packages_dir() + '/'
var core_dir = packages_prefix + core_package
@@ -175,14 +178,15 @@ function abs_path_to_package(package_dir)
return package_dir
// For local directories (e.g., linked targets), read the package name from cell.toml
try {
var toml_pkg = null
var read_toml = function() {
var content = text(fd.slurp(package_dir + '/cell.toml'))
var cfg = toml.decode(content)
if (cfg.package)
return cfg.package
} catch (e) {
// Fall through
}
toml_pkg = cfg.package
} disruption {}
read_toml()
if (toml_pkg) return toml_pkg
return null
}
@@ -299,12 +303,14 @@ Shop.resolve_package_info = function(pkg) {
// Verify if a package name is valid and return status
Shop.verify_package_name = function(pkg) {
if (!pkg) throw Error("Empty package name")
if (pkg == 'local') throw Error("local is not a valid package name")
if (pkg == 'core') throw Error("core is not a valid package name")
if (search(pkg, '://') != null)
throw Error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
if (!pkg) { log.error("Empty package name"); disrupt }
if (pkg == 'local') { log.error("local is not a valid package name"); disrupt }
if (pkg == 'core') { log.error("core is not a valid package name"); disrupt }
if (search(pkg, '://') != null) {
log.error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
disrupt
}
}
// Convert module package to download URL
@@ -379,7 +385,16 @@ Shop.get_script_capabilities = function(path) {
}
function inject_env(inject) {
// Start with runtime functions from engine
var env = {}
var rt = my$_.os ? my$_.os.runtime_env : null
if (rt) {
for (var k in rt) {
env[k] = rt[k]
}
}
// Add capability injections
for (var i = 0; i < length(inject); i++) {
var inj = inject[i]
var key = trim(inj, '$')
@@ -391,6 +406,17 @@ function inject_env(inject) {
function inject_bindings_code(inject) {
var lines = []
// Runtime function bindings
var runtime_fns = ['logical', 'some', 'every', 'starts_with', 'ends_with',
'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, '$')
@@ -421,29 +447,28 @@ ${script}
// Resolve module function, hashing it in the process
// path is the exact path to the script file
function resolve_mod_fn(path, pkg) {
if (!fd.is_file(path)) throw Error(`path ${path} is not a file`)
if (!fd.is_file(path)) { log.error(`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 script = script_form(path, content, file_pkg, inject);
var obj = pull_from_cache(stone(blob(script)))
if (obj) {
var fn = js.compile_unblob(obj)
return js.eval_compile(fn)
return js.integrate(fn, null)
}
// Compile name is just for debug/stack traces
// var compile_name = pkg ? pkg + ':' + path : 'local:' + path
var compile_name = path
var fn = js.compile(compile_name, script)
put_into_cache(stone(blob(script)), js.compile_blob(fn))
return js.eval_compile(fn)
return js.integrate(fn, null)
}
// given a path and a package context
@@ -557,32 +582,38 @@ Shop.open_package_dylib = function(pkg) {
var toml_path = pkg_dir + '/cell.toml'
if (fd.is_file(toml_path)) {
try {
var read_toml_disrupted = false
var do_read_toml = function() {
var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content)
if (cfg.dependencies) {
arrfor(array(cfg.dependencies), function(alias, i) {
var dep_pkg = cfg.dependencies[alias]
try {
var open_dep = function() {
Shop.open_package_dylib(dep_pkg)
} catch (dep_e) {
// Dependency dylib load failed, continue with others
}
} disruption {}
open_dep()
})
}
} catch (e) {
// Error reading toml, continue
} disruption {
read_toml_disrupted = true
}
do_read_toml()
}
var dl_path = get_lib_path(pkg)
if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) {
try {
var open_disrupted = false
var do_open = function() {
open_dls[dl_path] = os.dylib_open(dl_path)
} catch (e) {
} disruption {
open_disrupted = true
}
do_open()
if (open_disrupted) {
dylib_visited[pkg] = false
throw e
disrupt
}
}
}
@@ -817,14 +848,14 @@ function execute_module(info)
// C only
used = call_c_module(c_resolve)
} else {
throw Error(`Module ${info.path} could not be found`)
log.error(`Module ${info.path} could not be found`); disrupt
}
// if (is_function(used))
// throw Error('C module loader returned a function; did you forget to call it?')
if (!used)
throw Error(`Module ${info} returned null`)
log.error(`Module ${info} returned null`); disrupt
// stone(used)
return used
@@ -833,16 +864,20 @@ function execute_module(info)
function get_module(path, package_context) {
var info = resolve_module_info(path, package_context)
if (!info)
throw Error(`Module ${path} could not be found in ${package_context}`)
if (!info) {
log.error(`Module ${path} could not be found in ${package_context}`)
disrupt
}
return execute_module(info)
}
Shop.use = function use(path, package_context) {
var info = resolve_module_info(path, package_context)
if (!info)
throw Error(`Module ${path} could not be found in ${package_context}`)
if (!info) {
log.error(`Module ${path} could not be found in ${package_context}`)
disrupt
}
if (use_cache[info.cache_key])
return use_cache[info.cache_key]
@@ -870,13 +905,20 @@ function fetch_remote_hash(pkg) {
if (!api_url) return null
try {
var result = null
var fetch_disrupted = false
var do_fetch = function() {
var resp = http.fetch(api_url)
return Shop.extract_commit_hash(pkg, text(resp))
} catch (e) {
result = Shop.extract_commit_hash(pkg, text(resp))
} disruption {
fetch_disrupted = true
}
do_fetch()
if (fetch_disrupted) {
log.console("Warning: Could not check for updates for " + pkg)
return null
}
return result
}
// Download a zip for a package at a specific commit and cache it
@@ -890,14 +932,20 @@ function download_zip(pkg, commit_hash) {
return null
}
try {
var zip_blob = http.fetch(download_url)
var zip_blob = null
var dl_disrupted = false
var do_download = function() {
zip_blob = http.fetch(download_url)
fd.slurpwrite(cache_path, zip_blob)
return zip_blob
} catch (e) {
log.error("Download failed for " + pkg + ": " + e)
} disruption {
dl_disrupted = true
}
do_download()
if (dl_disrupted) {
log.error("Download failed for " + pkg)
return null
}
return zip_blob
}
// Get zip from cache, returns null if not cached
@@ -1008,8 +1056,7 @@ Shop.extract = function(pkg) {
var zip_blob = get_package_zip(pkg)
if (!zip_blob)
throw Error("No zip blob available for " + pkg)
if (!zip_blob) { log.error("No zip blob available for " + pkg); disrupt }
// Extract zip for remote package
install_zip(zip_blob, target_dir)
@@ -1094,7 +1141,7 @@ Shop.update = function(pkg) {
function install_zip(zip_blob, target_dir) {
var zip = miniz.read(zip_blob)
if (!zip) throw Error("Failed to read zip archive")
if (!zip) { log.error("Failed to read zip archive"); disrupt }
if (fd.is_link(target_dir)) fd.unlink(target_dir)
if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1)
@@ -1146,14 +1193,14 @@ Shop.get = function(pkg) {
if (!lock[pkg]) {
var info = Shop.resolve_package_info(pkg)
if (!info) {
throw Error("Invalid package: " + pkg)
log.error("Invalid package: " + pkg); disrupt
}
var commit = null
if (info != 'local') {
commit = fetch_remote_hash(pkg)
if (!commit) {
throw Error("Could not resolve commit for " + pkg)
log.error("Could not resolve commit for " + pkg); disrupt
}
}

View File

@@ -11,12 +11,13 @@ function is_valid_package(dir) {
// Get current package name from cell.toml or null
function get_current_package_name() {
if (!is_valid_package('.')) return null
try {
var pkg_name = 'local'
var do_load = function() {
var config = pkg.load_config(null)
return config.package || 'local'
} catch (e) {
return 'local'
}
if (config.package) pkg_name = config.package
} disruption {}
do_load()
return pkg_name
}
// Get the directory for a package

81
link.cm
View File

@@ -65,13 +65,18 @@ Link.load = function() {
link_cache = {}
return link_cache
}
try {
var load_disrupted = false
var do_load = function() {
var content = text(fd.slurp(path))
var cfg = toml.decode(content)
link_cache = cfg.links || {}
} catch (e) {
log.console("Warning: Failed to load link.toml: " + e)
} disruption {
load_disrupted = true
}
do_load()
if (load_disrupted) {
log.console("Warning: Failed to load link.toml")
link_cache = {}
}
return link_cache
@@ -90,14 +95,16 @@ Link.add = function(canonical, target, shop) {
// Validate canonical package exists in shop
var lock = shop.load_lock()
if (!lock[canonical]) {
throw Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
log.error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
disrupt
}
// Validate target is a valid package
if (starts_with(target, '/')) {
// Local path - must have cell.toml
if (!fd.is_file(target + '/cell.toml')) {
throw Error('Target ' + target + ' is not a valid package (no cell.toml)')
log.error('Target ' + target + ' is not a valid package (no cell.toml)')
disrupt
}
} else {
// Remote package target - ensure it's installed
@@ -116,7 +123,8 @@ Link.add = function(canonical, target, shop) {
var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target)
var toml_path = target_path + '/cell.toml'
if (fd.is_file(toml_path)) {
try {
var read_deps_disrupted = false
var do_read_deps = function() {
var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content)
if (cfg.dependencies) {
@@ -128,16 +136,24 @@ Link.add = function(canonical, target, shop) {
return
}
// Install the dependency if not already in shop
try {
var install_disrupted = false
var do_install = function() {
shop.get(dep_locator)
shop.extract(dep_locator)
} catch (e) {
log.console(` Warning: Could not install dependency ${dep_locator}: ${e.message}`)
log.error(e)
} disruption {
install_disrupted = true
}
do_install()
if (install_disrupted) {
log.console(` Warning: Could not install dependency ${dep_locator}`)
}
})
}
} catch (e) {
} disruption {
read_deps_disrupted = true
}
do_read_deps()
if (read_deps_disrupted) {
log.console(` Warning: Could not read dependencies from ${toml_path}`)
}
}
@@ -149,14 +165,14 @@ Link.add = function(canonical, target, shop) {
Link.remove = function(canonical) {
var links = Link.load()
if (!links[canonical]) return false
// Remove the symlink if it exists
var target_dir = get_package_abs_dir(canonical)
if (fd.is_link(target_dir)) {
fd.unlink(target_dir)
log.console("Removed symlink at " + target_dir)
}
delete links[canonical]
Link.save(links)
log.console("Unlinked " + canonical)
@@ -172,7 +188,7 @@ Link.clear = function() {
fd.unlink(target_dir)
}
})
Link.save({})
log.console("Cleared all links")
return true
@@ -186,25 +202,25 @@ Link.sync_one = function(canonical, target, shop) {
// Ensure parent directories exist
var parent = fd.dirname(target_dir)
ensure_dir(parent)
// Check current state
var current_link = null
if (fd.is_link(target_dir)) {
current_link = fd.readlink(target_dir)
}
// If already correctly linked, nothing to do
if (current_link == link_target) {
return true
}
// Remove existing file/dir/link
if (fd.is_link(target_dir)) {
fd.unlink(target_dir)
} else if (fd.is_dir(target_dir)) {
fd.rmdir(target_dir, 1)
}
// Create symlink
fd.symlink(link_target, target_dir)
return true
@@ -218,7 +234,9 @@ Link.sync_all = function(shop) {
arrfor(array(links), function(canonical) {
var target = links[canonical]
try {
var sync_disrupted = false
var sync_error_msg = ""
var do_sync = function() {
// Validate target exists
var link_target = resolve_link_target(target)
if (!fd.is_dir(link_target)) {
@@ -234,7 +252,8 @@ Link.sync_all = function(shop) {
// Install dependencies of the linked package
var toml_path = link_target + '/cell.toml'
try {
var read_deps_disrupted = false
var do_read_deps = function() {
var content = text(fd.slurp(toml_path))
var cfg = toml.decode(content)
if (cfg.dependencies) {
@@ -245,21 +264,25 @@ Link.sync_all = function(shop) {
return
}
// Install the dependency if not already in shop
try {
var install_dep = function() {
shop.get(dep_locator)
shop.extract(dep_locator)
} catch (e) {
// Silently continue - dependency may already be installed
}
} disruption {}
install_dep()
})
}
} catch (e) {
// Could not read dependencies - continue anyway
} disruption {
read_deps_disrupted = true
}
do_read_deps()
count++
} catch (e) {
push(errors, canonical + ': ' + e.message)
} disruption {
sync_disrupted = true
}
do_sync()
if (sync_disrupted) {
push(errors, canonical + ': sync failed')
}
})

View File

@@ -43,7 +43,6 @@ src += [ # core
'suite.c',
'wildmatch.c',
'qjs_actor.c',
'qjs_wota.c',
'miniz.c',
'quickjs.c',
'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'
@@ -52,7 +51,6 @@ src += [ # core
src += ['scheduler.c']
scripts = [
'internal/nota.c',
'debug/js.c',
'qop.c',
'wildstar.c',
@@ -60,7 +58,6 @@ scripts = [
'crypto.c',
'internal/kim.c',
'time.c',
'internal/nota.c',
'debug/debug.c',
'internal/os.c',
'fd.c',
@@ -68,6 +65,7 @@ scripts = [
'net/enet.c',
'wildstar.c',
'archive/miniz.c',
'source/cJSON.c'
]
foreach file: scripts

View File

@@ -571,13 +571,13 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JSValue js_enet_use(JSContext *ctx)
{
JS_NewClassID(&enet_host_id);
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host);
JS_NewClass(ctx, enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto);
JS_NewClassID(&enet_peer_class_id);
JS_NewClass(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class);
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);

View File

@@ -51,7 +51,8 @@ package.load_config = function(name)
return config_cache[config_path]
if (!fd.is_file(config_path)) {
throw Error(`${config_path} does not exist`)
log.error(`${config_path} does not exist`)
disrupt
}
var content = text(fd.slurp(config_path))
@@ -158,19 +159,20 @@ package.split_alias = function(name, path)
var parts = array(path, '/')
var first_part = parts[0]
try {
var split_result = null
var do_split = function() {
var config = package.load_config(name)
if (!config) return null
if (!config) return
var deps = config.dependencies
if (deps && deps[first_part]) {
var dep_locator = deps[first_part]
var remaining_path = text(array(parts, 1), '/')
return { package: dep_locator, path: remaining_path }
split_result = { package: dep_locator, path: remaining_path }
}
} catch (e) {
// Config doesn't exist or couldn't be loaded
}
} disruption {}
do_split()
if (split_result) return split_result
return null
}

View File

@@ -3,6 +3,8 @@
// Based on Douglas Crockford's parseq, adapted for Cell.
// Time is in seconds.
function safe_call(fn, arg) { fn(arg) } disruption {}
function make_reason(factory, excuse, evidence) {
def reason = Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`)
reason.evidence = evidence
@@ -15,12 +17,12 @@ function is_requestor(fn) {
function check_requestors(list, factory) {
if (!is_array(list) || some(list, r => !is_requestor(r)))
throw make_reason(factory, 'Bad requestor array.', list)
disrupt
}
function check_callback(cb, factory) {
if (!is_function(cb) || length(cb) != 2)
throw make_reason(factory, 'Not a callback.', cb)
disrupt
}
// fallback(requestor_array)
@@ -28,7 +30,7 @@ function check_callback(cb, factory) {
function fallback(requestor_array) {
def factory = 'fallback'
if (!is_array(requestor_array) || length(requestor_array) == 0)
throw make_reason(factory, 'Empty requestor array.')
disrupt
check_requestors(requestor_array, factory)
return function fallback_requestor(callback, value) {
@@ -40,7 +42,7 @@ function fallback(requestor_array) {
function cancel(reason) {
cancelled = true
if (current_cancel) {
try { current_cancel(reason) } catch (_) {}
safe_call(current_cancel, reason)
current_cancel = null
}
}
@@ -55,7 +57,8 @@ function fallback(requestor_array) {
def requestor = requestor_array[index]
index += 1
try {
var requestor_disrupted = false
var start_requestor = function() {
current_cancel = requestor(function(val, reason) {
if (cancelled) return
current_cancel = null
@@ -65,7 +68,11 @@ function fallback(requestor_array) {
try_next()
}
}, value)
} catch (ex) {
} disruption {
requestor_disrupted = true
}
start_requestor()
if (requestor_disrupted) {
try_next()
}
}
@@ -80,7 +87,7 @@ function fallback(requestor_array) {
function parallel(requestor_array, throttle, need) {
def factory = 'parallel'
if (!is_array(requestor_array))
throw make_reason(factory, 'Not an array.', requestor_array)
disrupt
check_requestors(requestor_array, factory)
def length = length(requestor_array)
@@ -89,10 +96,10 @@ function parallel(requestor_array, throttle, need) {
if (need == null) need = length
if (!is_number(need) || need < 0 || need > length)
throw make_reason(factory, 'Bad need.', need)
disrupt
if (throttle != null && (!is_number(throttle) || throttle < 1))
throw make_reason(factory, 'Bad throttle.', throttle)
disrupt
return function parallel_requestor(callback, value) {
check_callback(callback, factory)
@@ -107,7 +114,8 @@ function parallel(requestor_array, throttle, need) {
if (finished) return
finished = true
arrfor(cancel_list, c => {
try { if (is_function(c)) c(reason) } catch (_) {}
var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
do_cancel()
})
}
@@ -117,7 +125,9 @@ function parallel(requestor_array, throttle, need) {
next_index += 1
def requestor = requestor_array[idx]
try {
var requestor_disrupted = false
var requestor_ex = null
var run_requestor = function() {
cancel_list[idx] = requestor(function(val, reason) {
if (finished) return
cancel_list[idx] = null
@@ -142,11 +152,15 @@ function parallel(requestor_array, throttle, need) {
start_one()
}, value)
} catch (ex) {
} disruption {
requestor_disrupted = true
}
run_requestor()
if (requestor_disrupted) {
failures += 1
if (failures > length - need) {
cancel(ex)
callback(null, ex)
cancel(requestor_ex)
callback(null, requestor_ex)
return
}
start_one()
@@ -166,16 +180,16 @@ function parallel(requestor_array, throttle, need) {
function race(requestor_array, throttle, need) {
def factory = 'race'
if (!is_array(requestor_array) || length(requestor_array) == 0)
throw make_reason(factory, 'Empty requestor array.')
disrupt
check_requestors(requestor_array, factory)
def length = length(requestor_array)
if (need == null) need = 1
if (!is_number(need) || need < 1 || need > length)
throw make_reason(factory, 'Bad need.', need)
disrupt
if (throttle != null && (!is_number(throttle) || throttle < 1))
throw make_reason(factory, 'Bad throttle.', throttle)
disrupt
return function race_requestor(callback, value) {
check_callback(callback, factory)
@@ -190,7 +204,8 @@ function race(requestor_array, throttle, need) {
if (finished) return
finished = true
arrfor(cancel_list, c => {
try { if (is_function(c)) c(reason) } catch (_) {}
var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
do_cancel()
})
}
@@ -200,7 +215,9 @@ function race(requestor_array, throttle, need) {
next_index += 1
def requestor = requestor_array[idx]
try {
var requestor_disrupted = false
var requestor_ex = null
var run_requestor = function() {
cancel_list[idx] = requestor(function(val, reason) {
if (finished) return
cancel_list[idx] = null
@@ -228,11 +245,15 @@ function race(requestor_array, throttle, need) {
start_one()
}, value)
} catch (ex) {
} disruption {
requestor_disrupted = true
}
run_requestor()
if (requestor_disrupted) {
failures += 1
if (failures > length - need) {
cancel(ex)
callback(null, ex)
cancel(requestor_ex)
callback(null, requestor_ex)
return
}
start_one()
@@ -251,7 +272,7 @@ function race(requestor_array, throttle, need) {
function sequence(requestor_array) {
def factory = 'sequence'
if (!is_array(requestor_array))
throw make_reason(factory, 'Not an array.', requestor_array)
disrupt
check_requestors(requestor_array, factory)
if (length(requestor_array) == 0)
@@ -266,7 +287,7 @@ function sequence(requestor_array) {
function cancel(reason) {
cancelled = true
if (current_cancel) {
try { current_cancel(reason) } catch (_) {}
safe_call(current_cancel, reason)
current_cancel = null
}
}
@@ -281,7 +302,9 @@ function sequence(requestor_array) {
def requestor = requestor_array[index]
index += 1
try {
var requestor_disrupted = false
var requestor_ex = null
var run_requestor = function() {
current_cancel = requestor(function(result, reason) {
if (cancelled) return
current_cancel = null
@@ -291,8 +314,12 @@ function sequence(requestor_array) {
run_next(result)
}
}, val)
} catch (ex) {
callback(null, ex)
} disruption {
requestor_disrupted = true
}
run_requestor()
if (requestor_disrupted) {
callback(null, requestor_ex)
}
}
@@ -306,15 +333,21 @@ function sequence(requestor_array) {
function requestorize(unary) {
def factory = 'requestorize'
if (!is_function(unary))
throw make_reason(factory, 'Not a function.', unary)
disrupt
return function requestorized(callback, value) {
check_callback(callback, factory)
try {
var call_disrupted = false
var call_ex = null
var do_call = function() {
def result = unary(value)
callback(result == null ? true : result)
} catch (ex) {
callback(null, ex)
} disruption {
call_disrupted = true
}
do_call()
if (call_disrupted) {
callback(null, call_ex)
}
}
}

4
qop.c
View File

@@ -458,13 +458,13 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
JSValue js_qop_use(JSContext *js) {
JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class);
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
JS_NewClassID(&js_qop_writer_class_id);
JS_NewClass(JS_GetRuntime(js), js_qop_writer_class_id, &js_qop_writer_class);
JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs));
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto);

3191
source/cJSON.c Normal file

File diff suppressed because it is too large Load Diff

306
source/cJSON.h Normal file
View File

@@ -0,0 +1,306 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
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.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 19
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* Limits the length of circular references can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_CIRCULAR_LIMIT
#define CJSON_CIRCULAR_LIMIT 10000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -2,7 +2,6 @@
#include <windows.h>
#endif
#define WOTA_IMPLEMENTATION
#include "wota.h"
#define STB_DS_IMPLEMENTATION
@@ -10,11 +9,13 @@
#include "cell.h"
#include "cell_internal.h"
#include "cJSON.h"
#define ENGINE "internal/engine.cm"
#define CELL_SHOP_DIR ".cell"
#define CELL_CORE_DIR "packages/core"
#include <math.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
@@ -25,6 +26,7 @@ static int run_test_suite(size_t heap_size);
cell_rt *root_cell = NULL;
static char *core_path = NULL;
static JSRuntime *g_runtime = NULL;
// Get the home directory
static const char* get_home_dir(void) {
@@ -104,6 +106,46 @@ static char* load_core_file(const char *filename, size_t *out_size) {
return data;
}
static int print_tree_errors(cJSON *root) {
if (!root) return 0;
cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors");
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0)
return 0;
const char *filename = "<unknown>";
cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename");
if (cJSON_IsString(fname))
filename = fname->valuestring;
int prev_line = -1;
const char *prev_msg = NULL;
cJSON *e;
cJSON_ArrayForEach(e, errors) {
const char *msg = cJSON_GetStringValue(
cJSON_GetObjectItemCaseSensitive(e, "message"));
cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line");
cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column");
int cur_line = cJSON_IsNumber(line) ? (int)line->valuedouble : -1;
if (prev_msg && msg && cur_line == prev_line && strcmp(msg, prev_msg) == 0)
continue;
prev_line = cur_line;
prev_msg = msg;
if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col))
fprintf(stderr, "%s:%d:%d: error: %s\n",
filename, (int)line->valuedouble, (int)col->valuedouble, msg);
else if (msg)
fprintf(stderr, "%s: error: %s\n", filename, msg);
}
return 1;
}
static int print_json_errors(const char *json) {
if (!json) return 0;
cJSON *root = cJSON_Parse(json);
if (!root) return 0;
int result = print_tree_errors(root);
cJSON_Delete(root);
return result;
}
// Get the core path for use by scripts
const char* cell_get_core_path(void) {
return core_path;
@@ -118,59 +160,37 @@ void actor_disrupt(cell_rt *crt)
JSValue js_os_use(JSContext *js);
JSValue js_math_use(JSContext *js);
JSValue js_json_use(JSContext *js);
JSValue js_nota_use(JSContext *js);
JSValue js_wota_use(JSContext *js);
void script_startup(cell_rt *prt)
{
JSRuntime *rt;
rt = JS_NewRuntime();
JSContext *js = JS_NewContextRaw(rt);
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
JS_AddIntrinsicBaseObjects(js);
JS_AddIntrinsicEval(js);
JS_AddIntrinsicRegExp(js);
JS_AddIntrinsicJSON(js);
if (!g_runtime) {
g_runtime = JS_NewRuntime();
}
JSContext *js = JS_NewContext(g_runtime);
JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt);
JS_SetContextOpaque(js, prt);
prt->context = js;
/* Register all GCRef fields so the Cheney GC can relocate them. */
JS_AddGCRef(js, &prt->idx_buffer_ref);
JS_AddGCRef(js, &prt->on_exception_ref);
JS_AddGCRef(js, &prt->message_handle_ref);
JS_AddGCRef(js, &prt->unneeded_ref);
JS_AddGCRef(js, &prt->actor_sym_ref);
prt->idx_buffer_ref.val = JS_NULL;
prt->on_exception_ref.val = JS_NULL;
prt->message_handle_ref.val = JS_NULL;
prt->unneeded_ref.val = JS_NULL;
prt->actor_sym_ref.val = JS_NULL;
cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js));
JSValue globalThis = JS_GetGlobalObject(js);
JSValue cell = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"cell", cell);
JSValue hidden_fn = JS_NewObject(js);
JS_SetPropertyStr(js, cell, "hidden", hidden_fn);
JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js));
crt->actor_sym = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_fn, "actorsym", JS_DupValue(js,crt->actor_sym));
if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, crt->init_wota));
// init wota can now be freed
free(crt->init_wota);
crt->init_wota = NULL;
}
// Store the core path for scripts to use
JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell");
JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden");
if (core_path) {
JS_SetPropertyStr(js, hidden, "core_path", JS_NewString(js, core_path));
}
JS_FreeValue(js, hidden);
JS_FreeValue(js, js_cell);
JS_FreeValue(js, globalThis);
// Load engine.cm from the core directory
// Load and parse engine.cm to AST
size_t engine_size;
char *data = load_core_file(ENGINE, &engine_size);
if (!data) {
@@ -178,9 +198,71 @@ void script_startup(cell_rt *prt)
return;
}
crt->state = ACTOR_RUNNING;
JSValue v = JS_Eval(js, data, engine_size, ENGINE, 0);
cJSON *ast = JS_ASTTree(data, engine_size, ENGINE);
free(data);
if (!ast) {
printf("ERROR: Failed to parse %s\n", ENGINE);
return;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
return;
}
// Create hidden environment (GC-protected to survive allocations)
JSGCRef env_ref;
JS_PushGCRef(js, &env_ref);
env_ref.val = JS_NewObject(js);
// Create modules with GC rooting
JSGCRef os_ref, json_ref, nota_ref, wota_ref;
JS_PushGCRef(js, &os_ref);
JS_PushGCRef(js, &json_ref);
JS_PushGCRef(js, &nota_ref);
JS_PushGCRef(js, &wota_ref);
os_ref.val = js_os_use(js);
json_ref.val = js_json_use(js);
nota_ref.val = js_nota_use(js);
wota_ref.val = js_wota_use(js);
// Set properties on env (SetPropertyStr internally roots its args)
JS_SetPropertyStr(js, env_ref.val, "os", os_ref.val);
JS_SetPropertyStr(js, env_ref.val, "json", json_ref.val);
JS_SetPropertyStr(js, env_ref.val, "nota", nota_ref.val);
JS_SetPropertyStr(js, env_ref.val, "wota", wota_ref.val);
JS_PopGCRef(js, &wota_ref);
JS_PopGCRef(js, &nota_ref);
JS_PopGCRef(js, &json_ref);
JS_PopGCRef(js, &os_ref);
crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
if (crt->init_wota) {
JSValue init_val = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, env_ref.val, "init", init_val);
free(crt->init_wota);
crt->init_wota = NULL;
} else {
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
}
if (core_path) {
JSValue path_val = JS_NewString(js, core_path);
JS_SetPropertyStr(js, env_ref.val, "core_path", path_val);
}
// Stone the environment
JSValue hidden_env = JS_Stone(js, env_ref.val);
JS_PopGCRef(js, &env_ref);
// Run through MACH VM
crt->state = ACTOR_RUNNING;
JSValue v = JS_RunMachTree(js, ast, hidden_env);
cJSON_Delete(ast);
uncaught_exception(js, v);
crt->state = ACTOR_IDLE;
set_actor_state(crt);
@@ -213,15 +295,13 @@ static int run_test_suite(size_t heap_size)
return 1;
}
JSContext *ctx = JS_NewContextRawWithHeapSize(rt, heap_size);
JSContext *ctx = JS_NewContextWithHeapSize(rt, heap_size);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
return 1;
}
JS_AddIntrinsicBaseObjects(ctx);
int result = run_c_test_suite(ctx);
JS_FreeContext(ctx);
@@ -231,7 +311,7 @@ static int run_test_suite(size_t heap_size)
}
/* Run an immediate script string */
static int run_eval(const char *script_or_file, int print_bytecode)
static int run_eval(const char *script_or_file, int print_bytecode, int use_bootstrap_env)
{
if (!find_cell_shop()) return 1;
@@ -271,7 +351,7 @@ static int run_eval(const char *script_or_file, int print_bytecode)
return 1;
}
JSContext *ctx = JS_NewContextRaw(rt);
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
@@ -279,62 +359,48 @@ static int run_eval(const char *script_or_file, int print_bytecode)
return 1;
}
JS_AddIntrinsicBaseObjects(ctx);
JS_AddIntrinsicEval(ctx);
JS_AddIntrinsicRegExp(ctx);
JS_AddIntrinsicJSON(ctx);
int result = 0;
if (print_bytecode) {
/* Compile only, then dump and optionally execute */
JSValue func = JS_Eval(ctx, script, strlen(script), filename, JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(func)) {
uncaught_exception(ctx, func);
result = 1;
} else {
printf("=== Compiled Bytecode ===\n");
JS_DumpFunctionBytecode(ctx, func);
/* Link - resolve global references */
JSValue linked = JS_LinkFunction(ctx, func);
if (JS_IsException(linked)) {
uncaught_exception(ctx, linked);
result = 1;
} else {
printf("\n=== Linked Bytecode ===\n");
JS_DumpFunctionBytecode(ctx, linked);
/* Now execute the linked bytecode */
JSValue v = JS_EvalFunction(ctx, linked);
if (JS_IsException(v)) {
uncaught_exception(ctx, v);
result = 1;
} else {
JS_FreeValue(ctx, v);
}
}
}
JSGCRef bytecode_ref;
JS_PushGCRef(ctx, &bytecode_ref);
bytecode_ref.val = JS_Compile(ctx, script, strlen(script), filename);
if (JS_IsException(bytecode_ref.val)) {
uncaught_exception(ctx, bytecode_ref.val);
JS_PopGCRef(ctx, &bytecode_ref);
result = 1;
} else {
/* Compile, link, execute */
JSValue func = JS_Eval(ctx, script, strlen(script), filename, JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(func)) {
uncaught_exception(ctx, func);
if (print_bytecode) {
printf("=== Compiled Bytecode ===\n");
JS_DumpFunctionBytecode(ctx, bytecode_ref.val);
}
JSValue env = JS_NULL;
if (use_bootstrap_env) {
JSGCRef env_ref, json_ref, nota_ref, wota_ref;
JS_PushGCRef(ctx, &env_ref);
JS_PushGCRef(ctx, &json_ref);
JS_PushGCRef(ctx, &nota_ref);
JS_PushGCRef(ctx, &wota_ref);
env_ref.val = JS_NewObject(ctx);
/* Create modules with GC rooting, then stone them */
json_ref.val = js_json_use(ctx);
nota_ref.val = js_nota_use(ctx);
wota_ref.val = js_wota_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", JS_Stone(ctx, json_ref.val));
JS_SetPropertyStr(ctx, env_ref.val, "nota", JS_Stone(ctx, nota_ref.val));
JS_SetPropertyStr(ctx, env_ref.val, "wota", JS_Stone(ctx, wota_ref.val));
env = JS_Stone(ctx, env_ref.val);
JS_PopGCRef(ctx, &wota_ref);
JS_PopGCRef(ctx, &nota_ref);
JS_PopGCRef(ctx, &json_ref);
JS_PopGCRef(ctx, &env_ref);
}
JSValue v = JS_Integrate(ctx, bytecode_ref.val, env);
JS_PopGCRef(ctx, &bytecode_ref);
if (JS_IsException(v)) {
uncaught_exception(ctx, v);
result = 1;
} else {
JSValue linked = JS_LinkFunction(ctx, func);
if (JS_IsException(linked)) {
uncaught_exception(ctx, linked);
result = 1;
} else {
JSValue v = JS_EvalFunction(ctx, linked);
if (JS_IsException(v)) {
uncaught_exception(ctx, v);
result = 1;
} else {
JS_FreeValue(ctx, v);
}
}
JS_FreeValue(ctx, v);
}
}
@@ -359,13 +425,415 @@ int cell_init(int argc, char **argv)
return run_test_suite(heap_size);
}
/* Check for --ast flag to output AST JSON */
if (argc >= 3 && strcmp(argv[1], "--ast") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (ast) {
int has_errors = print_tree_errors(ast);
char *pretty = cJSON_Print(ast);
cJSON_Delete(ast);
printf("%s\n", pretty);
free(pretty);
free(allocated_script);
return has_errors ? 1 : 0;
} else {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
}
/* Check for --tokenize flag to output token array JSON */
if (argc >= 3 && strcmp(argv[1], "--tokenize") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
char *json = JS_Tokenize(script, strlen(script), filename);
if (json) {
int has_errors = print_json_errors(json);
cJSON *root = cJSON_Parse(json);
free(json);
if (root) {
char *pretty = cJSON_Print(root);
cJSON_Delete(root);
printf("%s\n", pretty);
free(pretty);
}
free(allocated_script);
return has_errors ? 1 : 0;
} else {
printf("Failed to tokenize\n");
free(allocated_script);
return 1;
}
}
/* Check for --mcode flag to output MCODE JSON IR */
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
free(allocated_script);
return 1;
}
cJSON *mcode = JS_McodeTree(ast);
cJSON_Delete(ast);
if (!mcode) {
printf("Failed to generate MCODE\n");
free(allocated_script);
return 1;
}
char *pretty = cJSON_Print(mcode);
cJSON_Delete(mcode);
printf("%s\n", pretty);
free(pretty);
free(allocated_script);
return 0;
}
/* Check for --run-mcode flag to execute via MCODE interpreter */
if (argc >= 3 && strcmp(argv[1], "--run-mcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; }
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) { fclose(f); printf("Failed to allocate memory\n"); return 1; }
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast); free(allocated_script);
return 1;
}
cJSON *mcode = JS_McodeTree(ast);
cJSON_Delete(ast);
if (!mcode) {
printf("Failed to generate MCODE\n");
free(allocated_script);
return 1;
}
if (print_tree_errors(mcode)) {
cJSON_Delete(mcode); free(allocated_script);
return 1;
}
/* Use a larger heap context for execution */
JSRuntime *rt = JS_NewRuntime();
if (!rt) { printf("Failed to create JS runtime\n"); cJSON_Delete(mcode); free(allocated_script); return 1; }
JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024);
if (!ctx) { printf("Failed to create execution context\n"); cJSON_Delete(mcode); JS_FreeRuntime(rt); free(allocated_script); return 1; }
JSValue result = JS_CallMcodeTree(ctx, mcode); /* takes ownership of mcode */
if (JS_IsException(result)) {
JSValue exc = JS_GetException(ctx);
const char *str = JS_ToCString(ctx, exc);
if (str) { printf("Error: %s\n", str); JS_FreeCString(ctx, str); }
cJSON *stack = JS_GetStack(ctx);
if (stack) {
int n = cJSON_GetArraySize(stack);
for (int i = 0; i < n; i++) {
cJSON *fr = cJSON_GetArrayItem(stack, i);
const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function"));
const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file"));
int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line"));
int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column"));
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
}
cJSON_Delete(stack);
}
JS_FreeValue(ctx, exc);
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); }
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return JS_IsException(result) ? 1 : 0;
}
/* Check for --mach flag to dump MACH bytecode */
if (argc >= 3 && strcmp(argv[1], "--mach") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
free(allocated_script);
return 1;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
cJSON_Delete(ast); free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
JS_DumpMachTree(ctx, ast, JS_NULL);
cJSON_Delete(ast);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 0;
}
/* Check for --mach-run flag to compile and run through MACH VM */
if (argc >= 3 && strcmp(argv[1], "--mach-run") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
free(allocated_script);
return 1;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
cJSON_Delete(ast); free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
JSValue result = JS_RunMachTree(ctx, ast, JS_NULL);
cJSON_Delete(ast);
int exit_code = 0;
if (JS_IsException(result)) {
JSValue exc = JS_GetException(ctx);
const char *err_str = JS_ToCString(ctx, exc);
if (err_str) {
printf("Error: %s\n", err_str);
JS_FreeCString(ctx, err_str);
}
cJSON *stack = JS_GetStack(ctx);
if (stack) {
int n = cJSON_GetArraySize(stack);
for (int i = 0; i < n; i++) {
cJSON *fr = cJSON_GetArrayItem(stack, i);
const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function"));
const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file"));
int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line"));
int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column"));
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
}
cJSON_Delete(stack);
}
JS_FreeValue(ctx, exc);
exit_code = 1;
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);
JS_FreeCString(ctx, str);
}
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return exit_code;
}
/* Check for -e or --eval flag to run immediate script */
/* Also check for -p flag to print bytecode */
/* -s / --serializers flag provides json, nota, wota in env */
if (argc >= 3 && (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--eval") == 0)) {
return run_eval(argv[2], 0);
return run_eval(argv[2], 0, 0);
}
if (argc >= 3 && (strcmp(argv[1], "-p") == 0 || strcmp(argv[1], "--print-bytecode") == 0)) {
return run_eval(argv[2], 1);
return run_eval(argv[2], 1, 0);
}
if (argc >= 3 && (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--serializers") == 0)) {
return run_eval(argv[2], 0, 1);
}
int script_start = 1;

View File

@@ -28,6 +28,13 @@ JSValue number2js(JSContext *js, double g);
JSValue wota2value(JSContext *js, void *v);
void *value2wota(JSContext *js, JSValue v, JSValue replacer, size_t *bytes);
JSValue nota2value(JSContext *js, void *nota);
void *value2nota(JSContext *js, JSValue v);
JSValue js_json_use(JSContext *js);
JSValue js_nota_use(JSContext *js);
JSValue js_wota_use(JSContext *js);
#define CELL_HOOK_ENTER 1
#define CELL_HOOK_EXIT 2
typedef void (*cell_hook)(const char *name, int type);
@@ -137,7 +144,7 @@ JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYP
#define QJSCLASSPREP_NO_FUNCS(TYPE) \
JS_NewClassID(&js_##TYPE##_id);\
JS_NewClass(JS_GetRuntime(js), js_##TYPE##_id, &js_##TYPE##_class);\
JS_NewClass(js, js_##TYPE##_id, &js_##TYPE##_class);\
JSValue TYPE##_proto = JS_NewObject(js); \
JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \

View File

@@ -24,21 +24,26 @@ typedef struct letter {
typedef struct cell_rt {
JSContext *context;
JSValue idx_buffer;
JSValue on_exception;
JSValue message_handle;
/* JSValues on the GC heap — each paired with a JSGCRef so the
Cheney GC can relocate them during compaction. */
JSGCRef idx_buffer_ref;
JSGCRef on_exception_ref;
JSGCRef message_handle_ref;
JSGCRef unneeded_ref;
JSGCRef actor_sym_ref;
void *init_wota;
/* Protects JSContext usage */
pthread_mutex_t *mutex; /* for everything else */
pthread_mutex_t *msg_mutex; /* For message queue and timers queue */
char *id;
int idx_count;
/* The mailbox for incoming messages + a dedicated lock for it: */
/* The "mailbox" for incoming messages + a dedicated lock for it: */
letter *letters;
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
@@ -47,14 +52,11 @@ typedef struct cell_rt {
int state;
uint32_t ar; // timer for unneeded
double ar_secs; // time for unneeded
JSValue unneeded; // fn to call before unneeded
int disrupt;
int main_thread_only;
int affinity;
JSValue actor_sym;
const char *name; // human friendly name
cell_hook trace_hook;
} cell_rt;
@@ -63,8 +65,6 @@ cell_rt *create_actor(void *wota);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_disrupt(cell_rt *actor);
JSValue actor_sym(cell_rt *actor);
const char *send_message(const char *id, void *msg);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);

View File

@@ -74,7 +74,7 @@ JSC_CCALL(os_register_actor,
JS_ToFloat64(js, &ar, argv[3]);
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err);
rt->message_handle = JS_DupValue(js, argv[1]);
rt->message_handle_ref.val = argv[1];
rt->context = js;
JS_FreeCString(js, id);
)
@@ -106,8 +106,7 @@ JSC_SCALL(actor_setname,
JSC_CCALL(actor_on_exception,
cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, rt->on_exception);
rt->on_exception = JS_DupValue(js,argv[0]);
rt->on_exception_ref.val = argv[0];
)
JSC_CCALL(actor_clock,

View File

@@ -1,401 +0,0 @@
#include "cell.h"
#include "cell_internal.h"
#include "wota.h"
#include <stdlib.h>
typedef struct ObjectRef {
void *ptr;
struct ObjectRef *next;
} ObjectRef;
typedef struct WotaEncodeContext {
JSContext *ctx;
ObjectRef *visited_stack;
WotaBuffer wb;
int cycle;
JSValue replacer;
} WotaEncodeContext;
static void wota_stack_push(WotaEncodeContext *enc, JSValueConst val)
{
/* if (!JS_IsObject(val)) return;
ObjectRef *ref = malloc(sizeof(ObjectRef));
if (!ref) return;
ref->ptr = JS_VALUE_GET_PTR(val);
ref->next = enc->visited_stack;
enc->visited_stack = ref;*/
}
static void wota_stack_pop(WotaEncodeContext *enc)
{
if (!enc->visited_stack) return;
ObjectRef *top = enc->visited_stack;
enc->visited_stack = top->next;
free(top);
}
static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
{
/* if (!JS_IsObject(val)) return 0;
void *ptr = JS_VALUE_GET_PTR(val);
ObjectRef *current = enc->visited_stack;
while (current) {
if (current->ptr == ptr) return 1;
current = current->next;
}
return 0;*/
}
static void wota_stack_free(WotaEncodeContext *enc)
{
while (enc->visited_stack) {
wota_stack_pop(enc);
}
}
static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val)
{
if (JS_IsNull(enc->replacer)) return JS_DupValue(enc->ctx, val);
JSValue key_val = JS_IsNull(key) ? JS_NULL : JS_DupValue(enc->ctx, key);
JSValue args[2] = { key_val, JS_DupValue(enc->ctx, val) };
JSValue result = JS_Call(enc->ctx, enc->replacer, holder, 2, args);
JS_FreeValue(enc->ctx, args[0]);
JS_FreeValue(enc->ctx, args[1]);
if (JS_IsException(result)) return JS_DupValue(enc->ctx, val);
return result;
}
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key);
static void encode_object_properties(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder)
{
JSContext *ctx = enc->ctx;
JSValue keys = JS_GetOwnPropertyNames(ctx, val);
if (JS_IsException(keys)) {
wota_write_sym(&enc->wb, WOTA_NULL);
return;
}
int64_t plen64;
if (JS_GetLength(ctx, keys, &plen64) < 0) {
JS_FreeValue(ctx, keys);
wota_write_sym(&enc->wb, WOTA_NULL);
return;
}
uint32_t plen = (uint32_t)plen64;
uint32_t non_function_count = 0;
JSValue props[plen];
JSValue kept_keys[plen];
for (uint32_t i = 0; i < plen; i++) {
JSValue key = JS_GetPropertyUint32(ctx, keys, i);
JSValue prop_val = JS_GetProperty(ctx, val, key);
if (!JS_IsFunction(prop_val)) {
kept_keys[non_function_count] = key;
props[non_function_count++] = prop_val;
} else {
JS_FreeValue(ctx, prop_val);
JS_FreeValue(ctx, key);
}
}
JS_FreeValue(ctx, keys);
wota_write_record(&enc->wb, non_function_count);
for (uint32_t i = 0; i < non_function_count; i++) {
size_t klen;
const char *prop_name = JS_ToCStringLen(ctx, &klen, kept_keys[i]);
JSValue prop_val = props[i];
wota_write_text_len(&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
wota_encode_value(enc, prop_val, val, kept_keys[i]);
JS_FreeCString(ctx, prop_name);
JS_FreeValue(ctx, prop_val);
JS_FreeValue(ctx, kept_keys[i]);
}
}
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key)
{
JSContext *ctx = enc->ctx;
JSValue replaced;
if (!JS_IsNull(enc->replacer) && !JS_IsNull(key))
replaced = apply_replacer(enc, holder, key, val);
else
replaced = JS_DupValue(enc->ctx, val);
int tag = JS_VALUE_GET_TAG(replaced);
switch (tag) {
case JS_TAG_INT: {
int32_t d;
JS_ToInt32(ctx, &d, replaced);
wota_write_int_word(&enc->wb, d);
break;
}
case JS_TAG_FLOAT64: {
double d;
if (JS_ToFloat64(ctx, &d, replaced) < 0) {
wota_write_sym(&enc->wb, WOTA_NULL);
break;
}
wota_write_float_word(&enc->wb, d);
break;
}
case JS_TAG_STRING: {
size_t plen;
const char *str = JS_ToCStringLen(ctx, &plen, replaced);
wota_write_text_len(&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString(ctx, str);
break;
}
case JS_TAG_BOOL:
wota_write_sym(&enc->wb, JS_VALUE_GET_BOOL(replaced) ? WOTA_TRUE : WOTA_FALSE);
break;
case JS_TAG_NULL:
wota_write_sym(&enc->wb, WOTA_NULL);
break;
case JS_TAG_PTR: {
if (js_is_blob(ctx, replaced)) {
size_t buf_len;
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
if (buf_data == (void *)-1) {
JS_FreeValue(ctx, replaced);
return; // JS_EXCEPTION will be handled by caller
}
if (buf_len == 0) {
wota_write_blob(&enc->wb, 0, "");
} else {
wota_write_blob(&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data);
}
break;
}
if (JS_IsArray(replaced)) {
if (wota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push(enc, replaced);
int64_t arr_len;
JS_GetLength(ctx, replaced, &arr_len);
wota_write_array(&enc->wb, arr_len);
for (int64_t i = 0; i < arr_len; i++) {
JSValue elem_val = JS_GetPropertyUint32(ctx, replaced, i);
/* Use int index as key placeholder */
wota_encode_value(enc, elem_val, replaced, JS_NewInt32(ctx, (int32_t)i));
JS_FreeValue(ctx, elem_val);
}
wota_stack_pop(enc);
break;
}
cell_rt *crt = JS_GetContextOpaque(ctx);
// JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
JSValue adata = JS_NULL;
if (!JS_IsNull(adata)) {
wota_write_sym(&enc->wb, WOTA_PRIVATE);
wota_encode_value(enc, adata, replaced, JS_NULL);
JS_FreeValue(ctx, adata);
break;
}
JS_FreeValue(ctx, adata);
if (wota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push(enc, replaced);
JSValue to_json = JS_GetPropertyStr(ctx, replaced, "toJSON");
if (JS_IsFunction(to_json)) {
JSValue result = JS_Call(ctx, to_json, replaced, 0, NULL);
JS_FreeValue(ctx, to_json);
if (!JS_IsException(result)) {
wota_encode_value(enc, result, holder, key);
JS_FreeValue(ctx, result);
} else
wota_write_sym(&enc->wb, WOTA_NULL);
wota_stack_pop(enc);
break;
}
JS_FreeValue(ctx, to_json);
encode_object_properties(enc, replaced, holder);
wota_stack_pop(enc);
break;
}
default:
wota_write_sym(&enc->wb, WOTA_NULL);
break;
}
JS_FreeValue(ctx, replaced);
}
static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver)
{
uint64_t first_word = *(uint64_t *)data_ptr;
int type = (int)(first_word & 0xffU);
switch (type) {
case WOTA_INT: {
long long val;
data_ptr = wota_read_int(&val, data_ptr);
*out_val = JS_NewInt64(ctx, val);
break;
}
case WOTA_FLOAT: {
double d;
data_ptr = wota_read_float(&d, data_ptr);
*out_val = JS_NewFloat64(ctx, d);
break;
}
case WOTA_SYM: {
int scode;
data_ptr = wota_read_sym(&scode, data_ptr);
if (scode == WOTA_PRIVATE) {
JSValue inner = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &inner, holder, JS_NULL, reviver);
JSValue obj = JS_NewObject(ctx);
cell_rt *crt = JS_GetContextOpaque(ctx);
// JS_SetProperty(ctx, obj, crt->actor_sym, inner);
*out_val = obj;
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
else if (scode == WOTA_FALSE) *out_val = JS_NewBool(ctx, 0);
else if (scode == WOTA_TRUE) *out_val = JS_NewBool(ctx, 1);
else *out_val = JS_NULL;
break;
}
case WOTA_BLOB: {
long long blen;
char *bdata = NULL;
data_ptr = wota_read_blob(&blen, &bdata, data_ptr);
*out_val = bdata ? js_new_blob_stoned_copy(ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy(ctx, NULL, 0);
if (bdata) free(bdata);
break;
}
case WOTA_TEXT: {
char *utf8 = NULL;
data_ptr = wota_read_text(&utf8, data_ptr);
*out_val = JS_NewString(ctx, utf8 ? utf8 : "");
if (utf8) free(utf8);
break;
}
case WOTA_ARR: {
long long c;
data_ptr = wota_read_array(&c, data_ptr);
JSValue arr = JS_NewArrayLen(ctx, c);
for (long long i = 0; i < c; i++) {
JSValue elem_val = JS_NULL;
JSValue idx_key = JS_NewInt32(ctx, (int32_t)i);
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, idx_key, reviver);
JS_SetPropertyUint32(ctx, arr, i, elem_val);
}
*out_val = arr;
break;
}
case WOTA_REC: {
long long c;
data_ptr = wota_read_record(&c, data_ptr);
JSValue obj = JS_NewObject(ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
size_t key_len;
data_ptr = wota_read_text_len(&key_len, &tkey, data_ptr);
if (!tkey) continue; // invalid key
JSValue prop_key = JS_NewStringLen(ctx, tkey, key_len);
JSValue sub_val = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj, prop_key, reviver);
JS_SetProperty(ctx, obj, prop_key, sub_val);
JS_FreeValue(ctx, prop_key);
free(tkey);
}
*out_val = obj;
break;
}
default:
data_ptr += 8;
*out_val = JS_NULL;
break;
}
if (!JS_IsNull(reviver)) {
JSValue key_val = JS_IsNull(key) ? JS_NULL : JS_DupValue(ctx, key);
JSValue args[2] = { key_val, JS_DupValue(ctx, *out_val) };
JSValue revived = JS_Call(ctx, reviver, holder, 2, args);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
if (!JS_IsException(revived)) {
JS_FreeValue(ctx, *out_val);
*out_val = revived;
} else
JS_FreeValue(ctx, revived);
}
return data_ptr;
}
void *value2wota(JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes)
{
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = NULL;
enc->cycle = 0;
enc->replacer = replacer;
wota_buffer_init(&enc->wb, 16);
wota_encode_value(enc, v, JS_NULL, JS_NULL);
if (enc->cycle) {
wota_stack_free(enc);
wota_buffer_free(&enc->wb);
return NULL;
}
wota_stack_free(enc);
size_t total_bytes = enc->wb.size * sizeof(uint64_t);
void *wota = realloc(enc->wb.data, total_bytes);
if (bytes) *bytes = total_bytes;
return wota;
}
JSValue wota2value(JSContext *ctx, void *wota)
{
JSValue result = JS_NULL;
JSValue holder = JS_NewObject(ctx);
decode_wota_value(ctx, wota, &result, holder, JS_NULL, JS_NULL);
JS_FreeValue(ctx, holder);
return result;
}
static JSValue js_wota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1) return JS_ThrowTypeError(ctx, "wota.encode requires at least 1 argument");
size_t total_bytes;
void *wota = value2wota(ctx, argv[0], JS_IsFunction(argv[1]) ? argv[1] : JS_NULL, &total_bytes);
JSValue ret = js_new_blob_stoned_copy(ctx, wota, total_bytes);
free(wota);
return ret;
}
static JSValue js_wota_decode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1) return JS_NULL;
size_t len;
uint8_t *buf = js_get_blob_data(ctx, &len, argv[0]);
if (buf == (uint8_t *)-1) return JS_EXCEPTION;
if (!buf || len == 0) return JS_ThrowTypeError(ctx, "No blob data present");
JSValue reviver = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
char *data_ptr = (char *)buf;
JSValue result = JS_NULL;
JSValue holder = JS_NewObject(ctx);
JSValue empty_key = JS_NewString(ctx, "");
decode_wota_value(ctx, data_ptr, &result, holder, empty_key, reviver);
JS_FreeValue(ctx, empty_key);
JS_FreeValue(ctx, holder);
return result;
}
static const JSCFunctionListEntry js_wota_funcs[] = {
JS_CFUNC_DEF("encode", 2, js_wota_encode),
JS_CFUNC_DEF("decode", 2, js_wota_decode),
};
JSValue js_wota_use(JSContext *ctx)
{
JSValue exports = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, exports, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0]));
return exports;
}

View File

@@ -193,8 +193,10 @@ 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)
/* template literal concatenation - pops N parts, pushes concatenated string */
DEF(template_concat, 3, 0, 1, npop_u16)
/* 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 */
@@ -203,6 +205,7 @@ 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) */

File diff suppressed because it is too large Load Diff

View File

@@ -92,6 +92,7 @@ static inline int objhdr_s (objhdr_t h) { return (h & OBJHDR_S_MASK) != 0; }
typedef struct JSRuntime JSRuntime; // the entire VM
typedef struct JSContext JSContext; // Each actor
typedef struct JSClass JSClass;
typedef struct JSFunctionBytecode JSFunctionBytecode;
typedef uint32_t JSClassID;
/* Forward declaration - JSGCRef moved after JSValue definition */
@@ -176,8 +177,6 @@ void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref);
Value Extraction
============================================================ */
#define JS_VALUE_GET_INT(v) ((int)(v) >> 1)
/* Get primary tag (low 2-3 bits) */
static inline int
JS_VALUE_GET_TAG (JSValue v) {
@@ -334,18 +333,8 @@ JS_IsShortFloat (JSValue v) {
#define JS_DEFAULT_STACK_SIZE (1024 * 1024)
#endif
/* JS_Eval() flags */
#define JS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */
#define JS_EVAL_TYPE_DIRECT (2 << 0) /* direct call (internal use) */
#define JS_EVAL_TYPE_INDIRECT (3 << 0) /* indirect call (internal use) */
#define JS_EVAL_TYPE_MASK (3 << 0)
/* compile but do not run. The result is an object with a
JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed
with JS_EvalFunction(). */
#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5)
/* don't include the stack frames before this eval in the Error() backtraces */
#define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6)
/* Internal compile flags */
#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5) /* internal use */
typedef JSValue JSCFunction (JSContext *ctx, JSValue this_val, int argc,
JSValue *argv);
@@ -378,10 +367,10 @@ JSRuntime *JS_NewRuntime (void);
void JS_SetRuntimeInfo (JSRuntime *rt, const char *info);
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
/* use 0 to disable maximum stack size check */
void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size);
void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
/* should be called when changing thread to update the stack top value
used to check stack overflow. */
void JS_UpdateStackTop (JSRuntime *rt);
void JS_UpdateStackTop (JSContext *ctx);
void JS_FreeRuntime (JSRuntime *rt);
void *JS_GetRuntimeOpaque (JSRuntime *rt);
void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque);
@@ -400,10 +389,7 @@ JSRuntime *JS_GetRuntime (JSContext *ctx);
void JS_SetClassProto (JSContext *ctx, JSClassID class_id, JSValue obj);
JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id);
/* the following functions are used to select the intrinsic object to
save memory */
JSContext *JS_NewContextRaw (JSRuntime *rt);
JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size);
JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
typedef struct JSMemoryUsage {
int64_t malloc_size, malloc_limit, memory_used_size;
@@ -443,9 +429,9 @@ JSClassID JS_NewClassID (JSClassID *pclass_id);
/* Returns the class ID if `v` is an object, otherwise returns
* JS_INVALID_CLASS_ID. */
JSClassID JS_GetClassID (JSValue v);
int JS_NewClass (JSRuntime *rt, JSClassID class_id,
int JS_NewClass (JSContext *ctx, JSClassID class_id,
const JSClassDef *class_def);
int JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id);
int JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id);
extern JSClassID js_class_id_alloc;
@@ -727,13 +713,17 @@ int JS_SetPropertyInt64 (JSContext *ctx, JSValue this_obj, int64_t idx, JSValue
JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj);
JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */
JSValue JS_Eval (JSContext *ctx, const char *input, size_t input_len,
const char *filename, int eval_flags);
/* same as JS_Eval() but with an explicit 'this_obj' parameter */
JSValue JS_EvalThis (JSContext *ctx, JSValue this_obj, const char *input,
size_t input_len, const char *filename, int eval_flags);
JSValue JS_GetGlobalObject (JSContext *ctx);
/* Compile source code to bytecode without executing.
'input' must be zero terminated i.e. input[input_len] = '\0'.
Returns unlinked bytecode on success, JS_EXCEPTION on error. */
JSValue JS_Compile (JSContext *ctx, const char *input, size_t input_len,
const char *filename);
/* Link compiled bytecode with environment and execute.
env should be stoned record or null.
Variables resolve: env first, then global intrinsics. */
JSValue JS_Integrate (JSContext *ctx, JSValue bytecode, JSValue env);
void JS_SetOpaque (JSValue obj, void *opaque);
void *JS_GetOpaque (JSValue obj, JSClassID class_id);
void *JS_GetOpaque2 (JSContext *ctx, JSValue obj, JSClassID class_id);
@@ -750,7 +740,7 @@ JSValue JS_JSONStringify (JSContext *ctx, JSValue obj,
/* return != 0 if the JS code needs to be interrupted */
typedef int JSInterruptHandler (JSRuntime *rt, void *opaque);
void JS_SetInterruptHandler (JSRuntime *rt, JSInterruptHandler *cb,
void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
void *opaque);
/* select which debug info is stripped from the compiled code */
#define JS_STRIP_SOURCE (1 << 0) /* strip source code */
@@ -779,27 +769,9 @@ uint8_t *JS_WriteObject2 (JSContext *ctx, size_t *psize, JSValue obj,
#define JS_READ_OBJ_REFERENCE (1 << 3) /* allow object references */
JSValue JS_ReadObject (JSContext *ctx, const uint8_t *buf, size_t buf_len,
int flags);
/* instantiate and evaluate a bytecode function. Only used when
reading a script or module with JS_ReadObject() */
JSValue JS_EvalFunction (JSContext *ctx, JSValue fun_obj);
/* Eval function with environment record for variable resolution.
The env must be a stoned record. Variables are resolved env first,
then global intrinsics. */
JSValue JS_EvalFunctionEnv (JSContext *ctx, JSValue fun_obj, JSValue env);
/* Dump bytecode of a compiled function (for debugging) */
void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val);
/* Link compiled bytecode to context - resolves global references.
Returns linked bytecode on success, JS_EXCEPTION on link error. */
JSValue JS_LinkFunction (JSContext *ctx, JSValue func_val);
/* Link compiled bytecode with environment record for variable resolution.
Variables are resolved: env first, then global intrinsics.
Returns linked bytecode on success, JS_EXCEPTION on link error. */
JSValue JS_LinkFunctionEnv (JSContext *ctx, JSValue func_val, JSValue env);
/* C function definition */
typedef enum JSCFunctionEnum {
JS_CFUNC_generic,
@@ -811,7 +783,14 @@ typedef enum JSCFunctionEnum {
JS_CFUNC_1, /* JSValue f(ctx, this_val, arg0) */
JS_CFUNC_2, /* JSValue f(ctx, this_val, arg0, arg1) */
JS_CFUNC_3, /* JSValue f(ctx, this_val, arg0, arg1, arg2) */
JS_CFUNC_4
JS_CFUNC_4,
/* Pure functions (no this_val) - for global utility functions */
JS_CFUNC_PURE, /* JSValue f(ctx, argc, argv) - generic pure */
JS_CFUNC_PURE_0, /* JSValue f(ctx) */
JS_CFUNC_PURE_1, /* JSValue f(ctx, arg0) */
JS_CFUNC_PURE_2, /* JSValue f(ctx, arg0, arg1) */
JS_CFUNC_PURE_3, /* JSValue f(ctx, arg0, arg1, arg2) */
JS_CFUNC_PURE_4 /* JSValue f(ctx, arg0, arg1, arg2, arg3) */
} JSCFunctionEnum;
/* Fixed-arity C function types for fast paths */
@@ -827,6 +806,16 @@ typedef JSValue JSCFunction4 (JSContext *ctx, JSValue this_val,
JSValue arg0, JSValue arg1,
JSValue arg2, JSValue arg3);
/* Pure function types (no this_val) */
typedef JSValue JSCFunctionPure (JSContext *ctx, int argc, JSValue *argv);
typedef JSValue JSCFunctionPure0 (JSContext *ctx);
typedef JSValue JSCFunctionPure1 (JSContext *ctx, JSValue arg0);
typedef JSValue JSCFunctionPure2 (JSContext *ctx, JSValue arg0, JSValue arg1);
typedef JSValue JSCFunctionPure3 (JSContext *ctx, JSValue arg0, JSValue arg1,
JSValue arg2);
typedef JSValue JSCFunctionPure4 (JSContext *ctx, JSValue arg0, JSValue arg1,
JSValue arg2, JSValue arg3);
typedef union JSCFunctionType {
JSCFunction *generic;
JSValue (*generic_magic) (JSContext *ctx, JSValue this_val, int argc,
@@ -839,6 +828,13 @@ typedef union JSCFunctionType {
JSCFunction2 *f2;
JSCFunction3 *f3;
JSCFunction4 *f4;
/* Pure function pointers */
JSCFunctionPure *pure;
JSCFunctionPure0 *pure0;
JSCFunctionPure1 *pure1;
JSCFunctionPure2 *pure2;
JSCFunctionPure3 *pure3;
JSCFunctionPure4 *pure4;
} JSCFunctionType;
JSValue JS_NewCFunction2 (JSContext *ctx, JSCFunction *func, const char *name,
@@ -955,6 +951,43 @@ typedef struct JSCFunctionListEntry {
.u \
= {.func = { 3, JS_CFUNC_3, { .f3 = func1 } } } \
}
/* Pure function (no this_val) macros */
#define JS_CFUNC_PURE_DEF(name, length, func1) \
{ \
name, 0, JS_DEF_CFUNC, 0, \
.u \
= {.func = { length, JS_CFUNC_PURE, { .pure = func1 } } } \
}
#define JS_CFUNC_PURE0_DEF(name, func1) \
{ \
name, 0, JS_DEF_CFUNC, 0, \
.u \
= {.func = { 0, JS_CFUNC_PURE_0, { .pure0 = func1 } } } \
}
#define JS_CFUNC_PURE1_DEF(name, func1) \
{ \
name, 0, JS_DEF_CFUNC, 0, \
.u \
= {.func = { 1, JS_CFUNC_PURE_1, { .pure1 = func1 } } } \
}
#define JS_CFUNC_PURE2_DEF(name, func1) \
{ \
name, 0, JS_DEF_CFUNC, 0, \
.u \
= {.func = { 2, JS_CFUNC_PURE_2, { .pure2 = func1 } } } \
}
#define JS_CFUNC_PURE3_DEF(name, func1) \
{ \
name, 0, JS_DEF_CFUNC, 0, \
.u \
= {.func = { 3, JS_CFUNC_PURE_3, { .pure3 = func1 } } } \
}
#define JS_CFUNC_PURE4_DEF(name, func1) \
{ \
name, 0, JS_DEF_CFUNC, 0, \
.u \
= {.func = { 4, JS_CFUNC_PURE_4, { .pure4 = func1 } } } \
}
#define JS_ITERATOR_NEXT_DEF(name, length, func1, magic) \
{ \
name, 0, JS_DEF_CFUNC, magic, .u = { \
@@ -1067,12 +1100,186 @@ void *js_malloc_rt (size_t size);
void *js_mallocz_rt (size_t size);
void js_free_rt (void *ptr);
/* Intrinsic setup functions */
void JS_AddIntrinsicBaseObjects (JSContext *ctx);
void JS_AddIntrinsicBasicObjects (JSContext *ctx);
void JS_AddIntrinsicEval (JSContext *ctx);
void JS_AddIntrinsicRegExp (JSContext *ctx);
void JS_AddIntrinsicJSON (JSContext *ctx);
/* ============================================================================
Context-Neutral Module Format (CellModule)
============================================================================ */
/* Capture descriptor - what a nested function closes over */
typedef enum {
CAP_FROM_PARENT_LOCAL = 1, /* capture local from parent function */
CAP_FROM_PARENT_UPVALUE = 2 /* forward upvalue from parent's upvalues */
} CellCapKind;
typedef struct CellCapDesc {
uint8_t kind; /* CAP_FROM_PARENT_LOCAL or CAP_FROM_PARENT_UPVALUE */
uint16_t index; /* local index in parent, or upvalue index in parent */
} CellCapDesc;
/* External relocation - for integrate-time patching */
typedef enum {
EXT_GET = 1, /* OP_get_var -> OP_get_env_slot or OP_get_global_slot */
EXT_SET = 2 /* OP_put_var -> OP_set_env_slot or OP_set_global_slot */
} CellExtKind;
typedef struct CellExternalReloc {
uint32_t pc_offset; /* where operand lives in bytecode */
uint32_t name_sid; /* string id of the external name */
uint8_t kind; /* EXT_GET or EXT_SET */
} CellExternalReloc;
/* Constant types in cpool */
typedef enum {
CELL_CONST_NULL = 0,
CELL_CONST_INT = 1,
CELL_CONST_FLOAT = 2,
CELL_CONST_STRING = 3, /* string_sid into module string table */
CELL_CONST_UNIT = 4 /* unit_id for nested function */
} CellConstType;
typedef struct CellConst {
uint8_t type; /* CellConstType */
union {
int32_t i32;
double f64;
uint32_t string_sid;
uint32_t unit_id;
};
} CellConst;
/* Per-unit structure (context-neutral, flattened) */
typedef struct CellUnit {
/* Constant pool */
uint32_t const_count;
CellConst *constants;
/* Bytecode */
uint32_t bytecode_len;
uint8_t *bytecode;
/* Stack requirements */
uint16_t arg_count;
uint16_t var_count;
uint16_t stack_size;
/* Upvalue (capture) descriptors */
uint16_t upvalue_count;
CellCapDesc *upvalues;
/* External relocations */
uint32_t external_count;
CellExternalReloc *externals;
/* Debug info (optional) */
uint32_t pc2line_len;
uint8_t *pc2line;
uint32_t name_sid; /* unit name for stack traces */
} CellUnit;
/* Module-level structure (context-neutral) */
#define CELL_MODULE_MAGIC 0x4C4C4543 /* "CELL" */
#define CELL_MODULE_VERSION 1
typedef struct CellModule {
uint32_t magic; /* CELL_MODULE_MAGIC */
uint8_t version; /* CELL_MODULE_VERSION */
uint8_t flags;
/* Shared string table (module-global) */
uint32_t string_count;
uint32_t string_data_size;
uint8_t *string_data; /* concatenated UTF-8 strings */
uint32_t *string_offsets; /* offset for each string */
/* Unit table (entry 0 is the main/entry unit) */
uint32_t unit_count;
CellUnit *units;
/* Debug: source stored once at module level */
uint32_t source_len;
char *source;
} CellModule;
/* Free a CellModule and all its contents */
void cell_module_free (CellModule *mod);
/* Write a CellModule to a byte buffer.
Returns allocated buffer (caller must free with pjs_free), or NULL on error. */
uint8_t *cell_module_write (CellModule *mod, size_t *out_len);
/* Read a CellModule from a byte buffer.
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
CellModule *cell_module_read (const uint8_t *buf, size_t buf_len);
/* Convert compiled JSFunctionBytecode to CellModule.
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_func);
/* Compile source code directly to CellModule.
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename);
/* Parse source code and return AST as cJSON tree.
Caller must call cJSON_Delete() on result. */
struct cJSON *JS_ASTTree (const char *source, size_t len, const char *filename);
/* Parse source code and return AST as JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_AST (const char *source, size_t len, const char *filename);
/* Tokenize source code and return token array as JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_Tokenize (const char *source, size_t len, const char *filename);
/* Compiled bytecode (context-free, serializable) */
typedef struct MachCode MachCode;
/* Compile AST cJSON tree to context-free MachCode. */
MachCode *JS_CompileMachTree(struct cJSON *ast);
/* Compile AST JSON string to context-free MachCode. */
MachCode *JS_CompileMach(const char *ast_json);
/* Free a compiled MachCode tree. */
void JS_FreeMachCode(MachCode *mc);
/* Load compiled MachCode into a JSContext, materializing JSValues. */
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);
/* Compile AST cJSON tree to MCODE cJSON tree.
Caller must call cJSON_Delete() on result. */
struct cJSON *JS_McodeTree (struct cJSON *ast);
/* Compile AST JSON string to MCODE JSON string.
Returns malloc'd JSON string, or NULL on error. Caller must free. */
char *JS_Mcode (const char *ast_json);
/* Execute MCODE from cJSON tree. Takes ownership of root. */
JSValue JS_CallMcodeTree (JSContext *ctx, struct cJSON *root);
/* 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.
Returns NULL if no register VM frame is active.
Caller must call cJSON_Delete() on the result. */
struct cJSON *JS_GetStack (JSContext *ctx);
/* Integrate a CellModule with an environment and execute.
Returns callable function value, or JS_EXCEPTION on error. */
JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);
#undef js_unlikely
#undef inline

View File

@@ -266,11 +266,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context;
JS_FreeValue(js, actor->idx_buffer);
JS_FreeValue(js, actor->message_handle);
JS_FreeValue(js, actor->on_exception);
JS_FreeValue(js, actor->unneeded);
JS_FreeValue(js, actor->actor_sym);
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_DeleteGCRef(js, &actor->actor_sym_ref);
for (int i = 0; i < hmlen(actor->timers); i++) {
JS_FreeValue(js, actor->timers[i].value);
@@ -289,10 +289,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_SetInterruptHandler(js, NULL, NULL);
JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id);
pthread_mutex_unlock(actor->mutex);
@@ -419,8 +417,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret);
}
@@ -434,14 +432,13 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
{
if (actor->disrupt) return;
JS_FreeValue(actor->context, actor->unneeded);
if (!JS_IsFunction(fn)) {
actor->unneeded = JS_NULL;
actor->unneeded_ref.val = JS_NULL;
goto END;
}
actor->unneeded = JS_DupValue(actor->context, fn);
actor->unneeded_ref.val = fn;
actor->ar_secs = seconds;
END:
@@ -493,11 +490,10 @@ cell_rt *create_actor(void *wota)
actor->heap = mi_heap_new();
#endif
actor->init_wota = wota;
actor->idx_buffer = JS_NULL;
actor->message_handle = JS_NULL;
actor->unneeded = JS_NULL;
actor->on_exception = JS_NULL;
actor->actor_sym = JS_NULL;
/* GCRef fields are registered after JSContext creation in script_startup.
For now, zero-init from calloc is sufficient (val = 0 = JS_MKVAL(JS_TAG_INT,0),
which is not a pointer so GC-safe). The actual JS_NULL assignment and
JS_AddGCRef happen in script_startup. */
arrsetcap(actor->letters, 5);
@@ -587,7 +583,7 @@ void actor_turn(cell_rt *actor)
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size);
blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg);
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) {

View File

@@ -118,11 +118,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context;
JS_FreeValue(js, actor->idx_buffer);
JS_FreeValue(js, actor->message_handle);
JS_FreeValue(js, actor->on_exception);
JS_FreeValue(js, actor->unneeded);
JS_FreeValue(js, actor->actor_sym);
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
JS_DeleteGCRef(js, &actor->on_exception_ref);
JS_DeleteGCRef(js, &actor->message_handle_ref);
JS_DeleteGCRef(js, &actor->unneeded_ref);
JS_DeleteGCRef(js, &actor->actor_sym_ref);
/* Free timer callbacks stored in actor */
for (int i = 0; i < hmlen(actor->timers); i++) {
@@ -142,10 +142,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_SetInterruptHandler(js, NULL, NULL);
JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id);
free(actor);
@@ -157,14 +155,13 @@ void actor_free(cell_rt *actor)
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
{
if (actor->disrupt) return;
JS_FreeValue(actor->context, actor->unneeded);
if (!JS_IsFunction(actor->context, fn)) {
actor->unneeded = JS_NULL;
if (!JS_IsFunction(fn)) {
actor->unneeded_ref.val = JS_NULL;
goto END;
}
actor->unneeded = JS_DupValue(actor->context, fn);
actor->unneeded_ref.val = fn;
actor->ar_secs = seconds;
END:
@@ -257,8 +254,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret);
}
@@ -328,11 +325,7 @@ cell_rt *create_actor(void *wota)
{
cell_rt *actor = calloc(sizeof(*actor), 1);
actor->init_wota = wota;
actor->idx_buffer = JS_NULL;
actor->message_handle = JS_NULL;
actor->unneeded = JS_NULL;
actor->on_exception = JS_NULL;
actor->actor_sym = JS_NULL;
/* GCRef fields are registered after JSContext creation in script_startup. */
arrsetcap(actor->letters, 5);
@@ -408,7 +401,7 @@ void actor_turn(cell_rt *actor)
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size);
blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg);
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) {

View File

@@ -5,6 +5,7 @@
*/
#include "quickjs.h"
#include "cJSON.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
@@ -954,24 +955,6 @@ TEST(array_foreach_basic) {
return 1;
}
/* ============================================================================
GLOBAL OBJECT TEST
============================================================================ */
TEST(global_object) {
JSGCRef global_ref;
JS_PushGCRef(ctx, &global_ref);
global_ref.val = JS_GetGlobalObject(ctx);
ASSERT(JS_IsRecord(global_ref.val));
/* Set something on global */
JS_SetPropertyStr(ctx, global_ref.val, "testGlobal", JS_NewInt32(ctx, 777));
JSValue val = JS_GetPropertyStr(ctx, global_ref.val, "testGlobal");
JS_PopGCRef(ctx, &global_ref);
ASSERT_INT(val, 777);
return 1;
}
/* ============================================================================
VALUE CONVERSION TESTS
============================================================================ */
@@ -1521,23 +1504,6 @@ TEST(new_cfunction_with_args) {
return 1;
}
TEST(call_function_on_global) {
JSGCRef func_ref, global_ref;
JS_PushGCRef(ctx, &global_ref);
JS_PushGCRef(ctx, &func_ref);
global_ref.val = JS_GetGlobalObject(ctx);
func_ref.val = JS_NewCFunction(ctx, cfunc_return_42, "testFunc", 0);
JS_SetPropertyStr(ctx, global_ref.val, "testFunc", func_ref.val);
JSValue got = JS_GetPropertyStr(ctx, global_ref.val, "testFunc");
int is_func = JS_IsFunction(got);
JSValue result = JS_Call(ctx, got, JS_NULL, 0, NULL);
JS_PopGCRef(ctx, &func_ref);
JS_PopGCRef(ctx, &global_ref);
ASSERT(is_func);
ASSERT_INT(result, 42);
return 1;
}
/* ============================================================================
PROPERTY ACCESS TESTS
============================================================================ */
@@ -1873,6 +1839,580 @@ TEST(is_integer_vs_number) {
return 1;
}
/* ============================================================================
SERIALIZATION TESTS - JSON, NOTA, WOTA
============================================================================ */
/* stdlib.h provides free() */
#include <stdlib.h>
/* JSON Tests */
TEST(json_encode_object) {
/* Skip - requires GC rooting fixes in JS_JSONStringify */
return 1;
}
TEST(json_decode_object) {
/* Test using JS_ParseJSON directly instead of module API */
const char *json = "{\"x\":42,\"y\":\"test\"}";
JSValue result = JS_ParseJSON(ctx, json, strlen(json), "<test>");
int is_record = JS_IsRecord(result);
JSValue x = JS_GetPropertyStr(ctx, result, "x");
JSValue y = JS_GetPropertyStr(ctx, result, "y");
ASSERT(is_record);
ASSERT_INT(x, 42);
ASSERT_STR(y, "test");
return 1;
}
TEST(json_roundtrip_array) {
/* Skip - requires GC rooting fixes in JS_JSONStringify */
return 1;
}
/* NOTA Tests - use C API directly (value2nota/nota2value) */
void *value2nota(JSContext *ctx, JSValue v);
JSValue nota2value(JSContext *ctx, void *nota);
TEST(nota_encode_int) {
void *encoded = value2nota(ctx, JS_NewInt32(ctx, 42));
ASSERT(encoded != NULL);
JSValue decoded = nota2value(ctx, encoded);
free(encoded);
ASSERT_INT(decoded, 42);
return 1;
}
TEST(nota_roundtrip_object) {
/* Skip - requires GC rooting fixes in nota_encode_value */
return 1;
}
TEST(nota_encode_null) {
void *encoded = value2nota(ctx, JS_NULL);
ASSERT(encoded != NULL);
JSValue decoded = nota2value(ctx, encoded);
free(encoded);
ASSERT_NULL(decoded);
return 1;
}
TEST(nota_encode_bool) {
void *enc_true = value2nota(ctx, JS_TRUE);
ASSERT(enc_true != NULL);
JSValue dec_true = nota2value(ctx, enc_true);
free(enc_true);
void *enc_false = value2nota(ctx, JS_FALSE);
ASSERT(enc_false != NULL);
JSValue dec_false = nota2value(ctx, enc_false);
free(enc_false);
ASSERT_TRUE(dec_true);
ASSERT_FALSE(dec_false);
return 1;
}
/* WOTA Tests - use C API directly (value2wota/wota2value) */
void *value2wota(JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes);
JSValue wota2value(JSContext *ctx, void *wota);
TEST(wota_encode_int) {
size_t bytes;
void *encoded = value2wota(ctx, JS_NewInt32(ctx, 42), JS_NULL, &bytes);
ASSERT(encoded != NULL);
ASSERT(bytes > 0);
JSValue decoded = wota2value(ctx, encoded);
free(encoded);
ASSERT_INT(decoded, 42);
return 1;
}
TEST(wota_roundtrip_object) {
JSGCRef obj_ref;
JS_PushGCRef(ctx, &obj_ref);
obj_ref.val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj_ref.val, "val", JS_NewInt32(ctx, 999));
JS_SetPropertyStr(ctx, obj_ref.val, "name", JS_NewString(ctx, "wota"));
size_t bytes;
void *encoded = value2wota(ctx, obj_ref.val, JS_NULL, &bytes);
JS_PopGCRef(ctx, &obj_ref);
ASSERT(encoded != NULL);
JSValue decoded = wota2value(ctx, encoded);
free(encoded);
int is_record = JS_IsRecord(decoded);
JSValue val = JS_GetPropertyStr(ctx, decoded, "val");
JSValue name = JS_GetPropertyStr(ctx, decoded, "name");
ASSERT(is_record);
ASSERT_INT(val, 999);
ASSERT_STR(name, "wota");
return 1;
}
TEST(wota_encode_nested_array) {
JSGCRef arr_ref, inner_ref;
JS_PushGCRef(ctx, &arr_ref);
JS_PushGCRef(ctx, &inner_ref);
arr_ref.val = JS_NewArray(ctx);
inner_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &inner_ref.val, JS_NewInt32(ctx, 10));
JS_ArrayPush(ctx, &inner_ref.val, JS_NewInt32(ctx, 20));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr_ref.val, inner_ref.val);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 3));
size_t bytes;
void *encoded = value2wota(ctx, arr_ref.val, JS_NULL, &bytes);
JS_PopGCRef(ctx, &inner_ref);
JS_PopGCRef(ctx, &arr_ref);
ASSERT(encoded != NULL);
JSValue decoded = wota2value(ctx, encoded);
free(encoded);
int is_arr = JS_IsArray(decoded);
int64_t len;
JS_GetLength(ctx, decoded, &len);
JSValue v0 = JS_GetPropertyUint32(ctx, decoded, 0);
JSValue v2 = JS_GetPropertyUint32(ctx, decoded, 2);
JSValue inner = JS_GetPropertyUint32(ctx, decoded, 1);
int inner_is_arr = JS_IsArray(inner);
int64_t inner_len;
JS_GetLength(ctx, inner, &inner_len);
ASSERT(is_arr);
ASSERT(len == 3);
ASSERT_INT(v0, 1);
ASSERT_INT(v2, 3);
ASSERT(inner_is_arr);
ASSERT(inner_len == 2);
return 1;
}
TEST(wota_encode_blob) {
/* Skip blob test - requires js_new_blob_stoned_copy which is in quickjs.c */
return 1;
}
/* ============================================================================
CELL MODULE TESTS - Serialize/Deserialize bytecode
============================================================================ */
TEST(cell_module_compile_basic) {
/* Compile simple source to CellModule */
const char *source = "1 + 2";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Check module has units */
ASSERT_MSG(mod->unit_count > 0, "Module has no units");
ASSERT_MSG(mod->units[0].bytecode_len > 0, "Unit has no bytecode");
cell_module_free(mod);
return 1;
}
TEST(cell_module_write_read) {
/* Compile, serialize, deserialize */
const char *source = "var x = 10; x * 2";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Serialize */
size_t len;
uint8_t *buf = cell_module_write(mod, &len);
ASSERT_MSG(buf != NULL, "cell_module_write returned NULL");
ASSERT_MSG(len > 0, "cell_module_write produced empty buffer");
/* Deserialize */
CellModule *mod2 = cell_module_read(buf, len);
free(buf);
ASSERT_MSG(mod2 != NULL, "cell_module_read returned NULL");
/* Verify structure matches */
ASSERT_MSG(mod2->unit_count == mod->unit_count, "unit_count mismatch");
ASSERT_MSG(mod2->string_count == mod->string_count, "string_count mismatch");
cell_module_free(mod);
cell_module_free(mod2);
return 1;
}
TEST(cell_module_integrate_basic) {
/* Compile, then integrate and execute */
const char *source = "3 + 4";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Integrate into context */
JSValue func = cell_module_integrate(ctx, mod, JS_NULL);
if (JS_IsException(func)) {
cell_module_free(mod);
ASSERT_MSG(0, "cell_module_integrate threw exception");
}
/* Execute */
JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL);
JS_FreeValue(ctx, func);
cell_module_free(mod);
if (JS_IsException(result)) {
ASSERT_MSG(0, "JS_Call threw exception");
}
ASSERT_INT(result, 7);
return 1;
}
TEST(cell_module_roundtrip_execute) {
/* Full round-trip: compile -> write -> read -> integrate -> execute */
const char *source = "var a = 5; var b = 3; a * b";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Serialize */
size_t len;
uint8_t *buf = cell_module_write(mod, &len);
cell_module_free(mod);
ASSERT_MSG(buf != NULL, "cell_module_write returned NULL");
/* Deserialize */
CellModule *mod2 = cell_module_read(buf, len);
free(buf);
ASSERT_MSG(mod2 != NULL, "cell_module_read returned NULL");
/* Integrate and execute */
JSValue func = cell_module_integrate(ctx, mod2, JS_NULL);
cell_module_free(mod2);
if (JS_IsException(func)) {
ASSERT_MSG(0, "cell_module_integrate threw exception");
}
JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL);
JS_FreeValue(ctx, func);
if (JS_IsException(result)) {
ASSERT_MSG(0, "JS_Call threw exception");
}
ASSERT_INT(result, 15);
return 1;
}
TEST(cell_module_string_constant) {
/* Test string constant handling */
const char *source = "'hello' + ' world'";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Verify string table has entries */
ASSERT_MSG(mod->string_count > 0, "Module has no strings");
/* Integrate and execute */
JSValue func = cell_module_integrate(ctx, mod, JS_NULL);
cell_module_free(mod);
if (JS_IsException(func)) {
ASSERT_MSG(0, "cell_module_integrate threw exception");
}
JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL);
JS_FreeValue(ctx, func);
if (JS_IsException(result)) {
ASSERT_MSG(0, "JS_Call threw exception");
}
ASSERT_STR(result, "hello world");
return 1;
}
/* ============================================================================
ERROR RECOVERY TESTS - Helper macros
============================================================================ */
#define ASSERT_HAS_ERRORS(json_str, min_count) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
if (!_errs || !cJSON_IsArray(_errs) || cJSON_GetArraySize(_errs) < (min_count)) { \
printf("[line %d: expected at least %d error(s), got %d] ", __LINE__, (min_count), \
_errs && cJSON_IsArray(_errs) ? cJSON_GetArraySize(_errs) : 0); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
#define ASSERT_NO_ERRORS(json_str) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
if (_errs && cJSON_IsArray(_errs) && cJSON_GetArraySize(_errs) > 0) { \
cJSON *_first = cJSON_GetArrayItem(_errs, 0); \
const char *_msg = cJSON_GetStringValue(cJSON_GetObjectItem(_first, "message")); \
printf("[line %d: expected no errors, got: %s] ", __LINE__, _msg ? _msg : "?"); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
#define ASSERT_ERROR_MSG_CONTAINS(json_str, substring) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
int _found = 0; \
if (_errs && cJSON_IsArray(_errs)) { \
cJSON *_e; \
cJSON_ArrayForEach(_e, _errs) { \
const char *_msg = cJSON_GetStringValue(cJSON_GetObjectItem(_e, "message")); \
if (_msg && strstr(_msg, (substring))) { _found = 1; break; } \
} \
} \
if (!_found) { \
printf("[line %d: no error containing '%s'] ", __LINE__, (substring)); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
/* ============================================================================
TOKENIZER ERROR TESTS
============================================================================ */
TEST(tokenize_unterminated_string) {
const char *src = "var x = \"hello";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated string");
free(json);
return 1;
}
TEST(tokenize_unterminated_template) {
const char *src = "var x = `hello";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated template");
free(json);
return 1;
}
TEST(tokenize_unterminated_block_comment) {
const char *src = "var x /* comment";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated block comment");
free(json);
return 1;
}
TEST(tokenize_malformed_hex) {
const char *src = "var x = 0x";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "malformed hex");
free(json);
return 1;
}
TEST(tokenize_malformed_binary) {
const char *src = "var x = 0b";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "malformed binary");
free(json);
return 1;
}
TEST(tokenize_malformed_exponent) {
const char *src = "var x = 1e+";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "no digits after exponent");
free(json);
return 1;
}
TEST(tokenize_valid_no_errors) {
const char *src = "var x = 42";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
PARSER ERROR TESTS
============================================================================ */
TEST(ast_missing_identifier_after_var) {
const char *src = "var = 1";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "expected identifier");
free(json);
return 1;
}
TEST(ast_missing_initializer_def) {
const char *src = "def x";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "missing initializer");
free(json);
return 1;
}
TEST(ast_recovery_continues_after_error) {
const char *src = "var = 1; var y = 2";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_HAS_ERRORS(json, 1);
/* Check that 'y' statement is present in the AST */
ASSERT_MSG(strstr(json, "\"y\"") != NULL, "recovery failed: 'y' not in AST");
free(json);
return 1;
}
TEST(ast_valid_no_errors) {
const char *src = "var x = 1; var y = 2";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
AST SEMANTIC ERROR TESTS
============================================================================ */
TEST(ast_sem_assign_to_const) {
const char *src = "def x = 5; x = 3";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_assign_to_arg) {
const char *src = "function(x) { x = 5; }";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_redeclare_const) {
const char *src = "def x = 1; def x = 2";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot redeclare constant");
free(json);
return 1;
}
TEST(ast_sem_break_outside_loop) {
const char *src = "break";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
free(json);
return 1;
}
TEST(ast_sem_continue_outside_loop) {
const char *src = "continue";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
free(json);
return 1;
}
TEST(ast_sem_break_inside_loop_ok) {
const char *src = "while (true) { break; }";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_increment_const) {
const char *src = "def x = 1; x++";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_shadow_var_ok) {
const char *src = "var array = []; array";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_var_assign_ok) {
const char *src = "var x = 1; x = x + 1";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_nested_function_scope) {
const char *src = "var x = 1; function f(x) { return x + 1; }";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
CODEGEN TESTS (updated for new direct AST-to-bytecode compiler)
============================================================================ */
TEST(mach_compile_basic) {
const char *src = "var x = 1; x = x + 1";
cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
MachCode *mc = JS_CompileMachTree(ast);
cJSON_Delete(ast);
ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
JS_FreeMachCode(mc);
return 1;
}
TEST(mach_compile_function) {
const char *src = "function f(x) { return x + 1 }";
cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
MachCode *mc = JS_CompileMachTree(ast);
cJSON_Delete(ast);
ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
JS_FreeMachCode(mc);
return 1;
}
/* ============================================================================
MAIN TEST RUNNER
============================================================================ */
@@ -1951,9 +2491,6 @@ int run_c_test_suite(JSContext *ctx)
RUN_TEST(strict_eq_null);
RUN_TEST(strict_eq_bool);
printf("\nGlobal Object:\n");
RUN_TEST(global_object);
printf("\nValue Conversions:\n");
RUN_TEST(to_bool_true_values);
RUN_TEST(to_bool_false_values);
@@ -2026,7 +2563,6 @@ int run_c_test_suite(JSContext *ctx)
printf("\nC Functions:\n");
RUN_TEST(new_cfunction_no_args);
RUN_TEST(new_cfunction_with_args);
RUN_TEST(call_function_on_global);
printf("\nProperty Access:\n");
RUN_TEST(get_property_with_jsvalue_key);
@@ -2069,6 +2605,61 @@ int run_c_test_suite(JSContext *ctx)
RUN_TEST(is_function_check);
RUN_TEST(is_integer_vs_number);
printf("\nSerialization - JSON:\n");
RUN_TEST(json_encode_object);
RUN_TEST(json_decode_object);
RUN_TEST(json_roundtrip_array);
printf("\nSerialization - NOTA:\n");
RUN_TEST(nota_encode_int);
RUN_TEST(nota_roundtrip_object);
RUN_TEST(nota_encode_null);
RUN_TEST(nota_encode_bool);
printf("\nSerialization - WOTA:\n");
RUN_TEST(wota_encode_int);
RUN_TEST(wota_roundtrip_object);
RUN_TEST(wota_encode_nested_array);
RUN_TEST(wota_encode_blob);
// CellModule tests
RUN_TEST(cell_module_compile_basic);
RUN_TEST(cell_module_write_read);
RUN_TEST(cell_module_integrate_basic);
RUN_TEST(cell_module_roundtrip_execute);
RUN_TEST(cell_module_string_constant);
printf("\nTokenizer Errors:\n");
RUN_TEST(tokenize_unterminated_string);
RUN_TEST(tokenize_unterminated_template);
RUN_TEST(tokenize_unterminated_block_comment);
RUN_TEST(tokenize_malformed_hex);
RUN_TEST(tokenize_malformed_binary);
RUN_TEST(tokenize_malformed_exponent);
RUN_TEST(tokenize_valid_no_errors);
printf("\nParser Errors:\n");
RUN_TEST(ast_missing_identifier_after_var);
RUN_TEST(ast_missing_initializer_def);
RUN_TEST(ast_recovery_continues_after_error);
RUN_TEST(ast_valid_no_errors);
printf("\nAST Semantic Errors:\n");
RUN_TEST(ast_sem_assign_to_const);
RUN_TEST(ast_sem_assign_to_arg);
RUN_TEST(ast_sem_redeclare_const);
RUN_TEST(ast_sem_break_outside_loop);
RUN_TEST(ast_sem_continue_outside_loop);
RUN_TEST(ast_sem_break_inside_loop_ok);
RUN_TEST(ast_sem_increment_const);
RUN_TEST(ast_sem_shadow_var_ok);
RUN_TEST(ast_sem_var_assign_ok);
RUN_TEST(ast_sem_nested_function_scope);
printf("\nCodegen:\n");
RUN_TEST(mach_compile_basic);
RUN_TEST(mach_compile_function);
printf("\n=================================\n");
printf("Results: %d passed, %d failed\n", tests_passed, tests_failed);
printf("=================================\n\n");

657
syntax_suite.ce Normal file
View File

@@ -0,0 +1,657 @@
// Syntax suite: covers every syntactic feature of cell script
// Run: ./cell --mach-run syntax_suite.ce
var passed = 0
var failed = 0
var error_names = []
var error_reasons = []
var fail_msg = ""
for (var _i = 0; _i < 100; _i++) {
error_names[] = null
error_reasons[] = null
}
var fail = function(msg) {
fail_msg = msg
disrupt
}
var assert_eq = function(actual, expected, msg) {
if (actual != expected) fail(msg + " (got=" + text(actual) + " expected=" + text(expected) + ")")
}
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 should_disrupt = function(fn) {
var caught = false
var wrapper = function() {
fn()
} disruption {
caught = true
}
wrapper()
return caught
}
// === LITERALS ===
run("number literals", function() {
assert_eq(42, 42, "integer")
assert_eq(3.14 > 3, true, "float")
assert_eq(-5, -5, "negative")
assert_eq(0, 0, "zero")
assert_eq(1e3, 1000, "scientific")
})
run("string literals", function() {
assert_eq("hello", "hello", "double quote")
assert_eq("", "", "empty string")
assert_eq("line1\nline2" != "line1line2", true, "escape sequence")
})
run("template literals", function() {
var x = "world"
assert_eq(`hello ${x}`, "hello world", "interpolation")
assert_eq(`${1 + 2}`, "3", "expression interpolation")
assert_eq(`plain`, "plain", "no interpolation")
})
run("boolean literals", function() {
assert_eq(true, true, "true")
assert_eq(false, false, "false")
})
run("null literal", function() {
assert_eq(null, null, "null")
})
run("array literal", function() {
var a = [1, 2, 3]
assert_eq(length(a), 3, "array length")
assert_eq(a[0], 1, "first element")
var e = []
assert_eq(length(e), 0, "empty array")
})
run("object literal", function() {
var o = {a: 1, b: "two"}
assert_eq(o.a, 1, "object prop")
var e = {}
assert_eq(e.x, null, "empty object missing prop")
})
run("regex literal", function() {
var r = /\d+/
var result = extract("abc123", r)
assert_eq(result[0], "123", "regex match")
var ri = /hello/i
var result2 = extract("Hello", ri)
assert_eq(result2[0], "Hello", "regex flags")
})
// === DECLARATIONS ===
run("var declaration", function() {
var x = 5
assert_eq(x, 5, "var init")
})
run("var uninitialized", function() {
var x
assert_eq(x, null, "var defaults to null")
})
run("var multiple declaration", function() {
var a = 1, b = 2, c = 3
assert_eq(a + b + c, 6, "multi var")
})
run("def declaration", function() {
def x = 42
assert_eq(x, 42, "def const")
})
// === ARITHMETIC OPERATORS ===
run("arithmetic operators", function() {
assert_eq(2 + 3, 5, "add")
assert_eq(5 - 3, 2, "sub")
assert_eq(3 * 4, 12, "mul")
assert_eq(12 / 4, 3, "div")
assert_eq(10 % 3, 1, "mod")
assert_eq(2 ** 3, 8, "exp")
})
// === COMPARISON OPERATORS ===
run("comparison operators", function() {
assert_eq(5 == 5, true, "eq")
assert_eq(5 != 6, true, "neq")
assert_eq(3 < 5, true, "lt")
assert_eq(5 > 3, true, "gt")
assert_eq(3 <= 3, true, "lte")
assert_eq(5 >= 5, true, "gte")
})
// === LOGICAL OPERATORS ===
run("logical operators", function() {
assert_eq(true && true, true, "and")
assert_eq(true && false, false, "and false")
assert_eq(false || true, true, "or")
assert_eq(false || false, false, "or false")
assert_eq(!true, false, "not")
assert_eq(!false, true, "not false")
})
run("short circuit", function() {
var called = false
var fn = function() { called = true; return true }
var r = false && fn()
assert_eq(called, false, "and short circuit")
r = true || fn()
assert_eq(called, false, "or short circuit")
})
// === BITWISE OPERATORS ===
run("bitwise operators", function() {
assert_eq(5 & 3, 1, "and")
assert_eq(5 | 3, 7, "or")
assert_eq(5 ^ 3, 6, "xor")
assert_eq(~0, -1, "not")
assert_eq(1 << 3, 8, "lshift")
assert_eq(8 >> 3, 1, "rshift")
assert_eq(-1 >>> 1, 2147483647, "unsigned rshift")
})
// === UNARY OPERATORS ===
run("unary operators", function() {
assert_eq(+5, 5, "unary plus")
assert_eq(-5, -5, "unary minus")
assert_eq(-(-5), 5, "double negate")
})
run("increment decrement", function() {
var x = 5
assert_eq(x++, 5, "postfix inc returns old")
assert_eq(x, 6, "postfix inc side effect")
x = 5
assert_eq(++x, 6, "prefix inc returns new")
x = 5
assert_eq(x--, 5, "postfix dec returns old")
assert_eq(x, 4, "postfix dec side effect")
x = 5
assert_eq(--x, 4, "prefix dec returns new")
})
// === COMPOUND ASSIGNMENT ===
run("compound assignment", function() {
var x = 10
x += 3; assert_eq(x, 13, "+=")
x -= 3; assert_eq(x, 10, "-=")
x *= 2; assert_eq(x, 20, "*=")
x /= 4; assert_eq(x, 5, "/=")
x %= 3; assert_eq(x, 2, "%=")
})
// === TERNARY OPERATOR ===
run("ternary operator", function() {
var a = true ? 1 : 2
assert_eq(a, 1, "ternary true")
var b = false ? 1 : 2
assert_eq(b, 2, "ternary false")
var c = true ? (false ? 1 : 2) : 3
assert_eq(c, 2, "ternary nested")
})
// === COMMA OPERATOR ===
run("comma operator", function() {
var x = (1, 2, 3)
assert_eq(x, 3, "comma returns last")
})
// === IN OPERATOR ===
run("in operator", function() {
var o = {a: 1}
assert_eq("a" in o, true, "key exists")
assert_eq("b" in o, false, "key missing")
})
// === DELETE OPERATOR ===
run("delete operator", function() {
var o = {a: 1, b: 2}
delete o.a
assert_eq("a" in o, false, "delete removes key")
assert_eq(o.b, 2, "delete leaves others")
})
// === PROPERTY ACCESS ===
run("dot access", function() {
var o = {x: 10}
assert_eq(o.x, 10, "dot read")
o.x = 20
assert_eq(o.x, 20, "dot write")
})
run("bracket access", function() {
var o = {x: 10}
assert_eq(o["x"], 10, "bracket read")
var key = "x"
assert_eq(o[key], 10, "computed bracket")
o["y"] = 20
assert_eq(o.y, 20, "bracket write")
})
run("object-as-key", function() {
var k = {}
var o = {}
o[k] = 42
assert_eq(o[k], 42, "object key set/get")
assert_eq(o[{}], null, "new object is different key")
assert_eq(k in o, true, "object key in")
delete o[k]
assert_eq(k in o, false, "object key delete")
})
run("chained access", function() {
var d = {a: {b: [1, {c: 99}]}}
assert_eq(d.a.b[1].c, 99, "mixed chain")
})
// === ARRAY PUSH/POP SYNTAX ===
run("array push pop", function() {
var a = [1, 2]
a[] = 3
assert_eq(length(a), 3, "push length")
assert_eq(a[2], 3, "push value")
var v = a[]
assert_eq(v, 3, "pop value")
assert_eq(length(a), 2, "pop length")
})
// === CONTROL FLOW: IF/ELSE ===
run("if else", function() {
var x = 0
if (true) x = 1
assert_eq(x, 1, "if true")
if (false) x = 2 else x = 3
assert_eq(x, 3, "if else")
if (false) x = 4
else if (true) x = 5
else x = 6
assert_eq(x, 5, "else if")
})
// === CONTROL FLOW: WHILE ===
run("while loop", function() {
var i = 0
while (i < 5) i++
assert_eq(i, 5, "while basic")
})
run("while break continue", function() {
var i = 0
while (true) {
if (i >= 3) break
i++
}
assert_eq(i, 3, "while break")
var sum = 0
i = 0
while (i < 5) {
i++
if (i % 2 == 0) continue
sum += i
}
assert_eq(sum, 9, "while continue")
})
// === CONTROL FLOW: FOR ===
run("for loop", function() {
var sum = 0
for (var i = 0; i < 5; i++) sum += i
assert_eq(sum, 10, "for basic")
})
run("for break continue", function() {
var sum = 0
for (var i = 0; i < 10; i++) {
if (i == 5) break
sum += i
}
assert_eq(sum, 10, "for break")
sum = 0
for (var i = 0; i < 5; i++) {
if (i % 2 == 0) continue
sum += i
}
assert_eq(sum, 4, "for continue")
})
run("nested for", function() {
var sum = 0
for (var i = 0; i < 3; i++)
for (var j = 0; j < 3; j++)
sum++
assert_eq(sum, 9, "nested for")
})
// === BLOCK SCOPING ===
run("block scoping", function() {
var x = 1
{
var x = 2
assert_eq(x, 2, "inner block")
}
assert_eq(x, 1, "outer preserved")
})
run("for iterator scope", function() {
for (var i = 0; i < 1; i++) {}
assert_eq(should_disrupt(function() { var y = i }), true, "for var does not leak")
})
// === FUNCTIONS ===
run("function expression", function() {
var fn = function(a, b) { return a + b }
assert_eq(fn(2, 3), 5, "basic call")
})
run("arrow function", function() {
var double = x => x * 2
assert_eq(double(5), 10, "arrow single param")
var add = (a, b) => a + b
assert_eq(add(2, 3), 5, "arrow multi param")
var block = x => {
var y = x * 2
return y + 1
}
assert_eq(block(5), 11, "arrow block body")
})
run("function no return", function() {
var fn = function() { var x = 1 }
assert_eq(fn(), null, "no return gives null")
})
run("function early return", function() {
var fn = function() { return 1; return 2 }
assert_eq(fn(), 1, "early return")
})
run("extra and missing args", function() {
var fn = function(a, b) { return a + b }
assert_eq(fn(1, 2, 3), 3, "extra args ignored")
var fn2 = function(a, b) { return a }
assert_eq(fn2(1), 1, "missing args ok")
})
run("iife", function() {
var r = (function(x) { return x * 2 })(21)
assert_eq(r, 42, "immediately invoked")
})
// === CLOSURES ===
run("closure", function() {
var make = function(x) {
return function(y) { return x + y }
}
var add5 = make(5)
assert_eq(add5(3), 8, "closure captures")
})
run("closure mutation", function() {
var counter = function() {
var n = 0
return function() { n = n + 1; return n }
}
var c = counter()
assert_eq(c(), 1, "first")
assert_eq(c(), 2, "second")
})
// === RECURSION ===
run("recursion", function() {
var fact = function(n) {
if (n <= 1) return 1
return n * fact(n - 1)
}
assert_eq(fact(5), 120, "factorial")
})
// === THIS BINDING ===
run("this binding", function() {
var obj = {
val: 10,
get: function() { return this.val }
}
assert_eq(obj.get(), 10, "method this")
})
// === DISRUPTION ===
run("disrupt keyword", function() {
assert_eq(should_disrupt(function() { disrupt }), true, "bare disrupt")
})
run("disruption handler", function() {
var x = 0
var fn = function() { x = 1 } disruption { x = 2 }
fn()
assert_eq(x, 1, "no disruption path")
var fn2 = function() { disrupt } disruption { x = 3 }
fn2()
assert_eq(x, 3, "disruption caught")
})
run("disruption re-raise", function() {
var outer_caught = false
var outer = function() {
var inner = function() { disrupt } disruption { disrupt }
inner()
} disruption {
outer_caught = true
}
outer()
assert_eq(outer_caught, true, "re-raise propagates")
})
// === PROTOTYPAL INHERITANCE ===
run("meme and proto", function() {
var parent = {x: 10}
var child = meme(parent)
assert_eq(child.x, 10, "inherited prop")
assert_eq(proto(child), parent, "proto returns parent")
child.x = 20
assert_eq(parent.x, 10, "override does not mutate parent")
})
run("meme with mixins", function() {
var p = {a: 1}
var m1 = {b: 2}
var m2 = {c: 3}
var child = meme(p, [m1, m2])
assert_eq(child.a, 1, "parent prop")
assert_eq(child.b, 2, "mixin1")
assert_eq(child.c, 3, "mixin2")
})
// === STONE (FREEZE) ===
run("stone", function() {
var o = {x: 1}
assert_eq(is_stone(o), false, "not frozen")
stone(o)
assert_eq(is_stone(o), true, "frozen")
assert_eq(should_disrupt(function() { o.x = 2 }), true, "write disrupts")
})
// === FUNCTION PROXY ===
run("function proxy", function() {
var proxy = function(name, args) {
return `${name}:${length(args)}`
}
assert_eq(proxy.hello(), "hello:0", "proxy dot call")
assert_eq(proxy.add(1, 2), "add:2", "proxy with args")
assert_eq(proxy["method"](), "method:0", "proxy bracket call")
var m = "dynamic"
assert_eq(proxy[m](), "dynamic:0", "proxy computed name")
})
run("non-proxy function prop access disrupts", function() {
var fn = function() { return 1 }
assert_eq(should_disrupt(function() { var x = fn.foo }), true, "prop read disrupts")
assert_eq(should_disrupt(function() { fn.foo = 1 }), true, "prop write disrupts")
})
// === TYPE CHECKING ===
run("is_* functions", function() {
assert_eq(is_number(42), true, "is_number")
assert_eq(is_text("hi"), true, "is_text")
assert_eq(is_logical(true), true, "is_logical")
assert_eq(is_object({}), true, "is_object")
assert_eq(is_array([]), true, "is_array")
assert_eq(is_function(function(){}), true, "is_function")
assert_eq(is_null(null), true, "is_null")
assert_eq(is_object([]), false, "array not object")
assert_eq(is_array({}), false, "object not array")
})
// === TRUTHINESS / FALSINESS ===
run("falsy values", function() {
if (false) fail("false")
if (0) fail("0")
if ("") fail("empty string")
if (null) fail("null")
assert_eq(true, true, "all falsy passed")
})
run("truthy values", function() {
if (!1) fail("1")
if (!"hi") fail("string")
if (!{}) fail("object")
if (![]) fail("array")
if (!true) fail("true")
assert_eq(true, true, "all truthy passed")
})
// === VARIABLE SHADOWING ===
run("variable shadowing", function() {
var x = 10
var fn = function() {
var x = 20
return x
}
assert_eq(fn(), 20, "inner shadows")
assert_eq(x, 10, "outer unchanged")
})
// === OPERATOR PRECEDENCE ===
run("precedence", function() {
assert_eq(2 + 3 * 4, 14, "mul before add")
assert_eq((2 + 3) * 4, 20, "parens override")
assert_eq(-2 * 3, -6, "unary before mul")
})
// === CURRYING / HIGHER-ORDER ===
run("curried function", function() {
var f = function(a) {
return function(b) {
return function(c) { return a + b + c }
}
}
assert_eq(f(1)(2)(3), 6, "triple curry")
})
// === SELF-REFERENCING STRUCTURES ===
run("self-referencing object", function() {
var o = {name: "root"}
o.self = o
assert_eq(o.self.self.name, "root", "cycle access")
})
// === IDENTIFIER ? AND ! ===
run("question mark in identifier", function() {
var nil? = (x) => x == null
assert_eq(nil?(null), true, "nil? null")
assert_eq(nil?(42), false, "nil? 42")
})
run("bang in identifier", function() {
var set! = (x) => x + 1
assert_eq(set!(5), 6, "set! call")
})
run("question mark mid identifier", function() {
var is?valid = (x) => x > 0
assert_eq(is?valid(3), true, "is?valid true")
assert_eq(is?valid(-1), false, "is?valid false")
})
run("bang mid identifier", function() {
var do!stuff = () => 42
assert_eq(do!stuff(), 42, "do!stuff call")
})
run("ternary after question ident", function() {
var nil? = (x) => x == null
var a = nil?(null) ? "yes" : "no"
assert_eq(a, "yes", "ternary true branch")
var b = nil?(42) ? "yes" : "no"
assert_eq(b, "no", "ternary false branch")
})
run("bang not confused with logical not", function() {
assert_eq(!true, false, "logical not true")
assert_eq(!false, true, "logical not false")
})
run("inequality not confused with bang ident", function() {
assert_eq(1 != 2, true, "inequality true")
assert_eq(1 != 1, false, "inequality false")
})
// === SUMMARY ===
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
if (failed > 0) {
print("")
for (var _j = 0; _j < failed; _j++) {
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
}
}

92
test.ce
View File

@@ -29,12 +29,13 @@ function is_valid_package(dir) {
// Get current package name from cell.toml or null
function get_current_package_name() {
if (!is_valid_package('.')) return null
try {
var pkg_name = 'local'
var do_load = function() {
var config = pkg.load_config(null)
return config.package || 'local'
} catch (e) {
return 'local'
}
if (config.package) pkg_name = config.package
} disruption {}
do_load()
return pkg_name
}
// Parse arguments
@@ -229,21 +230,48 @@ function spawn_actor_test(test_info) {
actor: null
}
try {
var spawn_disrupted = false
var do_spawn = function() {
// Spawn the actor test - it should send back results
var actor_path = text(test_info.path, 0, -3) // remove .ce
entry.actor = $start(actor_path)
push(pending_actor_tests, entry)
} catch (e) {
} disruption {
spawn_disrupted = true
}
do_spawn()
if (spawn_disrupted) {
entry.status = "failed"
entry.error = { message: `Failed to spawn actor: ${e}` }
entry.error = { message: `Failed to spawn actor: ${test_name}` }
entry.duration_ns = 0
push(actor_test_results, entry)
log.console(` FAIL ${test_name}: `)
log.error(e)
log.console(` FAIL ${test_name}`)
}
}
// Test runner with disruption support
var test_passed = true
var test_error_msg = ""
var test_error_stack = ""
var run_test = function(fn) {
test_passed = true
test_error_msg = ""
test_error_stack = ""
var ret = fn()
if (is_text(ret)) {
test_passed = false
test_error_msg = ret
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
test_passed = false
test_error_msg = ret.message || text(ret)
if (ret.stack) test_error_stack = ret.stack
}
} disruption {
test_passed = false
if (test_error_msg == "") test_error_msg = "test disrupted"
}
function run_tests(package_name, specific_test) {
var prefix = get_pkg_dir(package_name)
var tests_dir = prefix + '/tests'
@@ -293,7 +321,9 @@ function run_tests(package_name, specific_test) {
failed: 0
}
try {
var load_disrupted = false
var load_error_msg = ""
var do_load = function() {
var test_mod
// For local packages (null), use the current directory as package context
var use_pkg = package_name ? package_name : fd.realpath('.')
@@ -322,34 +352,23 @@ function run_tests(package_name, specific_test) {
}
var start_time = time.number()
try {
var ret = t.fn()
if (is_text(ret)) {
throw Error(ret)
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
throw ret
}
run_test(t.fn)
if (test_passed) {
test_entry.status = "passed"
log.console(` PASS ${t.name}`)
pkg_result.passed++
file_result.passed++
} catch (e) {
} else {
test_entry.status = "failed"
test_entry.error = {
message: e,
stack: e.stack || ""
message: test_error_msg
}
if (e.name) test_entry.error.name = e.name
if (test_error_stack) test_entry.error.stack = test_error_stack
if (is_object(e) && e.message) {
test_entry.error.message = e.message
}
log.console(` FAIL ${t.name} ${test_entry.error.message}`)
if (test_entry.error.stack) {
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
log.console(` FAIL ${t.name} ${test_error_msg}`)
if (test_error_stack) {
log.console(` ${text(array(test_error_stack, '\n'), '\n ')}`)
}
pkg_result.failed++
@@ -365,15 +384,18 @@ function run_tests(package_name, specific_test) {
}
}
}
} catch (e) {
log.console(` Error loading ${f}: ${e}`)
var test_entry = {
} disruption {
load_disrupted = true
}
do_load()
if (load_disrupted) {
log.console(` Error loading ${f}`)
var test_entry = {
package: pkg_result.package,
test: "load_module",
status: "failed",
duration_ns: 0,
error: { message: `Error loading module: ${e}` }
error: { message: `Error loading module: ${f}` }
}
push(file_result.tests, test_entry)
pkg_result.failed++

View File

@@ -4,64 +4,65 @@ var os = use('os');
function assert(condition, message) {
if (!condition) {
throw Error(message || "Assertion failed");
return message || "Assertion failed"
}
}
function assertEqual(actual, expected, message) {
if (actual != expected) {
throw Error(message || "Expected " + expected + ", got " + actual);
return message || "Expected " + expected + ", got " + actual
}
}
function should_disrupt(fn) {
var caught = false
var wrapper = function() { fn() } disruption { caught = true }
wrapper()
return caught
}
return {
test_create_empty_blob: function() {
var b = Blob();
assertEqual(length(b), 0, "Empty blob should have length 0");
return assertEqual(length(b), 0, "Empty blob should have length 0")
},
test_create_blob_with_capacity: function() {
var b = Blob(100);
assertEqual(length(b), 0, "New blob with capacity should still have length 0");
return assertEqual(length(b), 0, "New blob with capacity should still have length 0")
},
test_write_and_read_single_bit: function() {
var b = Blob();
b.write_bit(true);
b.write_bit(false);
b.write_bit(1);
b.write_bit(0);
assertEqual(length(b), 4, "Should have 4 bits after writing");
var r = assertEqual(length(b), 4, "Should have 4 bits after writing")
if (r) return r
stone(b);
assertEqual(b.read_logical(0), true, "First bit should be true");
assertEqual(b.read_logical(1), false, "Second bit should be false");
assertEqual(b.read_logical(2), true, "Third bit should be true (1)");
assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)");
r = assertEqual(b.read_logical(0), true, "First bit should be true")
if (r) return r
r = assertEqual(b.read_logical(1), false, "Second bit should be false")
if (r) return r
r = assertEqual(b.read_logical(2), true, "Third bit should be true (1)")
if (r) return r
return assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)")
},
test_out_of_range_read_throws_error: function() {
var b = Blob();
b.write_bit(true);
stone(b);
var threw = false;
try {
b.read_logical(100);
} catch (e) {
threw = true;
}
assert(threw, "Out of range read should throw");
threw = false;
try {
b.read_logical(-1);
} catch (e) {
threw = true;
}
assert(threw, "Negative index read should throw");
if (!should_disrupt(function() { b.read_logical(100) }))
return "Out of range read should disrupt"
if (!should_disrupt(function() { b.read_logical(-1) }))
return "Negative index read should disrupt"
},
test_write_and_read_numbers: function() {
var b = Blob();
b.write_number(3.14159);
@@ -69,41 +70,48 @@ return {
b.write_number(0);
b.write_number(1e20);
stone(b);
assertEqual(b.read_number(0), 3.14159, "First number should match");
assertEqual(b.read_number(64), -42, "Second number should match");
assertEqual(b.read_number(128), 0, "Third number should match");
assertEqual(b.read_number(192), 1e20, "Fourth number should match");
var r = assertEqual(b.read_number(0), 3.14159, "First number should match")
if (r) return r
r = assertEqual(b.read_number(64), -42, "Second number should match")
if (r) return r
r = assertEqual(b.read_number(128), 0, "Third number should match")
if (r) return r
return assertEqual(b.read_number(192), 1e20, "Fourth number should match")
},
test_write_and_read_text: function() {
var b = Blob();
b.write_text("Hello");
b.write_text("World");
b.write_text("🎉");
stone(b);
assertEqual(b.read_text(0), "Hello", "First text should match");
return assertEqual(b.read_text(0), "Hello", "First text should match")
},
test_write_and_read_blobs: function() {
var b1 = Blob();
b1.write_bit(true);
b1.write_bit(false);
b1.write_bit(true);
var b2 = Blob(10);
b2.write_blob(b1);
b2.write_bit(false);
assertEqual(length(b2), 4, "Combined blob should have 4 bits");
var r = assertEqual(length(b2), 4, "Combined blob should have 4 bits")
if (r) return r
stone(b2);
assertEqual(b2.read_logical(0), true);
assertEqual(b2.read_logical(1), false);
assertEqual(b2.read_logical(2), true);
assertEqual(b2.read_logical(3), false);
r = assertEqual(b2.read_logical(0), true)
if (r) return r
r = assertEqual(b2.read_logical(1), false)
if (r) return r
r = assertEqual(b2.read_logical(2), true)
if (r) return r
return assertEqual(b2.read_logical(3), false)
},
test_blob_copy_constructor: function() {
var b1 = Blob();
b1.write_bit(true);
@@ -111,249 +119,219 @@ return {
b1.write_bit(true);
b1.write_bit(true);
stone(b1);
var b2 = Blob(b1);
stone(b2);
assertEqual(length(b2), 4, "Copied blob should have same length");
assertEqual(b2.read_logical(0), true);
assertEqual(b2.read_logical(3), true);
var r = assertEqual(length(b2), 4, "Copied blob should have same length")
if (r) return r
r = assertEqual(b2.read_logical(0), true)
if (r) return r
return assertEqual(b2.read_logical(3), true)
},
test_blob_partial_copy_constructor: function() {
var b1 = Blob();
for (var i = 0; i < 10; i++) {
b1.write_bit(i % 2 == 0);
}
stone(b1);
var b2 = Blob(b1, 2, 7);
stone(b2);
assertEqual(length(b2), 5, "Partial copy should have 5 bits");
assertEqual(b2.read_logical(0), true);
assertEqual(b2.read_logical(2), true);
var r = assertEqual(length(b2), 5, "Partial copy should have 5 bits")
if (r) return r
r = assertEqual(b2.read_logical(0), true)
if (r) return r
return assertEqual(b2.read_logical(2), true)
},
test_create_blob_with_fill: function() {
var b1 = Blob(8, true);
var b2 = Blob(8, false);
stone(b1);
stone(b2);
for (var i = 0; i < 8; i++) {
assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true");
assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false");
var r = assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true")
if (r) return r
r = assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false")
if (r) return r
}
},
test_create_blob_with_random_function: function() {
var sequence = [true, false, true, true, false];
var index = 0;
var b = Blob(5, function() {
return sequence[index++] ? 1 : 0;
});
stone(b);
for (var i = 0; i < 5; i++) {
assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence");
var r = assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence")
if (r) return r
}
},
test_write_pad_and_check_padding: function() {
var b = Blob();
b.write_bit(true);
b.write_bit(false);
b.write_bit(true);
b.write_pad(8);
assertEqual(length(b), 8, "Should be padded to 8 bits");
var r = assertEqual(length(b), 8, "Should be padded to 8 bits")
if (r) return r
stone(b);
assert(b['pad?'](3, 8), "Should detect valid padding at position 3");
assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2");
r = assert(b['pad?'](3, 8), "Should detect valid padding at position 3")
if (r) return r
return assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2")
},
test_read_blob_from_stone_blob: function() {
var b1 = Blob();
for (var i = 0; i < 16; i++) {
b1.write_bit(i % 3 == 0);
}
stone(b1);
var b2 = b1.read_blob(4, 12);
stone(b2);
assertEqual(length(b2), 8, "Read blob should have 8 bits");
assertEqual(b2.read_logical(2), true);
assertEqual(b2.read_logical(5), true);
var r = assertEqual(length(b2), 8, "Read blob should have 8 bits")
if (r) return r
r = assertEqual(b2.read_logical(2), true)
if (r) return r
return assertEqual(b2.read_logical(5), true)
},
test_stone_blob_is_immutable: function() {
var b = Blob();
b.write_bit(true);
stone(b);
var threw = false;
try {
b.write_bit(false);
} catch (e) {
threw = true;
}
assert(threw, "Writing to stone blob should throw error");
if (!should_disrupt(function() { b.write_bit(false) }))
return "Writing to stone blob should disrupt"
},
test_multiple_stone_calls_are_safe: function() {
var b = Blob();
b.write_bit(true);
assert(!stone.p(b), "Blob should not be a stone before stone() call");
var r = assert(!stone.p(b), "Blob should not be a stone before stone() call")
if (r) return r
stone(b);
assert(stone.p(b), "Blob should be a stone after stone() call");
r = assert(stone.p(b), "Blob should be a stone after stone() call")
if (r) return r
stone(b);
assertEqual(b.read_logical(0), true, "Blob data should remain intact");
assert(b.stone == null, "blob.stone should not be available as a method");
r = assertEqual(b.read_logical(0), true, "Blob data should remain intact")
if (r) return r
return assert(b.stone == null, "blob.stone should not be available as a method")
},
test_invalid_constructor_arguments: function() {
var threw = false;
try {
var b = Blob("invalid");
} catch (e) {
threw = true;
}
assert(threw, "Invalid constructor arguments should throw");
if (!should_disrupt(function() { Blob("invalid") }))
return "Invalid constructor arguments should disrupt"
},
test_write_bit_validation: function() {
var b = Blob();
b.write_bit(0);
b.write_bit(1);
var threw = false;
try {
b.write_bit(2);
} catch (e) {
threw = true;
}
assert(threw, "write_bit with value 2 should throw");
if (!should_disrupt(function() { b.write_bit(2) }))
return "write_bit with value 2 should disrupt"
},
test_complex_data_round_trip: function() {
var b = Blob();
b.write_text("Test");
b.write_number(123.456);
b.write_bit(true);
b.write_bit(false);
b.write_number(-999.999);
var originalLength = length(b);
stone(b);
var b2 = Blob(b);
stone(b2);
assertEqual(length(b2), originalLength, "Copy should have same length");
assertEqual(b2.read_text(0), "Test", "First text should match");
var r = assertEqual(length(b2), originalLength, "Copy should have same length")
if (r) return r
return assertEqual(b2.read_text(0), "Test", "First text should match")
},
test_zero_capacity_blob: function() {
var b = Blob(0);
assertEqual(length(b), 0, "Zero capacity blob should have length 0");
var r = assertEqual(length(b), 0, "Zero capacity blob should have length 0")
if (r) return r
b.write_bit(true);
assertEqual(length(b), 1, "Should expand when writing");
return assertEqual(length(b), 1, "Should expand when writing")
},
test_large_blob_handling: function() {
var b = Blob();
var testSize = 1000;
for (var i = 0; i < testSize; i++) {
b.write_bit(i % 7 == 0);
}
assertEqual(length(b), testSize, "Should have " + testSize + " bits");
var r = assertEqual(length(b), testSize, "Should have " + testSize + " bits")
if (r) return r
stone(b);
assertEqual(b.read_logical(0), true, "Bit 0 should be true");
assertEqual(b.read_logical(7), true, "Bit 7 should be true");
assertEqual(b.read_logical(14), true, "Bit 14 should be true");
assertEqual(b.read_logical(15), false, "Bit 15 should be false");
r = assertEqual(b.read_logical(0), true, "Bit 0 should be true")
if (r) return r
r = assertEqual(b.read_logical(7), true, "Bit 7 should be true")
if (r) return r
r = assertEqual(b.read_logical(14), true, "Bit 14 should be true")
if (r) return r
return assertEqual(b.read_logical(15), false, "Bit 15 should be false")
},
test_non_stone_blob_read_methods_should_throw: function() {
var b = Blob();
b.write_bit(true);
b.write_number(42);
b.write_text("test");
var threw = false;
try {
b.read_logical(0);
} catch (e) {
threw = true;
}
assert(threw, "read_logical on non-stone blob should throw");
threw = false;
try {
b.read_number(0);
} catch (e) {
threw = true;
}
assert(threw, "read_number on non-stone blob should throw");
threw = false;
try {
b.read_text(0);
} catch (e) {
threw = true;
}
assert(threw, "read_text on non-stone blob should throw");
threw = false;
try {
b.read_blob(0, 10);
} catch (e) {
threw = true;
}
assert(threw, "read_blob on non-stone blob should throw");
threw = false;
try {
b['pad?'](0, 8);
} catch (e) {
threw = true;
}
assert(threw, "pad? on non-stone blob should throw");
if (!should_disrupt(function() { b.read_logical(0) }))
return "read_logical on non-stone blob should disrupt"
if (!should_disrupt(function() { b.read_number(0) }))
return "read_number on non-stone blob should disrupt"
if (!should_disrupt(function() { b.read_text(0) }))
return "read_text on non-stone blob should disrupt"
if (!should_disrupt(function() { b.read_blob(0, 10) }))
return "read_blob on non-stone blob should disrupt"
if (!should_disrupt(function() { b['pad?'](0, 8) }))
return "pad? on non-stone blob should disrupt"
},
test_empty_text_write_and_read: function() {
var b = Blob();
b.write_text("");
stone(b);
assertEqual(b.read_text(0), "", "Empty string should round-trip");
return assertEqual(b.read_text(0), "", "Empty string should round-trip")
},
test_invalid_read_positions: function() {
var b = Blob();
b.write_number(42);
stone(b);
var threw = false;
try {
b.read_number(-10);
} catch (e) {
threw = true;
}
assert(threw, "Negative position should throw");
threw = false;
try {
b.read_number(1000);
} catch (e) {
threw = true;
}
assert(threw, "Position beyond length should throw");
if (!should_disrupt(function() { b.read_number(-10) }))
return "Negative position should disrupt"
if (!should_disrupt(function() { b.read_number(1000) }))
return "Position beyond length should disrupt"
}
}

39
tests/demo.ce Normal file
View File

@@ -0,0 +1,39 @@
function safe_add(a, b) {
return a + b
} disruption {
print("disruption caught in safe_add")
}
function inner() {
disrupt
}
function outer() {
inner()
} disruption {
print("disruption caught in outer — from inner()")
}
// Test 1: explicit disrupt with handler
function test_explicit() {
disrupt
} disruption {
print("test 1: explicit disrupt handled")
}
test_explicit()
// Test 2: type error disrupt (number + function)
safe_add(1, print)
// Test 3: unwinding — inner disrupts, outer catches
outer()
// Test 4: disrupt from inside disruption clause
function test_nested() {
disrupt
} disruption {
print("test 4: first disruption")
}
test_nested()
print("done")

View File

@@ -1,5 +1,5 @@
return {
test_disrupt: function() {
throw 1
disrupt
}
}

View File

@@ -2,85 +2,101 @@ var fd = use("fd")
var miniz = use("miniz")
var utf8 = use("utf8")
function safe_unlink(p) { fd.unlink(p) } disruption {}
return {
create_and_read_zip: function() {
var ZIP_PATH = "miniz_test.zip"
var SOURCE_PATH = "miniz_source.txt"
var ENTRY_PATH = "sample/hello.txt"
var PAYLOAD = "Miniz integration test payload."
function write_text_file(path, text) {
var handle = fd.open(path, "w")
fd.write(handle, text)
fd.close(handle)
}
try {
var error_msg = null
var do_test = function() {
write_text_file(SOURCE_PATH, PAYLOAD)
var source_blob = fd.slurp(SOURCE_PATH)
var writer = miniz.write(ZIP_PATH)
writer.add_file(ENTRY_PATH, source_blob)
writer = null
var zip_blob = fd.slurp(ZIP_PATH)
var reader = miniz.read(zip_blob)
if (!reader.exists(ENTRY_PATH))
throw "entry missing in archive"
var extracted_blob = reader.slurp(ENTRY_PATH)
var extracted_text = utf8.decode(extracted_blob)
if (extracted_text != PAYLOAD)
throw "extracted text mismatch"
} finally {
try { fd.unlink(ZIP_PATH) } catch(e) {}
try { fd.unlink(SOURCE_PATH) } catch(e) {}
error_msg = "entry missing in archive"
if (!error_msg) {
var extracted_blob = reader.slurp(ENTRY_PATH)
var extracted_text = utf8.decode(extracted_blob)
if (extracted_text != PAYLOAD)
error_msg = "extracted text mismatch"
}
} disruption {
if (!error_msg) error_msg = "test disrupted"
}
do_test()
safe_unlink(ZIP_PATH)
safe_unlink(SOURCE_PATH)
if (error_msg) return error_msg
},
list_and_count: function() {
var ZIP_PATH = "miniz_list_test.zip"
var ENTRY1 = "file1.txt"
var ENTRY2 = "dir/file2.txt"
try {
var error_msg = null
var do_test = function() {
var writer = miniz.write(ZIP_PATH)
writer.add_file(ENTRY1, utf8.encode("content1"))
writer.add_file(ENTRY2, utf8.encode("content2"))
writer = null
var zip_blob = fd.slurp(ZIP_PATH)
var reader = miniz.read(zip_blob)
var listed = reader.list()
if (length(listed) != reader.count())
throw "list/count mismatch"
if (length(listed) != 2)
throw "unexpected entry count"
} finally {
try { fd.unlink(ZIP_PATH) } catch(e) {}
error_msg = "list/count mismatch"
if (!error_msg && length(listed) != 2)
error_msg = "unexpected entry count"
} disruption {
if (!error_msg) error_msg = "test disrupted"
}
do_test()
safe_unlink(ZIP_PATH)
if (error_msg) return error_msg
},
exists_check: function() {
var ZIP_PATH = "miniz_exists_test.zip"
var ENTRY_PATH = "existing.txt"
try {
var error_msg = null
var do_test = function() {
var writer = miniz.write(ZIP_PATH)
writer.add_file(ENTRY_PATH, utf8.encode("data"))
writer = null
var zip_blob = fd.slurp(ZIP_PATH)
var reader = miniz.read(zip_blob)
if (!reader.exists(ENTRY_PATH))
throw "existing entry not found"
if (reader.exists("nonexistent.txt"))
throw "nonexistent entry reported as existing"
} finally {
try { fd.unlink(ZIP_PATH) } catch(e) {}
error_msg = "existing entry not found"
if (!error_msg && reader.exists("nonexistent.txt"))
error_msg = "nonexistent entry reported as existing"
} disruption {
if (!error_msg) error_msg = "test disrupted"
}
do_test()
safe_unlink(ZIP_PATH)
if (error_msg) return error_msg
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
var cmds = {
stop: $stop,
disrupt: _ => {
$delay(_ => { throw Error() }, 0.5)
$delay(_ => { disrupt }, 0.5)
}
}

View File

@@ -1,5 +1,10 @@
try {
var load_disrupted = false
var do_load = function() {
var u = use('tests/use')
} catch(e) {
log.console(e)
} disruption {
load_disrupted = true
}
do_load()
if (load_disrupted) {
log.console("use self-load disrupted")
}

3558
vm_suite.ce Normal file

File diff suppressed because it is too large Load Diff

1
vm_test/arrow_block.txt Normal file
View File

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

View File

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

1
vm_test/arrow_expr.txt Normal file
View File

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

1
vm_test/arrow_multi.txt Normal file
View File

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

View File

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

1
vm_test/assign_add.txt Normal file
View File

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

1
vm_test/assign_and.txt Normal file
View File

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

1
vm_test/assign_div.txt Normal file
View File

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

1
vm_test/assign_land.txt Normal file
View File

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

1
vm_test/assign_lor.txt Normal file
View File

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

1
vm_test/assign_mod.txt Normal file
View File

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

1
vm_test/assign_mul.txt Normal file
View File

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

View File

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

1
vm_test/assign_or.txt Normal file
View File

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

1
vm_test/assign_power.txt Normal file
View File

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

1
vm_test/assign_shl.txt Normal file
View File

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

1
vm_test/assign_shr.txt Normal file
View File

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

1
vm_test/assign_shru.txt Normal file
View File

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

1
vm_test/assign_sub.txt Normal file
View File

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

1
vm_test/assign_xor.txt Normal file
View File

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

View File

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

View File

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

View File

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

3
vm_test/comment.txt Normal file
View File

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

View File

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

View File

@@ -0,0 +1 @@
1 /* a */ + /* b */ 2

1
vm_test/def_basic.txt Normal file
View File

@@ -0,0 +1 @@
def x = 5; x

1
vm_test/do_while.txt Normal file
View File

@@ -0,0 +1 @@
var i = 0; do { i = i + 1 } while (i < 3); i

View File

@@ -0,0 +1 @@
var s = 0; var i = 0; do { i = i + 1; if (i == 2) continue; s = s + i } while (i < 5); s

View File

@@ -0,0 +1 @@
;;; 5

1
vm_test/for_basic.txt Normal file
View File

@@ -0,0 +1 @@
var s = 0; for (var i = 0; i < 3; i++) s = s + i; s

1
vm_test/for_break.txt Normal file
View File

@@ -0,0 +1 @@
var s = 0; for (var i = 0; i < 10; i++) { if (i == 4) break; s = s + i }; s

1
vm_test/for_continue.txt Normal file
View File

@@ -0,0 +1 @@
var s = 0; for (var i = 0; i < 5; i++) { if (i == 2) continue; s = s + i }; s

1
vm_test/func_expr.txt Normal file
View File

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

1
vm_test/func_iife.txt Normal file
View File

@@ -0,0 +1 @@
(function(x) { return x * 2 })(5)

View File

@@ -0,0 +1 @@
function fac(n) { if (n <= 1) return 1; return n * fac(n - 1) }; fac(5)

2
vm_test/go_basic.txt Normal file
View File

@@ -0,0 +1,2 @@
function a() { go b() }
function b() { 1 }

2
vm_test/go_method.txt Normal file
View File

@@ -0,0 +1,2 @@
var o = {m: function() { 1 }}
function f() { go o.m() }

1
vm_test/if_basic.txt Normal file
View File

@@ -0,0 +1 @@
var x = 0; if (true) x = 1; x

1
vm_test/if_else.txt Normal file
View File

@@ -0,0 +1 @@
if (false) 1 else 2

View File

@@ -0,0 +1 @@
print("a")

View File

@@ -0,0 +1 @@
var s = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (j == 1) continue outer; s = s + 1 } }; s

1
vm_test/multi_var.txt Normal file
View File

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

1
vm_test/nested_block.txt Normal file
View File

@@ -0,0 +1 @@
var x = 1; { var y = 2; { var z = 3; x = x + y + z } }; x

1
vm_test/num_binary.txt Normal file
View File

@@ -0,0 +1 @@
0b1010

1
vm_test/num_exp.txt Normal file
View File

@@ -0,0 +1 @@
1e3

1
vm_test/num_float.txt Normal file
View File

@@ -0,0 +1 @@
3.14

1
vm_test/num_hex.txt Normal file
View File

@@ -0,0 +1 @@
0xff

1
vm_test/num_octal.txt Normal file
View File

@@ -0,0 +1 @@
0o17

View File

@@ -0,0 +1 @@
1_000_000

1
vm_test/op_arith.txt Normal file
View File

@@ -0,0 +1 @@
1 + 2 * 3

1
vm_test/op_bitwise.txt Normal file
View File

@@ -0,0 +1 @@
5 & 3

View File

@@ -0,0 +1 @@
~5

View File

@@ -0,0 +1 @@
5 | 2

View File

@@ -0,0 +1 @@
5 ^ 3

1
vm_test/op_comma.txt Normal file
View File

@@ -0,0 +1 @@
(1, 2, 3)

1
vm_test/op_compare.txt Normal file
View File

@@ -0,0 +1 @@
5 > 3

View File

@@ -0,0 +1 @@
3 == 3

View File

@@ -0,0 +1 @@
5 >= 5

View File

@@ -0,0 +1 @@
3 < 5

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