From dab8718289464d626b66e30538bbe1112f15eac0 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 9 May 2026 03:40:38 +0000 Subject: [PATCH] ocaml: phase 4 'let PATTERN = expr in body' tuple destructuring (+3 tests, 541 total) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When 'let' is followed by '(', parse-let now reads a full pattern (via the existing parse-pattern used by match), expects '=', then 'in', and desugars to: let PATTERN = EXPR in BODY => match EXPR with PATTERN -> BODY This reuses the entire pattern-matching machinery, so any pattern the match parser accepts works here too — paren-tuples, nested tuples, cons patterns, list patterns. No 'rec' allowed for pattern bindings (real OCaml's restriction). let (a, b) = (1, 2) in a + b = 3 let (a, b, c) = (10, 20, 30) in a + b + c = 60 let pair = (5, 7) in let (x, y) = pair in x * y = 35 Also retroactively cleaned up Printf's iter-97 width-pos packing hack ('width * 1000000 + spec_pos') — it's now 'let (width, spec_pos) = parse_width_loop after_flags in ...' like real OCaml. --- lib/ocaml/parser.sx | 13 +++++++++++++ lib/ocaml/runtime.sx | 6 ++---- lib/ocaml/test.sh | 13 +++++++++++++ plans/ocaml-on-sx.md | 8 ++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index f84e6263..fd7db26a 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -765,6 +765,19 @@ (consume! "keyword" "in") (let ((body (parse-expr))) (list :let-open path body)))))) + ;; `let PATTERN = expr in body` — non-trivial pattern + ;; desugars to `match expr with PATTERN -> body`. Triggers + ;; on `(` (paren-tuple, possibly nested) immediately after + ;; `let` (no `rec` allowed for pattern bindings). + ((at-op? "(") + (let ((pat (parse-pattern))) + (begin + (consume! "op" "=") + (let ((rhs (parse-expr))) + (begin + (consume! "keyword" "in") + (let ((body (parse-expr))) + (list :match rhs (list (list :case pat body))))))))) (else (let ((reccy false) (bindings (list))) (begin diff --git a/lib/ocaml/runtime.sx b/lib/ocaml/runtime.sx index 56ba3f7e..dbd6b7d0 100644 --- a/lib/ocaml/runtime.sx +++ b/lib/ocaml/runtime.sx @@ -562,7 +562,7 @@ else cont := false else cont := false done; - (!w) * 1000000 + (!i) + (!w, !i) in let rec walk pos prefix = if pos >= n then prefix @@ -573,9 +573,7 @@ let left_flag = ref false in let zero_flag = ref false in let after_flags = parse_flags_loop (pos + 1) left_flag zero_flag in - let packed = parse_width_loop after_flags in - let width = packed / 1000000 in - let spec_pos = packed - width * 1000000 in + let (width, spec_pos) = parse_width_loop after_flags in if spec_pos < n && is_spec (_string_get fmt spec_pos) then let spec = _string_get fmt spec_pos in let left = !left_flag in diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 6b12658f..f395cc78 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1342,6 +1342,14 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 5084) (eval "(ocaml-run \"Printf.sprintf \\\"hi=%-3d, hex=%04x\\\" 9 15\")") +;; ── let (a, b) = expr in body — tuple destructure ───────────── +(epoch 5090) +(eval "(ocaml-run \"let (a, b) = (1, 2) in a + b\")") +(epoch 5091) +(eval "(ocaml-run \"let (a, b, c) = (10, 20, 30) in a + b + c\")") +(epoch 5092) +(eval "(ocaml-run \"let pair = (5, 7) in let (x, y) = pair in x * y\")") + EPOCHS OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -2132,6 +2140,11 @@ check 5082 "%05d 42 zero-pad" '"00042"' check 5083 "%4s hi" '" hi"' check 5084 "%-3d %04x mixed" '"hi=9 , hex=000f"' +# ── let (a, b) = expr in body ─────────────────────────────────── +check 5090 "let (a, b) = (1,2)" '3' +check 5091 "let (a, b, c) = (10,20,30)" '60' +check 5092 "let pair; let (x, y) = pair" '35' + 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 6f48af2c..ae9de5b5 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,14 @@ _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-09 Phase 4 — `let PATTERN = expr in body` tuple + destructuring (+3 tests, 541 total). When `let` is followed by `(`, + parse-let now reads a full pattern, expects `=`, then `in`, and + desugars to `(:match expr ((:case PATTERN body)))`. Reuses the + pattern parser used by match. `let (a, b, c) = (10, 20, 30) in + a+b+c` → 60. Also retroactively cleans up the Printf width-pos + packing hack from iteration 97 — it's now `let (width, spec_pos) + = parse_width_loop after_flags in ...` like real OCaml. - 2026-05-09 Phase 6 — Printf width specifiers `%5d` / `%-5d` / `%05d` / `%4s` etc. (+5 tests, 538 total). Walker now parses optional `-` (left-align) and `0` (zero-pad) flags after `%`, then