More work. Highlights:
[sod] / module.lisp
1 ;;; -*-lisp-*-
2 ;;;
3 ;;; Modules and module parser
4 ;;;
5 ;;; (c) 2009 Straylight/Edgeware
6 ;;;
7
8 ;;;----- Licensing notice ---------------------------------------------------
9 ;;;
10 ;;; This file is part of the Simple Object Definition system.
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 (defclass module ()
32 ((name :initarg :name :type pathname :reader module-name)
33 (pset :initarg :pset :initform (make-pset) :type pset :reader module-pset)
34 (items :initarg :items :initform nil :type list :accessor module-items)
35 (dependencies :initarg :dependencies :initform nil
36 :type list :accessor module-dependencies)
37 (state :initarg :state :initform nil :accessor module-state))
38 (:documentation
39 "A module is a container for the definitions made in a source file.
40
41 Modules are the fundamental units of translation. The main job of a
42 module is to remember which definitions it contains, so that they can be
43 translated and written to output files. The module contains the following
44 handy bits of information:
45
46 * A (path) name, which is the filename we used to find it. The default
47 output filenames are derived from this. (We use the file's truename
48 as the hash key to prevent multiple inclusion, and that's a different
49 thing.)
50
51 * A property list containing other useful things.
52
53 * A list of the classes defined in the source file.
54
55 * Lists of C fragments to be included in the output header and C source
56 files.
57
58 * A list of other modules that this one depends on.
59
60 Modules are usually constructed by the PARSE-MODULE function, which is in
61 turn usually invoked by IMPORT-MODULE, though there's nothing to stop
62 fancy extensions building modules programmatically."))
63
64 (defparameter *module* nil
65 "The current module under construction.
66
67 This is always an instance of MODULE. Once we've finished constructing
68 it, we'll call CHANGE-CLASS to turn it into an instance of whatever type
69 is requested in the module's :LISP-CLASS property.")
70
71 (defgeneric module-import (object)
72 (:documentation
73 "Import definitions into the current environment.
74
75 Instructs the OBJECT to import its definitions into the current
76 environment. Modules pass the request on to their constituents. There's
77 a default method which does nothing at all.
78
79 It's not usual to modify the current module. Inserting things into the
80 *TYPE-MAP* is a good plan.")
81 (:method (object) nil))
82
83 (defgeneric add-to-module (module item)
84 (:documentation
85 "Add ITEM to the MODULE's list of accumulated items.
86
87 The module items participate in the MODULE-IMPORT and ADD-OUTPUT-HOOKS
88 protocols."))
89
90 (defgeneric finalize-module (module)
91 (:documentation
92 "Finalizes a module, setting everything which needs setting.
93
94 This isn't necessary if you made the module by hand. If you've
95 constructed it incrementally, then it might be a good plan. In
96 particular, it will change the class (using CHANGE-CLASS) of the module
97 according to the class choice set in the module's :LISP-CLASS property.
98 This has the side effects of calling SHARED-INITIALIZE, setting the
99 module's state to T, and checking for unrecognized properties. (Therefore
100 subclasses should add a method to SHARED-INITIALIZE should take care of
101 looking at interesting properties, just to make sure they're ticked
102 off.)"))
103
104 (defmethod module-import ((module module))
105 (dolist (item (module-items module))
106 (module-import item)))
107
108 (defmethod add-to-module ((module module) item)
109 (setf (module-items module)
110 (nconc (module-items module) (list item)))
111 (module-import item))
112
113 (defmethod shared-initialize :after ((module module) slot-names &key pset)
114 "Tick off known properties on the property set."
115 (declare (ignore slot-names))
116 (when pset
117 (dolist (prop '(:guard))
118 (get-property pset prop nil))))
119
120 (defmethod finalize-module ((module module))
121 (let* ((pset (module-pset module))
122 (class (get-property pset :lisp-class :symbol 'module)))
123
124 ;; Always call CHANGE-CLASS, even if it's the same one; this will
125 ;; exercise the property-set fiddling in SHARED-INITIALIZE and we can
126 ;; catch unknown-property errors.
127 (change-class module class :state t :pset pset)
128 (check-unused-properties pset)
129 module))
130
131 ;;;--------------------------------------------------------------------------
132 ;;; Module importing.
133
134 (defun read-module (pathname &key (truename (truename pathname)) location)
135 "Reads a module.
136
137 The module is returned if all went well; NIL is returned if an error
138 occurred.
139
140 The PATHNAME argument is the file to read. TRUENAME should be the file's
141 truename, if known: often, the file will have been searched for using
142 PROBE-FILE or similar, which drops the truename into your lap."
143
144 ;; Deal with a module which is already in the map. If its state is a
145 ;; file-location then it's in progress and we have a cyclic dependency.
146 (let ((module (gethash truename *module-map*)))
147 (cond ((typep (module-state module) 'file-location)
148 (error "Module ~A already being imported at ~A"
149 pathname (module-state module)))
150 (module
151 (return-from read-module module))))
152
153 ;; Make a new module. Be careful to remove the module from the map if we
154 ;; didn't succeed in constructing it.
155 (let ((*module* (make-instance 'module
156 :name pathname
157 :state (file-location location)))
158 (*type-map* (make-hash-table :test #'equal)))
159 (module-import *builtin-module*)
160 (setf (gethash truename *module-map*) *module*)
161 (unwind-protect
162 (with-open-file (f-stream pathname :direction :input)
163 (let* ((*module* (make-instance 'module :name pathname))
164 (pai-stream (make-instance 'position-aware-input-stream
165 :stream f-stream
166 :file pathname))
167 (lexer (make-instance 'sod-lexer :stream pai-stream)))
168 (with-default-error-location (lexer)
169 (next-char lexer)
170 (next-token lexer)
171 (parse-module lexer *module*)
172 (finalize-module *module*))))
173 (unless (eq (module-state *module*) t)
174 (remhash truename *module-map*)))))
175
176 ;;;--------------------------------------------------------------------------
177 ;;; Module parsing protocol.
178
179 (defgeneric parse-module-declaration (tag lexer pset)
180 (:method (tag lexer pset)
181 (error "Unexpected module declaration ~(~A~)" tag)))
182
183 (defun parse-module (lexer)
184 "Main dispatching for module parser.
185
186 Calls PARSE-MODULE-DECLARATION for the identifiable declarations."
187
188 ;; A little fancy footwork is required because `class' is a reserved word.
189 (loop
190 (flet ((dispatch (tag pset)
191 (next-token lexer)
192 (parse-module-declaration tag lexer pset)
193 (check-unused-properties pset)))
194 (restart-case
195 (case (token-type lexer)
196 (:eof (return))
197 (#\; (next-token lexer))
198 (t (let ((pset (parse-property-set lexer)))
199 (case (token-type lexer)
200 (:id (dispatch (string-to-symbol (token-value lexer)
201 :keyword)
202 pset))
203 (t (error "Unexpected token ~A: ignoring"
204 (format-token lexer)))))))
205 (continue ()
206 :report "Ignore the error and continue parsing."
207 nil)))))
208
209 ;;;--------------------------------------------------------------------------
210 ;;; Type definitions.
211
212 (defclass type-item ()
213 ((name :initarg :name :type string :reader type-name)))
214
215 (defmethod module-import ((item type-item))
216 (let* ((name (type-name item))
217 (def (gethash name *type-map*))
218 (type (make-simple-type name)))
219 (cond ((not def)
220 (setf (gethash name *type-map*) type))
221 ((not (eq def type))
222 (error "Conflicting types `~A'" name)))))
223
224 (defmethod module-import ((class sod-class))
225 (record-sod-class class))
226
227 ;;;--------------------------------------------------------------------------
228 ;;; File searching.
229
230 (defparameter *module-dirs* nil
231 "A list of directories (as pathname designators) to search for files.
232
233 Both SOD module files and Lisp extension files are searched for in this
234 list. The search works by merging the requested pathname with each
235 element of this list in turn. The list is prefixed by the pathname of the
236 requesting file, so that it can refer to other files relative to wherever
237 it was found.
238
239 See FIND-FILE for the grubby details.")
240
241 (defun find-file (lexer 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 LEXER's current file, and also in
245 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, THUNK
255 is not invoked with any additional handlers defined."
256
257 (handler-case
258 (dolist (dir (cons (stream-pathname (lexer-stream lexer))
259 *module-dirs*)
260 (values nil nil))
261 (let* ((path (merge-pathnames name dir))
262 (probe (probe-file path)))
263 (when probe
264 (return (values path probe)))))
265 (file-error (error)
266 (error "Error searching for ~A ~S: ~A" what (namestring name) error))
267 (:no-error (path probe)
268 (cond ((null path)
269 (error "Failed to find ~A ~S" what name))
270 (t
271 (funcall thunk path probe))))))
272
273 (defmethod parse-module-declaration ((tag (eql :import)) lexer pset)
274 (let ((name (require-token lexer :string)))
275 (when name
276 (find-file lexer
277 (merge-pathnames name
278 (make-pathname :type "SOD" :case :common))
279 "module"
280 (lambda (path true)
281 (handler-case
282 (let ((module (read-module path :truename true)))
283 (when module
284 (module-import module)
285 (pushnew module (module-dependencies *module*))))
286 (file-error (error)
287 (cerror* "Error reading module ~S: ~A"
288 path error)))))
289 (require-token lexer #\;))))
290
291 (defmethod parse-module-declaration ((tag (eql :load)) lexer pset)
292 (let ((name (require-token lexer :string)))
293 (when name
294 (find-file lexer
295 (merge-pathnames name
296 (make-pathname :type "LISP" :case :common))
297 "Lisp file"
298 (lambda (path true)
299 (handler-case (load true :verbose nil :print nil)
300 (error (error)
301 (cerror* "Error loading Lisp file ~S: ~A"
302 path error)))))
303 (require-token lexer #\;))))
304
305 ;;;--------------------------------------------------------------------------
306 ;;; Modules.
307
308 #+(or)
309 (defun parse-module (lexer)
310 "Parse a module from the given LEXER.
311
312 The newly constructed module is returned. This is the top-level parsing
313 function."
314
315 (let ((hfrags nil)
316 (cfrags nil)
317 (classes nil)
318 (plist nil)
319 (deps nil))
320
321 (labels ((fragment (func)
322 (next-token lexer)
323 (when (require-token lexer #\{ :consumep nil)
324 (let ((frag (scan-c-fragment lexer '(#\}))))
325 (next-token lexer)
326 (require-token lexer #\})
327 (funcall func frag)))))
328
329 (tagbody
330
331 top
332 ;; module : empty | module-def module
333 ;;
334 ;; Just read module-defs until we reach the end of the file.
335 (case (token-type lexer)
336
337 (:eof
338 (go done))
339 (#\;
340 (next-token lexer)
341 (go top))
342
343 ;; module-def : `lisp' sexp
344 ;;
345 ;; Process an in-line Lisp form immediately.
346 (:lisp
347 (let ((form (with-lexer-stream (stream lexer)
348 (read stream t))))
349 (handler-case
350 (eval form)
351 (error (error)
352 (cerror* "Error in Lisp form: ~A" error))))
353 (next-token lexer)
354 (go top))
355
356 ;; module-def : `typename' ids `;'
357 ;; ids : id | ids `,' id
358 ;;
359 ;; Add ids as registered type names. We don't need to know what
360 ;; they mean at this level.
361 (:typename
362 (next-token lexer)
363 (loop
364 (let ((id (require-token lexer :id)))
365 (cond ((null id)
366 (return))
367 ((gethash id *type-map*)
368 (cerror* "Type ~A is already defined" id))
369 (t
370 (setf (gethash id *type-map*)
371 (make-instance 'simple-c-type :name id))))
372 (unless (eql (token-type lexer) #\,)
373 (return))
374 (next-token lexer)))
375 (go semicolon))
376
377 ;; module-def : `source' `{' c-stuff `}'
378 ;; module-def : `header' `{' c-stuff `}'
379 (:source
380 (fragment (lambda (frag) (push frag cfrags)))
381 (go top))
382 (:header
383 (fragment (lambda (frag) (push frag hfrags)))
384 (go top))
385
386 ;; Anything else is an error.
387 (t
388 (cerror* "Unexpected token ~A ignored" (format-token lexer))
389 (next-token lexer)
390 (go top)))
391
392 semicolon
393 ;; Scan a terminating semicolon.
394 (require-token lexer #\;)
395 (go top)
396
397 done)
398
399 ;; Assemble the module and we're done.
400 (make-instance 'module
401 :name (stream-pathname (lexer-stream lexer))
402 :plist plist
403 :classes classes
404 :header-fragments hfrags
405 :source-fragments cfrags
406 :dependencies deps))))
407
408 ;;;----- That's all, folks --------------------------------------------------