chiark / gitweb /
It lives!
[sod] / module.lisp
CommitLineData
abdf50aa
MW
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
d9c15186
MW
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
ddee4bb1
MW
134(defun build-module
135 (name body-func &key (truename (probe-file name)) location)
136 (let ((*module* (make-instance 'module
137 :name (pathname name)
138 :state (file-location location)))
139 (*type-map* (make-hash-table :test #'equal)))
140 (module-import *builtin-module*)
141 (when truename
142 (setf (gethash truename *module-map*) *module*))
143 (unwind-protect
144 (progn
145 (funcall body-func)
146 (finalize-module *module*))
147 (when (and truename (not (eq (module-state *module*) t)))
148 (remhash truename *module-map*)))))
149
150(defmacro define-module
151 ((name &key (truename nil truenamep) (location nil locationp))
152 &body body)
153 `(build-module ,name
154 (lambda () ,@body)
155 ,@(and truenamep `(:truename ,truename))
156 ,@(and locationp `(:location ,location))))
157
d9c15186
MW
158(defun read-module (pathname &key (truename (truename pathname)) location)
159 "Reads a module.
160
161 The module is returned if all went well; NIL is returned if an error
162 occurred.
163
164 The PATHNAME argument is the file to read. TRUENAME should be the file's
165 truename, if known: often, the file will have been searched for using
166 PROBE-FILE or similar, which drops the truename into your lap."
167
168 ;; Deal with a module which is already in the map. If its state is a
a07d8d00 169 ;; FILE-LOCATION then it's in progress and we have a cyclic dependency.
d9c15186 170 (let ((module (gethash truename *module-map*)))
a07d8d00
MW
171 (cond ((null module))
172 ((typep (module-state module) 'file-location)
d9c15186
MW
173 (error "Module ~A already being imported at ~A"
174 pathname (module-state module)))
175 (module
176 (return-from read-module module))))
177
178 ;; Make a new module. Be careful to remove the module from the map if we
179 ;; didn't succeed in constructing it.
ddee4bb1
MW
180 (define-module (pathname :location location :truename truename)
181 (let ((*readtable* (copy-readtable)))
182 (with-open-file (f-stream pathname :direction :input)
183 (let* ((pai-stream (make-instance 'position-aware-input-stream
184 :stream f-stream
185 :file pathname))
186 (lexer (make-instance 'sod-lexer :stream pai-stream)))
187 (with-default-error-location (lexer)
188 (next-char lexer)
189 (next-token lexer)
a07d8d00 190 (parse-module lexer)))))))
d9c15186
MW
191
192;;;--------------------------------------------------------------------------
193;;; Module parsing protocol.
194
195(defgeneric parse-module-declaration (tag lexer pset)
196 (:method (tag lexer pset)
a07d8d00
MW
197 (error "Unexpected module declaration ~(~A~)" tag))
198 (:method :before (tag lexer pset)
199 (next-token lexer)))
d9c15186
MW
200
201(defun parse-module (lexer)
202 "Main dispatching for module parser.
203
204 Calls PARSE-MODULE-DECLARATION for the identifiable declarations."
205
d9c15186 206 (loop
a07d8d00
MW
207 (restart-case
208 (case (token-type lexer)
209 (:eof (return))
210 (#\; (next-token lexer))
211 (t (let ((pset (parse-property-set lexer)))
212 (case (token-type lexer)
213 (:id (let ((tag (intern (frob-case (token-value lexer))
214 :keyword)))
215 (parse-module-declaration tag lexer pset)
216 (check-unused-properties pset)))
217 (t (error "Unexpected token ~A: ignoring"
218 (format-token lexer)))))))
219 (continue ()
220 :report "Ignore the error and continue parsing."
221 nil))))
d9c15186
MW
222
223;;;--------------------------------------------------------------------------
224;;; Type definitions.
225
226(defclass type-item ()
a07d8d00
MW
227 ((name :initarg :name :type string :reader type-name))
228 (:documentation
229 "A note that a module exports a type.
230
231 We can only export simple types, so we only need to remember the name.
232 The magic simple-type cache will ensure that we get the same type object
233 when we do the import."))
d9c15186
MW
234
235(defmethod module-import ((item type-item))
236 (let* ((name (type-name item))
237 (def (gethash name *type-map*))
238 (type (make-simple-type name)))
239 (cond ((not def)
240 (setf (gethash name *type-map*) type))
241 ((not (eq def type))
242 (error "Conflicting types `~A'" name)))))
243
244(defmethod module-import ((class sod-class))
245 (record-sod-class class))
246
a07d8d00
MW
247(defmethod parse-module-declaration ((tag (eql :typename)) lexer pset)
248 "module-decl ::= `typename' id-list `;'"
249 (loop (let ((name (require-token lexer :id)))
250 (unless name (return))
251 (if (gethash name *type-map*)
252 (cerror* "Type `~A' already defined" name)
253 (add-to-module *module* (make-instance 'type-item :name name)))
254 (unless (require-token lexer #\, :errorp nil) (return))))
255 (require-token lexer #\;))
256
257;;;--------------------------------------------------------------------------
258;;; Fragments.
259
260(defclass code-fragment-item ()
261 ((fragment :initarg :fragment :type c-fragment :reader code-fragment)
262 (reason :initarg :reason :type keyword :reader code-fragment-reason)
263 (name :initarg :name :type t :reader code-fragment-name)
264 (constraints :initarg :constraints :type list
265 :reader code-fragment-constraints))
266 (:documentation
267 "A plain fragment of C to be dropped in at top-level."))
268
269(defmacro define-fragment ((reason name) &body things)
270 (categorize (thing things)
271 ((constraints (listp thing))
272 (frags (typep thing '(or string c-fragment))))
273 (when (null frags)
274 (error "Missing code fragment"))
275 (when (cdr frags)
276 (error "Multiple code fragments"))
277 `(add-to-module
278 *module*
279 (make-instance 'code-fragment-item
280 :fragment ',(car frags)
281 :name ,name
282 :reason ,reason
283 :constraints (list ,@(mapcar (lambda (constraint)
284 (cons 'list constraint))
285 constraints))))))
286
287(defmethod parse-module-declaration ((tag (eql :code)) lexer pset)
288 "module-decl ::= `code' id `:' id [constraint-list] `{' c-fragment `}'
289 constraint ::= id*"
290 (labels ((parse-constraint ()
291 (let ((list nil))
292 (loop (let ((id (require-token lexer :id
293 :errorp (null list))))
294 (unless id (return))
295 (push id list)))
296 (nreverse list)))
297 (parse-constraints ()
298 (let ((list nil))
299 (when (require-token lexer #\[ :errorp nil)
300 (loop (let ((constraint (parse-constraint)))
301 (push constraint list)
302 (unless (require-token lexer #\, :errorp nil)
303 (return))))
304 (require-token lexer #\]))
305 (nreverse list)))
306 (keywordify (id)
307 (and id (intern (substitute #\- #\_ (frob-case id)) :keyword))))
308 (let* ((reason (prog1 (keywordify (require-token lexer :id))
309 (require-token lexer #\:)))
310 (name (keywordify (require-token lexer :id)))
311 (constraints (parse-constraints)))
312 (when (require-token lexer #\{ :consumep nil)
313 (let ((frag (scan-c-fragment lexer '(#\}))))
314 (next-token lexer)
315 (require-token lexer #\})
316 (add-to-module *module*
317 (make-instance 'code-fragment-item
318 :name name
319 :reason reason
320 :constraints constraints
321 :fragment frag)))))))
322
abdf50aa
MW
323;;;--------------------------------------------------------------------------
324;;; File searching.
325
326(defparameter *module-dirs* nil
327 "A list of directories (as pathname designators) to search for files.
328
329 Both SOD module files and Lisp extension files are searched for in this
330 list. The search works by merging the requested pathname with each
331 element of this list in turn. The list is prefixed by the pathname of the
332 requesting file, so that it can refer to other files relative to wherever
333 it was found.
334
335 See FIND-FILE for the grubby details.")
336
337(defun find-file (lexer name what thunk)
338 "Find a file called NAME on the module search path, and call THUNK on it.
339
340 The file is searched for relative to the LEXER's current file, and also in
341 the directories mentioned in the *MODULE-DIRS* list. If the file is
342 found, then THUNK is invoked with two arguments: the name we used to find
343 it (which might be relative to the starting directory) and the truename
344 found by PROBE-FILE.
345
346 If the file wasn't found, or there was some kind of error, then an error
347 is signalled; WHAT should be a noun phrase describing the kind of thing we
348 were looking for, suitable for inclusion in the error message.
349
350 While FIND-FILE establishes condition handlers for its own purposes, THUNK
351 is not invoked with any additional handlers defined."
352
353 (handler-case
354 (dolist (dir (cons (stream-pathname (lexer-stream lexer))
355 *module-dirs*)
356 (values nil nil))
357 (let* ((path (merge-pathnames name dir))
358 (probe (probe-file path)))
359 (when probe
360 (return (values path probe)))))
361 (file-error (error)
362 (error "Error searching for ~A ~S: ~A" what (namestring name) error))
363 (:no-error (path probe)
364 (cond ((null path)
a07d8d00 365 (error "Failed to find ~A ~S" what (namestring name)))
abdf50aa
MW
366 (t
367 (funcall thunk path probe))))))
368
d9c15186 369(defmethod parse-module-declaration ((tag (eql :import)) lexer pset)
a07d8d00 370 "module-decl ::= `import' string `;'"
d9c15186
MW
371 (let ((name (require-token lexer :string)))
372 (when name
373 (find-file lexer
374 (merge-pathnames name
375 (make-pathname :type "SOD" :case :common))
376 "module"
377 (lambda (path true)
378 (handler-case
379 (let ((module (read-module path :truename true)))
380 (when module
381 (module-import module)
382 (pushnew module (module-dependencies *module*))))
383 (file-error (error)
384 (cerror* "Error reading module ~S: ~A"
385 path error)))))
386 (require-token lexer #\;))))
387
388(defmethod parse-module-declaration ((tag (eql :load)) lexer pset)
a07d8d00 389 "module-decl ::= `load' string `;'"
d9c15186
MW
390 (let ((name (require-token lexer :string)))
391 (when name
392 (find-file lexer
393 (merge-pathnames name
394 (make-pathname :type "LISP" :case :common))
395 "Lisp file"
396 (lambda (path true)
397 (handler-case (load true :verbose nil :print nil)
398 (error (error)
399 (cerror* "Error loading Lisp file ~S: ~A"
400 path error)))))
401 (require-token lexer #\;))))
402
a07d8d00
MW
403;;;--------------------------------------------------------------------------
404;;; Lisp escapes.
405
406(defmethod parse-module-declaration :around ((tag (eql :lisp)) lexer pset)
407 "module-decl ::= `lisp' s-expression `;'"
408 (let ((form (with-lexer-stream (stream lexer) (read stream t))))
409 (eval form))
410 (next-token lexer)
411 (require-token lexer #\;))
412
413;;;--------------------------------------------------------------------------
414;;; Class declarations.
415
416(defmethod parse-module-declaration ((tag (eql :class)) lexer pset)
417 "module-decl ::= `class' id [`:' id-list] `{' class-item* `}'"
418 (let* ((location (file-location lexer))
419 (name (let ((name (require-token lexer :id)))
420 (make-class-type name location)
421 (when (require-token lexer #\; :errorp nil)
422 (return-from parse-module-declaration))
423 name))
424 (supers (when (require-token lexer #\: :errorp nil)
425 (let ((list nil))
426 (loop (let ((id (require-token lexer :id)))
427 (unless id (return))
428 (push id list)
429 (unless (require-token lexer #\, :errorp nil)
430 (return))))
431 (nreverse list))))
432 (class (make-sod-class name (mapcar #'find-sod-class supers)
433 pset location))
434 (nick (sod-class-nickname class)))
435 (require-token lexer #\{)
436
437 (labels ((parse-item ()
438 "Try to work out what kind of item this is. Messy."
439 (let* ((pset (parse-property-set lexer))
440 (location (file-location lexer)))
441 (cond ((declaration-specifier-p lexer)
442 (let ((declspec (parse-c-type lexer)))
443 (multiple-value-bind (type name)
444 (parse-c-declarator lexer declspec :dottedp t)
445 (cond ((null type)
446 nil)
447 ((consp name)
448 (parse-method type (car name) (cdr name)
449 pset location))
450 ((typep type 'c-function-type)
451 (parse-message type name pset location))
452 (t
453 (parse-slots declspec type name
454 pset location))))))
455 ((not (eq (token-type lexer) :id))
456 (cerror* "Expected <class-item>; found ~A (skipped)"
457 (format-token lexer))
458 (next-token lexer))
459 ((string= (token-value lexer) "class")
460 (next-token lexer)
461 (parse-initializers #'make-sod-class-initializer
462 pset location))
463 (t
464 (parse-initializers #'make-sod-instance-initializer
465 pset location)))))
466
467 (parse-method (type nick name pset location)
468 "class-item ::= declspec+ dotted-declarator -!- method-body
469
470 method-body ::= `{' c-fragment `}' | `extern' `;'
471
472 The dotted-declarator must describe a function type."
473 (let ((body (cond ((eq (token-type lexer) #\{)
474 (prog1 (scan-c-fragment lexer '(#\}))
475 (next-token lexer)
476 (require-token lexer #\})))
477 ((and (eq (token-type lexer) :id)
478 (string= (token-value lexer)
479 "extern"))
480 (next-token lexer)
481 (require-token lexer #\;)
482 nil)
483 (t
484 (cerror* "Expected <method-body>; ~
485 found ~A"
486 (format-token lexer))))))
487 (make-sod-method class nick name type body pset location)))
488
489 (parse-message (type name pset location)
490 "class-item ::= declspec+ declarator -!- (method-body | `;')
491
492 The declarator must describe a function type."
493 (make-sod-message class name type pset location)
494 (unless (require-token lexer #\; :errorp nil)
495 (parse-method type nick name nil location)))
496
497 (parse-initializer-body ()
498 "initializer ::= `=' `{' c-fragment `}' | `=' c-fragment"
499 (let ((char (lexer-char lexer)))
500 (loop
501 (when (or (null char) (not (whitespace-char-p char)))
502 (return))
503 (setf char (next-char lexer)))
504 (cond ((eql char #\{)
505 (next-char lexer)
506 (let ((frag (scan-c-fragment lexer '(#\}))))
507 (next-token lexer)
508 (require-token lexer #\})
509 (values :compound frag)))
510 (t
511 (let ((frag (scan-c-fragment lexer '(#\, #\;))))
512 (next-token lexer)
513 (values :simple frag))))))
514
515 (parse-slots (declspec type name pset location)
516 "class-item ::=
517 declspec+ init-declarator [`,' init-declarator-list] `;'
518
519 init-declarator ::= declarator -!- [initializer]"
520 (loop
521 (make-sod-slot class name type pset location)
522 (when (eql (token-type lexer) #\=)
523 (multiple-value-bind (kind form) (parse-initializer-body)
524 (make-sod-instance-initializer class nick name
525 kind form nil
526 location)))
527 (unless (require-token lexer #\, :errorp nil)
528 (return))
529 (setf (values type name)
530 (parse-c-declarator lexer declspec)
531 location (file-location lexer)))
532 (require-token lexer #\;))
533
534 (parse-initializers (constructor pset location)
535 "class-item ::= [`class'] -!- slot-initializer-list `;'
536
537 slot-initializer ::= id `.' id initializer"
538 (loop
539 (let ((nick (prog1 (require-token lexer :id)
540 (require-token lexer #\.)))
541 (name (require-token lexer :id)))
542 (require-token lexer #\=)
543 (multiple-value-bind (kind form)
544 (parse-initializer-body)
545 (funcall constructor class nick name kind form
546 pset location)))
547 (unless (require-token lexer #\, :errorp nil)
548 (return))
549 (setf location (file-location lexer)))
550 (require-token lexer #\;)))
551
552 (loop
553 (when (require-token lexer #\} :errorp nil)
554 (return))
555 (parse-item)))
556
557 (finalize-sod-class class)
558 (add-to-module *module* class)))
559
abdf50aa
MW
560;;;--------------------------------------------------------------------------
561;;; Modules.
562
d9c15186 563#+(or)
abdf50aa
MW
564(defun parse-module (lexer)
565 "Parse a module from the given LEXER.
566
567 The newly constructed module is returned. This is the top-level parsing
568 function."
569
570 (let ((hfrags nil)
571 (cfrags nil)
572 (classes nil)
573 (plist nil)
574 (deps nil))
575
576 (labels ((fragment (func)
577 (next-token lexer)
578 (when (require-token lexer #\{ :consumep nil)
579 (let ((frag (scan-c-fragment lexer '(#\}))))
580 (next-token lexer)
581 (require-token lexer #\})
582 (funcall func frag)))))
583
584 (tagbody
585
586 top
587 ;; module : empty | module-def module
588 ;;
589 ;; Just read module-defs until we reach the end of the file.
590 (case (token-type lexer)
591
592 (:eof
593 (go done))
594 (#\;
595 (next-token lexer)
596 (go top))
597
abdf50aa
MW
598 ;; module-def : `lisp' sexp
599 ;;
600 ;; Process an in-line Lisp form immediately.
601 (:lisp
a07d8d00 602
abdf50aa
MW
603 (next-token lexer)
604 (go top))
605
606 ;; module-def : `typename' ids `;'
607 ;; ids : id | ids `,' id
608 ;;
609 ;; Add ids as registered type names. We don't need to know what
610 ;; they mean at this level.
611 (:typename
612 (next-token lexer)
613 (loop
614 (let ((id (require-token lexer :id)))
615 (cond ((null id)
616 (return))
617 ((gethash id *type-map*)
618 (cerror* "Type ~A is already defined" id))
619 (t
620 (setf (gethash id *type-map*)
621 (make-instance 'simple-c-type :name id))))
622 (unless (eql (token-type lexer) #\,)
623 (return))
624 (next-token lexer)))
625 (go semicolon))
626
627 ;; module-def : `source' `{' c-stuff `}'
628 ;; module-def : `header' `{' c-stuff `}'
629 (:source
630 (fragment (lambda (frag) (push frag cfrags)))
631 (go top))
632 (:header
633 (fragment (lambda (frag) (push frag hfrags)))
634 (go top))
635
636 ;; Anything else is an error.
637 (t
638 (cerror* "Unexpected token ~A ignored" (format-token lexer))
639 (next-token lexer)
640 (go top)))
641
642 semicolon
643 ;; Scan a terminating semicolon.
644 (require-token lexer #\;)
645 (go top)
646
647 done)
648
649 ;; Assemble the module and we're done.
650 (make-instance 'module
651 :name (stream-pathname (lexer-stream lexer))
652 :plist plist
653 :classes classes
654 :header-fragments hfrags
655 :source-fragments cfrags
656 :dependencies deps))))
657
658;;;----- That's all, folks --------------------------------------------------