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 ((compiled-form cons
))
35 "Prints the given compiled ParenScript form starting at the given
37 (ps-print%
(car compiled-form
) (cdr compiled-form
)))
41 (defparameter *indent-level
* 0)
42 (defparameter *indent-num-space
* 4)
44 (defun newline-and-indent ()
46 (loop repeat
(* *indent-level
* *indent-num-space
*)
47 do
(write-char #\Space
))))
50 (defvar *js-quote-char
* #\'
51 "Specifies which character JS should use for delimiting strings.
53 This variable is useful when have to embed some javascript code
54 in an html attribute delimited by #\\\" as opposed to #\\', or
57 (defparameter *js-lisp-escaped-chars
*
61 (#\f .
#.
(code-char 12))
66 (defmethod ps-print ((string string
))
67 (flet ((lisp-special-char-to-js (lisp-char)
68 (car (rassoc lisp-char
*js-lisp-escaped-chars
*))))
69 (write-char *js-quote-char
*)
70 (loop for char across string
71 for code
= (char-code char
)
72 for special
= (lisp-special-char-to-js char
)
73 do
(cond (special (write-char #\\)
75 ((or (<= code
#x1f
) (>= code
#x80
))
76 (format *ps-output-stream
* "\\u~4,'0x" code
))
77 (t (write-char char
)))
78 finally
(write-char *js-quote-char
*))))
80 (defmethod ps-print ((number number
))
81 (format *ps-output-stream
* (if (integerp number
) "~S" "~F") number
))
83 ;;; expression and operator precedence rules
85 (defun expression-precedence (expr)
88 (js-expression-if (op-precedence 'js-expression-if
))
89 (js-assign (op-precedence '=))
90 (operator (op-precedence (second expr
)))
94 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
95 (defparameter *op-precedence-hash
* (make-hash-table :test
#'equal
))
97 ;;; generate the operator precedences from *OP-PRECEDENCES*
99 (dolist (ops '((js-aref)
107 (in js-expression-if
)
115 (js-assign *= /= %
= += -
= <<= >>= >>>= \
&= ^
= \|
=)
118 (let ((op-name (symbol-name op
)))
119 (setf (gethash op-name
*op-precedence-hash
*) precedence
)))
122 (defun op-precedence (op)
123 (gethash (if (symbolp op
)
126 *op-precedence-hash
*)))
128 (defprinter script-quote
(val)
130 (write-string "null")
131 (error "Cannot translate quoted value ~S to javascript" val
)))
133 (defprinter js-literal
(str)
136 (defprinter js-keyword
(str)
139 (defun print-comma-list (ps-forms)
140 (loop for
(form . rest
) on ps-forms
142 unless rest do
(setf after
"")
143 doing
(progn (ps-print form
)
144 (write-string after
))))
146 (defprinter array-literal
(&rest initial-contents
)
148 (print-comma-list initial-contents
)
151 (defprinter js-aref
(array indices
)
153 (loop for idx in indices do
154 (progn (write-char #\
[)
158 (defprinter object-literal
(&rest slot-definitions
)
160 (loop for
((key . value
) . rest
) on slot-definitions
162 unless rest do
(setf after
"")
163 doing
(progn (format *ps-output-stream
* "~A: " (js-translate-symbol key
))
165 (write-string after
)))
168 (defprinter js-variable
(var)
169 (write-string (js-translate-symbol var
)))
171 ;;; arithmetic operators
172 (defun script-convert-op-name (op)
181 (defun parenthesize-print (ps-form)
186 (defprinter operator
(op args
)
187 (loop for
(arg . rest
) on args
188 with precedence
= (op-precedence op
)
189 with op-string
= (format nil
" ~A " op
)
190 unless rest do
(setf op-string
"")
191 do
(progn (if (>= (expression-precedence arg
) precedence
)
192 (parenthesize-print arg
)
194 (write-string op-string
))))
196 (defprinter unary-operator
(op arg
&key prefix
)
199 (if (eql 'operator
(car arg
))
200 (parenthesize-print arg
)
205 ;;; function and method calls
206 (defprinter js-funcall
(fun-designator args
)
207 (cond ((member (car fun-designator
) '(js-variable js-aref js-slot-value
))
208 (ps-print fun-designator
))
209 ((eql 'js-lambda
(car fun-designator
))
211 (ps-print fun-designator
)
213 ((eql 'js-funcall
(car fun-designator
))
214 (ps-print fun-designator
)))
216 (print-comma-list args
)
219 (defprinter js-method-call
(method object args
)
220 ;; TODO: this may not be the best way to add ()'s around lambdas
221 ;; probably there is or should be a more general solution working
222 ;; in other situations involving lambda's
223 (if (or (numberp object
) (and (consp object
) (member (car object
) '(js-lambda js-object operator js-expression-if
))))
224 (parenthesize-print object
)
226 (write-string (js-translate-symbol method
))
228 (print-comma-list args
)
231 (defprinter js-block
(statement-p statements
)
233 (progn (write-char #\
{)
234 (incf *indent-level
*)
235 (loop for statement in statements
236 do
(progn (newline-and-indent)
239 (decf *indent-level
*)
242 (progn (write-char #\
()
243 (loop for
(statement . rest
) on statements
245 unless rest do
(setf after
"")
246 do
(progn (ps-print statement
)
247 (write-string after
)))
250 (defprinter js-lambda
(args body
)
251 (print-fun-def nil args body
))
253 (defprinter js-defun
(name args body
)
254 (print-fun-def name args body
))
256 (defun print-fun-def (name args body-block
)
257 (format *ps-output-stream
* "function ~:[~;~A~](" name
(js-translate-symbol name
))
258 (loop for
(arg . rest
) on args
260 unless rest do
(setf after
"")
261 do
(progn (write-string (js-translate-symbol arg
))
262 (write-string after
))
263 finally
(write-string ") "))
264 (ps-print body-block
))
267 (defprinter js-object
(slot-defs)
269 (loop for
((slot-name slot-value
) . rest
) on slot-defs
271 unless rest do
(setf after
"")
272 do
(progn (if (and (listp slot-name
) (eql 'script-quote
(car slot-name
)) (symbolp (second slot-name
)))
273 (write-string (js-translate-symbol (second slot-name
)))
274 (ps-print slot-name
))
276 (ps-print slot-value
)
277 (write-string after
)))
280 (defprinter js-slot-value
(obj slot
)
281 (if (and (listp obj
) (member (car obj
) '(js-expression-if)))
282 (parenthesize-print obj
)
284 (if (and (listp slot
) (eql 'script-quote
(car slot
)))
285 (progn (write-char #\.
)
286 (if (symbolp (second slot
))
287 (write-string (js-translate-symbol (second slot
)))
289 (progn (write-char #\
[)
294 (defprinter js-cond
(clauses)
295 (loop for
(test body-block
) in clauses
296 for start
= "if (" then
" else if ("
297 do
(progn (if (string= test
"true")
298 (write-string " else ")
299 (progn (ps-print test
)
300 (write-string ") ")))
301 (ps-print body-block
))))
303 (defprinter js-statement-if
(test then-block else-block
)
304 (write-string "if (")
307 (ps-print then-block
)
309 (write-string " else ")
310 (ps-print else-block
)))
312 (defprinter js-expression-if
(test then else
)
315 (if (>= (expression-precedence then
) (op-precedence 'js-expression-if
))
316 (parenthesize-print then
)
320 (if (>= (expression-precedence else
) (op-precedence 'js-expression-if
))
321 (parenthesize-print else
)
323 (write-string "undefined")))
325 (defprinter js-assign
(lhs rhs
)
330 (defprinter js-defvar
(var-name &rest var-value
)
331 (write-string "var ")
332 (write-string (js-translate-symbol var-name
))
335 (ps-print (car var-value
))))
338 (defprinter js-for
(vars steps test body-block
)
339 (write-string "for (")
340 (loop for
((var-name . var-init
) . rest
) on vars
341 for decl
= "var " then
""
343 unless rest do
(setf after
"")
344 do
(progn (write-string decl
)
345 (write-string (js-translate-symbol var-name
))
348 (write-string after
)))
352 (loop for
((var-name . var-init
) . rest
) on vars
355 unless rest do
(setf after
"")
356 do
(progn (write-string (js-translate-symbol var-name
))
359 (write-string after
)))
361 (ps-print body-block
))
363 (defprinter js-for-each
(var object body-block
)
364 (write-string "for (var ")
365 (write-string (js-translate-symbol var
))
366 (write-string " in ")
369 (ps-print body-block
))
371 (defprinter js-while
(test body-block
)
372 (write-string "while (")
375 (ps-print body-block
))
377 (defprinter js-with
(expression body-block
)
378 (write-string "with (")
379 (ps-print expression
)
381 (ps-print body-block
))
383 (defprinter js-switch
(test clauses
)
384 (flet ((print-body-statements (body-statements)
385 (incf *indent-level
*)
386 (loop for statement in body-statements do
387 (progn (newline-and-indent)
390 (decf *indent-level
*)))
391 (write-string "switch (")
394 (loop for
(val body-block
) in clauses
395 for body-statements
= (third body-block
)
396 do
(progn (newline-and-indent)
397 (if (eql val
'default
)
398 (progn (write-string "default: ")
399 (print-body-statements body-statements
))
400 (progn (write-string "case ")
403 (print-body-statements body-statements
)))))
406 (defprinter js-try
(body-block &key catch finally
)
407 (write-string "try ")
408 (ps-print body-block
)
410 (write-string " catch (")
411 (write-string (js-translate-symbol (first catch
)))
413 (ps-print (second catch
)))
415 (write-string " finally ")
419 (defprinter js-regex
(regex)
420 (flet ((first-slash-p (string)
421 (and (> (length string
) 0) (char= (char string
0) #\
/))))
422 (let ((slash (unless (first-slash-p regex
) "/")))
423 (format *ps-output-stream
* (concatenate 'string slash
"~A" slash
) regex
))))
425 (defprinter js-return
(value)
426 (write-sequence "return " *ps-output-stream
*)
429 ;;; conditional compilation
430 (defprinter cc-if
(test body-forms
)
431 (write-string "/*@if ")
433 (incf *indent-level
*)
434 (dolist (form body-forms
)
438 (decf *indent-level
*)
440 (write-string "@end @*/"))
442 (defprinter js-instanceof
(value type
)
445 (write-string " instanceof ")
449 (defprinter js-named-operator
(op value
)
450 (format *ps-output-stream
* "~(~A~) " op
)