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