diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 459e4fc2..bd2b77d8 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -353,6 +353,15 @@ (advance-tok!) (set-nth! wild-counter 0 (+ (nth wild-counter 0) 1)) (str "__wild_" (nth wild-counter 0)))) + ;; ~name / ?name labeled/optional param — drop the label + ;; prefix and treat as a positional ident name. Optional + ;; default `?(name = default)` is not handled here yet. + ((and (or (at-op? "~") (at-op? "?")) + (= (ocaml-tok-type (peek-tok-at 1)) "ident")) + (begin + (advance-tok!) ;; ~ or ? + (let ((nm (ocaml-tok-value (peek-tok)))) + (begin (advance-tok!) nm)))) ((check-tok? "ident" nil) (let ((nm (ocaml-tok-value (peek-tok)))) (begin (advance-tok!) nm))) @@ -565,6 +574,9 @@ ((and (= tt "keyword") (or (= tv "true") (= tv "false") (= tv "begin"))) true) ((and (= tt "op") (or (= tv "(") (= tv "[") (= tv "{") (= tv "!"))) true) + ;; ~name or ?name (labeled/optional arg) — drop the + ;; label prefix and use the value as a positional arg. + ((and (= tt "op") (or (= tv "~") (= tv "?"))) true) (else false))))) (define parse-atom-postfix (fn () @@ -637,6 +649,20 @@ ((at-op? "!") (begin (advance-tok!) (list :deref (parse-atom-postfix)))) + ;; Labeled/optional arg ~name[:VAL] or ?name[:VAL] + ;; — drop the label prefix. With colon, parse VAL + ;; as the arg. Without, the variable named `name` + ;; (punning) becomes the arg. + ((or (at-op? "~") (at-op? "?")) + (begin + (advance-tok!) + (let ((nm (ocaml-tok-value + (consume! "ident" nil)))) + (cond + ((at-op? ":") + (begin (advance-tok!) + (parse-atom-postfix))) + (else (list :var nm)))))) (else (parse-atom-postfix))))) (begin (set! head (list :app head arg)) (loop)))))) (loop) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 2834ef4c..8723d255 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -1396,6 +1396,14 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 5142) (eval "(ocaml-run-program \"let h (a, b) (c, d) = a * b + c * d ;; h (1, 2) (3, 4)\")") +;; ── Labeled / optional args (label dropped) ────────────────── +(epoch 5150) +(eval "(ocaml-run \"let f ~x ~y = x + y in f ~x:3 ~y:7\")") +(epoch 5151) +(eval "(ocaml-run \"let f ~x ~y = x * y in let x = 4 in let y = 5 in f ~x ~y\")") +(epoch 5152) +(eval "(ocaml-run \"let f ?x ~y = x + y in f ?x:1 ~y:2\")") + EPOCHS OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -2219,6 +2227,11 @@ check 5140 "top let f (a, b) = a+b" '10' check 5141 "top let g x (a, b) mixed" '6' check 5142 "top let h (a, b) (c, d)" '14' +# ── Labeled / optional args (label dropped, positional) ───────── +check 5150 "f ~x:3 ~y:7 sum" '10' +check 5151 "f ~x ~y punning" '20' +check 5152 "f ?x:1 ~y:2 (no Some wrap)" '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 17e0c98c..7a4264be 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -407,6 +407,20 @@ _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 — basic labeled / optional argument syntax + (label dropped, positional semantics) (+3 tests, 562 total). Three + parser changes: + (1) `at-app-start?` returns true on op `~` or `?` so the app loop + keeps consuming labeled args; + (2) the app arg parser handles `~name:VAL` (drop label, parse VAL), + `?name:VAL` (same), and `~name` punning (treat as `(:var name)`); + (3) `try-consume-param!` drops `~` / `?` and treats the following + ident as a regular positional param name. + Order in the call must match definition order — we don't reorder + args by label name. Optional args don't auto-wrap in Some, so the + function body sees the raw value for `?x:V`. Lets us write + `let f ~x ~y = x + y in f ~x:3 ~y:7` and `let x = 4 in let y = 5 + in f ~x ~y` (punning). - 2026-05-09 Phase 5.1 — merge_sort.ml baseline (user-implemented mergesort, sorted sum = 44). Stress-tests `let (a, b) = split rest in (x :: a, y :: b)` (let-tuple destructuring inside a recursive