Files
rose-ash/lib/js/test.sh
giles 2bd3a6b2ba js-on-sx: Array.prototype includes/find/some/every/reverse + Object fallbacks
Array: includes, find, findIndex, some, every, reverse via
tail-recursive helpers.

Object: hasOwnProperty, isPrototypeOf, propertyIsEnumerable,
toString, valueOf, toLocaleString fallback in js-invoke-method
when js-get-prop returns undefined. Lets o.hasOwnProperty('k')
work on plain dicts.

376/378 unit (+13), 148/148 slice unchanged.
2026-04-23 21:11:12 +00:00

1518 lines
54 KiB
Bash
Executable File

#!/usr/bin/env bash
# Fast JS-on-SX test runner — epoch protocol direct to sx_server.exe.
# Mirrors lib/hyperscript/test.sh.
#
# Usage:
# bash lib/js/test.sh # run all tests
# bash lib/js/test.sh -v # verbose
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: $SX_SERVER not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
VERBOSE="${1:-}"
PASS=0
FAIL=0
ERRORS=""
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/r7rs.sx")
(epoch 2)
(load "lib/js/lexer.sx")
(epoch 3)
(load "lib/js/parser.sx")
(epoch 4)
(load "lib/js/transpile.sx")
(epoch 5)
(load "lib/js/runtime.sx")
;; ── Phase 0: stubs still behave ─────────────────────────────────
(epoch 10)
(eval "(len (js-tokenize \"\"))")
(epoch 11)
(eval "(js-parse (list))")
(epoch 12)
(eval "(js-transpile (list))")
(epoch 13)
(eval "(js-to-boolean 0)")
(epoch 14)
(eval "(js-to-boolean 1)")
(epoch 15)
(eval "(js-to-boolean \"\")")
(epoch 16)
(eval "(js-to-boolean \"x\")")
;; ── Phase 1: lexer ──────────────────────────────────────────────
;; Empty input → just EOF
(epoch 100)
(eval "(len (js-tokenize \"\"))")
(epoch 101)
(eval "(get (nth (js-tokenize \"\") 0) :type)")
;; Whitespace only
(epoch 102)
(eval "(len (js-tokenize \" \\n\\t \"))")
;; Single integer
(epoch 110)
(eval "(get (nth (js-tokenize \"42\") 0) :type)")
(epoch 111)
(eval "(get (nth (js-tokenize \"42\") 0) :value)")
;; Float
(epoch 112)
(eval "(get (nth (js-tokenize \"3.14\") 0) :value)")
;; Exponent
(epoch 113)
(eval "(get (nth (js-tokenize \"1e3\") 0) :value)")
;; Hex
(epoch 114)
(eval "(get (nth (js-tokenize \"0xff\") 0) :value)")
;; Leading-dot number
(epoch 115)
(eval "(get (nth (js-tokenize \".5\") 0) :value)")
;; Strings
(epoch 120)
(eval "(get (nth (js-tokenize \"\\\"hi\\\"\") 0) :value)")
(epoch 121)
(eval "(get (nth (js-tokenize \"'ab'\") 0) :value)")
(epoch 122)
(eval "(get (nth (js-tokenize \"\\\"a\\\\nb\\\"\") 0) :value)")
;; Identifiers vs keywords
(epoch 130)
(eval "(get (nth (js-tokenize \"foo\") 0) :type)")
(epoch 131)
(eval "(get (nth (js-tokenize \"if\") 0) :type)")
(epoch 132)
(eval "(get (nth (js-tokenize \"_x1\") 0) :value)")
(epoch 133)
(eval "(get (nth (js-tokenize \"$y\") 0) :value)")
;; Operators — multi-char longest match
(epoch 140)
(eval "(get (nth (js-tokenize \"===\") 0) :value)")
(epoch 141)
(eval "(get (nth (js-tokenize \"!==\") 0) :value)")
(epoch 142)
(eval "(get (nth (js-tokenize \"<=\") 0) :value)")
(epoch 143)
(eval "(get (nth (js-tokenize \"&&\") 0) :value)")
(epoch 144)
(eval "(get (nth (js-tokenize \"=>\") 0) :value)")
(epoch 145)
(eval "(get (nth (js-tokenize \"...\") 0) :value)")
;; Punctuation
(epoch 150)
(eval "(get (nth (js-tokenize \"(\") 0) :type)")
(epoch 151)
(eval "(get (nth (js-tokenize \";\") 0) :value)")
;; Comments are stripped
(epoch 160)
(eval "(len (js-tokenize \"// comment\\n\"))")
(epoch 161)
(eval "(len (js-tokenize \"/* block */\"))")
(epoch 162)
(eval "(get (nth (js-tokenize \"1 // c\\n2\") 0) :value)")
(epoch 163)
(eval "(get (nth (js-tokenize \"1 // c\\n2\") 1) :value)")
;; Compound expression — count tokens for `a + b * 3`
(epoch 170)
(eval "(len (js-tokenize \"a + b * 3\"))")
(epoch 171)
(eval "(get (nth (js-tokenize \"a + b * 3\") 1) :value)")
(epoch 172)
(eval "(get (nth (js-tokenize \"a + b * 3\") 3) :value)")
;; ── Phase 2: parser ─────────────────────────────────────────────
;; Literals → AST nodes
(epoch 200)
(eval "(js-parse-expr \"42\")")
(epoch 201)
(eval "(js-parse-expr \"3.14\")")
(epoch 202)
(eval "(js-parse-expr \"\\\"hi\\\"\")")
(epoch 203)
(eval "(js-parse-expr \"true\")")
(epoch 204)
(eval "(js-parse-expr \"false\")")
(epoch 205)
(eval "(js-parse-expr \"null\")")
(epoch 206)
(eval "(js-parse-expr \"undefined\")")
(epoch 207)
(eval "(js-parse-expr \"this\")")
(epoch 208)
(eval "(js-parse-expr \"foo\")")
;; Binary operators with precedence
(epoch 210)
(eval "(js-parse-expr \"1+2\")")
(epoch 211)
(eval "(js-parse-expr \"a + b * c\")")
(epoch 212)
(eval "(js-parse-expr \"a * b + c\")")
(epoch 213)
(eval "(js-parse-expr \"a === b\")")
(epoch 214)
(eval "(js-parse-expr \"a && b || c\")")
(epoch 215)
(eval "(js-parse-expr \"a ** b ** c\")")
(epoch 216)
(eval "(js-parse-expr \"(a + b) * c\")")
;; Unary operators
(epoch 220)
(eval "(js-parse-expr \"-x\")")
(epoch 221)
(eval "(js-parse-expr \"!x\")")
(epoch 222)
(eval "(js-parse-expr \"~x\")")
(epoch 223)
(eval "(js-parse-expr \"typeof x\")")
(epoch 224)
(eval "(js-parse-expr \"void x\")")
(epoch 225)
(eval "(js-parse-expr \"+x\")")
;; Member access and index
(epoch 230)
(eval "(js-parse-expr \"a.b\")")
(epoch 231)
(eval "(js-parse-expr \"a.b.c\")")
(epoch 232)
(eval "(js-parse-expr \"a[0]\")")
(epoch 233)
(eval "(js-parse-expr \"a[b+1]\")")
;; Function calls
(epoch 240)
(eval "(js-parse-expr \"f()\")")
(epoch 241)
(eval "(js-parse-expr \"f(x)\")")
(epoch 242)
(eval "(js-parse-expr \"f(a, b, c)\")")
(epoch 243)
(eval "(js-parse-expr \"a.b(c)\")")
(epoch 244)
(eval "(js-parse-expr \"a.b(c)[d]\")")
;; Array literals
(epoch 250)
(eval "(js-parse-expr \"[]\")")
(epoch 251)
(eval "(js-parse-expr \"[1, 2, 3]\")")
(epoch 252)
(eval "(js-parse-expr \"[[1,2],[3,4]]\")")
;; Object literals
(epoch 260)
(eval "(js-parse-expr \"{}\")")
(epoch 261)
(eval "(js-parse-expr \"{a: 1}\")")
(epoch 262)
(eval "(js-parse-expr \"{a: 1, b: 2}\")")
(epoch 263)
(eval "(js-parse-expr \"{\\\"k\\\": 1}\")")
;; Conditional
(epoch 270)
(eval "(js-parse-expr \"a ? b : c\")")
(epoch 271)
(eval "(js-parse-expr \"a ? b ? c : d : e\")")
;; Arrow functions
(epoch 280)
(eval "(js-parse-expr \"x => x + 1\")")
(epoch 281)
(eval "(js-parse-expr \"() => 42\")")
(epoch 282)
(eval "(js-parse-expr \"(a, b) => a + b\")")
(epoch 283)
(eval "(js-parse-expr \"x => y => x + y\")")
;; Assignment
(epoch 290)
(eval "(js-parse-expr \"a = b\")")
(epoch 291)
(eval "(js-parse-expr \"a = b = c\")")
(epoch 292)
(eval "(js-parse-expr \"a += 1\")")
;; ── Phase 3: transpile + end-to-end js-eval ────────────────────
;; Literals — numbers/strings/bools/null/undefined round-trip.
(epoch 300)
(eval "(js-eval \"42\")")
(epoch 301)
(eval "(js-eval \"3.14\")")
(epoch 302)
(eval "(js-eval \"\\\"hi\\\"\")")
(epoch 303)
(eval "(js-eval \"true\")")
(epoch 304)
(eval "(js-eval \"false\")")
(epoch 305)
(eval "(js-eval \"null\")")
(epoch 306)
(eval "(js-eval \"undefined\")")
;; Arithmetic
(epoch 310)
(eval "(js-eval \"1 + 2\")")
(epoch 311)
(eval "(js-eval \"1 + 2 * 3\")")
(epoch 312)
(eval "(js-eval \"(1 + 2) * 3\")")
(epoch 313)
(eval "(js-eval \"10 - 4\")")
(epoch 314)
(eval "(js-eval \"10 / 4\")")
(epoch 315)
(eval "(js-eval \"10 % 3\")")
(epoch 316)
(eval "(js-eval \"2 ** 10\")")
(epoch 317)
(eval "(js-eval \"-5\")")
(epoch 318)
(eval "(js-eval \"+5\")")
(epoch 319)
(eval "(js-eval \"~5\")")
;; String concat via +
(epoch 320)
(eval "(js-eval \"\\\"a\\\" + \\\"b\\\"\")")
(epoch 321)
(eval "(js-eval \"1 + \\\"2\\\"\")")
(epoch 322)
(eval "(js-eval \"\\\"n=\\\" + 42\")")
;; Comparisons
(epoch 330)
(eval "(js-eval \"1 === 1\")")
(epoch 331)
(eval "(js-eval \"1 === 2\")")
(epoch 332)
(eval "(js-eval \"1 === \\\"1\\\"\")")
(epoch 333)
(eval "(js-eval \"1 !== 1\")")
(epoch 334)
(eval "(js-eval \"1 < 2\")")
(epoch 335)
(eval "(js-eval \"2 <= 2\")")
(epoch 336)
(eval "(js-eval \"3 > 1\")")
(epoch 337)
(eval "(js-eval \"3 >= 3\")")
(epoch 338)
(eval "(js-eval \"\\\"a\\\" < \\\"b\\\"\")")
;; Abstract equality
(epoch 340)
(eval "(js-eval \"1 == \\\"1\\\"\")")
(epoch 341)
(eval "(js-eval \"null == undefined\")")
(epoch 342)
(eval "(js-eval \"0 == false\")")
;; Logical — short-circuit, value-returning
(epoch 350)
(eval "(js-eval \"true && false\")")
(epoch 351)
(eval "(js-eval \"1 && 2\")")
(epoch 352)
(eval "(js-eval \"0 && 2\")")
(epoch 353)
(eval "(js-eval \"false || 5\")")
(epoch 354)
(eval "(js-eval \"0 || \\\"x\\\"\")")
(epoch 355)
(eval "(js-eval \"null ?? 7\")")
(epoch 356)
(eval "(js-eval \"0 ?? 7\")")
(epoch 357)
(eval "(js-eval \"!true\")")
(epoch 358)
(eval "(js-eval \"!0\")")
;; Conditional
(epoch 360)
(eval "(js-eval \"true ? 10 : 20\")")
(epoch 361)
(eval "(js-eval \"0 ? 10 : 20\")")
(epoch 362)
(eval "(js-eval \"1 < 2 ? 'yes' : 'no'\")")
;; Arrays
(epoch 370)
(eval "(js-eval \"[1,2,3]\")")
(epoch 371)
(eval "(js-eval \"[1,2,3].length\")")
(epoch 372)
(eval "(js-eval \"[1,2,3][1]\")")
(epoch 373)
(eval "(js-eval \"[[1,2],[3,4]][1][0]\")")
(epoch 374)
(eval "(js-eval \"[]\")")
;; Objects
(epoch 380)
(eval "(js-eval \"({a:1, b:2}).a\")")
(epoch 381)
(eval "(js-eval \"({a:1, b:2}).b\")")
(epoch 382)
(eval "(js-eval \"({a:{b:{c:42}}}).a.b.c\")")
(epoch 383)
(eval "(js-eval \"({})\")")
;; Arrow functions and calls
(epoch 390)
(eval "(js-eval \"(x => x + 1)(41)\")")
(epoch 391)
(eval "(js-eval \"((a,b) => a + b)(3,4)\")")
(epoch 392)
(eval "(js-eval \"(x => y => x + y)(3)(4)\")")
(epoch 393)
(eval "(js-eval \"(() => 42)()\")")
;; Member access chains + function calls
(epoch 400)
(eval "(js-eval \"Math.abs(-5)\")")
(epoch 401)
(eval "(js-eval \"Math.floor(3.9)\")")
(epoch 402)
(eval "(js-eval \"Math.ceil(3.1)\")")
(epoch 403)
(eval "(js-eval \"Math.max(1,5,3)\")")
(epoch 404)
(eval "(js-eval \"Math.min(1,5,3)\")")
(epoch 405)
(eval "(js-eval \"Math.PI > 3\")")
;; typeof
(epoch 410)
(eval "(js-eval \"typeof 42\")")
(epoch 411)
(eval "(js-eval \"typeof 'x'\")")
(epoch 412)
(eval "(js-eval \"typeof true\")")
(epoch 413)
(eval "(js-eval \"typeof undefined\")")
(epoch 414)
(eval "(js-eval \"typeof null\")")
(epoch 415)
(eval "(js-eval \"typeof (x => x)\")")
;; ── Phase 6: statements ─────────────────────────────────────────
;; Parser-level — program shape and statements
(epoch 500)
(eval "(js-parse (js-tokenize \"let x = 1;\"))")
(epoch 501)
(eval "(js-parse (js-tokenize \"const x = 1, y = 2;\"))")
(epoch 502)
(eval "(js-parse (js-tokenize \"var x;\"))")
(epoch 503)
(eval "(js-parse (js-tokenize \"if (x) y else z\"))")
(epoch 504)
(eval "(js-parse (js-tokenize \"while (x) y\"))")
(epoch 505)
(eval "(js-parse (js-tokenize \"do y while (x);\"))")
(epoch 506)
(eval "(js-parse (js-tokenize \"for (let i = 0; i < 5; i = i + 1) x\"))")
(epoch 507)
(eval "(js-parse (js-tokenize \"{ a; b; c; }\"))")
(epoch 508)
(eval "(js-parse (js-tokenize \"return;\"))")
(epoch 509)
(eval "(js-parse (js-tokenize \"break;\"))")
(epoch 510)
(eval "(js-parse (js-tokenize \"continue;\"))")
(epoch 511)
(eval "(js-parse (js-tokenize \"function f(x) { return x; }\"))")
(epoch 512)
(eval "(js-parse (js-tokenize \";;;;\"))")
;; Runtime — statements evaluate
(epoch 520)
(eval "(js-eval \"let x = 1; let y = 2; x + y\")")
(epoch 521)
(eval "(js-eval \"let x = 10; if (x > 5) 1 else 2\")")
(epoch 522)
(eval "(js-eval \"let x = 0; while (x < 5) { x = x + 1; } x\")")
(epoch 523)
(eval "(js-eval \"let x = 0; do { x = x + 1; } while (x < 3); x\")")
(epoch 524)
(eval "(js-eval \"let n = 0; for (let i = 0; i < 10; i = i + 1) n = n + i; n\")")
(epoch 525)
(eval "(js-eval \"let x = 0; for (let i = 0; i < 10; i = i + 1) { if (i === 5) break; x = i; } x\")")
(epoch 526)
(eval "(js-eval \"let s = 0; for (let i = 0; i < 10; i = i + 1) { if (i % 2 === 0) continue; s = s + i; } s\")")
(epoch 527)
(eval "(js-eval \"{ let x = 1; let y = 2; x + y }\")")
(epoch 528)
(eval "(js-eval \"let a; a\")")
(epoch 529)
(eval "(js-eval \"let a = 5, b = 10; a * b\")")
(epoch 530)
(eval "(js-eval \"let i = 0; while (true) { i = i + 1; if (i >= 3) break; } i\")")
;; ── Phase 7: functions and scoping ──────────────────────────────
(epoch 600)
(eval "(js-eval \"function add(a, b) { return a + b; } add(3, 4)\")")
(epoch 601)
(eval "(js-eval \"function fact(n) { if (n <= 1) return 1; return n * fact(n - 1); } fact(6)\")")
(epoch 602)
(eval "(js-eval \"function fib(n) { if (n < 2) return n; return fib(n-1) + fib(n-2); } fib(10)\")")
(epoch 603)
(eval "(js-eval \"function outer() { function inner() { return 42; } return inner(); } outer()\")")
(epoch 604)
(eval "(js-eval \"function make(n) { return function() { return n; }; } make(7)()\")")
(epoch 605)
(eval "(js-eval \"function counter() { let n = 0; return () => { n = n + 1; return n; }; } let c = counter(); c(); c(); c()\")")
(epoch 606)
(eval "(js-eval \"function f(x = 5) { return x; } f()\")")
(epoch 607)
(eval "(js-eval \"function f(x = 5) { return x; } f(10)\")")
(epoch 608)
(eval "(js-eval \"function f(x, y = 100) { return x + y; } f(1)\")")
(epoch 609)
(eval "(js-eval \"function sum(...nums) { let t = 0; for (let i = 0; i < nums.length; i = i + 1) t = t + nums[i]; return t; } sum(1,2,3,4,5)\")")
(epoch 610)
(eval "(js-eval \"function f() { return; } f()\")")
(epoch 611)
(eval "(js-eval \"function twice(f, x) { return f(f(x)); } twice(n => n + 1, 10)\")")
(epoch 612)
(eval "(js-eval \"function early(n) { for (let i = 0; i < 100; i = i + 1) { if (i === n) return i * 2; } return -1; } early(7)\")")
(epoch 613)
(eval "(js-eval \"var x = 1; x + 1\")")
(epoch 614)
(eval "(js-eval \"function f() { let y = 10; return y; } f()\")")
(epoch 615)
(eval "(js-eval \"let f = x => { let y = x + 1; return y * 2; }; f(3)\")")
(epoch 616)
(eval "(js-eval \"(function(x) { return x * 2; })(21)\")")
;; ── Phase 8: objects, prototypes, `this`, `new`, classes ─────────
;; Array mutation
(epoch 700)
(eval "(js-eval \"var a = [1,2,3]; a[0] = 99; a[0]\")")
(epoch 701)
(eval "(js-eval \"var a = []; a.push(1); a.push(2); a.push(3); a.length\")")
(epoch 702)
(eval "(js-eval \"var a = [10,20,30]; a[1] = 99; a[1]\")")
(epoch 703)
(eval "(js-eval \"var a = [1,2,3]; a.indexOf(2)\")")
(epoch 704)
(eval "(js-eval \"var a = [1,2,3]; a.join('-')\")")
;; Object property set/get
(epoch 710)
(eval "(js-eval \"var o = { x: 1 }; o.y = 2; o.x + o.y\")")
(epoch 711)
(eval "(js-eval \"var o = {}; o['k'] = 42; o.k\")")
;; Method calls — `this`
(epoch 720)
(eval "(js-eval \"var o = { x: 5, getX: function() { return this.x; } }; o.getX()\")")
(epoch 721)
(eval "(js-eval \"var o = { x: 5, scale: function(n) { return this.x * n; } }; o.scale(4)\")")
(epoch 722)
(eval "(js-eval \"var o = { nums: [1,2,3], first: function() { return this.nums[0]; } }; o.first()\")")
;; Arrow fn — lexical `this`
(epoch 730)
(eval "(js-eval \"var o = { x: 5, get: function() { var f = () => this.x; return f(); } }; o.get()\")")
;; `new` + constructor
(epoch 740)
(eval "(js-eval \"function Foo() { this.x = 42; } var f = new Foo(); f.x\")")
(epoch 741)
(eval "(js-eval \"function Pt(x,y) { this.x = x; this.y = y; } var p = new Pt(3,4); p.x + p.y\")")
;; Prototype chain
(epoch 750)
(eval "(js-eval \"function Pt(x,y) { this.x = x; this.y = y; } Pt.prototype.sum = function() { return this.x + this.y; }; var p = new Pt(3,4); p.sum()\")")
(epoch 751)
(eval "(js-eval \"function A() {} A.prototype.foo = 99; var a = new A(); a.foo\")")
;; instanceof
(epoch 760)
(eval "(js-eval \"function A() {} var a = new A(); a instanceof A\")")
(epoch 761)
(eval "(js-eval \"function A() {} function B() {} var a = new A(); a instanceof B\")")
;; `in` operator
(epoch 770)
(eval "(js-eval \"var o = { x: 1 }; 'x' in o\")")
(epoch 771)
(eval "(js-eval \"var o = { x: 1 }; 'y' in o\")")
;; ES6 classes
(epoch 780)
(eval "(js-eval \"class Pt { constructor(x,y) { this.x = x; this.y = y; } sum() { return this.x + this.y; } } var p = new Pt(3,4); p.sum()\")")
(epoch 781)
(eval "(js-eval \"class A { hello() { return 'A'; } } class B extends A { } var b = new B(); b.hello()\")")
(epoch 782)
(eval "(js-eval \"class A { hello() { return 'A'; } } class B extends A { hello() { return 'B'; } } var b = new B(); b.hello()\")")
(epoch 783)
(eval "(js-eval \"class Pt { constructor(x,y) { this.x = x; this.y = y; } } var p = new Pt(1,2); p instanceof Pt\")")
;; Throw / try / catch / finally
(epoch 790)
(eval "(js-eval \"try { throw 'boom' } catch(e) { e }\")")
(epoch 791)
(eval "(js-eval \"try { throw { msg: 'nope' } } catch(e) { e.msg }\")")
(epoch 792)
(eval "(js-eval \"var x = 0; try { x = 1; throw 'e'; } catch(e) { x = 2; } finally { x = 3; } x\")")
(epoch 793)
(eval "(js-eval \"try { throw new Error('oops') } catch(e) { e.message }\")")
(epoch 794)
(eval "(js-eval \"try { throw new TypeError('bad') } catch(e) { e.name + ':' + e.message }\")")
;; ── Phase 9 parser: async/await AST shape ────────────────────────
(epoch 795)
(eval "(js-parse-expr \"await f()\")")
(epoch 796)
(eval "(js-parse-expr \"async x => x + 1\")")
(epoch 797)
(eval "(js-parse-expr \"async (a, b) => a + b\")")
(epoch 798)
(eval "(js-parse-expr \"async () => 42\")")
(epoch 799)
(eval "(js-parse-expr \"async function(a) { return a; }\")")
;; ── Phase 9: Promises & async/await ──────────────────────────────
;; Tests use __drain() to force microtask queue to run before
;; reading the result variable.
;;
;; Promise construction, resolve/reject, .then / .catch / .finally
(epoch 800)
(eval "(js-eval \"var p = Promise.resolve(42); p.state\")")
(epoch 801)
(eval "(js-eval \"var p = Promise.reject('err'); p.state\")")
(epoch 802)
(eval "(js-eval \"var r = null; Promise.resolve(7).then(x => { r = x + 1; }); __drain(); r\")")
(epoch 803)
(eval "(js-eval \"var r = null; Promise.reject('boom').catch(e => { r = 'caught:' + e; }); __drain(); r\")")
(epoch 804)
(eval "(js-eval \"var r = null; var hit = 0; Promise.resolve(5).finally(() => { hit = 1; }).then(v => { r = v; }); __drain(); '' + hit + ':' + r\")")
;; .then chaining
(epoch 810)
(eval "(js-eval \"var r = null; Promise.resolve(1).then(x => x + 1).then(x => x * 10).then(x => { r = x; }); __drain(); r\")")
(epoch 811)
(eval "(js-eval \"var r = null; Promise.resolve(1).then(x => { throw 'oops'; }).catch(e => { r = e; }); __drain(); r\")")
;; new Promise(executor)
(epoch 820)
(eval "(js-eval \"var r = null; new Promise((res, rej) => res(123)).then(v => { r = v; }); __drain(); r\")")
(epoch 821)
(eval "(js-eval \"var r = null; new Promise((res, rej) => rej('nope')).catch(e => { r = e; }); __drain(); r\")")
(epoch 822)
(eval "(js-eval \"var r = null; new Promise((res, rej) => { throw 'executor-threw'; }).catch(e => { r = e; }); __drain(); r\")")
;; Promise.all and Promise.race
(epoch 830)
(eval "(js-eval \"var r = null; Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then(vs => { r = vs[0] + vs[1] + vs[2]; }); __drain(); r\")")
(epoch 831)
(eval "(js-eval \"var r = null; Promise.all([1, 2, 3]).then(vs => { r = vs.length; }); __drain(); r\")")
(epoch 832)
(eval "(js-eval \"var r = null; Promise.all([Promise.resolve(1), Promise.reject('x')]).catch(e => { r = e; }); __drain(); r\")")
(epoch 833)
(eval "(js-eval \"var r = null; Promise.race([Promise.resolve('first'), Promise.resolve('second')]).then(v => { r = v; }); __drain(); r\")")
;; async/await basics
(epoch 840)
(eval "(js-eval \"async function f() { return 1; } var r = null; f().then(v => { r = v; }); __drain(); r\")")
(epoch 841)
(eval "(js-eval \"async function f() { return 2 + 3; } var r = null; f().then(v => { r = v; }); __drain(); r\")")
(epoch 842)
(eval "(js-eval \"async function a() { return 10; } async function b() { var x = await a(); return x + 1; } var r = null; b().then(v => { r = v; }); __drain(); r\")")
(epoch 843)
(eval "(js-eval \"async function throws() { throw 'x'; } var r = null; throws().catch(e => { r = e; }); __drain(); r\")")
(epoch 844)
(eval "(js-eval \"async function div(a,b) { if (b === 0) throw 'divZero'; return a / b; } var r = null; div(10,2).then(v => { r = v; }); __drain(); r\")")
(epoch 845)
(eval "(js-eval \"async function div(a,b) { if (b === 0) throw 'divZero'; return a / b; } var r = null; div(10,0).catch(e => { r = e; }); __drain(); r\")")
;; async arrow functions
(epoch 850)
(eval "(js-eval \"var f = async x => x * 2; var r = null; f(21).then(v => { r = v; }); __drain(); r\")")
(epoch 851)
(eval "(js-eval \"var f = async (a,b) => a - b; var r = null; f(10,3).then(v => { r = v; }); __drain(); r\")")
(epoch 852)
(eval "(js-eval \"var f = async () => 99; var r = null; f().then(v => { r = v; }); __drain(); r\")")
;; await chains multi-step
(epoch 860)
(eval "(js-eval \"async function a() { return 10; } async function b() { var x = await a(); var y = await a(); return x + y; } var r = null; b().then(v => { r = v; }); __drain(); r\")")
(epoch 861)
(eval "(js-eval \"async function main() { var vs = await Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]); return vs[0] + vs[1] + vs[2]; } var r = null; main().then(v => { r = v; }); __drain(); r\")")
;; typeof a Promise → object
(epoch 870)
(eval "(js-eval \"typeof Promise.resolve(1)\")")
;; ── Phase 11: template strings ────────────────────────────────────
;; Lexer — plain template (no interpolation) still returns a string
(epoch 900)
(eval "(get (nth (js-tokenize \"`hello`\") 0) :type)")
(epoch 901)
(eval "(get (nth (js-tokenize \"`hello`\") 0) :value)")
;; Lexer — interpolated template yields a list payload
(epoch 902)
(eval "(list? (get (nth (js-tokenize \"`hi ${x}!`\") 0) :value))")
(epoch 903)
(eval "(len (get (nth (js-tokenize \"`hi ${x}!`\") 0) :value))")
;; Parser — plain template becomes (js-str ...)
(epoch 910)
(eval "(js-parse-expr \"`hi`\")")
;; Parser — interpolated template becomes (js-tpl (parts...))
(epoch 911)
(eval "(js-parse-expr \"`a${1}b`\")")
(epoch 912)
(eval "(js-parse-expr \"`${x}`\")")
;; js-eval — no interpolation
(epoch 920)
(eval "(js-eval \"`hello`\")")
(epoch 921)
(eval "(js-eval \"``\")")
;; Single interpolation
(epoch 922)
(eval "(js-eval \"`hi ${42}!`\")")
;; Multiple interpolations + string coercion
(epoch 923)
(eval "(js-eval \"var a=2,b=3; `${a}+${b}=${a+b}`\")")
;; Identifier coercion
(epoch 924)
(eval "(js-eval \"var x = 5; `num: ${x}`\")")
;; Boolean and null/undefined coercion
(epoch 925)
(eval "(js-eval \"`b=${true},n=${null},u=${undefined}`\")")
;; Member access in expr
(epoch 926)
(eval "(js-eval \"var o={x:1,y:2}; `${o.x+o.y}`\")")
;; Array indexing and .length
(epoch 927)
(eval "(js-eval \"var xs=[1,2,3]; `first=${xs[0]}, len=${xs.length}`\")")
;; Nested ternary inside ${}
(epoch 928)
(eval "(js-eval \"var n=5; `n is ${n>0?'pos':'neg'}`\")")
;; Nested template inside interpolation
(epoch 929)
(eval "(js-eval \"`outer ${`inner ${1+2}`} end`\")")
;; Call inside interpolation
(epoch 930)
(eval "(js-eval \"var f = (x)=>x*2; `f(3)=${f(3)}`\")")
;; Template as arrow-fn body
(epoch 931)
(eval "(js-eval \"var g = (x) => `val=${x}`; g(42)\")")
;; Escape sequences: \n
(epoch 932)
(eval "(js-eval \"`a\\nb`.length\")")
;; Escaped dollar — literal ${} (no interpolation)
(epoch 933)
(eval "(js-eval \"`\\${not-expr}`\")")
;; Escaped backtick inside template
(epoch 934)
(eval "(js-eval \"`has\\`tick`\")")
;; Interpolation adjacency (no space)
(epoch 935)
(eval "(js-eval \"`${1}${2}${3}`\")")
;; Interpolation only (no plain text)
(epoch 936)
(eval "(js-eval \"`${42}`\")")
;; Object in interpolation yields dict string (our js-to-string policy)
(epoch 937)
(eval "(js-eval \"var xs=[1,2]; `len is ${xs.length + 10}`\")")
;; Empty interpolations between text
(epoch 938)
(eval "(js-eval \"`[${''}-${''}]`\")")
;; ── Phase 11.regex: regex literal lexing ────────────────────────
;; Simple regex at start of file
(epoch 1000)
(eval "(get (nth (js-tokenize \"/abc/\") 0) :type)")
(epoch 1001)
(eval "(get (get (nth (js-tokenize \"/abc/\") 0) :value) :pattern)")
(epoch 1002)
(eval "(get (get (nth (js-tokenize \"/abc/\") 0) :value) :flags)")
;; With flags
(epoch 1003)
(eval "(get (get (nth (js-tokenize \"/a+/gi\") 0) :value) :flags)")
;; Character class with embedded /
(epoch 1004)
(eval "(get (get (nth (js-tokenize \"/[/]/\") 0) :value) :pattern)")
;; Escaped /
(epoch 1005)
(eval "(get (get (nth (js-tokenize \"/a\\\\/b/\") 0) :value) :pattern)")
;; After `return` keyword → regex
(epoch 1006)
(eval "(get (nth (js-tokenize \"return /x/\") 1) :type)")
;; After `=` op → regex
(epoch 1007)
(eval "(get (nth (js-tokenize \"x = /y/\") 2) :type)")
;; After ident `x` → division (not regex)
(epoch 1008)
(eval "(get (nth (js-tokenize \"a/b\") 1) :type)")
(epoch 1009)
(eval "(get (nth (js-tokenize \"a/b\") 1) :value)")
;; After `)` → division
(epoch 1010)
(eval "(get (nth (js-tokenize \"(a)/b\") 3) :type)")
;; After number → division
(epoch 1011)
(eval "(get (nth (js-tokenize \"1/2\") 1) :type)")
;; Regex /= must still be division-assignment in expr context
(epoch 1012)
(eval "(get (nth (js-tokenize \"x/=2\") 1) :type)")
(epoch 1013)
(eval "(get (nth (js-tokenize \"x/=2\") 1) :value)")
;; Inside function body after statement separator
(epoch 1014)
(eval "(get (nth (js-tokenize \"; /abc/\") 1) :type)")
;; After `throw`
(epoch 1015)
(eval "(get (nth (js-tokenize \"throw /x/\") 1) :type)")
;; ── Phase 11.regex: parser ──────────────────────────────────────
(epoch 1020)
(eval "(first (js-parse-expr \"/abc/\"))")
(epoch 1021)
(eval "(nth (js-parse-expr \"/foo/gi\") 1)")
(epoch 1022)
(eval "(nth (js-parse-expr \"/foo/gi\") 2)")
;; ── Phase 11.regex: transpile ───────────────────────────────────
(epoch 1030)
(eval "(first (js-transpile (js-parse-expr \"/abc/\")))")
;; ── Phase 11.regex: runtime — regex object shape ───────────────
(epoch 1040)
(eval "(get (js-regex-new \"ab\" \"g\") :source)")
(epoch 1041)
(eval "(get (js-regex-new \"ab\" \"g\") :flags)")
(epoch 1042)
(eval "(get (js-regex-new \"ab\" \"g\") :global)")
(epoch 1043)
(eval "(js-regex? (js-regex-new \"ab\" \"\"))")
;; .source / .flags / .global etc via property access
(epoch 1050)
(eval "(js-eval \"/abc/g.source\")")
(epoch 1051)
(eval "(js-eval \"/abc/gi.flags\")")
(epoch 1052)
(eval "(js-eval \"/abc/g.global\")")
(epoch 1053)
(eval "(js-eval \"/abc/i.ignoreCase\")")
;; .test() via stub: substring-based
(epoch 1060)
(eval "(js-eval \"/foo/.test('hello foo')\")")
(epoch 1061)
(eval "(js-eval \"/zzz/.test('hello')\")")
;; ── Phase 11.math: expanded constants + functions ────────────────
(epoch 1100)
(eval "(js-eval \"Math.sqrt(16)\")")
(epoch 1101)
(eval "(js-eval \"Math.pow(2, 10)\")")
(epoch 1102)
(eval "(js-eval \"Math.trunc(3.7)\")")
(epoch 1103)
(eval "(js-eval \"Math.trunc(-3.7)\")")
(epoch 1104)
(eval "(js-eval \"Math.sign(5)\")")
(epoch 1105)
(eval "(js-eval \"Math.sign(-5)\")")
(epoch 1106)
(eval "(js-eval \"Math.sign(0)\")")
(epoch 1107)
(eval "(js-eval \"Math.hypot(3, 4)\")")
(epoch 1108)
(eval "(js-eval \"Math.cbrt(27)\")")
(epoch 1109)
(eval "(js-eval \"Math.PI > 3.14\")")
(epoch 1110)
(eval "(js-eval \"Math.E > 2.7\")")
(epoch 1111)
(eval "(js-eval \"Math.SQRT2 > 1.41\")")
;; ── Phase 11.number: Number builtin ─────────────────────────────
(epoch 1200)
(eval "(js-eval \"Number.isInteger(5)\")")
(epoch 1201)
(eval "(js-eval \"Number.isInteger(5.5)\")")
(epoch 1202)
(eval "(js-eval \"Number.isFinite(5)\")")
(epoch 1203)
(eval "(js-eval \"Number.isFinite(1/0)\")")
(epoch 1204)
(eval "(js-eval \"Number.isSafeInteger(1)\")")
(epoch 1205)
(eval "(js-eval \"Number.MAX_SAFE_INTEGER\")")
(epoch 1206)
(eval "(js-eval \"Number.EPSILON > 0\")")
(epoch 1207)
(eval "(js-eval \"isFinite(1)\")")
(epoch 1208)
(eval "(js-eval \"isFinite(1/0)\")")
;; ── Phase 11.incdec: ++ / -- ─────────────────────────────────────
(epoch 1300)
(eval "(js-eval \"var x = 5; x++; x\")")
(epoch 1301)
(eval "(js-eval \"var x = 5; var y = x++; y\")")
(epoch 1302)
(eval "(js-eval \"var x = 5; ++x\")")
(epoch 1303)
(eval "(js-eval \"var x = 5; var y = ++x; y\")")
(epoch 1304)
(eval "(js-eval \"var x = 5; x--; x\")")
(epoch 1305)
(eval "(js-eval \"var x = 5; --x\")")
(epoch 1306)
(eval "(js-eval \"var o = {n: 10}; o.n++; o.n\")")
(epoch 1307)
(eval "(js-eval \"var o = {n: 10}; ++o.n\")")
(epoch 1308)
(eval "(js-eval \"var a = [1,2,3]; a[1]++; a[1]\")")
(epoch 1309)
(eval "(js-eval \"var x = 5; for (var i = 0; i < 3; i++) x++; x\")")
(epoch 1310)
(eval "(js-eval \"var sum = 0; for (var i = 1; i <= 5; i++) sum = sum + i; sum\")")
;; ── Phase 11.strings: extended String.prototype methods ─────────
(epoch 1400)
(eval "(js-eval \"'hello world'.includes('world')\")")
(epoch 1401)
(eval "(js-eval \"'hello world'.includes('xyz')\")")
(epoch 1402)
(eval "(js-eval \"'hello'.startsWith('hel')\")")
(epoch 1403)
(eval "(js-eval \"'hello'.startsWith('llo')\")")
(epoch 1404)
(eval "(js-eval \"'hello'.endsWith('llo')\")")
(epoch 1405)
(eval "(js-eval \"'hello'.endsWith('hel')\")")
(epoch 1406)
(eval "(js-eval \"' hi '.trim()\")")
(epoch 1407)
(eval "(js-eval \"'abc'.repeat(3)\")")
(epoch 1408)
(eval "(js-eval \"'5'.padStart(3, '0')\")")
(epoch 1409)
(eval "(js-eval \"'5'.padEnd(3, 'x')\")")
(epoch 1410)
(eval "(js-eval \"'hello'.toString()\")")
;; ── Phase 11.object: Object builtin ─────────────────────────────
(epoch 1500)
(eval "(js-eval \"Object.keys({a:1, b:2}).length\")")
(epoch 1501)
(eval "(js-eval \"Object.values({a:1, b:2}).length\")")
(epoch 1502)
(eval "(js-eval \"var o = Object.assign({a:1}, {b:2}); o.a + o.b\")")
(epoch 1503)
(eval "(js-eval \"Array.isArray([1,2])\")")
(epoch 1504)
(eval "(js-eval \"Array.isArray('abc')\")")
(epoch 1505)
(eval "(js-eval \"Array.of(1,2,3).length\")")
;; ── Phase 11.switch: switch/case/default/break ──────────────────
(epoch 1600)
(eval "(js-eval \"var r=0; switch(1){case 1: r=10; break;} r\")")
(epoch 1601)
(eval "(js-eval \"var r=0; switch(2){case 1: r=10; break; case 2: r=20; break;} r\")")
(epoch 1602)
(eval "(js-eval \"var r=0; switch(9){case 1: r=10; break; default: r=99;} r\")")
(epoch 1603)
(eval "(js-eval \"var r=0; switch(1){case 1: case 2: r=12; break;} r\")")
(epoch 1604)
(eval "(js-eval \"var r=''; switch('a'){case 'a': r='yes'; break;} r\")")
(epoch 1605)
(eval "(js-eval \"var r=0; switch(1){case 1: r=10; case 2: r=20; break;} r\")")
;; ── Phase 11.array: more Array.prototype ────────────────────────
(epoch 1700)
(eval "(js-eval \"[1,2,3].includes(2)\")")
(epoch 1701)
(eval "(js-eval \"[1,2,3].includes(5)\")")
(epoch 1702)
(eval "(js-eval \"[1,2,3].find((x)=>x>1)\")")
(epoch 1703)
(eval "(js-eval \"[1,2,3].findIndex((x)=>x>1)\")")
(epoch 1704)
(eval "(js-eval \"[1,2,3].some((x)=>x>2)\")")
(epoch 1705)
(eval "(js-eval \"[1,2,3].some((x)=>x>5)\")")
(epoch 1706)
(eval "(js-eval \"[1,2,3].every((x)=>x>0)\")")
(epoch 1707)
(eval "(js-eval \"[1,2,3].every((x)=>x>2)\")")
(epoch 1708)
(eval "(js-eval \"[1,2,3].reverse().join(',')\")")
;; ── Phase 11.objmethod: hasOwnProperty + toString ───────────────
(epoch 1800)
(eval "(js-eval \"var o = {x:1}; o.hasOwnProperty('x')\")")
(epoch 1801)
(eval "(js-eval \"var o = {x:1}; o.hasOwnProperty('y')\")")
(epoch 1802)
(eval "(js-eval \"({}).toString()\")")
(epoch 1803)
(eval "(js-eval \"({x:1}).valueOf().x\")")
EPOCHS
OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
check() {
local epoch="$1" desc="$2" expected="$3"
local actual
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1)
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " || true)
fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(error $epoch " || true)
fi
if [ -z "$actual" ]; then
actual="<no output for epoch $epoch>"
fi
if echo "$actual" | grep -qF -- "$expected"; then
PASS=$((PASS + 1))
[ "$VERBOSE" = "-v" ] && echo "$desc"
else
FAIL=$((FAIL + 1))
ERRORS+="$desc (epoch $epoch)
expected: $expected
actual: $actual
"
fi
}
# ── Smoke: stubs load and return something sensible ────────────────
check 10 "js-tokenize empty tokens length" '1'
check 11 "js-parse empty" '()'
check 12 "js-transpile passes" '()'
check 13 "to-boolean 0 false" 'false'
check 14 "to-boolean 1 true" 'true'
check 15 "to-boolean \"\" false" 'false'
check 16 "to-boolean \"x\" true" 'true'
# ── Phase 1: lexer ────────────────────────────────────────────────
check 100 "empty → 1 EOF token" '1'
check 101 "empty[0] is EOF" '"eof"'
check 102 "whitespace only" '1'
check 110 "'42' is number" '"number"'
check 111 "'42' value" '42'
check 112 "'3.14' value" '3.14'
check 113 "'1e3' value" '1000'
check 114 "'0xff' value" '255'
check 115 "'.5' value" '0.5'
check 120 '"hi" string value' '"hi"'
check 121 "'ab' string value" '"ab"'
check 122 'escaped \n string' '"a\nb"'
check 130 "'foo' is ident" '"ident"'
check 131 "'if' is keyword" '"keyword"'
check 132 "'_x1' value" '"_x1"'
check 133 "'\$y' value" '"$y"'
check 140 "'===' full match" '"==="'
check 141 "'!==' full match" '"!=="'
check 142 "'<=' full match" '"<="'
check 143 "'&&' full match" '"&&"'
check 144 "'=>' full match" '"=>"'
check 145 "'...' spread" '"..."'
check 150 "'(' is punct" '"punct"'
check 151 "';' value" '";"'
check 160 "line comment → EOF" '1'
check 161 "block comment → EOF" '1'
check 162 "comment splits 1|2 first" '1'
check 163 "comment splits 1|2 second" '2'
check 170 "'a + b * 3' → 6 tokens incl EOF" '6'
check 171 "'a + b * 3' tok1 is +" '"+"'
check 172 "'a + b * 3' tok3 is *" '"*"'
# ── Phase 2: parser ───────────────────────────────────────────────
# Literals
check 200 "num literal" '(js-num 42)'
check 201 "float literal" '(js-num 3.14)'
check 202 "string literal" '(js-str "hi")'
check 203 "true literal" '(js-bool true)'
check 204 "false literal" '(js-bool false)'
check 205 "null literal" '(js-null)'
check 206 "undefined literal" '(js-undef)'
check 207 "this literal" '(js-ident "this")'
check 208 "ident literal" '(js-ident "foo")'
# Binary operators with precedence
check 210 "1+2" '(js-binop "+" (js-num 1) (js-num 2))'
check 211 "a + b * c" '(js-binop "+" (js-ident "a") (js-binop "*" (js-ident "b") (js-ident "c")))'
check 212 "a * b + c" '(js-binop "+" (js-binop "*" (js-ident "a") (js-ident "b")) (js-ident "c"))'
check 213 "a === b" '(js-binop "===" (js-ident "a") (js-ident "b"))'
check 214 "a && b || c" '(js-binop "||" (js-binop "&&" (js-ident "a") (js-ident "b")) (js-ident "c"))'
check 215 "a ** b ** c (right-assoc)" '(js-binop "**" (js-ident "a") (js-binop "**" (js-ident "b") (js-ident "c")))'
check 216 "(a + b) * c" '(js-binop "*" (js-binop "+" (js-ident "a") (js-ident "b")) (js-ident "c"))'
# Unary operators
check 220 "-x" '(js-unop "-" (js-ident "x"))'
check 221 "!x" '(js-unop "!" (js-ident "x"))'
check 222 "~x" '(js-unop "~" (js-ident "x"))'
check 223 "typeof x" '(js-unop "typeof" (js-ident "x"))'
check 224 "void x" '(js-unop "void" (js-ident "x"))'
check 225 "+x" '(js-unop "+" (js-ident "x"))'
# Member / index
check 230 "a.b" '(js-member (js-ident "a") "b")'
check 231 "a.b.c" '(js-member (js-member (js-ident "a") "b") "c")'
check 232 "a[0]" '(js-index (js-ident "a") (js-num 0))'
check 233 "a[b+1]" '(js-index (js-ident "a") (js-binop "+" (js-ident "b") (js-num 1)))'
# Function calls
check 240 "f()" '(js-call (js-ident "f") ())'
check 241 "f(x)" '(js-call (js-ident "f") ((js-ident "x")))'
check 242 "f(a,b,c)" '(js-call (js-ident "f") ((js-ident "a") (js-ident "b") (js-ident "c")))'
check 243 "a.b(c)" '(js-call (js-member (js-ident "a") "b") ((js-ident "c")))'
check 244 "a.b(c)[d]" '(js-index (js-call (js-member (js-ident "a") "b") ((js-ident "c"))) (js-ident "d"))'
# Array literals
check 250 "[]" '(js-array ())'
check 251 "[1,2,3]" '(js-array ((js-num 1) (js-num 2) (js-num 3)))'
check 252 "[[1,2],[3,4]]" '(js-array ((js-array ((js-num 1) (js-num 2))) (js-array ((js-num 3) (js-num 4)))))'
# Object literals
check 260 "{}" '(js-object ())'
check 261 "{a:1}" '(js-object ({:value (js-num 1) :key "a"}))'
check 262 "{a:1,b:2}" '(js-object ({:value (js-num 1) :key "a"} {:value (js-num 2) :key "b"}))'
check 263 '{"k":1}' '(js-object ({:value (js-num 1) :key "k"}))'
# Conditional
check 270 "a?b:c" '(js-cond (js-ident "a") (js-ident "b") (js-ident "c"))'
check 271 "nested ternary" '(js-cond (js-ident "a") (js-cond (js-ident "b") (js-ident "c") (js-ident "d")) (js-ident "e"))'
# Arrow functions
check 280 "x => x+1" '(js-arrow ("x") (js-binop "+" (js-ident "x") (js-num 1)))'
check 281 "() => 42" '(js-arrow () (js-num 42))'
check 282 "(a,b) => a+b" '(js-arrow ("a" "b") (js-binop "+" (js-ident "a") (js-ident "b")))'
check 283 "x => y => x+y" '(js-arrow ("x") (js-arrow ("y") (js-binop "+" (js-ident "x") (js-ident "y"))))'
# Assignment
check 290 "a = b" '(js-assign "=" (js-ident "a") (js-ident "b"))'
check 291 "a = b = c (right-assoc)" '(js-assign "=" (js-ident "a") (js-assign "=" (js-ident "b") (js-ident "c")))'
check 292 "a += 1" '(js-assign "+=" (js-ident "a") (js-num 1))'
# ── Phase 3: transpile + end-to-end js-eval ──────────────────────
# Literals
check 300 "js-eval 42" '42'
check 301 "js-eval 3.14" '3.14'
check 302 "js-eval \"hi\"" '"hi"'
check 303 "js-eval true" 'true'
check 304 "js-eval false" 'false'
check 305 "js-eval null" 'nil'
check 306 "js-eval undefined" '"js-undefined"'
# Arithmetic
check 310 "js-eval 1+2" '3'
check 311 "js-eval 1+2*3" '7'
check 312 "js-eval (1+2)*3" '9'
check 313 "js-eval 10-4" '6'
check 314 "js-eval 10/4" '2.5'
check 315 "js-eval 10%3" '1'
check 316 "js-eval 2**10" '1024'
check 317 "js-eval -5" '-5'
check 318 "js-eval +5" '5'
check 319 "js-eval ~5" '-6'
# String concat
check 320 "js-eval \"a\"+\"b\"" '"ab"'
check 321 "js-eval 1+\"2\"" '"12"'
check 322 "js-eval \"n=\"+42" '"n=42"'
# Comparisons
check 330 "js-eval 1===1" 'true'
check 331 "js-eval 1===2" 'false'
check 332 "js-eval 1===\"1\"" 'false'
check 333 "js-eval 1!==1" 'false'
check 334 "js-eval 1<2" 'true'
check 335 "js-eval 2<=2" 'true'
check 336 "js-eval 3>1" 'true'
check 337 "js-eval 3>=3" 'true'
check 338 "js-eval 'a'<'b'" 'true'
# Loose equality
check 340 "js-eval 1==\"1\"" 'true'
check 341 "js-eval null==undef" 'true'
check 342 "js-eval 0==false" 'true'
# Logical
check 350 "js-eval true&&false" 'false'
check 351 "js-eval 1&&2" '2'
check 352 "js-eval 0&&2" '0'
check 353 "js-eval false||5" '5'
check 354 "js-eval 0||\"x\"" '"x"'
check 355 "js-eval null??7" '7'
check 356 "js-eval 0??7" '0'
check 357 "js-eval !true" 'false'
check 358 "js-eval !0" 'true'
# Conditional
check 360 "js-eval true?10:20" '10'
check 361 "js-eval 0?10:20" '20'
check 362 "js-eval ternary" '"yes"'
# Arrays
check 370 "js-eval [1,2,3]" '(1 2 3)'
check 371 "js-eval arr.length" '3'
check 372 "js-eval arr[1]" '2'
check 373 "js-eval nested arr" '3'
check 374 "js-eval []" '()'
# Objects
check 380 "js-eval obj.a" '1'
check 381 "js-eval obj.b" '2'
check 382 "js-eval deep obj" '42'
check 383 "js-eval {}" '{}'
# Arrow functions
check 390 "js-eval x=>x+1" '42'
check 391 "js-eval (a,b)=>a+b" '7'
check 392 "js-eval curried" '7'
check 393 "js-eval ()=>42" '42'
# Member calls
check 400 "Math.abs(-5)" '5'
check 401 "Math.floor(3.9)" '3'
check 402 "Math.ceil(3.1)" '4'
check 403 "Math.max" '5'
check 404 "Math.min" '1'
check 405 "Math.PI>3" 'true'
# typeof
check 410 "typeof 42" '"number"'
check 411 "typeof 'x'" '"string"'
check 412 "typeof true" '"boolean"'
check 413 "typeof undefined" '"undefined"'
check 414 "typeof null" '"object"'
check 415 "typeof arrow" '"function"'
# ── Phase 6: statements (parser) ──────────────────────────────────
check 500 "let decl" '(js-program ((js-var "let" ((js-vardecl "x" (js-num 1))))))'
check 501 "const multi-decl" '(js-program ((js-var "const" ((js-vardecl "x" (js-num 1)) (js-vardecl "y" (js-num 2))))))'
check 502 "var no init" '(js-program ((js-var "var" ((js-vardecl "x" (js-undef))))))'
check 503 "if-else" '(js-program ((js-if (js-ident "x") (js-exprstmt (js-ident "y")) (js-exprstmt (js-ident "z")))))'
check 504 "while" '(js-program ((js-while (js-ident "x") (js-exprstmt (js-ident "y")))))'
check 505 "do-while" '(js-program ((js-do-while (js-exprstmt (js-ident "y")) (js-ident "x"))))'
check 506 "for" 'js-for'
check 507 "block" 'js-block'
check 508 "return;" '(js-program ((js-return nil)))'
check 509 "break;" '(js-program ((js-break)))'
check 510 "continue;" '(js-program ((js-continue)))'
check 511 "function decl" 'js-funcdecl'
check 512 "empty stmts skipped" 'js-empty'
# ── Phase 6: statement evaluation ─────────────────────────────────
check 520 "multi-decl sum" '3'
check 521 "if expr" '1'
check 522 "while incr" '5'
check 523 "do-while incr" '3'
check 524 "for sum 0..9" '45'
check 525 "break" '4'
check 526 "continue" '25'
check 527 "block scope" '3'
check 528 "let a uninit" '"js-undefined"'
check 529 "let multi-decl *" '50'
check 530 "break from while(true)" '3'
# ── Phase 7: functions ────────────────────────────────────────────
check 600 "function add" '7'
check 601 "fact(6)" '720'
check 602 "fib(10)" '55'
check 603 "nested funcs" '42'
check 604 "closure" '7'
check 605 "counter closure" '3'
check 606 "default param missing" '5'
check 607 "default param passed" '10'
check 608 "default second arg" '101'
check 609 "rest params" '15'
check 610 "bare return" '"js-undefined"'
check 611 "higher-order" '12'
check 612 "early return from loop" '14'
check 613 "var decl" '2'
check 614 "inner let" '10'
check 615 "arrow block body" '8'
check 616 "iife function expr" '42'
# ── Phase 8: objects, prototypes, `this`, `new`, classes ──────────
check 700 "array[i] = v; array[i]" '99'
check 701 "array.push length" '3'
check 702 "array middle mutation" '99'
check 703 "array.indexOf" '1'
check 704 "array.join" '"1-2-3"'
check 710 "obj.y = v; obj.x + obj.y" '3'
check 711 "obj[k] = v; obj.k" '42'
check 720 "method this.x" '5'
check 721 "method this.x * n" '20'
check 722 "method this.nums[0]" '1'
check 730 "arrow lexical this" '5'
check 740 "new Foo() this.x" '42'
check 741 "new Pt(x,y) this" '7'
check 750 "proto.sum with this" '7'
check 751 "proto prop lookup" '99'
check 760 "a instanceof A" 'true'
check 761 "a instanceof B" 'false'
check 770 "'x' in o true" 'true'
check 771 "'y' in o false" 'false'
check 780 "class Pt sum()" '7'
check 781 "class inherit method" '"A"'
check 782 "class override method" '"B"'
check 783 "class instanceof" 'true'
check 790 "try/catch string" '"boom"'
check 791 "try/catch object" '"nope"'
check 792 "finally runs" '3'
check 793 "new Error caught" '"oops"'
check 794 "new TypeError caught" '"TypeError:bad"'
# ── Phase 9 parser: async/await AST ───────────────────────────────
check 795 "await parse" '(js-await (js-call (js-ident "f") ()))'
check 796 "async single arrow" '(js-arrow-async ("x") (js-binop "+" (js-ident "x") (js-num 1)))'
check 797 "async multi-arrow" '(js-arrow-async ("a" "b") (js-binop "+" (js-ident "a") (js-ident "b")))'
check 798 "async empty arrow" '(js-arrow-async () (js-num 42))'
check 799 "async funcexpr" 'js-funcexpr-async'
# ── Phase 9: Promises & async/await ───────────────────────────────
check 800 "Promise.resolve state" '"fulfilled"'
check 801 "Promise.reject state" '"rejected"'
check 802 "then basic" '8'
check 803 "catch basic" '"caught:boom"'
check 804 "finally pass-through" '"1:5"'
check 810 ".then chain" '20'
check 811 ".then throw → catch" '"oops"'
check 820 "new Promise resolve" '123'
check 821 "new Promise reject" '"nope"'
check 822 "executor throw → reject" '"executor-threw"'
check 830 "Promise.all" '6'
check 831 "Promise.all with non-Promise values" '3'
check 832 "Promise.all reject" '"x"'
check 833 "Promise.race first" '"first"'
check 840 "async fn basic" '1'
check 841 "async fn computed" '5'
check 842 "await fn" '11'
check 843 "async throw" '"x"'
check 844 "async + catch ok" '5'
check 845 "async + catch reject" '"divZero"'
check 850 "async arrow single" '42'
check 851 "async arrow pair" '7'
check 852 "async arrow empty" '99'
check 860 "await x2" '20'
check 861 "await Promise.all" '6'
check 870 "typeof Promise" '"object"'
# ── Phase 11: template strings ────────────────────────────────────
check 900 "tpl plain: type" '"template"'
check 901 "tpl plain: value" '"hello"'
check 902 "tpl interp: value is list" 'true'
check 903 "tpl interp: 2 parts" '2'
check 910 "parse plain" '(js-str "hi")'
check 911 'parse interp a${1}b' '(js-tpl ((js-str "a") (js-num 1) (js-str "b")))'
check 912 'parse ${x} only' '(js-tpl ((js-ident "x")))'
check 920 "plain" '"hello"'
check 921 "empty template" '""'
check 922 'single ${42}' '"hi 42!"'
check 923 "multi interp" '"2+3=5"'
check 924 "ident interp" '"num: 5"'
check 925 "bool/null/undef coerce" '"b=true,n=null,u=undefined"'
check 926 "member access interp" '"3"'
check 927 "array index+length" '"first=1, len=3"'
check 928 "nested ternary" '"n is pos"'
check 929 "nested template" '"outer inner 3 end"'
check 930 'call in ${}' '"f(3)=6"'
check 931 "arrow returns tpl" '"val=42"'
check 932 "escape \\n length" '3'
check 933 "escaped dollar" '${not-expr}'
check 934 "escaped backtick" '"has`tick"'
check 935 "adjacent interps" '"123"'
check 936 'bare ${42}' '"42"'
check 937 "expr in interp" '"len is 12"'
check 938 "empty interps" '"[-]"'
# ── Phase 11.regex: lexer ────────────────────────────────────────
check 1000 "regex at sof → type" '"regex"'
check 1001 "regex pattern" '"abc"'
check 1002 "regex empty flags" '""'
check 1003 "regex with gi flags" '"gi"'
check 1004 "regex class with /" '"[/]"'
check 1005 "regex escaped /" '"a\\/b"'
check 1006 "after return → regex" '"regex"'
check 1007 "after = → regex" '"regex"'
check 1008 "after ident → op" '"op"'
check 1009 "after ident div value" '"/"'
check 1010 "after ) → op" '"op"'
check 1011 "after number → op" '"op"'
check 1012 "x/=2 is /=-assign" '"op"'
check 1013 "x/=2 /= op value" '"/="'
check 1014 "after ; → regex" '"regex"'
check 1015 "after throw → regex" '"regex"'
# ── Phase 11.regex: parser ───────────────────────────────────────
check 1020 "parse /abc/ head" 'js-regex'
check 1021 "parse pattern arg" '"foo"'
check 1022 "parse flags arg" '"gi"'
# ── Phase 11.regex: transpile ────────────────────────────────────
check 1030 "transpile uses js-regex-new" 'js-regex-new'
# ── Phase 11.regex: runtime obj ──────────────────────────────────
check 1040 "regex source" '"ab"'
check 1041 "regex flags" '"g"'
check 1042 "regex global true" 'true'
check 1043 "js-regex? true" 'true'
# ── Phase 11.regex: property access ──────────────────────────────
check 1050 "literal .source" '"abc"'
check 1051 "literal .flags" '"gi"'
check 1052 "literal .global" 'true'
check 1053 "literal .ignoreCase" 'true'
# ── Phase 11.regex: test() ───────────────────────────────────────
check 1060 "test match" 'true'
check 1061 "test no match" 'false'
# ── Phase 11.math: expanded Math ────────────────────────────────
check 1100 "Math.sqrt(16)" '4'
check 1101 "Math.pow(2,10)" '1024'
check 1102 "Math.trunc(3.7)" '3'
check 1103 "Math.trunc(-3.7)" '-3'
check 1104 "Math.sign(5)" '1'
check 1105 "Math.sign(-5)" '-1'
check 1106 "Math.sign(0)" '0'
check 1107 "Math.hypot(3,4)" '5'
check 1108 "Math.cbrt(27)" '3'
check 1109 "Math.PI" 'true'
check 1110 "Math.E" 'true'
check 1111 "Math.SQRT2" 'true'
check 1200 "Number.isInteger(5)" 'true'
check 1201 "Number.isInteger(5.5)" 'false'
check 1202 "Number.isFinite(5)" 'true'
check 1203 "Number.isFinite(Inf)" 'false'
check 1204 "Number.isSafeInteger(1)" 'true'
check 1205 "Number.MAX_SAFE_INTEGER" '9007199254740991'
check 1206 "Number.EPSILON > 0" 'true'
check 1207 "isFinite(1)" 'true'
check 1208 "isFinite(Inf)" 'false'
# ── Phase 11.incdec: ++ / -- ────────────────────────────────────
check 1300 "x++ bumps x" '6'
check 1301 "x++ returns old" '5'
check 1302 "++x bumps and returns" '6'
check 1303 "++x captured new" '6'
check 1304 "x-- decrements" '4'
check 1305 "--x decrements, returns new" '4'
check 1306 "obj.n++" '11'
check 1307 "++obj.n" '11'
check 1308 "a[1]++" '3'
check 1309 "for-loop x++" '8'
check 1310 "for-loop accumulator" '15'
# ── Phase 11.strings: extended String.prototype ─────────────────
check 1400 "includes match" 'true'
check 1401 "includes no match" 'false'
check 1402 "startsWith match" 'true'
check 1403 "startsWith no match" 'false'
check 1404 "endsWith match" 'true'
check 1405 "endsWith no match" 'false'
check 1406 "trim whitespace" '"hi"'
check 1407 "repeat 3x" '"abcabcabc"'
check 1408 "padStart 0s" '"005"'
check 1409 "padEnd x" '"5xx"'
check 1410 "toString self" '"hello"'
# ── Phase 11.object: Object + Array ─────────────────────────────
check 1500 "Object.keys count" '2'
check 1501 "Object.values.length" '2'
check 1502 "Object.assign merge" '3'
check 1503 "Array.isArray yes" 'true'
check 1504 "Array.isArray no" 'false'
check 1505 "Array.of length" '3'
# ── Phase 11.switch ─────────────────────────────────────────────
check 1600 "switch case 1 match" '10'
check 1601 "switch case 2 match" '20'
check 1602 "switch default" '99'
check 1603 "switch fallthrough stops on break" '12'
check 1604 "switch on string" '"yes"'
check 1605 "switch fallthrough chains" '20'
# ── Phase 11.array: more Array.prototype ────────────────────────
check 1700 "Array.includes yes" 'true'
check 1701 "Array.includes no" 'false'
check 1702 "Array.find" '2'
check 1703 "Array.findIndex" '1'
check 1704 "Array.some yes" 'true'
check 1705 "Array.some no" 'false'
check 1706 "Array.every yes" 'true'
check 1707 "Array.every no" 'false'
check 1708 "Array.reverse" '"3,2,1"'
# ── Phase 11.objmethod: hasOwnProperty ──────────────────────────
check 1800 "hasOwnProperty yes" 'true'
check 1801 "hasOwnProperty no" 'false'
check 1802 "({}).toString()" '"[object Object]"'
check 1803 "obj.valueOf().x" '1'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "$PASS/$TOTAL JS-on-SX tests passed"
else
echo "$PASS/$TOTAL passed, $FAIL failed:"
echo ""
echo "$ERRORS"
fi
[ $FAIL -eq 0 ]