#!/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")
(epoch 6)
(load "lib/js/regex.sx")
(epoch 7)
(load "lib/js/stdlib.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\")")

;; ── Phase 11.fromCharCode: String/parseInt/parseFloat ──────────
(epoch 1900)
(eval "(js-eval \"String.fromCharCode(65)\")")
(epoch 1901)
(eval "(js-eval \"String.fromCharCode(72, 105)\")")
(epoch 1902)
(eval "(js-eval \"parseInt('42')\")")
(epoch 1903)
(eval "(js-eval \"parseInt(3.7)\")")
(epoch 1904)
(eval "(js-eval \"parseFloat('3.14')\")")

;; ── Phase 11.json: JSON.stringify / JSON.parse ────────────────
(epoch 2000)
(eval "(js-eval \"JSON.stringify(42)\")")
(epoch 2001)
(eval "(js-eval \"JSON.stringify('hi')\")")
(epoch 2002)
(eval "(js-eval \"JSON.stringify([1,2,3])\")")
(epoch 2003)
(eval "(js-eval \"JSON.stringify({a:1})\")")
(epoch 2004)
(eval "(js-eval \"JSON.stringify(true)\")")
(epoch 2005)
(eval "(js-eval \"JSON.parse('42')\")")
(epoch 2006)
(eval "(js-eval \"JSON.parse('true')\")")
(epoch 2007)
(eval "(js-eval \"JSON.parse('\\\"hello\\\"')\")")
(epoch 2008)
(eval "(js-eval \"JSON.parse('[1,2,3]').length\")")
(epoch 2009)
(eval "(js-eval \"JSON.parse('{\\\"a\\\":1}').a\")")

;; ── Phase 11.array2: flat + fill + indexOf start ───────────────
(epoch 2100)
(eval "(js-eval \"[1,[2,3],4].flat().length\")")
(epoch 2101)
(eval "(js-eval \"[1,[2,[3]]].flat(2).length\")")
(epoch 2102)
(eval "(js-eval \"[1,2,3].fill(0).join(',')\")")
(epoch 2103)
(eval "(js-eval \"[1,2,3,4].fill(0, 1, 3).join(',')\")")
(epoch 2104)
(eval "(js-eval \"[1,2,1,2].indexOf(2, 2)\")")

;; ── Phase 11.forofin: for..of / for..in ─────────────────────────
(epoch 2200)
(eval "(js-eval \"var s=0; for (var x of [1,2,3]) s=s+x; s\")")
(epoch 2201)
(eval "(js-eval \"var r=''; for (var k in {a:1, b:2}) r=r+k; r.length\")")
(epoch 2202)
(eval "(js-eval \"var s=''; for (var c of 'abc') s=s+c; s\")")

;; ── Phase 11.strings2: replace/search/match + Array.from ───────
(epoch 2300)
(eval "(js-eval \"'hello world'.replace('world', 'JS')\")")
(epoch 2301)
(eval "(js-eval \"'hello'.replace(/l/, 'L')\")")
(epoch 2302)
(eval "(js-eval \"'hello'.search('ll')\")")
(epoch 2303)
(eval "(js-eval \"'hello'.search(/ll/)\")")
(epoch 2304)
(eval "(js-eval \"'hello'.match('ll')[0]\")")
(epoch 2305)
(eval "(js-eval \"Array.from([1,2,3]).length\")")
(epoch 2306)
(eval "(js-eval \"Array.from('abc').length\")")
(epoch 2307)
(eval "(js-eval \"Array.from([1,2,3], (x)=>x*2).join(',')\")")

;; ── Phase 11.coerce: string-to-number coercion in int params ──
(epoch 2400)
(eval "(js-eval \"'abcd'.charAt('2')\")")
(epoch 2401)
(eval "(js-eval \"'abcd'.slice('1', '3')\")")

;; ── Phase 11.spread: ... in arrays and calls ───────────────────
(epoch 2500)
(eval "(js-eval \"var a=[1,2]; var b=[...a,3,4]; b.length\")")
(epoch 2501)
(eval "(js-eval \"var a=[1,2]; var b=[0,...a,3]; b.join(',')\")")
(epoch 2502)
(eval "(js-eval \"function f(a,b,c){ return a+b+c; } var args=[1,2,3]; f(...args)\")")
(epoch 2503)
(eval "(js-eval \"Math.max(...[1,5,3])\")")
(epoch 2504)
(eval "(js-eval \"var a=[...'abc']; a.length\")")

;; ── Phase 11.destruct: object and array destructuring ──────────
(epoch 2600)
(eval "(js-eval \"var {aa, bb} = {aa:1, bb:2}; aa+bb\")")
(epoch 2601)
(eval "(js-eval \"var [ax, bx, cx] = [1, 2, 3]; ax+bx+cx\")")
(epoch 2602)
(eval "(js-eval \"var [fst, , trd] = [1, 2, 3]; fst+trd\")")
(epoch 2603)
(eval "(js-eval \"var pt = {px: 100}; var {px} = pt; px + 1\")")

;; ── Phase 11.optchain: ?. optional chaining ─────────────────────
(epoch 2700)
(eval "(js-eval \"var o = {x: 5}; o?.x\")")
(epoch 2701)
(eval "(js-eval \"var o = null; o?.x\")")
(epoch 2702)
(eval "(js-eval \"var o = undefined; o?.x\")")
(epoch 2703)
(eval "(js-eval \"var o = {a:{b:7}}; o?.a?.b\")")
(epoch 2704)
(eval "(js-eval \"var o = {a:null}; var r = o?.a?.b; r === undefined\")")

;; ── Phase 11.logassign: &&= ||= ??= ─────────────────────────────
(epoch 2800)
(eval "(js-eval \"var q=0; q||=5; q\")")
(epoch 2801)
(eval "(js-eval \"var q=1; q&&=7; q\")")
(epoch 2802)
(eval "(js-eval \"var q=null; q??=99; q\")")
(epoch 2803)
(eval "(js-eval \"var q=42; q??=99; q\")")

;; ── Phase 11.callable: Number()/String()/Boolean()/Array() ─────
(epoch 2900)
(eval "(js-eval \"Number('42')\")")
(epoch 2901)
(eval "(js-eval \"String(123)\")")
(epoch 2902)
(eval "(js-eval \"String(true)\")")
(epoch 2903)
(eval "(js-eval \"Boolean(0)\")")
(epoch 2904)
(eval "(js-eval \"Boolean('hi')\")")
(epoch 2905)
(eval "(js-eval \"Array(3).length\")")
(epoch 2906)
(eval "(js-eval \"Array(1,2,3).length\")")

;; ── Phase 11.sort: Array.prototype.sort ──────────────────────
(epoch 3000)
(eval "(js-eval \"[3,1,2].sort().join(',')\")")
(epoch 3001)
(eval "(js-eval \"[10,5,20].sort().join(',')\")")
(epoch 3002)
(eval "(js-eval \"[3,1,2].sort((a,b)=>a-b).join(',')\")")
(epoch 3003)
(eval "(js-eval \"[3,1,2].sort((a,b)=>b-a).join(',')\")")

;; ── Phase 11.destruct2: rest in arr pattern, nested tolerance ──
(epoch 3100)
(eval "(js-eval \"var [h, ...tl] = [1,2,3,4]; tl.length\")")
(epoch 3101)
(eval "(js-eval \"var [h, ...tl] = [1,2,3,4]; tl.join(',')\")")

;; ── Phase 11.destruct3: rename in obj pattern ──────────────────
(epoch 3200)
(eval "(js-eval \"var {aa: x1} = {aa: 100}; x1\")")
(epoch 3201)
(eval "(js-eval \"var {aa: x2, bb: y2} = {aa:1, bb:2}; x2+y2\")")

;; ── Phase 11.delete ────────────────────────────────────────────
(epoch 3300)
(eval "(js-eval \"var o = {x: 5}; delete o.x; o.x === undefined\")")
(epoch 3301)
(eval "(js-eval \"var o = {a:1, b:2}; delete o.a; o.b\")")

;; ── Phase 11.numkey: numeric object-literal keys ─────────────
(epoch 3350)
(eval "(js-eval \"var a = {0: 41, 1: 42, 2: 43}; a[0]\")")
(epoch 3351)
(eval "(js-eval \"var a = {length: 3, 0: 10, 1: 20, 2: 30}; a.length\")")
(epoch 3352)
(eval "(js-eval \"var a = {0: 'x'}; a['0']\")")

;; ── Phase 11.fnmethod: Function.prototype.call/apply/bind ─────
(epoch 3400)
(eval "(js-eval \"function greet3(n) { return 'hi ' + n; } greet3.call(null, 'ada')\")")
(epoch 3401)
(eval "(js-eval \"function greet4(n) { return 'hi ' + n; } greet4.apply(null, ['bob'])\")")
(epoch 3402)
(eval "(js-eval \"function sum3(a,b,c) { return a+b+c; } var bnd2 = sum3.bind(null, 1, 2); bnd2(3)\")")
(epoch 3403)
(eval "(js-eval \"var obj2 = {x: 42}; function getX() { return this.x; } getX.call(obj2)\")")
(epoch 3404)
(eval "(js-eval \"function id(x) { return x; } id.apply(null, [7])\")")
(epoch 3405)
(eval "(js-eval \"var arr2 = [1,2]; Array.prototype.push.call(arr2, 10, 20); arr2.length\")")

;; ── Phase 11.nummethod: .toString(), .toFixed() on numbers ───
(epoch 3600)
(eval "(js-eval \"(5).toString()\")")
(epoch 3601)
(eval "(js-eval \"(16).toString(16)\")")
(epoch 3602)
(eval "(js-eval \"(10).toString(2)\")")
(epoch 3603)
(eval "(js-eval \"(3.14).toFixed(1)\")")
(epoch 3604)
(eval "(js-eval \"(3.14).toFixed(2)\")")
(epoch 3605)
(eval "(js-eval \"(0).toFixed(3)\")")
(epoch 3606)
(eval "(js-eval \"(7).valueOf()\")")
(epoch 3607)
(eval "(js-eval \"true.toString()\")")
(epoch 3608)
(eval "(js-eval \"false.toString()\")")
(epoch 3609)
(eval "(js-eval \"(true).valueOf()\")")

;; ── Phase 11.arrmore: more Array.prototype methods ──────────
(epoch 3700)
(eval "(js-eval \"[1,2,3].at(0)\")")
(epoch 3701)
(eval "(js-eval \"[1,2,3].at(-1)\")")
(epoch 3702)
(eval "(js-eval \"[1,2,3,4].flatMap(x=>[x,x*2]).join(',')\")")
(epoch 3703)
(eval "(js-eval \"[1,5,2,7,3].findLast(x=>x<5)\")")
(epoch 3704)
(eval "(js-eval \"[1,5,2,7,3].findLastIndex(x=>x<5)\")")
(epoch 3705)
(eval "(js-eval \"[1,2,3,4].reduceRight((acc,x)=>acc+','+x)\")")
(epoch 3706)
(eval "(js-eval \"[1,2,3].toString()\")")
(epoch 3707)
(eval "(js-eval \"[3,1,2].toReversed().join(',')\")")
(epoch 3708)
(eval "(js-eval \"[3,1,4,1,5].toSorted((a,b)=>a-b).join(',')\")")
(epoch 3709)
(eval "(js-eval \"var a=[1,2,3]; a.keys().join(',')\")")

;; ── Phase 11.fnlen: fn.length uses arity from lambda-params ─
(epoch 4100)
(eval "(js-eval \"function f(){} f.length\")")
(epoch 4101)
(eval "(js-eval \"function f(a){} f.length\")")
(epoch 4102)
(eval "(js-eval \"function f(a,b,c){} f.length\")")
(epoch 4103)
(eval "(js-eval \"function f(a, ...rest){} f.length\")")
(epoch 4104)
(eval "(js-eval \"Math.abs.length\")")
(epoch 4105)
(eval "(js-eval \"Math.floor.length\")")

;; ── Phase 11.coerce2: Number coercion edge cases ────────────
(epoch 4000)
(eval "(js-eval \"Number('abc')\")")
(epoch 4001)
(eval "(js-eval \"isNaN(Number('abc'))\")")
(epoch 4002)
(eval "(js-eval \"parseInt('123abc')\")")
(epoch 4003)
(eval "(js-eval \"parseFloat('3.14xyz')\")")
(epoch 4004)
(eval "(js-eval \"parseInt('0xff', 16)\")")
(epoch 4005)
(eval "(js-eval \"parseInt('1010', 2)\")")
(epoch 4006)
(eval "(js-eval \"Number(' 42 ')\")")
(epoch 4007)
(eval "(js-eval \"Number('')\")")
(epoch 4008)
(eval "(js-eval \"encodeURIComponent('a b')\")")
(epoch 4009)
(eval "(js-eval \"encodeURIComponent('a+b?c')\")")

;; ── Phase 11.objglobal: Object.getPrototypeOf, .create, .is, .hasOwn etc. ──
(epoch 3900)
(eval "(js-eval \"var o = Object.create(null); o.x=5; o.x\")")
(epoch 3901)
(eval "(js-eval \"Object.is(NaN, NaN)\")")
(epoch 3902)
(eval "(js-eval \"Object.is(0, 0)\")")
(epoch 3903)
(eval "(js-eval \"Object.hasOwn({a:1}, 'a')\")")
(epoch 3904)
(eval "(js-eval \"Object.hasOwn({a:1}, 'b')\")")
(epoch 3905)
(eval "(js-eval \"Object.fromEntries([['a',1],['b',2]]).a\")")
(epoch 3906)
(eval "(js-eval \"var o = {a:1}; Object.defineProperty(o,'b',{value:42}); o.b\")")
(epoch 3907)
(eval "(js-eval \"var o = {a:1}; Object.getOwnPropertyNames(o).length\")")
(epoch 3908)
(eval "(js-eval \"Object.isExtensible({})\")")

;; ── Phase 11.globals: NaN / Infinity / strict-eq ─────────────
(epoch 3750)
(eval "(js-eval \"typeof NaN\")")
(epoch 3751)
(eval "(js-eval \"isNaN(NaN)\")")
(epoch 3752)
(eval "(js-eval \"isNaN(5)\")")
(epoch 3753)
(eval "(js-eval \"NaN === NaN\")")
(epoch 3754)
(eval "(js-eval \"Infinity > 100\")")
(epoch 3755)
(eval "(js-eval \"-Infinity < 0\")")
(epoch 3756)
(eval "(js-eval \"isFinite(1)\")")
(epoch 3757)
(eval "(js-eval \"isFinite(Infinity)\")")

;; ── Phase 11.strmore: more String.prototype methods ─────────
(epoch 3800)
(eval "(js-eval \"'hello'.at(0)\")")
(epoch 3801)
(eval "(js-eval \"'hello'.at(-1)\")")
(epoch 3802)
(eval "(js-eval \"'hello'.codePointAt(0)\")")
(epoch 3803)
(eval "(js-eval \"'hello world hello'.lastIndexOf('hello')\")")
(epoch 3804)
(eval "(js-eval \"'abc'.localeCompare('abd')\")")
(epoch 3805)
(eval "(js-eval \"'abc'.localeCompare('abc')\")")
(epoch 3806)
(eval "(js-eval \"'hello hello'.replaceAll('hello', 'bye')\")")
(epoch 3807)
(eval "(js-eval \"'a,b,c'.replaceAll(',', '-')\")")
(epoch 3808)
(eval "(js-eval \"'hi'.toLocaleUpperCase()\")")
(epoch 3809)
(eval "(js-eval \"'HI'.toLocaleLowerCase()\")")

;; ── Phase 11.arrlike: Array.prototype.* on {length, 0:..., 1:...} ──
(epoch 3500)
(eval "(js-eval \"var a = {length: 3, 0: 41, 1: 42, 2: 43}; Array.prototype.slice.call(a).length\")")
(epoch 3501)
(eval "(js-eval \"var a = {length: 3, 0: 41, 1: 42, 2: 43}; Array.prototype.slice.call(a).join(',')\")")
(epoch 3502)
(eval "(js-eval \"var a = {length: 3, 0: 1, 1: 2, 2: 3}; Array.prototype.map.call(a, function(x){return x*2;}).join(',')\")")
(epoch 3503)
(eval "(js-eval \"var a = {length: 3, 0: 1, 1: 2, 2: 3}; Array.prototype.indexOf.call(a, 2)\")")
(epoch 3504)
(eval "(js-eval \"var a = {length: 3, 0: 1, 1: 2, 2: 3}; Array.prototype.filter.call(a, function(x){return x>1;}).join(',')\")")
(epoch 3505)
(eval "(js-eval \"var a = {length: 3, 0: 10, 1: 20, 2: 30}; var sum = 0; Array.prototype.forEach.call(a, function(x){sum += x;}); sum\")")

;; ── Phase 12: Regex engine ────────────────────────────────────────
;; Platform is installed (test key is a function, not undefined)
(epoch 5000)
(eval "(js-undefined? (get __js_regex_platform__ \"test\"))")
(epoch 5001)
(eval "(js-eval \"/foo/.test('hi foo bar')\")")
(epoch 5002)
(eval "(js-eval \"/foo/.test('hi bar')\")")
;; Case-insensitive flag
(epoch 5003)
(eval "(js-eval \"/FOO/i.test('hello foo world')\")")
;; Anchors
(epoch 5004)
(eval "(js-eval \"/^hello/.test('hello world')\")")
(epoch 5005)
(eval "(js-eval \"/^hello/.test('say hello')\")")
(epoch 5006)
(eval "(js-eval \"/world$/.test('hello world')\")")
;; Character classes
(epoch 5007)
(eval "(js-eval \"/\\\\d+/.test('abc 123')\")")
(epoch 5008)
(eval "(js-eval \"/\\\\w+/.test('hello')\")")
(epoch 5009)
(eval "(js-eval \"/[abc]/.test('dog')\")")
(epoch 5010)
(eval "(js-eval \"/[abc]/.test('cat')\")")
;; Quantifiers
(epoch 5011)
(eval "(js-eval \"/a*b/.test('b')\")")
(epoch 5012)
(eval "(js-eval \"/a+b/.test('b')\")")
(epoch 5013)
(eval "(js-eval \"/a{2,3}/.test('aa')\")")
(epoch 5014)
(eval "(js-eval \"/a{2,3}/.test('a')\")")
;; Dot
(epoch 5015)
(eval "(js-eval \"/h.llo/.test('hello')\")")
(epoch 5016)
(eval "(js-eval \"/h.llo/.test('hllo')\")")
;; exec result
(epoch 5017)
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.match\")")
(epoch 5018)
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.index\")")
(epoch 5019)
(eval "(js-eval \"var m = /foo(\\\\w+)/.exec('foobar'); m.groups[0]\")")
;; Alternation
(epoch 5020)
(eval "(js-eval \"/cat|dog/.test('I have a dog')\")")
(epoch 5021)
(eval "(js-eval \"/cat|dog/.test('I have a fish')\")")
;; Non-capturing group
(epoch 5022)
(eval "(js-eval \"/(?:foo)+/.test('foofoo')\")")
;; Negated char class
(epoch 5023)
(eval "(js-eval \"/[^abc]/.test('d')\")")
(epoch 5024)
(eval "(js-eval \"/[^abc]/.test('a')\")")
;; Range inside char class
(epoch 5025)
(eval "(js-eval \"/[a-z]+/.test('hello')\")")
;; Word boundary
(epoch 5026)
(eval "(js-eval \"/\\\\bword\\\\b/.test('a word here')\")")
(epoch 5027)
(eval "(js-eval \"/\\\\bword\\\\b/.test('password')\")")
;; Lazy quantifier
(epoch 5028)
(eval "(js-eval \"var m = /a+?/.exec('aaa'); m.match\")")
;; Global flag exec
(epoch 5029)
(eval "(js-eval \"var r=/\\\\d+/g; r.exec('a1b2'); r.exec('a1b2').match\")")
;; String.prototype.match with regex
(epoch 5030)
(eval "(js-eval \"'hello world'.match(/\\\\w+/).match\")")
;; String.prototype.search
(epoch 5031)
(eval "(js-eval \"'hello world'.search(/world/)\")")
;; String.prototype.replace with regex
(epoch 5032)
(eval "(js-eval \"'hello world'.replace(/world/, 'there')\")")
;; multiline anchor
(epoch 5033)
(eval "(js-eval \"/^bar/m.test('foo\\nbar')\")")

;; ── Phase 13: let/const TDZ infrastructure ───────────────────────
;; The TDZ sentinel and checker are defined in runtime.sx.
;; let/const bindings work normally after initialization.
(epoch 5100)
(eval "(js-eval \"let x = 5; x\")")
(epoch 5101)
(eval "(js-eval \"const y = 42; y\")")
;; TDZ sentinel exists and is detectable
(epoch 5102)
(eval "(js-tdz? __js_tdz_sentinel__)")
;; js-tdz-check passes through non-sentinel values
(epoch 5103)
(eval "(js-tdz-check \"x\" 42)")

;; ── Phase 22: Bitwise ops ────────────────────────────────────────
(epoch 6000)
(eval "(js-bitand 5 3)")
(epoch 6001)
(eval "(js-bitor 5 3)")
(epoch 6002)
(eval "(js-bitxor 5 3)")
(epoch 6003)
(eval "(js-lshift 1 4)")
(epoch 6004)
(eval "(js-rshift 32 2)")
(epoch 6005)
(eval "(js-rshift -8 1)")
(epoch 6006)
(eval "(js-urshift 4294967292 2)")
(epoch 6007)
(eval "(js-bitnot 0)")

;; ── Phase 22: Map ─────────────────────────────────────────────────
(epoch 6010)
(eval "(js-map? (js-map-new))")
(epoch 6011)
(eval "(get (js-map-set! (js-map-new) \"k\" 42) \"size\")")
(epoch 6012)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-get m \"a\"))")
(epoch 6013)
(eval "(let ((m (js-map-new))) (js-map-set! m \"x\" 9) (js-map-has m \"x\"))")
(epoch 6014)
(eval "(let ((m (js-map-new))) (js-map-set! m \"x\" 9) (js-map-has m \"y\"))")
(epoch 6015)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-set! m \"b\" 2) (get m \"size\"))")
(epoch 6016)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-delete! m \"a\") (get m \"size\"))")
(epoch 6017)
(eval "(let ((m (js-map-new))) (js-map-set! m \"a\" 1) (js-map-set! m \"a\" 99) (js-map-get m \"a\"))")

;; ── Phase 22: Set ─────────────────────────────────────────────────
(epoch 6020)
(eval "(js-set? (js-set-new))")
(epoch 6021)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-has s 1))")
(epoch 6022)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-has s 2))")
(epoch 6023)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-add! s 1) (get s \"size\"))")
(epoch 6024)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-add! s 2) (get s \"size\"))")
(epoch 6025)
(eval "(let ((s (js-set-new))) (js-set-add! s 1) (js-set-delete! s 1) (get s \"size\"))")

;; ── Phase 22: RegExp constructor ──────────────────────────────────
(epoch 6030)
(eval "(js-regex? (RegExp \"ab\" \"i\"))")
(epoch 6031)
(eval "(get (RegExp \"hello\" \"gi\") \"global\")")
(epoch 6032)
(eval "(get (RegExp \"foo\" \"i\") \"ignoreCase\")")
;; ── Phase 1.ASI: automatic semicolon insertion ─────────────────
(epoch 4200)
(eval "(js-eval \"function f() { return\n42\n} f()\")")
(epoch 4201)
(eval "(js-eval \"function g() { return 42 } g()\")")
(epoch 4202)
(eval "(let ((toks (js-tokenize \"a\nb\"))) (get (nth toks 1) :nl))")
(epoch 4203)
(eval "(let ((toks (js-tokenize \"a b\"))) (get (nth toks 1) :nl))")

(epoch 4300)
(eval "(js-eval \"var x = 5; x\")")
(epoch 4301)
(eval "(js-eval \"function f() { return x; var x = 42; } f()\")")
(epoch 4302)
(eval "(js-eval \"function f() { var y = 7; return y; } f()\")")
(epoch 4303)
(eval "(js-eval \"function f() { var z; z = 3; return z; } f()\")")

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: 3 parts"      '3'

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'

# ── Phase 11.fromCharCode / parseInt / parseFloat ──────────────
check 1900 "String.fromCharCode(65)"        '"A"'
check 1901 "String.fromCharCode(72,105)"    '"Hi"'
check 1902 "parseInt('42')"                 '42'
check 1903 "parseInt(3.7)"                  '3'
check 1904 "parseFloat('3.14')"             '3.14'

# ── Phase 11.json ──────────────────────────────────────────────
check 2000 "stringify(42)"                  '"42"'
check 2001 "stringify('hi')"                '"\"hi\""'
check 2002 "stringify array"                '"[1,2,3]"'
check 2003 "stringify object"               '"{\"a\":1}"'
check 2004 "stringify true"                 '"true"'
check 2005 "parse 42"                       '42'
check 2006 "parse true"                     'true'
check 2007 "parse string"                   '"hello"'
check 2008 "parse array length"             '3'
check 2009 "parse object.a"                 '1'

# ── Phase 11.array2 ────────────────────────────────────────────
check 2100 "flat() depth 1"                '4'
check 2101 "flat(2)"                       '3'
check 2102 "fill(0)"                       '"0,0,0"'
check 2103 "fill(0,1,3)"                   '"1,0,0,4"'
check 2104 "indexOf with start"            '3'

# ── Phase 11.forofin ───────────────────────────────────────────
check 2200 "for-of array"                  '6'
check 2201 "for-in object keys count"      '2'
check 2202 "for-of string"                 '"abc"'

# ── Phase 11.strings2 ─────────────────────────────────────────
check 2300 "replace string"                '"hello JS"'
check 2301 "replace regex"                 '"heLlo"'
check 2302 "search string"                 '2'
check 2303 "search regex"                  '2'
check 2304 "match string"                  '"ll"'
check 2305 "Array.from array"              '3'
check 2306 "Array.from string"             '3'
check 2307 "Array.from w/ map"             '"2,4,6"'

# ── Phase 11.coerce ───────────────────────────────────────────
check 2400 "charAt coerces string"         '"c"'
check 2401 "slice coerces strings"         '"bc"'

# ── Phase 11.spread ───────────────────────────────────────────
check 2500 "spread in array length"        '4'
check 2501 "spread middle of array"        '"0,1,2,3"'
check 2502 "spread in call args"           '6'
check 2503 "spread Math.max"               '5'
check 2504 "spread string"                 '3'

# ── Phase 11.destruct ─────────────────────────────────────────
check 2600 "obj destructure"               '3'
check 2601 "arr destructure"               '6'
check 2602 "arr destructure skip"          '4'
check 2603 "obj partial+add"               '101'

# ── Phase 11.optchain ──────────────────────────────────────────
check 2700 "?. obj present"                '5'
check 2701 "?. obj null"                   'undefined'
check 2702 "?. obj undef"                  'undefined'
check 2703 "?. chained"                    '7'
check 2704 "?. null-chain"                 'true'

# ── Phase 11.logassign ────────────────────────────────────────
check 2800 "||= falsy"                      '5'
check 2801 "&&= truthy"                     '7'
check 2802 "??= null"                       '99'
check 2803 "??= has value"                  '42'

# ── Phase 11.callable ─────────────────────────────────────────
check 2900 "Number('42')"                   '42'
check 2901 "String(123)"                    '"123"'
check 2902 "String(true)"                   '"true"'
check 2903 "Boolean(0)"                     'false'
check 2904 "Boolean('hi')"                  'true'
check 2905 "Array(3).length"                '3'
check 2906 "Array(1,2,3).length"            '3'

# ── Phase 11.sort ────────────────────────────────────────────
check 3000 "sort default"                   '"1,2,3"'
check 3001 "sort lex (10<5)"                '"10,20,5"'
check 3002 "sort numeric"                   '"1,2,3"'
check 3003 "sort reverse"                   '"3,2,1"'

# ── Phase 11.destruct2 ────────────────────────────────────────
check 3100 "rest arr length"               '3'
check 3101 "rest arr join"                 '"2,3,4"'

# ── Phase 11.destruct3 ────────────────────────────────────────
check 3200 "obj rename"                    '100'
check 3201 "obj multi rename"              '3'

# ── Phase 11.delete ───────────────────────────────────────────
check 3300 "delete obj.x"                  'true'
check 3301 "delete obj.a keeps b"         '2'

# ── Phase 11.numkey: numeric object-literal keys ─────────────
check 3350 "numeric key [0]"               '41'
check 3351 "numkey with length"           '3'
check 3352 "numeric key ['0']"             '"x"'

# ── Phase 11.fnmethod: call/apply/bind ────────────────────────
check 3400 "fn.call basic"                 '"hi ada"'
check 3401 "fn.apply basic"                '"hi bob"'
check 3402 "fn.bind partial"               '6'
check 3403 "fn.call this-binding"          '42'
check 3404 "fn.apply arg-unpack"           '7'
check 3405 "Array.prototype.push.call arr" '4'

# ── Phase 11.nummethod: number/boolean methods ────────────────
check 3600 "(5).toString()"                '"5"'
check 3601 "(16).toString(16)"             '"10"'
check 3602 "(10).toString(2)"              '"1010"'
check 3603 "(3.14).toFixed(1)"             '"3.1"'
check 3604 "(3.14).toFixed(2)"             '"3.14"'
check 3605 "(0).toFixed(3)"                '"0.000"'
check 3606 "(7).valueOf()"                 '7'
check 3607 "true.toString()"               '"true"'
check 3608 "false.toString()"              '"false"'
check 3609 "(true).valueOf()"              'true'

# ── Phase 11.arrmore: more Array.prototype methods ────────────
check 3700 "arr.at(0)"                     '1'
check 3701 "arr.at(-1)"                    '3'
check 3702 "arr.flatMap"                   '"1,2,2,4,3,6,4,8"'
check 3703 "arr.findLast"                  '3'
check 3704 "arr.findLastIndex"             '4'
check 3705 "arr.reduceRight"               '"4,3,2,1"'
check 3706 "arr.toString"                  '"1,2,3"'
check 3707 "arr.toReversed"                '"2,1,3"'
check 3708 "arr.toSorted"                  '"1,1,3,4,5"'
check 3709 "arr.keys"                      '"0,1,2"'

# ── Phase 11.fnlen: fn.length arity ───────────────────────────
check 4100 "fn () length"                  '0'
check 4101 "fn (a) length"                 '1'
check 4102 "fn (a,b,c) length"             '3'
check 4103 "fn rest length"                '1'
check 4104 "Math.abs.length"               '1'
check 4105 "Math.floor.length"             '1'

# ── Phase 11.coerce2: Number coercion edge cases ─────────────
check 4001 "isNaN(Number('abc'))"          'true'
check 4002 "parseInt leading digits"       '123'
check 4003 "parseFloat leading"            '3.14'
check 4005 "parseInt radix 2"              '10'
check 4006 "Number with spaces"            '42'
check 4007 "Number('')"                    '0'
check 4008 "encodeURIComponent space"      '"a%20b"'
check 4009 "encodeURIComponent +?"         '"a%2Bb%3Fc"'

# ── Phase 11.objglobal: Object.getPrototypeOf, .create, .is, .hasOwn ──
check 3900 "Object.create(null)"           '5'
check 3901 "Object.is(NaN,NaN)"            'true'
check 3902 "Object.is(0,0)"                'true'
check 3903 "Object.hasOwn a"               'true'
check 3904 "Object.hasOwn missing"         'false'
check 3905 "Object.fromEntries"            '1'
check 3906 "Object.defineProperty"         '42'
check 3907 "Object.getOwnPropertyNames"    '1'
check 3908 "Object.isExtensible"           'true'

# ── Phase 11.globals: NaN / Infinity / strict-eq ─────────────
check 3750 "typeof NaN"                    '"number"'
check 3751 "isNaN(NaN)"                    'true'
check 3752 "isNaN(5)"                      'false'
check 3753 "NaN === NaN"                   'false'
check 3754 "Infinity > 100"                'true'
check 3755 "-Infinity < 0"                 'true'
check 3756 "isFinite(1)"                   'true'
check 3757 "isFinite(Infinity)"            'false'

# ── Phase 11.strmore: more String.prototype methods ───────────
check 3800 "'hello'.at(0)"                 '"h"'
check 3801 "'hello'.at(-1)"                '"o"'
check 3802 "'hello'.codePointAt(0)"        '104'
check 3803 "lastIndexOf found"             '12'
check 3804 "localeCompare less"            '-1'
check 3805 "localeCompare equal"           '0'
check 3806 "replaceAll multiple"           '"bye bye"'
check 3807 "replaceAll commas"             '"a-b-c"'
check 3808 "toLocaleUpperCase"             '"HI"'
check 3809 "toLocaleLowerCase"             '"hi"'

# ── Phase 11.arrlike: array-like receivers on Array.prototype ─
check 3500 "slice.call arrLike length"     '3'
check 3501 "slice.call arrLike join"       '"41,42,43"'
check 3502 "map.call arrLike"              '"2,4,6"'
check 3503 "indexOf.call arrLike"          '1'
check 3504 "filter.call arrLike"           '"2,3"'
check 3505 "forEach.call arrLike sum"      '60'

# ── Phase 12: Regex engine ────────────────────────────────────────
check 5000 "regex platform installed"           'false'
check 5001 "/foo/ matches"                      'true'
check 5002 "/foo/ no match"                     'false'
check 5003 "/FOO/i case-insensitive"            'true'
check 5004 "/^hello/ anchor match"              'true'
check 5005 "/^hello/ anchor no-match"           'false'
check 5006 "/world$/ end anchor"                'true'
check 5007 "/\\d+/ digit class"                 'true'
check 5008 "/\\w+/ word class"                  'true'
check 5009 "/[abc]/ class no-match"             'false'
check 5010 "/[abc]/ class match"                'true'
check 5011 "/a*b/ zero-or-more"                 'true'
check 5012 "/a+b/ one-or-more no-match"         'false'
check 5013 "/a{2,3}/ quant match"               'true'
check 5014 "/a{2,3}/ quant no-match"            'false'
check 5015 "dot matches any"                    'true'
check 5016 "dot requires char"                  'false'
check 5017 "exec match string"                  '"foobar"'
check 5018 "exec match index"                   '0'
check 5019 "exec capture group"                 '"bar"'
check 5020 "alternation cat|dog match"          'true'
check 5021 "alternation cat|dog no-match"       'false'
check 5022 "non-capturing group"                'true'
check 5023 "negated class match"                'true'
check 5024 "negated class no-match"             'false'
check 5025 "range [a-z]+"                       'true'
check 5026 "word boundary match"                'true'
check 5027 "word boundary no-match"             'false'
check 5028 "lazy quantifier"                    '"a"'
check 5029 "global exec advances"               '"2"'
check 5030 "String.match regex"                 '"hello"'
check 5031 "String.search regex"                '6'
check 5032 "String.replace regex"               '"hello there"'
check 5033 "multiline anchor"                   'true'

# ── Phase 13: let/const TDZ infrastructure ───────────────────────
check 5100 "let binding initialized"            '5'
check 5101 "const binding initialized"          '42'
check 5102 "TDZ sentinel is detectable"         'true'
check 5103 "tdz-check passes non-sentinel"      '42'

# ── Phase 22: Bitwise ops ─────────────────────────────────────────
check 6000 "bitand 5&3"     '1'
check 6001 "bitor 5|3"      '7'
check 6002 "bitxor 5^3"     '6'
check 6003 "lshift 1<<4"    '16'
check 6004 "rshift 32>>2"   '8'
check 6005 "rshift -8>>1"   '-4'
check 6006 "urshift >>>"     '1073741823'
check 6007 "bitnot ~0"      '-1'

# ── Phase 22: Map ─────────────────────────────────────────────────
check 6010 "map? new map"          'true'
check 6011 "map set→size 1"        '1'
check 6012 "map get existing"      '1'
check 6013 "map has key yes"       'true'
check 6014 "map has key no"        'false'
check 6015 "map size 2 entries"    '2'
check 6016 "map delete→size 0"     '0'
check 6017 "map set overwrites"    '99'

# ── Phase 22: Set ─────────────────────────────────────────────────
check 6020 "set? new set"          'true'
check 6021 "set has after add"     'true'
check 6022 "set has absent"        'false'
check 6023 "set dedup size"        '1'
check 6024 "set size 2"            '2'
check 6025 "set delete→size 0"     '0'

# ── Phase 22: RegExp ──────────────────────────────────────────────
check 6030 "RegExp? result"        'true'
check 6031 "RegExp global flag"    'true'
check 6032 "RegExp ignoreCase"     'true'
# ── Phase 1.ASI: automatic semicolon insertion ────────────────────
check 4200 "return+newline → undefined"    '"js-undefined"'
check 4201 "return+space+val → val"        '42'
check 4202 "nl-before flag set after newline" 'true'
check 4203 "nl-before flag false on same line" 'false'

check 4300 "var decl program-level"         '5'
check 4301 "var hoisted before use → undef" '"js-undefined"'
check 4302 "var in function body"           '7'
check 4303 "var then set in function"       '3'

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 ]
