#!/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 \"`[${''}-${''}]`\")") 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="" 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" '"[-]"' 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 ]