-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsimple-module.lisp
137 lines (115 loc) · 4.56 KB
/
simple-module.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
(uiop:define-package :vernacular/simple-module
(:use)
(:documentation "Reference implementation of a module.
Most languages will expand into `simple-module' forms.")
(:mix :serapeum :alexandria :vernacular/shadows :vernacular/types)
(:import-from :alexandria :mappend)
(:import-from :trivia)
(:import-from :vernacular/types :vernacular-error)
(:import-from :serapeum :op :car-safe :keep)
(:import-from :vernacular/module
:module-ref
:module-ref-ns
:module-exports)
(:import-from :vernacular/parsers :slurp-stream :slurp-file)
(:import-from :vernacular/importing :with-imports)
(:shadow :read-module :module-progn)
(:export
:read-module :module-progn
:simple-module
:static-exports))
(defpackage :vernacular/simple-module-user
(:use :vernacular/simple-module :vernacular/shadows))
(in-package :vernacular/simple-module)
(defcondition simple-module-error (vernacular-error)
((module :initarg :module)))
(defcondition ns-error (simple-module-error)
((ns :initarg :ns)))
(defcondition no-macros-please (simple-module-error)
()
(:report (cl:lambda (c s) (declare (ignore c))
(format s "Simple modules cannot export macros."))))
(defcondition not-exported (ns-error)
((name :initarg :name))
(:report (cl:lambda (c s)
(with-slots (module name) c
(format s "~s not exported by module ~a."
name module)))))
(defun not-exported (module name ns)
(error 'not-exported
:module module
:name name
:ns ns))
(defun read-module (source stream)
(declare (ignore source))
`(module-progn
,@(slurp-stream stream)))
(defmacro module-progn (&body body)
(let* ((export-forms (keep :export body :key #'car-safe))
(exports (mappend #'rest export-forms))
(import-forms (keep :import body :key #'car-safe))
(import-specs (mapcar #'rest import-forms))
(body (remove-if (lambda (form)
(or (member form export-forms)
(member form import-forms)))
body))
(module-form
`(simple-module ,exports
,@body)))
(reduce (curry #'list 'with-imports)
import-specs
:initial-value module-form
:from-end t)))
(defun static-exports (source)
(let* ((forms (slurp-file source))
(export-forms (keep :export forms :key #'car)))
(mappend #'rest export-forms)))
(defun export-expr (spec)
(cond ((eql 'macro-function (public-ns spec))
(error 'no-macros-please))
((eql 'setf (private-ns spec))
`(function (setf ,(private-name spec))))
(t (private-side spec))))
(defstruct-read-only simple-module
(exports nil :type list)
(exports-table (lambda (module key ns)
(error 'not-exported
:module module
:ns ns
:name key))
:type function))
(defmethod module-exports ((sm simple-module))
(simple-module-exports sm))
(defmethod module-ref-ns ((sm simple-module) name (ns (eql 'macro-function)))
(declare (ignore name))
(error 'no-macros-please))
(defmethod module-ref-ns ((sm simple-module) name ns)
(funcall (simple-module-exports-table sm) sm name ns))
(defmacro simple-module ((&rest exports) &body body)
(let ((export-keys (nub (mapcar #'public-side exports))))
`(make-simple-module
:exports ',export-keys
:exports-table (mlet ,exports
,@body))))
(defmacro mlet (exports &body body)
(setf exports (nub exports))
(with-unique-names (simple-module-lookup)
`(local*
,@body
;; The name for the lambda is just to make debugging easier.
(named-lambda ,simple-module-lookup (module key ns)
,(let ((by-ns (assort exports :key #'public-ns)))
`(case ns
,@(loop for group in by-ns
for ns = (public-ns (first group))
;; Wrap the ns in a list to keep the expansion
;; readable. (No sharp quote.)
collect `((,ns)
(case key
,@(loop for export in group
for key = (make-keyword (public-name export))
collect `((,key) ,(export-expr export)))
(otherwise
(not-exported module key ns)))))
(otherwise
(not-exported module key ns))))))))