From ad252088c3aa9f45c3f3a26cf969b778f8c0b628 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 15:11:45 +0000 Subject: [PATCH] =?UTF-8?q?ocaml:=20phase=204=20module=20type=20S=20=3D=20?= =?UTF-8?q?sig=20=E2=80=A6=20end=20parser=20(+3=20tests,=20389=20total)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit module type S = sig DECLS end is parsed-and-discarded — sig..end balanced skipping in parse-decl-module-type. AST (:module-type-def NAME). Runtime no-op (signatures are type-level only). Allows real OCaml programs with module type decls to parse and run without stripping the sig blocks. --- lib/ocaml/eval.sx | 4 ++++ lib/ocaml/parser.sx | 43 +++++++++++++++++++++++++++++++++++++++++++ lib/ocaml/test.sh | 13 +++++++++++++ plans/ocaml-on-sx.md | 12 +++++++++--- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index fe38fed5..6f808939 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -704,6 +704,7 @@ (set! result (merge result (dict mname mod-val)))))))) ((= tag "type-def") nil) ((= tag "exception-def") nil) + ((= tag "module-type-def") nil) ((= tag "open") (let ((mod-val (ocaml-resolve-module-path (nth decl 1) env))) (cond @@ -884,6 +885,9 @@ ;; exception E [of T] — purely declarative; raise+match ;; already work on tagged ctor values. nil) + ((= tag "module-type-def") + ;; module type S = sig … end — no-op at runtime. + nil) ((or (= tag "open") (= tag "include")) ;; open M / include M — bring M's bindings into scope. (let ((mod-val (ocaml-resolve-module-path (nth decl 1) env))) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 228059fb..6befa7a3 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -1282,10 +1282,53 @@ (else (begin (advance-tok!) (skip)))))) (skip))))))) + ;; module type S = sig ... end + ;; Parsed-and-discarded (signatures are type-level only). Returns + ;; a (:module-type-def NAME) marker for the eval loop to ignore. + (define + parse-decl-module-type + (fn () + (advance-tok!) ;; "type" + (let ((name (ocaml-tok-value (consume! "ctor" nil)))) + (begin + (consume! "op" "=") + (cond + ((at-kw? "sig") + (begin + (advance-tok!) + (let ((depth 1)) + (begin + (define skip + (fn () + (cond + ((>= idx tok-len) nil) + ((= (ocaml-tok-type (peek-tok)) "eof") nil) + ((or (at-kw? "sig") (at-kw? "struct") (at-kw? "begin")) + (begin (set! depth (+ depth 1)) (advance-tok!) (skip))) + ((at-kw? "end") + (cond + ((= depth 1) nil) + (else + (begin (set! depth (- depth 1)) (advance-tok!) (skip))))) + (else (begin (advance-tok!) (skip)))))) + (skip) + (consume! "keyword" "end"))))) + (else + ;; module type S = AnotherSig — skip-to-boundary. + (skip-to-boundary!))) + (list :module-type-def name))))) + (define parse-decl-module (fn () (advance-tok!) + (cond + ((at-kw? "type") (parse-decl-module-type)) + (else (parse-decl-module-rest))))) + + (define + parse-decl-module-rest + (fn () (let ((name (ocaml-tok-value (consume! "ctor" nil))) (params (list))) (begin diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 3ee3766e..d7d14a13 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -956,6 +956,14 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 2603) (eval "(ocaml-run \"match { x = 5; y = 2 } with | { x = 1; y = y } -> y | _ -> 0\")") +;; ── module type S = sig … end ───────────────────────────────── +(epoch 2700) +(eval "(ocaml-parse-program \"module type S = sig val x : int val f : int -> int end\")") +(epoch 2701) +(eval "(ocaml-run-program \"module type S = sig val x : int end ;; module M = struct let x = 42 end ;; M.x\")") +(epoch 2702) +(eval "(ocaml-parse-program \"module type EMPTY = sig end\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1512,6 +1520,11 @@ check 2601 "match record name+age" '30' check 2602 "match record literal x=1" '2' check 2603 "match record literal fail" '0' +# ── module type S = sig … end ────────────────────────────────── +check 2700 "module type S parses" '("module-type-def" "S")' +check 2701 "module type then module" '42' +check 2702 "module type EMPTY" '("module-type-def" "EMPTY")' + 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 ae745d26..f9653a9b 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -191,9 +191,10 @@ SX CEK evaluator (both JS and OCaml hosts) - [x] `module M = struct let x = 1 let f y = x + y end` → SX dict `{"x" 1 "f" }`. -- [~] `module type S = sig val x : int val f : int -> int end` — signature - annotations are parsed-and-skipped (`skip-optional-sig`); typed - checking deferred to Phase 5. +- [x] `module type S = sig val x : int val f : int -> int end` parses + via `parse-decl-module-type`. Signature contents are skipped + (sig..end nesting tracked) — runtime no-op since types are + structural. AST: `(:module-type-def NAME)`. - [x] `module M : S = struct ... end` — coercive sealing (signature ignored). - [x] `functor (M : S) -> struct ... end` via shorthand `module F (M) = …`. - [x] `module F = Functor(Base)` — functor application; multi-param via @@ -376,6 +377,11 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 4 — `module type S = sig … end` parser (+3 tests, + 389 total). Signatures are parsed-and-discarded — sig..end balanced + skipping. AST: `(:module-type-def NAME)`. Runtime no-op (signatures + are type-level). Allows real OCaml code with module type decls to + parse and run without removing the sig blocks. - 2026-05-08 Phase 1+3 — record patterns `{ f = pat; … }` (+4 tests, 386 total). Parser adds `(:precord (FIELD PAT) …)` alongside the existing record-literal `{` handling. Eval matches against