An actual running implementation, which makes code that compiles.
[sod] / src / module-output.lisp
CommitLineData
048d0b2d
MW
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
ea578bb4 81 :file (stream-pathname stream)))))
9ec578d9
MW
82 (with-module-environment (module)
83 (hook-output module reason sequencer)
84 (invoke-sequencer-items sequencer stream))))
048d0b2d
MW
85
86;;;--------------------------------------------------------------------------
87;;; Output implementation.
88
89(defmethod hook-output progn ((module module) reason sequencer)
90
91 ;; Ask the module's items to sequence themselves.
92 (dolist (item (module-items module))
93 (hook-output item reason sequencer)))
94
95(defmethod hook-output progn ((frag code-fragment-item) reason sequencer)
96
97 ;; Output fragments when their reasons are called up.
98 (when (eq reason (code-fragment-reason frag))
99 (dolist (constraint (code-fragment-constraints frag))
100 (add-sequencer-constraint sequencer constraint))
101 (add-sequencer-item-function sequencer (code-fragment-name frag)
102 (lambda (stream)
103 (write (code-fragment frag)
104 :stream stream
105 :pretty nil
106 :escape nil)))))
107
108(defmethod hook-output progn ((module module) (reason (eql :h)) sequencer)
109 (sequence-output (stream sequencer)
110
111 :constraint
112 (:prologue
113 (:guard :start)
114 (:typedefs :start) :typedefs (:typedefs :end)
115 (:includes :start) :includes (:includes :end)
116 (:classes :start) :classes (:classes :end)
117 (:guard :end)
118 :epilogue)
119
120 (:prologue
121 (format stream "~
9ec578d9 122/* -*- mode: c; indent-tabs-mode: nil -*-
048d0b2d
MW
123 *
124 * Header file generated by SOD for ~A
125 */~2%"
126 (namestring (module-name module))))
127
128 ((:guard :start)
129 (format stream "~
130#ifndef ~A
131#define ~:*~A
132
133#ifdef __cplusplus
134 extern \"C\" {
135#endif~2%"
136 (or (get-property (module-pset module) :guard :id)
137 (guard-name (or (stream-pathname stream)
138 (guess-output-file module "H"))))))
139 ((:guard :end)
140 (banner "That's all, folks" stream)
141 (format stream "~
142#ifdef __cplusplus
143 }
144#endif
145
146#endif~%"))
147
148 ((:typedefs :start)
149 (banner "Forward type declarations" stream))
150 ((:typedefs :end)
151 (terpri stream))
152
153 ((:includes :start)
154 (banner "External header files" stream))
155 ((:includes :end)
156 (terpri stream))))
157
158(defmethod hook-output progn ((module module) (reason (eql :c)) sequencer)
159 (sequence-output (stream sequencer)
160
161 :constraint
162 (:prologue
163 (:includes :start) :includes (:includes :end)
164 (:classes :start) (:classes :end)
165 :epilogue)
166
167 (:prologue
168 (format stream "~
9ec578d9 169/* -*- mode: c; indent-tabs-mode: nil -*-
048d0b2d
MW
170 *
171 * Implementation file generated by SOD for ~A
172 */~2%"
173 (namestring (module-name module))))
174
175 (:epilogue
176 (banner "That's all, folks" stream :blank-line-p nil))
177
178 ((:includes :start)
179 (banner "External header files" stream))
180 ((:includes :end)
181 (terpri stream))))
182
9ec578d9
MW
183;;;--------------------------------------------------------------------------
184;;; Output types.
185
186(defvar *output-types* nil
187 "List of known output types.")
188
189(export 'declare-output-type)
190(defun declare-output-type (reason pathname)
191 "Record that REASON is a valid user-level output type.
192
193 The output file name will be constructed by merging the module's pathname
194 with PATHNAME."
195 (setf (get reason 'output-type) pathname))
196
197(export 'output-type-pathname)
198(defun output-type-pathname (reason)
199 "Return the PATHNAME template for the output type REASON.
200
201 Report an error if there is no such output type."
202 (or (get reason 'output-type)
203 (error "Unknown output type `~(~A~)'" reason)))
204
205(define-clear-the-decks reset-output-types
206 "Clear out the registered output types."
207 (dolist (reason *output-types*) (remprop reason 'output-type))
208 (setf *output-types* nil)
209 (declare-output-type :c (make-pathname :type "C" :case :common))
210 (declare-output-type :h (make-pathname :type "H" :case :common)))
211
048d0b2d 212;;;----- That's all, folks --------------------------------------------------