(cl:in-package #:sod)
;;;--------------------------------------------------------------------------
+;;; Module basics.
+
+(defclass module ()
+ ((name :initarg :name :type pathname :reader module-name)
+ (pset :initarg :pset :initform (make-pset) :type pset :reader module-pset)
+ (items :initarg :items :initform nil :type list :accessor module-items)
+ (dependencies :initarg :dependencies :initform nil
+ :type list :accessor module-dependencies)
+ (state :initarg :state :initform nil :accessor module-state))
+ (:documentation
+ "A module is a container for the definitions made in a source file.
+
+ Modules are the fundamental units of translation. The main job of a
+ module is to remember which definitions it contains, so that they can be
+ translated and written to output files. The module contains the following
+ handy bits of information:
+
+ * A (path) name, which is the filename we used to find it. The default
+ output filenames are derived from this. (We use the file's truename
+ as the hash key to prevent multiple inclusion, and that's a different
+ thing.)
+
+ * A property list containing other useful things.
+
+ * A list of the classes defined in the source file.
+
+ * Lists of C fragments to be included in the output header and C source
+ files.
+
+ * A list of other modules that this one depends on.
+
+ Modules are usually constructed by the PARSE-MODULE function, which is in
+ turn usually invoked by IMPORT-MODULE, though there's nothing to stop
+ fancy extensions building modules programmatically."))
+
+(defparameter *module* nil
+ "The current module under construction.
+
+ This is always an instance of MODULE. Once we've finished constructing
+ it, we'll call CHANGE-CLASS to turn it into an instance of whatever type
+ is requested in the module's :LISP-CLASS property.")
+
+(defgeneric module-import (object)
+ (:documentation
+ "Import definitions into the current environment.
+
+ Instructs the OBJECT to import its definitions into the current
+ environment. Modules pass the request on to their constituents. There's
+ a default method which does nothing at all.
+
+ It's not usual to modify the current module. Inserting things into the
+ *TYPE-MAP* is a good plan.")
+ (:method (object) nil))
+
+(defgeneric add-to-module (module item)
+ (:documentation
+ "Add ITEM to the MODULE's list of accumulated items.
+
+ The module items participate in the MODULE-IMPORT and ADD-OUTPUT-HOOKS
+ protocols."))
+
+(defgeneric finalize-module (module)
+ (:documentation
+ "Finalizes a module, setting everything which needs setting.
+
+ This isn't necessary if you made the module by hand. If you've
+ constructed it incrementally, then it might be a good plan. In
+ particular, it will change the class (using CHANGE-CLASS) of the module
+ according to the class choice set in the module's :LISP-CLASS property.
+ This has the side effects of calling SHARED-INITIALIZE, setting the
+ module's state to T, and checking for unrecognized properties. (Therefore
+ subclasses should add a method to SHARED-INITIALIZE should take care of
+ looking at interesting properties, just to make sure they're ticked
+ off.)"))
+
+(defmethod module-import ((module module))
+ (dolist (item (module-items module))
+ (module-import item)))
+
+(defmethod add-to-module ((module module) item)
+ (setf (module-items module)
+ (nconc (module-items module) (list item)))
+ (module-import item))
+
+(defmethod shared-initialize :after ((module module) slot-names &key pset)
+ "Tick off known properties on the property set."
+ (declare (ignore slot-names))
+ (when pset
+ (dolist (prop '(:guard))
+ (get-property pset prop nil))))
+
+(defmethod finalize-module ((module module))
+ (let* ((pset (module-pset module))
+ (class (get-property pset :lisp-class :symbol 'module)))
+
+ ;; Always call CHANGE-CLASS, even if it's the same one; this will
+ ;; exercise the property-set fiddling in SHARED-INITIALIZE and we can
+ ;; catch unknown-property errors.
+ (change-class module class :state t :pset pset)
+ (check-unused-properties pset)
+ module))
+
+;;;--------------------------------------------------------------------------
+;;; Module importing.
+
+(defun build-module
+ (name body-func &key (truename (probe-file name)) location)
+ (let ((*module* (make-instance 'module
+ :name (pathname name)
+ :state (file-location location)))
+ (*type-map* (make-hash-table :test #'equal)))
+ (module-import *builtin-module*)
+ (when truename
+ (setf (gethash truename *module-map*) *module*))
+ (unwind-protect
+ (progn
+ (funcall body-func)
+ (finalize-module *module*))
+ (when (and truename (not (eq (module-state *module*) t)))
+ (remhash truename *module-map*)))))
+
+(defmacro define-module
+ ((name &key (truename nil truenamep) (location nil locationp))
+ &body body)
+ `(build-module ,name
+ (lambda () ,@body)
+ ,@(and truenamep `(:truename ,truename))
+ ,@(and locationp `(:location ,location))))
+
+(defun read-module (pathname &key (truename (truename pathname)) location)
+ "Reads a module.
+
+ The module is returned if all went well; NIL is returned if an error
+ occurred.
+
+ The PATHNAME argument is the file to read. TRUENAME should be the file's
+ truename, if known: often, the file will have been searched for using
+ PROBE-FILE or similar, which drops the truename into your lap."
+
+ ;; Deal with a module which is already in the map. If its state is a
+ ;; file-location then it's in progress and we have a cyclic dependency.
+ (let ((module (gethash truename *module-map*)))
+ (cond ((typep (module-state module) 'file-location)
+ (error "Module ~A already being imported at ~A"
+ pathname (module-state module)))
+ (module
+ (return-from read-module module))))
+
+ ;; Make a new module. Be careful to remove the module from the map if we
+ ;; didn't succeed in constructing it.
+ (define-module (pathname :location location :truename truename)
+ (let ((*readtable* (copy-readtable)))
+ (with-open-file (f-stream pathname :direction :input)
+ (let* ((pai-stream (make-instance 'position-aware-input-stream
+ :stream f-stream
+ :file pathname))
+ (lexer (make-instance 'sod-lexer :stream pai-stream)))
+ (with-default-error-location (lexer)
+ (next-char lexer)
+ (next-token lexer)
+ (parse-module lexer *module*)))))))
+
+;;;--------------------------------------------------------------------------
+;;; Module parsing protocol.
+
+(defgeneric parse-module-declaration (tag lexer pset)
+ (:method (tag lexer pset)
+ (error "Unexpected module declaration ~(~A~)" tag)))
+
+(defun parse-module (lexer)
+ "Main dispatching for module parser.
+
+ Calls PARSE-MODULE-DECLARATION for the identifiable declarations."
+
+ ;; A little fancy footwork is required because `class' is a reserved word.
+ (loop
+ (flet ((dispatch (tag pset)
+ (next-token lexer)
+ (parse-module-declaration tag lexer pset)
+ (check-unused-properties pset)))
+ (restart-case
+ (case (token-type lexer)
+ (:eof (return))
+ (#\; (next-token lexer))
+ (t (let ((pset (parse-property-set lexer)))
+ (case (token-type lexer)
+ (:id (dispatch (string-to-symbol (token-value lexer)
+ :keyword)
+ pset))
+ (t (error "Unexpected token ~A: ignoring"
+ (format-token lexer)))))))
+ (continue ()
+ :report "Ignore the error and continue parsing."
+ nil)))))
+
+;;;--------------------------------------------------------------------------
+;;; Type definitions.
+
+(defclass type-item ()
+ ((name :initarg :name :type string :reader type-name)))
+
+(defmethod module-import ((item type-item))
+ (let* ((name (type-name item))
+ (def (gethash name *type-map*))
+ (type (make-simple-type name)))
+ (cond ((not def)
+ (setf (gethash name *type-map*) type))
+ ((not (eq def type))
+ (error "Conflicting types `~A'" name)))))
+
+(defmethod module-import ((class sod-class))
+ (record-sod-class class))
+
+;;;--------------------------------------------------------------------------
;;; File searching.
(defparameter *module-dirs* nil
(t
(funcall thunk path probe))))))
+(defmethod parse-module-declaration ((tag (eql :import)) lexer pset)
+ (let ((name (require-token lexer :string)))
+ (when name
+ (find-file lexer
+ (merge-pathnames name
+ (make-pathname :type "SOD" :case :common))
+ "module"
+ (lambda (path true)
+ (handler-case
+ (let ((module (read-module path :truename true)))
+ (when module
+ (module-import module)
+ (pushnew module (module-dependencies *module*))))
+ (file-error (error)
+ (cerror* "Error reading module ~S: ~A"
+ path error)))))
+ (require-token lexer #\;))))
+
+(defmethod parse-module-declaration ((tag (eql :load)) lexer pset)
+ (let ((name (require-token lexer :string)))
+ (when name
+ (find-file lexer
+ (merge-pathnames name
+ (make-pathname :type "LISP" :case :common))
+ "Lisp file"
+ (lambda (path true)
+ (handler-case (load true :verbose nil :print nil)
+ (error (error)
+ (cerror* "Error loading Lisp file ~S: ~A"
+ path error)))))
+ (require-token lexer #\;))))
+
;;;--------------------------------------------------------------------------
;;; Modules.
-(defclass module ()
- ((name :initarg :name
- :type pathname
- :accessor module-name)
- (plist :initform nil
- :initarg :plist
- :type list
- :accessor module-plist)
- (classes :initform nil
- :initarg :classes
- :type list
- :accessor module-classes)
- (source-fragments :initform nil
- :initarg :source-fragments
- :type list
- :accessor module-source-fragments)
- (header-fragments :initform nil
- :initarg :header-fragments
- :type list
- :accessor module-header-fragments)
- (dependencies :initform nil
- :initarg :dependencies
- :type list
- :accessor module-dependencies))
- (:documentation
- "A module is a container for the definitions made in a source file.
-
- Modules are the fundamental units of translation. The main job of a
- module is to remember which definitions it contains, so that they can be
- translated and written to output files. The module contains the following
- handy bits of information:
-
- * A (path) name, which is the filename we used to find it. The default
- output filenames are derived from this. (We use the file's truename
- as the hash key to prevent multiple inclusion, and that's a different
- thing.)
-
- * A property list containing other useful things.
-
- * A list of the classes defined in the source file.
-
- * Lists of C fragments to be included in the output header and C source
- files.
-
- * A list of other modules that this one depends on.
-
- Modules are usually constructed by the PARSE-MODULE function, which is in
- turn usually invoked by IMPORT-MODULE, though there's nothing to stop
- fancy extensions building modules programmatically."))
-
-(defun import-module (pathname &key (truename (truename pathname)))
- "Import a module.
-
- The module is returned if all went well; NIL is returned if an error
- occurred.
-
- The PATHNAME argument is the file to read. TRUENAME should be the file's
- truename, if known: often, the file will have been searched for using
- PROBE-FILE or similar, which drops the truename into your lap."
-
- (let ((module (gethash truename *module-map*)))
- (cond
-
- ;; The module's not there. (The *MODULE-MAP* never maps things to
- ;; NIL.)
- ((null module)
-
- ;; Mark the module as being in progress. Another attempt to import it
- ;; will fail.
- (setf (gethash truename *module-map*) :in-progress)
-
- ;; Be careful to restore the state of the module map on exit.
- (unwind-protect
-
- ;; Open the module file and parse it.
- (with-open-file (f-stream pathname :direction :input)
- (let* ((pai-stream (make-instance 'position-aware-input-stream
- :stream f-stream
- :file pathname))
- (lexer (make-instance 'sod-lexer :stream pai-stream)))
- (with-default-error-location (lexer)
- (restart-case
- (progn
- (next-char lexer)
- (next-token lexer)
- (setf module (parse-module lexer)))
- (continue ()
- :report "Ignore the import and continue"
- nil))))))
-
- ;; If we successfully parsed the module, then store it in the table;
- ;; otherwise remove it because we might want to try again. (That
- ;; might not work very well, but it could be worth a shot.)
- (if module
- (setf (gethash truename *module-map*) module)
- (remhash truename *module-map*))))
-
- ;; A module which is being read can't be included again.
- ((eql module :in-progress)
- (error "Cyclic module dependency involving module ~A" pathname))
-
- ;; A module which was successfully read. Just return it.
- (t
- module))))
-
+#+(or)
(defun parse-module (lexer)
"Parse a module from the given LEXER.
(next-token lexer)
(go top))
- ;; module-def : `import' string `;'
- ;;
- ;; Read another module of definitions from a file.
- (:import
- (next-token lexer)
- (let ((name (require-token lexer :string)))
- (when name
- (find-file lexer
- (merge-pathnames name (make-pathname
- :type "SOD"
- :case :common))
- "module"
- (lambda (path true)
- (handler-case
- (let ((module (import-module path
- :truename true)))
- (when module
- (push module deps)))
- (file-error (error)
- (cerror* "Error reading module ~S: ~A"
- path error)))))))
- (go semicolon))
-
- ;; module-def : `load' string `;'
- ;;
- ;; Load a Lisp extension from a file.
- (:load
- (next-token lexer)
- (let ((name (require-token lexer :string)))
- (when name
- (find-file lexer
- (merge-pathnames name
- (make-pathname :type "LISP"
- :case :common))
- "Lisp file"
- (lambda (path true)
- (handler-case (load true
- :verbose nil
- :print nil)
- (error (error)
- (cerror* "Error loading Lisp file ~S: ~A"
- path error)))))))
- (go semicolon))
-
;; module-def : `lisp' sexp
;;
;; Process an in-line Lisp form immediately.