src/utilities.lisp (once-only): Ensure that the BINDS argument is a list.
[sod] / src / module-impl.lisp
1 ;;; -*-lisp-*-
2 ;;;
3 ;;; Module protocol implementation
4 ;;;
5 ;;; (c) 2009 Straylight/Edgeware
6 ;;;
7
8 ;;;----- Licensing notice ---------------------------------------------------
9 ;;;
10 ;;; This file is part of the Sensible Object Design, an object system for C.
11 ;;;
12 ;;; SOD is free software; you can redistribute it and/or modify
13 ;;; it under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 2 of the License, or
15 ;;; (at your option) any later version.
16 ;;;
17 ;;; SOD is distributed in the hope that it will be useful,
18 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;;; GNU General Public License for more details.
21 ;;;
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with SOD; if not, write to the Free Software Foundation,
24 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 (cl:in-package #:sod)
27
28 ;;;--------------------------------------------------------------------------
29 ;;; Module basics.
30
31 (defmethod module-import ((module module))
32 (dolist (item (module-items module))
33 (module-import item)))
34
35 (defmethod add-to-module ((module module) item)
36 (setf (module-items module)
37 (nconc (module-items module) (list item)))
38 (module-import item))
39
40 (defmethod shared-initialize :after ((module module) slot-names &key pset)
41 "Tick off known properties on the property set."
42 (declare (ignore slot-names))
43 (dolist (prop '(:guard))
44 (get-property pset prop nil)))
45
46 (defmethod finalize-module ((module module))
47 (let* ((pset (module-pset module))
48 (class (get-property pset :module-class :symbol 'module)))
49
50 ;; Always call `change-class', even if it's the same one; this will
51 ;; exercise the property-set fiddling in `shared-initialize' and we can
52 ;; catch unknown-property errors.
53 (change-class module class :state t :pset pset)
54 (check-unused-properties pset)))
55
56 ;;;--------------------------------------------------------------------------
57 ;;; Module objects.
58
59 (defvar-unbound *module-map*
60 "Hash table mapping true names to module objects.")
61 (define-clear-the-decks reset-module-map
62 (setf *module-map* (make-hash-table :test #'equal)))
63
64 (defun build-module
65 (name thunk &key (truename (probe-file name)) location)
66 "Construct a new module.
67
68 This is the functionality underlying `define-module': see that macro for
69 full information."
70
71 ;; Check for an import cycle.
72 (when truename
73 (let ((existing (gethash truename *module-map*)))
74 (cond ((null existing))
75 ((eq (module-state existing) t)
76 (when (plusp (module-errors existing))
77 (error "Module `~A' contains errors" name))
78 (return-from build-module existing))
79 (t
80 (error "Module `~A' already being imported at ~A"
81 name (module-state existing))))))
82
83 ;; Construct the new module.
84 (let ((*module* (make-instance 'module
85 :name (pathname name)
86 :state (file-location location))))
87 (when truename
88 (setf (gethash truename *module-map*) *module*))
89 (unwind-protect
90 (with-module-environment ()
91 (module-import *builtin-module*)
92 (funcall thunk)
93 (finalize-module *module*)
94 *module*)
95 (when (and truename (not (eq (module-state *module*) t)))
96 (remhash truename *module-map*)))))
97
98 (defun call-with-module-environment (thunk &optional (module *module*))
99 "Invoke THUNK with bindings for the module variables in scope.
100
101 This is the guts of `with-module-environment', which you should probably
102 use instead."
103 (progv
104 (mapcar #'car *module-bindings-alist*)
105 (module-variables module)
106 (handler-bind ((error (lambda (cond)
107 (declare (ignore cond))
108 (incf (slot-value module 'errors))
109 :decline)))
110 (unwind-protect (funcall thunk)
111 (setf (module-variables module)
112 (mapcar (compose #'car #'symbol-value)
113 *module-bindings-alist*))))))
114
115 (defun call-with-temporary-module (thunk)
116 "Invoke THUNK in the context of a temporary module, returning its values.
117
118 This is mainly useful for testing things which depend on module variables.
119 This is the functionality underlying `with-temporary-module'."
120 (let ((*module* (make-instance 'module
121 :name "<temp>"
122 :state nil)))
123 (with-module-environment ()
124 (module-import *builtin-module*)
125 (funcall thunk))))
126
127 ;;;--------------------------------------------------------------------------
128 ;;; Type definitions.
129
130 (export 'type-item)
131 (defclass type-item ()
132 ((name :initarg :name :type string :reader type-name))
133 (:documentation
134 "A note that a module exports a type.
135
136 We can only export simple types, so we only need to remember the name.
137 The magic simple-type cache will ensure that we get the same type object
138 when we do the import."))
139
140 (defmethod module-import ((item type-item))
141 (let* ((name (type-name item))
142 (def (gethash name *module-type-map*))
143 (type (make-simple-type name)))
144 (cond ((not def)
145 (setf (gethash name *module-type-map*) type))
146 ((not (eq def type))
147 (error "Conflicting types `~A'" name)))))
148
149 (defmethod module-import ((class sod-class))
150 (record-sod-class class))
151
152 ;;;--------------------------------------------------------------------------
153 ;;; Code fragments.
154
155 (export '(c-fragment c-fragment-text))
156 (defclass c-fragment ()
157 ((location :initarg :location :type file-location :reader file-location)
158 (text :initarg :text :type string :reader c-fragment-text))
159 (:documentation
160 "Represents a fragment of C code to be written to an output file.
161
162 A C fragment is aware of its original location, and will bear proper
163 `#line' markers when written out."))
164
165 (defun output-c-excursion (stream location func)
166 "Invoke FUNC surrounding it by writing #line markers to STREAM.
167
168 The first marker describes LOCATION; the second refers to the actual
169 output position in STREAM. If LOCATION doesn't provide a line number then
170 no markers are output after all. If the output stream isn't
171 position-aware then no final marker is output.
172
173 FUNC is passed the output stream as an argument. Complicated games may be
174 played with interposed streams. Try not to worry about it."
175
176 (flet ((doit (stream)
177 (let* ((location (file-location location))
178 (line (file-location-line location))
179 (filename (file-location-filename location)))
180 (cond (line
181 (when (typep stream 'position-aware-stream)
182 (format stream "~&#line ~D~@[ ~S~]~%" line filename))
183 (funcall func stream)
184 (when (typep stream 'position-aware-stream)
185 (fresh-line stream)
186 (format stream "#line ~D ~S~%"
187 (1+ (position-aware-stream-line stream))
188 (let ((path (stream-pathname stream)))
189 (if path (namestring path)
190 "<sod-output>")))))
191 (t
192 (funcall func stream))))))
193 (print-ugly-stuff stream #'doit)))
194
195 (defmethod print-object ((fragment c-fragment) stream)
196 (let ((text (c-fragment-text fragment))
197 (location (file-location fragment)))
198 (if *print-escape*
199 (print-unreadable-object (fragment stream :type t)
200 (when location
201 (format stream "~A " location))
202 (cond ((< (length text) 40)
203 (prin1 text stream) stream)
204 (t
205 (prin1 (subseq text 0 37) stream)
206 (write-string "..." stream))))
207 (output-c-excursion stream location
208 (lambda (stream) (write-string text stream))))))
209
210 (defmethod make-load-form ((fragment c-fragment) &optional environment)
211 (make-load-form-saving-slots fragment :environment environment))
212
213 (export '(code-fragment-item code-fragment code-fragment-reason
214 code-fragment-name code-fragment-constraints))
215 (defclass code-fragment-item ()
216 ((fragment :initarg :fragment :type (or string c-fragment)
217 :reader code-fragment)
218 (reason :initarg :reason :type keyword :reader code-fragment-reason)
219 (name :initarg :name :type t :reader code-fragment-name)
220 (constraints :initarg :constraints :type list
221 :reader code-fragment-constraints))
222 (:documentation
223 "A plain fragment of C to be dropped in at top-level."))
224
225 ;;;--------------------------------------------------------------------------
226 ;;; File searching.
227
228 (export '*module-dirs*)
229 (defparameter *module-dirs* nil
230 "A list of directories (as pathname designators) to search for files.
231
232 Both SOD module files and Lisp extension files are searched for in this
233 list. The search works by merging the requested pathname with each
234 element of this list in turn. The list is prefixed by the pathname of the
235 requesting file, so that it can refer to other files relative to wherever
236 it was found.
237
238 See `find-file' for the grubby details.")
239
240 (export 'find-file)
241 (defun find-file (scanner name what thunk)
242 "Find a file called NAME on the module search path, and call THUNK on it.
243
244 The file is searched for relative to the SCANNER's current file, and also
245 in the directories mentioned in the `*module-dirs*' list. If the file is
246 found, then THUNK is invoked with two arguments: the name we used to find
247 it (which might be relative to the starting directory) and the truename
248 found by `probe-file'.
249
250 If the file wasn't found, or there was some kind of error, then an error
251 is signalled; WHAT should be a noun phrase describing the kind of thing we
252 were looking for, suitable for inclusion in the error message.
253
254 While `find-file' establishes condition handlers for its own purposes,
255 THUNK is not invoked with any additional handlers defined."
256
257 (handler-case
258 (dolist (dir (cons (pathname (scanner-filename scanner)) *module-dirs*)
259 (values nil nil))
260 (let* ((path (merge-pathnames name dir))
261 (probe (probe-file path)))
262 (when probe
263 (return (values path probe)))))
264 (file-error (error)
265 (error "Error searching for ~A ~S: ~A" what (namestring name) error))
266 (:no-error (path probe)
267 (cond ((null path)
268 (error "Failed to find ~A ~S" what (namestring name)))
269 (t
270 (funcall thunk path probe))))))
271
272 ;;;----- That's all, folks --------------------------------------------------