scheme: Phase 8 — define-library + import (minimal) + 7 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
eval.sx adds module support:
(define-library NAME EXPR...)
Where EXPR is one of:
(export NAME ...)
(import LIB-NAME ...)
(begin BODY ...)
(import LIB-NAME ...)
Looks up each library by key, copies its exported names
into the current env.
Library values: {:scm-tag :library :name :exports :env}
Stored in scheme-library-registry keyed by joined library-name
(`(my math)` → `"my/math"`).
Library body runs in a FRESH standard env (each library is its
own namespace). Only :exports are visible after import; private
internal definitions stay in the library's env. Internal calls
between library functions use the library's env, so public-facing
exports can rely on private helpers.
Multiple imports work — each library is independent.
NOT yet supported: cond-expand, include, include-library-
declarations, renaming (`(only ...)`, `(except ...)`, `(prefix ...)`,
`(rename ...)`). Standard R7RS modules use these but the core
two-operation flow (define-library / import) covers most everyday
module use.
7 tests: single export, multi-export, private-not-visible,
internal-calls-private, two-libs-both-imported, unknown-lib-error,
single-symbol library name.
296 total Scheme tests (62+23+49+78+25+20+13+10+9+7).
Phases done: 1, 2, 3, 3.5, 4, 5abc, 6ab, 7, 8, 9, 10.
Deferred: 6c (hygiene/scope-set — research-grade), 11 (conformance).
This commit is contained in:
@@ -568,6 +568,139 @@
|
||||
:rules (rest args)
|
||||
:env env}))))
|
||||
|
||||
;; ── define-library + import (R7RS Phase 8) ─────────────────────
|
||||
;;
|
||||
;; A library is a tagged value with exports + an env where the body
|
||||
;; was evaluated. The global registry maps a string key (joined from
|
||||
;; the library-name list) to the library value.
|
||||
;;
|
||||
;; (define-library NAME EXPR...) where EXPR can be:
|
||||
;; (export NAME ...)
|
||||
;; (import LIB-NAME ...)
|
||||
;; (begin BODY ...)
|
||||
;; cond-expand, include, include-library-declarations: deferred.
|
||||
;;
|
||||
;; (import LIB-NAME ...) at the top level: for each named library,
|
||||
;; look up its exports and bind them in the current env.
|
||||
|
||||
(define scheme-library-registry {})
|
||||
|
||||
(define scm-lib-key
|
||||
(fn (name)
|
||||
(cond
|
||||
((string? name) name)
|
||||
((list? name) (scm-join-strings name "/"))
|
||||
(:else (error "library name must be symbol or list")))))
|
||||
|
||||
(define scm-join-strings
|
||||
(fn (xs sep)
|
||||
(cond
|
||||
((or (nil? xs) (= (length xs) 0)) "")
|
||||
((= (length xs) 1) (first xs))
|
||||
(:else
|
||||
(str (first xs) sep (scm-join-strings (rest xs) sep))))))
|
||||
|
||||
(define scm-library?
|
||||
(fn (v)
|
||||
(and (dict? v) (= (get v :scm-tag) :library))))
|
||||
|
||||
(define scm-collect-exports
|
||||
(fn (forms acc)
|
||||
(cond
|
||||
((or (nil? forms) (= (length forms) 0)) acc)
|
||||
(:else
|
||||
(let ((form (first forms)))
|
||||
(cond
|
||||
((and (list? form) (>= (length form) 1)
|
||||
(string? (first form)) (= (first form) "export"))
|
||||
(scm-collect-exports (rest forms)
|
||||
(scm-list-concat acc (rest form))))
|
||||
(:else (scm-collect-exports (rest forms) acc))))))))
|
||||
|
||||
(define scm-run-library-body
|
||||
(fn (forms env)
|
||||
(cond
|
||||
((or (nil? forms) (= (length forms) 0)) nil)
|
||||
(:else
|
||||
(let ((form (first forms)))
|
||||
(cond
|
||||
;; export/import declarations: handled separately
|
||||
((and (list? form) (>= (length form) 1)
|
||||
(string? (first form))
|
||||
(or (= (first form) "export")
|
||||
(= (first form) "import")))
|
||||
(cond
|
||||
((= (first form) "import")
|
||||
(begin
|
||||
(scm-do-import (rest form) env)
|
||||
(scm-run-library-body (rest forms) env)))
|
||||
(:else (scm-run-library-body (rest forms) env))))
|
||||
;; begin: evaluate body
|
||||
((and (list? form) (>= (length form) 1)
|
||||
(string? (first form)) (= (first form) "begin"))
|
||||
(begin
|
||||
(scheme-eval-body (rest form) env)
|
||||
(scm-run-library-body (rest forms) env)))
|
||||
(:else (scm-run-library-body (rest forms) env))))))))
|
||||
|
||||
(define scm-do-import
|
||||
(fn (lib-names env)
|
||||
(cond
|
||||
((or (nil? lib-names) (= (length lib-names) 0)) nil)
|
||||
(:else
|
||||
(let ((key (scm-lib-key (first lib-names))))
|
||||
(cond
|
||||
((not (dict-has? scheme-library-registry key))
|
||||
(error (str "import: unknown library: " key)))
|
||||
(:else
|
||||
(begin
|
||||
(let ((lib (get scheme-library-registry key)))
|
||||
(scm-copy-exports! env
|
||||
(get lib :exports)
|
||||
(get lib :env)))
|
||||
(scm-do-import (rest lib-names) env)))))))))
|
||||
|
||||
(define scm-copy-exports!
|
||||
(fn (target-env exports source-env)
|
||||
(cond
|
||||
((or (nil? exports) (= (length exports) 0)) nil)
|
||||
(:else
|
||||
(let ((name (first exports)))
|
||||
(cond
|
||||
((refl-env-has? source-env name)
|
||||
(begin
|
||||
(scheme-env-bind! target-env name
|
||||
(refl-env-lookup source-env name))
|
||||
(scm-copy-exports! target-env (rest exports) source-env)))
|
||||
(:else
|
||||
(error (str "import: export not defined: " name)))))))))
|
||||
|
||||
(scheme-define-op! "define-library"
|
||||
(fn (args env)
|
||||
(cond
|
||||
((< (length args) 1)
|
||||
(error "define-library: expects (define-library NAME body...)"))
|
||||
(:else
|
||||
(let ((lib-name (first args))
|
||||
(body (rest args)))
|
||||
(let ((lib-env (scheme-standard-env))
|
||||
(exports (scm-collect-exports body (list)))
|
||||
(key (scm-lib-key lib-name)))
|
||||
(begin
|
||||
(scm-run-library-body body lib-env)
|
||||
(dict-set! scheme-library-registry key
|
||||
{:scm-tag :library
|
||||
:name lib-name
|
||||
:exports exports
|
||||
:env lib-env})
|
||||
key)))))))
|
||||
|
||||
(scheme-define-op! "import"
|
||||
(fn (args env)
|
||||
(begin
|
||||
(scm-do-import args env)
|
||||
nil)))
|
||||
|
||||
;; ── define-record-type (R7RS Phase 9) ──────────────────────────
|
||||
;;
|
||||
;; (define-record-type NAME
|
||||
|
||||
73
lib/scheme/tests/modules.sx
Normal file
73
lib/scheme/tests/modules.sx
Normal file
@@ -0,0 +1,73 @@
|
||||
;; lib/scheme/tests/modules.sx — define-library + import.
|
||||
|
||||
(define scm-mod-pass 0)
|
||||
(define scm-mod-fail 0)
|
||||
(define scm-mod-fails (list))
|
||||
|
||||
(define
|
||||
scm-mod-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! scm-mod-pass (+ scm-mod-pass 1))
|
||||
(begin
|
||||
(set! scm-mod-fail (+ scm-mod-fail 1))
|
||||
(append! scm-mod-fails {:name name :actual actual :expected expected})))))
|
||||
|
||||
(define
|
||||
scm-mod
|
||||
(fn
|
||||
(src)
|
||||
(scheme-eval-program (scheme-parse-all src) (scheme-standard-env))))
|
||||
|
||||
;; ── Basic define-library + import ───────────────────────────────
|
||||
|
||||
(scm-mod-test
|
||||
"simple lib: sq exported"
|
||||
(scm-mod
|
||||
"(define-library (my math)\n (export sq)\n (begin (define (sq x) (* x x))))\n (import (my math))\n (sq 5)")
|
||||
25)
|
||||
(scm-mod-test
|
||||
"lib: multiple exports"
|
||||
(scm-mod
|
||||
"(define-library (my math)\n (export sq cube)\n (begin\n (define (sq x) (* x x))\n (define (cube x) (* x x x))))\n (import (my math))\n (list (sq 5) (cube 3))")
|
||||
(list 25 27))
|
||||
(scm-mod-test
|
||||
"lib: single-symbol name"
|
||||
(scm-mod
|
||||
"(define-library (utils)\n (export greet)\n (begin (define (greet name) (string-append \"hi \" name))))\n (import (utils))\n (string=? (greet \"world\") \"hi world\")")
|
||||
true)
|
||||
|
||||
;; ── Unexported names are not visible ───────────────────────────
|
||||
|
||||
(scm-mod-test
|
||||
"lib: private name not exported"
|
||||
(scm-mod
|
||||
"(define-library (my math)\n (export sq)\n (begin\n (define (sq x) (* x x))\n (define (private-helper x) (+ x 1))))\n (import (my math))\n (guard (e (else 'unbound)) private-helper)")
|
||||
"unbound")
|
||||
|
||||
;; ── Library calls its own internals ─────────────────────────────
|
||||
|
||||
(scm-mod-test
|
||||
"lib: internal calls private fn"
|
||||
(scm-mod
|
||||
"(define-library (my math)\n (export public-add1)\n (begin\n (define (private-inc x) (+ x 1))\n (define (public-add1 x) (private-inc x))))\n (import (my math))\n (public-add1 41)")
|
||||
42)
|
||||
|
||||
;; ── Two libs, both imported ────────────────────────────────────
|
||||
|
||||
(scm-mod-test
|
||||
"two libs: both imported"
|
||||
(scm-mod
|
||||
"(define-library (a) (export af) (begin (define (af) 1)))\n (define-library (b) (export bf) (begin (define (bf) 2)))\n (import (a) (b))\n (+ (af) (bf))")
|
||||
3)
|
||||
|
||||
;; ── Unknown library import errors ──────────────────────────────
|
||||
|
||||
(scm-mod-test
|
||||
"import: unknown lib errors"
|
||||
(scm-mod "(guard (e (else 'unknown-lib)) (import (no such lib)))")
|
||||
"unknown-lib")
|
||||
|
||||
(define scm-mod-tests-run! (fn () {:total (+ scm-mod-pass scm-mod-fail) :passed scm-mod-pass :failed scm-mod-fail :fails scm-mod-fails}))
|
||||
Reference in New Issue
Block a user