3 ;;; Module protocol implementation
5 ;;; (c) 2009 Straylight/Edgeware
8 ;;;----- Licensing notice ---------------------------------------------------
10 ;;; This file is part of the Sensble Object Design, an object system for C.
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.
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.
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.
28 ;;;--------------------------------------------------------------------------
31 (defmethod module-import ((module module))
32 (dolist (item (module-items module))
33 (module-import item)))
35 (defmethod add-to-module ((module module) item)
36 (setf (module-items module)
37 (nconc (module-items module) (list item)))
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)))
46 (defmethod finalize-module ((module module))
47 (let* ((pset (module-pset module))
48 (class (get-property pset :lisp-class :symbol 'module)))
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)
57 ;;;--------------------------------------------------------------------------
60 (defparameter *module-map* (make-hash-table :test #'equal)
61 "Hash table mapping true names to module objects.")
64 (name thunk &key (truename (probe-file name)) location)
65 "Construct a new module.
67 This is the functionality underlying `define-module': see that macro for
70 ;; Check for an import cycle.
72 (let ((existing (gethash truename *module-map*)))
73 (cond ((null existing))
74 ((eq (module-state existing) t)
75 (return-from build-module existing))
77 (error "Module ~A already being imported at ~A"
78 name (module-state existing))))))
80 ;; Construct the new module.
81 (let ((*module* (make-instance 'module
83 :state (file-location location))))
85 (setf (gethash truename *module-map*) *module*))
87 (call-with-module-environment (lambda ()
88 (module-import *builtin-module*)
90 (finalize-module *module*)))
91 (when (and truename (not (eq (module-state *module*) t)))
92 (remhash truename *module-map*)))))
94 (defun call-with-temporary-module (thunk)
95 "Invoke THUNK in the context of a temporary module, returning its values.
97 This is mainly useful for testing things which depend on module variables.
98 This is the functionality underlying `with-temporary-module'."
99 (let ((*module* (make-instance 'module
102 (call-with-module-environment
104 (module-import *builtin-module*)
107 ;;;--------------------------------------------------------------------------
108 ;;; Type definitions.
111 (defclass type-item ()
112 ((name :initarg :name :type string :reader type-name))
114 "A note that a module exports a type.
116 We can only export simple types, so we only need to remember the name.
117 The magic simple-type cache will ensure that we get the same type object
118 when we do the import."))
120 (defmethod module-import ((item type-item))
121 (let* ((name (type-name item))
122 (def (gethash name *module-type-map*))
123 (type (make-simple-type name)))
125 (setf (gethash name *module-type-map*) type))
127 (error "Conflicting types `~A'" name)))))
129 (defmethod module-import ((class sod-class))
130 (record-sod-class class))
132 ;;;--------------------------------------------------------------------------
136 (defclass c-fragment ()
137 ((location :initarg :location :type file-location
138 :accessor c-fragment-location)
139 (text :initarg :text :type string :accessor c-fragment-text))
141 "Represents a fragment of C code to be written to an output file.
143 A C fragment is aware of its original location, and will bear proper #line
144 markers when written out."))
146 (defun output-c-excursion (stream location thunk)
147 "Invoke THUNK surrounding it by writing #line markers to STREAM.
149 The first marker describes LOCATION; the second refers to the actual
150 output position in STREAM. If LOCATION doesn't provide a line number then
151 no markers are output after all. If the output stream isn't
152 position-aware then no final marker is output."
154 (let* ((location (file-location location))
155 (line (file-location-line location))
156 (filename (file-location-filename location)))
158 (format stream "~&#line ~D~@[ ~S~]~%" line filename)
160 (when (typep stream 'position-aware-stream)
162 (format stream "~&#line ~D ~S~%"
163 (1+ (position-aware-stream-line stream))
164 (namestring (stream-pathname stream)))))
168 (defmethod print-object ((fragment c-fragment) stream)
169 (let ((text (c-fragment-text fragment))
170 (location (c-fragment-location fragment)))
172 (print-unreadable-object (fragment stream :type t)
174 (format stream "~A " location))
175 (cond ((< (length text) 40)
176 (prin1 text stream) stream)
178 (prin1 (subseq text 0 37) stream)
179 (write-string "..." stream))))
180 (output-c-excursion stream location
181 (lambda () (write-string text stream))))))
183 (defmethod make-load-form ((fragment c-fragment) &optional environment)
184 (make-load-form-saving-slots fragment :environment environment))
186 (export 'code-fragment-item)
187 (defclass code-fragment-item ()
188 ((fragment :initarg :fragment :type c-fragment :reader code-fragment)
189 (reason :initarg :reason :type keyword :reader code-fragment-reason)
190 (name :initarg :name :type t :reader code-fragment-name)
191 (constraints :initarg :constraints :type list
192 :reader code-fragment-constraints))
194 "A plain fragment of C to be dropped in at top-level."))
196 (defmacro define-fragment ((reason name) &body things)
197 (categorize (thing things)
198 ((constraints (listp thing))
199 (frags (typep thing '(or string c-fragment))))
201 (error "Missing code fragment"))
203 (error "Multiple code fragments"))
206 (make-instance 'code-fragment-item
207 :fragment ',(car frags)
210 :constraints (list ,@(mapcar (lambda (constraint)
211 (cons 'list constraint))
214 ;;;--------------------------------------------------------------------------
217 (export '*module-dirs*)
218 (defparameter *module-dirs* nil
219 "A list of directories (as pathname designators) to search for files.
221 Both SOD module files and Lisp extension files are searched for in this
222 list. The search works by merging the requested pathname with each
223 element of this list in turn. The list is prefixed by the pathname of the
224 requesting file, so that it can refer to other files relative to wherever
227 See `find-file' for the grubby details.")
230 (defun find-file (scanner name what thunk)
231 "Find a file called NAME on the module search path, and call THUNK on it.
233 The file is searched for relative to the SCANNER's current file, and also
234 in the directories mentioned in the `*module-dirs*' list. If the file is
235 found, then THUNK is invoked with two arguments: the name we used to find
236 it (which might be relative to the starting directory) and the truename
237 found by `probe-file'.
239 If the file wasn't found, or there was some kind of error, then an error
240 is signalled; WHAT should be a noun phrase describing the kind of thing we
241 were looking for, suitable for inclusion in the error message.
243 While `find-file' establishes condition handlers for its own purposes,
244 THUNK is not invoked with any additional handlers defined."
247 (dolist (dir (cons (pathname (scanner-filename scanner)) *module-dirs*)
249 (let* ((path (merge-pathnames name dir))
250 (probe (probe-file path)))
252 (return (values path probe)))))
254 (error "Error searching for ~A ~S: ~A" what (namestring name) error))
255 (:no-error (path probe)
257 (error "Failed to find ~A ~S" what (namestring name)))
259 (funcall thunk path probe))))))
261 ;;;----- That's all, folks --------------------------------------------------