1 (in-package :parenscript
)
3 (defvar *ps-output-stream
*)
5 (defmethod parenscript-print (ps-form &optional
*ps-output-stream
*)
6 (setf *indent-level
* 0)
7 (flet ((print-ps (form)
8 (let ((*standard-output
* *ps-output-stream
*))
9 (if (and (listp form
) (eql 'js-block
(car form
))) ;; ignore top-level block
10 (dolist (statement (third form
))
12 (format *ps-output-stream
* ";~%"))
14 (if *ps-output-stream
*
16 (with-output-to-string (*ps-output-stream
*)
17 (print-ps ps-form
)))))
19 (defgeneric ps-print%
(special-form-name special-form-args
))
21 (defmacro defprinter
(special-form content-args
&body body
)
22 "Given a special-form name and a destructuring lambda-list for its
23 arguments, defines a printer for that form using the given body."
26 `(defmethod ps-print%
((,sf
(eql ',special-form
)) ,sf-args
)
27 (declare (ignore ,sf
))
28 (destructuring-bind ,content-args
32 (defgeneric ps-print
(compiled-form))
34 (defmethod ps-print ((form null
)) ;; don't print nils (ex: result of defining macros, etc.)
37 (defmethod ps-print ((compiled-form cons
))
38 "Prints the given compiled ParenScript form starting at the given
40 (ps-print%
(car compiled-form
) (cdr compiled-form
)))
44 (defparameter *indent-level
* 0)
45 (defparameter *indent-num-space
* 4)
47 (defun newline-and-indent ()
49 (loop repeat
(* *indent-level
* *indent-num-space
*)
50 do
(write-char #\Space
))))
53 (defvar *js-quote-char
* #\'
54 "Specifies which character JS should use for delimiting strings.
56 This variable is useful when have to embed some javascript code
57 in an html attribute delimited by #\\\" as opposed to #\\', or
60 (defparameter *js-lisp-escaped-chars
*
64 (#\f .
#.
(code-char 12))
69 (defmethod ps-print ((string string
))
70 (flet ((lisp-special-char-to-js (lisp-char)
71 (car (rassoc lisp-char
*js-lisp-escaped-chars
*))))
72 (write-char *js-quote-char
*)
73 (loop for char across string
74 for code
= (char-code char
)
75 for special
= (lisp-special-char-to-js char
)
76 do
(cond (special (write-char #\\)
78 ((or (<= code
#x1f
) (>= code
#x80
))
79 (format *ps-output-stream
* "\\u~4,'0x" code
))
80 (t (write-char char
)))
81 finally
(write-char *js-quote-char
*))))
83 (defmethod ps-print ((number number
))
84 (format *ps-output-stream
* (if (integerp number
) "~S" "~F") number
))
86 ;;; expression and operator precedence rules
88 (defun expression-precedence (expr)
91 (js-expression-if (op-precedence 'js-expression-if
))
92 (js-assign (op-precedence '=))
93 (operator (op-precedence (second expr
)))
97 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
98 (defparameter *op-precedence-hash
* (make-hash-table :test
#'equal
))
100 ;;; generate the operator precedences from *OP-PRECEDENCES*
101 (let ((precedence 1))
102 (dolist (ops '((js-aref)
110 (in js-expression-if
)
118 (js-assign *= /= %
= += -
= <<= >>= >>>= \
&= ^
= \|
=)
121 (let ((op-name (symbol-name op
)))
122 (setf (gethash op-name
*op-precedence-hash
*) precedence
)))
125 (defun op-precedence (op)
126 (gethash (if (symbolp op
)
129 *op-precedence-hash
*)))
131 (defprinter script-quote
(val)
133 (write-string "null")
134 (error "Cannot translate quoted value ~S to javascript" val
)))
136 (defprinter js-literal
(str)
139 (defprinter js-keyword
(str)
142 (defun print-comma-list (ps-forms)
143 (loop for
(form . rest
) on ps-forms
145 unless rest do
(setf after
"")
146 doing
(progn (ps-print form
)
147 (write-string after
))))
149 (defprinter array-literal
(&rest initial-contents
)
151 (print-comma-list initial-contents
)
154 (defprinter js-aref
(array indices
)
156 (loop for idx in indices do
157 (progn (write-char #\
[)
161 (defprinter object-literal
(&rest slot-definitions
)
163 (loop for
((key . value
) . rest
) on slot-definitions
165 unless rest do
(setf after
"")
166 doing
(progn (format *ps-output-stream
* "~A: " (js-translate-symbol key
))
168 (write-string after
)))
171 (defprinter js-variable
(var)
172 (write-string (js-translate-symbol var
)))
174 ;;; arithmetic operators
175 (defun script-convert-op-name (op)
184 (defun parenthesize-print (ps-form)
189 (defprinter operator
(op args
)
190 (loop for
(arg . rest
) on args
191 with precedence
= (op-precedence op
)
192 with op-string
= (format nil
" ~A " op
)
193 unless rest do
(setf op-string
"")
194 do
(progn (if (>= (expression-precedence arg
) precedence
)
195 (parenthesize-print arg
)
197 (write-string op-string
))))
199 (defprinter unary-operator
(op arg
&key prefix
)
202 (if (and (listp arg
) (eql 'operator
(car arg
)))
203 (parenthesize-print arg
)
208 ;;; function and method calls
209 (defprinter js-funcall
(fun-designator args
)
210 (cond ((member (car fun-designator
) '(js-variable js-aref js-slot-value
))
211 (ps-print fun-designator
))
212 ((eql 'js-lambda
(car fun-designator
))
214 (ps-print fun-designator
)
216 ((eql 'js-funcall
(car fun-designator
))
217 (ps-print fun-designator
)))
219 (print-comma-list args
)
222 (defprinter js-method-call
(method object args
)
223 ;; TODO: this may not be the best way to add ()'s around lambdas
224 ;; probably there is or should be a more general solution working
225 ;; in other situations involving lambda's
226 (if (or (numberp object
) (and (consp object
) (member (car object
) '(js-lambda js-object operator js-expression-if
))))
227 (parenthesize-print object
)
229 (write-string (js-translate-symbol method
))
231 (print-comma-list args
)
234 (defprinter js-block
(statement-p statements
)
236 (progn (write-char #\
{)
237 (incf *indent-level
*)
238 (loop for statement in statements
239 do
(progn (newline-and-indent)
242 (decf *indent-level
*)
245 (progn (write-char #\
()
246 (loop for
(statement . rest
) on statements
248 unless rest do
(setf after
"")
249 do
(progn (ps-print statement
)
250 (write-string after
)))
253 (defprinter js-lambda
(args body
)
254 (print-fun-def nil args body
))
256 (defprinter js-defun
(name args body
)
257 (print-fun-def name args body
))
259 (defun print-fun-def (name args body-block
)
260 (format *ps-output-stream
* "function ~:[~;~A~](" name
(js-translate-symbol name
))
261 (loop for
(arg . rest
) on args
263 unless rest do
(setf after
"")
264 do
(progn (write-string (js-translate-symbol arg
))
265 (write-string after
))
266 finally
(write-string ") "))
267 (ps-print body-block
))
270 (defprinter js-object
(slot-defs)
272 (loop for
((slot-name slot-value
) . rest
) on slot-defs
274 unless rest do
(setf after
"")
275 do
(progn (if (and (listp slot-name
) (eql 'script-quote
(car slot-name
)) (symbolp (second slot-name
)))
276 (write-string (js-translate-symbol (second slot-name
)))
277 (ps-print slot-name
))
279 (ps-print slot-value
)
280 (write-string after
)))
283 (defprinter js-slot-value
(obj slot
)
284 (if (and (listp obj
) (member (car obj
) '(js-expression-if)))
285 (parenthesize-print obj
)
287 (if (and (listp slot
) (eql 'script-quote
(car slot
)))
288 (progn (write-char #\.
)
289 (if (symbolp (second slot
))
290 (write-string (js-translate-symbol (second slot
)))
292 (progn (write-char #\
[)
297 (defprinter js-cond-statement
(clauses)
298 (loop for
(test body-block
) in clauses
299 for start
= "if (" then
" else if ("
300 do
(progn (if (equalp test
"true")
301 (write-string " else ")
302 (progn (write-string start
)
304 (write-string ") ")))
305 (ps-print body-block
))))
307 (defprinter js-statement-if
(test then-block else-block
)
308 (write-string "if (")
311 (ps-print then-block
)
313 (write-string " else ")
314 (ps-print else-block
)))
316 (defprinter js-expression-if
(test then else
)
319 (if (>= (expression-precedence then
) (op-precedence 'js-expression-if
))
320 (parenthesize-print then
)
323 (if (>= (expression-precedence else
) (op-precedence 'js-expression-if
))
324 (parenthesize-print else
)
327 (defprinter js-assign
(lhs rhs
)
332 (defprinter js-defvar
(var-name &rest var-value
)
333 (write-string "var ")
334 (write-string (js-translate-symbol var-name
))
337 (ps-print (car var-value
))))
340 (defprinter js-for
(vars steps test body-block
)
341 (write-string "for (")
342 (loop for
((var-name . var-init
) . rest
) on vars
343 for decl
= "var " then
""
345 unless rest do
(setf after
"")
346 do
(progn (write-string decl
)
347 (write-string (js-translate-symbol var-name
))
350 (write-string after
)))
354 (loop for
((var-name . var-init
) . rest
) on vars
357 unless rest do
(setf after
"")
358 do
(progn (write-string (js-translate-symbol var-name
))
361 (write-string after
)))
363 (ps-print body-block
))
365 (defprinter js-for-each
(var object body-block
)
366 (write-string "for (var ")
367 (write-string (js-translate-symbol var
))
368 (write-string " in ")
371 (ps-print body-block
))
373 (defprinter js-while
(test body-block
)
374 (write-string "while (")
377 (ps-print body-block
))
379 (defprinter js-with
(expression body-block
)
380 (write-string "with (")
381 (ps-print expression
)
383 (ps-print body-block
))
385 (defprinter js-switch
(test clauses
)
386 (flet ((print-body-statements (body-statements)
387 (incf *indent-level
*)
388 (loop for statement in body-statements do
389 (progn (newline-and-indent)
392 (decf *indent-level
*)))
393 (write-string "switch (")
396 (loop for
(val body-block
) in clauses
397 for body-statements
= (third body-block
)
398 do
(progn (newline-and-indent)
399 (if (eql val
'default
)
400 (progn (write-string "default: ")
401 (print-body-statements body-statements
))
402 (progn (write-string "case ")
405 (print-body-statements body-statements
)))))
408 (defprinter js-try
(body-block &key catch finally
)
409 (write-string "try ")
410 (ps-print body-block
)
412 (write-string " catch (")
413 (write-string (js-translate-symbol (first catch
)))
415 (ps-print (second catch
)))
417 (write-string " finally ")
421 (defprinter js-regex
(regex)
422 (flet ((first-slash-p (string)
423 (and (> (length string
) 0) (char= (char string
0) #\
/))))
424 (let ((slash (unless (first-slash-p regex
) "/")))
425 (format *ps-output-stream
* (concatenate 'string slash
"~A" slash
) regex
))))
427 (defprinter js-return
(value)
428 (write-sequence "return " *ps-output-stream
*)
431 ;;; conditional compilation
432 (defprinter cc-if
(test body-forms
)
433 (write-string "/*@if ")
435 (incf *indent-level
*)
436 (dolist (form body-forms
)
440 (decf *indent-level
*)
442 (write-string "@end @*/"))
444 (defprinter js-instanceof
(value type
)
447 (write-string " instanceof ")
451 (defprinter js-named-operator
(op value
)
452 (format *ps-output-stream
* "~(~A~) " op
)