;;; -*-lisp-*- ;;; ;;; Module protocol definition ;;; ;;; (c) 2009 Straylight/Edgeware ;;; ;;;----- Licensing notice --------------------------------------------------- ;;; ;;; This file is part of the Sensible 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 'with-module-environment) (defmacro with-module-environment ((&optional (module '*module*)) &body body) "Evaluate the BODY with MODULE's variable bindings in scope." `(call-with-module-environment (lambda () ,@body) ,module)) ;;;-------------------------------------------------------------------------- ;;; 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." (multiple-value-bind (docs decls body) (parse-body body) `(add-clear-the-decks-function ',name (lambda () ,@docs ,@decls (block ,name ,@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 `:module-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) (declare (ignore 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 `hook-output' 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 `:module-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) (variables :initarg :variables :type list :accessor module-variables :initform (mapcar (compose #'cdr #'funcall) *module-bindings-alist*)) (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. * A list of module-variable values, in the order in which they're named in `*module-bindings-alist*'. 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)))) (export 'with-temporary-module) (defmacro with-temporary-module ((&key) &body body) "Evaluate BODY within the context of a temporary module." `(call-with-temporary-module (lambda () ,@body))) ;;;----- That's all, folks --------------------------------------------------