ocaml: phase 2 function | pat -> body (+4 tests, 198 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s

Sugar for fun + match. AST (:function CLAUSES) -> unary closure that
runs ocaml-match-clauses on its arg. let rec recognises :function as a
recursive rhs and ties the knot via cell, so

  let rec map f = function | [] -> [] | h::t -> f h :: map f t

works. ocaml-match-eval refactored to share clause-walk with function.
This commit is contained in:
2026-05-08 08:15:38 +00:00
parent 9b8b0b4325
commit 937342bbf0
4 changed files with 114 additions and 54 deletions

View File

@@ -156,23 +156,26 @@
(else ocaml-match-fail))))
(else (error (str "ocaml-match-pat: unknown pattern tag " tag)))))))
(define ocaml-match-clauses
(fn (val clauses env)
(begin
(define try-clauses
(fn (cs)
(cond
((empty? cs)
(error (str "ocaml-eval: match failure on " val)))
(else
(let ((clause (first cs)))
(let ((pat (nth clause 1)) (body (nth clause 2)))
(let ((env2 (ocaml-match-pat pat val env)))
(cond
((= env2 ocaml-match-fail) (try-clauses (rest cs)))
(else (ocaml-eval body env2))))))))))
(try-clauses clauses))))
(define ocaml-match-eval
(fn (scrut-ast clauses env)
(let ((val (ocaml-eval scrut-ast env)))
(begin
(define try-clauses
(fn (cs)
(cond
((empty? cs)
(error (str "ocaml-eval: match failure on " val)))
(else
(let ((clause (first cs)))
(let ((pat (nth clause 1)) (body (nth clause 2)))
(let ((env2 (ocaml-match-pat pat val env)))
(cond
((= env2 ocaml-match-fail) (try-clauses (rest cs)))
(else (ocaml-eval body env2))))))))))
(try-clauses clauses)))))
(ocaml-match-clauses (ocaml-eval scrut-ast env) clauses env)))
;; Auto-curry: (:fun ("x" "y" "z") body) → (fn (x) (fn (y) (fn (z) body))).
;; A zero-param lambda evaluates the body immediately on first call —
@@ -300,6 +303,11 @@
(fn-val arg-val))))))
((= tag "match")
(ocaml-match-eval (nth ast 1) (nth ast 2) env))
((= tag "function")
;; `function | pat -> body | …` — produces a unary closure that
;; matches its argument against the clauses.
(let ((clauses (nth ast 1)) (captured env))
(fn (arg) (ocaml-match-clauses arg clauses captured))))
((= tag "for")
;; (:for NAME LO HI DIR BODY) — DIR is "ascend" or "descend".
(let ((name (nth ast 1))
@@ -354,30 +362,30 @@
(ocaml-make-curried params rhs env))))
(ocaml-eval body (ocaml-env-extend env name rhs-val)))))
((= tag "let-rec")
;; For function bindings: tie the knot via a mutable cell. The
;; placeholder closure that's bound first dereferences the cell
;; on each call, so the function can call itself once the cell
;; is set to the real closure.
;; Tie the knot via a mutable cell when rhs is function-typed.
;; The placeholder closure dereferences the cell on each call.
(let ((name (nth ast 1)) (params (nth ast 2))
(rhs (nth ast 3)) (body (nth ast 4)))
(cond
((= (len params) 0)
;; Non-functional let-rec — OCaml only allows this when the
;; rhs is "syntactically a function or constructor". For the
;; common case of a value, evaluate non-recursively.
(let ((rhs-val (ocaml-eval rhs env)))
(ocaml-eval body (ocaml-env-extend env name rhs-val))))
(else
;; Use a one-element list as a mutable cell to tie the
;; recursive knot. The placeholder closure dereferences
;; the cell on each call.
(let ((cell (list nil)))
(let ((env2 (ocaml-env-extend env name
(fn (arg) ((nth cell 0) arg)))))
(let ((rhs-val (ocaml-make-curried params rhs env2)))
(begin
(set-nth! cell 0 rhs-val)
(ocaml-eval body env2)))))))))
(let ((rhs-fn?
(or (> (len params) 0)
(= (ocaml-tag-of rhs) "fun")
(= (ocaml-tag-of rhs) "function"))))
(cond
(rhs-fn?
(let ((cell (list nil)))
(let ((env2 (ocaml-env-extend env name
(fn (arg) ((nth cell 0) arg)))))
(let ((rhs-val
(if (= (len params) 0)
(ocaml-eval rhs env2)
(ocaml-make-curried params rhs env2))))
(begin
(set-nth! cell 0 rhs-val)
(ocaml-eval body env2))))))
(else
(let ((rhs-val (ocaml-eval rhs env)))
(ocaml-eval body
(ocaml-env-extend env name rhs-val))))))))
(else (error
(str "ocaml-eval: unknown AST tag " tag)))))))
@@ -406,21 +414,28 @@
(set! last v)))))
((= tag "def-rec")
(let ((name (nth decl 1)) (params (nth decl 2)) (rhs (nth decl 3)))
(cond
((= (len params) 0)
(let ((v (ocaml-eval rhs env)))
(begin
(set! env (ocaml-env-extend env name v))
(set! last v))))
(else
(let ((cell (list nil)))
(let ((env2 (ocaml-env-extend env name
(fn (arg) ((nth cell 0) arg)))))
(let ((v (ocaml-make-curried params rhs env2)))
(begin
(set-nth! cell 0 v)
(set! env env2)
(set! last v)))))))))
(let ((rhs-fn?
(or (> (len params) 0)
(= (ocaml-tag-of rhs) "fun")
(= (ocaml-tag-of rhs) "function"))))
(cond
(rhs-fn?
(let ((cell (list nil)))
(let ((env2 (ocaml-env-extend env name
(fn (arg) ((nth cell 0) arg)))))
(let ((v
(if (= (len params) 0)
(ocaml-eval rhs env2)
(ocaml-make-curried params rhs env2))))
(begin
(set-nth! cell 0 v)
(set! env env2)
(set! last v))))))
(else
(let ((v (ocaml-eval rhs env)))
(begin
(set! env (ocaml-env-extend env name v))
(set! last v))))))))
((= tag "expr")
(set! last (ocaml-eval (nth decl 1) env)))
(else (error (str "ocaml-run-program: bad decl " tag)))))))

View File

@@ -573,6 +573,28 @@
(begin (advance-tok!) (one) (loop)))))
(loop)
(cons :match (cons scrut (list cases)))))))))
(define parse-function
(fn ()
;; `function | pat -> body | …` ≡ fun x -> match x with | pat -> body
(let ()
(begin
(when (at-op? "|") (advance-tok!))
(let ((cases (list)))
(begin
(define one
(fn ()
(let ((p (parse-pattern)))
(begin
(consume! "op" "->")
(let ((body (parse-expr)))
(append! cases (list :case p body)))))))
(one)
(define loop
(fn ()
(when (at-op? "|")
(begin (advance-tok!) (one) (loop)))))
(loop)
(list :function cases)))))))
(define parse-for
(fn ()
(let ((name (ocaml-tok-value (consume! "ident" nil))))
@@ -609,6 +631,7 @@
((at-kw? "let") (begin (advance-tok!) (parse-let)))
((at-kw? "if") (begin (advance-tok!) (parse-if)))
((at-kw? "match") (begin (advance-tok!) (parse-match)))
((at-kw? "function") (begin (advance-tok!) (parse-function)))
((at-kw? "for") (begin (advance-tok!) (parse-for)))
((at-kw? "while") (begin (advance-tok!) (parse-while)))
(else (parse-tuple)))))

View File

@@ -507,6 +507,16 @@ cat > "$TMPFILE" << 'EPOCHS'
(epoch 624)
(eval "(ocaml-run \"let p = ref 1 in for i = 1 to 5 do p := !p * i done; !p\")")
;; ── function (sugar for fun + match) ───────────────────────────
(epoch 640)
(eval "(ocaml-run \"(function | None -> 0 | Some x -> x) (Some 7)\")")
(epoch 641)
(eval "(ocaml-run \"let f = function | None -> 0 | Some x -> x in f None\")")
(epoch 642)
(eval "(ocaml-run \"let rec len = function | [] -> 0 | _ :: t -> 1 + len t in len [1; 2; 3]\")")
(epoch 643)
(eval "(ocaml-run-program \"let rec map f = function | [] -> [] | h :: t -> f h :: map f t;; map (fun x -> x * x) [1; 2; 3; 4]\")")
EPOCHS
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
@@ -808,6 +818,12 @@ check 622 "while loop" '15'
check 623 "for 1..100 sum" '5050'
check 624 "for 1..5 product = 120" '120'
# ── function ────────────────────────────────────────────────────
check 640 "function None|Some Some 7" '7'
check 641 "function None=0" '0'
check 642 "rec function len" '3'
check 643 "rec function map x*x" '(1 4 9 16)'
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"

View File

@@ -148,8 +148,7 @@ SX CEK evaluator (both JS and OCaml hosts)
- [~] `let`/`let rec`/`let ... in` (single-binding done; mutually recursive
`and` deferred).
- [x] Lambda + application (curried by default — auto-curry multi-param defs).
- [ ] `fun`/`function` (single-arg lambda with immediate match on arg). _(`fun`
done; `function` blocked on parser support.)_
- [x] `fun`/`function` (single-arg lambda with immediate match on arg).
- [x] `if`/`then`/`else`, `begin`/`end`, sequence `;`.
- [x] Arithmetic, comparison, boolean ops, string `^`, `mod`.
- [x] Unit `()` value; `ignore`.
@@ -321,6 +320,13 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means:
_Newest first._
- 2026-05-08 Phase 2 — `function | pat -> body | …` parser + eval.
Sugar for `fun x -> match x with | …`. AST: `(:function CLAUSES)`
evaluated to a unary closure that runs `ocaml-match-clauses` on the
argument. `let rec` knot also triggers when rhs is `:function`, so
`let rec map f = function | [] -> [] | h::t -> f h :: map f t` works.
ocaml-match-eval refactored to share `ocaml-match-clauses` with the
function form. 198/198 (+4).
- 2026-05-08 Phase 2 — `for`/`while` loops. `(:for NAME LO HI DIR BODY)`
with `:ascend`/`:descend` direction (`to`/`downto`); `(:while COND BODY)`.
Both eval to unit and re-bind the loop var per iteration. 194/194 (+5).