From 017b63ba806e54f605ecee1803ecf532a6841229 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sat, 21 Feb 2026 19:23:53 -0600 Subject: [PATCH] inline intrinsics --- mcode.cm | 367 +++++++++++++++++++++++++++++++++++--------------- streamline.cm | 51 ++++++- vm_suite.ce | 181 +++++++++++++++++++++++++ 3 files changed, 487 insertions(+), 112 deletions(-) diff --git a/mcode.cm b/mcode.cm index c80eb7c5..ab9f62ca 100644 --- a/mcode.cm +++ b/mcode.cm @@ -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) diff --git a/streamline.cm b/streamline.cm index 86376382..5d6f0073 100644 --- a/streamline.cm +++ b/streamline.cm @@ -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 } diff --git a/vm_suite.ce b/vm_suite.ce index 6e1605fc..f6795f9b 100644 --- a/vm_suite.ce +++ b/vm_suite.ce @@ -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 // ============================================================================