// 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 = "" var _i = 0 for (_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 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 var i = 0 for (i = 0; i < 5; i++) sum += i assert_eq(sum, 10, "for basic") }) run("for break continue", function() { var sum = 0 var i = 0 for (i = 0; i < 10; i++) { if (i == 5) break sum += i } assert_eq(sum, 10, "for break") sum = 0 for (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 var i = 0, j = 0 for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) sum++ assert_eq(sum, 9, "nested for") }) // === 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(should_disrupt(function() { fn(1, 2, 3) }), true, "extra args disrupt") assert_eq(fn(1, 2), 3, "exact args work") 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 === 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]) } }