Commit | Line | Data |
---|---|---|
abdf50aa MW |
1 | ;;; -*-lisp-*- |
2 | ;;; | |
3 | ;;; Output driver for SOD translator | |
4 | ;;; | |
5 | ;;; (c) 2009 Straylight/Edgeware | |
6 | ;;; | |
7 | ||
8 | ;;;----- Licensing notice --------------------------------------------------- | |
9 | ;;; | |
10 | ;;; This file is part of the Simple Object Definition system. | |
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 | ;;;-------------------------------------------------------------------------- | |
1f1d88f5 MW |
29 | ;;; Sequencing machinery. |
30 | ||
31 | (defclass sequencer-item () | |
32 | ((name :initarg :name | |
33 | :reader sequencer-item-name) | |
34 | (functions :initarg :functions | |
35 | :initform nil | |
36 | :type list | |
37 | :accessor sequencer-item-functions)) | |
38 | (:documentation | |
39 | "Represents a distinct item to be sequenced by a SEQUENCER. | |
40 | ||
41 | A SEQUENCER-ITEM maintains a list of FUNCTIONS which are invoked when the | |
42 | sequencer is invoked. This class is not intended to be subclassed.")) | |
43 | ||
44 | (defmethod print-object ((item sequencer-item) stream) | |
45 | (print-unreadable-object (item stream :type t) | |
46 | (prin1 (sequencer-item-name item) stream))) | |
47 | ||
48 | (defclass sequencer () | |
49 | ((constraints :initarg :constraints | |
50 | :initform nil | |
51 | :type list | |
52 | :accessor sequencer-constraints) | |
53 | (table :initform (make-hash-table :test #'equal) | |
54 | :reader sequencer-table)) | |
55 | (:documentation | |
56 | "A sequencer tracks items and invokes them in the proper order. | |
57 | ||
58 | The job of a SEQUENCER object is threefold. Firstly, it collects | |
59 | sequencer items and stores them in its table indexed by name. Secondly, | |
60 | it gathers CONSTRAINTS, which impose an ordering on the items. Thirdly, | |
61 | it can be instructed to invoke the items in an order compatible with the | |
62 | established constraints. | |
63 | ||
64 | Sequencer item names may may any kind of object which can be compared with | |
65 | EQUAL. In particular, symbols, integers and strings are reasonable | |
66 | choices for atomic names, and lists work well for compound names -- so | |
67 | it's possible to construct a hierarchy.")) | |
68 | ||
69 | (defgeneric ensure-sequencer-item (sequencer name) | |
70 | (:documentation | |
71 | "Arrange that SEQUENCER has a sequencer-item called NAME. | |
72 | ||
73 | Returns the corresponding SEQUENCER-ITEM object.")) | |
74 | ||
75 | (defgeneric add-sequencer-constraint (sequencer constraint) | |
76 | (:documentation | |
77 | "Attach the given CONSTRAINT to an SEQUENCER. | |
78 | ||
79 | The CONSTRAINT should be a list of sequencer-item names; see | |
80 | ENSURE-SEQUENCER-ITEM for what they look like. Note that the names | |
81 | needn't have been declared in advance; indeed, they needn't be mentioned | |
82 | anywhere else at all.")) | |
83 | ||
84 | (defgeneric add-sequencer-item-function (sequencer name function) | |
85 | (:documentation | |
86 | "Arranges to call FUNCTION when the item called NAME is traversed. | |
87 | ||
88 | More than one function can be associated with a given sequencer item. | |
89 | They are called in the same order in which they were added. | |
90 | ||
91 | Note that an item must be mentioned in at least one constraint in order to | |
92 | be traversed by INVOKE-SEQUENCER-ITEMS. If there are no special ordering | |
93 | requirments for a particular item, then the trivial constraint (NAME) will | |
94 | suffice.")) | |
95 | ||
96 | (defgeneric invoke-sequencer-items (sequencer &rest arguments) | |
97 | (:documentation | |
98 | "Invoke functions attached to the SEQUENCER's items in the right order. | |
99 | ||
100 | Each function is invoked in turn with the list of ARGUMENTS. The return | |
101 | values of the functions are discarded.")) | |
102 | ||
103 | (defmethod ensure-sequencer-item ((sequencer sequencer) name) | |
104 | (with-slots (table) sequencer | |
105 | (or (gethash name table) | |
106 | (setf (gethash name table) | |
107 | (make-instance 'sequencer-item :name name))))) | |
108 | ||
109 | (defmethod add-sequencer-constraint ((sequencer sequencer) (constraint list)) | |
110 | (let ((converted-constraint (mapcar (lambda (name) | |
111 | (ensure-sequencer-item sequencer | |
112 | name)) | |
113 | constraint))) | |
114 | (with-slots (constraints) sequencer | |
115 | (pushnew converted-constraint constraints :test #'equal)))) | |
116 | ||
117 | (defmethod add-sequencer-item-function ((sequencer sequencer) name function) | |
118 | (let ((item (ensure-sequencer-item sequencer name))) | |
119 | (pushnew function (sequencer-item-functions item)))) | |
120 | ||
121 | (defmethod invoke-sequencer-items ((sequencer sequencer) &rest arguments) | |
122 | (dolist (item (merge-lists (reverse (sequencer-constraints sequencer)))) | |
123 | (dolist (function (reverse (sequencer-item-functions item))) | |
124 | (apply function arguments)))) | |
125 | ||
126 | ;;;-------------------------------------------------------------------------- | |
127 | ;;; Output preparation. | |
128 | ||
129 | (defgeneric add-output-hooks (object reason sequencer) | |
130 | (:documentation | |
131 | "Announces the intention to write SEQUENCER, with a particular REASON. | |
132 | ||
133 | The SEQUENCER is an SEQUENCER instance; the REASON will be a symbol which | |
134 | can be matched using an EQL-specializer. In response, OBJECT should add | |
135 | any constrains and item functions that it wishes, and pass the | |
136 | announcement to its sub-objects.") | |
137 | (:method-combination progn) | |
138 | (:method progn (object reason sequencer) | |
139 | nil)) | |
140 | ||
141 | (defvar *seen-announcement*) ;Keep me unbound! | |
142 | #+hmm | |
143 | (defmethod add-output-hooks :around (object reason sequencer &rest stuff) | |
144 | "Arrange not to invoke any object more than once during a particular | |
145 | announcement." | |
146 | (declare (ignore stuff)) | |
147 | (cond ((not (boundp '*seen-announcement*)) | |
148 | (let ((*seen-announcement* (make-hash-table))) | |
149 | (setf (gethash object *seen-announcement*) t) | |
150 | (call-next-method))) | |
151 | ((gethash object *seen-announcement*) | |
152 | nil) | |
153 | (t | |
154 | (setf (gethash object *seen-announcement*) t) | |
155 | (call-next-method)))) | |
156 | ||
157 | ;;;-------------------------------------------------------------------------- | |
abdf50aa MW |
158 | ;;; Utilities. |
159 | ||
160 | (defun banner (title output &key (blank-line-p t)) | |
161 | (format output "~&~%/*----- ~A ~A*/~%" | |
162 | title | |
163 | (make-string (- 77 2 5 1 (length title) 1 2) | |
164 | :initial-element #\-)) | |
165 | (when blank-line-p | |
166 | (terpri output))) | |
167 | ||
168 | ;;;-------------------------------------------------------------------------- | |
169 | ;;; Header output. | |
170 | ||
171 | (defun write-module-header (module) | |
172 | (let* ((file (merge-pathnames (make-pathname :type "H" :case :common) | |
173 | (module-name module))) | |
174 | (fakename (make-pathname :name (pathname-name file) | |
175 | :type (pathname-type file)))) | |
176 | (with-open-file (uoutput file | |
177 | :direction :output | |
178 | :if-exists :supersede | |
179 | :if-does-not-exist :create) | |
180 | (let ((output (make-instance 'position-aware-output-stream | |
181 | :stream uoutput | |
182 | :file fakename))) | |
183 | ||
184 | ;; Format the header and guards. | |
185 | (format output "~ | |
186 | /* -*-c-*- | |
187 | * | |
188 | * Header file generated by SOD for ~A | |
189 | */ | |
190 | ||
191 | #ifndef ~A | |
192 | #define ~:*~A | |
193 | ||
194 | #ifdef __cplusplus | |
195 | extern \"C\" { | |
196 | #endif~%" | |
197 | (namestring (module-name module)) | |
198 | (or (getf (module-plist module) 'include-guard) | |
199 | (with-output-to-string (guard) | |
200 | (let ((name (namestring file)) | |
201 | (uscore t)) | |
202 | (dotimes (i (length name)) | |
203 | (let ((ch (char name i))) | |
204 | (cond ((alphanumericp ch) | |
205 | (write-char (char-upcase ch) guard) | |
206 | (setf uscore nil)) | |
207 | ((not uscore) | |
208 | (write-char #\_ guard) | |
209 | (setf uscore t))))))))) | |
210 | ||
211 | ;; Forward declarations of all the structures and types. Nothing | |
212 | ;; interesting gets said here; this is just so that the user code | |
213 | ;; can talk meainingfully about the things we're meant to be | |
214 | ;; defining here. | |
215 | ;; | |
216 | ;; FIXME | |
217 | ||
218 | ;; The user fragments. | |
219 | (when (module-header-fragments module) | |
220 | (banner "User code" output) | |
221 | (dolist (frag (module-header-fragments module)) | |
1f1d88f5 | 222 | (princ frag output))) |
abdf50aa MW |
223 | |
224 | ;; The definitions of the necessary structures. | |
225 | ;; | |
226 | ;; FIXME | |
227 | ||
228 | ;; The definitions of the necessary direct-methods. | |
229 | ;; | |
230 | ;; FIXME | |
231 | ||
232 | ;; The trailer section. | |
233 | (banner "That's all, folks" output) | |
234 | (format output "~ | |
235 | #ifdef __cplusplus | |
236 | } | |
237 | #endif | |
238 | ||
239 | #endif~%"))))) | |
240 | ||
241 | ;;;-------------------------------------------------------------------------- | |
242 | ;;; Source output. | |
243 | ||
244 | (defun write-module-source (module) | |
245 | (let* ((file (merge-pathnames (make-pathname :type "C" :case :common) | |
246 | (module-name module))) | |
247 | (fakename (make-pathname :name (pathname-name file) | |
248 | :type (pathname-type file)))) | |
249 | (with-open-file (uoutput file | |
250 | :direction :output | |
251 | :if-exists :supersede | |
252 | :if-does-not-exist :create) | |
253 | (let ((output (make-instance 'position-aware-output-stream | |
254 | :stream uoutput | |
255 | :file fakename))) | |
256 | ||
257 | ;; Format the header. | |
258 | (format output "~ | |
259 | /* -*-c-*- | |
260 | * | |
261 | * Source file generated by SOD for ~A | |
262 | */~%" | |
263 | (namestring (module-name module))) | |
264 | ||
265 | ;; The user fragments. | |
266 | (when (module-source-fragments module) | |
267 | (banner "User code" output) | |
268 | (dolist (frag (module-source-fragments module)) | |
1f1d88f5 | 269 | (princ frag output))) |
abdf50aa MW |
270 | |
271 | ;; The definitions of the necessary tables. | |
272 | ;; | |
273 | ;; FIXME | |
274 | ||
275 | ;; The definitions of the necessary effective-methods. | |
276 | ;; | |
277 | ;; FIXME | |
278 | ||
279 | ;; The trailer section. | |
280 | (banner "That's all, folks" output :blank-line-p nil))))) | |
281 | ||
282 | ;;;----- That's all, folks -------------------------------------------------- |