Refactoring more or less complete. Maybe I should test it.
[sod] / src / module-output.lisp
diff --git a/src/module-output.lisp b/src/module-output.lisp
new file mode 100644 (file)
index 0000000..b093b82
--- /dev/null
@@ -0,0 +1,183 @@
+;;; -*-lisp-*-
+;;;
+;;; Output for modules
+;;;
+;;; (c) 2013 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)
+
+;;;--------------------------------------------------------------------------
+;;; Utilities.
+
+(export 'banner)
+(defun banner (title output &key (blank-line-p t))
+  "Write a banner to the OUTPUT stream, starting a new section called TITLE.
+
+   If BLANK-LINE-P is false, then leave a blank line after the banner.  (This
+   is useful for a final banner at the end of a file.)"
+  (format output "~&/*----- ~A ~A*/~%"
+         title
+         (make-string (- 77 2 5 1 (length title) 1 2)
+                      :initial-element #\-))
+  (when blank-line-p
+    (terpri output)))
+
+(export 'guard-name)
+(defun guard-name (filename)
+  "Return a sensible inclusion guard name for FILENAME."
+  (with-output-to-string (guard)
+    (let* ((pathname (make-pathname :name (pathname-name filename)
+                                   :type (pathname-type filename)))
+          (name (namestring pathname))
+          (uscore t))
+      (dotimes (i (length name))
+       (let ((ch (char name i)))
+         (cond ((alphanumericp ch)
+                (write-char (char-upcase ch) guard)
+                (setf uscore nil))
+               ((not uscore)
+                (write-char #\_ guard)
+                (setf uscore t))))))))
+
+(defun guess-output-file (module type)
+  "Guess the filename to use for a file TYPE, generated from MODULE.
+
+   Here, TYPE is a filetype string.  The result is returned as a pathname."
+  (merge-pathnames (make-pathname :type type :case :common)
+                  (module-name module)))
+
+;;;--------------------------------------------------------------------------
+;;; Main output interface.
+
+(export 'output-module)
+(defun output-module (module reason stream)
+  "Write the MODULE to STREAM, giving the output machinery the REASON.
+
+   This is the top-level interface for producing output."
+  (let ((sequencer (make-instance 'sequencer))
+       (stream (if (typep stream 'position-aware-output-stream)
+                   stream
+                   (make-instance 'position-aware-output-stream
+                                  :stream stream
+                                  :file (or (stream-pathname stream)
+                                            #p"<unnamed>")))))
+    (hook-output module reason sequencer)
+    (invoke-sequencer-items sequencer stream)))
+
+;;;--------------------------------------------------------------------------
+;;; Output implementation.
+
+(defmethod hook-output progn ((module module) reason sequencer)
+
+  ;; Ask the module's items to sequence themselves.
+  (dolist (item (module-items module))
+    (hook-output item reason sequencer)))
+
+(defmethod hook-output progn ((frag code-fragment-item) reason sequencer)
+
+  ;; Output fragments when their reasons are called up.
+  (when (eq reason (code-fragment-reason frag))
+    (dolist (constraint (code-fragment-constraints frag))
+      (add-sequencer-constraint sequencer constraint))
+    (add-sequencer-item-function sequencer (code-fragment-name frag)
+                                (lambda (stream)
+                                  (write (code-fragment frag)
+                                         :stream stream
+                                         :pretty nil
+                                         :escape nil)))))
+
+(defmethod hook-output progn ((module module) (reason (eql :h)) sequencer)
+  (sequence-output (stream sequencer)
+
+    :constraint
+    (:prologue
+     (:guard :start)
+     (:typedefs :start) :typedefs (:typedefs :end)
+     (:includes :start) :includes (:includes :end)
+     (:classes :start) :classes (:classes :end)
+     (:guard :end)
+     :epilogue)
+
+    (:prologue
+     (format stream "~
+/* -*-c-*-
+ *
+ * Header file generated by SOD for ~A
+ */~2%"
+            (namestring (module-name module))))
+
+    ((:guard :start)
+     (format stream "~
+#ifndef ~A
+#define ~:*~A
+
+#ifdef __cplusplus
+  extern \"C\" {
+#endif~2%"
+            (or (get-property (module-pset module) :guard :id)
+                (guard-name (or (stream-pathname stream)
+                                (guess-output-file module "H"))))))
+    ((:guard :end)
+     (banner "That's all, folks" stream)
+     (format stream "~
+#ifdef __cplusplus
+  }
+#endif
+
+#endif~%"))
+
+    ((:typedefs :start)
+     (banner "Forward type declarations" stream))
+    ((:typedefs :end)
+     (terpri stream))
+
+    ((:includes :start)
+     (banner "External header files" stream))
+    ((:includes :end)
+     (terpri stream))))
+
+(defmethod hook-output progn ((module module) (reason (eql :c)) sequencer)
+  (sequence-output (stream sequencer)
+
+    :constraint
+    (:prologue
+     (:includes :start) :includes (:includes :end)
+     (:classes :start) (:classes :end)
+     :epilogue)
+
+    (:prologue
+     (format stream "~
+/* -*-c-*-
+ *
+ * Implementation file generated by SOD for ~A
+ */~2%"
+            (namestring (module-name module))))
+
+    (:epilogue
+     (banner "That's all, folks" stream :blank-line-p nil))
+
+    ((:includes :start)
+     (banner "External header files" stream))
+    ((:includes :end)
+     (terpri stream))))
+
+;;;----- That's all, folks --------------------------------------------------