Very ragged work-in-progress.
[sod] / output.lisp
index 44ec6e2..67d2907 100644 (file)
 (cl:in-package #:sod)
 
 ;;;--------------------------------------------------------------------------
+;;; Sequencing machinery.
+
+(defclass sequencer-item ()
+  ((name :initarg :name
+        :reader sequencer-item-name)
+   (functions :initarg :functions
+             :initform nil
+             :type list
+             :accessor sequencer-item-functions))
+  (:documentation
+   "Represents a distinct item to be sequenced by a SEQUENCER.
+
+   A SEQUENCER-ITEM maintains a list of FUNCTIONS which are invoked when the
+   sequencer is invoked.  This class is not intended to be subclassed."))
+
+(defmethod print-object ((item sequencer-item) stream)
+  (print-unreadable-object (item stream :type t)
+    (prin1 (sequencer-item-name item) stream)))
+
+(defclass sequencer ()
+  ((constraints :initarg :constraints
+               :initform nil
+               :type list
+               :accessor sequencer-constraints)
+   (table :initform (make-hash-table :test #'equal)
+         :reader sequencer-table))
+  (:documentation
+   "A sequencer tracks items and invokes them in the proper order.
+
+   The job of a SEQUENCER object is threefold.  Firstly, it collects
+   sequencer items and stores them in its table indexed by name.  Secondly,
+   it gathers CONSTRAINTS, which impose an ordering on the items.  Thirdly,
+   it can be instructed to invoke the items in an order compatible with the
+   established constraints.
+
+   Sequencer item names may may any kind of object which can be compared with
+   EQUAL.  In particular, symbols, integers and strings are reasonable
+   choices for atomic names, and lists work well for compound names -- so
+   it's possible to construct a hierarchy."))
+
+(defgeneric ensure-sequencer-item (sequencer name)
+  (:documentation
+   "Arrange that SEQUENCER has a sequencer-item called NAME.
+
+   Returns the corresponding SEQUENCER-ITEM object."))
+
+(defgeneric add-sequencer-constraint (sequencer constraint)
+  (:documentation
+   "Attach the given CONSTRAINT to an SEQUENCER.
+
+   The CONSTRAINT should be a list of sequencer-item names; see
+   ENSURE-SEQUENCER-ITEM for what they look like.  Note that the names
+   needn't have been declared in advance; indeed, they needn't be mentioned
+   anywhere else at all."))
+
+(defgeneric add-sequencer-item-function (sequencer name function)
+  (:documentation
+   "Arranges to call FUNCTION when the item called NAME is traversed.
+
+   More than one function can be associated with a given sequencer item.
+   They are called in the same order in which they were added.
+
+   Note that an item must be mentioned in at least one constraint in order to
+   be traversed by INVOKE-SEQUENCER-ITEMS.  If there are no special ordering
+   requirments for a particular item, then the trivial constraint (NAME) will
+   suffice."))
+
+(defgeneric invoke-sequencer-items (sequencer &rest arguments)
+  (:documentation
+   "Invoke functions attached to the SEQUENCER's items in the right order.
+
+   Each function is invoked in turn with the list of ARGUMENTS.  The return
+   values of the functions are discarded."))
+
+(defmethod ensure-sequencer-item ((sequencer sequencer) name)
+  (with-slots (table) sequencer
+    (or (gethash name table)
+       (setf (gethash name table)
+             (make-instance 'sequencer-item :name name)))))
+
+(defmethod add-sequencer-constraint ((sequencer sequencer) (constraint list))
+  (let ((converted-constraint (mapcar (lambda (name)
+                                       (ensure-sequencer-item sequencer
+                                                              name))
+                                     constraint)))
+    (with-slots (constraints) sequencer
+      (pushnew converted-constraint constraints :test #'equal))))
+
+(defmethod add-sequencer-item-function ((sequencer sequencer) name function)
+  (let ((item (ensure-sequencer-item sequencer name)))
+    (pushnew function (sequencer-item-functions item))))
+
+(defmethod invoke-sequencer-items ((sequencer sequencer) &rest arguments)
+  (dolist (item (merge-lists (reverse (sequencer-constraints sequencer))))
+    (dolist (function (reverse (sequencer-item-functions item)))
+      (apply function arguments))))
+
+;;;--------------------------------------------------------------------------
+;;; Output preparation.
+
+(defgeneric add-output-hooks (object reason sequencer)
+  (:documentation
+   "Announces the intention to write SEQUENCER, with a particular REASON.
+
+   The SEQUENCER is an SEQUENCER instance; the REASON will be a symbol which
+   can be matched using an EQL-specializer.  In response, OBJECT should add
+   any constrains and item functions that it wishes, and pass the
+   announcement to its sub-objects.")
+  (:method-combination progn)
+  (:method progn (object reason sequencer)
+    nil))
+
+(defvar *seen-announcement*)           ;Keep me unbound!
+#+hmm
+(defmethod add-output-hooks :around (object reason sequencer &rest stuff)
+  "Arrange not to invoke any object more than once during a particular
+   announcement."
+  (declare (ignore stuff))
+  (cond ((not (boundp '*seen-announcement*))
+        (let ((*seen-announcement* (make-hash-table)))
+          (setf (gethash object *seen-announcement*) t)
+          (call-next-method)))
+       ((gethash object *seen-announcement*)
+        nil)
+       (t
+        (setf (gethash object *seen-announcement*) t)
+        (call-next-method))))
+
+;;;--------------------------------------------------------------------------
 ;;; Utilities.
 
 (defun banner (title output &key (blank-line-p t))
          (when (module-header-fragments module)
            (banner "User code" output)
            (dolist (frag (module-header-fragments module))
-             (write-fragment frag output)))
+             (princ frag output)))
 
          ;; The definitions of the necessary structures.
          ;;
          (when (module-source-fragments module)
            (banner "User code" output)
            (dolist (frag (module-source-fragments module))
-             (write-fragment frag output)))
+             (princ frag output)))
 
          ;; The definitions of the necessary tables.
          ;;