6 Commits

Author SHA1 Message Date
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
5 changed files with 1280 additions and 350 deletions

View File

@@ -1,17 +1,21 @@
(function engine() {
// Hidden vars (os, actorsym, init, core_path) come from env // Hidden vars (os, actorsym, init, core_path) come from env
var ACTORDATA = actorsym var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__' var SYSYM = '__SYSTEM__'
var _cell = {} var _cell = {}
var need_stop = false
var dylib_ext var dylib_ext
switch(os.platform()) { var cases = {
case 'Windows': dylib_ext = '.dll'; break; Windows: '.dll',
case 'macOS': dylib_ext = '.dylib'; break; macOS: '.dylib',
case 'Linux': dylib_ext = '.so'; break; Linux: '.so'
} }
print(os.platform())
dylib_ext = cases[os.platform()]
var MOD_EXT = '.cm' var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce' var ACTOR_EXT = '.ce'
@@ -51,14 +55,16 @@ var fd = use_embed('fd')
// Get the shop path from HOME environment // Get the shop path from HOME environment
var home = os.getenv('HOME') || os.getenv('USERPROFILE') var home = os.getenv('HOME') || os.getenv('USERPROFILE')
if (!home) { 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 shop_path = home + '/.cell'
var packages_path = shop_path + '/packages' var packages_path = shop_path + '/packages'
var core_path = packages_path + '/core' var core_path = packages_path + '/core'
if (!fd.is_dir(core_path)) { 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 = {} var use_cache = {}
@@ -79,7 +85,7 @@ function use_core(path) {
var script_blob = fd.slurp(file_path) var script_blob = fd.slurp(file_path)
var script = text(script_blob) var script = text(script_blob)
var mod = `(function setup_module(use){${script}})` 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]) var result = call(fn,sym, [use_core])
use_cache[cache_key] = result; use_cache[cache_key] = result;
return result; return result;
@@ -133,32 +139,27 @@ function log(name, args) {
var caller = caller_data(1) var caller = caller_data(1)
var msg = args[0] var msg = args[0]
switch(name) { if (name == 'console') {
case 'console': os.print(console_rec(caller.line, caller.file, msg))
os.print(console_rec(caller.line, caller.file, msg)) } else if (name == 'error') {
break if (msg == null) msg = Error()
case 'error': if (is_proto(msg, Error))
msg = msg ?? Error() msg = msg.name + ": " + msg.message + "\n" + msg.stack
if (is_proto(msg, Error)) os.print(console_rec(caller.line, caller.file, msg))
msg = msg.name + ": " + msg.message + "\n" + msg.stack } else if (name == 'system') {
os.print(console_rec(caller.line, caller.file, msg)) msg = "[SYSTEM] " + msg
break os.print(console_rec(caller.line, caller.file, msg))
case 'system': } else {
msg = "[SYSTEM] " + msg log.console(`unknown log type: ${name}`)
os.print(console_rec(caller.line, caller.file, msg))
break
default:
log.console(`unknown log type: ${name}`)
break
} }
} }
function disrupt(err) function actor_die(err)
{ {
if (is_function(err.toString)) { if (err && is_function(err.toString)) {
os.print(err.toString()) os.print(err.toString())
os.print("\n") os.print("\n")
os.print(err.stack) if (err.stack) os.print(err.stack)
} }
if (overling) { if (overling) {
@@ -185,14 +186,14 @@ function disrupt(err)
log.console(err.stack) 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" _cell.id = "newguy"
function create_actor(desc = {id:guid()}) { function create_actor(desc = {id:guid()}) {
@@ -241,11 +242,15 @@ os.runtime_env = runtime_env
$_.time_limit = function(requestor, seconds) $_.time_limit = function(requestor, seconds)
{ {
if (!pronto.is_requestor(requestor)) if (!pronto.is_requestor(requestor)) {
throw Error('time_limit: first argument must be a requestor'); log.error('time_limit: first argument must be a requestor')
if (!is_number(seconds) || seconds <= 0) disrupt
throw Error('time_limit: seconds must be a positive number'); }
if (!is_number(seconds) || seconds <= 0) {
log.error('time_limit: seconds must be a positive number')
disrupt
}
return function time_limit_requestor(callback, value) { return function time_limit_requestor(callback, value) {
pronto.check_callback(callback, 'time_limit') pronto.check_callback(callback, 'time_limit')
var finished = false var finished = false
@@ -260,7 +265,14 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = null timer_cancel = null
} }
if (requestor_cancel) { 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 requestor_cancel = null
} }
} }
@@ -268,15 +280,12 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = $_.delay(function() { timer_cancel = $_.delay(function() {
if (finished) return if (finished) return
def reason = make_reason(factory, 'Timeout.', seconds) def reason = make_reason(factory, 'Timeout.', seconds)
if (requestor_cancel) { safe_cancel_requestor(reason)
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
finished = true finished = true
callback(null, reason) callback(null, reason)
}, seconds) }, seconds)
try { function do_request() {
requestor_cancel = requestor(function(val, reason) { requestor_cancel = requestor(function(val, reason) {
if (finished) return if (finished) return
finished = true finished = true
@@ -286,16 +295,14 @@ $_.time_limit = function(requestor, seconds)
} }
callback(val, reason) callback(val, reason)
}, value) }, value)
} catch (ex) { } disruption {
cancel(ex) cancel(Error('requestor failed'))
callback(null, ex) callback(null, Error('requestor failed'))
} }
do_request()
return function(reason) { return function(reason) {
if (requestor_cancel) { safe_cancel_requestor(reason)
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
} }
} }
} }
@@ -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. // takes a function input value that will eventually be called with the current time in number form.
$_.portal = function(fn, port) { $_.portal = function(fn, port) {
if (portal) throw Error(`Already started a portal listening on ${portal.port}`) if (portal) {
if (!port) throw Error("Requires a valid port.") 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}`) log.system(`starting a portal on port ${port}`)
portal = enet.create_host({address: "any", port}) portal = enet.create_host({address: "any", port})
portal_fn = fn portal_fn = fn
} }
function handle_host(e) { function handle_host(e) {
switch (e.type) { if (e.type == "connect") {
case "connect": log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`)
log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`) peers[`${e.peer.address}:${e.peer.port}`] = e.peer
peers[`${e.peer.address}:${e.peer.port}`] = e.peer var queue = peer_queue.get(e.peer)
var queue = peer_queue.get(e.peer) if (queue) {
if (queue) { arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg))) log.system(`sent ${msg} out of queue`)
log.system(`sent ${msg} out of queue`)
peer_queue.delete(e.peer)
}
break
case "disconnect":
peer_queue.delete(e.peer) 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 if (data.data) populate_actor_addresses(data.data)
case "receive": turn(data)
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
} }
} }
@@ -487,10 +496,14 @@ $_.stop = function stop(actor) {
need_stop = true need_stop = true
return return
} }
if (!is_actor(actor)) if (!is_actor(actor)) {
throw Error('Can only call stop on an actor.') log.error('Can only call stop on an actor.')
if (is_null(underlings[actor[ACTORDATA].id])) disrupt
throw Error('Can only call stop on an underling or self.') }
if (is_null(underlings[actor[ACTORDATA].id])) {
log.error('Can only call stop on an underling or self.')
disrupt
}
sys_msg(actor, {kind:"stop"}) sys_msg(actor, {kind:"stop"})
} }
@@ -527,20 +540,22 @@ function actor_prep(actor, send) {
// Send a message immediately without queuing // Send a message immediately without queuing
function actor_send_immediate(actor, send) { function actor_send_immediate(actor, send) {
try { actor_send(actor, send)
actor_send(actor, send);
} catch (err) {
log.error("Failed to send immediate message:", err);
}
} }
function actor_send(actor, message) { function actor_send(actor, message) {
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
return return
if (!is_actor(actor) && !is_actor(actor.replycc)) throw Error(`Must send to an actor object. Attempted send to ${actor}`) if (!is_actor(actor) && !is_actor(actor.replycc)) {
log.error(`Must send to an actor object. Attempted send to ${actor}`)
if (!is_object(message)) throw Error('Must send an object record.') disrupt
}
if (!is_object(message)) {
log.error('Must send an object record.')
disrupt
}
// message to self // message to self
if (actor[ACTORDATA].id == _cell.id) { if (actor[ACTORDATA].id == _cell.id) {
@@ -583,12 +598,10 @@ function actor_send(actor, message) {
// Holds all messages queued during the current turn. // Holds all messages queued during the current turn.
var message_queue = [] 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 we've been flagged to stop, bail out before doing anything
if (need_stop) { if (need_stop) {
disrupt() actor_die()
message_queue = [] message_queue = []
return return
} }
@@ -608,19 +621,26 @@ var need_stop = false
var replies = {} var replies = {}
function send(actor, message, reply) { function send(actor, message, reply) {
if (!is_object(actor)) if (!is_object(actor)) {
throw Error(`Must send to an actor object. Provided: ${actor}`); log.error(`Must send to an actor object. Provided: ${actor}`)
disrupt
}
if (!is_object(message)) if (!is_object(message)) {
throw Error('Message must be an object') log.error('Message must be an object')
disrupt
}
var send_msg = {type:"user", data: message} var send_msg = {type:"user", data: message}
var target = actor
if (actor[HEADER] && actor[HEADER].replycc) { if (actor[HEADER] && actor[HEADER].replycc) {
var header = actor[HEADER] var header = actor[HEADER]
if (!header.replycc || !is_actor(header.replycc)) if (!header.replycc || !is_actor(header.replycc)) {
throw Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`) log.error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
disrupt
}
actor = header.replycc target = header.replycc
send_msg.return = header.reply send_msg.return = header.reply
} }
@@ -638,7 +658,7 @@ function send(actor, message, reply) {
} }
// Instead of sending immediately, queue it // Instead of sending immediately, queue it
actor_prep(actor, send_msg); actor_prep(target, send_msg);
} }
stone(send) stone(send)
@@ -669,7 +689,7 @@ overling = _cell.args.overling
$_.overling = overling $_.overling = overling
root = _cell.args.root root = _cell.args.root
root ??= $_.self if (root == null) root = $_.self
if (overling) { if (overling) {
$_.couple(overling) // auto couple to overling $_.couple(overling) // auto couple to overling
@@ -705,36 +725,35 @@ function handle_actor_disconnect(id) {
delete greeters[id] delete greeters[id]
} }
log.system(`actor ${id} disconnected`) 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) function handle_sysym(msg)
{ {
var from var from
switch(msg.kind) { if (msg.kind == 'stop') {
case 'stop': actor_die("got stop message")
disrupt("got stop message") } else if (msg.kind == 'underling') {
break from = msg.from
case 'underling': var greeter = greeters[from[ACTORDATA].id]
from = msg.from if (greeter) greeter(msg.message)
var greeter = greeters[from[ACTORDATA].id] if (msg.message.type == 'disrupt')
if (greeter) greeter(msg.message) delete underlings[from[ACTORDATA].id]
if (msg.message.type == 'disrupt') } else if (msg.kind == 'contact') {
delete underlings[from[ACTORDATA].id] if (portal_fn) {
break var letter2 = msg.data
case 'contact': letter2[HEADER] = msg
if (portal_fn) { delete msg.data
var letter2 = msg.data portal_fn(letter2)
letter2[HEADER] = msg } else {
delete msg.data log.error('Got a contact message, but no portal is established.')
portal_fn(letter2) disrupt
} else throw Error('Got a contact message, but no portal is established.') }
break } else if (msg.kind == 'couple') {
case 'couple': // from must be notified when we die // from must be notified when we die
from = msg.from from = msg.from
underlings[from[ACTORDATA].id] = true underlings[from[ACTORDATA].id] = true
log.system(`actor ${from} is coupled to me`) log.system(`actor ${from} is coupled to me`)
break
} }
} }
@@ -744,30 +763,27 @@ function handle_message(msg) {
return return
} }
switch (msg.type) { if (msg.type == "user") {
case "user": var letter = msg.data // what the sender really sent
var letter = msg.data // what the sender really sent _ObjectDefineProperty(letter, HEADER, {
_ObjectDefineProperty(letter, HEADER, { value: msg, enumerable: false
value: msg, enumerable: false })
}) _ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true value: { reply: msg.reply }, enumerable: false
value: { reply: msg.reply }, enumerable: false })
})
if (msg.return) {
if (msg.return) { var fn = replies[msg.return]
var fn = replies[msg.return] if (fn) fn(letter)
if (fn) fn(letter) delete replies[msg.return]
delete replies[msg.return]
return
}
if (receive_fn) receive_fn(letter)
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() function enet_check()
{ {
@@ -792,8 +808,10 @@ if (!locator) {
locator = shop.resolve_locator(_cell.args.program + ".ce", pkg) locator = shop.resolve_locator(_cell.args.program + ".ce", pkg)
} }
if (!locator) if (!locator) {
throw Error(`Main program ${_cell.args.program} could not be found`) os.print(`Main program ${_cell.args.program} could not be found\n`)
os.exit(1)
}
$_.clock(_ => { $_.clock(_ => {
// Get capabilities for the main program // Get capabilities for the main program
@@ -818,7 +836,6 @@ $_.clock(_ => {
var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env]) var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env])
if (val) if (val)
throw Error('Program must not return anything'); log.error('Program must not return anything')
disrupt
}) })
})()

View File

@@ -114,12 +114,19 @@ static int print_tree_errors(cJSON *root) {
cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename"); cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename");
if (cJSON_IsString(fname)) if (cJSON_IsString(fname))
filename = fname->valuestring; filename = fname->valuestring;
int prev_line = -1;
const char *prev_msg = NULL;
cJSON *e; cJSON *e;
cJSON_ArrayForEach(e, errors) { cJSON_ArrayForEach(e, errors) {
const char *msg = cJSON_GetStringValue( const char *msg = cJSON_GetStringValue(
cJSON_GetObjectItemCaseSensitive(e, "message")); cJSON_GetObjectItemCaseSensitive(e, "message"));
cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line"); cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line");
cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column"); 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)) if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col))
fprintf(stderr, "%s:%d:%d: error: %s\n", fprintf(stderr, "%s:%d:%d: error: %s\n",
filename, (int)line->valuedouble, (int)col->valuedouble, msg); filename, (int)line->valuedouble, (int)col->valuedouble, msg);
@@ -168,7 +175,7 @@ void script_startup(cell_rt *prt)
cell_rt *crt = JS_GetContextOpaque(js); cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js)); JS_FreeValue(js, js_blob_use(js));
// Load and compile engine.cm // Load and parse engine.cm to AST
size_t engine_size; size_t engine_size;
char *data = load_core_file(ENGINE, &engine_size); char *data = load_core_file(ENGINE, &engine_size);
if (!data) { if (!data) {
@@ -176,10 +183,15 @@ void script_startup(cell_rt *prt)
return; return;
} }
JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE); cJSON *ast = JS_ASTTree(data, engine_size, ENGINE);
free(data); free(data);
if (JS_IsException(bytecode)) { if (!ast) {
uncaught_exception(js, bytecode); printf("ERROR: Failed to parse %s\n", ENGINE);
return;
}
if (print_tree_errors(ast)) {
cJSON_Delete(ast);
return; return;
} }
@@ -209,9 +221,10 @@ void script_startup(cell_rt *prt)
// Stone the environment // Stone the environment
hidden_env = JS_Stone(js, hidden_env); hidden_env = JS_Stone(js, hidden_env);
// Integrate and run // Run through MACH VM
crt->state = ACTOR_RUNNING; 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); uncaught_exception(js, v);
crt->state = ACTOR_IDLE; crt->state = ACTOR_IDLE;
set_actor_state(crt); set_actor_state(crt);

File diff suppressed because it is too large Load Diff

View File

@@ -604,6 +604,48 @@ run("self-referencing object", function() {
assert_eq(o.self.self.name, "root", "cycle access") 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 === // === SUMMARY ===
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed)) print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))

View File

@@ -3369,6 +3369,182 @@ run("gc object from keys function under pressure", function() {
} }
}) })
// ============================================================================
// DEFAULT PARAMETER TESTS
// ============================================================================
run("default param constant", function() {
var f = function(a, b=10) { return a + b }
assert_eq(f(1), 11, "default param constant")
})
run("default param overridden", function() {
var f = function(a, b=10) { return a + b }
assert_eq(f(1, 2), 3, "default param overridden")
})
run("default param uses earlier param", function() {
var f = function(a, b=a+1) { return b }
assert_eq(f(5), 6, "default param uses earlier param")
})
run("default param uses earlier param overridden", function() {
var f = function(a, b=a+1) { return b }
assert_eq(f(5, 20), 20, "default param uses earlier param overridden")
})
run("multiple default params", function() {
var f = function(a, b=10, c=a+1) { return a + b + c }
assert_eq(f(1), 13, "multiple defaults f(1)")
assert_eq(f(1, 2), 5, "multiple defaults f(1,2)")
assert_eq(f(1, 2, 3), 6, "multiple defaults f(1,2,3)")
})
run("arrow function default param", function() {
var g = (x, y=100) => x + y
assert_eq(g(5), 105, "arrow default param")
assert_eq(g(5, 20), 25, "arrow default param overridden")
})
run("default param null passed explicitly", function() {
var f = function(a, b=10) { return b }
assert_eq(f(1, null), 10, "explicit null triggers default")
})
run("default param with string", function() {
var f = function(a, b="hello") { return b }
assert_eq(f(1), "hello", "default string param")
assert_eq(f(1, "world"), "world", "default string overridden")
})
run("default param first param has no default", function() {
var f = function(a, b=42) { return a }
assert_eq(f(7), 7, "first param no default")
})
// ============================================================================
// FUNCTINO TESTS
// ============================================================================
run("functino +! addition", function() {
assert_eq(+!(3, 4), 7, "+! addition")
})
run("functino -! subtraction", function() {
assert_eq(-!(10, 3), 7, "-! subtraction")
})
run("functino *! multiplication", function() {
assert_eq(*!(5, 6), 30, "*! multiplication")
})
run("functino /! division", function() {
assert_eq(/!(10, 2), 5, "/! division")
})
run("functino %! modulo", function() {
assert_eq(%!(10, 3), 1, "%! modulo")
})
run("functino **! power", function() {
assert_eq(**!(2, 10), 1024, "**! power")
})
run("functino <! less than", function() {
assert_eq(<!(3, 5), true, "<! true")
assert_eq(<!(5, 3), false, "<! false")
assert_eq(<!(3, 3), false, "<! equal")
})
run("functino >! greater than", function() {
assert_eq(>!(5, 3), true, ">! true")
assert_eq(>!(3, 5), false, ">! false")
assert_eq(>!(3, 3), false, ">! equal")
})
run("functino <=! less or equal", function() {
assert_eq(<=!(3, 5), true, "<=! less")
assert_eq(<=!(3, 3), true, "<=! equal")
assert_eq(<=!(5, 3), false, "<=! greater")
})
run("functino >=! greater or equal", function() {
assert_eq(>=!(5, 3), true, ">=! greater")
assert_eq(>=!(3, 3), true, ">=! equal")
assert_eq(>=!(3, 5), false, ">=! less")
})
run("functino =! equality", function() {
assert_eq(=!(5, 5), true, "=! true")
assert_eq(=!(5, 3), false, "=! false")
})
run("functino !=! inequality", function() {
assert_eq(!=!(5, 3), true, "!=! true")
assert_eq(!=!(5, 5), false, "!=! false")
})
run("functino =! with tolerance", function() {
assert_eq(=!(1.0, 1.0001, 0.001), true, "=! within tolerance")
assert_eq(=!(1.0, 1.01, 0.001), false, "=! outside tolerance")
})
run("functino !=! with tolerance", function() {
assert_eq(!=!(1.0, 1.01, 0.001), true, "!=! outside tolerance")
assert_eq(!=!(1.0, 1.0001, 0.001), false, "!=! within tolerance")
})
run("functino &! bitwise and", function() {
assert_eq(&!(0xff, 0x0f), 0x0f, "&! bitwise and")
})
run("functino |! bitwise or", function() {
assert_eq(|!(0xf0, 0x0f), 0xff, "|! bitwise or")
})
run("functino ^! bitwise xor", function() {
assert_eq(^!(0xff, 0x0f), 0xf0, "^! bitwise xor")
})
run("functino <<! shift left", function() {
assert_eq(<<!(1, 4), 16, "<<! shift left")
})
run("functino >>! shift right", function() {
assert_eq(>>!(16, 4), 1, ">>! shift right")
})
run("functino ~! bitwise not", function() {
assert_eq(~!(0), -1, "~! bitwise not 0")
})
run("functino []! array index", function() {
var arr = [10, 20, 30]
assert_eq([]!(arr, 0), 10, "[]! index 0")
assert_eq([]!(arr, 1), 20, "[]! index 1")
assert_eq([]!(arr, 2), 30, "[]! index 2")
})
run("functino &&! logical and", function() {
assert_eq(&&!(true, true), true, "&&! true true")
assert_eq(&&!(true, false), false, "&&! true false")
assert_eq(&&!(false, true), false, "&&! false true")
assert_eq(&&!(1, 2), 2, "&&! truthy returns right")
assert_eq(&&!(0, 2), 0, "&&! falsy returns left")
})
run("functino ||! logical or", function() {
assert_eq(||!(false, true), true, "||! false true")
assert_eq(||!(true, false), true, "||! true false")
assert_eq(||!(false, false), false, "||! false false")
assert_eq(||!(0, 5), 5, "||! falsy returns right")
assert_eq(||!(3, 5), 3, "||! truthy returns left")
})
run("functino >>>! unsigned shift right", function() {
assert_eq(>>>!(-1, 28), 15, ">>>! unsigned shift right")
})
// ============================================================================ // ============================================================================
// SUMMARY // SUMMARY
// ============================================================================ // ============================================================================