87 Commits
syntax ... mach

Author SHA1 Message Date
John Alanbrook
7469383e66 refactor into multiple files 2026-02-08 16:32:14 -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
34 changed files with 44564 additions and 33898 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);

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,17 +1,21 @@
(function engine() {
// Hidden vars (os, actorsym, init, core_path) come from env
var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__'
var _cell = {}
var need_stop = false
var dylib_ext
switch(os.platform()) {
case 'Windows': dylib_ext = '.dll'; break;
case 'macOS': dylib_ext = '.dylib'; break;
case 'Linux': dylib_ext = '.so'; break;
}
var cases = {
Windows: '.dll',
macOS: '.dylib',
Linux: '.so'
}
print(os.platform())
dylib_ext = cases[os.platform()]
var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce'
@@ -51,14 +55,16 @@ var fd = use_embed('fd')
// 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 = {}
@@ -79,7 +85,7 @@ function use_core(path) {
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)
var result = call(fn,sym, [use_core])
use_cache[cache_key] = result;
return result;
@@ -133,32 +139,27 @@ 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) {
@@ -185,14 +186,14 @@ function disrupt(err)
log.console(err.stack)
}
actor_mod.disrupt()
actor_mod["disrupt"]()
}
actor_mod.on_exception(disrupt)
actor_mod.on_exception(actor_die)
_cell.args = init ?? {}
_cell.args = init != null ? init : {}
_cell.id = "newguy"
function create_actor(desc = {id:guid()}) {
@@ -241,11 +242,15 @@ 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
@@ -260,7 +265,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
}
}
@@ -268,15 +280,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
@@ -286,16 +295,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)
}
}
}
@@ -408,52 +415,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)
}
}
@@ -487,10 +496,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"})
}
@@ -527,20 +540,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) {
@@ -583,12 +598,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
}
@@ -608,19 +621,26 @@ var need_stop = false
var replies = {}
function send(actor, message, reply) {
if (!is_object(actor))
throw Error(`Must send to an actor object. Provided: ${actor}`);
if (!is_object(actor)) {
log.error(`Must send to an actor object. Provided: ${actor}`)
disrupt
}
if (!is_object(message))
throw Error('Message must be an object')
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
target = header.replycc
send_msg.return = header.reply
}
@@ -638,7 +658,7 @@ function send(actor, message, reply) {
}
// Instead of sending immediately, queue it
actor_prep(actor, send_msg);
actor_prep(target, send_msg);
}
stone(send)
@@ -669,7 +689,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
@@ -705,36 +725,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`)
}
}
@@ -744,30 +763,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()
{
@@ -792,8 +808,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`)
if (!locator) {
os.print(`Main program ${_cell.args.program} could not be found\n`)
os.exit(1)
}
$_.clock(_ => {
// Get capabilities for the main program
@@ -818,7 +836,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

@@ -577,7 +577,7 @@ 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);
JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));

View File

@@ -44,7 +44,12 @@ src += [ # core
'wildmatch.c',
'qjs_actor.c',
'miniz.c',
'quickjs.c',
'runtime.c',
'cell_js.c',
'tokenize.c',
'parse.c',
'mach.c',
'mcode.c',
'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'
]

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);

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);

View File

@@ -9,6 +9,7 @@
#include "cell.h"
#include "cell_internal.h"
#include "cJSON.h"
#define ENGINE "internal/engine.cm"
#define CELL_SHOP_DIR ".cell"
@@ -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;
@@ -124,17 +166,31 @@ JSValue js_wota_use(JSContext *js);
void script_startup(cell_rt *prt)
{
JSRuntime *rt = JS_NewRuntime();
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
JSContext *js = JS_NewContext(rt);
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));
// Load and compile engine.cm
// Load and parse engine.cm to AST
size_t engine_size;
char *data = load_core_file(ENGINE, &engine_size);
if (!data) {
@@ -142,10 +198,15 @@ void script_startup(cell_rt *prt)
return;
}
JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE);
cJSON *ast = JS_ASTTree(data, engine_size, ENGINE);
free(data);
if (JS_IsException(bytecode)) {
uncaught_exception(js, bytecode);
if (!ast) {
printf("ERROR: Failed to parse %s\n", ENGINE);
return;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
return;
}
@@ -156,8 +217,8 @@ void script_startup(cell_rt *prt)
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
crt->actor_sym = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym));
crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null)
if (crt->init_wota) {
@@ -175,9 +236,10 @@ void script_startup(cell_rt *prt)
// Stone the environment
hidden_env = JS_Stone(js, hidden_env);
// Integrate and run
// Run through MACH VM
crt->state = ACTOR_RUNNING;
JSValue v = JS_Integrate(js, bytecode, hidden_env);
JSValue v = JS_RunMachTree(js, ast, hidden_env);
cJSON_Delete(ast);
uncaught_exception(js, v);
crt->state = ACTOR_IDLE;
set_actor_state(crt);
@@ -369,32 +431,20 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
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 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *json = JS_AST(ctx, script, strlen(script), filename);
if (json) {
printf("%s\n", json);
free(json);
return has_errors ? 1 : 0;
} else {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return json ? 0 : 1;
}
/* Check for --tokenize flag to output token array JSON */
@@ -426,35 +476,172 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
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;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
}
/* 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;
}
char *json = JS_Tokenize(ctx, script, strlen(script), filename);
if (json) {
printf("%s\n", json);
free(json);
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 {
printf("Failed to tokenize\n");
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 json ? 0 : 1;
return JS_IsException(result) ? 1 : 0;
}
/* Check for --mach flag to output machine code JSON */
/* 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;
@@ -483,116 +670,42 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
if (!ast) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
if (mach_json) {
printf("%s\n", mach_json);
free(mach_json);
} else {
printf("Failed to generate machine code\n");
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return mach_json ? 0 : 1;
}
/* Check for --vmcode flag to dump linked register VM bytecode */
if (argc >= 3 && strcmp(argv[1], "--vmcode") == 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;
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");
free(allocated_script);
cJSON_Delete(ast); free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
if (mach_json) {
JS_DumpRegisterMach(ctx, mach_json, JS_NULL);
free(mach_json);
} else {
printf("Failed to generate machine code\n");
}
JS_DumpMachTree(ctx, ast, JS_NULL);
cJSON_Delete(ast);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return mach_json ? 0 : 1;
return 0;
}
/* Check for --mach-run flag to generate and run machine code through register VM */
fprintf(stderr, "DEBUG: argc=%d argv[1]=%s\n", argc, argc > 1 ? argv[1] : "NULL");
fflush(stderr);
/* 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;
@@ -621,49 +734,34 @@ int cell_init(int argc, char **argv)
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");
free(allocated_script);
cJSON_Delete(ast); free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContextWithHeapSize(rt, 1024 * 1024); /* 1MB heap for register VM */
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
/* Parse to AST */
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Generate machine code */
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
if (!mach_json) {
printf("Failed to generate machine code\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Execute through register VM */
fprintf(stderr, "DEBUG: About to execute mach code\n");
fflush(stderr);
JSValue result = JS_IntegrateRegister(ctx, mach_json, JS_NULL);
fprintf(stderr, "DEBUG: Execution done, result tag=%d\n", (int)(result >> 48));
fflush(stderr);
free(mach_json);
JSValue result = JS_RunMachTree(ctx, ast, JS_NULL);
cJSON_Delete(ast);
int exit_code = 0;
if (JS_IsException(result)) {
@@ -673,9 +771,22 @@ int cell_init(int argc, char **argv)
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)) {
/* Print result */
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);

View File

@@ -144,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);

12740
source/cell_js.c Normal file

File diff suppressed because it is too large Load Diff

3316
source/mach.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,6 @@
extern int cell_init(int argc, char **argv);
#include <stdio.h>
int main(int argc, char **argv)
{
fprintf(stderr, "MAIN: starting\n");
fflush(stderr);
return cell_init(argc, argv);
}

3461
source/mcode.c Normal file

File diff suppressed because it is too large Load Diff

2087
source/parse.c Normal file

File diff suppressed because it is too large Load Diff

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,

2007
source/quickjs-internal.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -367,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);
@@ -429,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;
@@ -740,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 */
@@ -1218,27 +1218,64 @@ CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_
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 (JSContext *ctx, const char *source, size_t len, const char *filename);
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 (JSContext *ctx, const char *source, size_t len, const char *filename);
char *JS_Tokenize (const char *source, size_t len, const char *filename);
/* Generate register-based machine code from AST JSON.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_Mach (JSContext *ctx, const char *ast_json);
/* Compiled bytecode (context-free, serializable) */
typedef struct MachCode MachCode;
/* Link and execute register-based machine code.
mach_json: JSON output from JS_Mach
env: stone record for external variable resolution (or JS_NULL)
/* 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_IntegrateRegister (JSContext *ctx, const char *mach_json, JSValue env);
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
/* Dump linked register VM bytecode to stdout for debugging.
Links mach_json and prints the resulting bytecode with resolved labels. */
void JS_DumpRegisterMach (JSContext *ctx, const char *mach_json, JSValue env);
/* 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. */

13756
source/runtime.c Normal file

File diff suppressed because it is too large Load Diff

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>
@@ -2132,6 +2133,286 @@ TEST(cell_module_string_constant) {
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
============================================================================ */
@@ -2348,6 +2629,37 @@ int run_c_test_suite(JSContext *ctx)
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");

1432
source/tokenize.c Normal file

File diff suppressed because it is too large Load Diff

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])
}
}

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")

3558
vm_suite.ce Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
var x = 0; outer: { x = 1; break outer; x = 2 }; x

View File

@@ -1 +0,0 @@
var r; switch(2) { case 1: r = 1; break; case 2: r = 2; break }; r

336
vm_test/syntax.txt Normal file
View File

@@ -0,0 +1,336 @@
/* comprehensive syntax test - exercises all AST paths */
// --- variables and constants ---
var a
var b = 1
var c = 2, d = 3
def PI = 3.14159
def MSG = "hello"
// --- number literals ---
var n_int = 42
var n_float = 3.14
var n_hex = 0xFF
var n_octal = 0o77
var n_binary = 0b1010
var n_underscore = 1_000_000
var n_exp = 1e10
var n_exp_neg = 2.5e-3
// --- string literals ---
var s_single = 'single'
var s_double = "double"
var s_escape = "tab\there\nnewline\\slash\"quote"
var s_unicode = "\u0041\u0042"
// --- template literals ---
var t_basic = `hello ${b} world`
var t_nested = `a ${b + c} b ${d}`
var t_expr = `result: ${b > 0 ? "yes" : "no"}`
// --- array literals ---
var arr_empty = []
var arr_simple = [1, 2, 3]
var arr_mixed = [1, "two", true, null]
var arr_nested = [[1, 2], [3, [4, 5]]]
// --- record/object literals ---
var rec_empty = {}
var rec_simple = {x: 1, y: 2}
var rec_nested = {a: {b: {c: 3}}}
var shorthand_var = 10
var rec_shorthand = {shorthand_var}
var rec_computed = {["key" + "1"]: 100}
var rec_method = {
greet() { return "hi" }
}
var rec_mixed = {
name: "test",
value: 42,
nested: {inner: true},
items: [1, 2, 3]
}
// --- all binary arithmetic operators ---
var arith_add = 1 + 2
var arith_sub = 5 - 3
var arith_mul = 4 * 5
var arith_div = 10 / 3
var arith_mod = 10 % 3
var arith_pow = 2 ** 8
// --- comparison operators ---
var cmp_lt = 1 < 2
var cmp_gt = 2 > 1
var cmp_lte = 1 <= 1
var cmp_gte = 2 >= 2
var cmp_eq = 1 == 1
var cmp_neq = 1 != 2
// --- logical operators ---
var log_and = true && false
var log_or = false || true
var log_not = !false
// --- bitwise operators ---
var bit_and = 0xFF & 0x0F
var bit_or = 0xF0 | 0x0F
var bit_xor = 0xFF ^ 0x0F
var bit_not = ~0
var bit_shl = 1 << 8
var bit_shr = 256 >> 4
var bit_shru = -1 >>> 0
// --- nullish coalescing ---
var nullish = null ?? "default"
// --- ternary operator ---
var tern = b > 0 ? "pos" : "neg"
var tern_nested = b > 0 ? (b > 10 ? "big" : "small") : "neg"
// --- comma operator ---
var comma_result = (1, 2, 3)
// --- unary operators ---
var unary_pos = +b
var unary_neg = -b
// --- increment/decrement ---
var inc_val = 0
inc_val++
inc_val--
;++inc_val
;--inc_val
// --- all compound assignments ---
var ca = 10
ca += 5
ca -= 3
ca *= 2
ca /= 4
ca %= 3
ca **= 2
ca <<= 1
ca >>= 1
ca >>>= 0
ca &= 0xFF
ca |= 0x01
ca ^= 0x10
ca &&= true
ca ||= false
ca ??= 1
// --- chained assignment ---
var ch1, ch2
ch1 = ch2 = 42
// --- property access ---
var obj = {a: {b: {c: 1}}, items: [10, 20, 30]}
var dot_access = obj.a
var dot_chain = obj.a.b.c
var bracket_access = obj["a"]
var bracket_dynamic = obj["it" + "ems"]
var bracket_index = obj.items[1]
// --- optional chaining ---
var opt_obj = {x: {y: 1}}
var opt_prop = opt_obj?.x
var opt_deep = opt_obj?.x?.y
var opt_null = null?.foo
var opt_bracket = opt_obj?.["x"]
var opt_call_obj = {f: function() { return 1 }}
var opt_call = opt_call_obj?.f?.()
// --- function declarations ---
function add(a, b) {
return a + b
}
function no_params() {
return 42
}
function with_default(x = 10, y = 20) {
return x + y
}
// --- function expressions ---
var func_expr = function(x) { return x * 2 }
var func_named = function multiply(x, y) { return x * y }
// --- arrow functions (all forms) ---
var arrow_no_param = () => 0
var arrow_one = x => x + 1
var arrow_multi = (a, b) => a + b
var arrow_block = (a, b) => { return a + b }
var arrow_default = (x = 1, y = 2) => x + y
var arrow_default_expr = (x = 1 + 2, y = arr_simple[0]) => x + y
// --- closures ---
function make_counter() {
var count = 0
return function() {
count += 1
return count
}
}
function outer_fn() {
var x = 10
function middle() {
var y = 20
function inner() {
return x + y
}
return inner()
}
return middle()
}
// --- this in methods ---
var counter = {
val: 0,
inc() { this.val += 1 },
get() { return this.val }
}
// --- if/else/else-if ---
var if_result
if (b > 0) {
if_result = "positive"
} else if (b == 0) {
if_result = "zero"
} else {
if_result = "negative"
}
// --- while loop ---
var w = 0
while (w < 5) {
w += 1
}
// --- do-while loop ---
var dw = 0
do {
dw += 1
} while (dw < 3)
// --- for loop ---
var f_sum = 0
for (var i = 0; i < 10; i++) {
f_sum += i
}
// --- for loop with break ---
var fb = 0
for (var j = 0; j < 100; j++) {
if (j == 5) break
fb = j
}
// --- for loop with continue ---
var fc = 0
for (var k = 0; k < 10; k++) {
if (k % 2 == 0) continue
fc += k
}
// --- labeled break ---
outer: for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
if (y == 1) break outer
}
}
// --- labeled continue ---
var lc = 0
loop: for (var m = 0; m < 3; m++) {
for (var n = 0; n < 3; n++) {
if (n == 1) continue loop
lc += 1
}
}
// --- disrupt and disruption ---
function disrupt_test() {
disrupt
}
function disruption_test() {
var x = 1
} disruption {
var y = 2
}
function disrupt_with_disruption() {
disrupt
} disruption {
var handled = true
}
// --- delete operator ---
var del_obj = {a: 1, b: 2}
delete del_obj.a
// --- in operator ---
var in_result = "b" in del_obj
// --- go statement ---
function async_task() { return 1 }
function caller() { go async_task() }
// --- IIFE ---
var iife = (function() { return 99 })()
// --- recursive function ---
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
// --- mutually recursive functions ---
function is_even(n) {
if (n == 0) return true
return is_odd(n - 1)
}
function is_odd(n) {
if (n == 0) return false
return is_even(n - 1)
}
// --- block scoping ---
var block_val = 1
{
var block_val = 2
}
// --- nested blocks ---
{
var nb = 1
{
var nb2 = nb + 1
{
var nb3 = nb2 + 1
}
}
}
// --- empty statement ---
;
// --- parenthesized expression for precedence ---
var prec = (1 + 2) * (3 + 4)
// --- complex expressions ---
var complex = arr_simple[0] + rec_simple.x * (b > 0 ? 2 : 1)
// --- operator precedence chain ---
var prec_chain = 1 + 2 * 3 ** 2 - 4 / 2 % 3
// --- regex literals ---
var re = /hello/
var re_flags = /world/gi
// --- line comment at end ---
var end = 1 // done

View File

@@ -1 +0,0 @@
try { 1 } catch(e) { 2 }

View File

@@ -1 +0,0 @@
var x = 1; try { throw 0 } catch(e) { x = 2 } finally { x = x + 1 }; x

View File

@@ -1 +0,0 @@
var x = 1; try { x = 2 } finally { x = 3 }; x

View File

@@ -1 +0,0 @@
try { throw "err" } catch(e) { e }