inline intrinsics

This commit is contained in:
2026-02-21 19:23:53 -06:00
parent 6d6b53009f
commit 017b63ba80
3 changed files with 487 additions and 112 deletions

367
mcode.cm
View File

@@ -879,6 +879,77 @@ var mcode = function(ast) {
var inline_some = true
var inline_reduce = true
var inline_map = true
var inline_find = true
// --- Helper: emit arity-dispatched callback invocation ---
// ctx = {fn, fn_arity, result, null_s, frame, zero, one, az, ao, prefix}
// args = [slot_for_arg1, slot_for_arg2] — data args (not this)
// max_args = 1 or 2 — how many data args to support
var emit_arity_call = function(ctx, args, max_args) {
var call_one = gen_label(ctx.prefix + "_c1")
var call_two = gen_label(ctx.prefix + "_c2")
var call_done = gen_label(ctx.prefix + "_cd")
emit_3("eq", ctx.az, ctx.fn_arity, ctx.zero)
emit_jump_cond("jump_false", ctx.az, call_one)
emit_3("frame", ctx.frame, ctx.fn, 0)
emit_3("setarg", ctx.frame, 0, ctx.null_s)
emit_2("invoke", ctx.frame, ctx.result)
emit_jump(call_done)
emit_label(call_one)
if (max_args >= 2) {
emit_3("eq", ctx.ao, ctx.fn_arity, ctx.one)
emit_jump_cond("jump_false", ctx.ao, call_two)
}
emit_3("frame", ctx.frame, ctx.fn, 1)
emit_3("setarg", ctx.frame, 0, ctx.null_s)
emit_3("setarg", ctx.frame, 1, args[0])
emit_2("invoke", ctx.frame, ctx.result)
if (max_args < 2) {
emit_label(call_done)
return null
}
emit_jump(call_done)
emit_label(call_two)
emit_3("frame", ctx.frame, ctx.fn, 2)
emit_3("setarg", ctx.frame, 0, ctx.null_s)
emit_3("setarg", ctx.frame, 1, args[0])
emit_3("setarg", ctx.frame, 2, args[1])
emit_2("invoke", ctx.frame, ctx.result)
emit_label(call_done)
return null
}
// --- Helper: forward loop scaffolding ---
// L = {arr, len, i, check, item, one, loop_label, done_label}
// body_fn(L) — called between element load and increment
var emit_forward_loop = function(L, body_fn) {
emit_2("int", L.i, 0)
emit_label(L.loop_label)
emit_3("lt", L.check, L.i, L.len)
emit_jump_cond("jump_false", L.check, L.done_label)
emit_3("load_index", L.item, L.arr, L.i)
body_fn(L)
emit_3("add", L.i, L.i, L.one)
emit_jump(L.loop_label)
emit_label(L.done_label)
return null
}
// --- Helper: reverse loop scaffolding ---
var emit_reverse_loop = function(L, body_fn) {
var zero = alloc_slot()
emit_2("int", zero, 0)
emit_3("subtract", L.i, L.len, L.one)
emit_label(L.loop_label)
emit_3("ge", L.check, L.i, zero)
emit_jump_cond("jump_false", L.check, L.done_label)
emit_3("load_index", L.item, L.arr, L.i)
body_fn(L)
emit_3("subtract", L.i, L.i, L.one)
emit_jump(L.loop_label)
emit_label(L.done_label)
return null
}
// --- Helper: emit a reduce loop body ---
// r = {acc, i, arr, fn, len, fn_arity}; emits loop updating acc in-place.
@@ -895,13 +966,12 @@ var mcode = function(ast) {
var null_s = alloc_slot()
var one = alloc_slot()
var zero = alloc_slot()
var arity_is_zero = alloc_slot()
var arity_is_one = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var f = alloc_slot()
var loop_label = gen_label("reduce_loop")
var call_one_label = gen_label("reduce_call_one")
var call_two_label = gen_label("reduce_call_two")
var call_done_label = gen_label("reduce_call_done")
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: acc, null_s: null_s,
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "reduce"}
emit_2("int", one, 1)
emit_2("int", zero, 0)
emit_1("null", null_s)
@@ -913,27 +983,7 @@ var mcode = function(ast) {
}
emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i)
emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, acc)
emit_jump(call_done_label)
emit_label(call_one_label)
emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, acc)
emit_2("invoke", f, acc)
emit_jump(call_done_label)
emit_label(call_two_label)
emit_3("frame", f, fn_slot, 2)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, acc)
emit_3("setarg", f, 2, item)
emit_2("invoke", f, acc)
emit_label(call_done_label)
emit_arity_call(ctx, [acc, item], 2)
if (forward) {
emit_3("add", i, i, one)
} else {
@@ -942,60 +992,63 @@ var mcode = function(ast) {
emit_jump(loop_label)
}
// --- Inline expansion: arrfor(arr, fn) ---
var expand_inline_arrfor = function(dest, arr_slot, fn_slot) {
// --- Inline expansion: arrfor(arr, fn[, rev[, exit]]) ---
var expand_inline_arrfor = function(dest, args, nargs) {
var arr_slot = args.arr
var fn_slot = args.fn
var len = alloc_slot()
var i = alloc_slot()
var check = alloc_slot()
var item = alloc_slot()
var fn_arity = alloc_slot()
var arity_is_zero = alloc_slot()
var arity_is_one = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var null_s = alloc_slot()
var zero = alloc_slot()
var one = alloc_slot()
var f = alloc_slot()
var discard = alloc_slot()
var loop_label = gen_label("arrfor_loop")
var done_label = gen_label("arrfor_done")
var call_one_label = gen_label("arrfor_call_one")
var call_two_label = gen_label("arrfor_call_two")
var call_done_label = gen_label("arrfor_call_done")
var val = alloc_slot()
var eq_check = alloc_slot()
var early_exit = gen_label("arrfor_exit")
var done_final = gen_label("arrfor_final")
var rev_label = gen_label("arrfor_rev")
var final_label = gen_label("arrfor_fwd_done")
var fwd_L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("arrfor_fwd"), done_label: gen_label("arrfor_fwd_d")}
var rev_L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("arrfor_rev_l"), done_label: gen_label("arrfor_rev_d")}
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "arrfor"}
var body_fn = function(L) {
emit_arity_call(ctx, [L.item, L.i], 2)
if (nargs >= 4 && args.exit >= 0) {
emit_3("eq", eq_check, val, args.exit)
emit_jump_cond("jump_true", eq_check, early_exit)
}
return null
}
emit_2("length", len, arr_slot)
emit_2("int", i, 0)
emit_2("int", zero, 0)
emit_2("int", one, 1)
emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot)
emit_label(loop_label)
emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i)
emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, discard)
emit_jump(call_done_label)
emit_label(call_one_label)
emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_2("invoke", f, discard)
emit_jump(call_done_label)
emit_label(call_two_label)
emit_3("frame", f, fn_slot, 2)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_3("setarg", f, 2, i)
emit_2("invoke", f, discard)
emit_label(call_done_label)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(done_label)
if (nargs <= 2) {
emit_forward_loop(fwd_L, body_fn)
} else {
emit_jump_cond("jump_true", args.rev, rev_label)
emit_forward_loop(fwd_L, body_fn)
emit_jump(final_label)
emit_label(rev_label)
emit_reverse_loop(rev_L, body_fn)
emit_label(final_label)
}
emit_1("null", dest)
emit_jump(done_final)
if (nargs >= 4 && args.exit >= 0) {
emit_label(early_exit)
emit_2("move", dest, val)
}
emit_label(done_final)
return dest
}
@@ -1113,61 +1166,151 @@ var mcode = function(ast) {
var check = alloc_slot()
var item = alloc_slot()
var fn_arity = alloc_slot()
var arity_is_zero = alloc_slot()
var arity_is_one = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var null_s = alloc_slot()
var zero = alloc_slot()
var one = alloc_slot()
var f = alloc_slot()
var val = alloc_slot()
var loop_label = gen_label("filter_loop")
var call_one_label = gen_label("filter_call_one")
var call_two_label = gen_label("filter_call_two")
var call_done_label = gen_label("filter_call_done")
var skip_label = gen_label("filter_skip")
var done_label = gen_label("filter_done")
var skip = gen_label("filter_skip")
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "filter"}
var L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("filter_loop"), done_label: gen_label("filter_done")}
add_instr(["array", result, 0])
emit_2("length", len, arr_slot)
emit_2("int", i, 0)
emit_2("int", zero, 0)
emit_2("int", one, 1)
emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot)
emit_label(loop_label)
emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i)
emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, val)
emit_jump(call_done_label)
emit_label(call_one_label)
emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_2("invoke", f, val)
emit_jump(call_done_label)
emit_label(call_two_label)
emit_3("frame", f, fn_slot, 2)
emit_3("setarg", f, 0, null_s)
emit_3("setarg", f, 1, item)
emit_3("setarg", f, 2, i)
emit_2("invoke", f, val)
emit_label(call_done_label)
emit_jump_cond("jump_false", val, skip_label)
emit_2("push", result, item)
emit_label(skip_label)
emit_3("add", i, i, one)
emit_jump(loop_label)
emit_label(done_label)
emit_forward_loop(L, function(L) {
emit_arity_call(ctx, [L.item, L.i], 2)
emit_jump_cond("jump_false", val, skip)
emit_2("push", result, L.item)
emit_label(skip)
return null
})
emit_2("move", dest, result)
return dest
}
// --- Inline expansion: find(arr, target[, rev[, from]]) ---
var expand_inline_find = function(dest, args, nargs) {
var arr_slot = args.arr
var target = args.target
var len = alloc_slot()
var i = alloc_slot()
var check = alloc_slot()
var item = alloc_slot()
var fn_arity = alloc_slot()
var az = alloc_slot()
var ao = alloc_slot()
var null_s = alloc_slot()
var zero = alloc_slot()
var one = alloc_slot()
var f = alloc_slot()
var val = alloc_slot()
var is_fn = alloc_slot()
var eq_check = alloc_slot()
var fn_mode_label = gen_label("find_fn")
var found_label = gen_label("find_found")
var not_found_label = gen_label("find_nf")
var final_label = gen_label("find_final")
var vrev = gen_label("find_vrev")
var vdone = gen_label("find_vdone")
var frev = gen_label("find_frev")
var fdone = gen_label("find_fdone")
var vL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_vl"), done_label: gen_label("find_vd")}
var vrL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_vrl"), done_label: gen_label("find_vrd")}
var fL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_fl"), done_label: gen_label("find_fd")}
var ffL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_ffl"), done_label: gen_label("find_ffd")}
var frL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
loop_label: gen_label("find_frl"), done_label: gen_label("find_frd")}
var ctx = {fn: target, fn_arity: fn_arity, result: val, null_s: null_s,
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "find"}
var val_body = function(L) {
emit_3("eq", eq_check, L.item, target)
emit_jump_cond("jump_true", eq_check, found_label)
return null
}
var fn_body = function(L) {
emit_arity_call(ctx, [L.item, L.i], 2)
emit_jump_cond("jump_true", val, found_label)
return null
}
emit_2("length", len, arr_slot)
emit_2("int", zero, 0)
emit_2("int", one, 1)
emit_1("null", null_s)
emit_2("is_func", is_fn, target)
emit_jump_cond("jump_true", is_fn, fn_mode_label)
// === Value mode ===
if (nargs <= 2) {
emit_forward_loop(vL, val_body)
} else {
emit_jump_cond("jump_true", args.rev, vrev)
if (nargs >= 4 && args.from >= 0) {
emit_2("move", i, args.from)
}
if (nargs >= 4 && args.from >= 0) {
emit_label(vL.loop_label)
emit_3("lt", vL.check, vL.i, vL.len)
emit_jump_cond("jump_false", vL.check, vL.done_label)
emit_3("load_index", vL.item, vL.arr, vL.i)
val_body(vL)
emit_3("add", vL.i, vL.i, vL.one)
emit_jump(vL.loop_label)
emit_label(vL.done_label)
} else {
emit_forward_loop(vL, val_body)
}
emit_jump(vdone)
emit_label(vrev)
emit_reverse_loop(vrL, val_body)
emit_label(vdone)
}
emit_jump(not_found_label)
// === Function mode ===
emit_label(fn_mode_label)
emit_2("length", fn_arity, target)
if (nargs <= 2) {
emit_forward_loop(fL, fn_body)
} else {
emit_jump_cond("jump_true", args.rev, frev)
if (nargs >= 4 && args.from >= 0) {
emit_2("move", i, args.from)
}
if (nargs >= 4 && args.from >= 0) {
emit_label(ffL.loop_label)
emit_3("lt", ffL.check, ffL.i, ffL.len)
emit_jump_cond("jump_false", ffL.check, ffL.done_label)
emit_3("load_index", ffL.item, ffL.arr, ffL.i)
fn_body(ffL)
emit_3("add", ffL.i, ffL.i, ffL.one)
emit_jump(ffL.loop_label)
emit_label(ffL.done_label)
} else {
emit_forward_loop(ffL, fn_body)
}
emit_jump(fdone)
emit_label(frev)
emit_reverse_loop(frL, fn_body)
emit_label(fdone)
}
emit_label(not_found_label)
emit_1("null", dest)
emit_jump(final_label)
emit_label(found_label)
emit_2("move", dest, i)
emit_label(final_label)
return dest
}
// --- Inline expansion: array(arr, fn) → map ---
var expand_inline_map = function(dest, arr_slot, fn_slot) {
var result = alloc_slot()
@@ -1993,11 +2136,13 @@ var mcode = function(ast) {
return a1
}
// Callback intrinsics → inline mcode loops
if (nargs == 2 && fname == "arrfor" && inline_arrfor) {
if (fname == "arrfor" && nargs >= 2 && nargs <= 4 && inline_arrfor) {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
a2 = nargs >= 3 ? gen_expr(args_list[2], -1) : -1
a3 = nargs >= 4 ? gen_expr(args_list[3], -1) : -1
d = alloc_slot()
return expand_inline_arrfor(d, a0, a1)
return expand_inline_arrfor(d, {arr: a0, fn: a1, rev: a2, exit: a3}, nargs)
}
if (nargs == 2 && fname == "every" && inline_every) {
a0 = gen_expr(args_list[0], -1)
@@ -2017,6 +2162,14 @@ var mcode = function(ast) {
d = alloc_slot()
return expand_inline_filter(d, a0, a1)
}
if (fname == "find" && nargs >= 2 && nargs <= 4 && inline_find) {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
a2 = nargs >= 3 ? gen_expr(args_list[2], -1) : -1
a3 = nargs >= 4 ? gen_expr(args_list[3], -1) : -1
d = alloc_slot()
return expand_inline_find(d, {arr: a0, target: a1, rev: a2, from: a3}, nargs)
}
if (fname == "reduce" && nargs >= 2 && nargs <= 4 && inline_reduce) {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)

View File

@@ -2595,12 +2595,11 @@ var streamline = function(ir, log) {
reduce: true, array: true
}
var can_inline = function(callee_func, is_prefer) {
// Structural eligibility: closures, get/put, disruption, nested functions
var can_inline_structural = function(callee_func) {
var instrs = null
var i = 0
var instr = null
var count = 0
var limit = 0
if (callee_func.nr_close_slots > 0) return false
instrs = callee_func.instructions
if (instrs == null) return false
@@ -2622,7 +2621,16 @@ var streamline = function(ir, log) {
if (callee_func.disruption_pc != null && callee_func.disruption_pc > 0) {
return false
}
count = 0
return true
}
// Size eligibility: instruction count check
var can_inline_size = function(callee_func, is_prefer) {
var instrs = callee_func.instructions
var count = 0
var i = 0
var limit = 0
if (instrs == null) return false
i = 0
while (i < length(instrs)) {
if (is_array(instrs[i])) count = count + 1
@@ -2632,6 +2640,11 @@ var streamline = function(ir, log) {
return count <= limit
}
var can_inline = function(callee_func, is_prefer) {
if (!can_inline_structural(callee_func)) return false
return can_inline_size(callee_func, is_prefer)
}
// =========================================================
// Pass: inline_calls — inline same-module + sensory functions
// =========================================================
@@ -2673,6 +2686,9 @@ var streamline = function(ir, log) {
var inlined_body = null
var fi = null
var intrinsic_name = null
var is_single_use = false
var ref_count = 0
var ri = 0
if (instructions == null) return false
num_instr = length(instructions)
@@ -2767,8 +2783,33 @@ var streamline = function(ir, log) {
continue
}
// Check if callee is a single-use function literal — skip size limit
is_single_use = false
if (fi != null) {
ref_count = 0
ri = 0
while (ri < length(instructions)) {
if (is_array(instructions[ri])) {
// Count frame instructions that use this slot as callee (position 2)
if (instructions[ri][0] == "frame" && instructions[ri][2] == callee_slot) {
ref_count = ref_count + 1
}
// Also count setarg where slot is passed as value (position 3)
if (instructions[ri][0] == "setarg" && instructions[ri][3] == callee_slot) {
ref_count = ref_count + 1
}
}
ri = ri + 1
}
if (ref_count <= 1) is_single_use = true
}
// Check eligibility
if (!can_inline(callee_func, is_prefer)) {
if (!can_inline_structural(callee_func)) {
i = i + 1
continue
}
if (!is_single_use && !can_inline_size(callee_func, is_prefer)) {
i = i + 1
continue
}

View File

@@ -5982,6 +5982,187 @@ run("gc closure - factory pattern survives gc", function() {
assert_eq(b.say(), "hello bob", "second factory closure")
})
// ============================================================================
// INLINE LOOP EXPANSION TESTS
// ============================================================================
// --- filter inline expansion ---
run("filter inline - integer predicate", function() {
var result = filter([0, 1.25, 2, 3.5, 4, 5.75], is_integer)
assert_eq(length(result), 3, "filter integer count")
assert_eq(result[0], 0, "filter integer [0]")
assert_eq(result[1], 2, "filter integer [1]")
assert_eq(result[2], 4, "filter integer [2]")
})
run("filter inline - all pass", function() {
var result = filter([1, 2, 3], function(x) { return true })
assert_eq(length(result), 3, "filter all pass length")
})
run("filter inline - none pass", function() {
var result = filter([1, 2, 3], function(x) { return false })
assert_eq(length(result), 0, "filter none pass length")
})
run("filter inline - empty", function() {
var result = filter([], is_integer)
assert_eq(length(result), 0, "filter empty length")
})
run("filter inline - with index", function() {
var result = filter([10, 20, 30], function(e, i) { return i > 0 })
assert_eq(length(result), 2, "filter index length")
assert_eq(result[0], 20, "filter index [0]")
assert_eq(result[1], 30, "filter index [1]")
})
run("filter inline - large callback", function() {
var result = filter([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(x) {
var a = x * 2
var b = a + 1
var c = b * 3
var d = c - a
return d > 20
})
if (length(result) < 1) fail("filter large callback should return elements")
})
// --- find inline expansion ---
run("find inline - function forward", function() {
var idx = find([1, 2, 3], function(x) { return x == 2 })
assert_eq(idx, 1, "find fn forward")
})
run("find inline - function not found", function() {
var idx = find([1, 2, 3], function(x) { return x == 99 })
assert_eq(idx, null, "find fn not found")
})
run("find inline - value forward", function() {
var idx = find([10, 20, 30], 20)
assert_eq(idx, 1, "find value forward")
})
run("find inline - value not found", function() {
var idx = find([10, 20, 30], 99)
assert_eq(idx, null, "find value not found")
})
run("find inline - reverse", function() {
var idx = find([1, 2, 1, 2], function(x) { return x == 1 }, true)
assert_eq(idx, 2, "find reverse")
})
run("find inline - from", function() {
var idx = find([1, 2, 3, 2], function(x) { return x == 2 }, false, 2)
assert_eq(idx, 3, "find from")
})
run("find inline - empty", function() {
var idx = find([], 1)
assert_eq(idx, null, "find empty")
})
run("find inline - value reverse", function() {
var idx = find([10, 20, 30, 20], 20, true)
assert_eq(idx, 3, "find value reverse")
})
run("find inline - value with from", function() {
var idx = find([10, 20, 30, 20], 20, false, 2)
assert_eq(idx, 3, "find value with from")
})
run("find inline - with index callback", function() {
var idx = find(["a", "b", "c"], (x, i) => i == 2)
assert_eq(idx, 2, "find index callback")
})
// --- arrfor inline expansion ---
run("arrfor inline - basic sum", function() {
var sum = 0
arrfor([1, 2, 3, 4, 5], function(x) { sum = sum + x })
assert_eq(sum, 15, "arrfor basic sum")
})
run("arrfor inline - reverse", function() {
var order = []
arrfor([1, 2, 3], function(x) { order[] = x }, true)
assert_eq(order[0], 3, "arrfor reverse [0]")
assert_eq(order[1], 2, "arrfor reverse [1]")
assert_eq(order[2], 1, "arrfor reverse [2]")
})
run("arrfor inline - exit", function() {
var result = arrfor([1, 2, 3, 4, 5], function(x) {
if (x > 3) return true
return null
}, false, true)
assert_eq(result, true, "arrfor exit")
})
run("arrfor inline - no exit returns null", function() {
var result = arrfor([1, 2, 3], function(x) { })
assert_eq(result, null, "arrfor no exit null")
})
run("arrfor inline - with index", function() {
var indices = []
arrfor([10, 20, 30], (x, i) => { indices[] = i })
assert_eq(indices[0], 0, "arrfor index [0]")
assert_eq(indices[1], 1, "arrfor index [1]")
assert_eq(indices[2], 2, "arrfor index [2]")
})
run("arrfor inline - reverse with index", function() {
var items = []
arrfor(["a", "b", "c"], function(x, i) { items[] = text(i) + x }, true)
assert_eq(items[0], "2c", "arrfor rev index [0]")
assert_eq(items[1], "1b", "arrfor rev index [1]")
assert_eq(items[2], "0a", "arrfor rev index [2]")
})
// --- reduce inline expansion ---
run("reduce inline - no initial forward", function() {
var result = reduce([1, 2, 3, 4, 5, 6, 7, 8, 9], function(a, b) { return a + b })
assert_eq(result, 45, "reduce sum 1-9")
})
run("reduce inline - single element", function() {
var result = reduce([42], function(a, b) { return a + b })
assert_eq(result, 42, "reduce single")
})
run("reduce inline - empty", function() {
var result = reduce([], function(a, b) { return a + b })
assert_eq(result, null, "reduce empty")
})
run("reduce inline - with initial", function() {
var result = reduce([1, 2, 3], function(a, b) { return a + b }, 10)
assert_eq(result, 16, "reduce with initial")
})
run("reduce inline - with initial empty", function() {
var result = reduce([], function(a, b) { return a + b }, 99)
assert_eq(result, 99, "reduce initial empty")
})
run("reduce inline - reverse", function() {
var result = reduce([1, 2, 3], function(a, b) { return a - b }, 0, true)
assert_eq(result, -6, "reduce reverse")
})
run("reduce inline - intrinsic callback", function() {
var result = reduce([3, 7, 2, 9, 1], max)
assert_eq(result, 9, "reduce max")
})
// ============================================================================
// SUMMARY
// ============================================================================