Commit | Line | Data |
---|---|---|
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 | ||
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 | (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 | |
36 | arguments, 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)) |