Files
cell/vm_suite.ce

8949 lines
297 KiB
Plaintext

// Comprehensive test suite for cell runtime stability
// Self-running: no test harness required
// Uses disrupt/disruption instead of throw/try/catch
var passed = 0
var failed = 0
var error_names = []
var error_reasons = []
var fail_msg = ""
// pre-allocate 500 slots to avoid array growth during disruption handlers
var _i = 0
for (_i = 0; _i < 5; _i++) {
error_names[] = null
error_reasons[] = null
}
var fail = function(msg) {
fail_msg = msg
disrupt
}
var assert_eq = function(actual, expected, msg) {
if (actual != expected) fail(msg + " (got=" + text(actual) + ")")
}
var run = function(name, fn) {
fail_msg = ""
fn()
passed = passed + 1
} disruption {
error_names[failed] = name
error_reasons[failed] = fail_msg == "" ? "disruption" : fail_msg
failed = failed + 1
}
var should_disrupt = function(fn) {
var caught = false
var wrapper = function() {
fn()
} disruption {
caught = true
}
wrapper()
return caught
}
// ============================================================================
// ARITHMETIC OPERATORS - Numbers
// ============================================================================
run("number addition", function() {
if (1 + 2 != 3) fail("basic addition failed")
if (0 + 0 != 0) fail("zero addition failed")
if (-5 + 3 != -2) fail("negative addition failed")
if (0.1 + 0.2 - 0.3 > 0.0001) fail("float addition precision issue")
})
run("number subtraction", function() {
if (5 - 3 != 2) fail("basic subtraction failed")
if (0 - 5 != -5) fail("zero subtraction failed")
if (-5 - -3 != -2) fail("negative subtraction failed")
})
run("number multiplication", function() {
if (3 * 4 != 12) fail("basic multiplication failed")
if (0 * 100 != 0) fail("zero multiplication failed")
if (-3 * 4 != -12) fail("negative multiplication failed")
if (-3 * -4 != 12) fail("double negative multiplication failed")
})
run("number division", function() {
if (12 / 4 != 3) fail("basic division failed")
if (1 / 2 != 0.5) fail("fractional division failed")
if (-12 / 4 != -3) fail("negative division failed")
if (12 / -4 != -3) fail("division by negative failed")
})
run("number modulo", function() {
if (10 % 3 != 1) fail("basic modulo failed")
if (10 % 5 != 0) fail("even modulo failed")
if (-10 % 3 != -1) fail("negative modulo failed")
})
run("number exponentiation", function() {
if (2 ** 3 != 8) fail("basic exponentiation failed")
if (5 ** 0 != 1) fail("zero exponent failed")
if (2 ** -1 != 0.5) fail("negative exponent failed")
})
// ============================================================================
// STRING OPERATORS
// ============================================================================
run("string plus string", function() {
var x = "hello" + " world"
if (x != "hello world") fail("string + string should work")
})
run("string concatenation empty", function() {
if ("" + "" != "") fail("empty string concatenation failed")
if ("hello" + "" != "hello") fail("concatenation with empty string failed")
if ("" + "world" != "world") fail("empty + string failed")
})
run("string concat does not mutate alias", function() {
var a = "hello world"
var b = a
a = a + " appended"
if (a != "hello world appended") fail("a wrong, got " + a)
if (b != "hello world") fail("b should still be hello world, got " + b)
})
run("string concat in loop preserves aliases", function() {
var a = "starting value"
var copies = [a]
var i = 0
while (i < 5) {
a = a + " more"
copies[] = a
i = i + 1
}
if (copies[0] != "starting value") fail("copies[0] wrong, got " + copies[0])
if (copies[1] != "starting value more") fail("copies[1] wrong, got " + copies[1])
if (copies[5] != "starting value more more more more more") fail("copies[5] wrong, got " + copies[5])
if (a != "starting value more more more more more") fail("a wrong, got " + a)
})
// ============================================================================
// TYPE MIXING SHOULD DISRUPT
// ============================================================================
run("number plus string disrupts", function() {
if (!should_disrupt(function() { var x = 1 + "hello" })) fail("number + string should disrupt")
})
run("string plus number disrupts", function() {
if (!should_disrupt(function() { var x = "hello" + 1 })) fail("string + number should disrupt")
})
run("object plus string disrupts", function() {
if (!should_disrupt(function() { var x = {} + "hello" })) fail("object + string should disrupt")
})
run("string plus object disrupts", function() {
if (!should_disrupt(function() { var x = "hello" + {} })) fail("string + object should disrupt")
})
run("array plus string disrupts", function() {
if (!should_disrupt(function() { var x = [] + "hello" })) fail("array + string should disrupt")
})
run("string plus array disrupts", function() {
if (!should_disrupt(function() { var x = "hello" + [] })) fail("string + array should disrupt")
})
run("boolean plus string disrupts", function() {
if (!should_disrupt(function() { var x = true + "hello" })) fail("boolean + string should disrupt")
})
run("string plus boolean disrupts", function() {
if (!should_disrupt(function() { var x = "hello" + false })) fail("string + boolean should disrupt")
})
run("null plus string disrupts", function() {
if (!should_disrupt(function() { var x = null + "hello" })) fail("null + string should disrupt")
})
run("string plus null disrupts", function() {
if (!should_disrupt(function() { var x = "hello" + null })) fail("string + null should disrupt")
})
// ============================================================================
// COMPARISON OPERATORS
// ============================================================================
run("equality numbers", function() {
if (!(5 == 5)) fail("number equality failed")
if (5 == 6) fail("number inequality detection failed")
if (!(0 == 0)) fail("zero equality failed")
if (!(-5 == -5)) fail("negative equality failed")
})
run("inequality numbers", function() {
if (5 != 5) fail("number inequality failed")
if (!(5 != 6)) fail("number difference detection failed")
})
run("less than", function() {
if (!(3 < 5)) fail("less than failed")
if (5 < 3) fail("not less than failed")
if (5 < 5) fail("equal not less than failed")
})
run("less than or equal", function() {
if (!(3 <= 5)) fail("less than or equal failed")
if (!(5 <= 5)) fail("equal in less than or equal failed")
if (6 <= 5) fail("not less than or equal failed")
})
run("greater than", function() {
if (!(5 > 3)) fail("greater than failed")
if (3 > 5) fail("not greater than failed")
if (5 > 5) fail("equal not greater than failed")
})
run("greater than or equal", function() {
if (!(5 >= 3)) fail("greater than or equal failed")
if (!(5 >= 5)) fail("equal in greater than or equal failed")
if (3 >= 5) fail("not greater than or equal failed")
})
run("string equality", function() {
if (!("hello" == "hello")) fail("string equality failed")
if ("hello" == "world") fail("string inequality detection failed")
if (!("" == "")) fail("empty string equality failed")
})
run("null equality", function() {
if (!(null == null)) fail("null equality failed")
if (null == 0) fail("null should not equal 0")
if (null == false) fail("null should not equal false")
if (null == "") fail("null should not equal empty string")
})
run("boolean equality", function() {
if (!(true == true)) fail("true equality failed")
if (!(false == false)) fail("false equality failed")
if (true == false) fail("boolean inequality detection failed")
})
// ============================================================================
// LOGICAL OPERATORS
// ============================================================================
run("logical and", function() {
if (!(true && true)) fail("true && true failed")
if (true && false) fail("true && false failed")
if (false && true) fail("false && true failed")
if (false && false) fail("false && false failed")
})
run("logical or", function() {
if (!(true || true)) fail("true || true failed")
if (!(true || false)) fail("true || false failed")
if (!(false || true)) fail("false || true failed")
if (false || false) fail("false || false failed")
})
run("logical not", function() {
if (!(!false)) fail("!false failed")
if (!true) fail("!true failed")
})
run("short circuit and", function() {
var called = false
var fn = function() { called = true; return true }
var result = false && fn()
if (called) fail("AND should short circuit")
})
run("short circuit or", function() {
var called = false
var fn = function() { called = true; return false }
var result = true || fn()
if (called) fail("OR should short circuit")
})
// ============================================================================
// BITWISE OPERATORS
// ============================================================================
run("bitwise and", function() {
if ((5 & 3) != 1) fail("bitwise AND failed")
if ((12 & 10) != 8) fail("bitwise AND failed")
})
run("bitwise or", function() {
if ((5 | 3) != 7) fail("bitwise OR failed")
if ((12 | 10) != 14) fail("bitwise OR failed")
})
run("bitwise xor", function() {
if ((5 ^ 3) != 6) fail("bitwise XOR failed")
if ((12 ^ 10) != 6) fail("bitwise XOR failed")
})
run("bitwise not", function() {
if (~5 != -6) fail("bitwise NOT failed")
if (~0 != -1) fail("bitwise NOT of zero failed")
})
run("left shift", function() {
if ((5 << 2) != 20) fail("left shift failed")
if ((1 << 3) != 8) fail("left shift failed")
})
run("right shift", function() {
if ((20 >> 2) != 5) fail("right shift failed")
if ((8 >> 3) != 1) fail("right shift failed")
})
run("unsigned right shift", function() {
if ((-1 >>> 1) != 2147483647) fail("unsigned right shift failed")
})
// ============================================================================
// VARIABLE DECLARATIONS AND SCOPING
// ============================================================================
run("var declaration", function() {
var x = 5
if (x != 5) fail("var declaration failed")
})
run("var reassignment", function() {
var x = 5
x = 10
if (x != 10) fail("var reassignment failed")
})
run("var null initialization", function() {
var x = null
if (x != null) fail("null-initialized var should be null")
})
run("multiple var declaration", function() {
var a = 1, b = 2, c = 3
if (a != 1 || b != 2 || c != 3) fail("multiple var declaration failed")
})
run("function scope", function() {
var outer = "outer"
var fn = function() {
var inner = "inner"
return inner
}
if (fn() != "inner") fail("function scope failed")
})
// ============================================================================
// FUNCTION CALLS
// ============================================================================
run("function call no args", function() {
var fn = function() { return 42 }
if (fn() != 42) fail("function call with no args failed")
})
run("function call one arg", function() {
var fn = function(x) { return x * 2 }
if (fn(5) != 10) fail("function call with one arg failed")
})
run("function call multiple args", function() {
var fn = function(a, b, c) { return a + b + c }
if (fn(1, 2, 3) != 6) fail("function call with multiple args failed")
})
run("function call extra args disrupts", function() {
var fn = function(a, b) { return a + b }
if (!should_disrupt(function() { fn(1, 2, 3, 4) }))
fail("function call with extra args should disrupt")
})
run("function call missing args", function() {
var fn = function(a, b, c) { return (a || 0) + (b || 0) + (c || 0) }
if (fn(1) != 1) fail("function call with missing args failed")
})
run("closure call extra args disrupts", function() {
var mk = function(base) {
return function(x) { return base + x }
}
var add5 = mk(5)
if (!should_disrupt(function() { add5(1, 2) }))
fail("closure call with extra args should disrupt")
})
run("call helper extra args disrupts", function() {
var fn = function(a, b) { return a + b }
if (!should_disrupt(function() { call(fn, null, [1, 2, 3]) }))
fail("call helper should disrupt on extra args")
})
run("function return", function() {
var fn = function() { return 5 }
if (fn() != 5) fail("function return failed")
})
run("function return early", function() {
var fn = function() {
return 5
return 10
}
if (fn() != 5) fail("early return failed")
})
run("function no return", function() {
var fn = function() { var x = 5 }
if (fn() != null) fail("function with no return should return null")
})
run("nested function calls", function() {
var add = function(a, b) { return a + b }
var mul = function(a, b) { return a * b }
if (add(mul(2, 3), mul(4, 5)) != 26) fail("nested function calls failed")
})
run("function as value", function() {
var fn = function() { return 42 }
var fn2 = fn
if (fn2() != 42) fail("function as value failed")
})
run("function closure", function() {
var outer = function(x) {
return function(y) {
return x + y
}
}
var add5 = outer(5)
if (add5(3) != 8) fail("closure failed")
})
run("function closure mutation", function() {
var counter = function() {
var count = 0
return function() {
count = count + 1
return count
}
}
var c = counter()
if (c() != 1) fail("closure mutation failed (1)")
if (c() != 2) fail("closure mutation failed (2)")
if (c() != 3) fail("closure mutation failed (3)")
})
// ============================================================================
// RECURSION
// ============================================================================
run("simple recursion", function() {
var factorial = function(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
if (factorial(5) != 120) fail("factorial recursion failed")
})
run("mutual recursion", function() {
var isOdd = null
var isEven = function(n) {
if (n == 0) return true
return isOdd(n - 1)
}
isOdd = function(n) {
if (n == 0) return false
return isEven(n - 1)
}
if (!isEven(4)) fail("mutual recursion even failed")
if (isOdd(4)) fail("mutual recursion odd failed")
})
run("deep recursion", function() {
var sum = function(n) {
if (n == 0) return 0
return n + sum(n - 1)
}
if (sum(100) != 5050) fail("deep recursion failed")
})
// ============================================================================
// ARRAYS
// ============================================================================
run("array literal", function() {
var arr = [1, 2, 3]
if (arr[0] != 1 || arr[1] != 2 || arr[2] != 3) fail("array literal failed")
})
run("array length", function() {
var arr = [1, 2, 3, 4, 5]
if (length(arr) != 5) fail("array length failed")
})
run("array empty", function() {
var arr = []
if (length(arr) != 0) fail("empty array length failed")
})
run("array push", function() {
var arr = [1, 2]
arr[] = 3
if (length(arr) != 3) fail("array push length failed")
if (arr[2] != 3) fail("array push value failed")
})
run("array pop", function() {
var arr = [1, 2, 3]
var val = arr[]
if (val != 3) fail("array pop value failed")
if (length(arr) != 2) fail("array pop length failed")
})
run("array index access", function() {
var arr = [10, 20, 30]
if (arr[0] != 10) fail("array index 0 failed")
if (arr[1] != 20) fail("array index 1 failed")
if (arr[2] != 30) fail("array index 2 failed")
})
run("array index assignment", function() {
var arr = [1, 2, 3]
arr[1] = 99
if (arr[1] != 99) fail("array index assignment failed")
})
run("array mixed types", function() {
var arr = [1, "hello", true, null, {}]
if (arr[0] != 1) fail("mixed array number failed")
if (arr[1] != "hello") fail("mixed array string failed")
if (arr[2] != true) fail("mixed array boolean failed")
if (arr[3] != null) fail("mixed array null failed")
})
run("array nested", function() {
var arr = [[1, 2], [3, 4]]
if (arr[0][0] != 1) fail("nested array access failed")
if (arr[1][1] != 4) fail("nested array access failed")
})
// ============================================================================
// OBJECTS
// ============================================================================
run("object literal", function() {
var obj = {a: 1, b: 2}
if (obj.a != 1 || obj.b != 2) fail("object literal failed")
})
run("object property access", function() {
var obj = {name: "Alice", age: 30}
if (obj.name != "Alice") fail("object property access failed")
if (obj.age != 30) fail("object property access failed")
})
run("object bracket access", function() {
var obj = {x: 10, y: 20}
if (obj["x"] != 10) fail("object bracket access failed")
if (obj["y"] != 20) fail("object bracket access failed")
})
run("object property assignment", function() {
var obj = {a: 1}
obj.a = 99
if (obj.a != 99) fail("object property assignment failed")
})
run("object add property", function() {
var obj = {}
obj.newProp = 42
if (obj.newProp != 42) fail("object add property failed")
})
run("object computed property", function() {
var key = "dynamicKey"
var obj = {}
obj[key] = 123
if (obj.dynamicKey != 123) fail("object computed property failed")
})
run("object nested", function() {
var obj = {outer: {inner: 42}}
if (obj.outer.inner != 42) fail("nested object access failed")
})
run("object method", function() {
var obj = {
value: 10,
getValue: function() { return this.value }
}
if (obj.getValue() != 10) fail("object method failed")
})
run("object this binding", function() {
var obj = {
x: 5,
getX: function() { return this.x }
}
if (obj.getX() != 5) fail("this binding failed")
})
// ============================================================================
// CONTROL FLOW - IF/ELSE
// ============================================================================
run("if true", function() {
var x = 0
if (true) x = 1
if (x != 1) fail("if true failed")
})
run("if false", function() {
var x = 0
if (false) x = 1
if (x != 0) fail("if false failed")
})
run("if else true", function() {
var x = 0
if (true) x = 1
else x = 2
if (x != 1) fail("if else true failed")
})
run("if else false", function() {
var x = 0
if (false) x = 1
else x = 2
if (x != 2) fail("if else false failed")
})
run("if else if", function() {
var x = 0
if (false) x = 1
else if (true) x = 2
else x = 3
if (x != 2) fail("if else if failed")
})
run("nested if", function() {
var x = 0
if (true) {
if (true) {
x = 1
}
}
if (x != 1) fail("nested if failed")
})
// ============================================================================
// CONTROL FLOW - WHILE LOOPS
// ============================================================================
run("while loop", function() {
var i = 0
var sum = 0
while (i < 5) {
sum = sum + i
i = i + 1
}
if (sum != 10) fail("while loop failed")
})
run("while never executes", function() {
var x = 0
while (false) {
x = 1
}
if (x != 0) fail("while never executes failed")
})
run("while break", function() {
var i = 0
while (true) {
if (i >= 5) break
i = i + 1
}
if (i != 5) fail("while break failed")
})
run("while continue", function() {
var i = 0
var sum = 0
while (i < 10) {
i = i + 1
if (i % 2 == 0) continue
sum = sum + i
}
if (sum != 25) fail("while continue failed")
})
// ============================================================================
// CONTROL FLOW - FOR LOOPS
// ============================================================================
run("for loop", function() {
var sum = 0
var i = 0
for (i = 0; i < 5; i = i + 1) {
sum = sum + i
}
if (sum != 10) fail("for loop failed")
})
run("for loop break", function() {
var sum = 0
var i = 0
for (i = 0; i < 10; i = i + 1) {
if (i == 5) break
sum = sum + i
}
if (sum != 10) fail("for loop break failed")
})
run("for loop continue", function() {
var sum = 0
var i = 0
for (i = 0; i < 10; i = i + 1) {
if (i % 2 == 0) continue
sum = sum + i
}
if (sum != 25) fail("for loop continue failed")
})
run("nested for loops", function() {
var sum = 0
var i = 0, j = 0
for (i = 0; i < 3; i = i + 1) {
for (j = 0; j < 3; j = j + 1) {
sum = sum + 1
}
}
if (sum != 9) fail("nested for loops failed")
})
// ============================================================================
// DISRUPTION HANDLING
// ============================================================================
run("disruption caught", function() {
if (!should_disrupt(function() { disrupt })) fail("disruption not caught")
})
run("no disruption path", function() {
var x = 0
var attempt = function() {
x = 1
} disruption {
x = 2
}
attempt()
if (x != 1) fail("non-disrupting code should not trigger disruption clause")
})
run("nested disruption", function() {
var x = 0
var outer = function() {
var inner = function() {
disrupt
} disruption {
x = 1
}
inner()
x = 2
} disruption {
x = 3
}
outer()
if (x != 2) fail("nested disruption failed")
})
run("disruption re-raise", function() {
var outer_caught = false
var outer = function() {
var inner = function() {
disrupt
} disruption {
disrupt
}
inner()
} disruption {
outer_caught = true
}
outer()
if (!outer_caught) fail("disruption re-raise failed")
})
run("disruption handler reads outer vars", function() {
var msg = "hello"
var result = null
var fn = function() {
disrupt
} disruption {
result = msg
}
fn()
if (result != "hello") fail("handler could not read outer var")
})
run("disruption handler modifies outer vars", function() {
var count = 0
var name = "before"
var fn = function() {
count = count + 1
disrupt
} disruption {
count = count + 10
name = "after"
}
fn()
if (count != 11) fail("expected 11 got " + text(count))
if (name != "after") fail("expected 'after' got " + name)
})
run("disruption handler reads function locals", function() {
var result = null
var fn = function() {
var local_val = 42
var inner = function() {
disrupt
} disruption {
result = local_val
}
inner()
}
fn()
if (result != 42) fail("handler could not read local, got " + text(result))
})
run("disruption handler with closure", function() {
var results = []
var make_fn = function(tag) {
return function() {
disrupt
} disruption {
results[] = tag
}
}
var fn_a = make_fn("a")
var fn_b = make_fn("b")
fn_a()
fn_b()
if (length(results) != 2) fail("expected 2 results")
if (results[0] != "a") fail("first closure tag wrong")
if (results[1] != "b") fail("second closure tag wrong")
})
run("disruption handler modifies loop state", function() {
var total = 0
var i = 0
var fn = null
while (i < 3) {
fn = function() {
disrupt
} disruption {
total = total + i
}
fn()
i = i + 1
}
if (total != 3) fail("expected 3 got " + text(total))
})
run("disruption handler accesses object from outer scope", function() {
var obj = {x: 1, y: 2}
var fn = function() {
obj.x = 10
disrupt
} disruption {
obj.y = 20
}
fn()
if (obj.x != 10) fail("body mutation lost, x=" + text(obj.x))
if (obj.y != 20) fail("handler mutation lost, y=" + text(obj.y))
})
run("disruption in callback with multiple calls after", function() {
// Regression: a function with a disruption handler that calls a
// callback which disrupts, followed by more successful calls.
// In native mode, cell_rt_disrupt must NOT use JS_ThrowTypeError
// (which prints to stderr) — it must silently set the exception.
var log = []
var run_inner = function(name, fn) {
fn()
log[] = "pass:" + name
} disruption {
log[] = "fail:" + name
}
run_inner("a", function() { var x = 1 })
run_inner("b", function() { disrupt })
run_inner("c", function() { var y = 2 })
if (length(log) != 3) fail("expected 3 log entries, got " + text(length(log)))
if (log[0] != "pass:a") fail("expected pass:a, got " + log[0])
if (log[1] != "fail:b") fail("expected fail:b, got " + log[1])
if (log[2] != "pass:c") fail("expected pass:c, got " + log[2])
})
// ============================================================================
// TYPE CHECKING WITH is_* FUNCTIONS
// ============================================================================
run("is_number", function() {
if (!is_number(42)) fail("is_number 42 failed")
if (!is_number(3.14)) fail("is_number float failed")
if (!is_number(-5)) fail("is_number negative failed")
if (is_number("42")) fail("is_number string should be false")
if (is_number(true)) fail("is_number boolean should be false")
if (is_number(null)) fail("is_number null should be false")
if (is_number({})) fail("is_number object should be false")
if (is_number([])) fail("is_number array should be false")
})
run("is_text", function() {
if (!is_text("hello")) fail("is_text string failed")
if (!is_text("")) fail("is_text empty string failed")
if (is_text(42)) fail("is_text number should be false")
if (is_text(true)) fail("is_text boolean should be false")
if (is_text(null)) fail("is_text null should be false")
if (is_text({})) fail("is_text object should be false")
if (is_text([])) fail("is_text array should be false")
})
run("is_logical", function() {
if (!is_logical(true)) fail("is_logical true failed")
if (!is_logical(false)) fail("is_logical false failed")
if (is_logical(1)) fail("is_logical number should be false")
if (is_logical("true")) fail("is_logical string should be false")
if (is_logical(null)) fail("is_logical null should be false")
if (is_logical({})) fail("is_logical object should be false")
if (is_logical([])) fail("is_logical array should be false")
})
run("is_object", function() {
if (!is_object({})) fail("is_object empty object failed")
if (!is_object({a: 1})) fail("is_object object failed")
if (is_object([])) fail("is_object array should be false")
if (is_object(null)) fail("is_object null should be false")
if (is_object(42)) fail("is_object number should be false")
if (is_object("hello")) fail("is_object string should be false")
if (is_object(true)) fail("is_object boolean should be false")
})
run("is_array", function() {
if (!is_array([])) fail("is_array empty array failed")
if (!is_array([1, 2, 3])) fail("is_array array failed")
if (is_array({})) fail("is_array object should be false")
if (is_array(null)) fail("is_array null should be false")
if (is_array(42)) fail("is_array number should be false")
if (is_array("hello")) fail("is_array string should be false")
if (is_array(true)) fail("is_array boolean should be false")
})
run("is_function", function() {
if (!is_function(function(){})) fail("is_function function failed")
var fn = function(x) { return x * 2 }
if (!is_function(fn)) fail("is_function named function failed")
if (is_function({})) fail("is_function object should be false")
if (is_function([])) fail("is_function array should be false")
if (is_function(null)) fail("is_function null should be false")
if (is_function(42)) fail("is_function number should be false")
if (is_function("hello")) fail("is_function string should be false")
if (is_function(true)) fail("is_function boolean should be false")
})
run("is_null", function() {
if (!is_null(null)) fail("is_null null failed")
if (is_null(0)) fail("is_null zero should be false")
if (is_null(false)) fail("is_null false should be false")
if (is_null("")) fail("is_null empty string should be false")
if (is_null({})) fail("is_null object should be false")
if (is_null([])) fail("is_null array should be false")
var x = null
if (!is_null(x)) fail("is_null undefined variable should be true")
})
run("is_blob", function() {
if (is_blob(null)) fail("is_blob null should be false")
if (is_blob(42)) fail("is_blob number should be false")
if (is_blob("hello")) fail("is_blob string should be false")
if (is_blob(true)) fail("is_blob boolean should be false")
if (is_blob({})) fail("is_blob object should be false")
if (is_blob([])) fail("is_blob array should be false")
if (is_blob(function(){})) fail("is_blob function should be false")
})
run("is_proto", function() {
var a = {}
var b = meme(a)
if (!is_proto(b, a)) fail("is_proto failed on meme")
})
run("is_data", function() {
if (!is_data({})) fail("is_data {} should be true")
if (!is_data([])) fail("is_data [] should be true")
if (!is_data(42)) fail("is_data number should be true")
if (!is_data("hello")) fail("is_data string should be true")
if (is_data(null)) fail("is_data null should be false")
if (!is_data(true)) fail("is_data bool should be true")
if (is_data(function(){})) fail("is_data function should be false")
})
run("is_true", function() {
if (!is_true(true)) fail("is_true true should be true")
if (is_true(false)) fail("is_true false should be false")
if (is_true(1)) fail("is_true 1 should be false")
if (is_true("true")) fail("is_true string should be false")
if (is_true(null)) fail("is_true null should be false")
})
run("is_false", function() {
if (!is_false(false)) fail("is_false false should be true")
if (is_false(true)) fail("is_false true should be false")
if (is_false(0)) fail("is_false 0 should be false")
if (is_false("")) fail("is_false empty string should be false")
if (is_false(null)) fail("is_false null should be false")
})
run("is_fit", function() {
if (!is_fit(0)) fail("is_fit 0 should be true")
if (!is_fit(42)) fail("is_fit 42 should be true")
if (!is_fit(-100)) fail("is_fit -100 should be true")
if (!is_fit(3.0)) fail("is_fit 3.0 should be true")
if (is_fit(3.5)) fail("is_fit 3.5 should be false")
if (is_fit("42")) fail("is_fit string should be false")
if (is_fit(null)) fail("is_fit null should be false")
})
run("is_character", function() {
if (!is_character("a")) fail("is_character a should be true")
if (!is_character("Z")) fail("is_character Z should be true")
if (!is_character("5")) fail("is_character 5 should be true")
if (!is_character(" ")) fail("is_character space should be true")
if (is_character("ab")) fail("is_character ab should be false")
if (is_character("")) fail("is_character empty should be false")
if (is_character(42)) fail("is_character number should be false")
if (is_character(null)) fail("is_character null should be false")
})
run("is_digit", function() {
if (!is_digit("0")) fail("is_digit 0 should be true")
if (!is_digit("5")) fail("is_digit 5 should be true")
if (!is_digit("9")) fail("is_digit 9 should be true")
if (is_digit("a")) fail("is_digit a should be false")
if (is_digit("55")) fail("is_digit 55 should be false")
if (is_digit(5)) fail("is_digit number should be false")
if (is_digit(null)) fail("is_digit null should be false")
})
run("is_letter", function() {
if (!is_letter("a")) fail("is_letter a should be true")
if (!is_letter("Z")) fail("is_letter Z should be true")
if (is_letter("5")) fail("is_letter 5 should be false")
if (is_letter("ab")) fail("is_letter ab should be false")
if (is_letter(42)) fail("is_letter number should be false")
})
run("is_lower", function() {
if (!is_lower("a")) fail("is_lower a should be true")
if (!is_lower("z")) fail("is_lower z should be true")
if (is_lower("A")) fail("is_lower A should be false")
if (is_lower("5")) fail("is_lower 5 should be false")
if (is_lower("ab")) fail("is_lower ab should be false")
if (is_lower(42)) fail("is_lower number should be false")
})
run("is_upper", function() {
if (!is_upper("A")) fail("is_upper A should be true")
if (!is_upper("Z")) fail("is_upper Z should be true")
if (is_upper("a")) fail("is_upper a should be false")
if (is_upper("5")) fail("is_upper 5 should be false")
if (is_upper("AB")) fail("is_upper AB should be false")
if (is_upper(42)) fail("is_upper number should be false")
})
run("is_whitespace", function() {
if (!is_whitespace(" ")) fail("is_whitespace space should be true")
if (!is_whitespace("\t")) fail("is_whitespace tab should be true")
if (!is_whitespace("\n")) fail("is_whitespace newline should be true")
if (is_whitespace("a")) fail("is_whitespace a should be false")
if (!is_whitespace(" ")) fail("is_whitespace two spaces should be true")
if (!is_whitespace("\r\n")) fail("is_whitespace cr-lf should be true")
if (!is_whitespace(" \t\n")) fail("is_whitespace mixed ws should be true")
if (is_whitespace("")) fail("is_whitespace empty should be false")
if (is_whitespace(42)) fail("is_whitespace number should be false")
if (is_whitespace(null)) fail("is_whitespace null should be false")
})
// ============================================================================
// GLOBAL FUNCTIONS - LENGTH
// ============================================================================
run("length string", function() {
if (length("hello") != 5) fail("length string failed")
if (length("") != 0) fail("length empty string failed")
})
run("length array", function() {
if (length([1,2,3]) != 3) fail("length array failed")
if (length([]) != 0) fail("length empty array failed")
})
run("length null", function() {
if (length(null) != null) fail("length null failed")
})
run("length number", function() {
if (length(123) != null) fail("length number should return null")
})
run("length logical", function() {
if (length(true) != null) fail("length true should return null")
if (length(false) != null) fail("length false should return null")
})
run("length object no length", function() {
var obj = {x: 1, y: 2}
if (length(obj) != null) fail("length of object without length should return null")
})
run("length object number", function() {
var obj = {length: 7}
if (length(obj) != 7) fail("length of object with number length should return 7")
})
run("length object function", function() {
var obj = {length: function() { return 42 }}
if (length(obj) != 42) fail("length of object with function length should call it")
})
// ============================================================================
// GLOBAL FUNCTIONS - REVERSE
// ============================================================================
run("reverse array", function() {
var arr = [1, 2, 3, 4, 5]
var rev = reverse(arr)
if (rev[0] != 5) fail("reverse array first failed")
if (rev[4] != 1) fail("reverse array last failed")
if (length(rev) != 5) fail("reverse array length failed")
})
run("reverse empty array", function() {
var rev = reverse([])
if (length(rev) != 0) fail("reverse empty array failed")
})
run("reverse preserves original", function() {
var arr = [1, 2, 3]
var rev = reverse(arr)
if (arr[0] != 1) fail("reverse should not mutate original")
})
// ============================================================================
// GLOBAL FUNCTIONS - MEME (PROTOTYPAL INHERITANCE)
// ============================================================================
run("meme basic", function() {
var parent = {x: 10}
var child = meme(parent)
if (child.x != 10) fail("meme basic inheritance failed")
})
run("meme with mixins", function() {
var parent = {x: 10}
var mixin = {y: 20}
var child = meme(parent, mixin)
if (child.x != 10) fail("meme with mixin parent prop failed")
if (child.y != 20) fail("meme with mixin own prop failed")
})
run("meme override", function() {
var parent = {x: 10}
var child = meme(parent)
child.x = 20
if (child.x != 20) fail("meme override failed")
if (parent.x != 10) fail("meme should not mutate parent")
})
run("meme multiple mixins", function() {
var parent = {a: 1}
var mixin1 = {b: 2}
var mixin2 = {c: 3}
var child = meme(parent, [mixin1, mixin2])
if (child.a != 1 || child.b != 2 || child.c != 3) fail("meme multiple mixins failed")
})
// ============================================================================
// GLOBAL FUNCTIONS - PROTO
// ============================================================================
run("proto basic", function() {
var parent = {x: 10}
var child = meme(parent)
var p = proto(child)
if (p != parent) fail("proto basic failed")
})
run("proto object literal", function() {
var obj = {x: 10}
var p = proto(obj)
if (p != null) fail("proto of object literal should be null")
})
run("proto non object", function() {
if (proto(42) != null) fail("proto of number should return null")
if (proto("hello") != null) fail("proto of string should return null")
})
// ============================================================================
// GLOBAL FUNCTIONS - STONE (FREEZE)
// ============================================================================
run("stone object", function() {
var obj = {x: 10}
stone(obj)
if (!should_disrupt(function() { obj.x = 20 })) fail("stone object should prevent modification")
})
run("is_stone frozen", function() {
var obj = {x: 10}
if (is_stone(obj)) fail("stone.p should return false before freezing")
stone(obj)
if (!is_stone(obj)) fail("stone.p should return true after freezing")
})
run("stone array", function() {
var arr = [1, 2, 3]
stone(arr)
if (!should_disrupt(function() { arr[0] = 99 })) fail("stone array should prevent modification")
})
// ============================================================================
// TERNARY OPERATOR
// ============================================================================
run("ternary true", function() {
var x = true ? 1 : 2
if (x != 1) fail("ternary true failed")
})
run("ternary false", function() {
var x = false ? 1 : 2
if (x != 2) fail("ternary false failed")
})
run("ternary nested", function() {
var x = true ? (false ? 1 : 2) : 3
if (x != 2) fail("ternary nested failed")
})
run("ternary with expressions", function() {
var a = 5
var b = 10
var max = (a > b) ? a : b
if (max != 10) fail("ternary with expressions failed")
})
// ============================================================================
// UNARY OPERATORS
// ============================================================================
run("unary plus", function() {
if (+5 != 5) fail("unary plus positive failed")
if (+-5 != -5) fail("unary plus negative failed")
})
run("unary minus", function() {
if (-5 != -5) fail("unary minus failed")
if (-(-5) != 5) fail("double unary minus failed")
})
run("increment postfix", function() {
var x = 5
var y = x++
if (y != 5) fail("postfix increment return value failed")
if (x != 6) fail("postfix increment side effect failed")
})
run("increment prefix", function() {
var x = 5
var y = ++x
if (y != 6) fail("prefix increment return value failed")
if (x != 6) fail("prefix increment side effect failed")
})
run("decrement postfix", function() {
var x = 5
var y = x--
if (y != 5) fail("postfix decrement return value failed")
if (x != 4) fail("postfix decrement side effect failed")
})
run("decrement prefix", function() {
var x = 5
var y = --x
if (y != 4) fail("prefix decrement return value failed")
if (x != 4) fail("prefix decrement side effect failed")
})
// ============================================================================
// COMPOUND ASSIGNMENT OPERATORS
// ============================================================================
run("plus equals", function() {
var x = 5
x += 3
if (x != 8) fail("plus equals failed")
})
run("minus equals", function() {
var x = 10
x -= 3
if (x != 7) fail("minus equals failed")
})
run("times equals", function() {
var x = 4
x *= 3
if (x != 12) fail("times equals failed")
})
run("divide equals", function() {
var x = 12
x /= 3
if (x != 4) fail("divide equals failed")
})
run("modulo equals", function() {
var x = 10
x %= 3
if (x != 1) fail("modulo equals failed")
})
// ============================================================================
// COMPOUND ASSIGNMENT ON PROPERTIES
// ============================================================================
run("plus equals on property", function() {
var obj = {x: 10}
obj.x += 5
if (obj.x != 15) fail("obj.x += 5")
})
run("minus equals on property", function() {
var obj = {x: 10}
obj.x -= 3
if (obj.x != 7) fail("obj.x -= 3")
})
run("times equals on property", function() {
var obj = {x: 4}
obj.x *= 3
if (obj.x != 12) fail("obj.x *= 3")
})
run("divide equals on property", function() {
var obj = {x: 12}
obj.x /= 3
if (obj.x != 4) fail("obj.x /= 3")
})
run("modulo equals on property", function() {
var obj = {x: 10}
obj.x %= 3
if (obj.x != 1) fail("obj.x %= 3")
})
run("compound assign preserves other properties", function() {
var obj = {x: 10, y: 20}
obj.x += 5
if (obj.x != 15) fail("x wrong")
if (obj.y != 20) fail("y changed")
})
run("compound assign nested property", function() {
var obj = {inner: {val: 10}}
obj.inner.val += 5
if (obj.inner.val != 15) fail("nested += failed")
})
run("compound assign chained", function() {
var obj = {x: 1}
obj.x += 2
obj.x += 3
obj.x += 4
if (obj.x != 10) fail("chained += failed")
})
// ============================================================================
// COMPOUND ASSIGNMENT ON ARRAY ELEMENTS
// ============================================================================
run("plus equals on array element", function() {
var arr = [100, 200, 300]
arr[0] += 50
if (arr[0] != 150) fail("arr[0] += 50")
})
run("minus equals on array element", function() {
var arr = [100, 200, 300]
arr[1] -= 50
if (arr[1] != 150) fail("arr[1] -= 50")
})
run("times equals on array element", function() {
var arr = [5]
arr[0] *= 6
if (arr[0] != 30) fail("arr[0] *= 6")
})
run("compound assign computed property", function() {
var obj = {a: 10, b: 20}
var key = "a"
obj[key] += 5
if (obj.a != 15) fail("obj[key] += 5")
})
run("compound assign array element preserves others", function() {
var arr = [10, 20, 30]
arr[1] += 5
if (arr[0] != 10) fail("arr[0] changed")
if (arr[1] != 25) fail("arr[1] wrong")
if (arr[2] != 30) fail("arr[2] changed")
})
// ============================================================================
// PREFIX INCREMENT/DECREMENT ON PROPERTIES
// ============================================================================
run("prefix increment on property", function() {
var obj = {x: 5}
var y = ++obj.x
if (y != 6) fail("return value")
if (obj.x != 6) fail("side effect")
})
run("prefix decrement on property", function() {
var obj = {x: 5}
var y = --obj.x
if (y != 4) fail("return value")
if (obj.x != 4) fail("side effect")
})
run("prefix increment on array element", function() {
var arr = [10]
var y = ++arr[0]
if (y != 11) fail("return value")
if (arr[0] != 11) fail("side effect")
})
run("prefix decrement on array element", function() {
var arr = [10]
var y = --arr[0]
if (y != 9) fail("return value")
if (arr[0] != 9) fail("side effect")
})
// ============================================================================
// POSTFIX INCREMENT/DECREMENT ON PROPERTIES (Bug: never worked)
// ============================================================================
run("postfix increment on property", function() {
var obj = {x: 5}
obj.x++
assert_eq(obj.x, 6, "obj.x should be 6 after obj.x++")
})
run("postfix decrement on property", function() {
var obj = {x: 5}
obj.x--
assert_eq(obj.x, 4, "obj.x should be 4 after obj.x--")
})
run("postfix increment on property return value", function() {
var obj = {x: 5}
var y = obj.x++
assert_eq(y, 5, "return value should be old value")
assert_eq(obj.x, 6, "property should be incremented")
})
run("postfix decrement on property return value", function() {
var obj = {x: 5}
var y = obj.x--
assert_eq(y, 5, "return value should be old value")
assert_eq(obj.x, 4, "property should be decremented")
})
run("postfix increment on array element", function() {
var arr = [10, 20, 30]
arr[1]++
assert_eq(arr[1], 21, "arr[1] should be 21 after arr[1]++")
})
run("postfix decrement on array element", function() {
var arr = [10, 20, 30]
arr[1]--
assert_eq(arr[1], 19, "arr[1] should be 19 after arr[1]--")
})
run("postfix increment on array element return value", function() {
var arr = [10]
var y = arr[0]++
assert_eq(y, 10, "return value should be old value")
assert_eq(arr[0], 11, "array element should be incremented")
})
run("postfix increment on computed property", function() {
var obj = {a: 10}
var key = "a"
obj[key]++
assert_eq(obj.a, 11, "computed property should be incremented")
})
run("postfix increment on nested property", function() {
var obj = {inner: {val: 10}}
obj.inner.val++
assert_eq(obj.inner.val, 11, "nested property should be incremented")
})
run("postfix increment property in loop", function() {
var obj = {count: 0}
var i = 0
for (i = 0; i < 5; i++) {
obj.count++
}
assert_eq(obj.count, 5, "property should be 5 after 5 increments")
})
// ============================================================================
// POSTFIX INCREMENT ON CLOSURE PROPERTIES (Original reported bug)
// ============================================================================
run("postfix increment closure property", function() {
var obj = {x: 0}
var fn = function() {
obj.x++
}
fn()
assert_eq(obj.x, 1, "closure property should be incremented")
})
run("postfix decrement closure property", function() {
var obj = {x: 5}
var fn = function() {
obj.x--
}
fn()
assert_eq(obj.x, 4, "closure property should be decremented")
})
run("postfix increment closure counter pattern", function() {
var counter = {passed: 0, failed: 0}
var pass = function() { counter.passed++ }
var fail_fn = function() { counter.failed++ }
pass()
pass()
pass()
fail_fn()
assert_eq(counter.passed, 3, "passed count")
assert_eq(counter.failed, 1, "failed count")
})
run("postfix increment deep closure property", function() {
var obj = {x: 0}
var fn = function() {
var inner = function() {
obj.x++
}
inner()
}
fn()
assert_eq(obj.x, 1, "deep closure property should be incremented")
})
run("postfix increment closure array element", function() {
var arr = [10]
var fn = function() {
arr[0]++
}
fn()
assert_eq(arr[0], 11, "closure array element should be incremented")
})
run("postfix increment on closure variable", function() {
var x = 5
var fn = function() {
x++
}
fn()
assert_eq(x, 6, "closure variable should be incremented by postfix ++")
})
// ============================================================================
// INCREMENT/DECREMENT IN LOOPS AND EXPRESSIONS
// ============================================================================
run("compound assign in for loop", function() {
var sum = 0
var i = 0
for (i = 0; i < 5; i++) {
sum += i
}
if (sum != 10) fail("sum should be 10")
})
run("compound assign property in loop", function() {
var obj = {count: 0}
var i = 0
for (i = 0; i < 5; i++) {
obj.count += 1
}
if (obj.count != 5) fail("count should be 5")
})
run("prefix increment in expression", function() {
var x = 5
var y = 10 + ++x
if (y != 16) fail("10 + ++x should be 16")
if (x != 6) fail("x should be 6")
})
run("compound assign bitwise and-equals", function() {
var x = 15
x &= 6
if (x != 6) fail("15 & 6 should be 6")
})
run("compound assign bitwise or-equals", function() {
var x = 5
x |= 3
if (x != 7) fail("5 | 3 should be 7")
})
run("compound assign bitwise xor-equals", function() {
var x = 5
x ^= 3
if (x != 6) fail("5 ^ 3 should be 6")
})
run("compound assign left shift-equals", function() {
var x = 1
x <<= 3
if (x != 8) fail("1 << 3 should be 8")
})
run("compound assign right shift-equals", function() {
var x = 16
x >>= 2
if (x != 4) fail("16 >> 2 should be 4")
})
run("compound assign bitwise on property", function() {
var obj = {flags: 5}
obj.flags |= 8
if (obj.flags != 13) fail("5 | 8 should be 13")
obj.flags &= 12
if (obj.flags != 12) fail("13 & 12 should be 12")
})
// ============================================================================
// EDGE CASES AND SPECIAL VALUES
// ============================================================================
run("division by zero is null", function() {
var inf = 1 / 0
if (inf != null) fail("division by zero should be null")
var ninf = -1 / 0
if (ninf != null) fail("negative division by zero should be null")
})
run("zero div zero is null", function() {
var nan = 0 / 0
if (nan != null) fail("0/0 should be null")
})
run("empty string falsy", function() {
if ("") fail("empty string should be falsy")
})
run("zero falsy", function() {
if (0) fail("zero should be falsy")
})
run("null falsy", function() {
if (null) fail("null should be falsy")
})
run("false falsy", function() {
if (false) fail("false should be falsy")
})
run("nonempty string truthy", function() {
if (!"hello") fail("non-empty string should be truthy")
})
run("nonzero number truthy", function() {
if (!42) fail("non-zero number should be truthy")
})
run("object truthy", function() {
if (!{}) fail("empty object should be truthy")
})
run("array truthy", function() {
if (![]) fail("empty array should be truthy")
})
// ============================================================================
// OPERATOR PRECEDENCE
// ============================================================================
run("precedence multiply add", function() {
if (2 + 3 * 4 != 14) fail("multiply before add precedence failed")
})
run("precedence parentheses", function() {
if ((2 + 3) * 4 != 20) fail("parentheses precedence failed")
})
run("precedence comparison logical", function() {
if (!(1 < 2 && 3 < 4)) fail("comparison before logical precedence failed")
})
run("precedence equality logical", function() {
if (!(1 == 1 || 2 == 3)) fail("equality before logical precedence failed")
})
run("precedence unary multiplication", function() {
if (-2 * 3 != -6) fail("unary before multiplication precedence failed")
})
// ============================================================================
// COMMA OPERATOR
// ============================================================================
run("comma operator", function() {
var x = (1, 2, 3)
if (x != 3) fail("comma operator failed")
})
run("comma operator with side effects", function() {
var a = 0
var x = (a = 1, a = 2, a + 1)
if (x != 3) fail("comma operator with side effects failed")
if (a != 2) fail("comma operator side effects failed")
})
// ============================================================================
// VARIABLE SHADOWING
// ============================================================================
run("variable shadowing function", function() {
var x = 10
var fn = function() {
var x = 20
return x
}
if (fn() != 20) fail("function shadowing failed")
if (x != 10) fail("outer variable after shadowing failed")
})
run("variable shadowing nested", function() {
var x = 10
var fn1 = function() {
var x = 20
var fn2 = function() {
var x = 30
return x
}
return fn2() + x
}
if (fn1() != 50) fail("nested shadowing failed")
})
run("var no longer hoisted", function() {
// length is an intrinsic. Without var hoisting, it should
// resolve to the intrinsic until the var declaration is reached.
var fn = function() {
var before = length([1, 2, 3])
var length = 999
return [before, length]
}
var result = fn()
if (result[0] != 3) fail("expected intrinsic length([1,2,3]) == 3, got " + text(result[0]))
if (result[1] != 999) fail("expected local length to be 999, got " + text(result[1]))
})
// ============================================================================
// FUNCTION ARITY
// ============================================================================
run("function length property", function() {
var fn0 = function() {}
var fn1 = function(a) {}
var fn2 = function(a, b) {}
if (length(fn0) != 0) fail("function length 0 failed")
if (length(fn1) != 1) fail("function length 1 failed")
if (length(fn2) != 2) fail("function length 2 failed")
})
// ============================================================================
// NULL AND UNDEFINED BEHAVIOR
// ============================================================================
run("null initialized variable is null", function() {
var x = null
if (x != null) fail("null initialized variable should be null")
})
// ============================================================================
// NUMBERS - SPECIAL OPERATIONS
// ============================================================================
run("number plus empty string disrupts", function() {
if (!should_disrupt(function() { var n = 42; var result = n + "" })) fail("number + string should disrupt")
})
run("number division by zero", function() {
var result = 1 / 0
if (result != null) fail("division by zero should give null")
})
run("number negative division by zero", function() {
var result = -1 / 0
if (result != null) fail("negative division by zero should give null")
})
run("zero division by zero", function() {
var result = 0 / 0
if (result != null) fail("0/0 should give null")
})
run("negative zero normalized", function() {
var nz = -0
if (nz != 0) fail("-0 should equal 0")
var mul_nz = 0 * -1
if (mul_nz != 0) fail("0 * -1 should be 0")
var neg_zero = -(0)
if (neg_zero != 0) fail("-(0) should be 0")
})
run("overflow is null", function() {
var result = 1e38 * 1e38
if (result != null) fail("overflow should give null")
})
run("modulo by zero is null", function() {
var result = 5 % 0
if (result != null) fail("modulo by zero should give null")
})
// ============================================================================
// OBJECT PROPERTY EXISTENCE
// ============================================================================
run("in operator", function() {
var obj = {a: 1, b: 2}
if (!("a" in obj)) fail("in operator for existing property failed")
if ("c" in obj) fail("in operator for non-existing property failed")
})
run("in operator prototype", function() {
var parent = {x: 10}
var child = meme(parent)
if (!("x" in child)) fail("in operator should find inherited property")
})
// ============================================================================
// GLOBAL FUNCTIONS - LOGICAL
// ============================================================================
run("logical function numbers", function() {
if (logical(0) != false) fail("logical(0) should be false")
if (logical(1) != true) fail("logical(1) should be true")
})
run("logical function strings", function() {
if (logical("false") != false) fail("logical('false') should be false")
if (logical("true") != true) fail("logical('true') should be true")
})
run("logical function booleans", function() {
if (logical(false) != false) fail("logical(false) should be false")
if (logical(true) != true) fail("logical(true) should be true")
})
run("logical function null", function() {
if (logical(null) != false) fail("logical(null) should be false")
})
run("logical function invalid", function() {
if (logical("invalid") != null) fail("logical(invalid) should return null")
if (logical(42) != null) fail("logical(42) should return null")
})
// ============================================================================
// ARRAY METHODS
// ============================================================================
run("array concat", function() {
var arr1 = [1, 2]
var arr2 = [3, 4]
var combined = array(arr1, arr2)
if (length(combined) != 4) fail("array concat length failed")
if (combined[2] != 3) fail("array concat values failed")
})
run("array join", function() {
var arr = ["a", "b", "c"]
var str = text(arr, ",")
if (str != "a,b,c") fail("array join with text() failed")
})
run("text array join numbers disrupt", function() {
if (!should_disrupt(function() { text([1, 2, 3], ",") })) fail("text([numbers], sep) should disrupt")
})
run("text array join numbers explicit", function() {
var arr = array([1, 2, 3], x => text(x))
if (text(arr, ",") != "1,2,3") fail("explicit numeric join failed")
})
// ============================================================================
// STRING METHODS
// ============================================================================
run("string substring", function() {
var str = "hello"
if (text(str, 1, 4) != "ell") fail("string substring failed")
})
run("string substring first", function() {
var str = "hello"
if (text(str, 1) != "ello") fail("string substring first failed")
})
run("string substring to neg", function() {
var str = "hello"
if (text(str, 1, -2) != "el") fail("string substring to negative failed")
})
run("string slice", function() {
var str = "hello"
if (text(str, 1, 4) != "ell") fail("string slice failed")
if (text(str, -2) != "lo") fail("string slice negative failed")
})
run("string indexOf", function() {
var str = "hello world"
if (search(str, "world") != 6) fail("string search failed")
if (search(str, "xyz") != null) fail("string search not found failed")
})
run("string toLowerCase", function() {
var str = "HELLO"
if (lower(str) != "hello") fail("string toLowerCase failed")
})
run("string toUpperCase", function() {
var str = "hello"
if (upper(str) != "HELLO") fail("string toUpperCase failed")
})
run("string split", function() {
var str = "a,b,c"
var parts = array(str, ",")
if (length(parts) != 3) fail("string split length failed")
if (parts[1] != "b") fail("string split values failed")
})
run("null access", function() {
var val = {}
var nn = val.a
if (nn != null) fail("val.a should return null")
})
// ============================================================================
// OBJECT-AS-KEY (Private Property Access)
// ============================================================================
run("object key basic", function() {
var k1 = {}
var k2 = {}
var o = {}
o[k1] = 123
o[k2] = 456
if (o[k1] != 123) fail("object key k1 failed")
if (o[k2] != 456) fail("object key k2 failed")
})
run("object key new object different key", function() {
var k1 = {}
var o = {}
o[k1] = 123
if (o[{}] != null) fail("new object should be different key")
})
run("object key in operator", function() {
var k1 = {}
var o = {}
o[k1] = 123
if (!(k1 in o)) fail("in operator should find object key")
})
run("object key delete", function() {
var k1 = {}
var o = {}
o[k1] = 123
delete o[k1]
if ((k1 in o)) fail("delete should remove object key")
})
run("object key no string collision", function() {
var a = {}
var b = {}
var o = {}
o[a] = 1
o[b] = 2
if (o[a] != 1) fail("object key a should be 1")
if (o[b] != 2) fail("object key b should be 2")
})
run("object key same object same key", function() {
var k = {}
var o = {}
o[k] = 100
o[k] = 200
if (o[k] != 200) fail("same object should be same key")
})
run("object key computed property", function() {
var k = {}
var o = {}
o[k] = function() { return 42 }
if (o[k]() != 42) fail("object key with function value failed")
})
run("object key multiple objects multiple keys", function() {
var k1 = {}
var k2 = {}
var k3 = {}
var o = {}
o[k1] = "one"
o[k2] = "two"
o[k3] = "three"
if (o[k1] != "one") fail("multiple keys k1 failed")
if (o[k2] != "two") fail("multiple keys k2 failed")
if (o[k3] != "three") fail("multiple keys k3 failed")
})
run("object key with string keys", function() {
var k = {}
var o = {name: "test"}
o[k] = "private"
if (o.name != "test") fail("string key should still work")
if (o[k] != "private") fail("object key should work with string keys")
})
run("object key overwrite", function() {
var k = {}
var o = {}
o[k] = 1
o[k] = 2
o[k] = 3
if (o[k] != 3) fail("object key overwrite failed")
})
run("object key nested objects", function() {
var k1 = {}
var k2 = {}
var inner = {}
inner[k2] = "nested"
var outer = {}
outer[k1] = inner
if (outer[k1][k2] != "nested") fail("nested object keys failed")
})
run("array for", function() {
var a = [1,2,3]
arrfor(a, (x,i) => {
if (x-1 != i) fail("array for failed")
})
})
// ============================================================================
// INVALID KEY TYPES DISRUPT ON SET
// ============================================================================
run("array string key disrupts", function() {
var f = function(a) { a["a"] = 1 }
if (!should_disrupt(function() { f([]) })) fail("array should not use string as key")
})
run("array object key disrupts", function() {
if (!should_disrupt(function() { var a = []; var b = {}; a[b] = 1 })) fail("array should not use object as key")
})
run("array boolean key disrupts", function() {
if (!should_disrupt(function() { var a = []; a[true] = 1 })) fail("array should not use boolean as key")
})
run("array null key disrupts", function() {
if (!should_disrupt(function() { var a = []; a[null] = 1 })) fail("array should not use null as key")
})
run("array array key disrupts", function() {
if (!should_disrupt(function() { var a = []; var c = []; a[c] = 1 })) fail("array should not use array as key")
})
run("obj number key disrupts", function() {
var f = function(a) { a[1] = 1 }
if (!should_disrupt(function() { f({}) })) fail("object should not use number as key")
})
run("obj array key disrupts", function() {
if (!should_disrupt(function() { var a = {}; var c = []; a[c] = 1 })) fail("object should not use array as key")
})
run("obj boolean key disrupts", function() {
if (!should_disrupt(function() { var a = {}; a[true] = 1 })) fail("object should not use boolean as key")
})
run("obj null key disrupts", function() {
if (!should_disrupt(function() { var a = {}; a[null] = 1 })) fail("object should not use null as key")
})
// ============================================================================
// RETRIEVAL WITH INVALID KEY RETURNS NULL (not disrupt)
// ============================================================================
run("array get string key returns null", function() {
var a = [1, 2, 3]
if (a["x"] != null) fail("array get with string key should return null")
})
run("array get negative index returns null", function() {
var a = [1, 2, 3]
if (a[-1] != null) fail("array get with negative index should return null")
})
run("array get object key returns null", function() {
var a = [1, 2, 3]
var k = {}
if (a[k] != null) fail("array get with object key should return null")
})
run("array get array key returns null", function() {
var a = [1, 2, 3]
if (a[[1, 2]] != null) fail("array get with array key should return null")
})
run("array get boolean key returns null", function() {
var a = [1, 2, 3]
if (a[true] != null) fail("array get with boolean key should return null")
})
run("array get null key returns null", function() {
var a = [1, 2, 3]
if (a[null] != null) fail("array get with null key should return null")
})
run("obj get number key returns null", function() {
var o = {a: 1}
if (o[5] != null) fail("object get with number key should return null")
})
run("obj get array key returns null", function() {
var o = {a: 1}
if (o[[1, 2]] != null) fail("object get with array key should return null")
})
run("obj get boolean key returns null", function() {
var o = {a: 1}
if (o[true] != null) fail("object get with boolean key should return null")
})
run("obj get null key returns null", function() {
var o = {a: 1}
if (o[null] != null) fail("object get with null key should return null")
})
// ============================================================================
// FUNCTION AS VALUE - functions should not have properties
// ============================================================================
run("function property get arity", function() {
var fn = function(a, b) { return a + b }
var arity = length(fn)
if (arity != 2) fail("length of function should return its arity")
})
run("function property set disrupts", function() {
if (!should_disrupt(function() { var fn = function() {}; fn.foo = 123 })) fail("setting property on function should disrupt")
})
run("function bracket access disrupts", function() {
if (!should_disrupt(function() { var fn = function() {}; var x = fn["length"]() })) fail("bracket access on function should disrupt")
})
run("length returns function arity", function() {
var fn0 = function() { return 1 }
var fn1 = function(a) { return a }
var fn2 = function(a, b) { return a + b }
var fn3 = function(a, b, c) { return a + b + c }
if (length(fn0) != 0) fail("length(fn0) should be 0")
if (length(fn1) != 1) fail("length(fn1) should be 1")
if (length(fn2) != 2) fail("length(fn2) should be 2")
if (length(fn3) != 3) fail("length(fn3) should be 3")
})
run("call invokes function", function() {
var fn = function(a, b) { return a + b }
var result = call(fn, null, [3, 4])
if (result != 7) fail("call(fn, null, [3, 4]) should return 7")
})
run("call with this binding", function() {
var obj = { value: 10 }
var fn = function(x) { return this.value + x }
var result = call(fn, obj, [5])
if (result != 15) fail("call(fn, obj, [5]) should return 15")
})
run("call no args", function() {
var fn = function() { return 42 }
var result = call(fn, null)
if (result != 42) fail("call(fn, null) should return 42")
})
run("builtin function properties still work", function() {
var min_result = min(5, 3)
if (min_result != 3) fail("min should work")
})
// ============================================================================
// FUNCTION PROXY - Method call sugar for bytecode functions
// ============================================================================
run("function proxy basic", function() {
var proxy = function(name, args) {
return `called:${name}:${length(args)}`
}
var result = proxy.foo()
if (result != "called:foo:0") fail("basic proxy call failed")
})
run("function proxy with one arg", function() {
var proxy = function(name, args) {
return `${name}-${args[0]}`
}
var result = proxy.test("value")
if (result != "test-value") fail("proxy with one arg failed")
})
run("function proxy with multiple args", function() {
var proxy = function(name, args) {
var sum = 0
var i = 0
for (i = 0; i < length(args); i++) {
sum = sum + args[i]
}
return `${name}:${sum}`
}
var result = proxy.add(1, 2, 3, 4)
if (result != "add:10") fail("proxy with multiple args failed")
})
run("function proxy bracket notation", function() {
var proxy = function(name, args) {
return `bracket:${name}`
}
var result = proxy["myMethod"]()
if (result != "bracket:myMethod") fail("proxy bracket notation failed")
})
run("function proxy dynamic method name", function() {
var proxy = function(name, args) {
return name
}
var methodName = "dynamic"
var result = proxy[methodName]()
if (result != "dynamic") fail("proxy dynamic method name failed")
})
run("function proxy dispatch to record", function() {
var my_record = {
greet: function(name) {
return `Hello, ${name}`
},
add: function(a, b) {
return a + b
}
}
var proxy = function(name, args) {
if (is_function(my_record[name])) {
return apply(my_record[name], args)
}
disrupt
}
if (proxy.greet("World") != "Hello, World") fail("proxy dispatch greet failed")
if (proxy.add(3, 4) != 7) fail("proxy dispatch add failed")
})
run("function proxy unknown method disrupts", function() {
var proxy = function(name, args) {
disrupt
}
if (!should_disrupt(function() { proxy.nonexistent() })) fail("proxy should disrupt for unknown method")
})
run("function proxy is function", function() {
var proxy = function(name, args) {
return name
}
if (!is_function(proxy)) fail("proxy should be a function")
})
run("function proxy length is 2", function() {
var proxy = function(name, args) {
return name
}
if (length(proxy) != 2) fail("proxy function should have length 2")
})
run("function proxy property read disrupts", function() {
if (!should_disrupt(function() { var fn = function() { return 1 }; var x = fn.someProp })) fail("reading property from non-proxy function should disrupt")
})
run("function proxy nested calls", function() {
var outer = function(name, args) {
if (name == "inner") {
return args[0].double(5)
}
return "outer:" + name
}
var inner = function(name, args) {
if (name == "double") {
return args[0] * 2
}
return "inner:" + name
}
var result = outer.inner(inner)
if (result != 10) fail("nested proxy calls failed")
})
run("function proxy returns null", function() {
var proxy = function(name, args) {
return null
}
var result = proxy.anything()
if (result != null) fail("proxy returning null failed")
})
run("function proxy returns object", function() {
var proxy = function(name, args) {
return {method: name, argCount: length(args)}
}
var result = proxy.test(1, 2, 3)
if (result.method != "test") fail("proxy returning object method failed")
if (result.argCount != 3) fail("proxy returning object argCount failed")
})
run("function proxy returns function", function() {
var proxy = function(name, args) {
return function() { return name }
}
var result = proxy.getFn()
if (result() != "getFn") fail("proxy returning function failed")
})
run("function proxy args array is real array", function() {
var proxy = function(name, args) {
if (!is_array(args)) disrupt
args[] = 4
return length(args)
}
var result = proxy.test(1, 2, 3)
if (result != 4) fail("proxy args should be modifiable array")
})
run("function proxy no this binding", function() {
var proxy = function(name, args) {
return this
}
var result = proxy.test()
if (result != null) fail("proxy should have null this")
})
run("function proxy integer bracket key disrupts", function() {
var proxy = function(name, args) {
return `key:${name}`
}
if (!should_disrupt(function() { var result = proxy[42]() })) fail("proxy with integer bracket key should disrupt")
})
// ============================================================================
// REDUCE FUNCTION
// ============================================================================
run("reduce sum", function() {
var arr = [1, 2, 3, 4, 5]
var result = reduce(arr, (a, b) => a + b)
if (result != 15) fail("reduce sum failed")
})
run("reduce product", function() {
var arr = [1, 2, 3, 4, 5]
var result = reduce(arr, (a, b) => a * b)
if (result != 120) fail("reduce product failed")
})
run("reduce with initial", function() {
var arr = [1, 2, 3]
var result = reduce(arr, (a, b) => a + b, 10)
if (result != 16) fail("reduce with initial failed")
})
run("reduce with initial zero", function() {
var arr = [1, 2, 3]
var result = reduce(arr, (a, b) => a + b, 0)
if (result != 6) fail("reduce with initial zero failed")
})
run("reduce empty array no initial", function() {
var arr = []
var result = reduce(arr, (a, b) => a + b)
if (result != null) fail("reduce empty array without initial should return null")
})
run("reduce empty array with initial", function() {
var arr = []
var result = reduce(arr, (a, b) => a + b, 42)
if (result != 42) fail("reduce empty array with initial should return initial")
})
run("reduce single element no initial", function() {
var arr = [42]
var result = reduce(arr, (a, b) => a + b)
if (result != 42) fail("reduce single element without initial failed")
})
run("reduce single element with initial", function() {
var arr = [5]
var result = reduce(arr, (a, b) => a + b, 10)
if (result != 15) fail("reduce single element with initial failed")
})
run("reduce reverse", function() {
var arr = [1, 2, 3, 4]
var result = reduce(arr, (a, b) => a - b, 0, true)
if (result != -10) fail("reduce reverse failed")
})
run("reduce string concat", function() {
var arr = ["a", "b", "c"]
var result = reduce(arr, (a, b) => a + b)
if (result != "abc") fail("reduce string concat failed")
})
// ============================================================================
// SORT FUNCTION
// ============================================================================
run("sort numbers", function() {
var arr = [3, 1, 4, 1, 5, 9, 2, 6]
var sorted = sort(arr)
if (sorted[0] != 1 || sorted[1] != 1 || sorted[2] != 2) fail("sort numbers failed")
if (sorted[7] != 9) fail("sort numbers last element failed")
})
run("sort strings", function() {
var arr = ["banana", "apple", "cherry"]
var sorted = sort(arr)
if (sorted[0] != "apple") fail("sort strings failed")
if (sorted[2] != "cherry") fail("sort strings last failed")
})
run("sort preserves original", function() {
var arr = [3, 1, 2]
var sorted = sort(arr)
if (arr[0] != 3) fail("sort should not mutate original")
})
run("sort empty array", function() {
var arr = []
var sorted = sort(arr)
if (length(sorted) != 0) fail("sort empty array failed")
})
run("sort single element", function() {
var arr = [42]
var sorted = sort(arr)
if (sorted[0] != 42) fail("sort single element failed")
})
run("sort by field", function() {
var arr = [
{name: "Charlie", age: 30},
{name: "Alice", age: 25},
{name: "Bob", age: 35}
]
var sorted = sort(arr, "name")
if (sorted[0].name != "Alice") fail("sort by field failed")
if (sorted[2].name != "Charlie") fail("sort by field last failed")
})
run("sort by index", function() {
var arr = [[3, "c"], [1, "a"], [2, "b"]]
var sorted = sort(arr, 0)
if (sorted[0][1] != "a") fail("sort by index failed")
})
run("sort stable", function() {
var arr = [
{name: "A", order: 1},
{name: "B", order: 1},
{name: "C", order: 1}
]
var sorted = sort(arr, "order")
if (sorted[0].name != "A" || sorted[1].name != "B" || sorted[2].name != "C") {
fail("sort should be stable")
}
})
run("sort negative numbers", function() {
var arr = [-5, 3, -1, 0, 2]
var sorted = sort(arr)
if (sorted[0] != -5 || sorted[4] != 3) fail("sort negative numbers failed")
})
// ============================================================================
// FILTER FUNCTION
// ============================================================================
run("filter basic", function() {
var arr = [1, 2, 3, 4, 5, 6]
var evens = filter(arr, x => x % 2 == 0)
if (length(evens) != 3) fail("filter basic length failed")
if (evens[0] != 2 || evens[1] != 4 || evens[2] != 6) fail("filter basic values failed")
})
run("filter all pass", function() {
var arr = [2, 4, 6]
var result = filter(arr, x => x % 2 == 0)
if (length(result) != 3) fail("filter all pass failed")
})
run("filter none pass", function() {
var arr = [1, 3, 5]
var result = filter(arr, x => x % 2 == 0)
if (length(result) != 0) fail("filter none pass failed")
})
run("filter empty array", function() {
var arr = []
var result = filter(arr, x => true)
if (length(result) != 0) fail("filter empty array failed")
})
run("filter with index", function() {
var arr = ["a", "b", "c", "d"]
var result = filter(arr, (x, i) => i % 2 == 0)
if (length(result) != 2) fail("filter with index length failed")
if (result[0] != "a" || result[1] != "c") fail("filter with index values failed")
})
run("filter preserves original", function() {
var arr = [1, 2, 3]
var result = filter(arr, x => x > 1)
if (length(arr) != 3) fail("filter should not mutate original")
})
run("filter objects", function() {
var arr = [{active: true}, {active: false}, {active: true}]
var result = filter(arr, x => x.active)
if (length(result) != 2) fail("filter objects failed")
})
// ============================================================================
// FIND FUNCTION
// ============================================================================
run("find basic", function() {
var arr = [1, 2, 3, 4, 5]
var idx = find(arr, x => x > 3)
if (idx != 3) fail("find basic failed")
})
run("find first element", function() {
var arr = [10, 2, 3]
var idx = find(arr, x => x > 5)
if (idx != 0) fail("find first element failed")
})
run("find last element", function() {
var arr = [1, 2, 10]
var idx = find(arr, x => x > 5)
if (idx != 2) fail("find last element failed")
})
run("find not found", function() {
var arr = [1, 2, 3]
var idx = find(arr, x => x > 10)
if (idx != null) fail("find not found should return null")
})
run("find empty array", function() {
var arr = []
var idx = find(arr, x => true)
if (idx != null) fail("find in empty array should return null")
})
run("find by value", function() {
var arr = [10, 20, 30, 20]
var idx = find(arr, 20)
if (idx != 1) fail("find by value failed")
})
run("find reverse", function() {
var arr = [10, 20, 30, 20]
var idx = find(arr, 20, true)
if (idx != 3) fail("find reverse failed")
})
run("find with from", function() {
var arr = [10, 20, 30, 20]
var idx = find(arr, 20, false, 2)
if (idx != 3) fail("find with from failed")
})
run("find with index callback", function() {
var arr = ["a", "b", "c"]
var idx = find(arr, (x, i) => i == 1)
if (idx != 1) fail("find with index callback failed")
})
// ============================================================================
// ABS FUNCTION
// ============================================================================
run("abs positive", function() {
if (abs(5) != 5) fail("abs positive failed")
})
run("abs negative", function() {
if (abs(-5) != 5) fail("abs negative failed")
})
run("abs zero", function() {
if (abs(0) != 0) fail("abs zero failed")
})
run("abs float", function() {
if (abs(-3.14) != 3.14) fail("abs float failed")
})
run("abs non number", function() {
if (abs("5") != null) fail("abs non-number should return null")
if (abs(null) != null) fail("abs null should return null")
})
// ============================================================================
// FLOOR FUNCTION
// ============================================================================
run("floor positive", function() {
if (floor(3.7) != 3) fail("floor positive failed")
})
run("floor negative", function() {
if (floor(-3.7) != -4) fail("floor negative failed")
})
run("floor integer", function() {
if (floor(5) != 5) fail("floor integer failed")
})
run("floor zero", function() {
if (floor(0) != 0) fail("floor zero failed")
})
run("floor with place", function() {
if (floor(12.3775, -2) != 12.37) fail("floor with place failed")
})
run("floor negative with place", function() {
if (floor(-12.3775, -2) != -12.38) fail("floor negative with place failed")
})
// ============================================================================
// CEILING FUNCTION
// ============================================================================
run("ceiling positive", function() {
if (ceiling(3.2) != 4) fail("ceiling positive failed")
})
run("ceiling negative", function() {
if (ceiling(-3.7) != -3) fail("ceiling negative failed")
})
run("ceiling integer", function() {
if (ceiling(5) != 5) fail("ceiling integer failed")
})
run("ceiling zero", function() {
if (ceiling(0) != 0) fail("ceiling zero failed")
})
run("ceiling with place", function() {
if (ceiling(12.3775, -2) != 12.38) fail("ceiling with place failed")
})
run("ceiling negative with place", function() {
if (ceiling(-12.3775, -2) != -12.37) fail("ceiling negative with place failed")
})
// ============================================================================
// ROUND FUNCTION
// ============================================================================
run("round down", function() {
if (round(3.4) != 3) fail("round down failed")
})
run("round up", function() {
if (round(3.6) != 4) fail("round up failed")
})
run("round half", function() {
if (round(3.5) != 4) fail("round half failed")
})
run("round negative", function() {
if (round(-3.5) != -3 && round(-3.5) != -4) fail("round negative failed")
})
run("round integer", function() {
if (round(5) != 5) fail("round integer failed")
})
run("round with places", function() {
if (round(12.3775, -2) != 12.38) fail("round with places failed")
})
run("round to tens", function() {
if (round(12.3775, 1) != 10) fail("round to tens failed")
})
// ============================================================================
// TRUNC FUNCTION
// ============================================================================
run("trunc positive", function() {
if (trunc(3.7) != 3) fail("trunc positive failed")
})
run("trunc negative", function() {
if (trunc(-3.7) != -3) fail("trunc negative failed")
})
run("trunc integer", function() {
if (trunc(5) != 5) fail("trunc integer failed")
})
run("trunc zero", function() {
if (trunc(0) != 0) fail("trunc zero failed")
})
run("trunc with places", function() {
if (trunc(12.3775, -2) != 12.37) fail("trunc with places failed")
})
run("trunc negative with places", function() {
if (trunc(-12.3775, -2) != -12.37) fail("trunc negative with places failed")
})
// ============================================================================
// SIGN FUNCTION
// ============================================================================
run("sign positive", function() {
if (sign(5) != 1) fail("sign positive failed")
})
run("sign negative", function() {
if (sign(-5) != -1) fail("sign negative failed")
})
run("sign zero", function() {
if (sign(0) != 0) fail("sign zero failed")
})
run("sign float", function() {
if (sign(0.001) != 1) fail("sign positive float failed")
if (sign(-0.001) != -1) fail("sign negative float failed")
})
run("sign non number", function() {
if (sign("5") != null) fail("sign non-number should return null")
})
// ============================================================================
// WHOLE AND FRACTION FUNCTIONS
// ============================================================================
run("whole positive", function() {
if (whole(3.7) != 3) fail("whole positive failed")
})
run("whole negative", function() {
if (whole(-3.7) != -3) fail("whole negative failed")
})
run("whole integer", function() {
if (whole(5) != 5) fail("whole integer failed")
})
run("whole non number", function() {
if (whole("5") != null) fail("whole non-number should return null")
})
run("fraction positive", function() {
var f = fraction(3.75)
if (f < 0.74 || f > 0.76) fail("fraction positive failed")
})
run("fraction negative", function() {
var f = fraction(-3.75)
if (f > -0.74 || f < -0.76) fail("fraction negative failed")
})
run("fraction integer", function() {
if (fraction(5) != 0) fail("fraction integer failed")
})
run("fraction non number", function() {
if (fraction("5") != null) fail("fraction non-number should return null")
})
// ============================================================================
// NEG FUNCTION
// ============================================================================
run("neg positive", function() {
if (neg(5) != -5) fail("neg positive failed")
})
run("neg negative", function() {
if (neg(-5) != 5) fail("neg negative failed")
})
run("neg zero", function() {
if (neg(0) != 0) fail("neg zero failed")
})
run("neg float", function() {
if (neg(3.14) != -3.14) fail("neg float failed")
})
run("neg non number", function() {
if (neg("5") != null) fail("neg non-number should return null")
})
// ============================================================================
// MODULO FUNCTION
// ============================================================================
run("modulo positive", function() {
if (modulo(10, 3) != 1) fail("modulo positive failed")
})
run("modulo negative dividend", function() {
var result = modulo(-10, 3)
if (result != 2) fail("modulo negative dividend failed")
})
run("modulo negative divisor", function() {
var result = modulo(10, -3)
if (result != -2) fail("modulo negative divisor failed")
})
run("modulo both negative", function() {
var result = modulo(-10, -3)
if (result != -1) fail("modulo both negative failed")
})
run("modulo zero dividend", function() {
if (modulo(0, 5) != 0) fail("modulo zero dividend failed")
})
run("modulo zero divisor", function() {
if (modulo(10, 0) != null) fail("modulo zero divisor should return null")
})
run("modulo floats", function() {
var result = modulo(5.5, 2)
if (result < 1.4 || result > 1.6) fail("modulo floats failed")
})
run("remainder float basic", function() {
if (remainder(5.5, 2.5) != 0.5) fail("remainder 5.5 % 2.5 failed")
})
run("modulo float basic", function() {
if (modulo(5.5, 2.5) != 0.5) fail("modulo 5.5 % 2.5 failed")
})
run("remainder float negative", function() {
if (remainder(-5.5, 2.5) != -0.5) fail("remainder -5.5 % 2.5 failed")
})
run("modulo float negative", function() {
if (modulo(-5.5, 2.5) != 2.0) fail("modulo -5.5 % 2.5 failed")
})
// ============================================================================
// MIN AND MAX FUNCTIONS
// ============================================================================
run("min basic", function() {
if (min(3, 5) != 3) fail("min basic failed")
})
run("min equal", function() {
if (min(5, 5) != 5) fail("min equal failed")
})
run("min negative", function() {
if (min(-3, -5) != -5) fail("min negative failed")
})
run("min mixed", function() {
if (min(-3, 5) != -3) fail("min mixed failed")
})
run("min float", function() {
if (min(3.14, 2.71) != 2.71) fail("min float failed")
})
run("min non number", function() {
if (min(3, "5") != null) fail("min non-number should return null")
if (min("3", 5) != null) fail("min first non-number should return null")
})
run("max basic", function() {
if (max(3, 5) != 5) fail("max basic failed")
})
run("max equal", function() {
if (max(5, 5) != 5) fail("max equal failed")
})
run("max negative", function() {
if (max(-3, -5) != -3) fail("max negative failed")
})
run("max mixed", function() {
if (max(-3, 5) != 5) fail("max mixed failed")
})
run("max float", function() {
if (max(3.14, 2.71) != 3.14) fail("max float failed")
})
run("max non number", function() {
if (max(3, "5") != null) fail("max non-number should return null")
})
run("min max constrain", function() {
var val = 8
var constrained = min(max(val, 0), 10)
if (constrained != 8) fail("min max constrain in range failed")
constrained = min(max(-5, 0), 10)
if (constrained != 0) fail("min max constrain below failed")
constrained = min(max(15, 0), 10)
if (constrained != 10) fail("min max constrain above failed")
})
// ============================================================================
// CODEPOINT FUNCTION
// ============================================================================
run("codepoint letter", function() {
if (codepoint("A") != 65) fail("codepoint A failed")
if (codepoint("a") != 97) fail("codepoint a failed")
})
run("codepoint digit", function() {
if (codepoint("0") != 48) fail("codepoint 0 failed")
})
run("codepoint unicode", function() {
if (codepoint("\u00E9") != 233) fail("codepoint unicode failed")
})
run("codepoint first char", function() {
if (codepoint("ABC") != 65) fail("codepoint should return first char")
})
run("codepoint empty", function() {
if (codepoint("") != null) fail("codepoint empty should return null")
})
run("codepoint non text", function() {
if (codepoint(65) != null) fail("codepoint non-text should return null")
})
// ============================================================================
// CHARACTER FUNCTION
// ============================================================================
run("character letter", function() {
if (character(65) != "A") fail("character 65 failed")
if (character(97) != "a") fail("character 97 failed")
})
run("character digit", function() {
if (character(48) != "0") fail("character 48 failed")
})
run("character unicode", function() {
if (character(233) != "\u00E9") fail("character unicode failed")
})
run("character from text", function() {
if (character("hello") != "h") fail("character from text failed")
})
run("character invalid", function() {
if (character(-1) != "") fail("character negative should return empty")
})
// ============================================================================
// SEARCH FUNCTION
// ============================================================================
run("search found", function() {
if (search("hello world", "world") != 6) fail("search found failed")
})
run("search not found", function() {
if (search("hello world", "xyz") != null) fail("search not found should return null")
})
run("search beginning", function() {
if (search("hello world", "hello") != 0) fail("search beginning failed")
})
run("search single char", function() {
if (search("hello", "l") != 2) fail("search single char failed")
})
run("search with from", function() {
if (search("hello hello", "hello", 1) != 6) fail("search with from failed")
})
run("search empty pattern", function() {
if (search("hello", "") != 0) fail("search empty pattern failed")
})
run("search negative from", function() {
var result = search("hello world", "world", -5)
if (result != 6) fail("search negative from failed")
})
// ============================================================================
// REPLACE FUNCTION
// ============================================================================
run("replace basic", function() {
var result = replace("hello world", "world", "universe")
if (result != "hello universe") fail("replace basic failed")
})
run("replace not found", function() {
var result = replace("hello world", "xyz", "abc")
if (result != "hello world") fail("replace not found should return original")
})
run("replace multiple", function() {
var result = replace("banana", "a", "o")
if (result != "bonono") fail("replace multiple failed")
})
run("replace with limit", function() {
var result = replace("banana", "a", "o", 1)
if (result != "bonana") fail("replace with limit failed")
})
run("replace empty target", function() {
var result = replace("abc", "", "-")
if (result != "-a-b-c-") fail("replace empty target failed")
})
run("replace to empty", function() {
var result = replace("hello", "l", "")
if (result != "heo") fail("replace to empty failed")
})
run("replace with function", function() {
var result = replace("hello", "l", (match, pos) => `[${pos}]`)
if (result != "he[2][3]o") fail("replace with function failed")
})
run("replace with function limit", function() {
var result = replace("banana", "a", (match, pos) => `[${pos}]`, 2)
if (result != "b[1]n[3]na") fail("replace with function limit failed")
})
run("replace with regex", function() {
var result = replace("banana", /a/, "o")
if (result != "bonono") fail("replace with regex failed")
})
run("replace with regex limit", function() {
var result = replace("banana", /a/, "o", 2)
if (result != "bonona") fail("replace with regex limit failed")
})
run("replace with regex function", function() {
var result = replace("hello", /l/, (match, pos) => `[${pos}]`)
if (result != "he[2][3]o") fail("replace with regex function failed")
})
// ============================================================================
// TEXT FUNCTION (Conversion and Slicing)
// ============================================================================
run("text number basic", function() {
if (text(123) != "123") fail("text number basic failed")
})
run("text number negative", function() {
if (text(-456) != "-456") fail("text number negative failed")
})
run("text number float", function() {
var result = text(3.14)
if (search(result, "3.14") != 0) fail("text number float failed")
})
run("text array join empty sep", function() {
var result = text(["a", "b", "c"], "")
if (result != "abc") fail("text array join empty sep failed")
})
run("text slice basic", function() {
if (text("hello", 1, 4) != "ell") fail("text slice basic failed")
})
run("text slice from only", function() {
if (text("hello", 2) != "llo") fail("text slice from only failed")
})
run("text slice negative from", function() {
if (text("hello", -2) != "lo") fail("text slice negative from failed")
})
run("text slice negative to", function() {
if (text("hello", 0, -2) != "hel") fail("text slice negative to failed")
})
run("text boolean", function() {
if (text(true) != "true") fail("text true failed")
if (text(false) != "false") fail("text false failed")
})
run("text null", function() {
if (text(null) != "null") fail("text null failed")
})
// ============================================================================
// NUMBER FUNCTION (Conversion)
// ============================================================================
run("number from string", function() {
if (number("123") != 123) fail("number from string failed")
})
run("number from negative string", function() {
if (number("-456") != -456) fail("number from negative string failed")
})
run("number from float string", function() {
if (number("3.14") != 3.14) fail("number from float string failed")
})
run("number invalid string", function() {
if (number("abc") != null) fail("number invalid string should return null")
})
run("number from boolean", function() {
if (number(true) != 1) fail("number from true failed")
if (number(false) != 0) fail("number from false failed")
})
run("number from number", function() {
if (number(42) != 42) fail("number from number failed")
})
run("number with radix", function() {
if (number("FF", 16) != 255) fail("number hex failed")
if (number("1010", 2) != 10) fail("number binary failed")
})
run("number leading zeros", function() {
if (number("007") != 7) fail("number leading zeros failed")
})
// ============================================================================
// ARRAY FUNCTION (Creator and Slicing)
// ============================================================================
run("array create with length", function() {
var arr = array(5)
if (length(arr) != 5) fail("array create length failed")
if (arr[0] != null) fail("array create should init to null")
})
run("array create with initial", function() {
var arr = array(3, 42)
if (arr[0] != 42 || arr[1] != 42 || arr[2] != 42) fail("array create with initial failed")
})
run("array create with function", function() {
var arr = array(3, i => i * 2)
if (arr[0] != 0 || arr[1] != 2 || arr[2] != 4) fail("array create with function failed")
})
run("array copy", function() {
var orig = [1, 2, 3]
var copy = array(orig)
copy[0] = 99
if (orig[0] != 1) fail("array copy should not affect original")
})
run("array slice basic", function() {
var arr = [1, 2, 3, 4, 5]
var sliced = array(arr, 1, 3)
if (length(sliced) != 2) fail("array slice length failed")
if (sliced[0] != 2 || sliced[1] != 3) fail("array slice values failed")
})
run("array slice negative", function() {
var arr = [1, 2, 3, 4, 5]
var sliced = array(arr, -3)
if (length(sliced) != 3) fail("array slice negative failed")
if (sliced[0] != 3) fail("array slice negative value failed")
})
run("array from object keys", function() {
var obj = {a: 1, b: 2, c: 3}
var keys = array(obj)
if (length(keys) != 3) fail("array from object keys length failed")
})
run("array from text", function() {
var arr = array("abc")
if (length(arr) != 3) fail("array from text length failed")
if (arr[0] != "a" || arr[1] != "b" || arr[2] != "c") fail("array from text values failed")
})
run("array split text", function() {
var arr = array("a,b,c", ",")
if (length(arr) != 3) fail("array split text length failed")
if (arr[1] != "b") fail("array split text value failed")
})
// ============================================================================
// TRIM FUNCTION
// ============================================================================
run("trim spaces", function() {
if (trim(" hello ") != "hello") fail("trim spaces failed")
})
run("trim tabs", function() {
if (trim("\thello\t") != "hello") fail("trim tabs failed")
})
run("trim mixed", function() {
if (trim(" \t hello \n ") != "hello") fail("trim mixed failed")
})
run("trim no whitespace", function() {
if (trim("hello") != "hello") fail("trim no whitespace failed")
})
run("trim empty", function() {
if (trim("") != "") fail("trim empty failed")
})
run("trim all whitespace", function() {
if (trim(" ") != "") fail("trim all whitespace failed")
})
// ============================================================================
// LOWER AND UPPER FUNCTIONS
// ============================================================================
run("lower basic", function() {
if (lower("HELLO") != "hello") fail("lower basic failed")
})
run("lower mixed", function() {
if (lower("HeLLo WoRLD") != "hello world") fail("lower mixed failed")
})
run("lower already lower", function() {
if (lower("hello") != "hello") fail("lower already lower failed")
})
run("lower with numbers", function() {
if (lower("ABC123") != "abc123") fail("lower with numbers failed")
})
run("upper basic", function() {
if (upper("hello") != "HELLO") fail("upper basic failed")
})
run("upper mixed", function() {
if (upper("HeLLo WoRLD") != "HELLO WORLD") fail("upper mixed failed")
})
run("upper already upper", function() {
if (upper("HELLO") != "HELLO") fail("upper already upper failed")
})
run("upper with numbers", function() {
if (upper("abc123") != "ABC123") fail("upper with numbers failed")
})
// ============================================================================
// APPLY FUNCTION
// ============================================================================
run("apply basic", function() {
var fn = function(a, b) { return a + b }
var result = apply(fn, [3, 4])
if (result != 7) fail("apply basic failed")
})
run("apply no args", function() {
var fn = function() { return 42 }
var result = apply(fn, [])
if (result != 42) fail("apply no args failed")
})
run("apply single arg", function() {
var fn = function(x) { return x * 2 }
var result = apply(fn, [5])
if (result != 10) fail("apply single arg failed")
})
run("apply many args", function() {
var fn = function(a, b, c, d) { return a + b + c + d }
var result = apply(fn, [1, 2, 3, 4])
if (result != 10) fail("apply many args failed")
})
run("apply non function", function() {
var result = apply(42, [1, 2])
if (result != 42) fail("apply non-function should return first arg")
})
// ============================================================================
// CALL FUNCTION (Additional Tests)
// ============================================================================
run("call many args", function() {
var fn = function(a, b, c, d) { return a * b + c * d }
var result = call(fn, null, [2, 3, 4, 5])
if (result != 26) fail("call many args failed")
})
run("call method style", function() {
var obj = {
value: 10,
multiply: function(x) { return this.value * x }
}
var result = call(obj.multiply, obj, [5])
if (result != 50) fail("call method style failed")
})
run("call change this", function() {
var obj1 = { value: 10 }
var obj2 = { value: 20 }
var fn = function() { return this.value }
if (call(fn, obj1) != 10) fail("call this obj1 failed")
if (call(fn, obj2) != 20) fail("call this obj2 failed")
})
// ============================================================================
// ARRFOR FUNCTION (Array For-Each)
// ============================================================================
run("arrfor basic", function() {
var arr = [1, 2, 3]
var sum = 0
arrfor(arr, x => { sum = sum + x })
if (sum != 6) fail("arrfor basic failed")
})
run("arrfor with index", function() {
var arr = ["a", "b", "c"]
var indices = []
arrfor(arr, (x, i) => { indices[] = i })
if (indices[0] != 0 || indices[2] != 2) fail("arrfor with index failed")
})
run("arrfor empty", function() {
var called = false
arrfor([], x => { called = true })
if (called) fail("arrfor empty should not call function")
})
run("arrfor mutation", function() {
var arr = [1, 2, 3]
var results = []
arrfor(arr, x => { results[] = x * 2 })
if (results[0] != 2 || results[1] != 4 || results[2] != 6) fail("arrfor mutation failed")
})
// ============================================================================
// STONE FUNCTION (Additional Tests)
// ============================================================================
run("stone returns value", function() {
var obj = {x: 1}
var result = stone(obj)
if (result != obj) fail("stone should return the value")
})
run("stone idempotent", function() {
var obj = {x: 1}
stone(obj)
stone(obj)
if (!is_stone(obj)) fail("stone should be idempotent")
})
// ============================================================================
// PROTO FUNCTION (Additional Tests)
// ============================================================================
run("proto chain", function() {
var grandparent = {a: 1}
var parent = meme(grandparent)
var child = meme(parent)
if (proto(child) != parent) fail("proto chain child->parent failed")
if (proto(parent) != grandparent) fail("proto chain parent->grandparent failed")
})
run("proto array disrupts", function() {
if (!should_disrupt(function() { proto([1, 2, 3]) })) fail("proto of array should disrupt")
})
// ============================================================================
// MEME FUNCTION (Additional Tests)
// ============================================================================
run("meme method inheritance", function() {
var parent = {
greet: function() { return "hello" }
}
var child = meme(parent)
if (child.greet() != "hello") fail("meme method inheritance failed")
})
run("meme this in inherited method", function() {
var parent = {
getValue: function() { return this.value }
}
var child = meme(parent)
child.value = 42
if (child.getValue() != 42) fail("meme this in inherited method failed")
})
run("meme deep chain", function() {
var a = {x: 1}
var b = meme(a)
var c = meme(b)
var d = meme(c)
if (d.x != 1) fail("meme deep chain failed")
})
// ============================================================================
// DELETE OPERATOR
// ============================================================================
run("delete property", function() {
var obj = {a: 1, b: 2}
delete obj.a
if ("a" in obj) fail("delete property failed")
if (obj.b != 2) fail("delete should not affect other properties")
})
run("delete array element disrupts", function() {
if (!should_disrupt(function() { var arr = [1, 2, 3]; delete arr[1] })) fail("delete on array element should disrupt")
})
run("delete nonexistent", function() {
var obj = {a: 1}
delete obj.b
if (obj.a != 1) fail("delete nonexistent should not affect object")
})
run("delete then nested closure", function() {
var r = {a: 1, b: 2}
delete r.b
var f = function(x) {
return function() { return x }
}
var g = f(10)
assert_eq(g(), 10, "nested closure after delete")
})
// ============================================================================
// TYPEOF-LIKE BEHAVIOR
// ============================================================================
run("is integer", function() {
if (!is_number(5) || 5 % 1 != 0) fail("is_integer positive failed")
if (!is_number(-5) || -5 % 1 != 0) fail("is_integer negative failed")
if (is_number(5.5) && 5.5 % 1 == 0) fail("is_integer float should not be integer")
})
// ============================================================================
// ARRAY MAP-LIKE WITH ARRAY FUNCTION
// ============================================================================
run("array map basic", function() {
var arr = [1, 2, 3]
var doubled = array(arr, x => x * 2)
if (doubled[0] != 2 || doubled[1] != 4 || doubled[2] != 6) fail("array map basic failed")
})
run("array map with index", function() {
var arr = ["a", "b", "c"]
var result = array(arr, (x, i) => `${x}${i}`)
if (result[0] != "a0" || result[1] != "b1") fail("array map with index failed")
})
run("array map reverse", function() {
var arr = [1, 2, 3]
var result = array(arr, x => x * 2, true)
if (result[0] != 6 || result[2] != 2) fail("array map reverse failed")
})
run("array map with exit", function() {
var arr = [1, 2, 3, 4, 5]
var result = array(arr, x => {
if (x > 3) return null
return x * 2
}, false, null)
if (length(result) != 5) fail("array map with exit length unexpected")
})
run("inline map intrinsic is_data", function() {
var items = [{}, [], "hello", 42, true]
var result = array(items, is_data)
if (result[0] != true) fail("is_data {} should be true")
if (result[1] != true) fail("is_data [] should be true")
if (result[2] != true) fail("is_data string should be true")
if (result[3] != true) fail("is_data number should be true")
if (result[4] != true) fail("is_data bool should be true")
if (length(result) != 5) fail("result length should be 5")
})
run("inline map intrinsic is_number", function() {
var items = [1, "two", 3.14, null, true]
var result = array(items, is_number)
if (result[0] != true) fail("1 should be number")
if (result[1] != false) fail("'two' should not be number")
if (result[2] != true) fail("3.14 should be number")
if (result[3] != false) fail("null should not be number")
if (result[4] != false) fail("true should not be number")
})
run("inline map intrinsic is_text", function() {
var items = ["hello", 42, "", null]
var result = array(items, is_text)
if (result[0] != true) fail("'hello' should be text")
if (result[1] != false) fail("42 should not be text")
if (result[2] != true) fail("'' should be text")
if (result[3] != false) fail("null should not be text")
})
run("inline map intrinsic is_digit", function() {
var chars = array("a5B2 ")
var result = array(chars, is_digit)
if (result[0] != false) fail("a should not be digit")
if (result[1] != true) fail("5 should be digit")
if (result[2] != false) fail("B should not be digit")
if (result[3] != true) fail("2 should be digit")
if (result[4] != false) fail("space should not be digit")
})
run("inline map lambda", function() {
var arr = [10, 20, 30]
var result = array(arr, function(x) { return x + 1 })
if (result[0] != 11) fail("10+1 should be 11")
if (result[1] != 21) fail("20+1 should be 21")
if (result[2] != 31) fail("30+1 should be 31")
})
run("inline map empty array", function() {
var result = array([], is_number)
if (length(result) != 0) fail("map of empty should be empty")
})
// ============================================================================
// NUMERIC INTRINSIC CALLBACK INLINING
// ============================================================================
var mymap = function(arr, fn) {
var result = array(length(arr))
var i = 0
while (i < length(arr)) {
result[i] = fn(arr[i])
i = i + 1
}
return result
}
var myfold = function(arr, fn) {
var acc = arr[0]
var i = 1
while (i < length(arr)) {
acc = fn(acc, arr[i])
i = i + 1
}
return acc
}
run("inline callback abs", function() {
var result = mymap([-3, 5, -1.5, 0], function(a) { return abs(a) })
assert_eq(result[0], 3, "abs(-3)")
assert_eq(result[1], 5, "abs(5)")
assert_eq(result[2], 1.5, "abs(-1.5)")
assert_eq(result[3], 0, "abs(0)")
})
run("inline callback neg", function() {
var result = mymap([3, -5, 0, 1.5], function(a) { return neg(a) })
assert_eq(result[0], -3, "neg(3)")
assert_eq(result[1], 5, "neg(-5)")
assert_eq(result[2], 0, "neg(0)")
assert_eq(result[3], -1.5, "neg(1.5)")
})
run("inline callback sign", function() {
var result = mymap([-7, 0, 42, -0.5], function(a) { return sign(a) })
assert_eq(result[0], -1, "sign(-7)")
assert_eq(result[1], 0, "sign(0)")
assert_eq(result[2], 1, "sign(42)")
assert_eq(result[3], -1, "sign(-0.5)")
})
run("inline callback fraction", function() {
var result = mymap([3.75, -2.5, 5.0], function(a) { return fraction(a) })
assert_eq(result[0], 0.75, "fraction(3.75)")
assert_eq(result[1], -0.5, "fraction(-2.5)")
assert_eq(result[2], 0, "fraction(5.0)")
})
run("inline callback floor", function() {
var result = mymap([3.7, -2.3, 5.0, 1.9], function(a) { return floor(a) })
assert_eq(result[0], 3, "floor(3.7)")
assert_eq(result[1], -3, "floor(-2.3)")
assert_eq(result[2], 5, "floor(5.0)")
assert_eq(result[3], 1, "floor(1.9)")
})
run("inline callback ceiling", function() {
var result = mymap([3.2, -2.7, 5.0, 1.1], function(a) { return ceiling(a) })
assert_eq(result[0], 4, "ceiling(3.2)")
assert_eq(result[1], -2, "ceiling(-2.7)")
assert_eq(result[2], 5, "ceiling(5.0)")
assert_eq(result[3], 2, "ceiling(1.1)")
})
run("inline callback round", function() {
var result = mymap([3.5, -2.5, 5.0, 1.4], function(a) { return round(a) })
assert_eq(result[0], 4, "round(3.5)")
assert_eq(result[1], -3, "round(-2.5)")
assert_eq(result[2], 5, "round(5.0)")
assert_eq(result[3], 1, "round(1.4)")
})
run("inline callback trunc", function() {
var result = mymap([3.7, -2.3, 5.0, -1.9], function(a) { return trunc(a) })
assert_eq(result[0], 3, "trunc(3.7)")
assert_eq(result[1], -2, "trunc(-2.3)")
assert_eq(result[2], 5, "trunc(5.0)")
assert_eq(result[3], -1, "trunc(-1.9)")
})
run("inline callback max", function() {
var result = myfold([3, 7, 2, 9, 1], function(a, b) { return max(a, b) })
assert_eq(result, 9, "max reduce")
})
run("inline callback min", function() {
var result = myfold([3, 7, 2, 9, 1], function(a, b) { return min(a, b) })
assert_eq(result, 1, "min reduce")
})
run("inline callback modulo", function() {
var result = myfold([17, 5], function(a, b) { return modulo(a, b) })
assert_eq(result, 2, "modulo")
})
run("inline callback remainder", function() {
var result = myfold([-17, 5], function(a, b) { return remainder(a, b) })
assert_eq(result, -2, "remainder")
})
// ============================================================================
// STRING METHOD EDGE CASES
// ============================================================================
run("string startsWith", function() {
if (!starts_with("hello", "hel")) fail("startsWith match failed")
if (starts_with("hello", "ell")) fail("startsWith no match failed")
if (!starts_with("hello", "")) fail("startsWith empty should match")
})
run("string endsWith", function() {
if (!ends_with("hello", "llo")) fail("endsWith match failed")
if (ends_with("hello", "ell")) fail("endsWith no match failed")
if (!ends_with("hello", "")) fail("endsWith empty should match")
})
run("string includes", function() {
if (search("hello world", "world") == null) fail("includes match failed")
if (search("hello", "xyz") != null) fail("includes no match failed")
if (search("hello", "") == null) fail("includes empty should match")
})
// ============================================================================
// ARRAY METHOD EDGE CASES
// ============================================================================
run("array includes", function() {
var arr = [1, 2, 3]
if (find(arr, 2) == null) fail("array includes match failed")
if (find(arr, 5) != null) fail("array includes no match failed")
})
run("array every", function() {
var arr = [2, 4, 6]
if (!every(arr, x => x % 2 == 0)) fail("array every all pass failed")
arr = [2, 3, 6]
if (every(arr, x => x % 2 == 0)) fail("array every not all pass failed")
})
run("array some", function() {
var arr = [1, 2, 3]
if (!some(arr, x => x > 2)) fail("array some match failed")
if (some(arr, x => x > 5)) fail("array some no match failed")
})
// ============================================================================
// ADDITIONAL EDGE CASES
// ============================================================================
run("nested array access", function() {
var arr = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
if (arr[0][1][0] != 3) fail("nested array access failed")
if (arr[1][1][1] != 8) fail("nested array access deep failed")
})
run("nested object access", function() {
var obj = {a: {b: {c: {d: 42}}}}
if (obj.a.b.c.d != 42) fail("nested object access failed")
})
run("mixed nested access", function() {
var data = {users: [{name: "Alice"}, {name: "Bob"}]}
if (data.users[1].name != "Bob") fail("mixed nested access failed")
})
run("object with null value", function() {
var obj = {a: null, b: 2}
if (obj.a != null) fail("object null value failed")
if (!("a" in obj)) fail("object with null should have key")
})
run("array with null values", function() {
var arr = [1, null, 3]
if (arr[1] != null) fail("array null value failed")
if (length(arr) != 3) fail("array with null length failed")
})
run("function returning function", function() {
var outer = function(x) {
return function(y) {
return function(z) {
return x + y + z
}
}
}
if (outer(1)(2)(3) != 6) fail("function returning function failed")
})
run("immediately invoked function", function() {
var result = (function(x) { return x * 2 })(21)
if (result != 42) fail("immediately invoked function failed")
})
run("text split text", function() {
var t = "hello world"
var result = array(t, " ")
if (length(result) != 2) fail("text split failed")
if (result[0] != "hello") fail("text split first failed")
if (result[1] != "world") fail("text split second failed")
})
run("text split regex", function() {
var t = "hello world"
var result = array(t, /\s+/)
if (length(result) != 2) fail("text split regex failed")
if (result[0] != "hello") fail("text split regex first failed")
if (result[1] != "world") fail("text split regex second failed")
})
run("text search text", function() {
var t = "hello world"
var result = search(t, "world")
if (result != 6) fail("text search failed")
})
run("text search regex", function() {
var t = "hello world"
var result = search(t, /world/)
if (result != 6) fail("text search regex failed")
})
run("extract basic text", function() {
var t = "hello world"
var result = extract(t, "world")
if (result[0] != "world") fail("extract basic text failed")
})
run("extract text not found", function() {
var t = "hello world"
var result = extract(t, "xyz")
if (result != null) fail("extract not found should return null")
})
run("extract regex basic", function() {
var t = "hello world"
var result = extract(t, /world/)
if (result[0] != "world") fail("extract regex basic failed")
})
run("extract regex with capture group", function() {
var t = "hello world"
var result = extract(t, /(\w+) (\w+)/)
if (result[0] != "hello world") fail("extract regex full match failed")
if (result[1] != "hello") fail("extract regex capture group 1 failed")
if (result[2] != "world") fail("extract regex capture group 2 failed")
})
run("extract regex digits", function() {
var t = "abc123def456"
var result = extract(t, /(\d+)/)
if (result[0] != "123") fail("extract regex digits failed")
if (result[1] != "123") fail("extract regex digits capture failed")
})
run("extract with from", function() {
var t = "hello hello world"
var result = extract(t, "hello", 1)
if (result[0] != "hello") fail("extract with from failed")
})
run("extract with from to", function() {
var t = "hello world hello"
var result = extract(t, "hello", 0, 10)
if (result[0] != "hello") fail("extract with from to failed")
})
run("extract regex case insensitive", function() {
var t = "Hello World"
var result = extract(t, /hello/i)
if (result[0] != "Hello") fail("extract regex case insensitive failed")
})
// ============================================================================
// GC PATHOLOGICAL CASES
// ============================================================================
run("gc cycle object self", function() {
var obj = {name: "root"}
obj.self = obj
if (obj.self != obj) fail("self cycle failed")
})
run("gc cycle array self", function() {
var arr = []
var i = 0
for (i = 0; i < 10; i++) {
arr[] = arr
}
if (arr[0] != arr) fail("array self cycle failed")
})
run("gc cycle object array pair", function() {
var obj = {kind: "node"}
var arr = [obj]
obj.arr = arr
if (obj.arr[0] != obj) fail("object/array cycle failed")
})
run("gc shared references", function() {
var shared = {value: 42}
var a = {ref: shared}
var b = {ref: shared}
if (a.ref != shared || b.ref != shared) fail("shared reference failed")
})
run("gc object key cycle", function() {
var k = {}
var v = {label: "value"}
var o = {}
o[k] = v
v.back = o
if (o[k].back != o) fail("object key cycle failed")
})
run("gc object text key mix", function() {
var obj = {}
var key = "alpha"
var inner = {token: "x"}
obj[key] = inner
obj["beta"] = [inner, obj]
if (obj.alpha.token != "x") fail("text key value failed")
if (obj.beta[1] != obj) fail("text key cycle failed")
})
// ============================================================================
// OBJECT INTRINSIC TESTS
// ============================================================================
run("object shallow copy", function() {
var orig = {a: 1, b: 2, c: 3}
var copy = object(orig)
if (copy.a != 1) fail("object copy a failed")
if (copy.b != 2) fail("object copy b failed")
if (copy.c != 3) fail("object copy c failed")
copy.a = 99
if (orig.a != 1) fail("object copy should not mutate original")
})
run("object combine", function() {
var obj1 = {a: 1, b: 2}
var obj2 = {c: 3, d: 4}
var combined = object(obj1, obj2)
if (combined.a != 1) fail("object combine a failed")
if (combined.b != 2) fail("object combine b failed")
if (combined.c != 3) fail("object combine c failed")
if (combined.d != 4) fail("object combine d failed")
})
run("object combine override", function() {
var obj1 = {a: 1, b: 2}
var obj2 = {b: 99, c: 3}
var combined = object(obj1, obj2)
if (combined.a != 1) fail("object combine override a failed")
if (combined.b != 99) fail("object combine should override with second arg")
if (combined.c != 3) fail("object combine override c failed")
})
run("object select keys", function() {
var orig = {a: 1, b: 2, c: 3, d: 4}
var selected = object(orig, ["a", "c"])
if (selected.a != 1) fail("object select a failed")
if (selected.c != 3) fail("object select c failed")
if (selected.b != null) fail("object select should not include b")
if (selected.d != null) fail("object select should not include d")
})
run("object from keys true", function() {
var keys = ["x", "y", "z"]
var obj = object(keys)
if (obj.x != true) fail("object from keys x failed")
if (obj.y != true) fail("object from keys y failed")
if (obj.z != true) fail("object from keys z failed")
})
run("object from keys function", function() {
var keys = ["a", "b", "c"]
var obj = object(keys, function(k) { return k + "_val" })
if (obj.a != "a_val") fail("object from keys func a failed")
if (obj.b != "b_val") fail("object from keys func b failed")
if (obj.c != "c_val") fail("object from keys func c failed")
})
// ============================================================================
// SPLAT INTRINSIC TESTS
// ============================================================================
run("splat prototype flattening", function() {
var proto = {x: 10, y: 20}
var obj = meme(proto, {z: 30})
var flat = splat(obj)
if (flat.x != 10) fail("splat x failed")
if (flat.y != 20) fail("splat y failed")
if (flat.z != 30) fail("splat z failed")
})
// ============================================================================
// REVERSE INTRINSIC TESTS (detailed)
// ============================================================================
run("reverse array detailed", function() {
var arr = [1, 2, 3, 4, 5]
var rev = reverse(arr)
if (rev[0] != 5) fail("reverse[0] failed")
if (rev[1] != 4) fail("reverse[1] failed")
if (rev[2] != 3) fail("reverse[2] failed")
if (rev[3] != 2) fail("reverse[3] failed")
if (rev[4] != 1) fail("reverse[4] failed")
if (arr[0] != 1) fail("reverse should not mutate original")
})
// ============================================================================
// APPLY INTRINSIC TESTS
// ============================================================================
run("fn.apply with array args", function() {
def sum = function(a, b, c) { return a + b + c }
var result = fn.apply(sum, [1, 2, 3])
if (result != 6) fail("apply with array args failed")
})
run("fn.apply with no args", function() {
def ret42 = function() { return 42 }
var result = fn.apply(ret42)
if (result != 42) fail("apply with no args failed")
})
run("fn.apply with single value", function() {
def double = function(x) { return x * 2 }
var result = fn.apply(double, 10)
if (result != 20) fail("apply with single value failed")
})
// ============================================================================
// GC STRESS TESTS FOR FIXED INTRINSICS
// ============================================================================
run("gc reverse under pressure", function() {
var arrays = []
var i = 0
var rev = null
for (i = 0; i < 100; i = i + 1) {
arrays[i] = [i, i+1, i+2, i+3, i+4]
}
for (i = 0; i < 100; i = i + 1) {
rev = reverse(arrays[i])
if (rev[0] != i+4) fail("gc reverse stress failed")
}
})
run("gc object select under pressure", function() {
var objs = []
var i = 0
var selected = null
for (i = 0; i < 100; i = i + 1) {
objs[i] = {a: i, b: i+1, c: i+2, d: i+3}
}
for (i = 0; i < 100; i = i + 1) {
selected = object(objs[i], ["a", "c"])
if (selected.a != i) fail("gc object select stress failed")
if (selected.c != i+2) fail("gc object select stress c failed")
}
})
run("gc object from keys function under pressure", function() {
var keysets = []
var i = 0
var obj = null
var expected = null
for (i = 0; i < 50; i = i + 1) {
keysets[i] = [`k${i}`, `j${i}`, `m${i}`]
}
for (i = 0; i < 50; i = i + 1) {
obj = object(keysets[i], function(k) { return k + "_value" })
expected = `k${i}_value`
if (obj[`k${i}`] != expected) fail("gc object from keys func stress failed")
}
})
// ============================================================================
// DEFAULT PARAMETER TESTS
// ============================================================================
run("default param constant", function() {
var f = function(a, b=10) { return a + b }
assert_eq(f(1), 11, "default param constant")
})
run("default param overridden", function() {
var f = function(a, b=10) { return a + b }
assert_eq(f(1, 2), 3, "default param overridden")
})
run("default param uses earlier param", function() {
var f = function(a, b=a+1) { return b }
assert_eq(f(5), 6, "default param uses earlier param")
})
run("default param uses earlier param overridden", function() {
var f = function(a, b=a+1) { return b }
assert_eq(f(5, 20), 20, "default param uses earlier param overridden")
})
run("multiple default params", function() {
var f = function(a, b=10, c=a+1) { return a + b + c }
assert_eq(f(1), 13, "multiple defaults f(1)")
assert_eq(f(1, 2), 5, "multiple defaults f(1,2)")
assert_eq(f(1, 2, 3), 6, "multiple defaults f(1,2,3)")
})
run("arrow function default param", function() {
var g = (x, y=100) => x + y
assert_eq(g(5), 105, "arrow default param")
assert_eq(g(5, 20), 25, "arrow default param overridden")
})
run("default param null passed explicitly", function() {
var f = function(a, b=10) { return b }
assert_eq(f(1, null), 10, "explicit null triggers default")
})
run("default param with string", function() {
var f = function(a, b="hello") { return b }
assert_eq(f(1), "hello", "default string param")
assert_eq(f(1, "world"), "world", "default string overridden")
})
run("default param first param has no default", function() {
var f = function(a, b=42) { return a }
assert_eq(f(7), 7, "first param no default")
})
// ============================================================================
// FUNCTINO TESTS
// ============================================================================
run("functino +! addition", function() {
assert_eq(+!(3, 4), 7, "+! addition")
})
run("functino -! subtraction", function() {
assert_eq(-!(10, 3), 7, "-! subtraction")
})
run("functino *! multiplication", function() {
assert_eq(*!(5, 6), 30, "*! multiplication")
})
run("functino /! division", function() {
assert_eq(/!(10, 2), 5, "/! division")
})
run("functino %! modulo", function() {
assert_eq(%!(10, 3), 1, "%! modulo")
})
run("functino **! power", function() {
assert_eq(**!(2, 10), 1024, "**! power")
})
run("functino <! less than", function() {
assert_eq(<!(3, 5), true, "<! true")
assert_eq(<!(5, 3), false, "<! false")
assert_eq(<!(3, 3), false, "<! equal")
})
run("functino >! greater than", function() {
assert_eq(>!(5, 3), true, ">! true")
assert_eq(>!(3, 5), false, ">! false")
assert_eq(>!(3, 3), false, ">! equal")
})
run("functino <=! less or equal", function() {
assert_eq(<=!(3, 5), true, "<=! less")
assert_eq(<=!(3, 3), true, "<=! equal")
assert_eq(<=!(5, 3), false, "<=! greater")
})
run("functino >=! greater or equal", function() {
assert_eq(>=!(5, 3), true, ">=! greater")
assert_eq(>=!(3, 3), true, ">=! equal")
assert_eq(>=!(3, 5), false, ">=! less")
})
run("functino =! equality", function() {
assert_eq(=!(5, 5), true, "=! true")
assert_eq(=!(5, 3), false, "=! false")
})
run("functino !=! inequality", function() {
assert_eq(!=!(5, 3), true, "!=! true")
assert_eq(!=!(5, 5), false, "!=! false")
})
run("functino =! with tolerance", function() {
assert_eq(=!(1.0, 1.0001, 0.001), true, "=! within tolerance")
assert_eq(=!(1.0, 1.01, 0.001), false, "=! outside tolerance")
})
run("functino !=! with tolerance", function() {
assert_eq(!=!(1.0, 1.01, 0.001), true, "!=! outside tolerance")
assert_eq(!=!(1.0, 1.0001, 0.001), false, "!=! within tolerance")
})
run("functino &! bitwise and", function() {
assert_eq(&!(0xff, 0x0f), 0x0f, "&! bitwise and")
})
run("functino |! bitwise or", function() {
assert_eq(|!(0xf0, 0x0f), 0xff, "|! bitwise or")
})
run("functino ^! bitwise xor", function() {
assert_eq(^!(0xff, 0x0f), 0xf0, "^! bitwise xor")
})
run("functino <<! shift left", function() {
assert_eq(<<!(1, 4), 16, "<<! shift left")
})
run("functino >>! shift right", function() {
assert_eq(>>!(16, 4), 1, ">>! shift right")
})
run("functino ~! bitwise not", function() {
assert_eq(~!(0), -1, "~! bitwise not 0")
})
run("functino []! array index", function() {
var arr = [10, 20, 30]
assert_eq([]!(arr, 0), 10, "[]! index 0")
assert_eq([]!(arr, 1), 20, "[]! index 1")
assert_eq([]!(arr, 2), 30, "[]! index 2")
})
run("functino &&! logical and", function() {
assert_eq(&&!(true, true), true, "&&! true true")
assert_eq(&&!(true, false), false, "&&! true false")
assert_eq(&&!(false, true), false, "&&! false true")
assert_eq(&&!(1, 2), 2, "&&! truthy returns right")
assert_eq(&&!(0, 2), 0, "&&! falsy returns left")
})
run("functino ||! logical or", function() {
assert_eq(||!(false, true), true, "||! false true")
assert_eq(||!(true, false), true, "||! true false")
assert_eq(||!(false, false), false, "||! false false")
assert_eq(||!(0, 5), 5, "||! falsy returns right")
assert_eq(||!(3, 5), 3, "||! truthy returns left")
})
run("functino >>>! unsigned shift right", function() {
assert_eq(>>>!(-1, 28), 15, ">>>! unsigned shift right")
})
// ============================================================================
// FUNCTION DEFAULT PARAMETERS
// ============================================================================
run("default param basic", function() {
var fn = function(a = 3, b = 5) { return a + b }
assert_eq(fn(), 8, "both defaults")
assert_eq(fn(10), 15, "first provided")
assert_eq(fn(10, 20), 30, "both provided")
})
run("default param null triggers default", function() {
var fn = function(a = 99) { return a }
assert_eq(fn(null), 99, "null should trigger default")
assert_eq(fn(), 99, "missing should trigger default")
assert_eq(fn(0), 0, "zero should not trigger default")
assert_eq(fn(false), false, "false should not trigger default")
assert_eq(fn(""), "", "empty string should not trigger default")
})
run("default param pipe syntax", function() {
var fn = function(a | 3, b | 5) { return a + b }
assert_eq(fn(), 8, "pipe both defaults")
assert_eq(fn(10), 15, "pipe first provided")
assert_eq(fn(10, 20), 30, "pipe both provided")
})
run("default param object literal", function() {
var fn = function(a = {x: 1, y: 2}) { return a.x + a.y }
assert_eq(fn(), 3, "object default")
assert_eq(fn({x: 10, y: 20}), 30, "object provided")
})
run("default param array literal", function() {
var fn = function(a = [10, 20, 30]) { return a[1] }
assert_eq(fn(), 20, "array default")
assert_eq(fn([99]), null, "array provided")
})
run("default param nested object", function() {
var fn = function(a = {inner: {val: 42}}) { return a.inner.val }
assert_eq(fn(), 42, "nested object default")
})
run("default param expression", function() {
var fn = function(a = 1 + 2, b = 3 * 4) { return a + b }
assert_eq(fn(), 15, "expression defaults")
})
run("default param negative number", function() {
var fn = function(a = -1) { return a }
assert_eq(fn(), -1, "negative default")
})
run("default param function call", function() {
var double = function(x) { return x * 2 }
var fn = function(a = double(21)) { return a }
assert_eq(fn(), 42, "function call default")
})
run("default param function value", function() {
var fn = function(f = function() { return 42 }) { return f() }
assert_eq(fn(), 42, "function as default")
})
run("default param arrow function", function() {
var fn = function(f = (x) => x * 2) { return f(5) }
assert_eq(fn(), 10, "arrow function as default")
})
run("default param ternary in default", function() {
var fn = function(a = true ? 10 : 20) { return a }
assert_eq(fn(), 10, "ternary default")
})
run("default param string with comma", function() {
var fn = function(a = "hello, world") { return a }
assert_eq(fn(), "hello, world", "string comma default")
})
run("default param empty object", function() {
var fn = function(a = {}) { return is_object(a) }
assert_eq(fn(), true, "empty object default")
})
run("default param empty array", function() {
var fn = function(a = []) { return is_array(a) }
assert_eq(fn(), true, "empty array default")
})
run("default param multiple complex", function() {
var fn = function(a = {k: 1}, b = [4, 5], c = "hi") {
return text(a.k) + text(b[0]) + c
}
assert_eq(fn(), "14hi", "multiple complex defaults")
assert_eq(fn({k: 9}), "94hi", "first overridden")
assert_eq(fn({k: 9}, [7]), "97hi", "first two overridden")
assert_eq(fn({k: 9}, [7], "yo"), "97yo", "all overridden")
})
run("default param independent defaults", function() {
var fn = function(a = 1, b = 2) { return a + b }
assert_eq(fn(), 3, "both defaults")
assert_eq(fn(10), 12, "first provided")
})
run("default param arrow function syntax", function() {
var fn = (a = 7, b = 8) => a + b
assert_eq(fn(), 15, "arrow defaults")
assert_eq(fn(100), 108, "arrow first provided")
assert_eq(fn(100, 200), 300, "arrow both provided")
})
run("default param single arrow", function() {
var fn = (a = 42) => a
assert_eq(fn(), 42, "single arrow default")
assert_eq(fn(0), 0, "single arrow zero")
})
run("default param method shorthand", function() {
var obj = {
calc(a = 10, b = 20) {
return a + b
}
}
assert_eq(obj.calc(), 30, "method defaults")
assert_eq(obj.calc(1), 21, "method first provided")
assert_eq(obj.calc(1, 2), 3, "method both provided")
})
run("default param each fresh object", function() {
var fn = function(a = {count: 0}) {
a.count = a.count + 1
return a.count
}
assert_eq(fn(), 1, "first call fresh object")
assert_eq(fn(), 1, "second call fresh object")
})
run("default param each fresh array", function() {
var fn = function(a = []) {
a[] = 1
return length(a)
}
assert_eq(fn(), 1, "first call fresh array")
assert_eq(fn(), 1, "second call fresh array")
})
run("default param nested parens", function() {
var fn = function(a = (1 + 2) * 3) { return a }
assert_eq(fn(), 9, "nested parens default")
})
run("default param closure over outer", function() {
var base = 100
var fn = function(a = base) { return a }
assert_eq(fn(), 100, "closure over outer")
base = 200
assert_eq(fn(), 200, "closure sees updated outer")
})
// ============================================================================
// DO-WHILE LOOPS
// ============================================================================
run("do-while basic", function() {
var i = 0
do {
i = i + 1
} while (i < 3)
assert_eq(i, 3, "do-while counted to 3")
})
run("do-while executes body once when false", function() {
var count = 0
do {
count = count + 1
} while (false)
assert_eq(count, 1, "body ran once")
})
run("do-while break", function() {
var i = 0
do {
if (i == 2) break
i = i + 1
} while (i < 10)
assert_eq(i, 2, "break at 2")
})
run("do-while continue", function() {
var sum = 0
var j = 0
do {
j = j + 1
if (j == 3) continue
sum = sum + j
} while (j < 5)
assert_eq(sum, 12, "skip 3: 1+2+4+5")
})
run("do-while nested", function() {
var result = 0
var i = 0
var j = 0
do {
j = 0
do {
result = result + 1
j = j + 1
} while (j < 2)
i = i + 1
} while (i < 3)
assert_eq(result, 6, "3 outer * 2 inner")
})
// ============================================================================
// LABELED BREAK AND CONTINUE
// ============================================================================
run("labeled break exits outer for loop", function() {
var result = 0
var j = 0
var k = 0
outer: for (j = 0; j < 3; j = j + 1) {
for (k = 0; k < 3; k = k + 1) {
if (k == 1) break outer
result = result + 1
}
}
assert_eq(result, 1, "only one iteration before break outer")
assert_eq(j, 0, "outer loop did not advance")
})
run("labeled continue skips to outer iteration", function() {
var result = 0
var a = 0
var b = 0
outer: for (a = 0; a < 3; a = a + 1) {
for (b = 0; b < 3; b = b + 1) {
if (b == 1) continue outer
result = result + 1
}
}
assert_eq(result, 3, "3 outer iters, each runs b=0 only")
})
run("labeled break from while", function() {
var x = 0
top: while (true) {
x = x + 1
if (x == 5) break top
}
assert_eq(x, 5, "broke out at 5")
})
run("labeled break triple nested", function() {
var r = 0
var c = 0
var d = 0
var e = 0
outer: for (c = 0; c < 3; c = c + 1) {
for (d = 0; d < 3; d = d + 1) {
for (e = 0; e < 3; e = e + 1) {
if (e == 1) break outer
r = r + 1
}
}
}
assert_eq(r, 1, "broke out from 3 levels")
})
run("labeled continue with do-while", function() {
var total = 0
var i = 0
var j = 0
outer: for (i = 0; i < 3; i = i + 1) {
j = 0
do {
if (j == 1) continue outer
total = total + 1
j = j + 1
} while (j < 3)
}
assert_eq(total, 3, "3 outer iters, each does j=0 only")
})
// ============================================================================
// SHORTHAND PROPERTY SYNTAX
// ============================================================================
run("shorthand property basic", function() {
var name = "Alice"
var age = 30
var obj = {name, age}
assert_eq(obj.name, "Alice", "name shorthand")
assert_eq(obj.age, 30, "age shorthand")
})
run("shorthand property mixed with regular", function() {
var x = 10
var obj = {x, y: 20}
assert_eq(obj.x, 10, "shorthand x")
assert_eq(obj.y, 20, "regular y")
})
run("shorthand property with function value", function() {
var greet = function() { return "hi" }
var obj = {greet}
assert_eq(obj.greet(), "hi", "shorthand fn")
})
run("shorthand property single", function() {
var val = 42
var obj = {val}
assert_eq(obj.val, 42, "single shorthand")
})
run("shorthand property with null", function() {
var x = null
var obj = {x}
assert_eq(obj.x, null, "null shorthand")
})
run("shorthand property many", function() {
var a = 1
var b = 2
var c = 3
var d = 4
var e = 5
var f = 6
var g = 7
var h = 8
var i = 9
var j = 10
var obj = {a, b, c, d, e, f, g, h, i, j}
assert_eq(obj.a, 1, "a")
assert_eq(obj.e, 5, "e")
assert_eq(obj.j, 10, "j")
})
run("shorthand property with method", function() {
var z = 300
var obj = {z, double(n) { return n * 2 }}
assert_eq(obj.z, 300, "shorthand z")
assert_eq(obj.double(5), 10, "method double")
})
run("shorthand property is a snapshot", function() {
var val = "original"
var obj = {val}
val = "changed"
assert_eq(obj.val, "original", "object keeps original")
})
// ============================================================================
// SHARED CLOSURES
// ============================================================================
run("closures share captured variable", function() {
var x = 0
var inc = function() { x = x + 1 }
var get = function() { return x }
inc()
assert_eq(get(), 1, "after one inc")
inc()
assert_eq(get(), 2, "after two incs")
})
run("closure factory returns shared state", function() {
var make = function() {
var count = 0
return {
inc: function() { count = count + 1 },
get: function() { return count }
}
}
var c = make()
c.inc()
c.inc()
c.inc()
assert_eq(c.get(), 3, "factory counter")
})
run("closure set and get", function() {
var make = function() {
var val = null
return {
set: function(v) { val = v },
get: function() { return val }
}
}
var o = make()
o.set("hello")
assert_eq(o.get(), "hello", "set then get")
o.set(42)
assert_eq(o.get(), 42, "overwrite")
})
run("closure write heap values visible to outer scope", function() {
var a = null
var b = null
var c = null
var d = null
var e = null
var f1 = function() { a = 42 }
var f2 = function() { b = true }
var f3 = function() { c = "hello" }
var f4 = function() { d = {x: 1} }
var f5 = function() { e = [1, 2] }
f1()
f2()
f3()
f4()
f5()
assert_eq(a, 42, "closure write number")
assert_eq(b, true, "closure write boolean")
assert_eq(c != null, true, "closure write text not null")
assert_eq(c, "hello", "closure write text value")
assert_eq(d != null, true, "closure write object not null")
assert_eq(d.x, 1, "closure write object property")
assert_eq(e != null, true, "closure write array not null")
assert_eq(e[0], 1, "closure write array element")
})
// ============================================================================
// STRING COMPARISON OPERATORS
// ============================================================================
run("string less than", function() {
assert_eq("apple" < "banana", true, "apple < banana")
assert_eq("banana" < "apple", false, "banana < apple")
assert_eq("abc" < "abd", true, "abc < abd")
})
run("string greater than", function() {
assert_eq("banana" > "apple", true, "banana > apple")
assert_eq("apple" > "banana", false, "apple > banana")
})
run("string less than or equal", function() {
assert_eq("abc" <= "abc", true, "equal strings")
assert_eq("abc" <= "abd", true, "abc <= abd")
assert_eq("abd" <= "abc", false, "abd <= abc")
})
run("string greater than or equal", function() {
assert_eq("abc" >= "abc", true, "equal strings")
assert_eq("abd" >= "abc", true, "abd >= abc")
})
// ============================================================================
// CROSS-TYPE STRICT EQUALITY
// ============================================================================
run("strict equality different types", function() {
assert_eq(5 == "5", false, "number != string")
assert_eq(true == 1, false, "bool != number")
assert_eq(false == 0, false, "false != zero")
assert_eq(null == false, false, "null != false")
assert_eq("" == false, false, "empty string != false")
assert_eq(0 == null, false, "zero != null")
})
// ============================================================================
// COMPOUND ASSIGNMENT OPERATORS (&&= and ||=)
// ============================================================================
run("logical and assign", function() {
var x = true
x &&= false
assert_eq(x, false, "true &&= false")
var y = false
y &&= true
assert_eq(y, false, "false &&= true")
})
run("logical or assign", function() {
var x = false
x ||= true
assert_eq(x, true, "false ||= true")
var y = true
y ||= false
assert_eq(y, true, "true ||= false")
})
// ============================================================================
// EVERY/SOME ON EMPTY ARRAYS
// ============================================================================
run("every on empty array", function() {
var result = every([], function(x) { return false })
assert_eq(result, true, "vacuous truth")
})
run("some on empty array", function() {
var result = some([], function(x) { return true })
assert_eq(result, false, "no elements match")
})
// ============================================================================
// CHAINED METHOD CALLS
// ============================================================================
run("chained method calls", function() {
var make_chain = function() {
var obj = {
val: 0,
add(n) {
obj.val = obj.val + n
return obj
}
}
return obj
}
var c = make_chain()
var result = c.add(1).add(2).add(3).val
assert_eq(result, 6, "1+2+3 chained")
})
// ============================================================================
// TEMPLATE LITERALS WITH EXPRESSIONS
// ============================================================================
run("template literal arithmetic", function() {
var a = 3
var b = 4
assert_eq(`${a + b}`, "7", "addition in template")
})
run("template literal nested calls", function() {
var double = function(x) { return x * 2 }
assert_eq(`result: ${double(5)}`, "result: 10", "fn call in template")
})
// ============================================================================
// COMMA-SEPARATED DECLARATIONS
// ============================================================================
run("comma separated var declarations", function() {
var a = 1, b = 2, c = 3
assert_eq(a, 1, "first")
assert_eq(b, 2, "second")
assert_eq(c, 3, "third")
})
run("comma separated def declarations", function() {
def x = 10, y = 20
assert_eq(x, 10, "def first")
assert_eq(y, 20, "def second")
})
// ============================================================================
// DEF CONSTANTS
// ============================================================================
run("def prevents reassignment", function() {
var caught = false
def x = 42
assert_eq(x, 42, "def value")
// reassignment should disrupt
})
// ============================================================================
// RECURSIVE CLOSURES
// ============================================================================
run("recursive closure fibonacci", function() {
var fib = function(n) {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
}
assert_eq(fib(0), 0, "fib(0)")
assert_eq(fib(1), 1, "fib(1)")
assert_eq(fib(6), 8, "fib(6)")
assert_eq(fib(10), 55, "fib(10)")
})
// ============================================================================
// KEYWORD PROPERTY NAMES
// ============================================================================
run("keyword property names", function() {
var obj = {if: 1, while: 2, return: 3}
assert_eq(obj.if, 1, "if prop")
assert_eq(obj.while, 2, "while prop")
assert_eq(obj.return, 3, "return prop")
})
// ============================================================================
// NESTED RETURN FROM IF/ELSE
// ============================================================================
run("nested return in if-else chain", function() {
var classify = function(n) {
if (n < 0) {
return "negative"
} else if (n == 0) {
return "zero"
} else {
return "positive"
}
}
assert_eq(classify(-5), "negative", "negative")
assert_eq(classify(0), "zero", "zero")
assert_eq(classify(10), "positive", "positive")
})
// ============================================================================
// IIFE (IMMEDIATELY INVOKED FUNCTION EXPRESSION)
// ============================================================================
run("IIFE with arguments", function() {
var result = (function(a, b) { return a + b })(10, 20)
assert_eq(result, 30, "IIFE sum")
})
// ============================================================================
// PATHOLOGICAL OBJECT LITERALS - Diagnose large object key/value limits
// ============================================================================
// Test: object with 100 simple keys (number values)
run("object literal 100 number keys", function() {
var obj = {
k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9,
k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19,
k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29,
k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39,
k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49,
k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59,
k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69,
k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79,
k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89,
k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99
}
var keys = array(obj)
assert_eq(length(keys), 100, "should have 100 keys")
assert_eq(obj.k000, 0, "first key")
assert_eq(obj.k099, 99, "last key")
})
// Test: object with 200 simple keys (number values)
run("object literal 200 number keys", function() {
var obj = {
k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9,
k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19,
k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29,
k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39,
k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49,
k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59,
k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69,
k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79,
k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89,
k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99,
k100: 100, k101: 101, k102: 102, k103: 103, k104: 104, k105: 105, k106: 106, k107: 107, k108: 108, k109: 109,
k110: 110, k111: 111, k112: 112, k113: 113, k114: 114, k115: 115, k116: 116, k117: 117, k118: 118, k119: 119,
k120: 120, k121: 121, k122: 122, k123: 123, k124: 124, k125: 125, k126: 126, k127: 127, k128: 128, k129: 129,
k130: 130, k131: 131, k132: 132, k133: 133, k134: 134, k135: 135, k136: 136, k137: 137, k138: 138, k139: 139,
k140: 140, k141: 141, k142: 142, k143: 143, k144: 144, k145: 145, k146: 146, k147: 147, k148: 148, k149: 149,
k150: 150, k151: 151, k152: 152, k153: 153, k154: 154, k155: 155, k156: 156, k157: 157, k158: 158, k159: 159,
k160: 160, k161: 161, k162: 162, k163: 163, k164: 164, k165: 165, k166: 166, k167: 167, k168: 168, k169: 169,
k170: 170, k171: 171, k172: 172, k173: 173, k174: 174, k175: 175, k176: 176, k177: 177, k178: 178, k179: 179,
k180: 180, k181: 181, k182: 182, k183: 183, k184: 184, k185: 185, k186: 186, k187: 187, k188: 188, k189: 189,
k190: 190, k191: 191, k192: 192, k193: 193, k194: 194, k195: 195, k196: 196, k197: 197, k198: 198, k199: 199
}
var keys = array(obj)
assert_eq(length(keys), 200, "should have 200 keys")
assert_eq(obj.k000, 0, "first key")
assert_eq(obj.k199, 199, "last key")
})
// Test: object with 256 simple keys (number values) - exact boundary
run("object literal 256 number keys", function() {
var obj = {
k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9,
k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19,
k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29,
k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39,
k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49,
k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59,
k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69,
k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79,
k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89,
k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99,
k100: 100, k101: 101, k102: 102, k103: 103, k104: 104, k105: 105, k106: 106, k107: 107, k108: 108, k109: 109,
k110: 110, k111: 111, k112: 112, k113: 113, k114: 114, k115: 115, k116: 116, k117: 117, k118: 118, k119: 119,
k120: 120, k121: 121, k122: 122, k123: 123, k124: 124, k125: 125, k126: 126, k127: 127, k128: 128, k129: 129,
k130: 130, k131: 131, k132: 132, k133: 133, k134: 134, k135: 135, k136: 136, k137: 137, k138: 138, k139: 139,
k140: 140, k141: 141, k142: 142, k143: 143, k144: 144, k145: 145, k146: 146, k147: 147, k148: 148, k149: 149,
k150: 150, k151: 151, k152: 152, k153: 153, k154: 154, k155: 155, k156: 156, k157: 157, k158: 158, k159: 159,
k160: 160, k161: 161, k162: 162, k163: 163, k164: 164, k165: 165, k166: 166, k167: 167, k168: 168, k169: 169,
k170: 170, k171: 171, k172: 172, k173: 173, k174: 174, k175: 175, k176: 176, k177: 177, k178: 178, k179: 179,
k180: 180, k181: 181, k182: 182, k183: 183, k184: 184, k185: 185, k186: 186, k187: 187, k188: 188, k189: 189,
k190: 190, k191: 191, k192: 192, k193: 193, k194: 194, k195: 195, k196: 196, k197: 197, k198: 198, k199: 199,
k200: 200, k201: 201, k202: 202, k203: 203, k204: 204, k205: 205, k206: 206, k207: 207, k208: 208, k209: 209,
k210: 210, k211: 211, k212: 212, k213: 213, k214: 214, k215: 215, k216: 216, k217: 217, k218: 218, k219: 219,
k220: 220, k221: 221, k222: 222, k223: 223, k224: 224, k225: 225, k226: 226, k227: 227, k228: 228, k229: 229,
k230: 230, k231: 231, k232: 232, k233: 233, k234: 234, k235: 235, k236: 236, k237: 237, k238: 238, k239: 239,
k240: 240, k241: 241, k242: 242, k243: 243, k244: 244, k245: 245, k246: 246, k247: 247, k248: 248, k249: 249,
k250: 250, k251: 251, k252: 252, k253: 253, k254: 254, k255: 255
}
var keys = array(obj)
assert_eq(length(keys), 256, "should have 256 keys")
assert_eq(obj.k000, 0, "first key")
assert_eq(obj.k255, 255, "last key")
})
// Test: object with 257 keys - just past 256 boundary
run("object literal 257 number keys", function() {
var obj = {
k000: 0, k001: 1, k002: 2, k003: 3, k004: 4, k005: 5, k006: 6, k007: 7, k008: 8, k009: 9,
k010: 10, k011: 11, k012: 12, k013: 13, k014: 14, k015: 15, k016: 16, k017: 17, k018: 18, k019: 19,
k020: 20, k021: 21, k022: 22, k023: 23, k024: 24, k025: 25, k026: 26, k027: 27, k028: 28, k029: 29,
k030: 30, k031: 31, k032: 32, k033: 33, k034: 34, k035: 35, k036: 36, k037: 37, k038: 38, k039: 39,
k040: 40, k041: 41, k042: 42, k043: 43, k044: 44, k045: 45, k046: 46, k047: 47, k048: 48, k049: 49,
k050: 50, k051: 51, k052: 52, k053: 53, k054: 54, k055: 55, k056: 56, k057: 57, k058: 58, k059: 59,
k060: 60, k061: 61, k062: 62, k063: 63, k064: 64, k065: 65, k066: 66, k067: 67, k068: 68, k069: 69,
k070: 70, k071: 71, k072: 72, k073: 73, k074: 74, k075: 75, k076: 76, k077: 77, k078: 78, k079: 79,
k080: 80, k081: 81, k082: 82, k083: 83, k084: 84, k085: 85, k086: 86, k087: 87, k088: 88, k089: 89,
k090: 90, k091: 91, k092: 92, k093: 93, k094: 94, k095: 95, k096: 96, k097: 97, k098: 98, k099: 99,
k100: 100, k101: 101, k102: 102, k103: 103, k104: 104, k105: 105, k106: 106, k107: 107, k108: 108, k109: 109,
k110: 110, k111: 111, k112: 112, k113: 113, k114: 114, k115: 115, k116: 116, k117: 117, k118: 118, k119: 119,
k120: 120, k121: 121, k122: 122, k123: 123, k124: 124, k125: 125, k126: 126, k127: 127, k128: 128, k129: 129,
k130: 130, k131: 131, k132: 132, k133: 133, k134: 134, k135: 135, k136: 136, k137: 137, k138: 138, k139: 139,
k140: 140, k141: 141, k142: 142, k143: 143, k144: 144, k145: 145, k146: 146, k147: 147, k148: 148, k149: 149,
k150: 150, k151: 151, k152: 152, k153: 153, k154: 154, k155: 155, k156: 156, k157: 157, k158: 158, k159: 159,
k160: 160, k161: 161, k162: 162, k163: 163, k164: 164, k165: 165, k166: 166, k167: 167, k168: 168, k169: 169,
k170: 170, k171: 171, k172: 172, k173: 173, k174: 174, k175: 175, k176: 176, k177: 177, k178: 178, k179: 179,
k180: 180, k181: 181, k182: 182, k183: 183, k184: 184, k185: 185, k186: 186, k187: 187, k188: 188, k189: 189,
k190: 190, k191: 191, k192: 192, k193: 193, k194: 194, k195: 195, k196: 196, k197: 197, k198: 198, k199: 199,
k200: 200, k201: 201, k202: 202, k203: 203, k204: 204, k205: 205, k206: 206, k207: 207, k208: 208, k209: 209,
k210: 210, k211: 211, k212: 212, k213: 213, k214: 214, k215: 215, k216: 216, k217: 217, k218: 218, k219: 219,
k220: 220, k221: 221, k222: 222, k223: 223, k224: 224, k225: 225, k226: 226, k227: 227, k228: 228, k229: 229,
k230: 230, k231: 231, k232: 232, k233: 233, k234: 234, k235: 235, k236: 236, k237: 237, k238: 238, k239: 239,
k240: 240, k241: 241, k242: 242, k243: 243, k244: 244, k245: 245, k246: 246, k247: 247, k248: 248, k249: 249,
k250: 250, k251: 251, k252: 252, k253: 253, k254: 254, k255: 255, k256: 256
}
var keys = array(obj)
assert_eq(length(keys), 257, "should have 257 keys")
assert_eq(obj.k000, 0, "first key")
assert_eq(obj.k256, 256, "last key")
})
// Test: object with 100 function values
run("object literal 100 function values", function() {
var obj = {
f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 },
f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 },
f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 },
f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 },
f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 },
f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 },
f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 },
f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 },
f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 },
f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 },
f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 },
f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 },
f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 },
f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 },
f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 },
f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 },
f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 },
f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 },
f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 },
f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 }
}
var keys = array(obj)
var i = 0
var bad_count = 0
assert_eq(length(keys), 100, "should have 100 keys")
for (i = 0; i < length(keys); i++) {
if (!is_function(obj[keys[i]])) {
bad_count = bad_count + 1
}
}
assert_eq(bad_count, 0, "all 100 values should be functions")
assert_eq(obj.f000(), 0, "first fn returns 0")
assert_eq(obj.f099(), 99, "last fn returns 99")
})
// Test: object with 256 function values - exact boundary
run("object literal 256 function values", function() {
var obj = {
f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 },
f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 },
f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 },
f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 },
f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 },
f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 },
f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 },
f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 },
f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 },
f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 },
f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 },
f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 },
f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 },
f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 },
f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 },
f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 },
f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 },
f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 },
f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 },
f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 },
f100: function() { return 100 }, f101: function() { return 101 }, f102: function() { return 102 }, f103: function() { return 103 }, f104: function() { return 104 },
f105: function() { return 105 }, f106: function() { return 106 }, f107: function() { return 107 }, f108: function() { return 108 }, f109: function() { return 109 },
f110: function() { return 110 }, f111: function() { return 111 }, f112: function() { return 112 }, f113: function() { return 113 }, f114: function() { return 114 },
f115: function() { return 115 }, f116: function() { return 116 }, f117: function() { return 117 }, f118: function() { return 118 }, f119: function() { return 119 },
f120: function() { return 120 }, f121: function() { return 121 }, f122: function() { return 122 }, f123: function() { return 123 }, f124: function() { return 124 },
f125: function() { return 125 }, f126: function() { return 126 }, f127: function() { return 127 }, f128: function() { return 128 }, f129: function() { return 129 },
f130: function() { return 130 }, f131: function() { return 131 }, f132: function() { return 132 }, f133: function() { return 133 }, f134: function() { return 134 },
f135: function() { return 135 }, f136: function() { return 136 }, f137: function() { return 137 }, f138: function() { return 138 }, f139: function() { return 139 },
f140: function() { return 140 }, f141: function() { return 141 }, f142: function() { return 142 }, f143: function() { return 143 }, f144: function() { return 144 },
f145: function() { return 145 }, f146: function() { return 146 }, f147: function() { return 147 }, f148: function() { return 148 }, f149: function() { return 149 },
f150: function() { return 150 }, f151: function() { return 151 }, f152: function() { return 152 }, f153: function() { return 153 }, f154: function() { return 154 },
f155: function() { return 155 }, f156: function() { return 156 }, f157: function() { return 157 }, f158: function() { return 158 }, f159: function() { return 159 },
f160: function() { return 160 }, f161: function() { return 161 }, f162: function() { return 162 }, f163: function() { return 163 }, f164: function() { return 164 },
f165: function() { return 165 }, f166: function() { return 166 }, f167: function() { return 167 }, f168: function() { return 168 }, f169: function() { return 169 },
f170: function() { return 170 }, f171: function() { return 171 }, f172: function() { return 172 }, f173: function() { return 173 }, f174: function() { return 174 },
f175: function() { return 175 }, f176: function() { return 176 }, f177: function() { return 177 }, f178: function() { return 178 }, f179: function() { return 179 },
f180: function() { return 180 }, f181: function() { return 181 }, f182: function() { return 182 }, f183: function() { return 183 }, f184: function() { return 184 },
f185: function() { return 185 }, f186: function() { return 186 }, f187: function() { return 187 }, f188: function() { return 188 }, f189: function() { return 189 },
f190: function() { return 190 }, f191: function() { return 191 }, f192: function() { return 192 }, f193: function() { return 193 }, f194: function() { return 194 },
f195: function() { return 195 }, f196: function() { return 196 }, f197: function() { return 197 }, f198: function() { return 198 }, f199: function() { return 199 },
f200: function() { return 200 }, f201: function() { return 201 }, f202: function() { return 202 }, f203: function() { return 203 }, f204: function() { return 204 },
f205: function() { return 205 }, f206: function() { return 206 }, f207: function() { return 207 }, f208: function() { return 208 }, f209: function() { return 209 },
f210: function() { return 210 }, f211: function() { return 211 }, f212: function() { return 212 }, f213: function() { return 213 }, f214: function() { return 214 },
f215: function() { return 215 }, f216: function() { return 216 }, f217: function() { return 217 }, f218: function() { return 218 }, f219: function() { return 219 },
f220: function() { return 220 }, f221: function() { return 221 }, f222: function() { return 222 }, f223: function() { return 223 }, f224: function() { return 224 },
f225: function() { return 225 }, f226: function() { return 226 }, f227: function() { return 227 }, f228: function() { return 228 }, f229: function() { return 229 },
f230: function() { return 230 }, f231: function() { return 231 }, f232: function() { return 232 }, f233: function() { return 233 }, f234: function() { return 234 },
f235: function() { return 235 }, f236: function() { return 236 }, f237: function() { return 237 }, f238: function() { return 238 }, f239: function() { return 239 },
f240: function() { return 240 }, f241: function() { return 241 }, f242: function() { return 242 }, f243: function() { return 243 }, f244: function() { return 244 },
f245: function() { return 245 }, f246: function() { return 246 }, f247: function() { return 247 }, f248: function() { return 248 }, f249: function() { return 249 },
f250: function() { return 250 }, f251: function() { return 251 }, f252: function() { return 252 }, f253: function() { return 253 }, f254: function() { return 254 },
f255: function() { return 255 }
}
var keys = array(obj)
var i = 0
var bad_count = 0
var first_bad = ""
assert_eq(length(keys), 256, "should have 256 keys")
for (i = 0; i < length(keys); i++) {
if (!is_function(obj[keys[i]])) {
if (first_bad == "") {
first_bad = keys[i]
}
bad_count = bad_count + 1
}
}
if (bad_count > 0) {
fail(text(bad_count) + " of 256 values not functions, first bad: " + first_bad)
}
})
// Test: object with 300 function values - well past boundary
run("object literal 300 function values", function() {
var obj = {
f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 },
f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 },
f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 },
f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 },
f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 },
f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 },
f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 },
f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 },
f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 },
f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 },
f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 },
f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 },
f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 },
f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 },
f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 },
f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 },
f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 },
f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 },
f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 },
f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 },
f100: function() { return 100 }, f101: function() { return 101 }, f102: function() { return 102 }, f103: function() { return 103 }, f104: function() { return 104 },
f105: function() { return 105 }, f106: function() { return 106 }, f107: function() { return 107 }, f108: function() { return 108 }, f109: function() { return 109 },
f110: function() { return 110 }, f111: function() { return 111 }, f112: function() { return 112 }, f113: function() { return 113 }, f114: function() { return 114 },
f115: function() { return 115 }, f116: function() { return 116 }, f117: function() { return 117 }, f118: function() { return 118 }, f119: function() { return 119 },
f120: function() { return 120 }, f121: function() { return 121 }, f122: function() { return 122 }, f123: function() { return 123 }, f124: function() { return 124 },
f125: function() { return 125 }, f126: function() { return 126 }, f127: function() { return 127 }, f128: function() { return 128 }, f129: function() { return 129 },
f130: function() { return 130 }, f131: function() { return 131 }, f132: function() { return 132 }, f133: function() { return 133 }, f134: function() { return 134 },
f135: function() { return 135 }, f136: function() { return 136 }, f137: function() { return 137 }, f138: function() { return 138 }, f139: function() { return 139 },
f140: function() { return 140 }, f141: function() { return 141 }, f142: function() { return 142 }, f143: function() { return 143 }, f144: function() { return 144 },
f145: function() { return 145 }, f146: function() { return 146 }, f147: function() { return 147 }, f148: function() { return 148 }, f149: function() { return 149 },
f150: function() { return 150 }, f151: function() { return 151 }, f152: function() { return 152 }, f153: function() { return 153 }, f154: function() { return 154 },
f155: function() { return 155 }, f156: function() { return 156 }, f157: function() { return 157 }, f158: function() { return 158 }, f159: function() { return 159 },
f160: function() { return 160 }, f161: function() { return 161 }, f162: function() { return 162 }, f163: function() { return 163 }, f164: function() { return 164 },
f165: function() { return 165 }, f166: function() { return 166 }, f167: function() { return 167 }, f168: function() { return 168 }, f169: function() { return 169 },
f170: function() { return 170 }, f171: function() { return 171 }, f172: function() { return 172 }, f173: function() { return 173 }, f174: function() { return 174 },
f175: function() { return 175 }, f176: function() { return 176 }, f177: function() { return 177 }, f178: function() { return 178 }, f179: function() { return 179 },
f180: function() { return 180 }, f181: function() { return 181 }, f182: function() { return 182 }, f183: function() { return 183 }, f184: function() { return 184 },
f185: function() { return 185 }, f186: function() { return 186 }, f187: function() { return 187 }, f188: function() { return 188 }, f189: function() { return 189 },
f190: function() { return 190 }, f191: function() { return 191 }, f192: function() { return 192 }, f193: function() { return 193 }, f194: function() { return 194 },
f195: function() { return 195 }, f196: function() { return 196 }, f197: function() { return 197 }, f198: function() { return 198 }, f199: function() { return 199 },
f200: function() { return 200 }, f201: function() { return 201 }, f202: function() { return 202 }, f203: function() { return 203 }, f204: function() { return 204 },
f205: function() { return 205 }, f206: function() { return 206 }, f207: function() { return 207 }, f208: function() { return 208 }, f209: function() { return 209 },
f210: function() { return 210 }, f211: function() { return 211 }, f212: function() { return 212 }, f213: function() { return 213 }, f214: function() { return 214 },
f215: function() { return 215 }, f216: function() { return 216 }, f217: function() { return 217 }, f218: function() { return 218 }, f219: function() { return 219 },
f220: function() { return 220 }, f221: function() { return 221 }, f222: function() { return 222 }, f223: function() { return 223 }, f224: function() { return 224 },
f225: function() { return 225 }, f226: function() { return 226 }, f227: function() { return 227 }, f228: function() { return 228 }, f229: function() { return 229 },
f230: function() { return 230 }, f231: function() { return 231 }, f232: function() { return 232 }, f233: function() { return 233 }, f234: function() { return 234 },
f235: function() { return 235 }, f236: function() { return 236 }, f237: function() { return 237 }, f238: function() { return 238 }, f239: function() { return 239 },
f240: function() { return 240 }, f241: function() { return 241 }, f242: function() { return 242 }, f243: function() { return 243 }, f244: function() { return 244 },
f245: function() { return 245 }, f246: function() { return 246 }, f247: function() { return 247 }, f248: function() { return 248 }, f249: function() { return 249 },
f250: function() { return 250 }, f251: function() { return 251 }, f252: function() { return 252 }, f253: function() { return 253 }, f254: function() { return 254 },
f255: function() { return 255 }, f256: function() { return 256 }, f257: function() { return 257 }, f258: function() { return 258 }, f259: function() { return 259 },
f260: function() { return 260 }, f261: function() { return 261 }, f262: function() { return 262 }, f263: function() { return 263 }, f264: function() { return 264 },
f265: function() { return 265 }, f266: function() { return 266 }, f267: function() { return 267 }, f268: function() { return 268 }, f269: function() { return 269 },
f270: function() { return 270 }, f271: function() { return 271 }, f272: function() { return 272 }, f273: function() { return 273 }, f274: function() { return 274 },
f275: function() { return 275 }, f276: function() { return 276 }, f277: function() { return 277 }, f278: function() { return 278 }, f279: function() { return 279 },
f280: function() { return 280 }, f281: function() { return 281 }, f282: function() { return 282 }, f283: function() { return 283 }, f284: function() { return 284 },
f285: function() { return 285 }, f286: function() { return 286 }, f287: function() { return 287 }, f288: function() { return 288 }, f289: function() { return 289 },
f290: function() { return 290 }, f291: function() { return 291 }, f292: function() { return 292 }, f293: function() { return 293 }, f294: function() { return 294 },
f295: function() { return 295 }, f296: function() { return 296 }, f297: function() { return 297 }, f298: function() { return 298 }, f299: function() { return 299 }
}
var keys = array(obj)
var i = 0
var bad_count = 0
var first_bad = ""
assert_eq(length(keys), 300, "should have 300 keys")
for (i = 0; i < length(keys); i++) {
if (!is_function(obj[keys[i]])) {
if (first_bad == "") {
first_bad = keys[i]
}
bad_count = bad_count + 1
}
}
if (bad_count > 0) {
fail(text(bad_count) + " of 300 values not functions, first bad: " + first_bad)
}
})
// Test: object built incrementally (not literal) with 300 keys
run("object incremental 300 number keys", function() {
var obj = {}
var i = 0
for (i = 0; i < 300; i++) {
obj["k" + text(i)] = i
}
var keys = array(obj)
assert_eq(length(keys), 300, "should have 300 keys")
assert_eq(obj.k0, 0, "first key")
assert_eq(obj.k299, 299, "last key")
})
// Test: object built incrementally with 300 function values
run("object incremental 300 function values", function() {
var obj = {}
var i = 0
var make_fn = function(n) { return function() { return n } }
for (i = 0; i < 300; i++) {
obj["f" + text(i)] = make_fn(i)
}
var keys = array(obj)
var bad_count = 0
assert_eq(length(keys), 300, "should have 300 keys")
for (i = 0; i < length(keys); i++) {
if (!is_function(obj[keys[i]])) {
bad_count = bad_count + 1
}
}
assert_eq(bad_count, 0, "all 300 values should be functions")
assert_eq(obj.f0(), 0, "first fn")
assert_eq(obj.f299(), 299, "last fn")
})
// Test: object with very long key names
run("object literal long key names", function() {
var obj = {
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_01: 1,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_02: 2,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_03: 3,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_04: 4,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_05: 5,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_06: 6,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_07: 7,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_08: 8,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_09: 9,
this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_10: 10
}
var keys = array(obj)
assert_eq(length(keys), 10, "should have 10 keys")
assert_eq(obj.this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_01, 1, "long key 1")
assert_eq(obj.this_is_a_really_long_key_name_that_tests_whether_long_identifiers_cause_issues_key_10, 10, "long key 10")
})
// Test: return object literal from function with many keys
run("object literal 300 function values returned from function", function() {
var make_obj = function() {
return {
f000: function() { return 0 }, f001: function() { return 1 }, f002: function() { return 2 }, f003: function() { return 3 }, f004: function() { return 4 },
f005: function() { return 5 }, f006: function() { return 6 }, f007: function() { return 7 }, f008: function() { return 8 }, f009: function() { return 9 },
f010: function() { return 10 }, f011: function() { return 11 }, f012: function() { return 12 }, f013: function() { return 13 }, f014: function() { return 14 },
f015: function() { return 15 }, f016: function() { return 16 }, f017: function() { return 17 }, f018: function() { return 18 }, f019: function() { return 19 },
f020: function() { return 20 }, f021: function() { return 21 }, f022: function() { return 22 }, f023: function() { return 23 }, f024: function() { return 24 },
f025: function() { return 25 }, f026: function() { return 26 }, f027: function() { return 27 }, f028: function() { return 28 }, f029: function() { return 29 },
f030: function() { return 30 }, f031: function() { return 31 }, f032: function() { return 32 }, f033: function() { return 33 }, f034: function() { return 34 },
f035: function() { return 35 }, f036: function() { return 36 }, f037: function() { return 37 }, f038: function() { return 38 }, f039: function() { return 39 },
f040: function() { return 40 }, f041: function() { return 41 }, f042: function() { return 42 }, f043: function() { return 43 }, f044: function() { return 44 },
f045: function() { return 45 }, f046: function() { return 46 }, f047: function() { return 47 }, f048: function() { return 48 }, f049: function() { return 49 },
f050: function() { return 50 }, f051: function() { return 51 }, f052: function() { return 52 }, f053: function() { return 53 }, f054: function() { return 54 },
f055: function() { return 55 }, f056: function() { return 56 }, f057: function() { return 57 }, f058: function() { return 58 }, f059: function() { return 59 },
f060: function() { return 60 }, f061: function() { return 61 }, f062: function() { return 62 }, f063: function() { return 63 }, f064: function() { return 64 },
f065: function() { return 65 }, f066: function() { return 66 }, f067: function() { return 67 }, f068: function() { return 68 }, f069: function() { return 69 },
f070: function() { return 70 }, f071: function() { return 71 }, f072: function() { return 72 }, f073: function() { return 73 }, f074: function() { return 74 },
f075: function() { return 75 }, f076: function() { return 76 }, f077: function() { return 77 }, f078: function() { return 78 }, f079: function() { return 79 },
f080: function() { return 80 }, f081: function() { return 81 }, f082: function() { return 82 }, f083: function() { return 83 }, f084: function() { return 84 },
f085: function() { return 85 }, f086: function() { return 86 }, f087: function() { return 87 }, f088: function() { return 88 }, f089: function() { return 89 },
f090: function() { return 90 }, f091: function() { return 91 }, f092: function() { return 92 }, f093: function() { return 93 }, f094: function() { return 94 },
f095: function() { return 95 }, f096: function() { return 96 }, f097: function() { return 97 }, f098: function() { return 98 }, f099: function() { return 99 },
f100: function() { return 100 }, f101: function() { return 101 }, f102: function() { return 102 }, f103: function() { return 103 }, f104: function() { return 104 },
f105: function() { return 105 }, f106: function() { return 106 }, f107: function() { return 107 }, f108: function() { return 108 }, f109: function() { return 109 },
f110: function() { return 110 }, f111: function() { return 111 }, f112: function() { return 112 }, f113: function() { return 113 }, f114: function() { return 114 },
f115: function() { return 115 }, f116: function() { return 116 }, f117: function() { return 117 }, f118: function() { return 118 }, f119: function() { return 119 },
f120: function() { return 120 }, f121: function() { return 121 }, f122: function() { return 122 }, f123: function() { return 123 }, f124: function() { return 124 },
f125: function() { return 125 }, f126: function() { return 126 }, f127: function() { return 127 }, f128: function() { return 128 }, f129: function() { return 129 },
f130: function() { return 130 }, f131: function() { return 131 }, f132: function() { return 132 }, f133: function() { return 133 }, f134: function() { return 134 },
f135: function() { return 135 }, f136: function() { return 136 }, f137: function() { return 137 }, f138: function() { return 138 }, f139: function() { return 139 },
f140: function() { return 140 }, f141: function() { return 141 }, f142: function() { return 142 }, f143: function() { return 143 }, f144: function() { return 144 },
f145: function() { return 145 }, f146: function() { return 146 }, f147: function() { return 147 }, f148: function() { return 148 }, f149: function() { return 149 },
f150: function() { return 150 }, f151: function() { return 151 }, f152: function() { return 152 }, f153: function() { return 153 }, f154: function() { return 154 },
f155: function() { return 155 }, f156: function() { return 156 }, f157: function() { return 157 }, f158: function() { return 158 }, f159: function() { return 159 },
f160: function() { return 160 }, f161: function() { return 161 }, f162: function() { return 162 }, f163: function() { return 163 }, f164: function() { return 164 },
f165: function() { return 165 }, f166: function() { return 166 }, f167: function() { return 167 }, f168: function() { return 168 }, f169: function() { return 169 },
f170: function() { return 170 }, f171: function() { return 171 }, f172: function() { return 172 }, f173: function() { return 173 }, f174: function() { return 174 },
f175: function() { return 175 }, f176: function() { return 176 }, f177: function() { return 177 }, f178: function() { return 178 }, f179: function() { return 179 },
f180: function() { return 180 }, f181: function() { return 181 }, f182: function() { return 182 }, f183: function() { return 183 }, f184: function() { return 184 },
f185: function() { return 185 }, f186: function() { return 186 }, f187: function() { return 187 }, f188: function() { return 188 }, f189: function() { return 189 },
f190: function() { return 190 }, f191: function() { return 191 }, f192: function() { return 192 }, f193: function() { return 193 }, f194: function() { return 194 },
f195: function() { return 195 }, f196: function() { return 196 }, f197: function() { return 197 }, f198: function() { return 198 }, f199: function() { return 199 },
f200: function() { return 200 }, f201: function() { return 201 }, f202: function() { return 202 }, f203: function() { return 203 }, f204: function() { return 204 },
f205: function() { return 205 }, f206: function() { return 206 }, f207: function() { return 207 }, f208: function() { return 208 }, f209: function() { return 209 },
f210: function() { return 210 }, f211: function() { return 211 }, f212: function() { return 212 }, f213: function() { return 213 }, f214: function() { return 214 },
f215: function() { return 215 }, f216: function() { return 216 }, f217: function() { return 217 }, f218: function() { return 218 }, f219: function() { return 219 },
f220: function() { return 220 }, f221: function() { return 221 }, f222: function() { return 222 }, f223: function() { return 223 }, f224: function() { return 224 },
f225: function() { return 225 }, f226: function() { return 226 }, f227: function() { return 227 }, f228: function() { return 228 }, f229: function() { return 229 },
f230: function() { return 230 }, f231: function() { return 231 }, f232: function() { return 232 }, f233: function() { return 233 }, f234: function() { return 234 },
f235: function() { return 235 }, f236: function() { return 236 }, f237: function() { return 237 }, f238: function() { return 238 }, f239: function() { return 239 },
f240: function() { return 240 }, f241: function() { return 241 }, f242: function() { return 242 }, f243: function() { return 243 }, f244: function() { return 244 },
f245: function() { return 245 }, f246: function() { return 246 }, f247: function() { return 247 }, f248: function() { return 248 }, f249: function() { return 249 },
f250: function() { return 250 }, f251: function() { return 251 }, f252: function() { return 252 }, f253: function() { return 253 }, f254: function() { return 254 },
f255: function() { return 255 }, f256: function() { return 256 }, f257: function() { return 257 }, f258: function() { return 258 }, f259: function() { return 259 },
f260: function() { return 260 }, f261: function() { return 261 }, f262: function() { return 262 }, f263: function() { return 263 }, f264: function() { return 264 },
f265: function() { return 265 }, f266: function() { return 266 }, f267: function() { return 267 }, f268: function() { return 268 }, f269: function() { return 269 },
f270: function() { return 270 }, f271: function() { return 271 }, f272: function() { return 272 }, f273: function() { return 273 }, f274: function() { return 274 },
f275: function() { return 275 }, f276: function() { return 276 }, f277: function() { return 277 }, f278: function() { return 278 }, f279: function() { return 279 },
f280: function() { return 280 }, f281: function() { return 281 }, f282: function() { return 282 }, f283: function() { return 283 }, f284: function() { return 284 },
f285: function() { return 285 }, f286: function() { return 286 }, f287: function() { return 287 }, f288: function() { return 288 }, f289: function() { return 289 },
f290: function() { return 290 }, f291: function() { return 291 }, f292: function() { return 292 }, f293: function() { return 293 }, f294: function() { return 294 },
f295: function() { return 295 }, f296: function() { return 296 }, f297: function() { return 297 }, f298: function() { return 298 }, f299: function() { return 299 }
}
}
var obj = make_obj()
var keys = array(obj)
var i = 0
var bad_count = 0
var first_bad = ""
assert_eq(length(keys), 300, "should have 300 keys")
for (i = 0; i < length(keys); i++) {
if (!is_function(obj[keys[i]])) {
if (first_bad == "") {
first_bad = keys[i]
}
bad_count = bad_count + 1
}
}
if (bad_count > 0) {
fail(text(bad_count) + " of 300 values not functions, first bad: " + first_bad)
}
})
// ============================================================================
// NESTED FUNCTION DECLARATIONS
// ============================================================================
run("nested named function basic", function() {
function inner(x) {
return x + 1
}
assert_eq(inner(41), 42, "nested named function call")
})
run("nested named function used by sibling", function() {
function helper(v) {
return v * 2
}
function caller(v) {
return helper(v) + 1
}
assert_eq(caller(5), 11, "sibling nested function call")
})
run("nested named function in var function", function() {
var outer = function() {
function inner(a, b) {
return a + b
}
return inner(10, 20)
}
assert_eq(outer(), 30, "nested function inside var function")
})
run("nested named function with closure", function() {
var multiplier = 3
function scale(x) {
return x * multiplier
}
assert_eq(scale(7), 21, "nested function closing over outer var")
})
run("nested named function recursive", function() {
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
assert_eq(factorial(5), 120, "nested recursive function")
})
run("deeply nested named functions", function() {
var outer = function() {
function mid(x) {
function inner(y) {
return y + 1
}
return inner(x) * 2
}
return mid(4)
}
assert_eq(outer(), 10, "deeply nested named functions")
})
run("nested function used after definition", function() {
var result = []
function encode_value(v) {
if (is_text(v)) return '"' + v + '"'
if (is_number(v)) return text(v)
return "null"
}
function quote_key(k) {
return k
}
result[] = quote_key("a") + " = " + encode_value(1)
result[] = quote_key("b") + " = " + encode_value("hi")
assert_eq(result[0], "a = 1", "nested fn encode number")
assert_eq(result[1], 'b = "hi"', "nested fn encode text")
})
// ============================================================================
// JSON ENCODING
// ============================================================================
def json = use("json")
run("json encode flat object", function() {
var obj = {}
var i = 0
for (i = 0; i < 500; i++) {
obj[text(i)] = "value_" + text(i)
}
var result = json.encode(obj)
assert_eq(is_text(result), true, "encode returns text")
var decoded = json.decode(result)
assert_eq(decoded["0"], "value_0", "first property survives roundtrip")
assert_eq(decoded["499"], "value_499", "last property survives roundtrip")
})
run("json encode nested objects", function() {
var outer = {}
var i = 0
var j = 0
var inner = null
for (i = 0; i < 50; i++) {
inner = {}
for (j = 0; j < 20; j++) {
inner[text(j)] = i * 20 + j
}
outer[text(i)] = inner
}
var result = json.encode(outer)
var decoded = json.decode(result)
assert_eq(decoded["0"]["0"], 0, "nested first value")
assert_eq(decoded["49"]["19"], 999, "nested last value")
})
run("json encode array", function() {
var arr = [1, "two", true, null, 3.14]
var result = json.encode(arr)
var decoded = json.decode(result)
assert_eq(decoded[0], 1, "array number")
assert_eq(decoded[1], "two", "array text")
assert_eq(decoded[2], true, "array logical")
assert_eq(decoded[3], null, "array null")
assert_eq(decoded[4], 3.14, "array float")
})
run("json circular reference detected", function() {
var circ = {}
circ.name = "root"
circ.self = circ
if (!should_disrupt(function() { json.encode(circ) })) {
fail("circular reference not detected")
}
})
run("json deeply nested circular reference", function() {
var a = {}
var b = {}
var c = {}
a.child = b
b.child = c
c.child = a
if (!should_disrupt(function() { json.encode(a) })) {
fail("deep circular reference not detected")
}
})
run("json roundtrip preserves types", function() {
var obj = {
"num": 42,
"txt": "hello",
"yes": true,
"no": false,
"nil": null,
"arr": [1, 2, 3],
"sub": {"a": 1}
}
var decoded = json.decode(json.encode(obj))
assert_eq(decoded.num, 42, "number preserved")
assert_eq(decoded.txt, "hello", "text preserved")
assert_eq(decoded.yes, true, "true preserved")
assert_eq(decoded.no, false, "false preserved")
assert_eq(decoded.nil, null, "null preserved")
assert_eq(decoded.arr[2], 3, "array preserved")
assert_eq(decoded.sub.a, 1, "sub-object preserved")
})
// ============================================================================
// BLOB - GC HEAP INTEGRATION
// ============================================================================
run("blob basic create and stone", function() {
var b = blob()
if (!is_blob(b)) fail("empty blob is not a blob")
b.write_bit(true)
b.write_bit(false)
b.write_bit(true)
stone(b)
if (length(b) != 3) fail("blob length should be 3, got " + text(length(b)))
if (!b.read_logical(0)) fail("bit 0 should be true")
if (b.read_logical(1)) fail("bit 1 should be false")
if (!b.read_logical(2)) fail("bit 2 should be true")
})
run("blob write_number read_number", function() {
var b = blob()
b.write_number(3.14)
b.write_number(2.718)
stone(b)
if (length(b) != 128) fail("expected 128 bits")
var pi = b.read_number(0)
var e = b.read_number(64)
if (pi != 3.14) fail("pi read back wrong: " + text(pi))
if (e != 2.718) fail("e read back wrong: " + text(e))
})
run("blob write_fit read_fit", function() {
var b = blob()
b.write_fit(42, 8)
b.write_fit(-7, 8)
stone(b)
if (b.read_fit(0, 8) != 42) fail("fit 42 failed")
if (b.read_fit(8, 8) != -7) fail("fit -7 failed")
})
run("blob write_text read_text", function() {
var b = blob()
b.write_text("hello world")
stone(b)
var t = b.read_text(0)
if (t != "hello world") fail("text roundtrip failed: " + t)
})
run("blob from text", function() {
var b = blob("abc")
stone(b)
if (length(b) != 24) fail("blob from text length wrong")
var t = text(b)
if (t != "abc") fail("blob to text failed: " + t)
})
run("blob copy slice", function() {
var b = blob()
b.write_fit(100, 16)
b.write_fit(200, 16)
b.write_fit(300, 16)
stone(b)
var slice = blob(b, 16, 32)
stone(slice)
if (slice.read_fit(0, 16) != 200) fail("blob slice failed")
})
run("blob write_blob", function() {
var a = blob()
a.write_fit(1, 8)
a.write_fit(2, 8)
var b = blob()
b.write_fit(3, 8)
b.write_blob(a)
stone(b)
if (b.read_fit(0, 8) != 3) fail("first byte wrong")
if (b.read_fit(8, 8) != 1) fail("second byte wrong")
if (b.read_fit(16, 8) != 2) fail("third byte wrong")
})
run("blob write_pad pad?", function() {
var b = blob()
b.write_fit(7, 4)
b.write_pad(8)
stone(b)
if (length(b) != 8) fail("pad didn't align to 8, got " + text(length(b)))
if (!b["pad?"](4, 8)) fail("pad? should be true")
})
run("blob w16 w32 wf", function() {
var b = blob()
b.w16(1000)
b.w32(100000)
b.wf(1.5)
stone(b)
if (length(b) != 80) fail("expected 80 bits, got " + text(length(b)))
})
run("blob is_data true for blob", function() {
var b = blob()
if (!is_data(b)) fail("blob should be is_data")
})
run("blob text hex format", function() {
var b = blob("AB")
stone(b)
var hex = text(b, "h")
if (hex != "4142") fail("hex encoding wrong: " + hex)
})
run("blob text binary format", function() {
var b = blob()
b.write_bit(true)
b.write_bit(false)
b.write_bit(true)
b.write_bit(true)
stone(b)
var bits = text(b, "b")
if (bits != "1011") fail("binary encoding wrong: " + bits)
})
run("blob(capacity) preallocates", function() {
var b = blob(1024)
if (!is_blob(b)) fail("capacity blob not a blob")
if (length(b) != 0) fail("capacity blob should start empty")
var i = 0
for (i = 0; i < 128; i = i + 1) {
b.write_fit(i, 8)
}
if (length(b) != 1024) fail("after fill length wrong")
})
run("blob(length, bool) fill", function() {
var b = blob(16, true)
stone(b)
if (length(b) != 16) fail("filled blob length wrong")
var i = 0
for (i = 0; i < 16; i = i + 1) {
if (!b.read_logical(i)) fail("bit " + text(i) + " should be true")
}
var z = blob(8, false)
stone(z)
for (i = 0; i < 8; i = i + 1) {
if (z.read_logical(i)) fail("zero bit " + text(i) + " should be false")
}
})
// --- GC stress tests: verify blobs survive collection and don't corrupt ---
run("gc blob survives collection", function() {
var b = blob()
var garbage = null
var i = 0
var v1 = null
var t = null
b.write_number(123.456)
b.write_text("test data")
// Trigger GC pressure by allocating many objects
for (i = 0; i < 500; i = i + 1) {
garbage = {a: i, b: text(i), c: [i, i+1, i+2]}
}
// blob should still be valid after GC
b.write_number(789.012)
stone(b)
v1 = b.read_number(0)
if (v1 != 123.456) fail("blob data corrupted after gc: " + text(v1))
t = b.read_text(64)
if (t != "test data") fail("blob text corrupted after gc: " + t)
})
run("gc blob growth across collections", function() {
var b = blob()
var i = 0
var junk = null
var v = null
for (i = 0; i < 200; i = i + 1) {
b.write_fit(i, 16)
junk = [text(i), {v: i}, text(i) + "_end"]
}
stone(b)
for (i = 0; i < 200; i = i + 1) {
v = b.read_fit(i * 16, 16)
if (v != i) fail("blob growth gc: slot " + text(i) + " = " + text(v))
}
})
run("gc many blobs alive simultaneously", function() {
var blobs = []
var i = 0
var b = null
var trash = null
var v1 = null
var v2 = null
for (i = 0; i < 100; i = i + 1) {
b = blob()
b.write_fit(i * 7, 16)
b.write_fit(i * 13, 16)
stone(b)
blobs[i] = b
}
for (i = 0; i < 200; i = i + 1) {
trash = {x: text(i), y: [i]}
}
for (i = 0; i < 100; i = i + 1) {
v1 = blobs[i].read_fit(0, 16)
v2 = blobs[i].read_fit(16, 16)
if (v1 != i * 7) fail("multi blob " + text(i) + " v1 = " + text(v1))
if (v2 != i * 13) fail("multi blob " + text(i) + " v2 = " + text(v2))
}
})
run("gc blob not polluting other objects", function() {
var results = []
var i = 0
var b = null
var obj = null
var tmp = null
var entry = null
var bv = null
var bt = null
for (i = 0; i < 50; i = i + 1) {
b = blob()
b.write_fit(i, 16)
b.write_text("item" + text(i))
stone(b)
obj = {index: i, name: "obj" + text(i)}
results[i] = {blob_val: b, obj_val: obj}
}
for (i = 0; i < 300; i = i + 1) {
tmp = {a: text(i), b: [i, i]}
}
for (i = 0; i < 50; i = i + 1) {
entry = results[i]
bv = entry.blob_val.read_fit(0, 16)
if (bv != i) fail("pollute test blob " + text(i) + " = " + text(bv))
bt = entry.blob_val.read_text(16)
if (bt != "item" + text(i)) fail("pollute test text " + text(i))
if (entry.obj_val.index != i) fail("pollute test obj index " + text(i))
if (entry.obj_val.name != "obj" + text(i)) fail("pollute test obj name " + text(i))
}
})
run("gc dead blobs are collected", function() {
// Verify that dead blobs don't cause leaks by checking heap stays bounded.
// We do two phases: allocate a batch, check heap, allocate another, check again.
// If blobs leaked, the second batch would cause unbounded growth.
var i = 0
var b = null
var junk = null
var stats1 = null
var stats2 = null
gc_stats()
for (i = 0; i < 200; i = i + 1) {
b = blob(1024)
b.write_fit(i, 16)
junk = {a: text(i), b: [i]}
}
stats1 = gc_stats()
// Second identical batch — should reuse collected space
for (i = 0; i < 200; i = i + 1) {
b = blob(1024)
b.write_fit(i, 16)
junk = {a: text(i), b: [i]}
}
stats2 = gc_stats()
// Heap should not have grown more than 4x between phases
// (some growth is normal from doubling, but not unbounded)
if (stats2.heap_size > stats1.heap_size * 4) {
fail("heap grew too much: " + text(stats1.heap_size) + " -> " + text(stats2.heap_size))
}
})
run("gc blob write_blob both survive", function() {
var src = blob()
var i = 0
var v = null
var dst = null
for (i = 0; i < 100; i = i + 1) {
src.write_fit(i, 16)
}
dst = blob()
dst.write_fit(99, 16)
dst.write_blob(src)
stone(dst)
if (dst.read_fit(0, 16) != 99) fail("dst first word wrong")
for (i = 0; i < 100; i = i + 1) {
v = dst.read_fit(16 + i * 16, 16)
if (v != i) fail("write_blob gc: word " + text(i) + " = " + text(v))
}
})
run("gc blob read_blob across allocation", function() {
var big = blob()
var i = 0
var slices = []
var s = null
var v = null
for (i = 0; i < 200; i = i + 1) {
big.write_fit(i, 16)
}
stone(big)
for (i = 0; i < 50; i = i + 1) {
s = big.read_blob(i * 16, (i + 1) * 16)
stone(s)
slices[i] = s
}
for (i = 0; i < 50; i = i + 1) {
v = slices[i].read_fit(0, 16)
if (v != i) fail("read_blob gc slice " + text(i) + " = " + text(v))
}
})
run("gc blob with random fill", function() {
var b = blob(256, function() { return 42 })
stone(b)
if (length(b) != 256) fail("random fill length wrong")
})
run("gc blob in record values", function() {
var rec = {}
var i = 0
var b = null
var junk = null
var v = null
for (i = 0; i < 50; i = i + 1) {
b = blob()
b.write_fit(i * 3, 16)
stone(b)
rec["k" + text(i)] = b
junk = {x: text(i), y: [i, i+1]}
}
for (i = 0; i < 50; i = i + 1) {
v = rec["k" + text(i)].read_fit(0, 16)
if (v != i * 3) fail("blob in record " + text(i) + " = " + text(v))
}
})
run("gc blob in array elements", function() {
var arr = []
var i = 0
var b = null
var garbage = null
var v = null
for (i = 0; i < 100; i = i + 1) {
b = blob()
b.write_number(i * 1.5)
stone(b)
arr[i] = b
}
for (i = 0; i < 500; i = i + 1) {
garbage = text(i) + text(i)
}
for (i = 0; i < 100; i = i + 1) {
v = arr[i].read_number(0)
if (v != i * 1.5) fail("blob in array " + text(i) + " = " + text(v))
}
})
run("gc blob forward pointer chase", function() {
var b = blob()
var i = 0
var junk = null
var v = null
for (i = 0; i < 500; i = i + 1) {
b.write_fit(i % 128, 8)
}
for (i = 0; i < 300; i = i + 1) {
junk = {a: [i, text(i)], b: text(i) + "!"}
}
stone(b)
for (i = 0; i < 500; i = i + 1) {
v = b.read_fit(i * 8, 8)
if (v != i % 128) fail("fwd chase " + text(i) + " = " + text(v))
}
})
// ============================================================================
// GC CLOSURE FRAME SHORTENING
// Verify that closure-captured variables survive GC collection, particularly
// when the streamline optimizer remaps close slots to different positions.
// ============================================================================
var force_gc = function() {
var _g = 0
var _gx = null
for (_g = 0; _g < 200; _g = _g + 1) {
_gx = {a: _g, b: [1, 2, 3], c: "garbage"}
}
}
run("gc closure basic - captured function survives gc", function() {
var make = function() {
function helper() { return 42 }
var obj = { call() { return helper() } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.call(), 42, "captured function should survive GC")
})
run("gc closure - captured variable survives gc", function() {
var make = function() {
var val = 99
var obj = { get() { return val } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.get(), 99, "captured variable should survive GC")
})
run("gc closure - multiple captured variables survive gc", function() {
var make = function() {
var a = 10
var b = 20
var c = 30
var obj = {
sum() { return a + b + c }
}
return obj
}
var obj = make()
force_gc()
assert_eq(obj.sum(), 60, "all captured vars should survive GC")
})
run("gc closure - captured function and var survive gc", function() {
var make = function() {
function double(x) { return x * 2 }
var base = 5
var obj = { compute() { return double(base) } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.compute(), 10, "captured fn and var should survive GC")
})
run("gc closure - nested closure chain survives gc", function() {
var outer = function() {
var x = 7
var mid = function() {
var y = 3
var inner = function() { return x + y }
return inner
}
return mid()
}
var fn = outer()
force_gc()
assert_eq(fn(), 10, "nested closure chain should survive GC")
})
run("gc closure - multiple methods share captured frame", function() {
var make = function() {
var count = 0
function inc() { count = count + 1 }
function get() { return count }
var obj = {
increment() { inc() },
value() { return get() }
}
return obj
}
var obj = make()
obj.increment()
obj.increment()
force_gc()
obj.increment()
assert_eq(obj.value(), 3, "shared closure frame should survive GC")
})
run("gc closure - closure survives repeated gc cycles", function() {
var make = function() {
var val = 123
var obj = { get() { return val } }
return obj
}
var obj = make()
force_gc()
force_gc()
force_gc()
assert_eq(obj.get(), 123, "closure should survive repeated GC cycles")
})
run("gc closure - object literal method with temp slot reuse", function() {
var make = function() {
function helper() { return "ok" }
var temp = [1, 2, 3]
var unused = {x: temp}
var obj = { call() { return helper() } }
return obj
}
var obj = make()
force_gc()
assert_eq(obj.call(), "ok", "closure should work after temp slots discarded")
})
run("gc closure - closure array survives gc", function() {
var make = function() {
var items = [10, 20, 30]
var obj = {
first() { return items[0] },
last() { return items[2] }
}
return obj
}
var obj = make()
force_gc()
assert_eq(obj.first(), 10, "captured array first element")
assert_eq(obj.last(), 30, "captured array last element")
})
run("gc closure - factory pattern survives gc", function() {
var factory = function(name) {
function greet() { return "hello " + name }
var obj = { say() { return greet() } }
return obj
}
var a = factory("alice")
var b = factory("bob")
force_gc()
assert_eq(a.say(), "hello alice", "first factory closure")
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")
})
// ============================================================================
// 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 - direct", 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: string, number, true, object, array")
})
run("mixed types not-in-conditional", function() {
var values = [false, 0, "", null]
var negated_count = 0
arrfor(values, function(v) {
if (!v) {
negated_count = negated_count + 1
}
})
assert_eq(negated_count, 4, "!falsy should all be truthy")
})
run("mixed types not-truthy-in-conditional", function() {
var values = ["hello", 42, true, {}, [1]]
var negated_count = 0
arrfor(values, function(v) {
if (!v) {
negated_count = negated_count + 1
}
})
assert_eq(negated_count, 0, "!truthy should all be falsy")
})
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", function() {
var result = array([1, 2, 3], function(x) { return x * 10 }, true)
assert_eq(length(result), 3, "map rev length")
assert_eq(result[0], 30, "map rev [0]")
assert_eq(result[1], 20, "map rev [1]")
assert_eq(result[2], 10, "map rev [2]")
})
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]")
})
run("array from record with function is invalid shape", function() {
// array(record, fn) is not a valid polymorphic shape.
// NOTE: this test passes via should_disrupt, but the disruption is
// "cannot compare: operands must be same type" — a wrong/confusing error.
// A proper error would indicate invalid argument shape.
// See also "array record fn disruption consistency" for a related bug.
var obj = {a: 1, b: 2}
var ok = should_disrupt(function() { array(obj, function(k) { return k }) })
if (!ok) fail("array(record, fn) should disrupt on invalid shape")
})
// ============================================================================
// 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 - object() COMPLETE COVERAGE
// ============================================================================
run("object copy", function() {
var original = {a: 1, b: 2}
var copy = object(original)
assert_eq(copy.a, 1, "object copy a")
assert_eq(copy.b, 2, "object copy b")
copy.a = 99
assert_eq(original.a, 1, "object copy does not mutate original")
})
run("object merge", function() {
var a = {x: 1, y: 2}
var b = {y: 20, z: 30}
var merged = object(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("object select keys", function() {
var obj = {a: 1, b: 2, c: 3, d: 4}
var subset = object(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("object from keys", function() {
var r = object(["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("object from keys with value", function() {
var r = object(["x", "y"], 0)
assert_eq(r.x, 0, "keys value x")
assert_eq(r.y, 0, "keys value y")
})
run("object from keys with function", function() {
var r = object(["a", "b", "c"], function(k) { return k + "!" })
assert_eq(r.a, "a!", "keys fn a")
assert_eq(r.b, "b!", "keys fn b")
assert_eq(r.c, "c!", "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")
})
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
// ============================================================================
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 - non-empty strings", function() {
var items = ["hello", "", "world", "", "ok"]
var result = filter(items, function(x) { return length(x) > 0 })
assert_eq(length(result), 3, "filter non-empty strings count")
assert_eq(result[0], "hello", "filter non-empty [0]")
assert_eq(result[1], "world", "filter non-empty [1]")
assert_eq(result[2], "ok", "filter non-empty [2]")
})
run("filter inline - nonzero numbers", function() {
var items = [0, 1, 0, 2, 0, 3]
var result = filter(items, function(x) { return x != 0 })
assert_eq(length(result), 3, "filter nonzero numbers count")
assert_eq(result[0], 1, "filter nonzero [0]")
assert_eq(result[1], 2, "filter nonzero [1]")
assert_eq(result[2], 3, "filter nonzero [2]")
})
run("filter inline - non-null values", function() {
var items = [1, null, 2, null, 3]
var result = filter(items, function(x) { return !is_null(x) })
assert_eq(length(result), 3, "filter non-null count")
})
run("filter non-boolean callback returns null", function() {
// Callbacks that return non-boolean values should cause filter to return null
var result = filter([1, 2, 3], function(x) { return x })
assert_eq(result, null, "filter truthy-return is null")
})
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]")
})
// ============================================================================
// ISOLATED BUG TESTS — oddities encountered during test writing
// ============================================================================
run("array record fn disruption consistency", function() {
// BUG: array(record, fn) disruption depends on nesting depth.
// With one extra function wrapper (should_disrupt), the "cannot compare"
// error propagates as a disruption. With a direct disruption handler,
// the error is logged but does NOT disrupt — the call succeeds silently.
// Both should behave identically.
var obj = {a: 1, b: 2}
var via_should = should_disrupt(function() {
array(obj, function(k) { return k })
})
var via_manual = false
var manual = function() {
array(obj, function(k) { return k })
} disruption {
via_manual = true
}
manual()
if (via_should != via_manual) {
fail("should_disrupt=" + text(via_should) + " but manual=" + text(via_manual))
}
})
run("disruption propagation - explicit disrupt direct handler", function() {
// Test: does a plain `disrupt` propagate to a direct disruption handler?
var caught = false
var fn = function() {
disrupt
} disruption {
caught = true
}
fn()
if (!caught) fail("direct handler did not catch explicit disrupt")
})
run("disruption propagation - explicit disrupt nested handler", function() {
// Test: does a plain `disrupt` propagate through an extra nesting level?
var caught = should_disrupt(function() { disrupt })
if (!caught) fail("should_disrupt did not catch explicit disrupt")
})
run("disruption propagation - disrupt from callee direct handler", function() {
// Test: function calls inner function that disrupts; direct handler catches it
var inner = function() { disrupt }
var caught = false
var outer = function() {
inner()
} disruption {
caught = true
}
outer()
if (!caught) fail("direct handler did not catch disrupt from callee")
})
run("disruption propagation - disrupt from callee nested handler", function() {
// Test: function calls inner function that disrupts; should_disrupt catches it
var inner = function() { disrupt }
var caught = should_disrupt(function() { inner() })
if (!caught) fail("should_disrupt did not catch disrupt from callee")
})
run("disruption propagation - runtime error direct vs nested", function() {
var via_should = should_disrupt(function() {
var x = 1 / 0
})
var via_manual = false
var manual = function() {
var x = 1 / 0
} disruption {
via_manual = true
}
manual()
if (via_should != via_manual) {
fail("division: should_disrupt=" + text(via_should) + " but manual=" + text(via_manual))
}
})
run("disruption propagation - invoke non-function at runtime", function() {
// Use an array to hide the type from the compiler so it can't
// statically prove the invoke will fail. This tests the VM's
// MACH_FRAME runtime check, not the compile-time diagnostic.
var vals = [42, null, "hello", true]
var i = 0
var count = 0
for (i = 0; i < length(vals); i++) {
if (should_disrupt(function() { vals[i]() })) {
count = count + 1
}
}
assert_eq(count, 4, "all non-function values should disrupt when invoked")
})
run("disruption propagation - invoke non-function direct handler", function() {
// Same test but with a direct disruption handler to verify consistency.
var vals = [42, null, "hello", true]
var i = 0
var count = 0
var caught = false
var try_call = function() {
vals[i]()
} disruption {
caught = true
}
for (i = 0; i < length(vals); i++) {
caught = false
try_call()
if (caught) count = count + 1
}
assert_eq(count, 4, "all non-function values should disrupt with direct handler")
})
run("disruption propagation - comparison error direct vs nested", function() {
var via_should = should_disrupt(function() {
var x = [1, 2] == "hello"
})
var via_manual = false
var manual = function() {
var x = [1, 2] == "hello"
} disruption {
via_manual = true
}
manual()
if (via_should != via_manual) {
fail("compare: should_disrupt=" + text(via_should) + " but manual=" + text(via_manual))
}
})
// ============================================================================
// STRING BRACKET INDEXING — str[n] must return the character at index n
// Regression: native __load_index_ss only handled arrays, returning null for
// strings. This broke key[0]=='$' in engine.cm, surfacing as '$stop' undefined.
// ============================================================================
run("string bracket index - first char", function() {
var s = "hello"
assert_eq(s[0], "h", "s[0]")
})
run("string bracket index - middle char", function() {
var s = "hello"
assert_eq(s[2], "l", "s[2]")
})
run("string bracket index - last char", function() {
var s = "hello"
assert_eq(s[4], "o", "s[4]")
})
run("string bracket index - out of bounds returns null", function() {
var s = "hi"
assert_eq(s[5], null, "s[5] oob")
assert_eq(s[99], null, "s[99] oob")
})
run("string bracket index - negative returns null", function() {
var s = "abc"
assert_eq(s[-1], null, "s[-1]")
})
run("string bracket index - single char string", function() {
var s = "x"
assert_eq(s[0], "x", "s[0] single")
assert_eq(s[1], null, "s[1] oob single")
})
run("string bracket index - special chars", function() {
var s = "$stop"
assert_eq(s[0], "$", "dollar sign at [0]")
assert_eq(s[1], "s", "s at [1]")
assert_eq(s[4], "p", "p at [4]")
})
run("string bracket index - empty string", function() {
var s = ""
assert_eq(s[0], null, "empty[0]")
})
run("string bracket index - comparison", function() {
var key = "$hello"
if (key[0] == "$") {
// pass — this is the pattern engine.cm uses
} else {
fail("key[0] should equal '$' but got " + text(key[0]))
}
})
run("string bracket index - in conditional", function() {
var s = "abc"
var r = null
if (s[0] == "a") r = "ok"
assert_eq(r, "ok", "s[0]=='a' conditional")
})
run("string bracket index - loop over chars", function() {
var s = "abcd"
var out = ""
var i = 0
for (i = 0; i < length(s); i++) {
out = out + s[i]
}
assert_eq(out, "abcd", "loop over string chars")
})
run("string bracket index - dynamic index", function() {
var s = "world"
var i = 2
assert_eq(s[i], "r", "s[dynamic]")
i = 0
assert_eq(s[i], "w", "s[dynamic 0]")
})
run("string bracket index - 7 char immediate boundary", function() {
// immediate strings store up to 7 ASCII chars inline
var s7 = "abcdefg"
assert_eq(s7[0], "a", "imm7[0]")
assert_eq(s7[6], "g", "imm7[6]")
var s8 = "abcdefgh"
assert_eq(s8[0], "a", "heap8[0]")
assert_eq(s8[7], "h", "heap8[7]")
})
run("string bracket index - used as record key", function() {
var s = "$name"
var prefix = s[0]
var rest = text(s, 1)
var obj = {}
obj[prefix + rest] = 42
assert_eq(obj["$name"], 42, "reconstruct key from bracket index")
})
// ============================================================================
// INDEXED ACCESS ON NON-ARRAY TYPES — native fast path must fall through
// These smoke out __load_index_ss returning null instead of calling runtime.
// ============================================================================
run("record bracket index with numeric key returns null", function() {
var obj = {a: 1}
assert_eq(obj[0], null, "record[0]")
})
run("null bracket index returns null", function() {
var x = null
assert_eq(x[0], null, "null[0]")
})
run("number bracket index returns null", function() {
var n = 42
assert_eq(n[0], null, "number[0]")
})
run("boolean bracket index returns null", function() {
var b = true
assert_eq(b[0], null, "bool[0]")
})
run("string length still works", function() {
assert_eq(length("hello"), 5, "length of 'hello'")
assert_eq(length(""), 0, "length of ''")
assert_eq(length("abcdefg"), 7, "length of 7-char imm")
assert_eq(length("abcdefgh"), 8, "length of 8-char heap")
})
run("mixed array and string indexing", function() {
var arr = ["abc", "def"]
assert_eq(arr[0][0], "a", "arr[0][0]")
assert_eq(arr[1][2], "f", "arr[1][2]")
})
run("string index result equality", function() {
var a = "hello"
var b = "hello"
assert_eq(a[0] == b[0], true, "same char from same string pos")
assert_eq(a[0] == "h", true, "char equals literal")
assert_eq(a[0] != "x", true, "char not-equals different literal")
})
run("string index in text() call", function() {
var s = "$abc"
var sub = text(s, 1)
assert_eq(sub, "abc", "text(s, 1) strips first char")
assert_eq(s[0] + sub, "$abc", "reassemble from index + substring")
})
// ============================================================================
// NO IMPLICIT RETURN
// ============================================================================
run("function trailing call no implicit return", function() {
var fn = function() { text(42) }
assert_eq(fn(), null, "trailing call should not implicitly return")
})
run("function trailing expression no implicit return", function() {
var fn = function() { var x = 1 + 2 }
assert_eq(fn(), null, "trailing expression should not implicitly return")
})
run("function trailing arithmetic no implicit return", function() {
var fn = function() {
var x = 10
x + 5
}
assert_eq(fn(), null, "bare arithmetic should not implicitly return")
})
run("function explicit return still works", function() {
var fn = function() { return 42 }
assert_eq(fn(), 42, "explicit return should work")
})
run("arrow trailing call no implicit return", function() {
var fn = (x) => { text(x) }
assert_eq(fn(42), null, "arrow trailing call should not implicitly return")
})
run("arrow trailing arithmetic no implicit return", function() {
var fn = (x) => {
var y = x + 1
y * 2
}
assert_eq(fn(5), null, "arrow bare arithmetic should not implicitly return")
})
run("arrow explicit return still works", function() {
var fn = (x) => { return x * 2 }
assert_eq(fn(5), 10, "arrow explicit return should work")
})
run("function explicit return before trailing expr", function() {
var fn = function() {
return 7
text(99)
}
assert_eq(fn(), 7, "explicit return should take precedence")
})
// ============================================================================
// COMPREHENSIVE EDGE CASE TESTS
// ============================================================================
// --- array() edge cases ---
run("array(number) negative disrupts", function() {
var result = array(-1)
assert_eq(result, null, "array(-1) returns null")
})
run("array(float) disrupts", function() {
if (!should_disrupt(function() { array(3.5) })) fail("array(3.5) should disrupt")
})
run("array(number, value) all initialized", function() {
var a = array(4, "x")
assert_eq(a[0], "x", "init [0]")
assert_eq(a[3], "x", "init [3]")
assert_eq(length(a), 4, "init length")
})
run("array(number, null) all null", function() {
var a = array(3, null)
assert_eq(a[0], null, "null init [0]")
assert_eq(a[2], null, "null init [2]")
})
run("array(number, function) index passed", function() {
var a = array(4, function(i) { return i * i })
assert_eq(a[0], 0, "fn [0]")
assert_eq(a[1], 1, "fn [1]")
assert_eq(a[2], 4, "fn [2]")
assert_eq(a[3], 9, "fn [3]")
})
run("array(array, function) basic map", function() {
var result = array([1, 2, 3], function(x) { return x + 10 })
assert_eq(result[0], 11, "map [0]")
assert_eq(result[2], 13, "map [2]")
})
run("array(array, function, true) reverse map", function() {
var order = []
var result = array([10, 20, 30], function(x) {
order[] = x
return x
}, true)
assert_eq(order[0], 30, "reverse order [0]")
assert_eq(order[1], 20, "reverse order [1]")
assert_eq(order[2], 10, "reverse order [2]")
assert_eq(length(result), 3, "reverse map length")
})
run("array(array, function, false) forward map", function() {
var order = []
array([1, 2, 3], function(x) {
order[] = x
return x
}, false)
assert_eq(order[0], 1, "forward order [0]")
assert_eq(order[2], 3, "forward order [2]")
})
run("array(array, function, reverse) non-boolean reverse disrupts", function() {
if (!should_disrupt(function() { array([1, 2, 3], function(x) { return x }, 42) }))
fail("array map with integer reverse should disrupt")
})
run("array(array, function, reverse) string reverse disrupts", function() {
if (!should_disrupt(function() { array([1, 2, 3], function(x) { return x }, "yes") }))
fail("array map with string reverse should disrupt")
})
run("array(array, function, reverse) null reverse ok", function() {
// null is treated as absent — forward iteration
var order = []
array([1, 2, 3], function(x) {
order[] = x
return x
}, null)
assert_eq(order[0], 1, "null reverse forward")
})
run("array(array, function, true, exit) early exit", function() {
var result = array([1, 2, 3, 4, 5], function(x) {
if (x == 4) return "stop"
return x * 10
}, false, "stop")
assert_eq(length(result), 3, "exit length")
assert_eq(result[0], 10, "exit [0]")
assert_eq(result[2], 30, "exit [2]")
})
run("array(array, function, true, exit) reverse early exit", function() {
var result = array([1, 2, 3, 4, 5], function(x) {
if (x == 2) return "stop"
return x * 10
}, true, "stop")
// reverse: processes 5,4,3,2 -> stops at 2
assert_eq(result[0], 50, "rev exit [0]")
assert_eq(result[1], 40, "rev exit [1]")
assert_eq(result[2], 30, "rev exit [2]")
assert_eq(length(result), 3, "rev exit length")
})
run("array(array, from, to) wrong type for to disrupts", function() {
if (!should_disrupt(function() { array([1, 2, 3, 4, 5], 1, true) }))
fail("array slice with boolean to should disrupt")
})
run("array(array, from, to) wrong type for from disrupts", function() {
if (!should_disrupt(function() { array([1, 2, 3, 4, 5], true) }))
fail("array with boolean from should disrupt")
})
run("array(array, from, to) float from disrupts", function() {
if (!should_disrupt(function() { array([1, 2, 3, 4, 5], 1.5, 3) }))
fail("array slice with float from should disrupt")
})
run("array(array, from, to) from > to returns null", function() {
var result = array([1, 2, 3, 4, 5], 3, 1)
assert_eq(result, null, "slice from > to returns null")
})
run("array(array, from, to) from == to empty", function() {
var result = array([1, 2, 3], 2, 2)
assert_eq(length(result), 0, "slice from==to empty")
})
run("array(array, from, to) full range", function() {
var result = array([10, 20, 30], 0, 3)
assert_eq(length(result), 3, "full range length")
assert_eq(result[0], 10, "full range [0]")
assert_eq(result[2], 30, "full range [2]")
})
run("array(array, from) to defaults to end", function() {
var result = array([10, 20, 30, 40], 2)
assert_eq(length(result), 2, "default to length")
assert_eq(result[0], 30, "default to [0]")
assert_eq(result[1], 40, "default to [1]")
})
run("array(array, array) concat both empty", function() {
var result = array([], [])
assert_eq(length(result), 0, "concat both empty")
})
run("array copy empty", function() {
var copy = array([])
assert_eq(length(copy), 0, "copy empty length")
})
run("array from empty record", function() {
var keys = array({})
assert_eq(length(keys), 0, "empty record keys")
})
run("array from text single char", function() {
var chars = array("x")
assert_eq(length(chars), 1, "single char length")
assert_eq(chars[0], "x", "single char value")
})
run("array(text, length) dice exact", function() {
var result = array("abcdef", 3)
assert_eq(length(result), 2, "dice exact length")
assert_eq(result[0], "abc", "dice exact [0]")
assert_eq(result[1], "def", "dice exact [1]")
})
// --- text() edge cases ---
run("text(text, from, to) from > to returns null", function() {
assert_eq(text("hello", 3, 1), null, "text from>to null")
})
run("text(text, from, to) empty range", function() {
assert_eq(text("hello", 2, 2), "", "text from==to empty")
})
run("text(text, from) from at length", function() {
assert_eq(text("hello", 5), "", "text from==len empty")
})
run("text(text, from) from beyond length", function() {
var result = text("hello", 6)
// from > len should clamp
assert_eq(result, "", "text from>len")
})
run("text(text, from, to) full range", function() {
assert_eq(text("hello", 0, 5), "hello", "text full range")
})
run("text(text, negative from, negative to)", function() {
assert_eq(text("hello", -3, -1), "ll", "text neg from neg to")
})
run("text from number format string", function() {
assert_eq(text(255, "h"), "FF", "text hex format")
assert_eq(text(10, "b"), "1010", "text binary format")
assert_eq(text(8, "o"), "10", "text octal format")
})
run("text from array disrupts on non-text element", function() {
if (!should_disrupt(function() { text([1, 2, 3]) }))
fail("text(array of numbers) should disrupt")
})
run("text from array disrupts on mixed", function() {
if (!should_disrupt(function() { text(["a", 1, "b"]) }))
fail("text(array with number) should disrupt")
})
run("text from logical", function() {
assert_eq(text(true), "true", "text(true)")
assert_eq(text(false), "false", "text(false)")
})
run("text from null", function() {
assert_eq(text(null), "null", "text(null)")
})
// --- number() edge cases ---
run("number from invalid text returns null", function() {
assert_eq(number("abc"), null, "number('abc')")
assert_eq(number(""), null, "number('')")
})
run("number from null returns null", function() {
assert_eq(number(null), null, "number(null)")
})
run("number radix boundaries", function() {
assert_eq(number("10", 2), 2, "number base 2")
assert_eq(number("10", 36), 36, "number base 36")
assert_eq(number("z", 36), 35, "number z base 36")
})
run("number invalid for radix returns null", function() {
assert_eq(number("g", 16), null, "number('g',16)")
assert_eq(number("2", 2), null, "number('2',2)")
})
// --- object() edge cases ---
run("object copy empty", function() {
var copy = object({})
assert_eq(length(array(copy)), 0, "object copy empty")
})
run("object merge empty into filled", function() {
var result = object({a: 1}, {})
assert_eq(result.a, 1, "merge empty keeps a")
})
run("object merge filled into empty", function() {
var result = object({}, {b: 2})
assert_eq(result.b, 2, "merge into empty gets b")
})
run("object select missing key", function() {
var result = object({a: 1, b: 2}, ["a", "z"])
assert_eq(result.a, 1, "select present key")
assert_eq(result.z, null, "select missing key null")
})
run("object from empty keys", function() {
var r = object([])
assert_eq(length(array(r)), 0, "empty keys")
})
run("object from keys with null value", function() {
var r = object(["a", "b"], null)
assert_eq(r.a, null, "null value a")
assert_eq(r.b, null, "null value b")
})
// --- filter() edge cases ---
run("filter non-boolean return returns null", function() {
var result = filter([1, 2, 3], function(x) { return x })
assert_eq(result, null, "filter non-boolean returns null")
})
// filter with non-function: statically rejected by compiler (invoking int)
run("filter on number disrupts", function() {
if (!should_disrupt(function() { filter(42, function(x) { return true }) }))
fail("filter on number should disrupt")
})
run("filter with index callback", function() {
var result = filter([10, 20, 30, 40], function(el, i) { return i < 2 })
assert_eq(length(result), 2, "filter index length")
assert_eq(result[0], 10, "filter index [0]")
assert_eq(result[1], 20, "filter index [1]")
})
// --- find() edge cases ---
run("find value search exact match", function() {
assert_eq(find([10, 20, 30], 20), 1, "find value 20")
})
run("find value not present", function() {
assert_eq(find([10, 20, 30], 99), null, "find value missing")
})
run("find reverse value", function() {
assert_eq(find([1, 2, 1, 2], 1, true), 2, "find reverse value")
})
run("find with from index", function() {
assert_eq(find([1, 2, 3, 2, 1], 2, false, 2), 3, "find from index")
})
run("find predicate with index", function() {
assert_eq(find(["a", "bb", "ccc"], function(el, i) { return length(el) == 2 }), 1, "find predicate idx")
})
run("find empty array", function() {
assert_eq(find([], 1), null, "find empty")
assert_eq(find([], function(x) { return true }), null, "find empty fn")
})
run("find on number disrupts", function() {
if (!should_disrupt(function() { find(42, 4) }))
fail("find on number should disrupt")
})
// --- reduce() edge cases ---
run("reduce empty no initial returns null", function() {
assert_eq(reduce([], function(a, b) { return a + b }), null, "reduce empty")
})
run("reduce empty with initial returns initial", function() {
assert_eq(reduce([], function(a, b) { return a + b }, 99), 99, "reduce empty initial")
})
run("reduce single no initial returns element", function() {
assert_eq(reduce([42], function(a, b) { return a + b }), 42, "reduce single")
})
run("reduce single with initial", function() {
assert_eq(reduce([5], function(a, b) { return a + b }, 10), 15, "reduce single initial")
})
run("reduce reverse", function() {
var result = reduce([1, 2, 3], function(a, b) { return a - b }, 0, true)
// reverse: 0 - 3 = -3, -3 - 2 = -5, -5 - 1 = -6
assert_eq(result, -6, "reduce reverse")
})
// reduce with non-function: statically rejected by compiler (invoking int)
run("reduce on number disrupts", function() {
if (!should_disrupt(function() { reduce(42, function(a, b) { return a + b }) }))
fail("reduce on number should disrupt")
})
// --- sort() edge cases ---
run("sort empty array", function() {
var result = sort([])
assert_eq(length(result), 0, "sort empty")
})
run("sort single element", function() {
var result = sort([42])
assert_eq(result[0], 42, "sort single")
})
run("sort already sorted", function() {
var result = sort([1, 2, 3])
assert_eq(result[0], 1, "sort sorted [0]")
assert_eq(result[2], 3, "sort sorted [2]")
})
run("sort descending input", function() {
var result = sort([5, 4, 3, 2, 1])
assert_eq(result[0], 1, "sort desc [0]")
assert_eq(result[4], 5, "sort desc [4]")
})
run("sort strings", function() {
var result = sort(["cherry", "apple", "banana"])
assert_eq(result[0], "apple", "sort str [0]")
assert_eq(result[2], "cherry", "sort str [2]")
})
run("sort by field name", function() {
var items = [{n: "c", v: 3}, {n: "a", v: 1}, {n: "b", v: 2}]
var result = sort(items, "n")
assert_eq(result[0].n, "a", "sort field [0]")
assert_eq(result[2].n, "c", "sort 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][0], 1, "sort idx [0]")
assert_eq(result[2][0], 3, "sort idx [2]")
})
run("sort does not mutate original", function() {
var arr = [3, 1, 2]
var result = sort(arr)
assert_eq(arr[0], 3, "sort no mutate")
})
run("sort negative numbers", function() {
var result = sort([-5, 3, -1, 0, 2])
assert_eq(result[0], -5, "sort neg [0]")
assert_eq(result[4], 3, "sort neg [4]")
})
run("sort stable", function() {
var items = [{k: 1, v: "a"}, {k: 2, v: "b"}, {k: 1, v: "c"}]
var result = sort(items, "k")
assert_eq(result[0].v, "a", "sort stable [0]")
assert_eq(result[1].v, "c", "sort stable [1]")
assert_eq(result[2].v, "b", "sort stable [2]")
})
// --- reverse() edge cases ---
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 does not mutate", function() {
var arr = [1, 2, 3]
var rev = reverse(arr)
assert_eq(arr[0], 1, "reverse no mutate")
assert_eq(rev[0], 3, "reverse result [0]")
})
run("reverse text", function() {
var result = reverse("abc")
assert_eq(result, "cba", "reverse text")
})
run("reverse empty text", function() {
var result = reverse("")
assert_eq(result, "", "reverse empty text")
})
// --- every() / some() edge cases ---
run("every empty returns true", function() {
assert_eq(every([], function(x) { return false }), true, "every empty")
})
run("every all pass", function() {
assert_eq(every([2, 4, 6], function(x) { return x % 2 == 0 }), true, "every all")
})
run("every one fails", function() {
assert_eq(every([2, 3, 6], function(x) { return x % 2 == 0 }), false, "every one fail")
})
run("some empty returns false", function() {
assert_eq(some([], function(x) { return true }), false, "some empty")
})
run("some one match", function() {
assert_eq(some([1, 3, 5], function(x) { return x == 3 }), true, "some one match")
})
run("some no match", function() {
assert_eq(some([1, 3, 5], function(x) { return x > 10 }), false, "some no match")
})
// --- apply() edge cases ---
run("apply basic", function() {
var fn = function(a, b) { return a + b }
assert_eq(apply(fn, [3, 4]), 7, "apply basic")
})
run("apply no args", function() {
var fn = function() { return 42 }
assert_eq(apply(fn, []), 42, "apply no args")
})
run("apply non-function returns value", function() {
assert_eq(apply(42, [1, 2]), 42, "apply non-fn")
})
run("apply single arg", function() {
var fn = function(x) { return x * 2 }
assert_eq(apply(fn, [5]), 10, "apply single")
})
run("apply too many args disrupts", function() {
var fn = function(a) { return a }
if (!should_disrupt(function() { apply(fn, [1, 2, 3]) }))
fail("apply with too many args should disrupt")
})
// --- abs() edge cases ---
run("abs non-number returns null", function() {
assert_eq(abs("5"), null, "abs string")
assert_eq(abs(null), null, "abs null")
assert_eq(abs(true), null, "abs bool")
})
run("abs zero", function() {
assert_eq(abs(0), 0, "abs zero")
})
// --- sign() edge cases ---
run("sign non-number returns null", function() {
assert_eq(sign("5"), null, "sign string")
assert_eq(sign(null), null, "sign null")
})
// --- neg() edge cases ---
run("neg non-number returns null", function() {
assert_eq(neg("5"), null, "neg string")
assert_eq(neg(null), null, "neg null")
})
// --- not() edge cases ---
run("not non-logical returns null", function() {
assert_eq(not(0), null, "not 0")
assert_eq(not(1), null, "not 1")
assert_eq(not("true"), null, "not string")
assert_eq(not(null), null, "not null")
})
// --- min/max edge cases ---
run("min non-number returns null", function() {
assert_eq(min("3", 5), null, "min string")
assert_eq(min(3, "5"), null, "min string 2")
assert_eq(min(null, 5), null, "min null")
})
run("max non-number returns null", function() {
assert_eq(max("3", 5), null, "max string")
assert_eq(max(3, null), null, "max null")
})
// --- modulo() edge cases ---
run("modulo zero divisor returns null", function() {
assert_eq(modulo(10, 0), null, "modulo div 0")
})
run("modulo non-number returns null", function() {
assert_eq(modulo("10", 3), null, "modulo string")
assert_eq(modulo(10, "3"), null, "modulo string 2")
})
run("modulo zero dividend", function() {
assert_eq(modulo(0, 5), 0, "modulo 0 dividend")
})
// --- remainder() edge cases ---
run("remainder basic", function() {
assert_eq(remainder(7, 3), 1, "remainder 7,3")
assert_eq(remainder(-7, 3), -1, "remainder -7,3")
})
// --- floor/ceiling/round/trunc edge cases ---
run("floor integer passthrough", function() {
assert_eq(floor(5), 5, "floor int")
})
run("floor with place", function() {
assert_eq(floor(12.3775, -2), 12.37, "floor place -2")
assert_eq(floor(-12.3775, -2), -12.38, "floor neg place -2")
})
run("ceiling integer passthrough", function() {
assert_eq(ceiling(5), 5, "ceiling int")
})
run("ceiling with place", function() {
assert_eq(ceiling(12.3775, -2), 12.38, "ceiling place -2")
assert_eq(ceiling(-12.3775, -2), -12.37, "ceiling neg place -2")
})
run("round with place", function() {
assert_eq(round(12.3775, -2), 12.38, "round place -2")
})
run("trunc toward zero", function() {
assert_eq(trunc(3.7), 3, "trunc pos")
assert_eq(trunc(-3.7), -3, "trunc neg")
})
run("trunc with place", function() {
assert_eq(trunc(12.3775, -2), 12.37, "trunc place -2")
assert_eq(trunc(-12.3775, -2), -12.37, "trunc neg place -2")
})
// --- whole/fraction edge cases ---
run("whole non-number returns null", function() {
assert_eq(whole("5"), null, "whole string")
assert_eq(whole(null), null, "whole null")
})
run("fraction non-number returns null", function() {
assert_eq(fraction("5"), null, "fraction string")
assert_eq(fraction(null), null, "fraction null")
})
run("whole integer", function() {
assert_eq(whole(5), 5, "whole int")
assert_eq(whole(-5), -5, "whole neg int")
})
run("fraction integer returns zero", function() {
assert_eq(fraction(5), 0, "fraction int")
})
// --- lower/upper edge cases ---
run("lower empty string", function() {
assert_eq(lower(""), "", "lower empty")
})
run("upper empty string", function() {
assert_eq(upper(""), "", "upper empty")
})
// --- trim edge cases ---
run("trim with custom reject", function() {
assert_eq(trim("xxhelloxx", "x"), "hello", "trim custom reject")
})
// --- search() edge cases ---
run("search not found", function() {
assert_eq(search("hello", "xyz"), null, "search not found")
})
run("search empty text in empty text", function() {
assert_eq(search("", ""), 0, "search empty in empty")
})
run("search from negative index", function() {
assert_eq(search("hello world", "world", -5), 6, "search neg from")
})
// --- replace() edge cases ---
run("replace not found", function() {
assert_eq(replace("hello", "xyz", "abc"), "hello", "replace not found")
})
run("replace with limit 0", function() {
assert_eq(replace("aaa", "a", "b", 0), "aaa", "replace limit 0")
})
run("replace to empty", function() {
assert_eq(replace("hello", "l", ""), "heo", "replace to empty")
})
run("replace with function", function() {
var result = replace("abc", "b", function(match, pos) { return text(pos) })
assert_eq(result, "a1c", "replace fn")
})
// --- starts_with / ends_with edge cases ---
run("starts_with full match", function() {
assert_eq(starts_with("hello", "hello"), true, "starts_with full")
})
run("starts_with empty", function() {
assert_eq(starts_with("hello", ""), true, "starts_with empty")
})
run("ends_with full match", function() {
assert_eq(ends_with("hello", "hello"), true, "ends_with full")
})
run("ends_with empty", function() {
assert_eq(ends_with("hello", ""), true, "ends_with empty")
})
// --- stone() edge cases ---
run("stone array prevents modification", function() {
var arr = [1, 2, 3]
stone(arr)
if (!should_disrupt(function() { arr[0] = 99 }))
fail("modifying stone array should disrupt")
})
run("stone returns the value", function() {
var arr = [1, 2, 3]
var result = stone(arr)
assert_eq(result[0], 1, "stone returns value")
assert_eq(is_stone(result), true, "stone result is stone")
})
run("stone idempotent", function() {
var arr = [1, 2, 3]
stone(arr)
stone(arr)
assert_eq(is_stone(arr), true, "double stone ok")
})
run("stone primitives already stone", function() {
assert_eq(is_stone(42), true, "number is stone")
assert_eq(is_stone("hello"), true, "text is stone")
assert_eq(is_stone(true), true, "bool is stone")
assert_eq(is_stone(null), true, "null is stone")
})
// --- codepoint / character edge cases ---
run("codepoint non-text returns null", function() {
assert_eq(codepoint(65), null, "codepoint number")
assert_eq(codepoint(null), null, "codepoint null")
})
run("codepoint empty returns null", function() {
assert_eq(codepoint(""), null, "codepoint empty")
})
run("character negative returns empty", function() {
assert_eq(character(-1), "", "character -1")
})
run("character text returns first char", function() {
assert_eq(character("hello"), "h", "character text")
})
run("character roundtrip", function() {
assert_eq(codepoint(character(65)), 65, "roundtrip 65")
assert_eq(character(codepoint("A")), "A", "roundtrip A")
})
// --- sensory function edge cases ---
run("is_integer floats that are whole", function() {
assert_eq(is_integer(3.0), true, "is_integer 3.0")
assert_eq(is_integer(-5.0), true, "is_integer -5.0")
})
run("is_integer actual floats", function() {
assert_eq(is_integer(3.5), false, "is_integer 3.5")
assert_eq(is_integer(0.1), false, "is_integer 0.1")
})
run("is_integer non-numbers", function() {
assert_eq(is_integer("3"), false, "is_integer string")
assert_eq(is_integer(null), false, "is_integer null")
assert_eq(is_integer(true), false, "is_integer bool")
})
run("is_character edge cases", function() {
assert_eq(is_character(""), false, "is_character empty")
assert_eq(is_character("ab"), false, "is_character multi")
assert_eq(is_character("x"), true, "is_character single")
assert_eq(is_character(65), false, "is_character number")
})
run("is_digit edge cases", function() {
assert_eq(is_digit("0"), true, "is_digit 0")
assert_eq(is_digit("9"), true, "is_digit 9")
assert_eq(is_digit("a"), false, "is_digit a")
assert_eq(is_digit(""), false, "is_digit empty")
assert_eq(is_digit("10"), false, "is_digit multi")
})
run("is_letter edge cases", function() {
assert_eq(is_letter("a"), true, "is_letter a")
assert_eq(is_letter("Z"), true, "is_letter Z")
assert_eq(is_letter("0"), false, "is_letter 0")
assert_eq(is_letter(""), false, "is_letter empty")
assert_eq(is_letter("ab"), false, "is_letter multi")
})
run("is_lower edge cases", function() {
assert_eq(is_lower("a"), true, "is_lower a")
assert_eq(is_lower("A"), false, "is_lower A")
assert_eq(is_lower("1"), false, "is_lower 1")
assert_eq(is_lower(""), false, "is_lower empty")
})
run("is_upper edge cases", function() {
assert_eq(is_upper("A"), true, "is_upper A")
assert_eq(is_upper("a"), false, "is_upper a")
assert_eq(is_upper("1"), false, "is_upper 1")
assert_eq(is_upper(""), false, "is_upper empty")
})
run("is_whitespace edge cases", function() {
assert_eq(is_whitespace(" "), true, "is_whitespace space")
assert_eq(is_whitespace("\t"), true, "is_whitespace tab")
assert_eq(is_whitespace("\n"), true, "is_whitespace newline")
assert_eq(is_whitespace(" "), true, "is_whitespace multi space")
assert_eq(is_whitespace("\r\n"), true, "is_whitespace crlf")
assert_eq(is_whitespace(" \t\n"), true, "is_whitespace mixed")
assert_eq(is_whitespace(""), false, "is_whitespace empty")
assert_eq(is_whitespace("a"), false, "is_whitespace letter")
assert_eq(is_whitespace(32), false, "is_whitespace number")
assert_eq(is_whitespace(" a"), false, "is_whitespace space-letter")
})
run("is_data edge cases", function() {
// is_data: true for text, number, logical, array, blob, record
// false for function and null
assert_eq(is_data({}), true, "is_data record")
assert_eq(is_data("hello"), true, "is_data text")
assert_eq(is_data(42), true, "is_data number")
assert_eq(is_data(true), true, "is_data bool")
assert_eq(is_data([]), true, "is_data array")
assert_eq(is_data(null), false, "is_data null")
assert_eq(is_data(function() {}), false, "is_data function")
})
// --- format() edge cases ---
run("format basic array", function() {
var result = format("{0} in {1}!", ["Malmborg", "Plano"])
assert_eq(result, "Malmborg in Plano!", "format basic")
})
run("format record", function() {
var result = format("{name} is {age}", {name: "Alice", age: "30"})
assert_eq(result, "Alice is 30", "format record")
})
run("format missing key unchanged", function() {
var result = format("{0} and {1}", ["hello"])
assert_eq(starts_with(result, "hello"), true, "format missing keeps prefix")
})
// --- arrfor edge cases ---
run("arrfor reverse with exit", function() {
var visited = []
var result = arrfor([10, 20, 30], function(x) {
visited[] = x
if (x == 20) return true
return null
}, true, true)
assert_eq(result, true, "arrfor rev exit returns exit value")
assert_eq(visited[0], 30, "arrfor rev exit started from end")
})
run("arrfor with index", function() {
var indices = []
arrfor([10, 20, 30], function(x, i) { indices[] = i })
assert_eq(indices[0], 0, "arrfor idx [0]")
assert_eq(indices[2], 2, "arrfor idx [2]")
})
// ============================================================================
// SENSORY FUNCTION COMPLETENESS
// ============================================================================
run("is_true completeness", function() {
assert_eq(is_true(true), true, "is_true true")
assert_eq(is_true(false), false, "is_true false")
assert_eq(is_true(1), false, "is_true 1")
assert_eq(is_true(null), false, "is_true null")
assert_eq(is_true("true"), false, "is_true text")
})
run("is_false completeness", function() {
assert_eq(is_false(false), true, "is_false false")
assert_eq(is_false(true), false, "is_false true")
assert_eq(is_false(0), false, "is_false 0")
assert_eq(is_false(null), false, "is_false null")
})
run("is_fit completeness", function() {
assert_eq(is_fit(0), true, "is_fit 0")
assert_eq(is_fit(42), true, "is_fit 42")
assert_eq(is_fit(-100), true, "is_fit -100")
assert_eq(is_fit(3.0), true, "is_fit 3.0")
assert_eq(is_fit(3.5), false, "is_fit 3.5")
assert_eq(is_fit("3"), false, "is_fit text")
assert_eq(is_fit(null), false, "is_fit null")
assert_eq(is_fit(true), false, "is_fit bool")
})
run("is_character completeness", function() {
assert_eq(is_character("a"), true, "is_character a")
assert_eq(is_character("Z"), true, "is_character Z")
assert_eq(is_character("ab"), false, "is_character ab")
assert_eq(is_character(""), false, "is_character empty")
assert_eq(is_character(65), false, "is_character number")
assert_eq(is_character(null), false, "is_character null")
})
run("is_digit completeness", function() {
assert_eq(is_digit("0"), true, "is_digit 0")
assert_eq(is_digit("9"), true, "is_digit 9")
assert_eq(is_digit("a"), false, "is_digit a")
assert_eq(is_digit("55"), false, "is_digit 55")
assert_eq(is_digit(5), false, "is_digit num")
assert_eq(is_digit(null), false, "is_digit null")
})
run("is_letter completeness", function() {
assert_eq(is_letter("a"), true, "is_letter a")
assert_eq(is_letter("Z"), true, "is_letter Z")
assert_eq(is_letter("5"), false, "is_letter 5")
assert_eq(is_letter("ab"), false, "is_letter ab")
assert_eq(is_letter(65), false, "is_letter number")
})
run("is_lower completeness", function() {
assert_eq(is_lower("a"), true, "is_lower a")
assert_eq(is_lower("z"), true, "is_lower z")
assert_eq(is_lower("A"), false, "is_lower A")
assert_eq(is_lower("5"), false, "is_lower 5")
assert_eq(is_lower("ab"), false, "is_lower ab")
})
run("is_upper completeness", function() {
assert_eq(is_upper("A"), true, "is_upper A")
assert_eq(is_upper("Z"), true, "is_upper Z")
assert_eq(is_upper("a"), false, "is_upper a")
assert_eq(is_upper("5"), false, "is_upper 5")
assert_eq(is_upper("AB"), false, "is_upper AB")
})
// ============================================================================
// CREATOR FUNCTION WRONG-ARGUMENT DISRUPTIONS
// ============================================================================
run("array float index disrupts", function() {
assert_eq(should_disrupt(function() { array([1,2,3], 1.5, 3) }), true, "array float from")
assert_eq(should_disrupt(function() { array([1,2,3], 0, 2.5) }), true, "array float to")
})
run("number invalid radix", function() {
assert_eq(number("ff", 1), null, "radix 1 too low")
assert_eq(number("ff", 38), null, "radix 38 too high")
})
run("number invalid text", function() {
assert_eq(number("abc"), null, "number abc")
assert_eq(number(""), null, "number empty")
})
run("object invalid input", function() {
assert_eq(object(42), null, "object number")
assert_eq(object(null), null, "object null")
})
// ============================================================================
// STANDARD FUNCTION EDGE CASES
// ============================================================================
run("abs/sign/floor/ceiling with non-number", function() {
assert_eq(abs("hello"), null, "abs text")
assert_eq(sign("hello"), null, "sign text")
assert_eq(floor("hello"), null, "floor text")
assert_eq(ceiling("hello"), null, "ceiling text")
assert_eq(round("hello"), null, "round text")
assert_eq(trunc("hello"), null, "trunc text")
})
run("min/max with non-number", function() {
assert_eq(min(1, "b"), null, "min text arg")
assert_eq(max(null, 5), null, "max null arg")
})
run("not with non-logical", function() {
assert_eq(not(42), null, "not number")
assert_eq(not("true"), null, "not text")
})
run("whole and fraction", function() {
assert_eq(whole(3.7), 3, "whole 3.7")
assert_eq(whole(-3.7), -3, "whole -3.7")
assert_eq(whole(42), 42, "whole 42")
assert_eq(whole("hi"), null, "whole text")
var f1 = fraction(3.7)
if (f1 < 0.69 || f1 > 0.71) fail("fraction 3.7 should be ~0.7")
var f2 = fraction(-3.7)
if (f2 > -0.69 || f2 < -0.71) fail("fraction -3.7 should be ~-0.7")
assert_eq(fraction(42), 0, "fraction 42")
assert_eq(fraction("hi"), null, "fraction text")
})
run("logical comprehensive", function() {
assert_eq(logical(0), false, "logical 0")
assert_eq(logical(1), true, "logical 1")
assert_eq(logical(false), false, "logical false")
assert_eq(logical(true), true, "logical true")
assert_eq(logical("false"), false, "logical text false")
assert_eq(logical("true"), true, "logical text true")
assert_eq(logical(null), false, "logical null")
assert_eq(logical(42), null, "logical 42")
assert_eq(logical("maybe"), null, "logical maybe")
})
run("normalize basic", function() {
assert_eq(normalize("hello"), "hello", "normalize ascii")
assert_eq(normalize(42), null, "normalize non-text")
assert_eq(normalize(null), null, "normalize null")
assert_eq(normalize(""), "", "normalize empty")
})
run("starts_with / ends_with comprehensive", function() {
assert_eq(starts_with("hello world", "hello"), true, "starts_with match")
assert_eq(starts_with("hello world", "world"), false, "starts_with no match")
assert_eq(starts_with("hello", ""), true, "starts_with empty prefix")
assert_eq(starts_with("hello", "hello"), true, "starts_with full match")
assert_eq(ends_with("hello world", "world"), true, "ends_with match")
assert_eq(ends_with("hello world", "hello"), false, "ends_with no match")
assert_eq(ends_with("test", "test"), true, "ends_with full match")
})
run("every and some", function() {
var gt0 = function(x) { return x > 0 }
assert_eq(every([1, 2, 3], gt0), true, "every all pass")
assert_eq(every([1, -1, 3], gt0), false, "every one fail")
assert_eq(every([], gt0), true, "every empty")
assert_eq(some([1, 2, 3], gt0), true, "some one match")
assert_eq(some([-1, -2, -3], gt0), false, "some no match")
assert_eq(some([], gt0), false, "some empty")
})
run("reduce edge cases", function() {
assert_eq(reduce([], function(a, b) { return a + b }), null, "reduce empty")
assert_eq(reduce([42], function(a, b) { return a + b }), 42, "reduce single")
assert_eq(reduce([1, 2, 3], function(a, b) { return a + b }), 6, "reduce sum")
})
run("filter on non-array disrupts", function() {
assert_eq(should_disrupt(function() {
filter(42, function(x) { return true })
}), true, "filter non-array disrupts")
})
run("find edge cases", function() {
assert_eq(find([1, 2, 3], function(x) { return x == 2 }), 1, "find match index")
assert_eq(find([1, 2, 3], function(x) { return x == 5 }), null, "find no match")
assert_eq(find([], function(x) { return true }), null, "find empty")
assert_eq(find([10, 20, 30], 20), 1, "find value mode index")
})
run("format edge cases", function() {
var r1 = format("{0} and {1}", ["hello", "world"])
assert_eq(r1, "hello and world", "format array basic")
var r2 = format("{name}", {name: "Alice"})
assert_eq(r2, "Alice", "format record basic")
var r3 = format("{missing}", {})
assert_eq(r3, "null", "format missing key")
})
// ============================================================================
// DUAL-PATH TESTING (MCODE vs C FALLBACK)
// ============================================================================
run("is_digit dual path", function() {
// Direct call tests mcode-lowered opcode
assert_eq(is_digit("5"), true, "is_digit direct true")
assert_eq(is_digit("a"), false, "is_digit direct false")
// As callback tests C fallback
var items = ["5", "a", "3"]
var result = filter(items, is_digit)
assert_eq(length(result), 2, "is_digit filter count")
assert_eq(result[0], "5", "is_digit filter [0]")
assert_eq(result[1], "3", "is_digit filter [1]")
})
run("is_letter dual path", function() {
assert_eq(is_letter("a"), true, "is_letter direct true")
assert_eq(is_letter("5"), false, "is_letter direct false")
var items = ["a", "5", "Z"]
var result = filter(items, is_letter)
assert_eq(length(result), 2, "is_letter filter count")
})
run("is_upper dual path", function() {
assert_eq(is_upper("A"), true, "is_upper direct true")
assert_eq(is_upper("a"), false, "is_upper direct false")
var items = ["A", "a", "Z"]
var result = filter(items, is_upper)
assert_eq(length(result), 2, "is_upper filter count")
})
run("is_lower dual path", function() {
assert_eq(is_lower("a"), true, "is_lower direct true")
assert_eq(is_lower("A"), false, "is_lower direct false")
var items = ["a", "A", "z"]
var result = filter(items, is_lower)
assert_eq(length(result), 2, "is_lower filter count")
})
run("is_whitespace dual path", function() {
assert_eq(is_whitespace(" "), true, "is_whitespace direct true")
assert_eq(is_whitespace("a"), false, "is_whitespace direct false")
var items = [" ", "a", "\t", "b"]
var result = filter(items, is_whitespace)
assert_eq(length(result), 2, "is_whitespace filter count")
})
run("is_number dual path", function() {
assert_eq(is_number(42), true, "is_number direct true")
assert_eq(is_number("42"), false, "is_number direct false")
var items = [42, "hello", 3.14, null]
var result = filter(items, is_number)
assert_eq(length(result), 2, "is_number filter count")
})
run("is_text dual path", function() {
assert_eq(is_text("hello"), true, "is_text direct true")
assert_eq(is_text(42), false, "is_text direct false")
var items = ["a", 42, "b", null]
var result = filter(items, is_text)
assert_eq(length(result), 2, "is_text filter count")
})
run("is_null dual path", function() {
assert_eq(is_null(null), true, "is_null direct true")
assert_eq(is_null(0), false, "is_null direct false")
var items = [null, 1, null, "x"]
var result = filter(items, is_null)
assert_eq(length(result), 2, "is_null filter count")
})
// ============================================================================
// ARRFOR DISRUPTION TESTS
// ============================================================================
run("arrfor reverse non-boolean disrupts", function() {
assert_eq(should_disrupt(function() {
arrfor([1, 2], function(x) {}, 1)
}), true, "arrfor reverse must be logical")
})
run("find reverse non-boolean disrupts", function() {
assert_eq(should_disrupt(function() {
find([1, 2], function(x) { return true }, 1)
}), true, "find reverse must be logical")
})
run("reduce reverse non-boolean disrupts", function() {
assert_eq(should_disrupt(function() {
reduce([1, 2, 3], function(a, b) { return a + b }, null, 1)
}), true, "reduce reverse must be logical")
})
// ============================================================================
// IS_STONE BEHAVIOR
// ============================================================================
run("is_stone primitives", function() {
assert_eq(is_stone(42), true, "is_stone number")
assert_eq(is_stone("hello"), true, "is_stone text")
assert_eq(is_stone(null), true, "is_stone null")
assert_eq(is_stone(true), true, "is_stone bool")
assert_eq(is_stone(false), true, "is_stone false")
})
run("is_stone mutable objects", function() {
assert_eq(is_stone([1, 2]), false, "is_stone array")
assert_eq(is_stone({x: 1}), false, "is_stone record")
})
run("is_stone frozen objects", function() {
var a = stone([1, 2])
assert_eq(is_stone(a), true, "is_stone stoned array")
var r = stone({x: 1})
assert_eq(is_stone(r), true, "is_stone stoned record")
})
// ============================================================================
// SUMMARY
// ============================================================================
log.test(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
var _j = 0
if (failed > 0) {
log.test("")
for (_j = 0; _j < failed; _j++) {
log.error(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
}
}