diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index ddbfdd01..3bb16651 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -647,6 +647,60 @@ (begin (set! env (ocaml-env-extend env name v)) (set! last v)))))))) + ((= tag "def-mut") + ;; let x = ... and y = ... — non-recursive; each rhs is + ;; evaluated in the parent env, then all names bind in + ;; sequence. + (let ((bs (nth decl 1))) + (begin + (define run-one + (fn (b) + (let ((nm (nth b 0)) (ps (nth b 1)) (rh (nth b 2))) + (let ((v (if (= (len ps) 0) + (ocaml-eval rh env) + (ocaml-make-curried ps rh env)))) + (begin + (set! env (ocaml-env-extend env nm v)) + (set! last v)))))) + (define loop + (fn (xs) + (when (not (= xs (list))) + (begin (run-one (first xs)) (loop (rest xs)))))) + (loop bs)))) + ((= tag "def-rec-mut") + ;; let rec f = ... and g = ... — mutually recursive; + ;; bind all names with placeholder cells first, then + ;; evaluate each rhs in the joint env, finally fill cells. + (let ((bs (nth decl 1)) (cells (list)) (env2 env)) + (begin + (define alloc + (fn (xs) + (when (not (= xs (list))) + (let ((b (first xs))) + (let ((c (list nil)) (nm (nth b 0))) + (begin + (append! cells c) + (set! env2 (ocaml-env-extend env2 nm + (fn (a) ((nth c 0) a)))) + (alloc (rest xs)))))))) + (alloc bs) + (let ((idx 0)) + (begin + (define fill + (fn (xs) + (when (not (= xs (list))) + (let ((b (first xs))) + (let ((nm (nth b 0)) (ps (nth b 1)) (rh (nth b 2))) + (let ((v (if (= (len ps) 0) + (ocaml-eval rh env2) + (ocaml-make-curried ps rh env2)))) + (begin + (set-nth! (nth cells idx) 0 v) + (set! idx (+ idx 1)) + (set! last v) + (fill (rest xs))))))))) + (fill bs) + (set! env env2)))))) ((= tag "expr") (set! last (ocaml-eval (nth decl 1) env))) ((= tag "module-def") diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index cbe834d8..2a5b0e4f 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -790,46 +790,51 @@ ((at-kw? "module") nil) ((at-kw? "open") nil) ((at-kw? "include") nil) + ((at-kw? "and") nil) (else (begin (advance-tok!) (skip-to-boundary!)))))) (define parse-decl-let - (fn - () + (fn () (advance-tok!) - (let - ((reccy false)) + (let ((reccy false) (bindings (list))) (begin - (when - (at-kw? "rec") - (begin (advance-tok!) (set! reccy true))) - (let - ((name (ocaml-tok-value (consume! "ident" nil))) - (params (list))) - (begin - (define - collect-params - (fn - () - (when - (check-tok? "ident" nil) - (begin - (append! params (ocaml-tok-value (peek-tok))) - (advance-tok!) - (collect-params))))) - (collect-params) - (consume! "op" "=") - (let - ((expr-start (cur-pos))) + (when (at-kw? "rec") (begin (advance-tok!) (set! reccy true))) + (define parse-one! + (fn () + (let ((nm (ocaml-tok-value (consume! "ident" nil))) + (ps (list))) (begin - (skip-to-boundary!) - (let - ((expr-src (slice src expr-start (cur-pos)))) - (let - ((expr (ocaml-parse expr-src))) - (if - reccy - (list :def-rec name params expr) - (list :def name params expr)))))))))))) + (define collect-params + (fn () + (when (check-tok? "ident" nil) + (begin + (append! ps (ocaml-tok-value (peek-tok))) + (advance-tok!) + (collect-params))))) + (collect-params) + (consume! "op" "=") + (let ((expr-start (cur-pos))) + (begin + (skip-to-boundary!) + (let ((expr-src (slice src expr-start (cur-pos)))) + (let ((expr (ocaml-parse expr-src))) + (append! bindings (list nm ps expr)))))))))) + (parse-one!) + (define more + (fn () + (when (at-kw? "and") + (begin (advance-tok!) (parse-one!) (more))))) + (more) + (cond + ((= (len bindings) 1) + (let ((b (first bindings))) + (if reccy + (list :def-rec (nth b 0) (nth b 1) (nth b 2)) + (list :def (nth b 0) (nth b 1) (nth b 2))))) + (else + (if reccy + (list :def-rec-mut bindings) + (list :def-mut bindings)))))))) (define parse-decl-expr (fn diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 261f6dea..0be261ea 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -631,6 +631,14 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 832) (eval "(ocaml-run \"Result.is_error (Error \\\"oops\\\")\")") +;; ── let ... and ... mutual recursion ────────────────────────── +(epoch 850) +(eval "(ocaml-run-program \"let rec even n = if n = 0 then true else odd (n - 1) and odd n = if n = 0 then false else even (n - 1);; even 10\")") +(epoch 851) +(eval "(ocaml-run-program \"let rec even n = if n = 0 then true else odd (n - 1) and odd n = if n = 0 then false else even (n - 1);; odd 7\")") +(epoch 852) +(eval "(ocaml-run-program \"let x = 1 and y = 2;; x + y\")") + EPOCHS OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1003,6 +1011,11 @@ check 830 "Result.map Ok" '("Ok" 6)' check 831 "Result.is_ok" 'true' check 832 "Result.is_error" 'true' +# ── let ... and ... mutual recursion ───────────────────────────── +check 850 "even 10 (mutual rec)" 'true' +check 851 "odd 7 (mutual rec)" 'true' +check 852 "let x = 1 and y = 2" '3' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "ok $PASS/$TOTAL OCaml-on-SX tests passed" diff --git a/plans/ocaml-on-sx.md b/plans/ocaml-on-sx.md index 2d76261f..26694a4e 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -145,8 +145,11 @@ SX CEK evaluator (both JS and OCaml hosts) ### Phase 2 — Core evaluator (untyped) - [x] `ocaml-eval` entry: walks OCaml AST, produces SX values. -- [~] `let`/`let rec`/`let ... in` (single-binding done; mutually recursive - `and` deferred). +- [x] `let`/`let rec`/`let ... in`. Mutually recursive `let rec f = … and + g = …` works at top level via `(:def-rec-mut BINDINGS)`; placeholders + are bound first, rhs evaluated in the joint env, cells filled in. + `let x = … and y = …` (non-rec) emits `(:def-mut BINDINGS)` — + sequential bindings against the parent env. - [x] Lambda + application (curried by default — auto-curry multi-param defs). - [x] `fun`/`function` (single-arg lambda with immediate match on arg). - [x] `if`/`then`/`else`, `begin`/`end`, sequence `;`. @@ -327,6 +330,11 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 2 — `let ... and ...` mutual recursion at top level. + Parser collects all bindings into a list, emitting `(:def-rec-mut)` or + `(:def-mut)` when there are 2+. Eval allocates a placeholder cell per + recursive binding, builds an env with all of them visible, then fills + the cells. Even/odd mutual-recursion test passes. 251/251 (+3). - 2026-05-08 Phase 6 — `lib/ocaml/runtime.sx` minimal stdlib slice written entirely in OCaml syntax: List (length, rev, rev_append, map, filter, fold_left/right, append, iter, mem, for_all, exists, hd, tl,