Added &OPTIONAL to DESTRUCTURING-BIND. Currently it's just ignored (next step woudl...
[clinton/parenscript.git] / src / printer.lisp
1 (in-package "PARENSCRIPT")
2
3 (defvar *ps-print-pretty* t)
4 (defvar *indent-num-spaces* 4)
5 (defvar *js-string-delimiter* #\'
6 "Specifies which character should be used for delimiting strings.
7
8 This variable is used when you want to embed the resulting JavaScript
9 in an html attribute delimited by #\\\" as opposed to #\\', or
10 vice-versa.")
11
12 (defvar *indent-level*)
13
14 (defvar *psw-stream*)
15
16 (defun parenscript-print (form immediate?)
17 (declare (special immediate?))
18 (let ((*indent-level* 0)
19 (*psw-stream* (if immediate?
20 *psw-stream*
21 (make-string-output-stream)))
22 (%psw-accumulator ()))
23 (declare (special %psw-accumulator))
24 (if (and (listp form) (eq 'js:block (car form))) ; ignore top-level block
25 (loop for (statement . remaining) on (cdr form) do
26 (ps-print statement) (psw #\;) (when remaining (psw #\Newline)))
27 (ps-print form))
28 (unless immediate?
29 (reverse (cons (get-output-stream-string *psw-stream*) %psw-accumulator)))))
30
31 (defun psw (obj)
32 (declare (special %psw-accumulator immediate?))
33 (typecase obj
34 (string (write-string obj *psw-stream*))
35 (character (write-char obj *psw-stream*))
36 (otherwise
37 (if immediate?
38 (write-string (eval obj) *psw-stream*)
39 (setf %psw-accumulator
40 (cons obj
41 (cons (get-output-stream-string *psw-stream*)
42 %psw-accumulator)))))))
43
44 (defgeneric ps-print% (special-form-name special-form-args))
45
46 (defmacro defprinter (special-form content-args &body body)
47 "Given a special-form name and a destructuring lambda-list for its
48 arguments, defines a printer for that form using the given body."
49 (let ((sf (gensym))
50 (sf-args (gensym)))
51 `(defmethod ps-print% ((,sf (eql ',special-form)) ,sf-args)
52 (declare (ignorable ,sf))
53 (destructuring-bind ,content-args
54 ,sf-args
55 ,@body))))
56
57 (defgeneric ps-print (compiled-form))
58
59 (defmethod ps-print ((form null))) ; don't print top-level nils (ex: result of defining macros, etc.)
60
61 (defmethod ps-print ((s symbol))
62 (assert (keywordp s) nil "~S is not a symbol" s)
63 (ps-print (string-downcase s)))
64
65 (defmethod ps-print ((compiled-form cons))
66 (ps-print% (car compiled-form) (cdr compiled-form)))
67
68 (defun newline-and-indent ()
69 (if *ps-print-pretty*
70 (progn (psw #\Newline)
71 (loop repeat (* *indent-level* *indent-num-spaces*) do (psw #\Space)))
72 (psw #\Space)))
73
74 (defparameter *js-lisp-escaped-chars*
75 '((#\' . #\')
76 (#\\ . #\\)
77 (#\b . #\Backspace)
78 (#\f . #.(code-char 12))
79 (#\n . #\Newline)
80 (#\r . #\Return)
81 (#\t . #\Tab)))
82
83 (defmethod ps-print ((string string))
84 (flet ((lisp-special-char-to-js (lisp-char)
85 (car (rassoc lisp-char *js-lisp-escaped-chars*))))
86 (psw *js-string-delimiter*)
87 (loop for char across string
88 for code = (char-code char)
89 for special = (lisp-special-char-to-js char)
90 do (cond (special (psw #\\) (psw special))
91 ((or (<= code #x1f) (>= code #x80)) (format *psw-stream* "\\u~4,'0x" code))
92 (t (psw char))))
93 (psw *js-string-delimiter*)))
94
95 (defmethod ps-print ((number number))
96 (format *psw-stream* (if (integerp number) "~S" "~F") number))
97
98 ;;; expression and operator precedence rules
99
100 (defun expression-precedence (expr)
101 (if (consp expr)
102 (case (car expr)
103 ((js:slot-value js:aref) (op-precedence (car expr)))
104 (js:= (op-precedence 'js:=))
105 (js:? (op-precedence 'js:?))
106 (js:unary-operator (op-precedence (second expr)))
107 (operator (op-precedence (second expr)))
108 (otherwise -1))
109 -1))
110
111 (defprinter js:literal (str)
112 (psw str))
113
114 (defun print-comma-delimited-list (ps-forms)
115 (loop for (form . remaining) on ps-forms do
116 (ps-print form) (when remaining (psw ", "))))
117
118 (defprinter js:array (&rest initial-contents)
119 (psw #\[) (print-comma-delimited-list initial-contents) (psw #\]))
120
121 (defprinter js:aref (array indices)
122 (if (>= (expression-precedence array) (op-precedence 'js:aref))
123 (parenthesize-print array)
124 (ps-print array))
125 (loop for idx in indices do
126 (psw #\[) (ps-print idx) (psw #\])))
127
128 (defprinter js:variable (var)
129 (psw (symbol-to-js-string var)))
130
131 ;;; arithmetic operators
132 (defun parenthesize-print (ps-form)
133 (psw #\() (ps-print ps-form) (psw #\)))
134
135 (defprinter js:operator (op &rest args)
136 (loop for (arg . remaining) on args
137 with precedence = (op-precedence op) do
138 (if (>= (expression-precedence arg) precedence)
139 (parenthesize-print arg)
140 (ps-print arg))
141 (when remaining (format *psw-stream* " ~(~A~) " op))))
142
143 (defprinter js:unary-operator (op arg &key prefix space)
144 (when prefix (format *psw-stream* "~(~a~)~:[~; ~]" op space))
145 (if (> (expression-precedence arg)
146 (op-precedence (case op
147 (+ 'unary+)
148 (- 'unary-)
149 (t op))))
150 (parenthesize-print arg)
151 (ps-print arg))
152 (unless prefix (format *psw-stream* "~(~a~)" op)))
153
154 (defprinter js:funcall (fun-designator &rest args)
155 (funcall (if (member (car fun-designator) '(js:variable js:aref js:slot-value js:funcall))
156 #'ps-print
157 #'parenthesize-print)
158 fun-designator)
159 (psw #\() (print-comma-delimited-list args) (psw #\)))
160
161 (defprinter js:|,| (&rest expressions)
162 (psw #\()
163 (loop for (exp . remaining) on expressions do
164 (ps-print exp) (when remaining (psw ", ")))
165 (psw #\)))
166
167 (defprinter js:block (&rest statements)
168 (psw #\{)
169 (incf *indent-level*)
170 (dolist (statement statements)
171 (newline-and-indent) (ps-print statement) (psw #\;))
172 (decf *indent-level*)
173 (newline-and-indent)
174 (psw #\}))
175
176 (defprinter js:lambda (args body)
177 (print-fun-def nil args body))
178
179 (defprinter js:defun (name args body)
180 (print-fun-def name args body))
181
182 (defun print-fun-def (name args body-block)
183 (format *psw-stream* "function ~:[~;~A~](" name (symbol-to-js-string name))
184 (loop for (arg . remaining) on args do
185 (psw (symbol-to-js-string arg)) (when remaining (psw ", ")))
186 (psw ") ")
187 (ps-print body-block))
188
189 (defprinter js:object (&rest slot-defs)
190 (psw "{ ")
191 (loop for ((slot-name . slot-value) . remaining) on slot-defs do
192 (ps-print slot-name) (psw " : ") (ps-print slot-value)
193 (when remaining (psw ", ")))
194 (psw " }"))
195
196 (defprinter js:slot-value (obj slot)
197 (if (or (> (expression-precedence obj) (op-precedence 'js:slot-value))
198 (numberp obj)
199 (and (listp obj) (member (car obj) '(js:lambda js:object))))
200 (parenthesize-print obj)
201 (ps-print obj))
202 (if (and (symbolp slot) (not (keywordp slot)))
203 (progn (psw #\.) (psw (symbol-to-js-string slot)))
204 (progn (psw #\[) (ps-print slot) (psw #\]))))
205
206 (defprinter js:if (test consequent &rest clauses)
207 (psw "if (") (ps-print test) (psw ") ")
208 (ps-print consequent)
209 (loop while clauses do
210 (ecase (car clauses)
211 (:else-if (psw " else if (") (ps-print (cadr clauses)) (psw ") ")
212 (ps-print (caddr clauses))
213 (setf clauses (cdddr clauses)))
214 (:else (psw " else ")
215 (ps-print (cadr clauses))
216 (return)))))
217
218 (defprinter js:? (test then else)
219 (if (>= (expression-precedence test) (op-precedence 'js:?))
220 (parenthesize-print test)
221 (ps-print test))
222 (psw " ? ")
223 (if (>= (expression-precedence then) (op-precedence 'js:?))
224 (parenthesize-print then)
225 (ps-print then))
226 (psw " : ")
227 (if (>= (expression-precedence else) (op-precedence 'js:?))
228 (parenthesize-print else)
229 (ps-print else)))
230
231 (defprinter js:= (lhs rhs)
232 (ps-print lhs) (psw " = ") (ps-print rhs))
233
234 (defprinter js:var (var-name &rest var-value)
235 (psw "var ")
236 (psw (symbol-to-js-string var-name))
237 (when var-value
238 (psw " = ")
239 (ps-print (car var-value))))
240
241 (defprinter js:break (&optional label)
242 (psw "break")
243 (when label
244 (psw " ")
245 (psw (symbol-to-js-string label))))
246
247 (defprinter js:continue (&optional label)
248 (psw "continue")
249 (when label
250 (psw " ")
251 (psw (symbol-to-js-string label))))
252
253 ;;; iteration
254 (defprinter js:for (label vars tests steps body-block)
255 (when label (psw (symbol-to-js-string label)) (psw ": ") (newline-and-indent))
256 (psw "for (")
257 (loop for ((var-name . var-init) . remaining) on vars
258 for decl = "var " then "" do
259 (psw decl) (psw (symbol-to-js-string var-name)) (psw " = ") (ps-print var-init) (when remaining (psw ", ")))
260 (psw "; ")
261 (loop for (test . remaining) on tests do
262 (ps-print test) (when remaining (psw ", ")))
263 (psw "; ")
264 (loop for (step . remaining) on steps do
265 (ps-print step) (when remaining (psw ", ")))
266 (psw ") ")
267 (ps-print body-block))
268
269 (defprinter js:for-in (var object body-block)
270 (psw "for (var ") (ps-print var) (psw " in ")
271 (if (> (expression-precedence object) (op-precedence 'in))
272 (parenthesize-print object)
273 (ps-print object))
274 (psw ") ")
275 (ps-print body-block))
276
277 (defprinter js:while (test body-block)
278 (psw "while (") (ps-print test) (psw ") ")
279 (ps-print body-block))
280
281 (defprinter js:with (expression body-block)
282 (psw "with (") (ps-print expression) (psw ") ")
283 (ps-print body-block))
284
285 (defprinter js:switch (test clauses)
286 (flet ((print-body-statements (body-statements)
287 (incf *indent-level*)
288 (loop for statement in body-statements do
289 (progn (newline-and-indent)
290 (ps-print statement)
291 (psw #\;)))
292 (decf *indent-level*)))
293 (psw "switch (") (ps-print test) (psw ") {")
294 (loop for (val . statements) in clauses
295 do (progn (newline-and-indent)
296 (if (eq val 'default)
297 (progn (psw "default: ")
298 (print-body-statements statements))
299 (progn (psw "case ")
300 (ps-print val)
301 (psw #\:)
302 (print-body-statements statements)))))
303 (newline-and-indent)
304 (psw #\})))
305
306 (defprinter js:try (body-block &key catch finally)
307 (psw "try ")
308 (ps-print body-block)
309 (when catch
310 (psw " catch (") (psw (symbol-to-js-string (first catch))) (psw ") ")
311 (ps-print (second catch)))
312 (when finally
313 (psw " finally ")
314 (ps-print finally)))
315
316 ;;; regex
317 (defprinter js:regex (regex)
318 (let ((slash (unless (and (> (length regex) 0) (char= (char regex 0) #\/)) "/")))
319 (psw (concatenate 'string slash regex slash))))
320
321 ;;; conditional compilation
322 (defprinter js:cc-if (test &rest body)
323 (psw "/*@if ")
324 (ps-print test)
325 (incf *indent-level*)
326 (dolist (form body)
327 (newline-and-indent) (ps-print form) (psw #\;))
328 (decf *indent-level*)
329 (newline-and-indent)
330 (psw "@end @*/"))
331
332 (defprinter js:instanceof (value type)
333 (psw #\()
334 (if (> (expression-precedence value) (op-precedence 'js:instanceof))
335 (parenthesize-print value)
336 (ps-print value))
337 (psw " instanceof ")
338 (if (> (expression-precedence type) (op-precedence 'js:instanceof))
339 (parenthesize-print type)
340 (ps-print type))
341 (psw #\)))
342
343 (defprinter js:escape (literal-js)
344 ;; literal-js should be a form that evaluates to a string containing valid JavaScript
345 (psw literal-js))
346
347 ;;; named statements
348 (defprinter js:throw (x)
349 (psw "throw ") (ps-print x))
350
351 (defprinter js:return (x)
352 (psw "return ") (ps-print x))