From b0ac5de7e295a82acfd7b83128235df28f200239 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sun, 22 Feb 2026 10:08:40 -0600 Subject: [PATCH 1/2] more comprehensive vm_suite --- vm_suite.ce | 1151 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1151 insertions(+) diff --git a/vm_suite.ce b/vm_suite.ce index f6795f9b..13155636 100644 --- a/vm_suite.ce +++ b/vm_suite.ce @@ -6163,6 +6163,1157 @@ run("reduce inline - intrinsic callback", function() { assert_eq(result, 9, "reduce max") }) +// ============================================================================ +// BOOLEAN/TRUTHINESS FUSION REGRESSION +// Tests for the not + wary_false -> wary_true -> jump_true downgrade bug. +// When the optimizer fuses not + wary_false into wary_true, then downgrades +// wary_true to jump_true because it thinks the slot is T_BOOL, non-boolean +// truthy values (strings, numbers, objects, arrays) incorrectly fail the +// jump_true check (which tests == JS_TRUE) and fall through. +// ============================================================================ + +run("not string truthy in conditional", function() { + var s = "hello" + var result = "wrong" + if (!s) { + result = "falsely negated" + } else { + result = "correct" + } + assert_eq(result, "correct", "!string should be falsy in conditional") +}) + +run("not empty string in conditional", function() { + var s = "" + var result = "wrong" + if (!s) { + result = "correct" + } else { + result = "falsely truthy" + } + assert_eq(result, "correct", "!'' should be truthy in conditional") +}) + +run("not number truthy in conditional", function() { + var n = 42 + var result = "wrong" + if (!n) { + result = "falsely negated" + } else { + result = "correct" + } + assert_eq(result, "correct", "!42 should be falsy in conditional") +}) + +run("not zero in conditional", function() { + var n = 0 + var result = "wrong" + if (!n) { + result = "correct" + } else { + result = "falsely truthy" + } + assert_eq(result, "correct", "!0 should be truthy in conditional") +}) + +run("not object truthy in conditional", function() { + var o = {x: 1} + var result = "wrong" + if (!o) { + result = "falsely negated" + } else { + result = "correct" + } + assert_eq(result, "correct", "!object should be falsy in conditional") +}) + +run("not array truthy in conditional", function() { + var a = [1, 2, 3] + var result = "wrong" + if (!a) { + result = "falsely negated" + } else { + result = "correct" + } + assert_eq(result, "correct", "!array should be falsy in conditional") +}) + +run("not null in conditional", function() { + var v = null + var result = "wrong" + if (!v) { + result = "correct" + } else { + result = "falsely truthy" + } + assert_eq(result, "correct", "!null should be truthy in conditional") +}) + +run("not function truthy in conditional", function() { + var f = function() { return 1 } + var result = "wrong" + if (!f) { + result = "falsely negated" + } else { + result = "correct" + } + assert_eq(result, "correct", "!function should be falsy in conditional") +}) + +run("double not string preserves truthiness", function() { + var s = "hello" + var result = "wrong" + if (!!s) { + result = "correct" + } else { + result = "falsely falsy" + } + assert_eq(result, "correct", "!!string should be truthy") +}) + +run("double not number preserves truthiness", function() { + var n = 1 + var result = "wrong" + if (!!n) { + result = "correct" + } else { + result = "falsely falsy" + } + assert_eq(result, "correct", "!!number should be truthy") +}) + +run("not string in ternary", function() { + var s = "hello" + var result = !s ? "negated" : "not negated" + assert_eq(result, "not negated", "!string ternary") +}) + +run("not number in ternary", function() { + var n = 99 + var result = !n ? "negated" : "not negated" + assert_eq(result, "not negated", "!number ternary") +}) + +run("not string in while guard", function() { + var s = "hello" + var entered = false + while (!s) { + entered = true + s = "" + } + assert_eq(entered, false, "while(!string) should not enter") +}) + +run("not result used in find predicate", function() { + var items = ["hello", "", "world", ""] + var idx = find(items, function(x) { return !x }) + assert_eq(idx, 1, "find with !string predicate") +}) + +run("not result used in filter predicate", function() { + var items = ["hello", "", "world", "", "ok"] + var result = filter(items, function(x) { return !x }) + assert_eq(length(result), 2, "filter !string count") + assert_eq(result[0], "", "filter !string [0]") + assert_eq(result[1], "", "filter !string [1]") +}) + +run("not result used in some/every", function() { + var truthy_strings = ["hello", "world", "ok"] + var has_empty = some(truthy_strings, function(x) { return !x }) + assert_eq(has_empty, false, "some(!str) on truthy strings") + var all_truthy = every(truthy_strings, function(x) { return !(!x) }) + assert_eq(all_truthy, true, "every(!!str) on truthy strings") +}) + +run("mixed types truthiness in loop", function() { + var values = ["hello", 42, true, {}, [1], false, 0, "", null] + var truthy_count = 0 + arrfor(values, function(v) { + if (!(!v)) { + truthy_count = truthy_count + 1 + } + }) + assert_eq(truthy_count, 5, "truthy values: string, number, true, object, array") +}) + +run("negated intrinsic result in conditional", function() { + var arr = [1, 2, 3] + var idx = find(arr, 99) + var result = "wrong" + if (!idx && idx != 0) { + result = "not found" + } else { + result = "found" + } + assert_eq(result, "not found", "negated null find result") +}) + +// ============================================================================ +// POLYMORPHIC CREATOR - array() COMPLETE COVERAGE +// ============================================================================ + +run("array from number - nulls", function() { + var a = array(5) + assert_eq(length(a), 5, "array(5) length") + assert_eq(a[0], null, "array(5)[0]") + assert_eq(a[4], null, "array(5)[4]") +}) + +run("array from number - zero size", function() { + var a = array(0) + assert_eq(length(a), 0, "array(0) length") +}) + +run("array from number with value", function() { + var a = array(4, 7) + assert_eq(length(a), 4, "array(4,7) length") + assert_eq(a[0], 7, "array(4,7)[0]") + assert_eq(a[3], 7, "array(4,7)[3]") +}) + +run("array from number with function", function() { + var a = array(5, function(i) { return i * 10 }) + assert_eq(length(a), 5, "array(5,fn) length") + assert_eq(a[0], 0, "array(5,fn)[0]") + assert_eq(a[4], 40, "array(5,fn)[4]") +}) + +run("array from number with zero-arity function", function() { + var a = array(3, function() { return "x" }) + assert_eq(length(a), 3, "array(3,fn0) length") + assert_eq(a[0], "x", "array(3,fn0)[0]") + assert_eq(a[2], "x", "array(3,fn0)[2]") +}) + +run("array copy", function() { + var original = [10, 20, 30] + var copy = array(original) + assert_eq(length(copy), 3, "array copy length") + assert_eq(copy[0], 10, "array copy [0]") + copy[0] = 99 + assert_eq(original[0], 10, "copy does not mutate original") +}) + +run("array map with index", function() { + var result = array([10, 20, 30], function(el, i) { return el + i }) + assert_eq(result[0], 10, "map idx [0]") + assert_eq(result[1], 21, "map idx [1]") + assert_eq(result[2], 32, "map idx [2]") +}) + +run("array map reverse with exit", function() { + var result = array([1, 2, 3, 4, 5], function(x) { + if (x < 2) return null + return x * 10 + }, true, null) + assert_eq(result[0], 50, "map rev exit [0]") + assert_eq(result[1], 40, "map rev exit [1]") + assert_eq(result[2], 30, "map rev exit [2]") + assert_eq(result[3], 20, "map rev exit [3]") + assert_eq(length(result), 4, "map rev exit length") +}) + +run("array slice basic", function() { + var result = array([10, 20, 30, 40, 50], 1, 4) + assert_eq(length(result), 3, "slice length") + assert_eq(result[0], 20, "slice [0]") + assert_eq(result[2], 40, "slice [2]") +}) + +run("array slice negative from", function() { + var result = array([10, 20, 30, 40, 50], -3) + assert_eq(length(result), 3, "slice neg from length") + assert_eq(result[0], 30, "slice neg from [0]") + assert_eq(result[2], 50, "slice neg from [2]") +}) + +run("array slice negative to", function() { + var result = array([10, 20, 30, 40, 50], 1, -1) + assert_eq(length(result), 3, "slice neg to length") + assert_eq(result[0], 20, "slice neg to [0]") + assert_eq(result[2], 40, "slice neg to [2]") +}) + +run("array concat", function() { + var result = array([1, 2], [3, 4, 5]) + assert_eq(length(result), 5, "concat length") + assert_eq(result[0], 1, "concat [0]") + assert_eq(result[4], 5, "concat [4]") +}) + +run("array concat empty", function() { + var result = array([1, 2], []) + assert_eq(length(result), 2, "concat empty length") + var result2 = array([], [3, 4]) + assert_eq(length(result2), 2, "concat from empty length") + assert_eq(result2[0], 3, "concat from empty [0]") +}) + +run("array from record", function() { + var keys = array({x: 1, y: 2, z: 3}) + assert_eq(length(keys), 3, "array(record) length") + assert_eq(find(keys, "x") != null, true, "has key x") + assert_eq(find(keys, "y") != null, true, "has key y") + assert_eq(find(keys, "z") != null, true, "has key z") +}) + +run("array from text - chars", function() { + var chars = array("hello") + assert_eq(length(chars), 5, "array(text) length") + assert_eq(chars[0], "h", "array(text)[0]") + assert_eq(chars[4], "o", "array(text)[4]") +}) + +run("array from text - empty", function() { + var chars = array("") + assert_eq(length(chars), 0, "array('') length") +}) + +run("array from text - separator", function() { + var parts = array("a::b::c", "::") + assert_eq(length(parts), 3, "split :: length") + assert_eq(parts[0], "a", "split ::[0]") + assert_eq(parts[2], "c", "split ::[2]") +}) + +run("array from text - dice", function() { + var chunks = array("abcdef", 2) + assert_eq(length(chunks), 3, "dice length") + assert_eq(chunks[0], "ab", "dice [0]") + assert_eq(chunks[1], "cd", "dice [1]") + assert_eq(chunks[2], "ef", "dice [2]") +}) + +run("array from text - dice uneven", function() { + var chunks = array("abcde", 2) + assert_eq(length(chunks), 3, "dice uneven length") + assert_eq(chunks[0], "ab", "dice uneven [0]") + assert_eq(chunks[2], "e", "dice uneven [2]") +}) + +// ============================================================================ +// POLYMORPHIC CREATOR - text() COMPLETE COVERAGE +// ============================================================================ + +run("text from array - join no sep", function() { + var result = text(["a", "b", "c"]) + assert_eq(result, "abc", "join no sep") +}) + +run("text from array - join with sep", function() { + var result = text(["x", "y", "z"], "-") + assert_eq(result, "x-y-z", "join with sep") +}) + +run("text from array - join empty", function() { + var result = text([], ",") + assert_eq(result, "", "join empty") +}) + +run("text from array - join single", function() { + var result = text(["only"], ",") + assert_eq(result, "only", "join single") +}) + +run("text from number", function() { + assert_eq(text(42), "42", "text(42)") + assert_eq(text(0), "0", "text(0)") + assert_eq(text(-7), "-7", "text(-7)") + assert_eq(text(3.14), "3.14", "text(3.14)") +}) + +run("text from number radix", function() { + assert_eq(text(255, 16), "ff", "text(255,16)") + assert_eq(text(10, 2), "1010", "text(10,2)") + assert_eq(text(8, 8), "10", "text(8,8)") +}) + +run("text substring", function() { + var s = "abcdefgh" + assert_eq(text(s, 2, 5), "cde", "text(s,2,5)") + assert_eq(text(s, 5), "fgh", "text(s,5)") + assert_eq(text(s, -3), "fgh", "text(s,-3)") + assert_eq(text(s, 0, -3), "abcde", "text(s,0,-3)") +}) + +// ============================================================================ +// POLYMORPHIC CREATOR - number() COMPLETE COVERAGE +// ============================================================================ + +run("number from logical", function() { + assert_eq(number(true), 1, "number(true)") + assert_eq(number(false), 0, "number(false)") +}) + +run("number from text", function() { + assert_eq(number("42"), 42, "number('42')") + assert_eq(number("-7"), -7, "number('-7')") + assert_eq(number("3.14"), 3.14, "number('3.14')") +}) + +run("number from text radix", function() { + assert_eq(number("ff", 16), 255, "number('ff',16)") + assert_eq(number("1010", 2), 10, "number('1010',2)") + assert_eq(number("77", 8), 63, "number('77',8)") +}) + +run("number from number", function() { + assert_eq(number(42), 42, "number(42)") +}) + +// ============================================================================ +// POLYMORPHIC CREATOR - record() COMPLETE COVERAGE +// ============================================================================ + +run("record copy", function() { + var original = {a: 1, b: 2} + var copy = record(original) + assert_eq(copy.a, 1, "record copy a") + assert_eq(copy.b, 2, "record copy b") + copy.a = 99 + assert_eq(original.a, 1, "record copy does not mutate original") +}) + +run("record merge", function() { + var a = {x: 1, y: 2} + var b = {y: 20, z: 30} + var merged = record(a, b) + assert_eq(merged.x, 1, "merge keeps a.x") + assert_eq(merged.y, 20, "merge overrides y") + assert_eq(merged.z, 30, "merge adds z") +}) + +run("record select keys", function() { + var obj = {a: 1, b: 2, c: 3, d: 4} + var subset = record(obj, ["a", "c"]) + assert_eq(subset.a, 1, "select a") + assert_eq(subset.c, 3, "select c") + assert_eq(subset.b, null, "select excludes b") +}) + +run("record from keys", function() { + var r = record(["a", "b", "c"]) + assert_eq(r.a, true, "keys set a") + assert_eq(r.b, true, "keys set b") + assert_eq(r.c, true, "keys set c") +}) + +run("record from keys with value", function() { + var r = record(["x", "y"], 0) + assert_eq(r.x, 0, "keys value x") + assert_eq(r.y, 0, "keys value y") +}) + +run("record from keys with function", function() { + var r = record(["a", "b", "c"], function(k, i) { return i }) + assert_eq(r.a, 0, "keys fn a") + assert_eq(r.b, 1, "keys fn b") + assert_eq(r.c, 2, "keys fn c") +}) + +// ============================================================================ +// ARRFOR - COMPLETE PARAMETER COVERAGE +// ============================================================================ + +run("arrfor empty array", function() { + var called = false + arrfor([], function(x) { called = true }) + assert_eq(called, false, "arrfor empty should not call fn") +}) + +run("arrfor single element", function() { + var sum = 0 + arrfor([42], function(x) { sum = sum + x }) + assert_eq(sum, 42, "arrfor single") +}) + +run("arrfor collects elements in order", function() { + var out = [] + arrfor([10, 20, 30], function(x) { out[] = x }) + assert_eq(out[0], 10, "arrfor order [0]") + assert_eq(out[1], 20, "arrfor order [1]") + assert_eq(out[2], 30, "arrfor order [2]") +}) + +run("arrfor reverse collects backwards", function() { + var out = [] + arrfor([10, 20, 30], function(x) { out[] = x }, true) + assert_eq(out[0], 30, "arrfor rev [0]") + assert_eq(out[1], 20, "arrfor rev [1]") + assert_eq(out[2], 10, "arrfor rev [2]") +}) + +run("arrfor exit stops early", function() { + var visited = [] + var result = arrfor([1, 2, 3, 4, 5], function(x) { + visited[] = x + if (x == 3) return true + return null + }, false, true) + assert_eq(result, true, "arrfor exit returns exit value") + assert_eq(length(visited), 3, "arrfor exit visited count") +}) + +run("arrfor exit no match returns null", function() { + var result = arrfor([1, 2, 3], function(x) { + return null + }, false, true) + assert_eq(result, null, "arrfor exit no match") +}) + +run("arrfor reverse with exit", function() { + var visited = [] + var result = arrfor([1, 2, 3, 4, 5], function(x) { + visited[] = x + if (x == 3) return true + return null + }, true, true) + assert_eq(result, true, "arrfor rev exit value") + assert_eq(visited[0], 5, "arrfor rev exit [0]") + assert_eq(visited[1], 4, "arrfor rev exit [1]") + assert_eq(visited[2], 3, "arrfor rev exit [2]") + assert_eq(length(visited), 3, "arrfor rev exit count") +}) + +run("arrfor with index forward", function() { + var pairs = [] + arrfor(["a", "b", "c"], function(x, i) { + pairs[] = text(i) + ":" + x + }) + assert_eq(pairs[0], "0:a", "arrfor idx [0]") + assert_eq(pairs[1], "1:b", "arrfor idx [1]") + assert_eq(pairs[2], "2:c", "arrfor idx [2]") +}) + +run("arrfor with index reverse", function() { + var pairs = [] + arrfor(["a", "b", "c"], function(x, i) { + pairs[] = text(i) + ":" + x + }, true) + assert_eq(pairs[0], "2:c", "arrfor rev idx [0]") + assert_eq(pairs[1], "1:b", "arrfor rev idx [1]") + assert_eq(pairs[2], "0:a", "arrfor rev idx [2]") +}) + +run("arrfor with mixed types", function() { + var types = [] + arrfor(["hello", 42, true, null, [1]], function(x) { + if (is_text(x)) { + types[] = "text" + } else if (is_number(x)) { + types[] = "number" + } else if (is_logical(x)) { + types[] = "logical" + } else if (is_null(x)) { + types[] = "null" + } else if (is_array(x)) { + types[] = "array" + } + }) + assert_eq(types[0], "text", "mixed [0]") + assert_eq(types[1], "number", "mixed [1]") + assert_eq(types[2], "logical", "mixed [2]") + assert_eq(types[3], "null", "mixed [3]") + assert_eq(types[4], "array", "mixed [4]") +}) + +// ============================================================================ +// FIND - COMPLETE PARAMETER COVERAGE +// ============================================================================ + +run("find value - string", function() { + assert_eq(find(["a", "b", "c"], "b"), 1, "find string value") +}) + +run("find value - number", function() { + assert_eq(find([10, 20, 30], 20), 1, "find number value") +}) + +run("find value - boolean", function() { + assert_eq(find([false, true, false], true), 1, "find boolean value") +}) + +run("find value - null", function() { + assert_eq(find([1, null, 3], null), 1, "find null value") +}) + +run("find value - first of duplicates", function() { + assert_eq(find([1, 2, 3, 2, 1], 2), 1, "find first duplicate") +}) + +run("find value - not found", function() { + assert_eq(find([1, 2, 3], 99), null, "find value missing") +}) + +run("find function - basic predicate", function() { + assert_eq(find([5, 10, 15], function(x) { return x > 7 }), 1, "find fn >7") +}) + +run("find function - first element", function() { + assert_eq(find([100, 1, 2], function(x) { return x > 50 }), 0, "find fn first") +}) + +run("find function - last element", function() { + assert_eq(find([1, 2, 100], function(x) { return x > 50 }), 2, "find fn last") +}) + +run("find function - not found", function() { + assert_eq(find([1, 2, 3], function(x) { return x > 100 }), null, "find fn missing") +}) + +run("find function - with index", function() { + assert_eq(find(["a", "b", "c", "d"], function(x, i) { return i == 2 }), 2, "find fn index") +}) + +run("find reverse - value", function() { + assert_eq(find([1, 2, 3, 2, 1], 2, true), 3, "find rev value") +}) + +run("find reverse - function", function() { + assert_eq(find([1, 10, 2, 20], function(x) { return x > 5 }, true), 3, "find rev fn") +}) + +run("find reverse - not found", function() { + assert_eq(find([1, 2, 3], 99, true), null, "find rev missing") +}) + +run("find from - value", function() { + assert_eq(find([1, 2, 3, 2, 1], 2, false, 2), 3, "find from value") +}) + +run("find from - function", function() { + assert_eq(find([10, 5, 20, 5], function(x) { return x < 10 }, false, 2), 3, "find from fn") +}) + +run("find from - not found", function() { + assert_eq(find([1, 2, 3], 1, false, 1), null, "find from missing") +}) + +run("find empty array", function() { + assert_eq(find([], 1), null, "find empty value") + assert_eq(find([], function(x) { return true }), null, "find empty fn") +}) + +run("find with intrinsic predicate", function() { + assert_eq(find(["a", 1, "b", 2], is_number), 1, "find is_number") + assert_eq(find([1, 2, "x", 3], is_text), 2, "find is_text") +}) + +run("find in string array", function() { + var words = ["apple", "banana", "cherry", "date"] + var idx = find(words, function(w) { return starts_with(w, "ch") }) + assert_eq(idx, 2, "find starts_with") +}) + +// ============================================================================ +// FILTER - COMPLETE PARAMETER COVERAGE +// ============================================================================ + +run("filter with intrinsic predicate is_number", function() { + var result = filter(["a", 1, "b", 2, null, 3], is_number) + assert_eq(length(result), 3, "filter is_number count") + assert_eq(result[0], 1, "filter is_number [0]") + assert_eq(result[2], 3, "filter is_number [2]") +}) + +run("filter with intrinsic predicate is_text", function() { + var result = filter(["a", 1, "b", 2, null], is_text) + assert_eq(length(result), 2, "filter is_text count") + assert_eq(result[0], "a", "filter is_text [0]") + assert_eq(result[1], "b", "filter is_text [1]") +}) + +run("filter with index-based predicate", function() { + var result = filter([10, 20, 30, 40, 50], function(x, i) { return i >= 2 }) + assert_eq(length(result), 3, "filter index count") + assert_eq(result[0], 30, "filter index [0]") + assert_eq(result[2], 50, "filter index [2]") +}) + +run("filter preserves order", function() { + var result = filter([5, 3, 8, 1, 7, 2], function(x) { return x > 3 }) + assert_eq(result[0], 5, "filter order [0]") + assert_eq(result[1], 8, "filter order [1]") + assert_eq(result[2], 7, "filter order [2]") +}) + +run("filter on booleans", function() { + var result = filter([true, false, true, false, true], function(x) { return x }) + assert_eq(length(result), 3, "filter booleans count") +}) + +run("filter on mixed types", function() { + var result = filter(["hello", 0, "", 42, null, true, false], function(x) { + return !is_null(x) && !is_logical(x) + }) + assert_eq(length(result), 4, "filter mixed count") +}) + +run("filter single element pass", function() { + var result = filter([42], function(x) { return x > 0 }) + assert_eq(length(result), 1, "filter single pass") + assert_eq(result[0], 42, "filter single value") +}) + +run("filter single element fail", function() { + var result = filter([42], function(x) { return x < 0 }) + assert_eq(length(result), 0, "filter single fail") +}) + +run("filter objects by property", function() { + var items = [ + {name: "a", active: true}, + {name: "b", active: false}, + {name: "c", active: true}, + {name: "d", active: false} + ] + var result = filter(items, function(x) { return x.active }) + assert_eq(length(result), 2, "filter obj prop count") + assert_eq(result[0].name, "a", "filter obj [0]") + assert_eq(result[1].name, "c", "filter obj [1]") +}) + +// ============================================================================ +// REDUCE - COMPLETE PARAMETER COVERAGE +// ============================================================================ + +run("reduce subtraction", function() { + var result = reduce([10, 3, 2, 1], function(a, b) { return a - b }) + assert_eq(result, 4, "reduce subtraction") +}) + +run("reduce with string initial", function() { + var result = reduce([1, 2, 3], function(a, b) { return a + text(b) }, "nums:") + assert_eq(result, "nums:123", "reduce string initial") +}) + +run("reduce building compound value", function() { + var result = reduce([1, 2, 3], function(acc, x) { + return acc + "(" + text(x * 2) + ")" + }, "") + assert_eq(result, "(2)(4)(6)", "reduce compound build") +}) + +run("reduce two elements no initial", function() { + var result = reduce([10, 3], function(a, b) { return a - b }) + assert_eq(result, 7, "reduce two elements") +}) + +run("reduce reverse no initial", function() { + var result = reduce(["a", "b", "c"], function(a, b) { return a + b }, null, true) + assert_eq(result, "cba", "reduce reverse no initial") +}) + +run("reduce reverse with initial", function() { + var result = reduce([1, 2, 3], function(a, b) { return a + b }, 100, true) + assert_eq(result, 106, "reduce reverse with initial") +}) + +run("reduce with min intrinsic", function() { + var result = reduce([5, 3, 8, 1, 9], min) + assert_eq(result, 1, "reduce min") +}) + +run("reduce with max intrinsic", function() { + var result = reduce([5, 3, 8, 1, 9], max) + assert_eq(result, 9, "reduce max") +}) + +run("reduce counting with predicate", function() { + var result = reduce([1, 2, 3, 4, 5, 6], function(count, x) { + if (x % 2 == 0) return count + 1 + return count + }, 0) + assert_eq(result, 3, "reduce count evens") +}) + +// ============================================================================ +// SOME - COMPLETE COVERAGE +// ============================================================================ + +run("some basic match", function() { + assert_eq(some([1, 2, 3, 4], function(x) { return x > 3 }), true, "some > 3") +}) + +run("some no match", function() { + assert_eq(some([1, 2, 3], function(x) { return x > 10 }), false, "some > 10") +}) + +run("some all match", function() { + assert_eq(some([10, 20, 30], function(x) { return x > 5 }), true, "some all > 5") +}) + +run("some empty array", function() { + assert_eq(some([], function(x) { return true }), false, "some empty") +}) + +run("some single element match", function() { + assert_eq(some([42], function(x) { return x == 42 }), true, "some single match") +}) + +run("some single element no match", function() { + assert_eq(some([42], function(x) { return x == 99 }), false, "some single no match") +}) + +run("some with string predicate", function() { + var result = some(["hello", "world"], function(x) { return x == "world" }) + assert_eq(result, true, "some string match") +}) + +run("some with mixed types", function() { + var result = some([1, "two", null, true], is_null) + assert_eq(result, true, "some is_null") +}) + +run("some with objects", function() { + var items = [{v: 1}, {v: 5}, {v: 3}] + var result = some(items, function(x) { return x.v > 4 }) + assert_eq(result, true, "some obj predicate") +}) + +run("some stops early", function() { + var count = 0 + some([1, 2, 3, 4, 5], function(x) { + count = count + 1 + return x == 2 + }) + assert_eq(count, 2, "some short-circuits") +}) + +// ============================================================================ +// EVERY - COMPLETE COVERAGE +// ============================================================================ + +run("every all pass", function() { + assert_eq(every([2, 4, 6], function(x) { return x % 2 == 0 }), true, "every evens") +}) + +run("every one fails", function() { + assert_eq(every([2, 3, 6], function(x) { return x % 2 == 0 }), false, "every one odd") +}) + +run("every none pass", function() { + assert_eq(every([1, 3, 5], function(x) { return x % 2 == 0 }), false, "every no evens") +}) + +run("every empty array", function() { + assert_eq(every([], function(x) { return false }), true, "every empty is vacuously true") +}) + +run("every single element pass", function() { + assert_eq(every([42], function(x) { return x > 0 }), true, "every single pass") +}) + +run("every single element fail", function() { + assert_eq(every([-1], function(x) { return x > 0 }), false, "every single fail") +}) + +run("every with strings", function() { + var result = every(["hello", "world", "ok"], function(x) { return length(x) > 1 }) + assert_eq(result, true, "every strings > 1") +}) + +run("every with intrinsic", function() { + assert_eq(every([1, 2, 3], is_number), true, "every is_number") + assert_eq(every([1, "two", 3], is_number), false, "every is_number mixed") +}) + +run("every stops early", function() { + var count = 0 + every([1, 2, 3, 4, 5], function(x) { + count = count + 1 + return x < 3 + }) + assert_eq(count, 3, "every short-circuits") +}) + +run("every with objects", function() { + var items = [{active: true}, {active: true}, {active: true}] + var result = every(items, function(x) { return x.active }) + assert_eq(result, true, "every obj active") +}) + +// ============================================================================ +// SORT - COMPLETE COVERAGE +// ============================================================================ + +run("sort already sorted", function() { + var result = sort([1, 2, 3, 4, 5]) + assert_eq(result[0], 1, "sort sorted [0]") + assert_eq(result[4], 5, "sort sorted [4]") +}) + +run("sort reverse order", function() { + var result = sort([5, 4, 3, 2, 1]) + assert_eq(result[0], 1, "sort reversed [0]") + assert_eq(result[4], 5, "sort reversed [4]") +}) + +run("sort with duplicates", function() { + var result = sort([3, 1, 4, 1, 5, 9, 2, 6, 5, 3]) + assert_eq(result[0], 1, "sort dup [0]") + assert_eq(result[1], 1, "sort dup [1]") + assert_eq(result[9], 9, "sort dup [9]") +}) + +run("sort floats", function() { + var result = sort([3.14, 1.41, 2.72, 1.73]) + assert_eq(result[0], 1.41, "sort float [0]") + assert_eq(result[3], 3.14, "sort float [3]") +}) + +run("sort strings alphabetical", function() { + var result = sort(["banana", "apple", "cherry", "date"]) + assert_eq(result[0], "apple", "sort str [0]") + assert_eq(result[3], "date", "sort str [3]") +}) + +run("sort by record field - number", function() { + var items = [{n: 3, l: "c"}, {n: 1, l: "a"}, {n: 2, l: "b"}] + var result = sort(items, "n") + assert_eq(result[0].l, "a", "sort field [0]") + assert_eq(result[1].l, "b", "sort field [1]") + assert_eq(result[2].l, "c", "sort field [2]") +}) + +run("sort by record field - text", function() { + var items = [{name: "Charlie"}, {name: "Alice"}, {name: "Bob"}] + var result = sort(items, "name") + assert_eq(result[0].name, "Alice", "sort text field [0]") + assert_eq(result[2].name, "Charlie", "sort text field [2]") +}) + +run("sort by array index", function() { + var items = [[3, "c"], [1, "a"], [2, "b"]] + var result = sort(items, 0) + assert_eq(result[0][1], "a", "sort index [0]") + assert_eq(result[2][1], "c", "sort index [2]") +}) + +run("sort by external keys", function() { + var items = ["c", "a", "b"] + var keys = [3, 1, 2] + var result = sort(items, keys) + assert_eq(result[0], "a", "sort external [0]") + assert_eq(result[1], "b", "sort external [1]") + assert_eq(result[2], "c", "sort external [2]") +}) + +run("sort stability", function() { + var items = [ + {key: 1, order: "a"}, + {key: 1, order: "b"}, + {key: 1, order: "c"}, + {key: 1, order: "d"} + ] + var result = sort(items, "key") + assert_eq(result[0].order, "a", "stable [0]") + assert_eq(result[1].order, "b", "stable [1]") + assert_eq(result[2].order, "c", "stable [2]") + assert_eq(result[3].order, "d", "stable [3]") +}) + +run("sort empty", function() { + assert_eq(length(sort([])), 0, "sort empty") +}) + +run("sort single", function() { + var result = sort([42]) + assert_eq(result[0], 42, "sort single") + assert_eq(length(result), 1, "sort single length") +}) + +run("sort does not mutate original", function() { + var arr = [3, 1, 2] + var sorted = sort(arr) + assert_eq(arr[0], 3, "sort no mutate [0]") + assert_eq(arr[1], 1, "sort no mutate [1]") + assert_eq(sorted[0], 1, "sorted [0]") +}) + +// ============================================================================ +// REVERSE - ADDITIONAL COVERAGE +// ============================================================================ + +run("reverse single element", function() { + var result = reverse([42]) + assert_eq(result[0], 42, "reverse single") + assert_eq(length(result), 1, "reverse single length") +}) + +run("reverse two elements", function() { + var result = reverse([1, 2]) + assert_eq(result[0], 2, "reverse two [0]") + assert_eq(result[1], 1, "reverse two [1]") +}) + +run("reverse mixed types", function() { + var result = reverse(["a", 1, null, true]) + assert_eq(result[0], true, "reverse mixed [0]") + assert_eq(result[1], null, "reverse mixed [1]") + assert_eq(result[2], 1, "reverse mixed [2]") + assert_eq(result[3], "a", "reverse mixed [3]") +}) + +run("reverse does not mutate", function() { + var arr = [1, 2, 3] + var rev = reverse(arr) + assert_eq(arr[0], 1, "reverse no mutate [0]") + assert_eq(rev[0], 3, "reversed [0]") +}) + +// ============================================================================ +// INLINE LOOP EXPANSION - TRUTHINESS IN PREDICATES +// Tests that inlined loops correctly handle truthy/falsy for all types. +// ============================================================================ + +run("filter inline - string truthiness", function() { + var items = ["hello", "", "world", "", "ok"] + var result = filter(items, function(x) { return x }) + assert_eq(length(result), 3, "filter truthy strings count") + assert_eq(result[0], "hello", "filter truthy [0]") + assert_eq(result[1], "world", "filter truthy [1]") + assert_eq(result[2], "ok", "filter truthy [2]") +}) + +run("filter inline - number truthiness", function() { + var items = [0, 1, 0, 2, 0, 3] + var result = filter(items, function(x) { return x }) + assert_eq(length(result), 3, "filter truthy numbers count") + assert_eq(result[0], 1, "filter truthy num [0]") + assert_eq(result[1], 2, "filter truthy num [1]") + assert_eq(result[2], 3, "filter truthy num [2]") +}) + +run("filter inline - null truthiness", function() { + var items = [1, null, 2, null, 3] + var result = filter(items, function(x) { return x }) + assert_eq(length(result), 3, "filter non-null count") +}) + +run("filter inline - negated predicate", function() { + var items = ["hello", "", "world", ""] + var result = filter(items, function(x) { return !x }) + assert_eq(length(result), 2, "filter !string count") + assert_eq(result[0], "", "filter !string [0]") +}) + +run("find inline - string truthiness predicate", function() { + var items = ["", "", "found", ""] + var idx = find(items, function(x) { return x }) + assert_eq(idx, 2, "find truthy string") +}) + +run("find inline - negated predicate", function() { + var items = ["hello", "world", "", "ok"] + var idx = find(items, function(x) { return !x }) + assert_eq(idx, 2, "find !string") +}) + +run("some inline - string truthiness", function() { + var result = some(["", "", "hello"], function(x) { return x }) + assert_eq(result, true, "some truthy string") +}) + +run("every inline - string truthiness", function() { + var result = every(["hello", "world", "ok"], function(x) { return x }) + assert_eq(result, true, "every truthy strings") + var result2 = every(["hello", "", "ok"], function(x) { return x }) + assert_eq(result2, false, "every truthy strings with empty") +}) + +run("arrfor inline - conditional on string", function() { + var truthy = 0 + arrfor(["hello", "", "world", ""], function(x) { + if (x) { + truthy = truthy + 1 + } + }) + assert_eq(truthy, 2, "arrfor conditional string count") +}) + +run("reduce inline - conditional accumulation", function() { + var result = reduce(["a", "", "b", "", "c"], function(acc, x) { + if (x) return acc + x + return acc + }, "") + assert_eq(result, "abc", "reduce skip empty strings") +}) + +// ============================================================================ +// INLINE EXPANSION - ADDITIONAL EDGE CASES +// ============================================================================ + +run("filter inline - intrinsic is_integer", function() { + var result = filter([1, 1.5, 2, 2.5, 3], is_integer) + assert_eq(length(result), 3, "filter is_integer count") + assert_eq(result[0], 1, "filter is_integer [0]") + assert_eq(result[1], 2, "filter is_integer [1]") + assert_eq(result[2], 3, "filter is_integer [2]") +}) + +run("find inline - intrinsic is_text", function() { + var idx = find([1, 2, "hello", 3], is_text) + assert_eq(idx, 2, "find is_text") +}) + +run("find inline - intrinsic is_null", function() { + var idx = find([1, 2, null, 3], is_null) + assert_eq(idx, 2, "find is_null") +}) + +run("some inline - intrinsic is_array", function() { + assert_eq(some([1, "x", [3]], is_array), true, "some is_array true") + assert_eq(some([1, "x", 3], is_array), false, "some is_array false") +}) + +run("every inline - intrinsic is_number", function() { + assert_eq(every([1, 2, 3], is_number), true, "every is_number true") + assert_eq(every([1, "x", 3], is_number), false, "every is_number false") +}) + +run("arrfor inline - empty array", function() { + var called = false + arrfor([], function(x) { called = true }) + assert_eq(called, false, "arrfor inline empty") +}) + +run("reduce inline - string concat", function() { + var result = reduce(["a", "b", "c"], function(a, b) { return a + b }) + assert_eq(result, "abc", "reduce inline concat") +}) + +run("reduce inline - empty no initial returns null", function() { + var result = reduce([], function(a, b) { return a + b }) + assert_eq(result, null, "reduce inline empty null") +}) + +run("reduce inline - reverse string concat", function() { + var result = reduce(["a", "b", "c"], function(a, b) { return a + b }, "", true) + assert_eq(result, "cba", "reduce inline rev concat") +}) + +run("find inline - reverse from end", function() { + var idx = find([1, 2, 3, 4, 5], function(x) { return x % 2 == 0 }, true) + assert_eq(idx, 3, "find inline rev even") +}) + +run("arrfor inline - large array", function() { + var arr = array(100, function(i) { return i }) + var sum = 0 + arrfor(arr, function(x) { sum = sum + x }) + assert_eq(sum, 4950, "arrfor 100 elements sum") +}) + +run("filter inline - large array", function() { + var arr = array(100, function(i) { return i }) + var result = filter(arr, function(x) { return x % 10 == 0 }) + assert_eq(length(result), 10, "filter 100 elements count") + assert_eq(result[0], 0, "filter 100 [0]") + assert_eq(result[9], 90, "filter 100 [9]") +}) + // ============================================================================ // SUMMARY // ============================================================================ From d88692cd30c2c507be10d74f849ebd2e0bfc8022 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sun, 22 Feb 2026 10:31:15 -0600 Subject: [PATCH 2/2] fix inline issue --- mcode.cm | 57 +++++++++++++++++++++++++++++++++++++++++++-------- streamline.ce | 14 ++++++++++--- streamline.cm | 3 +++ vm_suite.ce | 11 ++++++++++ 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/mcode.cm b/mcode.cm index 79a79996..e7e4c935 100644 --- a/mcode.cm +++ b/mcode.cm @@ -882,13 +882,43 @@ var mcode = function(ast) { var inline_find = true // --- Helper: emit arity-dispatched callback invocation --- - // ctx = {fn, fn_arity, result, null_s, frame, zero, one, az, ao, prefix} + // ctx = {fn, fn_arity, result, null_s, frame, zero, one, az, ao, prefix, + // known_arity (optional — compile-time arity of callback literal)} // 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") + var call_one = null + var call_two = null + var call_done = null + var ka = ctx.known_arity + // When callback arity is known at compile time, emit only the matching + // call path. This avoids dead branches where parameters are nulled, + // which confuse the type checker after inlining (e.g. push on null). + if (ka != null) { + if (ka >= max_args) { + ka = max_args + } + if (ka == 0) { + emit_3("frame", ctx.frame, ctx.fn, 0) + emit_3("setarg", ctx.frame, 0, ctx.null_s) + emit_2("invoke", ctx.frame, ctx.result) + } else if (ka == 1 || max_args < 2) { + 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) + } else { + 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) + } + return null + } + call_one = gen_label(ctx.prefix + "_c1") + call_two = gen_label(ctx.prefix + "_c2") + 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) @@ -952,7 +982,7 @@ var mcode = function(ast) { } // --- Helper: emit a reduce loop body --- - // r = {acc, i, arr, fn, len, fn_arity}; emits loop updating acc in-place. + // r = {acc, i, arr, fn, len, fn_arity, known_arity}; emits loop updating acc in-place. // Caller must emit the done_label after calling this. var emit_reduce_loop = function(r, forward, done_label) { var acc = r.acc @@ -971,7 +1001,8 @@ var mcode = function(ast) { var f = alloc_slot() var loop_label = gen_label("reduce_loop") 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"} + frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "reduce", + known_arity: r.known_arity} emit_2("int", one, 1) emit_2("int", zero, 0) emit_1("null", null_s) @@ -1428,7 +1459,8 @@ var mcode = function(ast) { emit_2("length", fn_arity, fn_slot) emit_2("int", zero, 0) emit_2("int", one, 1) - r = {acc: acc, i: i, arr: arr_slot, fn: fn_slot, len: len, fn_arity: fn_arity} + r = {acc: acc, i: i, arr: arr_slot, fn: fn_slot, len: len, fn_arity: fn_arity, + known_arity: args.fn_known_arity} if (nargs == 2) { null_label = gen_label("reduce_null") d1 = gen_label("reduce_d1") @@ -1877,6 +1909,8 @@ var mcode = function(ast) { var guard_t = 0 var guard_err = null var guard_done = null + var cb_known = null + var cb_p = null if (expr == null) { return -1 @@ -2184,7 +2218,14 @@ var mcode = function(ast) { 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_reduce(d, {arr: a0, fn: a1, init: a2, rev: a3}, nargs) + cb_known = null + if (args_list[1].kind == "function") { + cb_p = args_list[1].list + if (cb_p == null) cb_p = args_list[1].parameters + cb_known = cb_p != null ? length(cb_p) : 0 + } + return expand_inline_reduce(d, {arr: a0, fn: a1, init: a2, rev: a3, + fn_known_arity: cb_known}, nargs) } // array(arr, fn) → inline map expansion // Skip when first arg is a number literal (that's array(N, fn) — creation, not map) diff --git a/streamline.ce b/streamline.ce index aafd74aa..a15d5e66 100644 --- a/streamline.ce +++ b/streamline.ce @@ -17,6 +17,7 @@ var show_ir = false var show_check = false var show_types = false var show_diagnose = false +var no_inline = false var filename = null var i = 0 var di = 0 @@ -33,6 +34,8 @@ for (i = 0; i < length(args); i++) { show_types = true } else if (args[i] == '--diagnose') { show_diagnose = true + } else if (args[i] == '--no-inline') { + no_inline = true } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell streamline [--stats] [--ir] [--check] [--types] [--diagnose] ") $stop() @@ -58,6 +61,11 @@ var compiled = null if (show_diagnose) { compiled = shop.mcode_file(filename) compiled._warn = true + if (no_inline) compiled._no_inline = true + optimized = use('streamline')(compiled) +} else if (no_inline) { + compiled = shop.mcode_file(filename) + compiled._no_inline = true optimized = use('streamline')(compiled) } else { optimized = shop.compile_file(filename) @@ -363,12 +371,12 @@ if (show_diagnose) { di = 0 while (di < length(optimized._diagnostics)) { diag = optimized._diagnostics[di] - print(`${diag.file}:${text(diag.line)}:${text(diag.col)}: ${diag.severity}: ${diag.message}`) + log.compile(`${diag.file}:${text(diag.line)}:${text(diag.col)}: ${diag.severity}: ${diag.message}`) di = di + 1 } - print(`\n${text(length(optimized._diagnostics))} diagnostic(s)`) + log.compile(`\n${text(length(optimized._diagnostics))} diagnostic(s)`) } else { - print("No diagnostics.") + log.compile("No diagnostics.") } } diff --git a/streamline.cm b/streamline.cm index 5c37279a..f7d10366 100644 --- a/streamline.cm +++ b/streamline.cm @@ -3213,6 +3213,9 @@ var streamline = function(ir, log) { } // Phase 2: Inline pass + if (ir._no_inline) { + return ir + } var changed_main = false var changed_fns = null if (ir.main != null) { diff --git a/vm_suite.ce b/vm_suite.ce index 13155636..d728b3eb 100644 --- a/vm_suite.ce +++ b/vm_suite.ce @@ -6928,6 +6928,17 @@ run("reduce counting with predicate", function() { assert_eq(result, 3, "reduce count evens") }) +run("reduce building array", function() { + var result = reduce([1, 2, 3], function(acc, x) { + acc[] = x * 2 + return acc + }, []) + assert_eq(length(result), 3, "reduce array build length") + assert_eq(result[0], 2, "reduce array [0]") + assert_eq(result[1], 4, "reduce array [1]") + assert_eq(result[2], 6, "reduce array [2]") +}) + // ============================================================================ // SOME - COMPLETE COVERAGE // ============================================================================