658 lines
15 KiB
Plaintext
658 lines
15 KiB
Plaintext
// Syntax suite: covers every syntactic feature of cell script
|
|
// Run: ./cell --mach-run syntax_suite.ce
|
|
|
|
var passed = 0
|
|
var failed = 0
|
|
var error_names = []
|
|
var error_reasons = []
|
|
var fail_msg = ""
|
|
|
|
for (var _i = 0; _i < 100; _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) + " expected=" + text(expected) + ")")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// === LITERALS ===
|
|
|
|
run("number literals", function() {
|
|
assert_eq(42, 42, "integer")
|
|
assert_eq(3.14 > 3, true, "float")
|
|
assert_eq(-5, -5, "negative")
|
|
assert_eq(0, 0, "zero")
|
|
assert_eq(1e3, 1000, "scientific")
|
|
})
|
|
|
|
run("string literals", function() {
|
|
assert_eq("hello", "hello", "double quote")
|
|
assert_eq("", "", "empty string")
|
|
assert_eq("line1\nline2" != "line1line2", true, "escape sequence")
|
|
})
|
|
|
|
run("template literals", function() {
|
|
var x = "world"
|
|
assert_eq(`hello ${x}`, "hello world", "interpolation")
|
|
assert_eq(`${1 + 2}`, "3", "expression interpolation")
|
|
assert_eq(`plain`, "plain", "no interpolation")
|
|
})
|
|
|
|
run("boolean literals", function() {
|
|
assert_eq(true, true, "true")
|
|
assert_eq(false, false, "false")
|
|
})
|
|
|
|
run("null literal", function() {
|
|
assert_eq(null, null, "null")
|
|
})
|
|
|
|
run("array literal", function() {
|
|
var a = [1, 2, 3]
|
|
assert_eq(length(a), 3, "array length")
|
|
assert_eq(a[0], 1, "first element")
|
|
var e = []
|
|
assert_eq(length(e), 0, "empty array")
|
|
})
|
|
|
|
run("object literal", function() {
|
|
var o = {a: 1, b: "two"}
|
|
assert_eq(o.a, 1, "object prop")
|
|
var e = {}
|
|
assert_eq(e.x, null, "empty object missing prop")
|
|
})
|
|
|
|
run("regex literal", function() {
|
|
var r = /\d+/
|
|
var result = extract("abc123", r)
|
|
assert_eq(result[0], "123", "regex match")
|
|
var ri = /hello/i
|
|
var result2 = extract("Hello", ri)
|
|
assert_eq(result2[0], "Hello", "regex flags")
|
|
})
|
|
|
|
// === DECLARATIONS ===
|
|
|
|
run("var declaration", function() {
|
|
var x = 5
|
|
assert_eq(x, 5, "var init")
|
|
})
|
|
|
|
run("var uninitialized", function() {
|
|
var x
|
|
assert_eq(x, null, "var defaults to null")
|
|
})
|
|
|
|
run("var multiple declaration", function() {
|
|
var a = 1, b = 2, c = 3
|
|
assert_eq(a + b + c, 6, "multi var")
|
|
})
|
|
|
|
run("def declaration", function() {
|
|
def x = 42
|
|
assert_eq(x, 42, "def const")
|
|
})
|
|
|
|
// === ARITHMETIC OPERATORS ===
|
|
|
|
run("arithmetic operators", function() {
|
|
assert_eq(2 + 3, 5, "add")
|
|
assert_eq(5 - 3, 2, "sub")
|
|
assert_eq(3 * 4, 12, "mul")
|
|
assert_eq(12 / 4, 3, "div")
|
|
assert_eq(10 % 3, 1, "mod")
|
|
assert_eq(2 ** 3, 8, "exp")
|
|
})
|
|
|
|
// === COMPARISON OPERATORS ===
|
|
|
|
run("comparison operators", function() {
|
|
assert_eq(5 == 5, true, "eq")
|
|
assert_eq(5 != 6, true, "neq")
|
|
assert_eq(3 < 5, true, "lt")
|
|
assert_eq(5 > 3, true, "gt")
|
|
assert_eq(3 <= 3, true, "lte")
|
|
assert_eq(5 >= 5, true, "gte")
|
|
})
|
|
|
|
// === LOGICAL OPERATORS ===
|
|
|
|
run("logical operators", function() {
|
|
assert_eq(true && true, true, "and")
|
|
assert_eq(true && false, false, "and false")
|
|
assert_eq(false || true, true, "or")
|
|
assert_eq(false || false, false, "or false")
|
|
assert_eq(!true, false, "not")
|
|
assert_eq(!false, true, "not false")
|
|
})
|
|
|
|
run("short circuit", function() {
|
|
var called = false
|
|
var fn = function() { called = true; return true }
|
|
var r = false && fn()
|
|
assert_eq(called, false, "and short circuit")
|
|
r = true || fn()
|
|
assert_eq(called, false, "or short circuit")
|
|
})
|
|
|
|
// === BITWISE OPERATORS ===
|
|
|
|
run("bitwise operators", function() {
|
|
assert_eq(5 & 3, 1, "and")
|
|
assert_eq(5 | 3, 7, "or")
|
|
assert_eq(5 ^ 3, 6, "xor")
|
|
assert_eq(~0, -1, "not")
|
|
assert_eq(1 << 3, 8, "lshift")
|
|
assert_eq(8 >> 3, 1, "rshift")
|
|
assert_eq(-1 >>> 1, 2147483647, "unsigned rshift")
|
|
})
|
|
|
|
// === UNARY OPERATORS ===
|
|
|
|
run("unary operators", function() {
|
|
assert_eq(+5, 5, "unary plus")
|
|
assert_eq(-5, -5, "unary minus")
|
|
assert_eq(-(-5), 5, "double negate")
|
|
})
|
|
|
|
run("increment decrement", function() {
|
|
var x = 5
|
|
assert_eq(x++, 5, "postfix inc returns old")
|
|
assert_eq(x, 6, "postfix inc side effect")
|
|
x = 5
|
|
assert_eq(++x, 6, "prefix inc returns new")
|
|
x = 5
|
|
assert_eq(x--, 5, "postfix dec returns old")
|
|
assert_eq(x, 4, "postfix dec side effect")
|
|
x = 5
|
|
assert_eq(--x, 4, "prefix dec returns new")
|
|
})
|
|
|
|
// === COMPOUND ASSIGNMENT ===
|
|
|
|
run("compound assignment", function() {
|
|
var x = 10
|
|
x += 3; assert_eq(x, 13, "+=")
|
|
x -= 3; assert_eq(x, 10, "-=")
|
|
x *= 2; assert_eq(x, 20, "*=")
|
|
x /= 4; assert_eq(x, 5, "/=")
|
|
x %= 3; assert_eq(x, 2, "%=")
|
|
})
|
|
|
|
// === TERNARY OPERATOR ===
|
|
|
|
run("ternary operator", function() {
|
|
var a = true ? 1 : 2
|
|
assert_eq(a, 1, "ternary true")
|
|
var b = false ? 1 : 2
|
|
assert_eq(b, 2, "ternary false")
|
|
var c = true ? (false ? 1 : 2) : 3
|
|
assert_eq(c, 2, "ternary nested")
|
|
})
|
|
|
|
// === COMMA OPERATOR ===
|
|
|
|
run("comma operator", function() {
|
|
var x = (1, 2, 3)
|
|
assert_eq(x, 3, "comma returns last")
|
|
})
|
|
|
|
// === IN OPERATOR ===
|
|
|
|
run("in operator", function() {
|
|
var o = {a: 1}
|
|
assert_eq("a" in o, true, "key exists")
|
|
assert_eq("b" in o, false, "key missing")
|
|
})
|
|
|
|
// === DELETE OPERATOR ===
|
|
|
|
run("delete operator", function() {
|
|
var o = {a: 1, b: 2}
|
|
delete o.a
|
|
assert_eq("a" in o, false, "delete removes key")
|
|
assert_eq(o.b, 2, "delete leaves others")
|
|
})
|
|
|
|
// === PROPERTY ACCESS ===
|
|
|
|
run("dot access", function() {
|
|
var o = {x: 10}
|
|
assert_eq(o.x, 10, "dot read")
|
|
o.x = 20
|
|
assert_eq(o.x, 20, "dot write")
|
|
})
|
|
|
|
run("bracket access", function() {
|
|
var o = {x: 10}
|
|
assert_eq(o["x"], 10, "bracket read")
|
|
var key = "x"
|
|
assert_eq(o[key], 10, "computed bracket")
|
|
o["y"] = 20
|
|
assert_eq(o.y, 20, "bracket write")
|
|
})
|
|
|
|
run("object-as-key", function() {
|
|
var k = {}
|
|
var o = {}
|
|
o[k] = 42
|
|
assert_eq(o[k], 42, "object key set/get")
|
|
assert_eq(o[{}], null, "new object is different key")
|
|
assert_eq(k in o, true, "object key in")
|
|
delete o[k]
|
|
assert_eq(k in o, false, "object key delete")
|
|
})
|
|
|
|
run("chained access", function() {
|
|
var d = {a: {b: [1, {c: 99}]}}
|
|
assert_eq(d.a.b[1].c, 99, "mixed chain")
|
|
})
|
|
|
|
// === ARRAY PUSH/POP SYNTAX ===
|
|
|
|
run("array push pop", function() {
|
|
var a = [1, 2]
|
|
a[] = 3
|
|
assert_eq(length(a), 3, "push length")
|
|
assert_eq(a[2], 3, "push value")
|
|
var v = a[]
|
|
assert_eq(v, 3, "pop value")
|
|
assert_eq(length(a), 2, "pop length")
|
|
})
|
|
|
|
// === CONTROL FLOW: IF/ELSE ===
|
|
|
|
run("if else", function() {
|
|
var x = 0
|
|
if (true) x = 1
|
|
assert_eq(x, 1, "if true")
|
|
if (false) x = 2 else x = 3
|
|
assert_eq(x, 3, "if else")
|
|
if (false) x = 4
|
|
else if (true) x = 5
|
|
else x = 6
|
|
assert_eq(x, 5, "else if")
|
|
})
|
|
|
|
// === CONTROL FLOW: WHILE ===
|
|
|
|
run("while loop", function() {
|
|
var i = 0
|
|
while (i < 5) i++
|
|
assert_eq(i, 5, "while basic")
|
|
})
|
|
|
|
run("while break continue", function() {
|
|
var i = 0
|
|
while (true) {
|
|
if (i >= 3) break
|
|
i++
|
|
}
|
|
assert_eq(i, 3, "while break")
|
|
var sum = 0
|
|
i = 0
|
|
while (i < 5) {
|
|
i++
|
|
if (i % 2 == 0) continue
|
|
sum += i
|
|
}
|
|
assert_eq(sum, 9, "while continue")
|
|
})
|
|
|
|
// === CONTROL FLOW: FOR ===
|
|
|
|
run("for loop", function() {
|
|
var sum = 0
|
|
for (var i = 0; i < 5; i++) sum += i
|
|
assert_eq(sum, 10, "for basic")
|
|
})
|
|
|
|
run("for break continue", function() {
|
|
var sum = 0
|
|
for (var i = 0; i < 10; i++) {
|
|
if (i == 5) break
|
|
sum += i
|
|
}
|
|
assert_eq(sum, 10, "for break")
|
|
sum = 0
|
|
for (var i = 0; i < 5; i++) {
|
|
if (i % 2 == 0) continue
|
|
sum += i
|
|
}
|
|
assert_eq(sum, 4, "for continue")
|
|
})
|
|
|
|
run("nested for", function() {
|
|
var sum = 0
|
|
for (var i = 0; i < 3; i++)
|
|
for (var j = 0; j < 3; j++)
|
|
sum++
|
|
assert_eq(sum, 9, "nested for")
|
|
})
|
|
|
|
// === BLOCK SCOPING ===
|
|
|
|
run("block scoping", function() {
|
|
var x = 1
|
|
{
|
|
var x = 2
|
|
assert_eq(x, 2, "inner block")
|
|
}
|
|
assert_eq(x, 1, "outer preserved")
|
|
})
|
|
|
|
run("for iterator scope", function() {
|
|
for (var i = 0; i < 1; i++) {}
|
|
assert_eq(should_disrupt(function() { var y = i }), true, "for var does not leak")
|
|
})
|
|
|
|
// === FUNCTIONS ===
|
|
|
|
run("function expression", function() {
|
|
var fn = function(a, b) { return a + b }
|
|
assert_eq(fn(2, 3), 5, "basic call")
|
|
})
|
|
|
|
run("arrow function", function() {
|
|
var double = x => x * 2
|
|
assert_eq(double(5), 10, "arrow single param")
|
|
var add = (a, b) => a + b
|
|
assert_eq(add(2, 3), 5, "arrow multi param")
|
|
var block = x => {
|
|
var y = x * 2
|
|
return y + 1
|
|
}
|
|
assert_eq(block(5), 11, "arrow block body")
|
|
})
|
|
|
|
run("function no return", function() {
|
|
var fn = function() { var x = 1 }
|
|
assert_eq(fn(), null, "no return gives null")
|
|
})
|
|
|
|
run("function early return", function() {
|
|
var fn = function() { return 1; return 2 }
|
|
assert_eq(fn(), 1, "early return")
|
|
})
|
|
|
|
run("extra and missing args", function() {
|
|
var fn = function(a, b) { return a + b }
|
|
assert_eq(fn(1, 2, 3), 3, "extra args ignored")
|
|
var fn2 = function(a, b) { return a }
|
|
assert_eq(fn2(1), 1, "missing args ok")
|
|
})
|
|
|
|
run("iife", function() {
|
|
var r = (function(x) { return x * 2 })(21)
|
|
assert_eq(r, 42, "immediately invoked")
|
|
})
|
|
|
|
// === CLOSURES ===
|
|
|
|
run("closure", function() {
|
|
var make = function(x) {
|
|
return function(y) { return x + y }
|
|
}
|
|
var add5 = make(5)
|
|
assert_eq(add5(3), 8, "closure captures")
|
|
})
|
|
|
|
run("closure mutation", function() {
|
|
var counter = function() {
|
|
var n = 0
|
|
return function() { n = n + 1; return n }
|
|
}
|
|
var c = counter()
|
|
assert_eq(c(), 1, "first")
|
|
assert_eq(c(), 2, "second")
|
|
})
|
|
|
|
// === RECURSION ===
|
|
|
|
run("recursion", function() {
|
|
var fact = function(n) {
|
|
if (n <= 1) return 1
|
|
return n * fact(n - 1)
|
|
}
|
|
assert_eq(fact(5), 120, "factorial")
|
|
})
|
|
|
|
// === THIS BINDING ===
|
|
|
|
run("this binding", function() {
|
|
var obj = {
|
|
val: 10,
|
|
get: function() { return this.val }
|
|
}
|
|
assert_eq(obj.get(), 10, "method this")
|
|
})
|
|
|
|
// === DISRUPTION ===
|
|
|
|
run("disrupt keyword", function() {
|
|
assert_eq(should_disrupt(function() { disrupt }), true, "bare disrupt")
|
|
})
|
|
|
|
run("disruption handler", function() {
|
|
var x = 0
|
|
var fn = function() { x = 1 } disruption { x = 2 }
|
|
fn()
|
|
assert_eq(x, 1, "no disruption path")
|
|
var fn2 = function() { disrupt } disruption { x = 3 }
|
|
fn2()
|
|
assert_eq(x, 3, "disruption caught")
|
|
})
|
|
|
|
run("disruption re-raise", function() {
|
|
var outer_caught = false
|
|
var outer = function() {
|
|
var inner = function() { disrupt } disruption { disrupt }
|
|
inner()
|
|
} disruption {
|
|
outer_caught = true
|
|
}
|
|
outer()
|
|
assert_eq(outer_caught, true, "re-raise propagates")
|
|
})
|
|
|
|
// === PROTOTYPAL INHERITANCE ===
|
|
|
|
run("meme and proto", function() {
|
|
var parent = {x: 10}
|
|
var child = meme(parent)
|
|
assert_eq(child.x, 10, "inherited prop")
|
|
assert_eq(proto(child), parent, "proto returns parent")
|
|
child.x = 20
|
|
assert_eq(parent.x, 10, "override does not mutate parent")
|
|
})
|
|
|
|
run("meme with mixins", function() {
|
|
var p = {a: 1}
|
|
var m1 = {b: 2}
|
|
var m2 = {c: 3}
|
|
var child = meme(p, [m1, m2])
|
|
assert_eq(child.a, 1, "parent prop")
|
|
assert_eq(child.b, 2, "mixin1")
|
|
assert_eq(child.c, 3, "mixin2")
|
|
})
|
|
|
|
// === STONE (FREEZE) ===
|
|
|
|
run("stone", function() {
|
|
var o = {x: 1}
|
|
assert_eq(is_stone(o), false, "not frozen")
|
|
stone(o)
|
|
assert_eq(is_stone(o), true, "frozen")
|
|
assert_eq(should_disrupt(function() { o.x = 2 }), true, "write disrupts")
|
|
})
|
|
|
|
// === FUNCTION PROXY ===
|
|
|
|
run("function proxy", function() {
|
|
var proxy = function(name, args) {
|
|
return `${name}:${length(args)}`
|
|
}
|
|
assert_eq(proxy.hello(), "hello:0", "proxy dot call")
|
|
assert_eq(proxy.add(1, 2), "add:2", "proxy with args")
|
|
assert_eq(proxy["method"](), "method:0", "proxy bracket call")
|
|
var m = "dynamic"
|
|
assert_eq(proxy[m](), "dynamic:0", "proxy computed name")
|
|
})
|
|
|
|
run("non-proxy function prop access disrupts", function() {
|
|
var fn = function() { return 1 }
|
|
assert_eq(should_disrupt(function() { var x = fn.foo }), true, "prop read disrupts")
|
|
assert_eq(should_disrupt(function() { fn.foo = 1 }), true, "prop write disrupts")
|
|
})
|
|
|
|
// === TYPE CHECKING ===
|
|
|
|
run("is_* functions", function() {
|
|
assert_eq(is_number(42), true, "is_number")
|
|
assert_eq(is_text("hi"), true, "is_text")
|
|
assert_eq(is_logical(true), true, "is_logical")
|
|
assert_eq(is_object({}), true, "is_object")
|
|
assert_eq(is_array([]), true, "is_array")
|
|
assert_eq(is_function(function(){}), true, "is_function")
|
|
assert_eq(is_null(null), true, "is_null")
|
|
assert_eq(is_object([]), false, "array not object")
|
|
assert_eq(is_array({}), false, "object not array")
|
|
})
|
|
|
|
// === TRUTHINESS / FALSINESS ===
|
|
|
|
run("falsy values", function() {
|
|
if (false) fail("false")
|
|
if (0) fail("0")
|
|
if ("") fail("empty string")
|
|
if (null) fail("null")
|
|
assert_eq(true, true, "all falsy passed")
|
|
})
|
|
|
|
run("truthy values", function() {
|
|
if (!1) fail("1")
|
|
if (!"hi") fail("string")
|
|
if (!{}) fail("object")
|
|
if (![]) fail("array")
|
|
if (!true) fail("true")
|
|
assert_eq(true, true, "all truthy passed")
|
|
})
|
|
|
|
// === VARIABLE SHADOWING ===
|
|
|
|
run("variable shadowing", function() {
|
|
var x = 10
|
|
var fn = function() {
|
|
var x = 20
|
|
return x
|
|
}
|
|
assert_eq(fn(), 20, "inner shadows")
|
|
assert_eq(x, 10, "outer unchanged")
|
|
})
|
|
|
|
// === OPERATOR PRECEDENCE ===
|
|
|
|
run("precedence", function() {
|
|
assert_eq(2 + 3 * 4, 14, "mul before add")
|
|
assert_eq((2 + 3) * 4, 20, "parens override")
|
|
assert_eq(-2 * 3, -6, "unary before mul")
|
|
})
|
|
|
|
// === CURRYING / HIGHER-ORDER ===
|
|
|
|
run("curried function", function() {
|
|
var f = function(a) {
|
|
return function(b) {
|
|
return function(c) { return a + b + c }
|
|
}
|
|
}
|
|
assert_eq(f(1)(2)(3), 6, "triple curry")
|
|
})
|
|
|
|
// === SELF-REFERENCING STRUCTURES ===
|
|
|
|
run("self-referencing object", function() {
|
|
var o = {name: "root"}
|
|
o.self = o
|
|
assert_eq(o.self.self.name, "root", "cycle access")
|
|
})
|
|
|
|
// === IDENTIFIER ? AND ! ===
|
|
|
|
run("question mark in identifier", function() {
|
|
var nil? = (x) => x == null
|
|
assert_eq(nil?(null), true, "nil? null")
|
|
assert_eq(nil?(42), false, "nil? 42")
|
|
})
|
|
|
|
run("bang in identifier", function() {
|
|
var set! = (x) => x + 1
|
|
assert_eq(set!(5), 6, "set! call")
|
|
})
|
|
|
|
run("question mark mid identifier", function() {
|
|
var is?valid = (x) => x > 0
|
|
assert_eq(is?valid(3), true, "is?valid true")
|
|
assert_eq(is?valid(-1), false, "is?valid false")
|
|
})
|
|
|
|
run("bang mid identifier", function() {
|
|
var do!stuff = () => 42
|
|
assert_eq(do!stuff(), 42, "do!stuff call")
|
|
})
|
|
|
|
run("ternary after question ident", function() {
|
|
var nil? = (x) => x == null
|
|
var a = nil?(null) ? "yes" : "no"
|
|
assert_eq(a, "yes", "ternary true branch")
|
|
var b = nil?(42) ? "yes" : "no"
|
|
assert_eq(b, "no", "ternary false branch")
|
|
})
|
|
|
|
run("bang not confused with logical not", function() {
|
|
assert_eq(!true, false, "logical not true")
|
|
assert_eq(!false, true, "logical not false")
|
|
})
|
|
|
|
run("inequality not confused with bang ident", function() {
|
|
assert_eq(1 != 2, true, "inequality true")
|
|
assert_eq(1 != 1, false, "inequality false")
|
|
})
|
|
|
|
// === SUMMARY ===
|
|
|
|
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
|
|
if (failed > 0) {
|
|
print("")
|
|
for (var _j = 0; _j < failed; _j++) {
|
|
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
|
|
}
|
|
}
|