From 982e9680fe02d484797899fb338d60ac9a67f31c Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 21:43:38 +0000 Subject: [PATCH] ocaml: phase 4 'M.(expr)' local-open expression form (+3 tests, 481 total) In parse-atom-postfix, after consuming '.', if the next token is '(', parse the inner expression and emit (:let-open M EXPR) instead of :field. Cleanly composes with the existing :let-open evaluator and loops to allow chained dot postfixes. List.(length [1;2;3]) = 3 List.(map (fun x -> x + 1) [1;2;3]) = [2;3;4] Option.(map (fun x -> x * 10) (Some 4)) = Some 40 --- lib/ocaml/parser.sx | 21 ++++++++++++++++----- lib/ocaml/test.sh | 13 +++++++++++++ plans/ocaml-on-sx.md | 6 ++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 42ab481a..642bc607 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -562,11 +562,22 @@ (when (at-op? ".") (begin (advance-tok!) - (let ((tok (peek-tok))) - (begin - (advance-tok!) - (set! head (list :field head (ocaml-tok-value tok))) - (loop))))))) + (cond + ((at-op? "(") + (begin + (advance-tok!) + (let ((inner (parse-expr))) + (begin + (consume! "op" ")") + (set! head (list :let-open head inner)) + (loop))))) + (else + (let ((tok (peek-tok))) + (begin + (advance-tok!) + (set! head (list :field head + (ocaml-tok-value tok))) + (loop))))))))) (loop) head)))) (set! diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 0f515f1c..59e816f5 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1186,6 +1186,14 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 4902) (eval "(ocaml-run \"let open Option in map (fun x -> x + 1) (Some 5)\")") +;; ── M.(expr) local-open expression form ─────────────────────── +(epoch 4910) +(eval "(ocaml-run \"List.(length [1;2;3])\")") +(epoch 4911) +(eval "(ocaml-run \"List.(map (fun x -> x + 1) [1;2;3])\")") +(epoch 4912) +(eval "(ocaml-run \"Option.(map (fun x -> x * 10) (Some 4))\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1880,6 +1888,11 @@ check 4900 "let open List; length" '3' check 4901 "let open List; map" '(2 4 6)' check 4902 "let open Option; map" '("Some" 6)' +# ── M.(expr) local-open expression form ────────────────────────── +check 4910 "M.(expr) length" '3' +check 4911 "M.(expr) map" '(2 3 4)' +check 4912 "M.(expr) Option map" '("Some" 40)' + 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 ba21a0e8..1b8ec2b9 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,12 @@ _Newest first._ binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree`) with insert + in-order traversal. Tests parametric ADT, recursive match, List.append, List.fold_left. +- 2026-05-08 Phase 4 — `M.(expr)` local-open expression form (+3 + tests, 481 total). Implemented in parse-atom-postfix: after + consuming `.`, if next token is `(`, parse the inner expression and + emit `(:let-open M EXPR)` instead of `:field`. Cleanly composes with + existing `:let-open` evaluator. `List.(length [1;2;3])` → 3, + `Option.(map (fun x -> x * 10) (Some 4))` → Some 40. - 2026-05-08 Phase 4 — `let open M in body` local opens (+3 tests, 478 total). Parser detects `let open` as a separate let-form, parses M as a path (Ctor(.Ctor)*), and emits `(:let-open PATH BODY)`. Eval