Array destructure now supports [a, ...rest]. Rest entry is transpiled to (define name (js-list-slice tmp i (len tmp))). Nested patterns like [[a,b], c] now parse (as holes) instead of erroring. jp-skip-balanced skips nested groups. 440/442 unit (+2), 148/148 slice unchanged.
1763 lines
65 KiB
Bash
Executable File
1763 lines
65 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\")")
|
|
|
|
;; ── 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(',')\")")
|
|
|
|
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'
|
|
|
|
# ── 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"'
|
|
|
|
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 ]
|