ocaml: phase 4 functors + module aliases (+5 tests, 225 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s

Parser: module F (M) (N) ... = struct DECLS end -> (:functor-def NAME
PARAMS DECLS). module N = expr (non-struct) -> (:module-alias NAME
BODY-SRC). Functor params accept (P) or (P : Sig) — signatures
parsed-and-skipped via skip-optional-sig.

Eval: ocaml-make-functor builds curried host-SX closures from module
dicts to a module dict. ocaml-resolve-module-path extended for :app so
F(A), F(A)(B), and Outer.Inner all resolve to dicts.

Phase 4 LOC ~290 cumulative (still well under 2000).
This commit is contained in:
2026-05-08 08:44:54 +00:00
parent d45e653a87
commit 5603ecc3a6
4 changed files with 211 additions and 27 deletions

View File

@@ -67,9 +67,11 @@
(fn (env name val)
(cons (list name val) env)))
;; Resolve a module path like `M` or `M.Sub` to a dict. Mirrors the
;; field-access escape hatch where `(:con NAME)` is treated as an env
;; lookup rather than a nullary ctor.
;; Resolve a module path / functor-application expression to a module dict.
;; Mirrors the field-access escape hatch where `(:con NAME)` is treated as
;; an env lookup rather than a nullary ctor; also handles `(:app FN ARG)`
;; for functor applications, `(:field …)` for sub-modules, and `(:var …)`
;; for lower-case bindings.
(define ocaml-resolve-module-path
(fn (path-expr env)
(let ((tag (ocaml-tag-of path-expr)))
@@ -79,12 +81,22 @@
((ocaml-env-has? env (nth path-expr 1))
(ocaml-env-lookup env (nth path-expr 1)))
(else (error (str "ocaml-eval: unknown module " (nth path-expr 1))))))
((= tag "var")
(cond
((ocaml-env-has? env (nth path-expr 1))
(ocaml-env-lookup env (nth path-expr 1)))
(else (error (str "ocaml-eval: unknown module-var " (nth path-expr 1))))))
((= tag "field")
(let ((parent (ocaml-resolve-module-path (nth path-expr 1) env)))
(cond
((dict? parent) (get parent (nth path-expr 2)))
(else (error
(str "ocaml-eval: not a module on path: " parent))))))
((= tag "app")
(let ((fn-val (ocaml-resolve-module-path (nth path-expr 1) env))
(arg-val (ocaml-resolve-module-path (nth path-expr 2) env)))
(fn-val arg-val)))
((= tag "unit") {})
(else (ocaml-eval path-expr env))))))
;; Merge a dict's bindings into an env (used by `open`/`include`).
@@ -470,6 +482,21 @@
(else (error
(str "ocaml-eval: unknown AST tag " tag)))))))
;; ocaml-make-functor — build a curried host-SX closure that accepts
;; argument modules (one per param) and returns the resulting module dict
;; produced by evaluating the functor's body.
(define ocaml-make-functor
(fn (params decls captured-env)
(cond
((= (len params) 1)
(fn (arg-mod)
(ocaml-eval-module decls
(ocaml-env-extend captured-env (first params) arg-mod))))
(else
(fn (arg-mod)
(ocaml-make-functor (rest params) decls
(ocaml-env-extend captured-env (first params) arg-mod)))))))
;; ocaml-eval-module — evaluate a list of decls in a fresh sub-env layered
;; on top of the parent. Returns a dict mapping each declared name to its
;; value. Used by `module M = struct DECLS end`.
@@ -520,6 +547,21 @@
(begin
(set! env (ocaml-env-extend env mname mod-val))
(set! result (merge result (dict mname mod-val)))))))
((= tag "functor-def")
(let ((mname (nth decl 1))
(mparams (nth decl 2))
(mdecls (nth decl 3)))
(let ((fn-val (ocaml-make-functor mparams mdecls env)))
(begin
(set! env (ocaml-env-extend env mname fn-val))
(set! result (merge result (dict mname fn-val)))))))
((= tag "module-alias")
(let ((mname (nth decl 1)) (body-src (nth decl 2)))
(let ((body-expr (ocaml-parse body-src)))
(let ((mod-val (ocaml-resolve-module-path body-expr env)))
(begin
(set! env (ocaml-env-extend env mname mod-val))
(set! result (merge result (dict mname mod-val))))))))
((= tag "open")
(let ((mod-val (ocaml-resolve-module-path (nth decl 1) env)))
(cond
@@ -605,6 +647,26 @@
(begin
(set! env (ocaml-env-extend env mname mod-val))
(set! last mod-val)))))
((= tag "functor-def")
;; module F (M1) (M2) ... = struct DECLS end — bind F
;; to a curried function from module dicts to a module
;; dict.
(let ((mname (nth decl 1))
(mparams (nth decl 2))
(mdecls (nth decl 3)))
(let ((functor-val
(ocaml-make-functor mparams mdecls env)))
(begin
(set! env (ocaml-env-extend env mname functor-val))
(set! last functor-val)))))
((= tag "module-alias")
;; module N = M / module N = F(A) / module N = M.Sub
(let ((mname (nth decl 1)) (body-src (nth decl 2)))
(let ((body-expr (ocaml-parse body-src)))
(let ((mod-val (ocaml-resolve-module-path body-expr env)))
(begin
(set! env (ocaml-env-extend env mname mod-val))
(set! last mod-val))))))
((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)))