Change naming convention around.
[sod] / src / output-proto.lisp
diff --git a/src/output-proto.lisp b/src/output-proto.lisp
new file mode 100644 (file)
index 0000000..1630de6
--- /dev/null
@@ -0,0 +1,171 @@
+;;; -*-lisp-*-
+;;;
+;;; Output scheduling protocol
+;;;
+;;; (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)
+
+;;;--------------------------------------------------------------------------
+;;; Sequencing machinery.
+
+(export '(sequencer-item make-sequencer-item sequencer-item-p
+         sequencer-item-name sequencer-item-functions))
+(defstruct (sequencer-item
+            (:constructor make-sequencer-item (name &optional functions)))
+  "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."
+  (name nil :read-only t)
+  (functions nil :type list))
+
+(export '(sequencer sequencer-constraints sequencer-table))
+(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."))
+
+(export 'ensure-sequencer-item)
+(defgeneric ensure-sequencer-item (sequencer name)
+  (:documentation
+   "Arrange that SEQUENCER has a sequencer-item called NAME.
+
+   Returns the corresponding SEQUENCER-ITEM object.
+
+   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."))
+
+(export 'add-sequencer-constraint)
+(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."))
+
+(export 'add-sequencer-item-function)
+(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."))
+
+(export 'invoke-sequencer-items)
+(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."))
+
+;;;--------------------------------------------------------------------------
+;;; Output preparation.
+
+(defgeneric hook-output (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 constraints and item functions that it wishes, and pass the
+   announcement to its sub-objects.  It is not uncommon for an object to pass
+   a reason to its sub-objects that is different from the REASON with which
+   it was itself invoked.")
+
+  (:method-combination progn)
+  (:method progn (object reason sequencer)))
+
+;;;--------------------------------------------------------------------------
+;;; Useful syntax.
+
+(defmacro sequence-output
+    ((streamvar sequencer) &body clauses)
+  "Register output behaviour in a convenient manner.
+
+   The full syntax isn't quite as described:
+
+       sequence-output (STREAMVAR SEQUENCER)
+         { :constraint CONSTRAINT }*
+         CLAUSE*
+
+       STREAMVAR ::= a symbol
+       SEQUENCER ::= a sequencer object, evaluated
+       CONSTRAINT ::= ( ITEM-NAME* )
+       CLAUSE ::= (ITEM-NAME FORM*)
+       ITEM-NAME ::= an atom or a list of expressions
+
+   An ITEM-NAME may be a self-evaluating atom (in which case it stands for
+   itself, clearly), a symbol (in which case the corresponding variable value
+   is used), or a list of forms (in which case the name used is the list of
+   the corresponding values).
+
+   The behaviour is as follows.  The CONSTRAINTS, if any, are added to the
+   sequencer.  Then, for each CLAUSE, a function is attached to the named
+   sequencer item whose behaviour is to bind STREAMVAR to the output stream
+   and evaluate the FORMs as a progn."
+
+  (let ((seqvar (gensym "SEQ")))
+    (labels ((convert-item-name (name)
+              (if (listp name)
+                  (cons 'list name)
+                  name))
+            (convert-constraint (constraint)
+              (cons 'list (mapcar #'convert-item-name constraint)))
+            (process-body (clauses)
+              (if (eq (car clauses) :constraint)
+                  (cons `(add-sequencer-constraint
+                          ,seqvar
+                          ,(convert-constraint (cadr clauses)))
+                        (process-body (cddr clauses)))
+                  (mapcar (lambda (clause)
+                            (let ((name (car clause))
+                                  (body (cdr clause)))
+                              `(add-sequencer-item-function
+                                ,seqvar
+                                ,(convert-item-name name)
+                                (lambda (,streamvar)
+                                  ,@body))))
+                          clauses))))
+      `(let ((,seqvar ,sequencer))
+        ,@(process-body clauses)))))
+
+;;;----- That's all, folks --------------------------------------------------