#!/usr/bin/env bash
# Fast Lua-on-SX test runner — epoch protocol direct to sx_server.exe.
# Mirrors lib/js/test.sh.
#
# Usage:
#   bash lib/lua/test.sh           # run all tests
#   bash lib/lua/test.sh -v        # verbose

set -uo pipefail
cd "$(git rev-parse --show-toplevel)"

SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
  # fallback to main repo binary when running from a worktree without _build
  SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
  echo "ERROR: sx_server.exe 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 0)
(load "lib/guest/lex.sx")
(epoch 0a)
(load "lib/guest/prefix.sx")
(epoch 1)
(load "lib/lua/tokenizer.sx")
(epoch 2)
(load "lib/lua/parser.sx")
(epoch 3)
(load "lib/lua/runtime.sx")
(epoch 4)
(load "lib/lua/transpile.sx")

;; ── Phase 1: tokenizer ──────────────────────────────────────────
(epoch 100)
(eval "(len (lua-tokenize \"\"))")
(epoch 101)
(eval "(get (nth (lua-tokenize \"\") 0) :type)")

;; Numbers
(epoch 110)
(eval "(get (nth (lua-tokenize \"42\") 0) :type)")
(epoch 111)
(eval "(get (nth (lua-tokenize \"42\") 0) :value)")
(epoch 112)
(eval "(get (nth (lua-tokenize \"3.14\") 0) :value)")
(epoch 113)
(eval "(get (nth (lua-tokenize \"0xff\") 0) :value)")
(epoch 114)
(eval "(get (nth (lua-tokenize \"1e3\") 0) :value)")
(epoch 115)
(eval "(get (nth (lua-tokenize \"1.5e-2\") 0) :value)")
(epoch 116)
(eval "(get (nth (lua-tokenize \".5\") 0) :value)")

;; Identifiers and keywords
(epoch 120)
(eval "(get (nth (lua-tokenize \"foo\") 0) :type)")
(epoch 121)
(eval "(get (nth (lua-tokenize \"foo\") 0) :value)")
(epoch 122)
(eval "(get (nth (lua-tokenize \"_bar1\") 0) :value)")
(epoch 123)
(eval "(get (nth (lua-tokenize \"local\") 0) :type)")
(epoch 124)
(eval "(get (nth (lua-tokenize \"function\") 0) :value)")
(epoch 125)
(eval "(get (nth (lua-tokenize \"nil\") 0) :type)")
(epoch 126)
(eval "(get (nth (lua-tokenize \"true\") 0) :value)")
(epoch 127)
(eval "(get (nth (lua-tokenize \"false\") 0) :type)")

;; Short strings
(epoch 130)
(eval "(get (nth (lua-tokenize \"\\\"hi\\\"\") 0) :type)")
(epoch 131)
(eval "(get (nth (lua-tokenize \"\\\"hi\\\"\") 0) :value)")
(epoch 132)
(eval "(get (nth (lua-tokenize \"'ab'\") 0) :value)")
(epoch 133)
(eval "(get (nth (lua-tokenize \"\\\"a\\\\nb\\\"\") 0) :value)")

;; Long strings
(epoch 140)
(eval "(get (nth (lua-tokenize \"[[hello]]\") 0) :type)")
(epoch 141)
(eval "(get (nth (lua-tokenize \"[[hello]]\") 0) :value)")
(epoch 142)
(eval "(get (nth (lua-tokenize \"[==[level 2]==]\") 0) :value)")

;; Operators (multi-char)
(epoch 150)
(eval "(get (nth (lua-tokenize \"==\") 0) :value)")
(epoch 151)
(eval "(get (nth (lua-tokenize \"~=\") 0) :value)")
(epoch 152)
(eval "(get (nth (lua-tokenize \"<=\") 0) :value)")
(epoch 153)
(eval "(get (nth (lua-tokenize \">=\") 0) :value)")
(epoch 154)
(eval "(get (nth (lua-tokenize \"..\") 0) :value)")
(epoch 155)
(eval "(get (nth (lua-tokenize \"...\") 0) :value)")
(epoch 156)
(eval "(get (nth (lua-tokenize \"::\") 0) :value)")

;; Single-char operators / punctuation
(epoch 160)
(eval "(get (nth (lua-tokenize \"+\") 0) :value)")
(epoch 161)
(eval "(get (nth (lua-tokenize \"-\") 0) :value)")
(epoch 162)
(eval "(get (nth (lua-tokenize \"*\") 0) :value)")
(epoch 163)
(eval "(get (nth (lua-tokenize \"/\") 0) :value)")
(epoch 164)
(eval "(get (nth (lua-tokenize \"%\") 0) :value)")
(epoch 165)
(eval "(get (nth (lua-tokenize \"^\") 0) :value)")
(epoch 166)
(eval "(get (nth (lua-tokenize \"#\") 0) :value)")
(epoch 167)
(eval "(get (nth (lua-tokenize \"(\") 0) :value)")
(epoch 168)
(eval "(get (nth (lua-tokenize \"{\") 0) :value)")
(epoch 169)
(eval "(get (nth (lua-tokenize \";\") 0) :value)")

;; Comments are stripped
(epoch 170)
(eval "(len (lua-tokenize \"-- comment\\n\"))")
(epoch 171)
(eval "(len (lua-tokenize \"-- comment\\n1\"))")
(epoch 172)
(eval "(get (nth (lua-tokenize \"-- c\\n42\") 0) :value)")
(epoch 173)
(eval "(len (lua-tokenize \"--[[ block ]] 1\"))")
(epoch 174)
(eval "(get (nth (lua-tokenize \"--[[ c ]] 42\") 0) :value)")
(epoch 175)
(eval "(get (nth (lua-tokenize \"--[==[ x ]==] 7\") 0) :value)")

;; Compound expressions
(epoch 180)
(eval "(len (lua-tokenize \"local x = 1\"))")
(epoch 181)
(eval "(get (nth (lua-tokenize \"local x = 1\") 0) :type)")
(epoch 182)
(eval "(get (nth (lua-tokenize \"local x = 1\") 0) :value)")
(epoch 183)
(eval "(get (nth (lua-tokenize \"local x = 1\") 1) :type)")
(epoch 184)
(eval "(get (nth (lua-tokenize \"local x = 1\") 2) :value)")
(epoch 185)
(eval "(get (nth (lua-tokenize \"local x = 1\") 3) :value)")

(epoch 190)
(eval "(len (lua-tokenize \"a.b:c()\"))")
(epoch 191)
(eval "(get (nth (lua-tokenize \"a.b:c()\") 1) :value)")
(epoch 192)
(eval "(get (nth (lua-tokenize \"a.b:c()\") 3) :value)")

;; ── Phase 1.parse: parser ────────────────────────────────────
;; Literals
(epoch 200)
(eval "(lua-parse-expr \"42\")")
(epoch 201)
(eval "(lua-parse-expr \"3.14\")")
(epoch 202)
(eval "(lua-parse-expr \"\\\"hi\\\"\")")
(epoch 203)
(eval "(lua-parse-expr \"true\")")
(epoch 204)
(eval "(lua-parse-expr \"false\")")
(epoch 205)
(eval "(lua-parse-expr \"nil\")")
(epoch 206)
(eval "(lua-parse-expr \"foo\")")
(epoch 207)
(eval "(lua-parse-expr \"...\")")

;; Binops with precedence
(epoch 210)
(eval "(lua-parse-expr \"1+2\")")
(epoch 211)
(eval "(lua-parse-expr \"a+b*c\")")
(epoch 212)
(eval "(lua-parse-expr \"a*b+c\")")
(epoch 213)
(eval "(lua-parse-expr \"a and b or c\")")
(epoch 214)
(eval "(lua-parse-expr \"a==b\")")
(epoch 215)
(eval "(lua-parse-expr \"a..b..c\")")
(epoch 216)
(eval "(lua-parse-expr \"a^b^c\")")
(epoch 217)
(eval "(lua-parse-expr \"(a+b)*c\")")

;; Unary
(epoch 220)
(eval "(lua-parse-expr \"-x\")")
(epoch 221)
(eval "(lua-parse-expr \"not x\")")
(epoch 222)
(eval "(lua-parse-expr \"#a\")")

;; Member/index/call
(epoch 230)
(eval "(lua-parse-expr \"a.b\")")
(epoch 231)
(eval "(lua-parse-expr \"a.b.c\")")
(epoch 232)
(eval "(lua-parse-expr \"a[0]\")")
(epoch 233)
(eval "(lua-parse-expr \"f()\")")
(epoch 234)
(eval "(lua-parse-expr \"f(1,2)\")")
(epoch 235)
(eval "(lua-parse-expr \"a:b()\")")

;; Table constructors
(epoch 240)
(eval "(lua-parse-expr \"{1,2,3}\")")
(epoch 241)
(eval "(lua-parse-expr \"{x=1,y=2}\")")
(epoch 242)
(eval "(lua-parse-expr \"{[1+1]=\\\"a\\\"}\")")
(epoch 243)
(eval "(lua-parse-expr \"{}\")")

;; Anonymous function
(epoch 250)
(eval "(lua-parse-expr \"function() return 1 end\")")
(epoch 251)
(eval "(lua-parse-expr \"function(a,b) return a+b end\")")
(epoch 252)
(eval "(lua-parse-expr \"function(...) return 1 end\")")

;; Statements
(epoch 260)
(eval "(lua-parse \"local x = 1\")")
(epoch 261)
(eval "(lua-parse \"local a, b = 1, 2\")")
(epoch 262)
(eval "(lua-parse \"x = 1\")")
(epoch 263)
(eval "(lua-parse \"a, b = 1, 2\")")
(epoch 264)
(eval "(lua-parse \"if x then y = 1 end\")")
(epoch 265)
(eval "(lua-parse \"if x then y = 1 else y = 2 end\")")
(epoch 266)
(eval "(lua-parse \"if x then y = 1 elseif z then y = 2 else y = 3 end\")")
(epoch 267)
(eval "(lua-parse \"while x < 10 do x = x + 1 end\")")
(epoch 268)
(eval "(lua-parse \"repeat x = x + 1 until x > 10\")")
(epoch 269)
(eval "(lua-parse \"for i = 1, 10 do x = i end\")")
(epoch 270)
(eval "(lua-parse \"for i = 1, 10, 2 do x = i end\")")
(epoch 271)
(eval "(lua-parse \"do local x = 1 end\")")
(epoch 272)
(eval "(lua-parse \"break\")")
(epoch 273)
(eval "(lua-parse \"return 42\")")
(epoch 274)
(eval "(lua-parse \"return 1, 2\")")
(epoch 275)
(eval "(lua-parse \"return\")")

;; Function declarations
(epoch 280)
(eval "(lua-parse \"function f() return 1 end\")")
(epoch 281)
(eval "(lua-parse \"local function f(x) return x * 2 end\")")
(epoch 282)
(eval "(lua-parse \"function t.m(x) return x end\")")
(epoch 283)
(eval "(lua-parse \"function t:m(x) return self end\")")

;; Calls as statements
(epoch 290)
(eval "(lua-parse \"print(42)\")")
(epoch 291)
(eval "(lua-parse \"a:b()\")")
(epoch 292)
(eval "(lua-parse \"t.f()\")")

;; Multi-statement chunks
(epoch 300)
(eval "(len (lua-parse \"local x = 1 x = x + 1 return x\"))")

;; ── Phase 2: transpile + eval ─────────────────────────────────
;; Literals via return
(epoch 400)
(eval "(lua-eval-ast \"return 1\")")
(epoch 401)
(eval "(lua-eval-ast \"return true\")")
(epoch 402)
(eval "(lua-eval-ast \"return false\")")
(epoch 403)
(eval "(lua-eval-ast \"return nil\")")
(epoch 404)
(eval "(lua-eval-ast \"return \\\"hi\\\"\")")

;; Arithmetic
(epoch 410)
(eval "(lua-eval-ast \"return 1 + 2\")")
(epoch 411)
(eval "(lua-eval-ast \"return 10 - 3\")")
(epoch 412)
(eval "(lua-eval-ast \"return 4 * 5\")")
(epoch 413)
(eval "(lua-eval-ast \"return 10 / 4\")")
(epoch 414)
(eval "(lua-eval-ast \"return 10 % 3\")")
(epoch 415)
(eval "(lua-eval-ast \"return 2 ^ 10\")")
(epoch 416)
(eval "(lua-eval-ast \"return (1 + 2) * 3\")")
(epoch 417)
(eval "(lua-eval-ast \"return 1 + 2 * 3\")")
(epoch 418)
(eval "(lua-eval-ast \"return -5 + 10\")")

;; String
(epoch 420)
(eval "(lua-eval-ast \"return \\\"a\\\" .. \\\"b\\\"\")")
(epoch 421)
(eval "(lua-eval-ast \"return \\\"count: \\\" .. 42\")")

;; Comparison
(epoch 430)
(eval "(lua-eval-ast \"return 1 < 2\")")
(epoch 431)
(eval "(lua-eval-ast \"return 3 > 2\")")
(epoch 432)
(eval "(lua-eval-ast \"return 2 == 2\")")
(epoch 433)
(eval "(lua-eval-ast \"return 1 ~= 2\")")
(epoch 434)
(eval "(lua-eval-ast \"return 1 <= 1\")")
(epoch 435)
(eval "(lua-eval-ast \"return 3 >= 2\")")

;; Logical (short-circuit, return value)
(epoch 440)
(eval "(lua-eval-ast \"return true and 42\")")
(epoch 441)
(eval "(lua-eval-ast \"return false or 99\")")
(epoch 442)
(eval "(lua-eval-ast \"return nil or 7\")")
(epoch 443)
(eval "(lua-eval-ast \"return 1 and 2\")")
(epoch 444)
(eval "(lua-eval-ast \"return false and 999\")")
(epoch 445)
(eval "(lua-eval-ast \"return not true\")")
(epoch 446)
(eval "(lua-eval-ast \"return not nil\")")
(epoch 447)
(eval "(lua-eval-ast \"return not 0\")")

;; Truthy
(epoch 450)
(eval "(lua-truthy? 0)")
(epoch 451)
(eval "(lua-truthy? nil)")
(epoch 452)
(eval "(lua-truthy? false)")
(epoch 453)
(eval "(lua-truthy? \"\")")

;; Control flow
(epoch 460)
(eval "(lua-eval-ast \"if true then return 1 else return 2 end\")")
(epoch 461)
(eval "(lua-eval-ast \"if 1 > 2 then return 100 else return 200 end\")")
(epoch 462)
(eval "(lua-eval-ast \"local x = 1 if x > 0 then x = x * 10 elseif x < 0 then x = 999 else x = 42 end return x\")")

;; Local and assignment
(epoch 470)
(eval "(lua-eval-ast \"local x = 5 return x * 2\")")
(epoch 471)
(eval "(lua-eval-ast \"local x = 0 x = x + 1 x = x + 1 return x\")")
(epoch 472)
(eval "(lua-eval-ast \"local a, b = 1, 2 return a + b\")")

;; Loops
(epoch 480)
(eval "(lua-eval-ast \"local sum = 0 for i = 1, 5 do sum = sum + i end return sum\")")
(epoch 481)
(eval "(lua-eval-ast \"local n = 0 for i = 10, 1, -1 do n = n + 1 end return n\")")
(epoch 482)
(eval "(lua-eval-ast \"local i = 0 while i < 5 do i = i + 1 end return i\")")
(epoch 483)
(eval "(lua-eval-ast \"local i = 0 repeat i = i + 1 until i >= 3 return i\")")
(epoch 484)
(eval "(lua-eval-ast \"local s = 0 for i = 1, 100 do s = s + i end return s\")")

;; ── Phase 3: functions + closures ─────────────────────────────
;; Anonymous
(epoch 500)
(eval "(lua-eval-ast \"local f = function(x) return x + 1 end return f(5)\")")
(epoch 501)
(eval "(lua-eval-ast \"local f = function() return 42 end return f()\")")
(epoch 502)
(eval "(lua-eval-ast \"return (function(a, b) return a * b end)(3, 4)\")")

;; Local function
(epoch 510)
(eval "(lua-eval-ast \"local function double(x) return x * 2 end return double(7)\")")
(epoch 511)
(eval "(lua-eval-ast \"local function sum3(a, b, c) return a + b + c end return sum3(1, 2, 3)\")")
(epoch 512)
(eval "(lua-eval-ast \"local function greet() return \\\"hi\\\" end return greet()\")")

;; Top-level function decl
(epoch 520)
(eval "(lua-eval-ast \"function add(a, b) return a + b end return add(3, 4)\")")
(epoch 521)
(eval "(lua-eval-ast \"function id(x) return x end return id(\\\"abc\\\")\")")

;; Closures — lexical capture
(epoch 530)
(eval "(lua-eval-ast \"local x = 10 local function getx() return x end return getx()\")")
(epoch 531)
(eval "(lua-eval-ast \"local function make_adder(n) return function(x) return x + n end end local add5 = make_adder(5) return add5(10)\")")
(epoch 532)
(eval "(lua-eval-ast \"local function counter() local n = 0 return function() n = n + 1 return n end end local c = counter() c() c() return c()\")")
(epoch 533)
(eval "(lua-eval-ast \"local a = 1 local b = 2 local function f() return a + b end a = 10 return f()\")")

;; Recursion
(epoch 540)
(eval "(lua-eval-ast \"local function fact(n) if n <= 1 then return 1 else return n * fact(n - 1) end end return fact(5)\")")
(epoch 541)
(eval "(lua-eval-ast \"function fib(n) if n < 2 then return n else return fib(n-1) + fib(n-2) end end return fib(10)\")")

;; Higher-order
(epoch 550)
(eval "(lua-eval-ast \"local function apply(f, x) return f(x) end local function sq(n) return n * n end return apply(sq, 4)\")")
(epoch 551)
(eval "(lua-eval-ast \"local function twice(f, x) return f(f(x)) end return twice(function(n) return n + 1 end, 5)\")")

;; Mixed with control flow
(epoch 560)
(eval "(lua-eval-ast \"local function max(a, b) if a > b then return a else return b end end return max(7, 3)\")")
(epoch 561)
(eval "(lua-eval-ast \"local function sum_to(n) local s = 0 for i = 1, n do s = s + i end return s end return sum_to(10)\")")

;; ── Phase 3: multi-return + unpack ────────────────────────────
(epoch 570)
(eval "(lua-eval-ast \"local a, b = (function() return 1, 2 end)() return a + b\")")
(epoch 571)
(eval "(lua-eval-ast \"local function two() return 10, 20 end local a, b = two() return b - a\")")
(epoch 572)
(eval "(lua-eval-ast \"function swap(a, b) return b, a end local x, y = swap(1, 2) return x * 10 + y\")")
(epoch 573)
(eval "(lua-eval-ast \"local function three() return 1, 2, 3 end local a, b = three() return a * 10 + b\")")
(epoch 574)
(eval "(lua-eval-ast \"local function two() return 1, 2 end local a, b, c = two() if c == nil then return a + b else return 0 end\")")
(epoch 575)
(eval "(lua-eval-ast \"local function two() return 5, 7 end local function outer() return two() end local a, b = outer() return a + b\")")
(epoch 576)
(eval "(lua-eval-ast \"local function two() return 9, 9 end local a, b = two(), 5 return a * 10 + b\")")
(epoch 577)
(eval "(lua-eval-ast \"local function pair() return 4, 5 end local x = pair() return x + 1\")")
(epoch 578)
(eval "(lua-eval-ast \"local function none() return end local a, b = none() if a == nil and b == nil then return 99 else return 0 end\")")
(epoch 579)
(eval "(lua-eval-ast \"local a = 0 local b = 0 local function m() return 7, 11 end a, b = m() return a + b\")")

;; ── Phase 3: table constructors ────────────────────────────────
;; Array part
(epoch 600)
(eval "(lua-eval-ast \"local t = {10, 20, 30} return t[1]\")")
(epoch 601)
(eval "(lua-eval-ast \"local t = {10, 20, 30} return t[3]\")")
(epoch 602)
(eval "(lua-eval-ast \"local t = {10, 20, 30} if t[4] == nil then return 1 else return 0 end\")")

;; Hash part
(epoch 610)
(eval "(lua-eval-ast \"local t = {x = 1, y = 2} return t.x + t.y\")")
(epoch 611)
(eval "(lua-eval-ast \"local t = {name = \\\"bob\\\", age = 30} return t.name\")")
(epoch 612)
(eval "(lua-eval-ast \"local t = {name = \\\"bob\\\", age = 30} return t.age\")")

;; Computed keys
(epoch 620)
(eval "(lua-eval-ast \"local k = \\\"answer\\\" local t = {[k] = 42} return t.answer\")")
(epoch 621)
(eval "(lua-eval-ast \"local t = {[1+1] = \\\"two\\\"} return t[2]\")")
(epoch 622)
(eval "(lua-eval-ast \"local t = {[\\\"a\\\" .. \\\"b\\\"] = 99} return t.ab\")")
(epoch 623)
(eval "(lua-eval-ast \"local t = {[true] = \\\"yes\\\", [false] = \\\"no\\\"} return t[true]\")")

;; Mixed array + hash
(epoch 630)
(eval "(lua-eval-ast \"local t = {1, 2, 3, key = \\\"v\\\"} return t[2]\")")
(epoch 631)
(eval "(lua-eval-ast \"local t = {1, 2, 3, key = \\\"v\\\"} return t.key\")")

;; Separators
(epoch 640)
(eval "(lua-eval-ast \"local t = {1, 2, 3,} return t[3]\")")
(epoch 641)
(eval "(lua-eval-ast \"local t = {1; 2; 3} return t[2]\")")
(epoch 642)
(eval "(lua-eval-ast \"local t = {1, 2; 3, 4} return t[4]\")")

;; Nested
(epoch 650)
(eval "(lua-eval-ast \"local t = {{1, 2}, {3, 4}} return t[2][1]\")")
(epoch 651)
(eval "(lua-eval-ast \"local t = {inner = {a = 1, b = 2}} return t.inner.b\")")

;; Dynamic values
(epoch 660)
(eval "(lua-eval-ast \"local x = 7 local t = {x, x * 2, x * 3} return t[2]\")")
(epoch 661)
(eval "(lua-eval-ast \"local function f(n) return n + 1 end local t = {f(1), f(2), f(3)} return t[3]\")")

;; Functions as values
(epoch 670)
(eval "(lua-eval-ast \"local t = {fn = function(x) return x * 2 end} return t.fn(5)\")")

;; ── Phase 3: raw table access (read + write + #) ───────────────
;; Write then read array index
(epoch 700)
(eval "(lua-eval-ast \"local t = {} t[1] = \\\"a\\\" t[2] = \\\"b\\\" return t[1]\")")
(epoch 701)
(eval "(lua-eval-ast \"local t = {} t[1] = 10 t[2] = 20 return t[1] + t[2]\")")

;; Write then read field
(epoch 710)
(eval "(lua-eval-ast \"local t = {} t.x = 100 return t.x\")")
(epoch 711)
(eval "(lua-eval-ast \"local t = {} t.name = \\\"alice\\\" t.age = 30 return t.name\")")
(epoch 712)
(eval "(lua-eval-ast \"local t = {x = 1} t.x = 99 return t.x\")")

;; Missing key is nil
(epoch 720)
(eval "(lua-eval-ast \"local t = {x = 1} if t.y == nil then return 99 else return 0 end\")")
(epoch 721)
(eval "(lua-eval-ast \"local t = {} if t[1] == nil then return 1 else return 0 end\")")

;; Length operator
(epoch 730)
(eval "(lua-eval-ast \"local t = {10, 20, 30, 40, 50} return #t\")")
(epoch 731)
(eval "(lua-eval-ast \"local t = {} return #t\")")
(epoch 732)
(eval "(lua-eval-ast \"local t = {} t[1] = 5 t[2] = 10 return #t\")")
(epoch 733)
(eval "(lua-eval-ast \"return #\\\"hello\\\"\")")

;; Mixed read/write
(epoch 740)
(eval "(lua-eval-ast \"local t = {count = 0} t.count = t.count + 1 t.count = t.count + 1 return t.count\")")
(epoch 741)
(eval "(lua-eval-ast \"local t = {} for i = 1, 5 do t[i] = i * i end return t[3]\")")
(epoch 742)
(eval "(lua-eval-ast \"local t = {} for i = 1, 10 do t[i] = i end return #t\")")

;; Chained field read/write
(epoch 750)
(eval "(lua-eval-ast \"local t = {a = {b = {c = 42}}} return t.a.b.c\")")
(epoch 751)
(eval "(lua-eval-ast \"local t = {a = {}} t.a.x = 7 return t.a.x\")")

;; Computed key read/write
(epoch 760)
(eval "(lua-eval-ast \"local k = \\\"foo\\\" local t = {} t[k] = 88 return t.foo\")")
(epoch 761)
(eval "(lua-eval-ast \"local t = {} t[\\\"x\\\" .. \\\"y\\\"] = 7 return t.xy\")")

;; Reference semantics
(epoch 770)
(eval "(lua-eval-ast \"local t = {} local s = t s.x = 42 return t.x\")")

;; ── Phase 4: metatables ────────────────────────────────────────
;; setmetatable / getmetatable
(epoch 800)
(eval "(lua-eval-ast \"local t = {} local r = setmetatable(t, {}) if r == t then return 1 else return 0 end\")")
(epoch 801)
(eval "(lua-eval-ast \"local mt = {} local t = setmetatable({}, mt) if getmetatable(t) == mt then return 1 else return 0 end\")")

;; type()
(epoch 810)
(eval "(lua-eval-ast \"return type(1)\")")
(epoch 811)
(eval "(lua-eval-ast \"return type(\\\"s\\\")\")")
(epoch 812)
(eval "(lua-eval-ast \"return type({})\")")
(epoch 813)
(eval "(lua-eval-ast \"return type(nil)\")")
(epoch 814)
(eval "(lua-eval-ast \"return type(true)\")")
(epoch 815)
(eval "(lua-eval-ast \"return type(function() end)\")")

;; __index
(epoch 820)
(eval "(lua-eval-ast \"local base = {x = 10} local t = setmetatable({}, {__index = base}) return t.x\")")
(epoch 821)
(eval "(lua-eval-ast \"local base = {x = 10} local t = setmetatable({y = 20}, {__index = base}) return t.x + t.y\")")
(epoch 822)
(eval "(lua-eval-ast \"local t = setmetatable({}, {__index = function(tbl, k) return k .. \\\"!\\\" end}) return t.hi\")")

;; __newindex
(epoch 830)
(eval "(lua-eval-ast \"local log = {} local t = setmetatable({}, {__newindex = function(tbl, k, v) log[1] = k end}) t.foo = 1 return log[1]\")")

;; Arithmetic metamethods
(epoch 840)
(eval "(lua-eval-ast \"local mt = {__add = function(a, b) return 99 end} local x = setmetatable({}, mt) return x + 1\")")
(epoch 841)
(eval "(lua-eval-ast \"local mt = {__add = function(a, b) return a.v + b.v end} local x = setmetatable({v = 3}, mt) local y = setmetatable({v = 4}, mt) return x + y\")")
(epoch 842)
(eval "(lua-eval-ast \"local mt = {__sub = function(a, b) return 7 end, __mul = function(a, b) return 11 end} local t = setmetatable({}, mt) return t - t + (t * t)\")")
(epoch 843)
(eval "(lua-eval-ast \"local mt = {__unm = function(a) return 42 end} local t = setmetatable({}, mt) return -t\")")
(epoch 844)
(eval "(lua-eval-ast \"local mt = {__concat = function(a, b) return \\\"cat\\\" end} local t = setmetatable({}, mt) return t .. \\\"x\\\"\")")

;; Comparison metamethods
(epoch 850)
(eval "(lua-eval-ast \"local mt = {__eq = function(a, b) return true end} local x = setmetatable({}, mt) local y = setmetatable({}, mt) if x == y then return 1 else return 0 end\")")
(epoch 851)
(eval "(lua-eval-ast \"local mt = {__lt = function(a, b) return true end} local x = setmetatable({}, mt) local y = setmetatable({}, mt) if x < y then return 1 else return 0 end\")")
(epoch 852)
(eval "(lua-eval-ast \"local mt = {__le = function(a, b) return true end} local x = setmetatable({}, mt) local y = setmetatable({}, mt) if x <= y then return 1 else return 0 end\")")

;; __call
(epoch 860)
(eval "(lua-eval-ast \"local mt = {__call = function(t, x) return x + 100 end} local obj = setmetatable({}, mt) return obj(5)\")")

;; __len
(epoch 870)
(eval "(lua-eval-ast \"local mt = {__len = function(t) return 99 end} local t = setmetatable({}, mt) return #t\")")

;; Classic OO pattern
(epoch 880)
(eval "(lua-eval-ast \"local Animal = {} Animal.__index = Animal function Animal:new(name) local o = setmetatable({name = name}, self) return o end function Animal:getName() return self.name end local a = Animal:new(\\\"Rex\\\") return a:getName()\")")

;; ── Phase 4: pcall / xpcall / error ────────────────────────────
(epoch 900)
(eval "(lua-eval-ast \"local ok, err = pcall(function() error(\\\"boom\\\") end) if ok then return 0 else return err end\")")
(epoch 901)
(eval "(lua-eval-ast \"local ok, v = pcall(function() return 42 end) if ok then return v else return -1 end\")")
(epoch 902)
(eval "(lua-eval-ast \"local ok, a, b = pcall(function() return 1, 2 end) return (ok and a + b) or 0\")")
(epoch 903)
(eval "(lua-eval-ast \"local function div(a, b) if b == 0 then error(\\\"div by zero\\\") end return a / b end local ok, r = pcall(div, 10, 2) if ok then return r else return -1 end\")")
(epoch 904)
(eval "(lua-eval-ast \"local function div(a, b) if b == 0 then error(\\\"div by zero\\\") end return a / b end local ok, e = pcall(div, 10, 0) if ok then return \\\"no\\\" else return e end\")")
(epoch 905)
(eval "(lua-eval-ast \"local function f() error({code = 42}) end local ok, err = pcall(f) return err.code\")")
(epoch 906)
(eval "(lua-eval-ast \"local ok1, e1 = pcall(function() local ok2, e2 = pcall(function() error(\\\"inner\\\") end) if not ok2 then error(\\\"outer:\\\" .. e2) end end) return e1\")")

;; xpcall
(epoch 910)
(eval "(lua-eval-ast \"local function f() error(\\\"raw\\\") end local function handler(e) return \\\"H:\\\" .. e end local ok, v = xpcall(f, handler) if ok then return \\\"no\\\" else return v end\")")
(epoch 911)
(eval "(lua-eval-ast \"local function f() return 99 end local function handler(e) return e end local ok, v = xpcall(f, handler) return v\")")

;; ── Phase 4: generic `for … in …` ──────────────────────────────
;; ipairs over array
(epoch 950)
(eval "(lua-eval-ast \"local t = {10, 20, 30} local sum = 0 for i, v in ipairs(t) do sum = sum + v end return sum\")")
(epoch 951)
(eval "(lua-eval-ast \"local t = {10, 20, 30} local last = 0 for i, v in ipairs(t) do last = i end return last\")")
(epoch 952)
(eval "(lua-eval-ast \"local t = {} local count = 0 for i, v in ipairs(t) do count = count + 1 end return count\")")
(epoch 953)
(eval "(lua-eval-ast \"local t = {1, 2, nil, 4} local c = 0 for i, v in ipairs(t) do c = c + 1 end return c\")")

;; pairs over hash
(epoch 960)
(eval "(lua-eval-ast \"local t = {a = 1, b = 2, c = 3} local sum = 0 for k, v in pairs(t) do sum = sum + v end return sum\")")
(epoch 961)
(eval "(lua-eval-ast \"local t = {x = 1, y = 2, z = 3} local n = 0 for k, v in pairs(t) do n = n + 1 end return n\")")

;; custom stateful iterator (if-else-return, works around early-return limit)
(epoch 970)
(eval "(lua-eval-ast \"local function range(n) local i = 0 return function() i = i + 1 if i > n then return nil else return i end end end local c = 0 for x in range(5) do c = c + x end return c\")")

;; 3-value iterator form (f, s, var)
(epoch 971)
(eval "(lua-eval-ast \"local function step(max, i) if i >= max then return nil else return i + 1, (i + 1) * (i + 1) end end local sum = 0 for i, v in step, 4, 0 do sum = sum + v end return sum\")")

;; pairs ignores __meta key
(epoch 980)
(eval "(lua-eval-ast \"local t = setmetatable({x = 1, y = 2}, {}) local n = 0 for k in pairs(t) do n = n + 1 end return n\")")

;; ── Phase 5: coroutines ────────────────────────────────────────
(epoch 1000)
(eval "(lua-eval-ast \"local co = coroutine.create(function() end) return coroutine.status(co)\")")
(epoch 1001)
(eval "(lua-eval-ast \"local co = coroutine.create(function() return 42 end) coroutine.resume(co) return coroutine.status(co)\")")
(epoch 1010)
(eval "(lua-eval-ast \"local co = coroutine.create(function() coroutine.yield(1) coroutine.yield(2) return 3 end) local ok1, v1 = coroutine.resume(co) local ok2, v2 = coroutine.resume(co) local ok3, v3 = coroutine.resume(co) return v1 * 100 + v2 * 10 + v3\")")
(epoch 1011)
(eval "(lua-eval-ast \"local co = coroutine.create(function(a, b) return a + b end) local ok, v = coroutine.resume(co, 10, 20) return v\")")
(epoch 1012)
(eval "(lua-eval-ast \"local co = coroutine.create(function() local x = coroutine.yield() return x + 100 end) coroutine.resume(co) local ok, v = coroutine.resume(co, 42) return v\")")
(epoch 1020)
(eval "(lua-eval-ast \"local co = coroutine.create(function() return 42 end) coroutine.resume(co) local ok, err = coroutine.resume(co) if ok then return \\\"no\\\" else return err end\")")
(epoch 1030)
(eval "(lua-eval-ast \"local gen = coroutine.wrap(function() coroutine.yield(1) coroutine.yield(2) coroutine.yield(3) end) return gen() + gen() + gen()\")")
(epoch 1040)
(eval "(lua-eval-ast \"local function iter() coroutine.yield(10) coroutine.yield(20) coroutine.yield(30) end local co = coroutine.create(iter) local sum = 0 for i = 1, 3 do local ok, v = coroutine.resume(co) sum = sum + v end return sum\")")

;; ── Phase 6: string library ───────────────────────────────────
(epoch 1100)
(eval "(lua-eval-ast \"return string.len(\\\"hello\\\")\")")
(epoch 1101)
(eval "(lua-eval-ast \"return string.upper(\\\"hi\\\")\")")
(epoch 1102)
(eval "(lua-eval-ast \"return string.lower(\\\"HI\\\")\")")
(epoch 1103)
(eval "(lua-eval-ast \"return string.rep(\\\"ab\\\", 3)\")")
(epoch 1110)
(eval "(lua-eval-ast \"return string.sub(\\\"hello\\\", 2, 4)\")")
(epoch 1111)
(eval "(lua-eval-ast \"return string.sub(\\\"hello\\\", -3)\")")
(epoch 1112)
(eval "(lua-eval-ast \"return string.sub(\\\"hello\\\", 1, -2)\")")
(epoch 1120)
(eval "(lua-eval-ast \"return string.byte(\\\"A\\\")\")")
(epoch 1121)
(eval "(lua-eval-ast \"return string.byte(\\\"ABC\\\", 2)\")")
(epoch 1130)
(eval "(lua-eval-ast \"return string.char(72, 105)\")")
(epoch 1131)
(eval "(lua-eval-ast \"return string.char(97, 98, 99)\")")
(epoch 1140)
(eval "(lua-eval-ast \"local s, e = string.find(\\\"hello world\\\", \\\"wor\\\") return s * 100 + e\")")
(epoch 1141)
(eval "(lua-eval-ast \"if string.find(\\\"abc\\\", \\\"z\\\") == nil then return 1 else return 0 end\")")
(epoch 1150)
(eval "(lua-eval-ast \"return string.match(\\\"hello\\\", \\\"ell\\\")\")")
(epoch 1160)
(eval "(lua-eval-ast \"local r, n = string.gsub(\\\"abcabc\\\", \\\"a\\\", \\\"X\\\") return r .. \\\":\\\" .. n\")")
(epoch 1161)
(eval "(lua-eval-ast \"local r, n = string.gsub(\\\"aaaa\\\", \\\"a\\\", \\\"b\\\", 2) return r .. \\\":\\\" .. n\")")
(epoch 1170)
(eval "(lua-eval-ast \"local c = 0 for w in string.gmatch(\\\"aa aa aa\\\", \\\"aa\\\") do c = c + 1 end return c\")")
(epoch 1180)
(eval "(lua-eval-ast \"return string.format(\\\"%s=%d\\\", \\\"x\\\", 42)\")")
(epoch 1181)
(eval "(lua-eval-ast \"return string.format(\\\"%d%%\\\", 50)\")")

;; ── Phase 6: math library ─────────────────────────────────────
(epoch 1200)
(eval "(lua-eval-ast \"return math.pi > 3.14 and math.pi < 3.15\")")
(epoch 1201)
(eval "(lua-eval-ast \"return math.huge > 1000000\")")
(epoch 1210)
(eval "(lua-eval-ast \"return math.abs(-7)\")")
(epoch 1211)
(eval "(lua-eval-ast \"return math.sqrt(16)\")")
(epoch 1212)
(eval "(lua-eval-ast \"return math.floor(3.7)\")")
(epoch 1213)
(eval "(lua-eval-ast \"return math.ceil(3.2)\")")
(epoch 1220)
(eval "(lua-eval-ast \"return math.max(3, 7, 1, 4)\")")
(epoch 1221)
(eval "(lua-eval-ast \"return math.min(3, 7, 1, 4)\")")
(epoch 1230)
(eval "(lua-eval-ast \"return math.pow(2, 8)\")")
(epoch 1231)
(eval "(lua-eval-ast \"return math.exp(0)\")")
(epoch 1232)
(eval "(lua-eval-ast \"return math.log(1)\")")
(epoch 1233)
(eval "(lua-eval-ast \"return math.log10(100)\")")
(epoch 1240)
(eval "(lua-eval-ast \"return math.sin(0) + math.cos(0)\")")
(epoch 1250)
(eval "(lua-eval-ast \"return math.fmod(10, 3)\")")
(epoch 1251)
(eval "(lua-eval-ast \"local i, f = math.modf(3.5) return i\")")
(epoch 1260)
(eval "(lua-eval-ast \"local r = math.random(100) return r >= 1 and r <= 100\")")
(epoch 1261)
(eval "(lua-eval-ast \"local r = math.random(5, 10) return r >= 5 and r <= 10\")")

;; ── Phase 6: table library ────────────────────────────────────
(epoch 1300)
(eval "(lua-eval-ast \"local t = {1, 2, 3} table.insert(t, 4) return #t\")")
(epoch 1301)
(eval "(lua-eval-ast \"local t = {1, 2, 3} table.insert(t, 4) return t[4]\")")
(epoch 1302)
(eval "(lua-eval-ast \"local t = {10, 30} table.insert(t, 2, 20) return t[2]\")")
(epoch 1303)
(eval "(lua-eval-ast \"local t = {10, 20, 30} table.insert(t, 1, 5) return t[1] * 100 + t[2]\")")
(epoch 1310)
(eval "(lua-eval-ast \"local t = {1, 2, 3} local v = table.remove(t) return v * 10 + #t\")")
(epoch 1311)
(eval "(lua-eval-ast \"local t = {10, 20, 30} table.remove(t, 1) return t[1] * 10 + t[2]\")")
(epoch 1320)
(eval "(lua-eval-ast \"local t = {\\\"a\\\", \\\"b\\\", \\\"c\\\"} return table.concat(t)\")")
(epoch 1321)
(eval "(lua-eval-ast \"local t = {\\\"a\\\", \\\"b\\\", \\\"c\\\"} return table.concat(t, \\\"-\\\")\")")
(epoch 1322)
(eval "(lua-eval-ast \"local t = {1, 2, 3, 4} return table.concat(t, \\\",\\\", 2, 3)\")")
(epoch 1330)
(eval "(lua-eval-ast \"local t = {3, 1, 4, 1, 5, 9, 2, 6} table.sort(t) return t[1] * 100 + t[8]\")")
(epoch 1331)
(eval "(lua-eval-ast \"local t = {3, 1, 2} table.sort(t, function(a, b) return a > b end) return t[1] * 100 + t[3]\")")
(epoch 1340)
(eval "(lua-eval-ast \"local t = {10, 20, 30} local a, b, c = unpack(t) return a + b + c\")")
(epoch 1341)
(eval "(lua-eval-ast \"local t = {10, 20, 30, 40} local a, b = table.unpack(t, 2, 3) return a + b\")")

;; ── Phase 6: io stub + print/tostring/tonumber ────────────────
(epoch 1400)
(eval "(lua-eval-ast \"io.write(\\\"hello\\\") return io.__buffer()\")")
(epoch 1401)
(eval "(lua-eval-ast \"io.write(\\\"a\\\", \\\"b\\\", \\\"c\\\") return io.__buffer()\")")
(epoch 1402)
(eval "(lua-eval-ast \"io.write(1, \\\" \\\", 2) return io.__buffer()\")")
(epoch 1410)
(eval "(lua-eval-ast \"print(\\\"x\\\", \\\"y\\\") return io.__buffer()\")")
(epoch 1411)
(eval "(lua-eval-ast \"print(1, 2, 3) return io.__buffer()\")")
(epoch 1420)
(eval "(lua-eval-ast \"return tostring(42)\")")
(epoch 1421)
(eval "(lua-eval-ast \"return tostring(nil)\")")
(epoch 1422)
(eval "(lua-eval-ast \"return tostring({})\")")
(epoch 1430)
(eval "(lua-eval-ast \"return tonumber(\\\"42\\\")\")")
(epoch 1431)
(eval "(lua-eval-ast \"if tonumber(\\\"abc\\\") == nil then return 1 else return 0 end\")")
(epoch 1440)
(eval "(lua-eval-ast \"if io.read() == nil then return 1 else return 0 end\")")
(epoch 1441)
(eval "(lua-eval-ast \"if io.open(\\\"x\\\") == nil then return 1 else return 0 end\")")

;; ── Phase 6: os stub ──────────────────────────────────────────
(epoch 1500)
(eval "(lua-eval-ast \"return type(os.time())\")")
(epoch 1501)
(eval "(lua-eval-ast \"local t1 = os.time() local t2 = os.time() return t2 > t1\")")
(epoch 1502)
(eval "(lua-eval-ast \"return os.difftime(100, 60)\")")
(epoch 1503)
(eval "(lua-eval-ast \"return type(os.clock())\")")
(epoch 1504)
(eval "(lua-eval-ast \"return type(os.date())\")")
(epoch 1505)
(eval "(lua-eval-ast \"local d = os.date(\\\"*t\\\") return d.year\")")
(epoch 1506)
(eval "(lua-eval-ast \"if os.getenv(\\\"HOME\\\") == nil then return 1 else return 0 end\")")
(epoch 1507)
(eval "(lua-eval-ast \"return type(os.tmpname())\")")

;; ── Phase 7: require / package ────────────────────────────────
(epoch 1600)
(eval "(lua-eval-ast \"package.preload.mymath = function() local m = {} m.add = function(a, b) return a + b end return m end local mm = require(\\\"mymath\\\") return mm.add(3, 4)\")")
(epoch 1601)
(eval "(lua-eval-ast \"package.preload.counter = function() local n = 0 local m = {} m.inc = function() n = n + 1 return n end return m end local c1 = require(\\\"counter\\\") local c2 = require(\\\"counter\\\") c1.inc() c1.inc() return c2.inc()\")")
(epoch 1602)
(eval "(lua-eval-ast \"local ok, err = pcall(require, \\\"nope\\\") if ok then return 0 else return 1 end\")")
(epoch 1603)
(eval "(lua-eval-ast \"package.preload.x = function() return {val = 42} end require(\\\"x\\\") return package.loaded.x.val\")")
(epoch 1604)
(eval "(lua-eval-ast \"package.preload.noret = function() end local r = require(\\\"noret\\\") return type(r)\")")

;; ── Phase 7: vararg `...` (scoreboard iteration) ──────────────
(epoch 1700)
(eval "(lua-eval-ast \"local function f(...) return ... end local a, b, c = f(1, 2, 3) return a + b + c\")")
(epoch 1701)
(eval "(lua-eval-ast \"local function f(...) local t = {...} return t[2] end return f(10, 20, 30)\")")
(epoch 1702)
(eval "(lua-eval-ast \"local function f(a, ...) return a + select(\\\"#\\\", ...) end return f(10, 1, 2, 3)\")")
(epoch 1703)
(eval "(lua-eval-ast \"local function f(...) return select(2, ...) end local a, b = f(10, 20, 30) return a + b\")")
(epoch 1704)
(eval "(lua-eval-ast \"local function sum(...) local s = 0 for _, v in ipairs({...}) do s = s + v end return s end return sum(1, 2, 3, 4, 5)\")")
(epoch 1705)
(eval "(lua-eval-ast \"local function f(a, b, ...) return a * 100 + b * 10 + select(\\\"#\\\", ...) end return f(7, 8, 1, 2, 3)\")")

;; ── Phase 7: do-block proper scoping ──────────────────────────
(epoch 1800)
(eval "(lua-eval-ast \"local i = 10 do local i = 100 end return i\")")
(epoch 1801)
(eval "(lua-eval-ast \"do local i = 10 do local i = 100 assert(i == 100) end assert(i == 10) end return \\\"ok\\\"\")")

;; ── if/else/elseif body scoping ──────────────────────────────
(epoch 1810)
(eval "(lua-eval-ast \"local x = 10 if true then local x = 99 end return x\")")
(epoch 1811)
(eval "(lua-eval-ast \"local x = 10 if false then else local x = 99 end return x\")")
(epoch 1812)
(eval "(lua-eval-ast \"local x = 10 if false then elseif true then local x = 99 end return x\")")

;; ── Lua 5.0-style `arg` table in vararg functions ─────────────
(epoch 1820)
(eval "(lua-eval-ast \"function f(a, ...) return arg.n end return f({}, 1, 2, 3)\")")
(epoch 1821)
(eval "(lua-eval-ast \"function f(a, ...) return arg[1] + arg[2] + arg[3] end return f({}, 10, 20, 30)\")")

;; ── Decimal-escape strings ────────────────────────────────────
(epoch 1830)
(eval "(lua-eval-ast \"return \\\"\\\\65\\\"\")")
(epoch 1831)
(eval "(lua-eval-ast \"if \\\"\\\\09912\\\" == \\\"c12\\\" then return 1 else return 0 end\")")

;; ── Unary-minus / ^ precedence (Lua spec: ^ tighter than -) ──
(epoch 1840)
(eval "(lua-eval-ast \"return -2^2\")")
(epoch 1841)
(eval "(lua-eval-ast \"return 2^3^2\")")
(epoch 1842)
(eval "(lua-eval-ast \"if -2^2 == -4 then return 1 else return 0 end\")")

;; ── Early-return inside nested block (guard+raise sentinel) ──
(epoch 1850)
(eval "(lua-eval-ast \"local function f(n) if n < 0 then return -1 end return n * 2 end return f(-5)\")")
(epoch 1851)
(eval "(lua-eval-ast \"local function f(n) if n < 0 then return -1 end return n * 2 end return f(7)\")")
(epoch 1852)
(eval "(lua-eval-ast \"function f(i) if type(i) ~= \\\"number\\\" then return i, \\\"jojo\\\" end if i > 0 then return i, f(i-1) end end local a, b = f(3) return a\")")

;; ── break via sentinel (escapes while/for-num/for-in/repeat) ──
(epoch 1860)
(eval "(lua-eval-ast \"local i = 0 while true do i = i + 1 if i >= 5 then break end end return i\")")
(epoch 1861)
(eval "(lua-eval-ast \"local s = 0 for i = 1, 100 do if i > 10 then break end s = s + i end return s\")")
(epoch 1862)
(eval "(lua-eval-ast \"local t = {10, 20, 99, 40} local s = 0 for i, v in ipairs(t) do if v == 99 then break end s = s + v end return s\")")
(epoch 1863)
(eval "(lua-eval-ast \"local i = 0 repeat i = i + 1 if i >= 3 then break end until false return i\")")

;; ── Method-call chaining (obj evaluated once) ────────────────
(epoch 1870)
(eval "(lua-eval-ast \"local a = {x=0} function a:add(x) self.x = self.x+x return self end return a:add(10):add(20):add(30).x\")")

;; ── Parenthesized expression truncates multi-return ──────────
(epoch 1880)
(eval "(lua-eval-ast \"local function f() return 1, 2, 3 end local a, b, c = (f()) return (a == 1 and b == nil and c == nil) and 1 or 0\")")
(epoch 1881)
(eval "(lua-eval-ast \"local function f() return 10, 20 end return (f()) + 1\")")

;; ── Lua patterns in match/gmatch/gsub ────────────────────────
(epoch 1890)
(eval "(lua-eval-ast \"return string.match(\\\"hello123world\\\", \\\"%d+\\\")\")")
(epoch 1891)
(eval "(lua-eval-ast \"return string.match(\\\"name=bob\\\", \\\"%a+\\\")\")")
(epoch 1892)
(eval "(lua-eval-ast \"local c = 0 for w in string.gmatch(\\\"a b c d\\\", \\\"%a\\\") do c = c + 1 end return c\")")
(epoch 1893)
(eval "(lua-eval-ast \"local r, n = string.gsub(\\\"1 + 2 = 3\\\", \\\"%d\\\", \\\"X\\\") return r .. \\\":\\\" .. n\")")
(epoch 1894)
(eval "(lua-eval-ast \"if string.find(\\\"hello\\\", \\\"^hel\\\") then return 1 else return 0 end\")")
(epoch 1895)
(eval "(lua-eval-ast \"if string.find(\\\"hello\\\", \\\"^wor\\\") then return 0 else return 1 end\")")

;; ── tonumber with base (Lua 5.1) ──────────────────────────────
(epoch 1900)
(eval "(lua-eval-ast \"return tonumber('1010', 2)\")")
(epoch 1901)
(eval "(lua-eval-ast \"return tonumber('FF', 16)\")")
(epoch 1902)
(eval "(lua-eval-ast \"if tonumber('99', 8) == nil then return 1 else return 0 end\")")

;; ── Pattern character sets [...] ──────────────────────────────
(epoch 1910)
(eval "(lua-eval-ast \"return string.match(\\\"hello123\\\", \\\"[a-z]+\\\")\")")
(epoch 1911)
(eval "(lua-eval-ast \"return string.match(\\\"hello123\\\", \\\"[0-9]+\\\")\")")
(epoch 1912)
(eval "(lua-eval-ast \"return string.match(\\\"abc\\\", \\\"[^a]+\\\")\")")

;; ── string.format width/precision/hex/octal/char ─────────────
(epoch 1920)
(eval "(lua-eval-ast \"return string.format('%5d', 42)\")")
(epoch 1921)
(eval "(lua-eval-ast \"return string.format('%05d', 42)\")")
(epoch 1922)
(eval "(lua-eval-ast \"return string.format('%x', 255)\")")
(epoch 1923)
(eval "(lua-eval-ast \"return string.format('%X', 255)\")")
(epoch 1924)
(eval "(lua-eval-ast \"return string.format('%c', 65)\")")
(epoch 1925)
(eval "(lua-eval-ast \"return string.format('%.3s', 'hello')\")")

;; ── New-style vararg: arg is nil when ... used in body ──────
(epoch 1930)
(eval "(lua-eval-ast \"function f(...) local x = {...} return arg == nil and 1 or 0 end return f(1,2,3)\")")
(epoch 1931)
(eval "(lua-eval-ast \"function f(...) return select('#', ...) end return f(10,20,30)\")")

EPOCHS

OUTPUT=$(timeout 60 "$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 "  ok $desc"
  else
    FAIL=$((FAIL + 1))
    ERRORS+="  FAIL $desc (epoch $epoch)
    expected: $expected
    actual:   $actual
"
  fi
}

# ── Empty / EOF ────────────────────────────────────────────────
check 100 "empty tokens length"            '1'
check 101 "empty first token is eof"       '"eof"'

# ── Numbers ────────────────────────────────────────────────────
check 110 "int token type"                 '"number"'
check 111 "int value"                      '42'
check 112 "float value"                    '3.14'
check 113 "hex value"                      '255'
check 114 "exponent"                       '1000'
check 115 "neg exponent"                   '0.015'
check 116 "leading-dot"                    '0.5'

# ── Identifiers / keywords ─────────────────────────────────────
check 120 "ident type"                     '"ident"'
check 121 "ident value"                    '"foo"'
check 122 "underscore ident"               '"_bar1"'
check 123 "local is keyword"               '"keyword"'
check 124 "function keyword"               '"function"'
check 125 "nil is keyword"                 '"keyword"'
check 126 "true value"                     '"true"'
check 127 "false type"                     '"keyword"'

# ── Strings ────────────────────────────────────────────────────
check 130 "string type"                    '"string"'
check 131 "string value"                   '"hi"'
check 132 "single-quote string"            '"ab"'
check 133 "escape sequence"                '"a'
check 140 "long string type"               '"string"'
check 141 "long string value"              '"hello"'
check 142 "level-2 long string"            '"level 2"'

# ── Operators ──────────────────────────────────────────────────
check 150 "=="                             '"=="'
check 151 "~="                             '"~="'
check 152 "<="                             '"<="'
check 153 ">="                             '">="'
check 154 ".."                             '".."'
check 155 "..."                            '"..."'
check 156 "::"                             '"::"'

check 160 "+"                              '"+"'
check 161 "-"                              '"-"'
check 162 "*"                              '"*"'
check 163 "/"                              '"/"'
check 164 "%"                              '"%"'
check 165 "^"                              '"^"'
check 166 "#"                              '"#"'
check 167 "("                              '"("'
check 168 "{"                              '"{"'
check 169 ";"                              '";"'

# ── Comments ───────────────────────────────────────────────────
check 170 "line comment only → eof"        '1'
check 171 "line comment + num"             '2'
check 172 "num after line comment"         '42'
check 173 "block comment → eof"            '2'
check 174 "num after block comment"        '42'
check 175 "num after level-2 block comment" '7'

# ── Compound ───────────────────────────────────────────────────
check 180 "local x = 1 token count"        '5'
check 181 "local is keyword"               '"keyword"'
check 182 "local value"                    '"local"'
check 183 "x is ident"                     '"ident"'
check 184 "= value"                        '"="'
check 185 "1 value"                        '1'

check 190 "a.b:c() token count"            '8'
check 191 "dot after ident"                '"."'
check 192 "colon after ident"              '":"'

# ── Phase 1.parse: parser ────────────────────────────────────
check 200 "parse int"                       '(lua-num 42)'
check 201 "parse float"                     '(lua-num 3.14)'
check 202 "parse string"                    '(lua-str "hi")'
check 203 "parse true"                      '(lua-true)'
check 204 "parse false"                     '(lua-false)'
check 205 "parse nil"                       '(lua-nil)'
check 206 "parse ident"                     '(lua-name "foo")'
check 207 "parse vararg"                    '(lua-vararg)'

check 210 "parse 1+2"                       '(lua-binop "+" (lua-num 1) (lua-num 2))'
check 211 "parse a+b*c prec"                '(lua-binop "+" (lua-name "a") (lua-binop "*"'
check 212 "parse a*b+c prec"                '(lua-binop "+" (lua-binop "*"'
check 213 "parse and/or prec"               '(lua-binop "or" (lua-binop "and"'
check 214 "parse =="                        '(lua-binop "==" (lua-name "a") (lua-name "b"))'
check 215 "parse .. right-assoc"            '(lua-binop ".." (lua-name "a") (lua-binop ".."'
check 216 "parse ^ right-assoc"             '(lua-binop "^" (lua-name "a") (lua-binop "^"'
check 217 "parse paren override"            '(lua-binop "*" (lua-paren (lua-binop "+"'

check 220 "parse -x"                        '(lua-unop "-" (lua-name "x"))'
check 221 "parse not x"                     '(lua-unop "not" (lua-name "x"))'
check 222 "parse #a"                        '(lua-unop "#" (lua-name "a"))'

check 230 "parse a.b"                       '(lua-field (lua-name "a") "b")'
check 231 "parse a.b.c"                     '(lua-field (lua-field (lua-name "a") "b") "c")'
check 232 "parse a[0]"                      '(lua-index (lua-name "a") (lua-num 0))'
check 233 "parse f()"                       '(lua-call (lua-name "f") ())'
check 234 "parse f(1,2)"                    '(lua-call (lua-name "f") ((lua-num 1) (lua-num 2)))'
check 235 "parse a:b()"                     '(lua-method-call (lua-name "a") "b" ())'

check 240 "parse {1,2,3}"                   '(lua-table (lua-pos (lua-num 1)) (lua-pos (lua-num 2))'
check 241 "parse {x=1,y=2}"                 '(lua-table (lua-kv (lua-str "x") (lua-num 1))'
check 242 "parse {[k]=v}"                   '(lua-table (lua-kv (lua-binop "+"'
check 243 "parse empty table"               '(lua-table)'

check 250 "parse function() 1 end"          '(lua-function () false'
check 251 "parse function(a,b)"             '(lua-function ("a" "b") false'
check 252 "parse function(...)"             '(lua-function () true'

check 260 "parse local x = 1"               '(lua-block (lua-local ("x") ((lua-num 1))))'
check 261 "parse local a,b = 1,2"           '(lua-block (lua-local ("a" "b") ((lua-num 1) (lua-num 2))))'
check 262 "parse x = 1"                     '(lua-assign ((lua-name "x")) ((lua-num 1)))'
check 263 "parse a,b = 1,2"                 '(lua-assign ((lua-name "a") (lua-name "b"))'
check 264 "parse if then end"               '(lua-if (lua-name "x")'
check 265 "parse if-else"                   '(lua-if (lua-name "x") (lua-block (lua-assign ((lua-name "y")) ((lua-num 1)))) () (lua-block'
check 266 "parse if-elseif-else"            '(((lua-name "z") (lua-block (lua-assign ((lua-name "y")) ((lua-num 2))))))'
check 267 "parse while"                     '(lua-while (lua-binop "<"'
check 268 "parse repeat"                    '(lua-repeat'
check 269 "parse for num"                   '(lua-for-num "i" (lua-num 1) (lua-num 10) nil'
check 270 "parse for num step"              '(lua-for-num "i" (lua-num 1) (lua-num 10) (lua-num 2)'
check 271 "parse do block"                  '(lua-do (lua-block (lua-local ("x") ((lua-num 1))))'
check 272 "parse break"                     '(lua-break)'
check 273 "parse return"                    '(lua-return ((lua-num 42)))'
check 274 "parse return 1,2"                '(lua-return ((lua-num 1) (lua-num 2)))'
check 275 "parse bare return"               '(lua-return ())'

check 280 "parse function decl"             '(lua-function-decl (lua-name "f")'
check 281 "parse local function"            '(lua-local-function "f" (lua-function ("x") false'
check 282 "parse function t.m"              '(lua-function-decl (lua-field (lua-name "t") "m")'
check 283 "parse method t:m"                'self'

check 290 "parse call stmt"                 '(lua-call-stmt (lua-call (lua-name "print")'
check 291 "parse method call stmt"          '(lua-call-stmt (lua-method-call'
check 292 "parse chained call stmt"         '(lua-call-stmt (lua-call (lua-field'

check 300 "parse multi-statement"           '4'

# ── Phase 2: transpile + eval ────────────────────────────────
check 400 "eval return 1"                   '1'
check 401 "eval return true"                'true'
check 402 "eval return false"               'false'
check 403 "eval return nil"                 'nil'
check 404 "eval return string"              '"hi"'

check 410 "eval 1+2"                        '3'
check 411 "eval 10-3"                       '7'
check 412 "eval 4*5"                        '20'
check 413 "eval 10/4"                       '2.5'
check 414 "eval 10%3"                       '1'
check 415 "eval 2^10"                       '1024'
check 416 "eval (1+2)*3"                    '9'
check 417 "eval 1+2*3 prec"                 '7'
check 418 "eval -5+10"                      '5'

check 420 "eval \"a\"..\"b\""               '"ab"'
check 421 "eval str..num"                   '"count: 42"'

check 430 "eval 1<2"                        'true'
check 431 "eval 3>2"                        'true'
check 432 "eval 2==2"                       'true'
check 433 "eval 1~=2"                       'true'
check 434 "eval 1<=1"                       'true'
check 435 "eval 3>=2"                       'true'

check 440 "eval true and 42"                '42'
check 441 "eval false or 99"                '99'
check 442 "eval nil or 7"                   '7'
check 443 "eval 1 and 2"                    '2'
check 444 "eval false and 999"              'false'
check 445 "eval not true"                   'false'
check 446 "eval not nil"                    'true'
check 447 "eval not 0"                      'false'

check 450 "truthy 0 (Lua truthy!)"          'true'
check 451 "truthy nil"                      'false'
check 452 "truthy false"                    'false'
check 453 "truthy empty string"             'true'

check 460 "if true then 1 else 2"           '1'
check 461 "if 1>2 then 100 else 200"        '200'
check 462 "if-elseif-else branching"        '10'

check 470 "local x=5; x*2"                  '10'
check 471 "mutate x"                        '2'
check 472 "local a,b = 1,2; a+b"            '3'

check 480 "for 1..5 sum"                    '15'
check 481 "for 10..1 step -1 count"         '10'
check 482 "while i<5 count"                 '5'
check 483 "repeat until i>=3"               '3'
check 484 "for 1..100 sum"                  '5050'

# ── Phase 3: functions + closures ─────────────────────────────
check 500 "anon fn call"                    '6'
check 501 "anon fn no args"                 '42'
check 502 "iife"                            '12'

check 510 "local function double"           '14'
check 511 "local function sum3"             '6'
check 512 "local function greet"            '"hi"'

check 520 "top-level function decl"         '7'
check 521 "top-level id string"             '"abc"'

check 530 "closure reads outer"             '10'
check 531 "closure factory add5(10)"        '15'
check 532 "closure with mutable counter"    '3'
check 533 "closure sees later mutation"     '12'

check 540 "recursive local fact(5)"         '120'
check 541 "recursive top-level fib(10)"     '55'

check 550 "apply(sq,4)"                     '16'
check 551 "twice(+1, 5)"                    '7'

check 560 "max with if"                     '7'
check 561 "sum_to(10) with for"             '55'

# ── Phase 3: multi-return + unpack ────────────────────────────
check 570 "anon-fn returns 2, unpack"       '3'
check 571 "local fn returns 2, unpack"      '10'
check 572 "swap via multi-return"           '21'
check 573 "extra returns discarded"         '12'
check 574 "missing returns nil-padded"      '3'
check 575 "tail-return passthrough"         '12'
check 576 "non-last call truncated to 1st"  '95'
check 577 "single-assign truncates to 1st"  '5'
check 578 "empty return → all nil"          '99'
check 579 "multi-assign (non-local)"        '18'

# ── Phase 3: table constructors ────────────────────────────────
check 600 "array t[1]"                      '10'
check 601 "array t[3]"                      '30'
check 602 "array out-of-range is nil"       '1'
check 610 "hash t.x+t.y"                    '3'
check 611 "hash name lookup"                '"bob"'
check 612 "hash age lookup"                 '30'
check 620 "computed [k]=v"                  '42'
check 621 "computed [1+1]"                  '"two"'
check 622 "computed [concat]"               '99'
check 623 "boolean key [true]"              '"yes"'
check 630 "mixed array part"                '2'
check 631 "mixed hash part"                 '"v"'
check 640 "trailing comma"                  '3'
check 641 "semicolon separators"            '2'
check 642 "mixed separators"                '4'
check 650 "nested array"                    '3'
check 651 "nested hash"                     '2'
check 660 "dynamic pos values"              '14'
check 661 "function calls as values"        '4'
check 670 "function-valued field + call"    '10'

# ── Phase 3: raw table access ─────────────────────────────────
check 700 "t[1]=v then read"                '"a"'
check 701 "t[1]+t[2] after writes"          '30'
check 710 "t.x=100 persists"                '100'
check 711 "t.name=... persists"             '"alice"'
check 712 "overwrite existing field"        '99'
check 720 "missing field is nil"            '99'
check 721 "missing index is nil"            '1'
check 730 "#t on 5-element array"           '5'
check 731 "#t on empty"                     '0'
check 732 "#t after inserts"                '2'
check 733 "#\"hello\""                      '5'
check 740 "t.count mutate chain"            '2'
check 741 "fill via for-num then read"      '9'
check 742 "#t after 10 inserts"             '10'
check 750 "chained t.a.b.c read"            '42'
check 751 "chained t.a.x write"             '7'
check 760 "t[k]=v reads via t.foo"          '88'
check 761 "t[concat]=v reads via t.xy"      '7'
check 770 "reference semantics t=s"         '42'

# ── Phase 4: metatables ───────────────────────────────────────
check 800 "setmetatable returns t"          '1'
check 801 "getmetatable roundtrip"          '1'
check 810 "type(1)"                         '"number"'
check 811 "type(string)"                    '"string"'
check 812 "type({})"                        '"table"'
check 813 "type(nil)"                       '"nil"'
check 814 "type(true)"                      '"boolean"'
check 815 "type(function)"                  '"function"'
check 820 "__index table lookup"            '10'
check 821 "__index chain + self"            '30'
check 822 "__index as function"             '"hi!"'
check 830 "__newindex fires"                '"foo"'
check 840 "__add table+number"              '99'
check 841 "__add two tables"                '7'
check 842 "__sub + __mul chain"             '18'
check 843 "__unm"                           '42'
check 844 "__concat"                        '"cat"'
check 850 "__eq"                            '1'
check 851 "__lt"                            '1'
check 852 "__le"                            '1'
check 860 "__call"                          '105'
check 870 "__len"                           '99'
check 880 "OO pattern self:m()"             '"Rex"'

# ── Phase 4: pcall / xpcall / error ───────────────────────────
check 900 "pcall catches error(msg)"        '"boom"'
check 901 "pcall ok path single val"        '42'
check 902 "pcall ok path multi val"         '3'
check 903 "pcall with args, ok"             '5'
check 904 "pcall with args, err"            '"div by zero"'
check 905 "error(table) preserved"          '42'
check 906 "nested pcall"                    '"outer:inner"'
check 910 "xpcall invokes handler"          '"H:raw"'
check 911 "xpcall ok path"                  '99'

# ── Phase 4: generic `for … in …` ─────────────────────────────
check 950 "ipairs sum"                      '60'
check 951 "ipairs last index"               '3'
check 952 "ipairs empty → 0"                '0'
check 953 "ipairs stops at nil"             '2'
check 960 "pairs hash sum"                  '6'
check 961 "pairs hash count"                '3'
check 970 "stateful closure iter"           '15'
check 971 "3-value iterator form"           '30'
check 980 "pairs skips __meta"              '2'

# ── Phase 5: coroutines ────────────────────────────────────────
check 1000 "coroutine.status initial"       '"suspended"'
check 1001 "coroutine.status after done"    '"dead"'
check 1010 "yield/resume × 3 sequence"      '123'
check 1011 "resume passes args to body"     '30'
check 1012 "resume passes args via yield"   '142'
check 1020 "resume dead returns error"      '"cannot resume dead coroutine"'
check 1030 "coroutine.wrap"                 '6'
check 1040 "iterator via coroutine"         '60'

# ── Phase 6: string library ───────────────────────────────────
check 1100 "string.len"                     '5'
check 1101 "string.upper"                   '"HI"'
check 1102 "string.lower"                   '"hi"'
check 1103 "string.rep"                     '"ababab"'
check 1110 "string.sub(s,i,j)"              '"ell"'
check 1111 "string.sub(s,-3)"               '"llo"'
check 1112 "string.sub(s,1,-2)"             '"hell"'
check 1120 "string.byte"                    '65'
check 1121 "string.byte(s,i)"               '66'
check 1130 "string.char(72,105)"            '"Hi"'
check 1131 "string.char(97,98,99)"          '"abc"'
check 1140 "string.find literal hit"        '709'
check 1141 "string.find literal miss"       '1'
check 1150 "string.match literal"           '"ell"'
check 1160 "string.gsub replace all"        '"XbcXbc:2"'
check 1161 "string.gsub with limit"         '"bbaa:2"'
check 1170 "string.gmatch iterator"         '3'
check 1180 "string.format %s=%d"            '"x=42"'
check 1181 "string.format %d%%"             '"50%"'

# ── Phase 6: math library ─────────────────────────────────────
check 1200 "math.pi in range"               'true'
check 1201 "math.huge big"                  'true'
check 1210 "math.abs(-7)"                   '7'
check 1211 "math.sqrt(16)"                  '4'
check 1212 "math.floor(3.7)"                '3'
check 1213 "math.ceil(3.2)"                 '4'
check 1220 "math.max(3,7,1,4)"              '7'
check 1221 "math.min(3,7,1,4)"              '1'
check 1230 "math.pow(2,8)"                  '256'
check 1231 "math.exp(0)"                    '1'
check 1232 "math.log(1)"                    '0'
check 1233 "math.log10(100)"                '2'
check 1240 "math.sin(0)+math.cos(0)"        '1'
check 1250 "math.fmod(10,3)"                '1'
check 1251 "math.modf(3.5) int part"        '3'
check 1260 "math.random(n) in range"        'true'
check 1261 "math.random(m,n) in range"      'true'

# ── Phase 6: table library ────────────────────────────────────
check 1300 "table.insert append → #t"       '4'
check 1301 "table.insert value"             '4'
check 1302 "table.insert(t,pos,v) mid"      '20'
check 1303 "table.insert(t,1,v) prepend"    '510'
check 1310 "table.remove() last"            '33'
check 1311 "table.remove(t,1) shift"        '230'
check 1320 "table.concat no sep"            '"abc"'
check 1321 "table.concat with sep"          '"a-b-c"'
check 1322 "table.concat range"             '"2,3"'
check 1330 "table.sort asc"                 '109'
check 1331 "table.sort desc via cmp"        '301'
check 1340 "unpack global"                  '60'
check 1341 "table.unpack(t,i,j)"            '50'

# ── Phase 6: io stub + print/tostring/tonumber ────────────────
check 1400 "io.write single"                '"hello"'
check 1401 "io.write multi strings"         '"abc"'
check 1402 "io.write numbers + spaces"      '"1 2"'
check 1410 "print two args tab-sep + NL"    '"x\ty\n"'
check 1411 "print three ints"               '"1\t2\t3\n"'
check 1420 "tostring(42)"                   '"42"'
check 1421 "tostring(nil)"                  '"nil"'
check 1422 "tostring({})"                   '"table"'
check 1430 "tonumber(\"42\")"               '42'
check 1431 "tonumber(\"abc\") → nil"        '1'
check 1440 "io.read() → nil"                '1'
check 1441 "io.open(x) → nil"               '1'

# ── Phase 6: os stub ──────────────────────────────────────────
check 1500 "os.time → number"               '"number"'
check 1501 "os.time monotonic"              'true'
check 1502 "os.difftime"                    '40'
check 1503 "os.clock → number"              '"number"'
check 1504 "os.date() default"              '"string"'
check 1505 "os.date(*t).year"               '1970'
check 1506 "os.getenv → nil"                '1'
check 1507 "os.tmpname → string"            '"string"'

# ── Phase 7: require / package ────────────────────────────────
check 1600 "require(preload) + call"        '7'
check 1601 "require returns cached"         '3'
check 1602 "require unknown module errors"  '1'
check 1603 "package.loaded populated"       '42'
check 1604 "nil return caches as true"      '"boolean"'

# ── Phase 7: vararg `...` (scoreboard iteration) ──────────────
check 1700 "f(...) return ... unpack"       '6'
check 1701 "{...} table from vararg"        '20'
check 1702 "f(a, ...) + select(#,...)"      '13'
check 1703 "select(2, ...) unpack"          '50'
check 1704 "sum via ipairs({...})"          '15'
check 1705 "f(a, b, ...) mixed"             '783'

# ── Phase 7: do-block proper scoping ──────────────────────────
check 1800 "inner do local shadows"         '10'
check 1801 "nested do scopes"               '"ok"'

# ── if/else/elseif body scoping ──────────────────────────────
check 1810 "if then local shadows"          '10'
check 1811 "else local shadows"             '10'
check 1812 "elseif local shadows"           '10'

# ── Lua 5.0-style `arg` table in vararg functions ─────────────
check 1820 "arg.n in vararg fn"             '3'
check 1821 "arg[i] access"                  '60'

# ── Decimal-escape strings ───────────────────────────────────
check 1830 "\\65 → A"                        '"A"'
check 1831 "\\099 + 12 → c12"                '1'

# ── Unary-minus / ^ precedence (Lua: ^ tighter than unary -) ──
check 1840 "-2^2 = -4"                      '-4'
check 1841 "2^3^2 = 512 (right-assoc)"      '512'
check 1842 "-2^2 == -4 true"                '1'

# ── Early-return inside nested block ─────────────────────────
check 1850 "early return negative path"     '-1'
check 1851 "non-early return path"          '14'
check 1852 "nested early-return recursion"  '3'

# ── break via sentinel ───────────────────────────────────────
check 1860 "break in while"                 '5'
check 1861 "break in for-num"               '55'
check 1862 "break in for-in"                '30'
check 1863 "break in repeat"                '3'

# ── Method-call chaining ─────────────────────────────────────
check 1870 "a:add():add():add().x chain"    '60'

# ── Parenthesized truncates multi-return ─────────────────────
check 1880 "(f()) scalar coerce"            '1'
check 1881 "(f()) + 1 scalar"               '11'

# ── Lua patterns in match/gmatch/gsub ────────────────────────
check 1890 "match %d+"                      '"123"'
check 1891 "match %a+"                      '"name"'
check 1892 "gmatch %a count"                '4'
check 1893 "gsub %d → X"                    '"X + X = X:3"'
check 1894 "find ^ anchor hit"              '1'
check 1895 "find ^ anchor miss"             '1'

# ── tonumber with base ───────────────────────────────────────
check 1900 "tonumber('1010', 2)"            '10'
check 1901 "tonumber('FF', 16)"             '255'
check 1902 "tonumber('99', 8) → nil"        '1'

# ── Pattern character sets [...] ─────────────────────────────
check 1910 "[a-z]+ on hello123"             '"hello"'
check 1911 "[0-9]+ on hello123"             '"123"'
check 1912 "[^a]+ on abc"                   '"bc"'

# ── string.format width/precision/hex/octal/char ────────────
check 1920 "%5d width"                      '"   42"'
check 1921 "%05d zero-pad"                  '"00042"'
check 1922 "%x hex"                         '"ff"'
check 1923 "%X HEX"                         '"FF"'
check 1924 "%c char"                        '"A"'
check 1925 "%.3s precision"                 '"hel"'

# ── New-style vararg: arg is nil when ... used in body ───────
check 1930 "new-style vararg arg==nil"      '1'
check 1931 "select # vararg count"          '3'

TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
  echo "ok $PASS/$TOTAL Lua-on-SX tests passed"
else
  echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
  echo ""
  echo "$ERRORS"
fi

[ $FAIL -eq 0 ]
