#!/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 1)
(load "lib/guest/lex.sx")
(load "lib/guest/prefix.sx")
(load "lib/guest/pratt.sx")
(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\")")

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-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: stdlib — math, string, table ──────────────────────────────────

cat >> "$TMPFILE" << 'EPOCHS2'

;; ── math library ───────────────────────────────────────────────
(epoch 500)
(eval "(lua-eval-ast \"return math.abs(-7)\")")
(epoch 501)
(eval "(lua-eval-ast \"return math.floor(3.9)\")")
(epoch 502)
(eval "(lua-eval-ast \"return math.ceil(3.1)\")")
(epoch 503)
(eval "(lua-eval-ast \"return math.sqrt(9)\")")
(epoch 504)
(eval "(lua-eval-ast \"return math.sin(0)\")")
(epoch 505)
(eval "(lua-eval-ast \"return math.cos(0)\")")
(epoch 506)
(eval "(lua-eval-ast \"return math.max(3, 7)\")")
(epoch 507)
(eval "(lua-eval-ast \"return math.min(3, 7)\")")
(epoch 508)
(eval "(lua-eval-ast \"return math.pi > 3\")")
(epoch 509)
(eval "(lua-eval-ast \"return math.huge > 0\")")

;; ── string library ─────────────────────────────────────────────
(epoch 520)
(eval "(lua-eval-ast \"return string.len(\\\"hello\\\")\")")
(epoch 521)
(eval "(lua-eval-ast \"return string.upper(\\\"hello\\\")\")")
(epoch 522)
(eval "(lua-eval-ast \"return string.lower(\\\"WORLD\\\")\")")
(epoch 523)
(eval "(lua-eval-ast \"return string.sub(\\\"hello\\\", 2, 4)\")")
(epoch 524)
(eval "(lua-eval-ast \"return string.rep(\\\"ab\\\", 3)\")")
(epoch 525)
(eval "(lua-eval-ast \"return string.reverse(\\\"hello\\\")\")")
(epoch 526)
(eval "(lua-eval-ast \"return string.byte(\\\"A\\\")\")")
(epoch 527)
(eval "(lua-eval-ast \"return string.char(72, 105)\")")
(epoch 528)
(eval "(lua-eval-ast \"return string.find(\\\"hello\\\", \\\"ll\\\")\")")
(epoch 529)
(eval "(lua-eval-ast \"return string.match(\\\"hello\\\", \\\"ell\\\")\")")
(epoch 530)
(eval "(lua-eval-ast \"return string.gsub(\\\"hello\\\", \\\"l\\\", \\\"r\\\")\")")

;; ── table library ──────────────────────────────────────────────
(epoch 540)
(eval "(lua-eval-ast \"local t = {10, 20, 30} t = table.insert(t, 40) return t[4]\")")
(epoch 541)
(eval "(lua-eval-ast \"local t = {10, 20, 30} t = table.remove(t) return t[3]\")")
(epoch 542)
(eval "(lua-eval-ast \"local t = {\\\"a\\\", \\\"b\\\", \\\"c\\\"} return table.concat(t, \\\",\\\")\")")
(epoch 543)
(eval "(lua-eval-ast \"local t = {3, 1, 2} t = table.sort(t) return t[1]\")")
(epoch 544)
(eval "(lua-eval-ast \"local t = {3, 1, 2} t = table.sort(t) return t[3]\")")

;; ── delay / force ──────────────────────────────────────────────
(epoch 550)
(eval "(lua-force (delay (+ 10 5)))")
(epoch 551)
(eval "(lua-force 42)")

EPOCHS2

OUTPUT2=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
OUTPUT="$OUTPUT
$OUTPUT2"

# math
check 500 "math.abs(-7)"         '7'
check 501 "math.floor(3.9)"      '3'
check 502 "math.ceil(3.1)"       '4'
check 503 "math.sqrt(9)"         '3'
check 504 "math.sin(0)"          '0'
check 505 "math.cos(0)"          '1'
check 506 "math.max(3,7)"        '7'
check 507 "math.min(3,7)"        '3'
check 508 "math.pi > 3"          'true'
check 509 "math.huge > 0"        'true'

# string
check 520 "string.len"           '5'
check 521 "string.upper"         '"HELLO"'
check 522 "string.lower"         '"world"'
check 523 "string.sub(2,4)"      '"ell"'
check 524 "string.rep(ab,3)"     '"ababab"'
check 525 "string.reverse"       '"olleh"'
check 526 "string.byte(A)"       '65'
check 527 "string.char(72,105)"  '"Hi"'
check 528 "string.find ll"       '3'
check 529 "string.match ell"     '"ell"'
check 530 "string.gsub l->r"     '"herro"'

# table
check 540 "table.insert"         '40'
check 541 "table.remove"         'nil'
check 542 "table.concat ,"       '"a,b,c"'
check 543 "table.sort [1]"       '1'
check 544 "table.sort [3]"       '3'

# delay/force
check 550 "lua-force delay"      '15'
check 551 "lua-force non-promise" '42'

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 ]
