Files
rose-ash/lib/ocaml/test.sh
giles 4dca583ee3
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
ocaml: phase 2 evaluator slice (+42 tests, 165 total)
ocaml-eval walks the AST and yields SX values. ocaml-run / ocaml-run-program
wrap parse + eval. Coverage: atoms, vars, app (curried), 22 binary ops,
prefix - and not, if/seq/tuple/list, fun (auto-curried via host SX
lambdas), let, let-rec (mutable-cell knot for recursive functions).
Initial env: not/succ/pred/abs/max/min/fst/snd/ignore. Tests: arithmetic,
comparison, string concat, closures, fact 5 / fib 10 / sum 100,
top-level decls, |> pipe.
2026-05-08 07:57:20 +00:00

696 lines
24 KiB
Bash
Executable File

#!/usr/bin/env bash
# Fast OCaml-on-SX test runner — epoch protocol direct to sx_server.exe.
# Mirrors lib/lua/test.sh.
#
# Usage:
# bash lib/ocaml/test.sh # run all tests
# bash lib/ocaml/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
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/ocaml/tokenizer.sx")
(load "lib/ocaml/parser.sx")
(load "lib/ocaml/eval.sx")
(load "lib/ocaml/tests/tokenize.sx")
;; ── empty / eof ────────────────────────────────────────────────
(epoch 100)
(eval "(ocaml-test-tok-count \"\")")
(epoch 101)
(eval "(ocaml-test-tok-type \"\" 0)")
;; ── numbers ────────────────────────────────────────────────────
(epoch 110)
(eval "(ocaml-test-tok-type \"42\" 0)")
(epoch 111)
(eval "(ocaml-test-tok-value \"42\" 0)")
(epoch 112)
(eval "(ocaml-test-tok-value \"3.14\" 0)")
(epoch 113)
(eval "(ocaml-test-tok-value \"0xff\" 0)")
(epoch 114)
(eval "(ocaml-test-tok-value \"1e3\" 0)")
(epoch 115)
(eval "(ocaml-test-tok-value \"1_000_000\" 0)")
(epoch 116)
(eval "(ocaml-test-tok-value \"3.14e-2\" 0)")
;; ── identifiers / constructors / keywords ─────────────────────
(epoch 120)
(eval "(ocaml-test-tok-type \"foo\" 0)")
(epoch 121)
(eval "(ocaml-test-tok-value \"foo_bar1\" 0)")
(epoch 122)
(eval "(ocaml-test-tok-type \"Some\" 0)")
(epoch 123)
(eval "(ocaml-test-tok-value \"Some\" 0)")
(epoch 124)
(eval "(ocaml-test-tok-type \"let\" 0)")
(epoch 125)
(eval "(ocaml-test-tok-value \"match\" 0)")
(epoch 126)
(eval "(ocaml-test-tok-type \"true\" 0)")
(epoch 127)
(eval "(ocaml-test-tok-value \"false\" 0)")
(epoch 128)
(eval "(ocaml-test-tok-value \"name'\" 0)")
;; ── strings ────────────────────────────────────────────────────
(epoch 130)
(eval "(ocaml-test-tok-type \"\\\"hi\\\"\" 0)")
(epoch 131)
(eval "(ocaml-test-tok-value \"\\\"hi\\\"\" 0)")
(epoch 132)
(eval "(ocaml-test-tok-value \"\\\"a\\\\nb\\\"\" 0)")
;; ── chars ──────────────────────────────────────────────────────
(epoch 140)
(eval "(ocaml-test-tok-type \"'a'\" 0)")
(epoch 141)
(eval "(ocaml-test-tok-value \"'a'\" 0)")
(epoch 142)
(eval "(ocaml-test-tok-value \"'\\\\n'\" 0)")
;; ── type variables ─────────────────────────────────────────────
(epoch 145)
(eval "(ocaml-test-tok-type \"'a\" 0)")
(epoch 146)
(eval "(ocaml-test-tok-value \"'a\" 0)")
;; ── multi-char operators ───────────────────────────────────────
(epoch 150)
(eval "(ocaml-test-tok-value \"->\" 0)")
(epoch 151)
(eval "(ocaml-test-tok-value \"|>\" 0)")
(epoch 152)
(eval "(ocaml-test-tok-value \"<-\" 0)")
(epoch 153)
(eval "(ocaml-test-tok-value \":=\" 0)")
(epoch 154)
(eval "(ocaml-test-tok-value \"::\" 0)")
(epoch 155)
(eval "(ocaml-test-tok-value \";;\" 0)")
(epoch 156)
(eval "(ocaml-test-tok-value \"@@\" 0)")
(epoch 157)
(eval "(ocaml-test-tok-value \"<>\" 0)")
(epoch 158)
(eval "(ocaml-test-tok-value \"&&\" 0)")
(epoch 159)
(eval "(ocaml-test-tok-value \"||\" 0)")
;; ── single-char punctuation ────────────────────────────────────
(epoch 160)
(eval "(ocaml-test-tok-value \"+\" 0)")
(epoch 161)
(eval "(ocaml-test-tok-value \"|\" 0)")
(epoch 162)
(eval "(ocaml-test-tok-value \";\" 0)")
(epoch 163)
(eval "(ocaml-test-tok-value \"(\" 0)")
(epoch 164)
(eval "(ocaml-test-tok-value \"!\" 0)")
(epoch 165)
(eval "(ocaml-test-tok-value \"@\" 0)")
;; ── comments ───────────────────────────────────────────────────
(epoch 170)
(eval "(ocaml-test-tok-count \"(* hi *)\")")
(epoch 171)
(eval "(ocaml-test-tok-value \"(* c *) 42\" 0)")
(epoch 172)
(eval "(ocaml-test-tok-count \"(* outer (* inner *) end *) 1\")")
(epoch 173)
(eval "(ocaml-test-tok-value \"(* outer (* inner *) end *) 1\" 0)")
;; ── compound expressions ───────────────────────────────────────
(epoch 180)
(eval "(ocaml-test-tok-count \"let x = 1\")")
(epoch 181)
(eval "(ocaml-test-tok-type \"let x = 1\" 0)")
(epoch 182)
(eval "(ocaml-test-tok-value \"let x = 1\" 0)")
(epoch 183)
(eval "(ocaml-test-tok-type \"let x = 1\" 1)")
(epoch 184)
(eval "(ocaml-test-tok-value \"let x = 1\" 2)")
(epoch 185)
(eval "(ocaml-test-tok-value \"let x = 1\" 3)")
(epoch 190)
(eval "(ocaml-test-tok-count \"match x with | None -> 0 | Some y -> y\")")
(epoch 191)
(eval "(ocaml-test-tok-value \"fun x -> x + 1\" 2)")
(epoch 192)
(eval "(ocaml-test-tok-type \"fun x -> x + 1\" 2)")
(epoch 193)
(eval "(ocaml-test-tok-type \"Some 42\" 0)")
(epoch 194)
(eval "(ocaml-test-tok-value \"a |> f |> g\" 1)")
(epoch 195)
(eval "(ocaml-test-tok-value \"x := !y\" 1)")
;; ── Phase 1.parse: parser ──────────────────────────────────────
;; Atoms
(epoch 200)
(eval "(ocaml-parse \"42\")")
(epoch 201)
(eval "(ocaml-parse \"3.14\")")
(epoch 202)
(eval "(ocaml-parse \"\\\"hi\\\"\")")
(epoch 203)
(eval "(ocaml-parse \"'a'\")")
(epoch 204)
(eval "(ocaml-parse \"true\")")
(epoch 205)
(eval "(ocaml-parse \"false\")")
(epoch 206)
(eval "(ocaml-parse \"x\")")
(epoch 207)
(eval "(ocaml-parse \"Some\")")
(epoch 208)
(eval "(ocaml-parse \"()\")")
;; Application (left-assoc)
(epoch 210)
(eval "(ocaml-parse \"f x\")")
(epoch 211)
(eval "(ocaml-parse \"f x y\")")
(epoch 212)
(eval "(ocaml-parse \"f (g x)\")")
(epoch 213)
(eval "(ocaml-parse \"Some 42\")")
;; Binops with precedence
(epoch 220)
(eval "(ocaml-parse \"1 + 2\")")
(epoch 221)
(eval "(ocaml-parse \"a + b * c\")")
(epoch 222)
(eval "(ocaml-parse \"a * b + c\")")
(epoch 223)
(eval "(ocaml-parse \"a && b || c\")")
(epoch 224)
(eval "(ocaml-parse \"a = b\")")
(epoch 225)
(eval "(ocaml-parse \"a ^ b ^ c\")")
(epoch 226)
(eval "(ocaml-parse \"a :: b :: []\")")
(epoch 227)
(eval "(ocaml-parse \"(a + b) * c\")")
(epoch 228)
(eval "(ocaml-parse \"a |> f |> g\")")
(epoch 229)
(eval "(ocaml-parse \"x mod 2\")")
;; Prefix
(epoch 230)
(eval "(ocaml-parse \"-x\")")
(epoch 231)
(eval "(ocaml-parse \"-1 + 2\")")
;; Tuples & lists
(epoch 240)
(eval "(ocaml-parse \"(1, 2, 3)\")")
(epoch 241)
(eval "(ocaml-parse \"[1; 2; 3]\")")
(epoch 242)
(eval "(ocaml-parse \"[]\")")
;; if / fun / let / let rec
(epoch 250)
(eval "(ocaml-parse \"if x then 1 else 2\")")
(epoch 251)
(eval "(ocaml-parse \"if c then x\")")
(epoch 252)
(eval "(ocaml-parse \"fun x -> x + 1\")")
(epoch 253)
(eval "(ocaml-parse \"fun x y -> x + y\")")
(epoch 254)
(eval "(ocaml-parse \"let x = 1 in x\")")
(epoch 255)
(eval "(ocaml-parse \"let f x = x + 1 in f 2\")")
(epoch 256)
(eval "(ocaml-parse \"let rec f x = f x in f 1\")")
(epoch 257)
(eval "(ocaml-parse \"let f x y = x + y in f 1 2\")")
;; begin/end
(epoch 260)
(eval "(ocaml-parse \"begin 1 + 2 end\")")
;; ── Top-level decls ────────────────────────────────────────────
(epoch 270)
(eval "(ocaml-parse-program \"let x = 1\")")
(epoch 271)
(eval "(ocaml-parse-program \"let x = 1 ;;\")")
(epoch 272)
(eval "(ocaml-parse-program \"let f x = x + 1\")")
(epoch 273)
(eval "(ocaml-parse-program \"let rec fact n = if n = 0 then 1 else n * fact (n - 1)\")")
(epoch 274)
(eval "(ocaml-parse-program \"let x = 1 let y = 2\")")
(epoch 275)
(eval "(ocaml-parse-program \"1 + 2 ;;\")")
(epoch 276)
(eval "(ocaml-parse-program \"let x = 1 ;; let y = 2 ;; x + y\")")
(epoch 277)
(eval "(len (ocaml-parse-program \"let x = 1 ;; let y = 2 ;; x + y\"))")
(epoch 278)
(eval "(ocaml-parse-program \"\")")
;; ── Match / patterns ───────────────────────────────────────────
(epoch 300)
(eval "(ocaml-parse \"match x with | None -> 0 | Some y -> y\")")
(epoch 301)
(eval "(ocaml-parse \"match x with None -> 0 | Some y -> y\")")
(epoch 302)
(eval "(ocaml-parse \"match l with | [] -> 0 | h :: t -> 1\")")
(epoch 303)
(eval "(ocaml-parse \"match p with | (a, b) -> a + b\")")
(epoch 304)
(eval "(ocaml-parse \"match n with | 0 -> 1 | _ -> n\")")
(epoch 305)
(eval "(ocaml-parse \"match x with | true -> 1 | false -> 0\")")
(epoch 306)
(eval "(ocaml-parse \"match x with | Pair (a, b) -> a + b\")")
(epoch 307)
(eval "(ocaml-parse \"match x with | \\\"hi\\\" -> 1 | _ -> 0\")")
(epoch 308)
(eval "(ocaml-parse \"match x with | () -> 0\")")
;; ── Sequences (;) ──────────────────────────────────────────────
(epoch 320)
(eval "(ocaml-parse \"1; 2\")")
(epoch 321)
(eval "(ocaml-parse \"1; 2; 3\")")
(epoch 322)
(eval "(ocaml-parse \"(1; 2)\")")
(epoch 323)
(eval "(ocaml-parse \"begin a; b; c end\")")
(epoch 324)
(eval "(ocaml-parse \"let x = 1 in x; x\")")
(epoch 325)
(eval "(ocaml-parse \"if c then (a; b) else c\")")
(epoch 326)
(eval "(ocaml-parse \"[1; 2; 3]\")")
(epoch 327)
(eval "(ocaml-parse \"1; 2;\")")
(epoch 328)
(eval "(ocaml-parse \"begin a; end\")")
(epoch 329)
(eval "(ocaml-parse \"match x with | _ -> a; b\")")
;; ── Phase 2: evaluator ─────────────────────────────────────────
;; Atoms
(epoch 400)
(eval "(ocaml-run \"42\")")
(epoch 401)
(eval "(ocaml-run \"3.14\")")
(epoch 402)
(eval "(ocaml-run \"true\")")
(epoch 403)
(eval "(ocaml-run \"false\")")
(epoch 404)
(eval "(ocaml-run \"\\\"hi\\\"\")")
;; Arithmetic
(epoch 410)
(eval "(ocaml-run \"1 + 2\")")
(epoch 411)
(eval "(ocaml-run \"10 - 3\")")
(epoch 412)
(eval "(ocaml-run \"4 * 5\")")
(epoch 413)
(eval "(ocaml-run \"20 / 4\")")
(epoch 414)
(eval "(ocaml-run \"10 mod 3\")")
(epoch 415)
(eval "(ocaml-run \"2 ** 10\")")
(epoch 416)
(eval "(ocaml-run \"(1 + 2) * 3\")")
(epoch 417)
(eval "(ocaml-run \"1 + 2 * 3\")")
(epoch 418)
(eval "(ocaml-run \"-5 + 10\")")
;; Comparison & boolean
(epoch 420)
(eval "(ocaml-run \"1 < 2\")")
(epoch 421)
(eval "(ocaml-run \"3 > 2\")")
(epoch 422)
(eval "(ocaml-run \"2 = 2\")")
(epoch 423)
(eval "(ocaml-run \"1 <> 2\")")
(epoch 424)
(eval "(ocaml-run \"true && false\")")
(epoch 425)
(eval "(ocaml-run \"true || false\")")
(epoch 426)
(eval "(ocaml-run \"not false\")")
;; String
(epoch 430)
(eval "(ocaml-run \"\\\"a\\\" ^ \\\"b\\\"\")")
(epoch 431)
(eval "(ocaml-run \"\\\"hello\\\" ^ \\\" \\\" ^ \\\"world\\\"\")")
;; Conditional
(epoch 440)
(eval "(ocaml-run \"if true then 1 else 2\")")
(epoch 441)
(eval "(ocaml-run \"if 1 > 2 then 100 else 200\")")
;; Let / lambda / app
(epoch 450)
(eval "(ocaml-run \"let x = 5 in x * 2\")")
(epoch 451)
(eval "(ocaml-run \"let f x = x + 1 in f 41\")")
(epoch 452)
(eval "(ocaml-run \"let f x y = x + y in f 3 4\")")
(epoch 453)
(eval "(ocaml-run \"(fun x -> x * x) 7\")")
(epoch 454)
(eval "(ocaml-run \"(fun x -> fun y -> x + y) 10 20\")")
(epoch 455)
(eval "(ocaml-run \"let f = fun x -> x + 1 in f 9\")")
;; Closures capture
(epoch 460)
(eval "(ocaml-run \"let x = 10 in let f y = x + y in f 5\")")
(epoch 461)
(eval "(ocaml-run \"let make_adder n = fun x -> n + x in (make_adder 100) 1\")")
;; Recursion
(epoch 470)
(eval "(ocaml-run \"let rec fact n = if n = 0 then 1 else n * fact (n - 1) in fact 5\")")
(epoch 471)
(eval "(ocaml-run \"let rec fib n = if n < 2 then n else fib (n - 1) + fib (n - 2) in fib 10\")")
(epoch 472)
(eval "(ocaml-run \"let rec sum n = if n = 0 then 0 else n + sum (n - 1) in sum 100\")")
;; Sequence
(epoch 480)
(eval "(ocaml-run \"1; 2; 3\")")
(epoch 481)
(eval "(ocaml-run \"begin 10 end\")")
;; Programs (top-level decls)
(epoch 490)
(eval "(ocaml-run-program \"let x = 1;; let y = 2;; x + y\")")
(epoch 491)
(eval "(ocaml-run-program \"let rec fact n = if n = 0 then 1 else n * fact (n - 1);; fact 6\")")
(epoch 492)
(eval "(ocaml-run-program \"let inc x = x + 1;; let double x = x * 2;; double (inc 4)\")")
;; Pipe
(epoch 495)
(eval "(ocaml-run \"let f x = x * 2 in 5 |> f\")")
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 is eof" '"eof"'
# numbers
check 110 "int type" '"number"'
check 111 "int value" '42'
check 112 "float value" '3.14'
check 113 "hex value" '255'
check 114 "exponent" '1000'
check 115 "underscored int" '1000000'
check 116 "neg exponent" '0.0314'
# idents / ctors / keywords
check 120 "ident type" '"ident"'
check 121 "ident value" '"foo_bar1"'
check 122 "ctor type" '"ctor"'
check 123 "ctor value" '"Some"'
check 124 "let keyword type" '"keyword"'
check 125 "match keyword value" '"match"'
check 126 "true is keyword" '"keyword"'
check 127 "false value" '"false"'
check 128 "primed ident" "\"name'\""
# strings
check 130 "string type" '"string"'
check 131 "string value" '"hi"'
check 132 "escape sequence" '"a'
# chars
check 140 "char type" '"char"'
check 141 "char value" '"a"'
check 142 "char escape" '"'
# tyvars
check 145 "tyvar type" '"tyvar"'
check 146 "tyvar value" '"a"'
# multi-char ops
check 150 "->" '"->"'
check 151 "|>" '"|>"'
check 152 "<-" '"<-"'
check 153 ":=" '":="'
check 154 "::" '"::"'
check 155 ";;" '";;"'
check 156 "@@" '"@@"'
check 157 "<>" '"<>"'
check 158 "&&" '"&&"'
check 159 "||" '"||"'
# single ops
check 160 "+" '"+"'
check 161 "|" '"|"'
check 162 ";" '";"'
check 163 "(" '"("'
check 164 "!" '"!"'
check 165 "@" '"@"'
# comments
check 170 "block comment alone -> eof" '1'
check 171 "num after block comment" '42'
check 172 "nested comment count" '2'
check 173 "nested comment value" '1'
# compound
check 180 "let x = 1 count" '5'
check 181 "let is keyword" '"keyword"'
check 182 "let value" '"let"'
check 183 "x is ident" '"ident"'
check 184 "= value" '"="'
check 185 "1 value" '1'
check 190 "match expr count" '13'
check 191 "fun -> arrow value" '"->"'
check 192 "fun -> arrow type" '"op"'
check 193 "Some is ctor" '"ctor"'
check 194 "first |> value" '"|>"'
check 195 "ref assign :=" '":="'
# ── Parser tests ────────────────────────────────────────────────
check 200 "parse int" '("int" 42)'
check 201 "parse float" '("float" 3.14)'
check 202 "parse string" '("string" "hi")'
check 203 "parse char" '("char" "a")'
check 204 "parse true" '("bool" true)'
check 205 "parse false" '("bool" false)'
check 206 "parse var" '("var" "x")'
check 207 "parse ctor" '("con" "Some")'
check 208 "parse unit" '("unit")'
check 210 "parse f x" '("app" ("var" "f") ("var" "x"))'
check 211 "parse f x y left-assoc" '("app" ("app" ("var" "f") ("var" "x")) ("var" "y"))'
check 212 "parse f (g x)" '("app" ("var" "f") ("app" ("var" "g") ("var" "x")))'
check 213 "parse Some 42" '("app" ("con" "Some") ("int" 42))'
check 220 "parse 1+2" '("op" "+" ("int" 1) ("int" 2))'
check 221 "parse a + b * c prec" '("op" "+" ("var" "a") ("op" "*"'
check 222 "parse a*b + c prec" '("op" "+" ("op" "*"'
check 223 "parse && / || prec" '("op" "||" ("op" "&&"'
check 224 "parse a = b" '("op" "=" ("var" "a") ("var" "b"))'
check 225 "parse ^ right-assoc" '("op" "^" ("var" "a") ("op" "^"'
check 226 "parse :: right-assoc" '("op" "::" ("var" "a") ("op" "::"'
check 227 "parse parens override" '("op" "*" ("op" "+"'
check 228 "parse |> chain" '("op" "|>" ("op" "|>"'
check 229 "parse mod kw-binop" '("op" "mod" ("var" "x") ("int" 2))'
check 230 "parse -x" '("neg" ("var" "x"))'
check 231 "parse -1+2" '("op" "+" ("neg" ("int" 1)) ("int" 2))'
check 240 "parse tuple" '("tuple" ("int" 1) ("int" 2) ("int" 3))'
check 241 "parse list literal" '("list" ("int" 1) ("int" 2) ("int" 3))'
check 242 "parse []" '("list")'
check 250 "parse if/then/else" '("if" ("var" "x") ("int" 1) ("int" 2))'
check 251 "parse if w/o else" '("if" ("var" "c") ("var" "x") ("unit"))'
check 252 "parse fun x -> ..." '("fun" ("x") ("op" "+" ("var" "x") ("int" 1)))'
check 253 "parse fun x y ->" '("fun" ("x" "y")'
check 254 "parse let x = 1 in x" '("let" "x" () ("int" 1) ("var" "x"))'
check 255 "parse let f x =" '("let" "f" ("x") ("op" "+"'
check 256 "parse let rec f x =" '("let-rec" "f" ("x")'
check 257 "parse let f x y =" '("let" "f" ("x" "y")'
check 260 "parse begin/end" '("op" "+" ("int" 1) ("int" 2))'
# ── Top-level decls ─────────────────────────────────────────────
check 270 "program: let x = 1" '("program" ("def" "x" () ("int" 1)))'
check 271 "program: let x = 1 ;;" '("program" ("def" "x" () ("int" 1)))'
check 272 "program: let f x = x+1" '("program" ("def" "f" ("x") ("op" "+"'
check 273 "program: let rec fact" '("def-rec" "fact" ("n")'
check 274 "program: two decls" '("def" "x" () ("int" 1)) ("def" "y"'
check 275 "program: bare expr" '("program" ("expr" ("op" "+" ("int" 1) ("int" 2))))'
check 276 "program: mixed decls + expr" '("def" "y" () ("int" 2)) ("expr"'
check 277 "program: 4 forms incl head" '4'
check 278 "program: empty" '("program")'
# ── Match / patterns ────────────────────────────────────────────
check 300 "match Some/None" '("match" ("var" "x") (("case" ("pcon" "None") ("int" 0)) ("case" ("pcon" "Some" ("pvar" "y")) ("var" "y")))'
check 301 "match no leading bar" '("match" ("var" "x") (("case" ("pcon" "None") ("int" 0)) ("case" ("pcon" "Some"'
check 302 "match list cons" '("case" ("plist") ("int" 0)) ("case" ("pcons" ("pvar" "h") ("pvar" "t")) ("int" 1))'
check 303 "match tuple pat" '("ptuple" ("pvar" "a") ("pvar" "b"))'
check 304 "match int + wildcard" '("case" ("plit" ("int" 0)) ("int" 1)) ("case" ("pwild")'
check 305 "match bool literals" '("plit" ("bool" true))'
check 306 "match ctor with tuple arg" '("pcon" "Pair" ("pvar" "a") ("pvar" "b"))'
check 307 "match string literal" '("plit" ("string" "hi"))'
check 308 "match unit pattern" '("plit" ("unit"))'
# ── Sequences ───────────────────────────────────────────────────
check 320 "seq 1;2" '("seq" ("int" 1) ("int" 2))'
check 321 "seq 1;2;3" '("seq" ("int" 1) ("int" 2) ("int" 3))'
check 322 "seq in parens" '("seq" ("int" 1) ("int" 2))'
check 323 "seq in begin/end" '("seq" ("var" "a") ("var" "b") ("var" "c"))'
check 324 "let body absorbs seq" '("let" "x" () ("int" 1) ("seq" ("var" "x") ("var" "x")))'
check 325 "if-branch parens for seq" '("if" ("var" "c") ("seq" ("var" "a") ("var" "b"))'
check 326 "list ; is separator" '("list" ("int" 1) ("int" 2) ("int" 3))'
check 327 "trailing ; OK" '("seq" ("int" 1) ("int" 2))'
check 328 "begin a; end singleton seq" '("seq" ("var" "a"))'
check 329 "match clause body absorbs ;" '("case" ("pwild") ("seq" ("var" "a") ("var" "b")))'
# ── Phase 2: evaluator ──────────────────────────────────────────
# atoms
check 400 "eval int" '42'
check 401 "eval float" '3.14'
check 402 "eval true" 'true'
check 403 "eval false" 'false'
check 404 "eval string" '"hi"'
# arithmetic
check 410 "eval 1+2" '3'
check 411 "eval 10-3" '7'
check 412 "eval 4*5" '20'
check 413 "eval 20/4" '5'
check 414 "eval 10 mod 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'
# comparison & boolean
check 420 "eval 1<2" 'true'
check 421 "eval 3>2" 'true'
check 422 "eval 2=2" 'true'
check 423 "eval 1<>2" 'true'
check 424 "eval true && false" 'false'
check 425 "eval true || false" 'true'
check 426 "eval not false" 'true'
# string
check 430 'eval "a" ^ "b"' '"ab"'
check 431 "eval string concat 3" '"hello world"'
# conditional
check 440 "eval if true 1 else 2" '1'
check 441 "eval if 1>2 100 else 200" '200'
# let / lambda / app
check 450 "eval let x=5 x*2" '10'
check 451 "eval let f x = x+1; f 41" '42'
check 452 "eval let f x y = x+y; f 3 4" '7'
check 453 "eval (fun x -> x*x) 7" '49'
check 454 "eval curried lambdas" '30'
check 455 "eval named lambda" '10'
# closures
check 460 "eval closure capture" '15'
check 461 "eval make_adder" '101'
# recursion
check 470 "eval fact 5" '120'
check 471 "eval fib 10" '55'
check 472 "eval sum 100" '5050'
# sequence
check 480 "eval 1; 2; 3 → 3" '3'
check 481 "eval begin 10 end" '10'
# programs
check 490 "run-prog x+y" '3'
check 491 "run-prog fact 6" '720'
check 492 "run-prog inc + double" '10'
# pipe
check 495 "eval x |> f" '10'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"
else
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
echo ""
echo "$ERRORS"
fi
[ $FAIL -eq 0 ]