diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 2a5b0e4f..fc72ffdd 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -282,6 +282,56 @@ (list :pcons lhs (parse-pattern-cons)))) (else lhs))))) (set! parse-pattern (fn () (parse-pattern-cons))) + + (define peek-tok-at + (fn (n) + (if (< (+ idx n) tok-len) (nth tokens (+ idx n)) nil))) + + ;; Param consumption — matches ident, `_` (wildcard), or `()` + ;; (unit). Returns a fresh ident name or nil if no param at cursor. + (define wild-counter (list 0)) + (define try-consume-param! + (fn () + (cond + ((and (check-tok? "ident" nil) + (= (ocaml-tok-value (peek-tok)) "_")) + (begin + (advance-tok!) + (set-nth! wild-counter 0 (+ (nth wild-counter 0) 1)) + (str "__wild_" (nth wild-counter 0)))) + ((check-tok? "ident" nil) + (let ((nm (ocaml-tok-value (peek-tok)))) + (begin (advance-tok!) nm))) + ((and (at-op? "(") (= (ocaml-tok-value (peek-tok-at 1)) ")")) + (begin + (advance-tok!) (advance-tok!) + (set-nth! wild-counter 0 (+ (nth wild-counter 0) 1)) + (str "__unit_" (nth wild-counter 0)))) + ((and (at-op? "(") (= (ocaml-tok-type (peek-tok-at 1)) "ident")) + ;; (x : T) — typed param. Skip the `: T` part. + (let ((nm (ocaml-tok-value (peek-tok-at 1)))) + (begin + (advance-tok!) (advance-tok!) + (when (at-op? ":") + (begin + ;; Skip until matching `)`. + (let ((d 1)) + (begin + (define skip + (fn () + (cond + ((>= idx tok-len) nil) + ((at-op? "(") + (begin (set! d (+ d 1)) (advance-tok!) (skip))) + ((at-op? ")") + (cond + ((= d 1) nil) + (else (begin (set! d (- d 1)) (advance-tok!) (skip))))) + (else (begin (advance-tok!) (skip)))))) + (skip))))) + (consume! "op" ")") + nm))) + (else nil)))) (define parse-expr (fn () nil)) (define parse-expr-no-seq (fn () nil)) (define parse-tuple (fn () nil)) @@ -492,14 +542,10 @@ (begin (define collect-params - (fn - () - (when - (check-tok? "ident" nil) - (begin - (append! params (ocaml-tok-value (peek-tok))) - (advance-tok!) - (collect-params))))) + (fn () + (let ((nm (try-consume-param!))) + (when (not (= nm nil)) + (begin (append! params nm) (collect-params)))))) (collect-params) (when (= (len params) 0) @@ -522,14 +568,10 @@ (begin (define collect-params - (fn - () - (when - (check-tok? "ident" nil) - (begin - (append! params (ocaml-tok-value (peek-tok))) - (advance-tok!) - (collect-params))))) + (fn () + (let ((nm (try-consume-param!))) + (when (not (= nm nil)) + (begin (append! params nm) (collect-params)))))) (collect-params) (consume! "op" "=") (let @@ -806,11 +848,22 @@ (begin (define collect-params (fn () - (when (check-tok? "ident" nil) - (begin - (append! ps (ocaml-tok-value (peek-tok))) - (advance-tok!) - (collect-params))))) + (cond + ((check-tok? "ident" nil) + (begin + (append! ps (ocaml-tok-value (peek-tok))) + (advance-tok!) + (collect-params))) + ((and (at-op? "(") + (< (+ idx 1) tok-len) + (let ((t1 (nth tokens (+ idx 1)))) + (and (= (ocaml-tok-type t1) "op") + (= (ocaml-tok-value t1) ")")))) + (begin + (advance-tok!) (advance-tok!) + (append! ps (str "__unit_" idx)) + (collect-params))) + (else nil)))) (collect-params) (consume! "op" "=") (let ((expr-start (cur-pos))) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 5b87ac87..c204564f 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -702,9 +702,21 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 974) (eval "(ocaml-run \"Int.min 7 3\")") +;; ── Unit / wildcard parameters ────────────────────────────────── +(epoch 1000) +(eval "(ocaml-run \"let f () = 42 in f ()\")") +(epoch 1001) +(eval "(ocaml-run \"(fun () -> 99) ()\")") +(epoch 1002) +(eval "(ocaml-run \"let f _ = 1 in f 5\")") +(epoch 1003) +(eval "(ocaml-run-program \"let f () = 7;; f ()\")") +(epoch 1004) +(eval "(ocaml-run-program \"let g _ x = x + 1;; g 99 41\")") + EPOCHS -OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) +OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) check() { local epoch="$1" desc="$2" expected="$3" @@ -1112,6 +1124,13 @@ check 972 "Int.abs -5" '5' check 973 "Int.max" '7' check 974 "Int.min" '3' +# ── Unit / wildcard parameters ────────────────────────────────── +check 1000 "let f () = 42 in f ()" '42' +check 1001 "(fun () -> 99) ()" '99' +check 1002 "let f _ = 1 in f 5" '1' +check 1003 "top-level let f () =" '7' +check 1004 "wildcard top-level" '42' + 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 a7b756d1..9f051c29 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -343,6 +343,12 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 1 — unit `()` and wildcard `_` parameters in `let f () + = …` / `fun _ -> …` / `let f _ = …`. Parser helper `try-consume-param!` + now handles ident, wildcard `_` (renamed to `__wild_N`), unit `()` + (renamed to `__unit_N`), and typed `(x : T)` (signature skipped). + Same for top-level `parse-decl-let`. test.sh timeout extended from + 60s to 180s for the growing suite. 283/283 (+5). - 2026-05-08 Phase 6 — extended stdlib slice (+13 tests, 278 total). Host primitives exposed via `_string_*`, `_char_*`, `_int_*`, `_string_of_*` underscore-prefixed builtins so the OCaml-side