Files
rose-ash/lib/lua/test.sh
giles 418a0dc120
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
lua: raw table access — mutating set!, len via has-key?, +19 tests
2026-04-24 17:23:39 +00:00

912 lines
34 KiB
Bash
Executable File

#!/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/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\")")
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: 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'
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 ]