ocaml: phase 1 sequence operator ; (+10 tests, 123 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s

Two-phase grammar: parse-expr-no-seq (prior entry) + parse-expr wraps
it with ;-chaining. List bodies keep parse-expr-no-seq so ; remains a
separator inside [...]. Match clause bodies use the seq variant and stop
at | — real OCaml semantics. Trailing ; before end/)/|/in/then/else/eof
permitted.
This commit is contained in:
2026-05-08 07:48:52 +00:00
parent 9102e57d89
commit a6ab944c39
3 changed files with 93 additions and 14 deletions

View File

@@ -17,6 +17,7 @@
;; let let x = e in body (no rec, function shorthand) ;; let let x = e in body (no rec, function shorthand)
;; let rec f x = e in body ;; let rec f x = e in body
;; match match e with [|] p -> body | p -> body | ... ;; match match e with [|] p -> body | p -> body | ...
;; sequence e1 ; e2 → (:seq e1 e2 …) (lowest-precedence binary)
;; ;;
;; Pattern scope: ;; Pattern scope:
;; _ (wildcard), int/string/char/bool literals, ident (var binding), ;; _ (wildcard), int/string/char/bool literals, ident (var binding),
@@ -30,6 +31,7 @@
;; (:op OP LHS RHS) ;; (:op OP LHS RHS)
;; (:neg E) (:not E) ;; (:neg E) (:not E)
;; (:tuple ITEMS) (:list ITEMS) ;; (:tuple ITEMS) (:list ITEMS)
;; (:seq EXPRS)
;; (:if C T E) ;; (:if C T E)
;; (:fun PARAMS BODY) ;; (:fun PARAMS BODY)
;; (:let NAME PARAMS EXPR BODY) (:let-rec NAME PARAMS EXPR BODY) ;; (:let NAME PARAMS EXPR BODY) (:let-rec NAME PARAMS EXPR BODY)
@@ -280,6 +282,7 @@
(else lhs))))) (else lhs)))))
(set! parse-pattern (fn () (parse-pattern-cons))) (set! parse-pattern (fn () (parse-pattern-cons)))
(define parse-expr (fn () nil)) (define parse-expr (fn () nil))
(define parse-expr-no-seq (fn () nil))
(define parse-tuple (fn () nil)) (define parse-tuple (fn () nil))
(define parse-binop-rhs (fn (lhs min-prec) lhs)) (define parse-binop-rhs (fn (lhs min-prec) lhs))
(define parse-prefix (fn () nil)) (define parse-prefix (fn () nil))
@@ -324,7 +327,7 @@
(let (let
((items (list))) ((items (list)))
(begin (begin
(append! items (parse-expr)) (append! items (parse-expr-no-seq))
(define (define
loop loop
(fn (fn
@@ -336,7 +339,7 @@
(when (when
(not (at-op? "]")) (not (at-op? "]"))
(begin (begin
(append! items (parse-expr)) (append! items (parse-expr-no-seq))
(loop))))))) (loop)))))))
(loop) (loop)
(consume! "op" "]") (consume! "op" "]")
@@ -521,17 +524,17 @@
(fn (fn
() ()
(let (let
((cond-expr (parse-expr))) ((cond-expr (parse-expr-no-seq)))
(begin (begin
(consume! "keyword" "then") (consume! "keyword" "then")
(let (let
((then-expr (parse-expr))) ((then-expr (parse-expr-no-seq)))
(cond (cond
((at-kw? "else") ((at-kw? "else")
(begin (begin
(advance-tok!) (advance-tok!)
(let (let
((else-expr (parse-expr))) ((else-expr (parse-expr-no-seq)))
(list :if cond-expr then-expr else-expr)))) (list :if cond-expr then-expr else-expr))))
(else (list :if cond-expr then-expr (list :unit))))))))) (else (list :if cond-expr then-expr (list :unit)))))))))
(define (define
@@ -539,7 +542,7 @@
(fn (fn
() ()
(let (let
((scrut (parse-expr))) ((scrut (parse-expr-no-seq)))
(begin (begin
(consume! "keyword" "with") (consume! "keyword" "with")
(when (at-op? "|") (advance-tok!)) (when (at-op? "|") (advance-tok!))
@@ -555,7 +558,7 @@
(begin (begin
(consume! "op" "->") (consume! "op" "->")
(let (let
((body (parse-match-body))) ((body (parse-expr)))
(append! cases (list :case p body))))))) (append! cases (list :case p body)))))))
(one) (one)
(define (define
@@ -567,9 +570,8 @@
(begin (advance-tok!) (one) (loop))))) (begin (advance-tok!) (one) (loop)))))
(loop) (loop)
(cons :match (cons scrut (list cases))))))))) (cons :match (cons scrut (list cases)))))))))
(define parse-match-body (fn () (parse-expr)))
(set! (set!
parse-expr parse-expr-no-seq
(fn (fn
() ()
(cond (cond
@@ -578,6 +580,40 @@
((at-kw? "if") (begin (advance-tok!) (parse-if))) ((at-kw? "if") (begin (advance-tok!) (parse-if)))
((at-kw? "match") (begin (advance-tok!) (parse-match))) ((at-kw? "match") (begin (advance-tok!) (parse-match)))
(else (parse-tuple))))) (else (parse-tuple)))))
(set!
parse-expr
(fn
()
(let
((lhs (parse-expr-no-seq)))
(cond
((at-op? ";")
(let
((items (list lhs)))
(begin
(define
loop
(fn
()
(when
(at-op? ";")
(begin
(advance-tok!)
(cond
((at-kw? "end") nil)
((at-op? ")") nil)
((at-op? "|") nil)
((at-kw? "in") nil)
((at-kw? "then") nil)
((at-kw? "else") nil)
((= (ocaml-tok-type (peek-tok)) "eof") nil)
(else
(begin
(append! items (parse-expr-no-seq))
(loop))))))))
(loop)
(cons :seq items))))
(else lhs)))))
(let (let
((result (parse-expr))) ((result (parse-expr)))
(begin (begin

View File

@@ -300,6 +300,28 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 308) (epoch 308)
(eval "(ocaml-parse \"match x with | () -> 0\")") (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\")")
EPOCHS EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
@@ -476,6 +498,18 @@ check 306 "match ctor with tuple arg" '("pcon" "Pair" ("pvar" "a") ("pvar" "b
check 307 "match string literal" '("plit" ("string" "hi"))' check 307 "match string literal" '("plit" ("string" "hi"))'
check 308 "match unit pattern" '("plit" ("unit"))' 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")))'
TOTAL=$((PASS + FAIL)) TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed" echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"

View File

@@ -128,11 +128,12 @@ SX CEK evaluator (both JS and OCaml hosts)
- [~] **Parser:** expressions: literals, identifiers, constructor application, - [~] **Parser:** expressions: literals, identifiers, constructor application,
lambda, application (left-assoc), binary ops with precedence (29 ops via lambda, application (left-assoc), binary ops with precedence (29 ops via
`lib/guest/pratt.sx`), `if`/`then`/`else`, `let`/`in`, `let rec`, `lib/guest/pratt.sx`), `if`/`then`/`else`, `let`/`in`, `let rec`,
`fun`/`->`, tuples, list literals, `begin`/`end`, unit `()`. Top-level `fun`/`->`, `match`/`with`, tuples, list literals, sequences `;`,
decls: `let [rec] name params* = expr` and bare expressions, `;;`-separated `begin`/`end`, unit `()`. Top-level decls: `let [rec] name params* = expr`
via `ocaml-parse-program`. _(Pending: `type`/`module`/`exception`/`open`/ and bare expressions, `;;`-separated via `ocaml-parse-program`. _(Pending:
`include` decls, `match`/`with`, `try`/`with`, `function`, record literals/ `type`/`module`/`exception`/`open`/`include` decls, `try`/`with`,
updates, field access, sequences `;`, `and` mutually-recursive bindings.)_ `function`, record literals/updates, field access, `and` mutually-recursive
bindings.)_
- [~] **Patterns:** constructor (nullary + with args, incl. flattened tuple - [~] **Patterns:** constructor (nullary + with args, incl. flattened tuple
args `Pair (a, b)``(:pcon "Pair" PA PB)`), literal (int/string/char/ args `Pair (a, b)``(:pcon "Pair" PA PB)`), literal (int/string/char/
bool/unit), variable, wildcard `_`, tuple, list cons `::`, list literal. bool/unit), variable, wildcard `_`, tuple, list cons `::`, list literal.
@@ -314,6 +315,14 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means:
_Newest first._ _Newest first._
- 2026-05-07 Phase 1 — sequence operator `;`. Lowest-precedence binary;
`e1; e2; e3``(:seq e1 e2 e3)`. Two-phase grammar: `parse-expr-no-seq`
is the prior expression entry point; new `parse-expr` wraps it with
`;` chaining. List-literal items still use `parse-expr-no-seq` so `;`
retains its separator role inside `[…]`. Match-clause bodies use the
seq variant and stop at `|`, matching real OCaml semantics. Trailing `;`
before `end`/`)`/`|`/`in`/`then`/`else`/eof is permitted. 123/123 tests
passing (+10).
- 2026-05-07 Phase 1 — `match`/`with` + pattern parser. Patterns: wildcard, - 2026-05-07 Phase 1 — `match`/`with` + pattern parser. Patterns: wildcard,
literal, var, ctor (nullary + with arg, with tuple-arg flattening so literal, var, ctor (nullary + with arg, with tuple-arg flattening so
`Pair (a, b)``(:pcon "Pair" PA PB)`), tuple, list literal, cons `::` `Pair (a, b)``(:pcon "Pair" PA PB)`), tuple, list literal, cons `::`