Modified the printer so that PS and PS-INLINE compile and print
[clinton/parenscript.git] / src / printer.lisp
CommitLineData
4a987e2b 1(in-package :parenscript)
9da682ca 2
cb8f8e58
VS
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
8This variable is used when you want to embed the resulting JavaScript
9in an html attribute delimited by #\\\" as opposed to #\\', or
10vice-versa.")
11
12(defvar *indent-level*)
13(defvar *print-accumulator*)
14
15(defmethod parenscript-print (form)
16 (let ((*indent-level* 0)
17 (*print-accumulator* ()))
18 (if (and (listp form) (eql 'js-block (car form))) ; ignore top-level block
19 (loop for (statement . remaining) on (third form) do
20 (ps-print statement) (psw ";") (when remaining (psw #\Newline)))
21 (ps-print form))
22 (reduce (lambda (acc next-token)
23 (if (and (stringp next-token)
24 (stringp (car (last acc))))
25 (append (butlast acc) (list (concatenate 'string (car (last acc)) next-token)))
26 (append acc (list next-token))))
27 (cons () (reverse *print-accumulator*)))))
28
29(defun psw (obj)
30 (push (if (characterp obj) (string obj) obj) *print-accumulator*))
4ff112cb 31
839600e9 32(defgeneric ps-print% (special-form-name special-form-args))
9da682ca 33
4a987e2b
VS
34(defmacro defprinter (special-form content-args &body body)
35 "Given a special-form name and a destructuring lambda-list for its
36arguments, defines a printer for that form using the given body."
37 (let ((sf (gensym))
38 (sf-args (gensym)))
839600e9 39 `(defmethod ps-print% ((,sf (eql ',special-form)) ,sf-args)
89aa7077 40 (declare (ignorable ,sf))
4a987e2b
VS
41 (destructuring-bind ,content-args
42 ,sf-args
43 ,@body))))
44
839600e9 45(defgeneric ps-print (compiled-form))
4a987e2b 46
cb8f8e58 47(defmethod ps-print ((form null))) ; don't print top-level nils (ex: result of defining macros, etc.)
53a1beac 48
f2bb932e
VS
49(defmethod ps-print ((s symbol))
50 (assert (keywordp s))
51 (ps-print (js-translate-symbol s)))
52
839600e9 53(defmethod ps-print ((compiled-form cons))
839600e9 54 (ps-print% (car compiled-form) (cdr compiled-form)))
4a987e2b 55
116f7450 56(defun newline-and-indent ()
cb8f8e58
VS
57 (if *ps-print-pretty*
58 (when (and (stringp (car *print-accumulator*))
59 (not (char= #\Newline (char (car *print-accumulator*) (1- (length (car *print-accumulator*))))))
60 (psw #\Newline))
61 (loop repeat (* *indent-level* *indent-num-spaces*) do (psw #\Space)))
62 (psw #\Space)))
4a987e2b
VS
63
64(defparameter *js-lisp-escaped-chars*
65 '((#\' . #\')
66 (#\\ . #\\)
67 (#\b . #\Backspace)
68 (#\f . #.(code-char 12))
69 (#\n . #\Newline)
70 (#\r . #\Return)
71 (#\t . #\Tab)))
72
839600e9 73(defmethod ps-print ((string string))
4a987e2b
VS
74 (flet ((lisp-special-char-to-js (lisp-char)
75 (car (rassoc lisp-char *js-lisp-escaped-chars*))))
c639fe7f 76 (psw *js-string-delimiter*)
839600e9
VS
77 (loop for char across string
78 for code = (char-code char)
79 for special = (lisp-special-char-to-js char)
c639fe7f 80 do (cond (special (psw #\\) (psw special))
cb8f8e58 81 ((or (<= code #x1f) (>= code #x80)) (psw (format nil "\\u~4,'0x" code)))
c639fe7f
VS
82 (t (psw char))))
83 (psw *js-string-delimiter*)))
839600e9
VS
84
85(defmethod ps-print ((number number))
cb8f8e58 86 (psw (format nil (if (integerp number) "~S" "~F") number)))
4a987e2b
VS
87
88;;; expression and operator precedence rules
89
90(defun expression-precedence (expr)
91 (if (consp expr)
92 (case (car expr)
6a46e1ef 93 ((js-slot-value js-aref) (op-precedence (car expr)))
4a987e2b 94 (js-assign (op-precedence '=))
cb1e91d0 95 (js-expression-if (op-precedence 'js-expression-if))
1222b323 96 (unary-operator (op-precedence (second expr)))
4a987e2b
VS
97 (operator (op-precedence (second expr)))
98 (otherwise 0))
99 0))
100
101(eval-when (:compile-toplevel :load-toplevel :execute)
f3847d1c 102 (defparameter *op-precedence-hash* (make-hash-table :test 'eq))
4a987e2b 103
4a987e2b 104 (let ((precedence 1))
6a46e1ef
TC
105 (dolist (ops '((new js-slot-value js-aref)
106 (postfix++ postfix--)
107 (delete void typeof ++ -- unary+ unary- ~ !)
4a987e2b
VS
108 (* / %)
109 (+ -)
6a46e1ef
TC
110 (<< >> >>>)
111 (< > <= >= js-instance-of in)
112 (== != === !== eql)
4a987e2b
VS
113 (&)
114 (^)
115 (\|)
116 (\&\& and)
117 (\|\| or)
6a46e1ef
TC
118 (js-expression-if)
119 (= *= /= %= += -= <<= >>= >>>= \&\= ^= \|= js-assign)
4a987e2b
VS
120 (comma)))
121 (dolist (op ops)
f3847d1c 122 (setf (gethash op *op-precedence-hash*) precedence))
4a987e2b
VS
123 (incf precedence)))
124
125 (defun op-precedence (op)
f3847d1c 126 (gethash op *op-precedence-hash*)))
9da682ca 127
462ca010 128(defprinter ps-quote (val)
4a987e2b 129 (if (null val)
4ff112cb 130 (psw "null")
4a987e2b 131 (error "Cannot translate quoted value ~S to javascript" val)))
cc4f1551 132
4a987e2b 133(defprinter js-literal (str)
4ff112cb 134 (psw str))
46f794a4 135
4ff112cb
VS
136(defun print-comma-delimited-list (ps-forms)
137 (loop for (form . remaining) on ps-forms do
138 (ps-print form) (when remaining (psw ", "))))
cc4f1551 139
4a987e2b 140(defprinter array-literal (&rest initial-contents)
4ff112cb 141 (psw #\[) (print-comma-delimited-list initial-contents) (psw #\]))
839600e9
VS
142
143(defprinter js-aref (array indices)
2ee6879d 144 (if (>= (expression-precedence array) #.(op-precedence 'js-aref))
d43d746e
TC
145 (parenthesize-print array)
146 (ps-print array))
839600e9 147 (loop for idx in indices do
4ff112cb 148 (psw #\[) (ps-print idx) (psw #\])))
839600e9
VS
149
150(defprinter object-literal (&rest slot-definitions)
4ff112cb
VS
151 (psw #\{)
152 (loop for ((key . value) . remaining) on slot-definitions do
cb8f8e58 153 (psw (format nil "~A: " (js-translate-symbol key)))
4ff112cb
VS
154 (ps-print value)
155 (when remaining (psw ", ")))
156 (psw " }"))
4a987e2b
VS
157
158(defprinter js-variable (var)
4ff112cb 159 (psw (js-translate-symbol var)))
cc4f1551
RD
160
161;;; arithmetic operators
839600e9 162(defun parenthesize-print (ps-form)
4ff112cb 163 (psw #\() (ps-print ps-form) (psw #\)))
cc4f1551 164
4a987e2b 165(defprinter operator (op args)
4ff112cb
VS
166 (loop for (arg . remaining) on args
167 with precedence = (op-precedence op) do
168 (if (>= (expression-precedence arg) precedence)
169 (parenthesize-print arg)
170 (ps-print arg))
cb8f8e58 171 (when remaining (psw (format nil " ~(~A~) " op)))))
4a987e2b 172
6a46e1ef 173(defprinter unary-operator (op arg &key prefix space)
cb8f8e58 174 (when prefix (psw (format nil "~(~a~)~:[~; ~]" op space)))
6a46e1ef
TC
175 (if (> (expression-precedence arg)
176 (op-precedence (case op
177 (+ 'unary+)
178 (- 'unary-)
179 (t op))))
839600e9
VS
180 (parenthesize-print arg)
181 (ps-print arg))
cb8f8e58 182 (unless prefix (psw (format nil "~(~a~)" op))))
4a987e2b
VS
183
184;;; function and method calls
185(defprinter js-funcall (fun-designator args)
839600e9
VS
186 (cond ((member (car fun-designator) '(js-variable js-aref js-slot-value))
187 (ps-print fun-designator))
188 ((eql 'js-lambda (car fun-designator))
4ff112cb 189 (psw #\() (ps-print fun-designator) (psw #\)))
839600e9
VS
190 ((eql 'js-funcall (car fun-designator))
191 (ps-print fun-designator)))
4ff112cb 192 (psw #\() (print-comma-delimited-list args) (psw #\)))
4a987e2b
VS
193
194(defprinter js-method-call (method object args)
839600e9
VS
195 ;; TODO: this may not be the best way to add ()'s around lambdas
196 ;; probably there is or should be a more general solution working
e0032a96 197 ;; in other situations involving lambdas
116f7450 198 (if (or (numberp object) (and (consp object) (member (car object) '(js-lambda js-object operator js-expression-if))))
839600e9
VS
199 (parenthesize-print object)
200 (ps-print object))
4ff112cb
VS
201 (psw (js-translate-symbol method))
202 (psw #\() (print-comma-delimited-list args) (psw #\)))
cc4f1551 203
e0032a96
VS
204(defprinter js-block (block-type statements)
205 (case block-type
206 (:statement
207 (psw #\{)
208 (incf *indent-level*)
209 (dolist (statement statements)
210 (newline-and-indent) (ps-print statement) (psw #\;))
211 (decf *indent-level*)
212 (newline-and-indent)
213 (psw #\}))
214 (:expression
215 (psw #\()
216 (loop for (statement . remaining) on statements do
217 (ps-print statement) (when remaining (psw ", ")))
218 (psw #\)))))
4a987e2b
VS
219
220(defprinter js-lambda (args body)
839600e9 221 (print-fun-def nil args body))
4a987e2b
VS
222
223(defprinter js-defun (name args body)
839600e9
VS
224 (print-fun-def name args body))
225
116f7450 226(defun print-fun-def (name args body-block)
cb8f8e58 227 (psw (format nil "function ~:[~;~A~](" name (js-translate-symbol name)))
4ff112cb
VS
228 (loop for (arg . remaining) on args do
229 (psw (js-translate-symbol arg)) (when remaining (psw ", ")))
230 (psw ") ")
116f7450 231 (ps-print body-block))
cc4f1551 232
4ff112cb 233;;; object literals
4a987e2b 234(defprinter js-object (slot-defs)
4ff112cb
VS
235 (psw "{ ")
236 (loop for ((slot-name slot-value) . remaining) on slot-defs do
462ca010 237 (if (and (listp slot-name) (eql 'ps-quote (car slot-name)) (symbolp (second slot-name)))
4ff112cb
VS
238 (psw (js-translate-symbol (second slot-name)))
239 (ps-print slot-name))
240 (psw " : ")
241 (ps-print slot-value)
242 (when remaining (psw ", ")))
243 (psw " }"))
cc4f1551 244
4a987e2b 245(defprinter js-slot-value (obj slot)
88350c69 246 (if (> (expression-precedence obj) #.(op-precedence 'js-slot-value))
839600e9
VS
247 (parenthesize-print obj)
248 (ps-print obj))
462ca010 249 (if (and (listp slot) (eql 'ps-quote (car slot)))
4ff112cb 250 (progn (psw #\.)
839600e9 251 (if (symbolp (second slot))
4ff112cb 252 (psw (js-translate-symbol (second slot)))
839600e9 253 (ps-print slot)))
4ff112cb 254 (progn (psw #\[) (ps-print slot) (psw #\]))))
cc4f1551 255
0949f072 256(defprinter js-cond-statement (clauses)
839600e9 257 (loop for (test body-block) in clauses
4ff112cb
VS
258 for start = "if (" then " else if (" do
259 (if (equalp test "true")
260 (psw " else ")
261 (progn (psw start)
262 (ps-print test)
263 (psw ") ")))
264 (ps-print body-block)))
4a987e2b 265
116f7450 266(defprinter js-statement-if (test then-block else-block)
4ff112cb 267 (psw "if (") (ps-print test) (psw ") ")
116f7450
VS
268 (ps-print then-block)
269 (when else-block
4ff112cb 270 (psw " else ")
116f7450 271 (ps-print else-block)))
4a987e2b
VS
272
273(defprinter js-expression-if (test then else)
839600e9 274 (ps-print test)
4ff112cb 275 (psw " ? ")
839600e9
VS
276 (if (>= (expression-precedence then) (op-precedence 'js-expression-if))
277 (parenthesize-print then)
278 (ps-print then))
4ff112cb 279 (psw " : ")
5705b542
VS
280 (if (>= (expression-precedence else) (op-precedence 'js-expression-if))
281 (parenthesize-print else)
282 (ps-print else)))
cc4f1551 283
4a987e2b 284(defprinter js-assign (lhs rhs)
4ff112cb 285 (ps-print lhs) (psw " = ") (ps-print rhs))
cc4f1551 286
58c4ef4f 287(defprinter js-var (var-name &rest var-value)
4ff112cb
VS
288 (psw "var ")
289 (psw (js-translate-symbol var-name))
839600e9 290 (when var-value
4ff112cb 291 (psw " = ")
839600e9 292 (ps-print (car var-value))))
cc4f1551 293
c452748e
TC
294(defprinter js-break (&optional label)
295 (psw "break")
296 (when label
297 (psw " ")
298 (psw (js-translate-symbol label))))
299
300(defprinter js-continue (&optional label)
301 (psw "continue")
302 (when label
303 (psw " ")
304 (psw (js-translate-symbol label))))
305
cc4f1551 306;;; iteration
6a2ce72d
TC
307(defprinter js-for (label vars tests steps body-block)
308 (when label (psw (js-translate-symbol label)) (psw ": ") (newline-and-indent))
4ff112cb
VS
309 (psw "for (")
310 (loop for ((var-name . var-init) . remaining) on vars
311 for decl = "var " then "" do
312 (psw decl) (psw (js-translate-symbol var-name)) (psw " = ") (ps-print var-init) (when remaining (psw ", ")))
313 (psw "; ")
6a2ce72d
TC
314 (loop for (test . remaining) on tests do
315 (ps-print test) (when remaining (psw ", ")))
4ff112cb 316 (psw "; ")
6a2ce72d
TC
317 (loop for (step . remaining) on steps do
318 (ps-print step) (when remaining (psw ", ")))
4ff112cb 319 (psw ") ")
116f7450 320 (ps-print body-block))
cc4f1551 321
6a2ce72d 322(defprinter js-for-in (var object body-block)
6a46e1ef
TC
323 (psw "for (") (ps-print var) (psw " in ")
324 (if (> (expression-precedence object) (op-precedence 'in))
325 (parenthesize-print object)
326 (ps-print object))
327 (psw ") ")
116f7450 328 (ps-print body-block))
cc4f1551 329
4a987e2b 330(defprinter js-while (test body-block)
4ff112cb 331 (psw "while (") (ps-print test) (psw ") ")
116f7450 332 (ps-print body-block))
4a987e2b
VS
333
334(defprinter js-with (expression body-block)
4ff112cb 335 (psw "with (") (ps-print expression) (psw ") ")
116f7450 336 (ps-print body-block))
4a987e2b
VS
337
338(defprinter js-switch (test clauses)
116f7450
VS
339 (flet ((print-body-statements (body-statements)
340 (incf *indent-level*)
341 (loop for statement in body-statements do
342 (progn (newline-and-indent)
343 (ps-print statement)
4ff112cb 344 (psw #\;)))
116f7450 345 (decf *indent-level*)))
4ff112cb 346 (psw "switch (") (ps-print test) (psw ") {")
e0032a96 347 (loop for (val . statements) in clauses
116f7450 348 do (progn (newline-and-indent)
675edae3 349 (if (eq val 'default)
4ff112cb 350 (progn (psw "default: ")
e0032a96 351 (print-body-statements statements))
4ff112cb 352 (progn (psw "case ")
116f7450 353 (ps-print val)
4ff112cb 354 (psw #\:)
e0032a96
VS
355 (print-body-statements statements)))))
356 (newline-and-indent)
4ff112cb 357 (psw #\})))
116f7450
VS
358
359(defprinter js-try (body-block &key catch finally)
4ff112cb 360 (psw "try ")
116f7450 361 (ps-print body-block)
839600e9 362 (when catch
4ff112cb 363 (psw " catch (") (psw (js-translate-symbol (first catch))) (psw ") ")
839600e9
VS
364 (ps-print (second catch)))
365 (when finally
4ff112cb 366 (psw " finally ")
116f7450 367 (ps-print finally)))
cc4f1551
RD
368
369;;; regex
4a987e2b
VS
370(defprinter js-regex (regex)
371 (flet ((first-slash-p (string)
839600e9 372 (and (> (length string) 0) (char= (char string 0) #\/))))
4a987e2b 373 (let ((slash (unless (first-slash-p regex) "/")))
cb8f8e58 374 (psw (format nil (concatenate 'string slash "~A" slash) regex)))))
cc4f1551 375
cc4f1551 376;;; conditional compilation
4a987e2b 377(defprinter cc-if (test body-forms)
4ff112cb 378 (psw "/*@if ")
839600e9 379 (ps-print test)
116f7450 380 (incf *indent-level*)
839600e9 381 (dolist (form body-forms)
4ff112cb 382 (newline-and-indent) (ps-print form) (psw #\;))
116f7450
VS
383 (decf *indent-level*)
384 (newline-and-indent)
4ff112cb 385 (psw "@end @*/"))
cc4f1551 386
4a987e2b 387(defprinter js-instanceof (value type)
6a46e1ef
TC
388 (psw #\()
389 (if (> (expression-precedence value) (op-precedence 'js-instance-of))
390 (parenthesize-print value)
391 (ps-print value))
392 (psw " instanceof ")
393 (if (> (expression-precedence type) (op-precedence 'js-instance-of))
394 (parenthesize-print type)
395 (ps-print type))
396 (psw #\)))
397
cb8f8e58
VS
398(defprinter js-escape (lisp-form)
399 (psw `(ps1* ,lisp-form)))
400
6a46e1ef
TC
401;;; named statements
402(macrolet ((def-stmt-printer (&rest stmts)
403 `(progn ,@(mapcar (lambda (stmt)
404 `(defprinter ,(intern (format nil "JS-~a" stmt)) (expr)
cb8f8e58 405 (psw (format nil "~(~a~) " ',stmt))
6a46e1ef
TC
406 (ps-print expr)))
407 stmts))))
408 (def-stmt-printer throw return))