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