Change naming convention around.
[sod] / src / module-proto.lisp
diff --git a/src/module-proto.lisp b/src/module-proto.lisp
new file mode 100644 (file)
index 0000000..93034a4
--- /dev/null
@@ -0,0 +1,211 @@
+;;; -*-lisp-*-
+;;;
+;;; Module protocol definition
+;;;
+;;; (c) 2009 Straylight/Edgeware
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of the Sensble Object Design, an object system for C.
+;;;
+;;; SOD is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; SOD is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with SOD; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+(cl:in-package #:sod)
+
+;;;--------------------------------------------------------------------------
+;;; Module environment.
+
+(defvar *module-bindings-alist* nil
+  "An alist of (SYMBOL . THUNK) pairs.
+
+   During module construction, each SYMBOL is special-bound to the value
+   returned by the corresponding THUNK.")
+
+(export 'add-module-binding)
+(defun add-module-binding (symbol thunk)
+  "Add a new module variable binding.
+
+   During module construction, SYMBOL will be special-bound to the value
+   returned by THUNK.  If you can, use `define-module-var' instead."
+  (aif (assoc symbol *module-bindings-alist*)
+       (setf (cdr it) thunk)
+       (asetf *module-bindings-alist* (acons symbol thunk it))))
+
+(export 'define-module-var)
+(defmacro define-module-var (name value-form &optional documentation)
+  "Add a new module variable binding.
+
+   During module construction, NAME will be special-bound to the value of
+   VALUE-FORM.  The NAME is proclaimed special, but is initially left
+   unbound."
+  `(progn
+     (defvar ,name)
+     ,@(and documentation
+           `((setf (documentation ',name 'variable) ,documentation)))
+     (add-module-binding ',name (lambda () ,value-form))))
+
+(export 'call-with-module-environment)
+(defun call-with-module-environment (thunk)
+  "Invoke THUNK with a new collection of bindings for the module variables."
+  (progv
+      (mapcar #'car *module-bindings-alist*)
+      (mapcar (compose #'cdr #'funcall) *module-bindings-alist*)
+    (funcall thunk)))
+
+;;;--------------------------------------------------------------------------
+;;; The reset switch.
+
+(defvar *clear-the-decks-alist* nil
+  "List tracking functions to be called by `clear-the-decks'.")
+
+(export 'add-clear-the-decks-function)
+(defun add-clear-the-decks-function (symbol thunk)
+  "Add a function to the `clear-the-decks' list.
+
+   If a function tagged by SYMBOL already exists on the list, then that
+   function is replaced; otherwise a new function is added."
+  (aif (assoc symbol *clear-the-decks-alist*)
+       (setf (cdr it) thunk)
+       (asetf *clear-the-decks-alist* (acons symbol thunk it))))
+
+(export 'define-clear-the-decks)
+(defmacro define-clear-the-decks (name &body body)
+  "Add behaviour to `clear-the-decks'.
+
+   When `clear-the-decks' is called, the BODY will be evaluated as a progn.
+   The relative order of `clear-the-decks' operations is unspecified."
+  `(add-clear-the-decks-function ',name (lambda () ,@body)))
+
+(export 'clear-the-decks)
+(defun clear-the-decks ()
+  "Invoke a sequence of functions to reset the world."
+  (dolist (item *clear-the-decks-alist*)
+    (funcall (cdr item))))
+
+;;;--------------------------------------------------------------------------
+;;; Module construction protocol.
+
+(export '*module*)
+(defparameter *module* nil
+  "The current module under construction.
+
+   During module 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.")
+
+(export 'module-import)
+(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
+   `*module-type-map*' is a good plan.")
+  (:method (object) nil))
+
+(export 'add-to-module)
+(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."))
+
+(export 'finalize-module)
+(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' taking care of looking at interesting properties, just
+   to make sure they're ticked off.)"))
+
+;;;--------------------------------------------------------------------------
+;;; Module objects.
+
+(export '(module module-name module-pset module-items module-dependencies))
+(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 items which the module contains.
+
+     * A list of other modules that this one depends on.
+
+   Modules are usually constructed by the `read-module' function, though
+   there's nothing to stop fancy extensions building modules
+   programmatically."))
+
+(export 'define-module)
+(defmacro define-module
+    ((name &key (truename nil truenamep) (location nil locationp))
+     &body body)
+  "Define and return a new module.
+
+   The module will be called NAME; it will be included in the `*module-map*'
+   only if it has a TRUENAME (which defaults to the truename of NAME, or nil
+   if there is no file with that name).  The module is populated by
+   evaluating the BODY in a dynamic environment where `*module*' is bound to
+   the module under construction, and any other module variables are bound to
+   appropriate initial values -- see `*module-bindings-alist*' and
+   `define-module-var'.
+
+   If a module with the same NAME is already known, then it is returned
+   unchanged: the BODY is not evaluated.
+
+   The LOCATION may be any printable value other than `t' (though
+   `file-location' objects are most usual) indicating what provoked this
+   module definition: it gets reported to the user if an import cycle is
+   detected.  This check is made only if a TRUENAME is supplied.
+
+   Evaluation order irregularity: the TRUENAME and LOCATION arguments are
+   always evaluated in that order, regardless of their order in the macro
+   call site (which this macro can't detect)."
+
+  `(build-module ,name
+                (lambda () ,@body)
+                ,@(and truenamep `(:truename ,truename))
+                ,@(and locationp `(:location ,location))))
+
+;;;----- That's all, folks --------------------------------------------------