More WIP.
[sod] / src / module-output.lisp
1 ;;; -*-lisp-*-
2 ;;;
3 ;;; Output for modules
4 ;;;
5 ;;; (c) 2013 Straylight/Edgeware
6 ;;;
7
8 ;;;----- Licensing notice ---------------------------------------------------
9 ;;;
10 ;;; This file is part of the Sensble Object Design, an object system for C.
11 ;;;
12 ;;; SOD is free software; you can redistribute it and/or modify
13 ;;; it under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 2 of the License, or
15 ;;; (at your option) any later version.
16 ;;;
17 ;;; SOD is distributed in the hope that it will be useful,
18 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;;; GNU General Public License for more details.
21 ;;;
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with SOD; if not, write to the Free Software Foundation,
24 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 (cl:in-package #:sod)
27
28 ;;;--------------------------------------------------------------------------
29 ;;; Utilities.
30
31 (export 'banner)
32 (defun banner (title output &key (blank-line-p t))
33 "Write a banner to the OUTPUT stream, starting a new section called TITLE.
34
35 If BLANK-LINE-P is false, then leave a blank line after the banner. (This
36 is useful for a final banner at the end of a file.)"
37 (format output "~&/*----- ~A ~A*/~%"
38 title
39 (make-string (- 77 2 5 1 (length title) 1 2)
40 :initial-element #\-))
41 (when blank-line-p
42 (terpri output)))
43
44 (export 'guard-name)
45 (defun guard-name (filename)
46 "Return a sensible inclusion guard name for FILENAME."
47 (with-output-to-string (guard)
48 (let* ((pathname (make-pathname :name (pathname-name filename)
49 :type (pathname-type filename)))
50 (name (namestring pathname))
51 (uscore t))
52 (dotimes (i (length name))
53 (let ((ch (char name i)))
54 (cond ((alphanumericp ch)
55 (write-char (char-upcase ch) guard)
56 (setf uscore nil))
57 ((not uscore)
58 (write-char #\_ guard)
59 (setf uscore t))))))))
60
61 (defun guess-output-file (module type)
62 "Guess the filename to use for a file TYPE, generated from MODULE.
63
64 Here, TYPE is a filetype string. The result is returned as a pathname."
65 (merge-pathnames (make-pathname :type type :case :common)
66 (module-name module)))
67
68 ;;;--------------------------------------------------------------------------
69 ;;; Main output interface.
70
71 (export 'output-module)
72 (defun output-module (module reason stream)
73 "Write the MODULE to STREAM, giving the output machinery the REASON.
74
75 This is the top-level interface for producing output."
76 (let ((sequencer (make-instance 'sequencer))
77 (stream (if (typep stream 'position-aware-output-stream)
78 stream
79 (make-instance 'position-aware-output-stream
80 :stream stream
81 :file (stream-pathname stream)))))
82 (hook-output module reason sequencer)
83 (invoke-sequencer-items sequencer stream)))
84
85 ;;;--------------------------------------------------------------------------
86 ;;; Output implementation.
87
88 (defmethod hook-output progn ((module module) reason sequencer)
89
90 ;; Ask the module's items to sequence themselves.
91 (dolist (item (module-items module))
92 (hook-output item reason sequencer)))
93
94 (defmethod hook-output progn ((frag code-fragment-item) reason sequencer)
95
96 ;; Output fragments when their reasons are called up.
97 (when (eq reason (code-fragment-reason frag))
98 (dolist (constraint (code-fragment-constraints frag))
99 (add-sequencer-constraint sequencer constraint))
100 (add-sequencer-item-function sequencer (code-fragment-name frag)
101 (lambda (stream)
102 (write (code-fragment frag)
103 :stream stream
104 :pretty nil
105 :escape nil)))))
106
107 (defmethod hook-output progn ((module module) (reason (eql :h)) sequencer)
108 (sequence-output (stream sequencer)
109
110 :constraint
111 (:prologue
112 (:guard :start)
113 (:typedefs :start) :typedefs (:typedefs :end)
114 (:includes :start) :includes (:includes :end)
115 (:classes :start) :classes (:classes :end)
116 (:guard :end)
117 :epilogue)
118
119 (:prologue
120 (format stream "~
121 /* -*-c-*-
122 *
123 * Header file generated by SOD for ~A
124 */~2%"
125 (namestring (module-name module))))
126
127 ((:guard :start)
128 (format stream "~
129 #ifndef ~A
130 #define ~:*~A
131
132 #ifdef __cplusplus
133 extern \"C\" {
134 #endif~2%"
135 (or (get-property (module-pset module) :guard :id)
136 (guard-name (or (stream-pathname stream)
137 (guess-output-file module "H"))))))
138 ((:guard :end)
139 (banner "That's all, folks" stream)
140 (format stream "~
141 #ifdef __cplusplus
142 }
143 #endif
144
145 #endif~%"))
146
147 ((:typedefs :start)
148 (banner "Forward type declarations" stream))
149 ((:typedefs :end)
150 (terpri stream))
151
152 ((:includes :start)
153 (banner "External header files" stream))
154 ((:includes :end)
155 (terpri stream))))
156
157 (defmethod hook-output progn ((module module) (reason (eql :c)) sequencer)
158 (sequence-output (stream sequencer)
159
160 :constraint
161 (:prologue
162 (:includes :start) :includes (:includes :end)
163 (:classes :start) (:classes :end)
164 :epilogue)
165
166 (:prologue
167 (format stream "~
168 /* -*-c-*-
169 *
170 * Implementation file generated by SOD for ~A
171 */~2%"
172 (namestring (module-name module))))
173
174 (:epilogue
175 (banner "That's all, folks" stream :blank-line-p nil))
176
177 ((:includes :start)
178 (banner "External header files" stream))
179 ((:includes :end)
180 (terpri stream))))
181
182 ;;;----- That's all, folks --------------------------------------------------