Use strings, and not symbols, to name javascript functions/macros
[clinton/parenscript.git] / js.lisp
CommitLineData
8e198a08
MB
1(in-package :js)
2
3;;; ecmascript standard:
4;;; http://www.ecma-international.org/publications/standards/Ecma-262.htm
5
6;;; javascript name conversion
7
8(defparameter *special-chars*
9 '((#\! . "Bang")
10 (#\? . "What")
11 (#\# . "Hash")
12 (#\$ . "Dollar")
13 (#\@ . "At")
14 (#\% . "Percent")
15 (#\+ . "Plus")
16 (#\* . "Star")
17 (#\/ . "Slash")))
18
19(defun string-chars (string)
20 (coerce string 'list))
21
22(defun constant-string-p (string)
23 (let ((len (length string))
24 (constant-chars '(#\+ #\*)))
25 (and (> len 2)
26 (member (char string 0) constant-chars)
27 (member (char string (1- len)) constant-chars))))
28
29(defun first-uppercase-p (string)
30 (and (> (length string) 1)
31 (member (char string 0) '(#\+ #\*))))
32
bcf5db4d
MB
33(defun untouchable-string-p (string)
34 (and (> (length string) 1)
35 (char= #\: (char string 0))))
36
8e198a08
MB
37(defun symbol-to-js (symbol)
38 (when (symbolp symbol)
39 (setf symbol (symbol-name symbol)))
40 (let ((symbols (string-split symbol '(#\.))))
41 (cond ((null symbols) "")
42 ((= (length symbols) 1)
43 (let (res
bcf5db4d 44 (do-not-touch nil)
8e198a08
MB
45 (lowercase t)
46 (all-uppercase nil))
47 (cond ((constant-string-p symbol)
48 (setf all-uppercase t
49 symbol (subseq symbol 1 (1- (length symbol)))))
50 ((first-uppercase-p symbol)
51 (setf lowercase nil
bcf5db4d
MB
52 symbol (subseq symbol 1)))
53 ((untouchable-string-p symbol)
54 (setf do-not-touch t
55 symbol (subseq symbol 1))))
8e198a08 56 (flet ((reschar (c)
bcf5db4d
MB
57 (push (cond
58 (do-not-touch c)
59 ((and lowercase (not all-uppercase))
60 (char-downcase c))
61 (t (char-upcase c)))
62 res)
8e198a08
MB
63 (setf lowercase t)))
64 (dotimes (i (length symbol))
65 (let ((c (char symbol i)))
66 (cond
67 ((eql c #\-)
68 (setf lowercase (not lowercase)))
69 ((assoc c *special-chars*)
70 (dolist (i (coerce (cdr (assoc c *special-chars*)) 'list))
71 (reschar i)))
72 (t (reschar c))))))
73 (coerce (nreverse res) 'string)))
74 (t (string-join (mapcar #'symbol-to-js symbols) ".")))))
75
76;;; js language types
77
78(defgeneric js-equal (obj1 obj2))
79(defmethod js-equal ((obj1 list) (obj2 list))
80 (and (= (length obj1) (length obj2))
81 (every #'js-equal obj1 obj2)))
82(defmethod js-equal ((obj1 t) (obj2 t))
83 (equal obj1 obj2))
84
85(defmacro defjsclass (name superclasses slots &rest class-options)
86 (let ((slot-names (mapcar #'(lambda (slot) (if (atom slot) slot (first slot))) slots)))
87 `(progn
88 (defclass ,name ,superclasses
89 ,slots ,@class-options)
90 (defmethod js-equal ((obj1 ,name) (obj2 ,name))
91 (every #'(lambda (slot)
92 (js-equal (slot-value obj1 slot)
93 (slot-value obj2 slot)))
94 ',slot-names)))))
95
96(defjsclass statement ()
97 ((value :initarg :value :accessor value :initform nil)))
98
99(defjsclass expression (statement)
100 ((value)))
101
102;;; indenter
103
104(defun special-append-to-last (form elt)
105 (flet ((special-append (form elt)
106 (let ((len (length form)))
107 (if (and (> len 0)
108 (member (char form (1- len))
109 '(#\; #\, #\})))
110 form
111 (concatenate 'string form elt)))))
112 (cond ((stringp form)
113 (special-append form elt))
114 ((consp form)
115 (let ((last (last form)))
116 (if (stringp (car last))
117 (rplaca last (special-append (car last) elt))
118 (append-to-last (car last) elt))
119 form))
120 (t (error "unsupported form ~S" form)))))
121
122(defun dwim-join (value-string-lists max-length
123 &key start end
124 join-before join-after
125 white-space (separator " ")
126 (append-to-last #'append-to-last)
127 (collect t))
128 #+nil
129 (format t "value-string-lists: ~S~%" value-string-lists)
130
131 (unless start
132 (setf start ""))
133
134 (unless join-before
135 (setf join-before ""))
136
137 ;;; collect single value-string-lists until line full
138
139 (do* ((string-lists value-string-lists (cdr string-lists))
140 (string-list (car string-lists) (car string-lists))
141 (cur-elt start)
142 (cur-empty t)
143 (white-space (or white-space (make-string (length start) :initial-element #\Space)))
144 (res nil))
145 ((null string-lists)
146 (unless cur-empty
147 (push cur-elt res))
148 (if (null res)
149 (list (concatenate 'string start end))
150 (progn
151 (when end
152 (setf (first res)
153 (funcall append-to-last (first res) end)))
154 (nreverse res))))
155
156 #+nil
157 (format t "string-list: ~S~%" string-list)
158
159 (when join-after
160 (unless (null (cdr string-lists))
161 (funcall append-to-last string-list join-after)))
162
163 (if (and collect (= (length string-list) 1))
164 (progn
165 #+nil
166 (format t "cur-elt: ~S line-length ~D, max-length ~D, string: ~S~%"
167 cur-elt
168 (+ (length (first string-list))
169 (length cur-elt))
170 max-length
171 (first string-list))
172 (if (or cur-empty
173 (< (+ (length (first string-list))
174 (length cur-elt)) max-length))
175 (setf cur-elt
176 (concatenate 'string cur-elt
177 (if cur-empty "" (concatenate 'string separator join-before))
178 (first string-list))
179 cur-empty nil)
180 (progn
181 (push cur-elt res)
182 (setf cur-elt (concatenate 'string white-space
183 join-before (first string-list))
184 cur-empty nil))))
185
186 (progn
187 (unless cur-empty
188 (push cur-elt res)
189 (setf cur-elt white-space
190 cur-empty t))
191 (setf res (nconc (nreverse
192 (cons (concatenate 'string
193 cur-elt (if (null res)
194 "" join-before)
195 (first string-list))
196 (mapcar #'(lambda (x) (concatenate 'string white-space x))
197 (cdr string-list)))) res))
198 (setf cur-elt white-space cur-empty t)))))
199
200(defmethod js-to-strings ((expression expression) start-pos)
30b3e3eb 201 (declare (ignore start-pos))
8e198a08
MB
202 (list (princ-to-string (value expression))))
203
204(defmethod js-to-statement-strings ((expression expression) start-pos)
205 (js-to-strings expression start-pos))
206
207(defmethod js-to-statement-strings ((statement statement) start-pos)
30b3e3eb 208 (declare (ignore start-pos))
8e198a08
MB
209 (list (princ-to-string (value statement))))
210
211;;; compiler macros
212
213(eval-when (:compile-toplevel :load-toplevel :execute)
3549e19d
MB
214 (defvar *js-compiler-macros* (make-hash-table :test 'equal)
215 "*JS-COMPILER-MACROS* is a hash-table containing the functions corresponding
8e198a08
MB
216to javascript special forms, indexed by their name. Javascript special
217forms are compiler macros for JS expressions."))
218
219(defmacro define-js-compiler-macro (name lambda-list &rest body)
220 "Define a javascript compiler macro NAME. Arguments are destructured
221according to LAMBDA-LIST. The resulting JS language types are appended
222to the ongoing javascript compilation."
223 (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
224 `(eval-when (:compile-toplevel :load-toplevel :execute)
225 (defun ,js-name ,lambda-list ,@body)
3549e19d 226 (setf (gethash ,(symbol-name name) *js-compiler-macros*) #',js-name))))
8e198a08
MB
227
228(defun js-compiler-macro-form-p (form)
3549e19d 229 (when (gethash (symbol-name (car form)) *js-compiler-macros*)
8e198a08
MB
230 t))
231
232(defun js-get-compiler-macro (name)
3549e19d 233 (gethash (symbol-name name) *js-compiler-macros*))
8e198a08
MB
234
235;;; macro expansion
236
237(eval-when (:compile-toplevel :load-toplevel :execute)
3549e19d 238 (defvar *js-macro-toplevel* (make-hash-table :test 'equal)
8e198a08
MB
239 "Toplevel of macro expansion, holds all the toplevel javascript macros.")
240 (defvar *js-macro-env* (list *js-macro-toplevel*)
241 "Current macro environment."))
242
243(defun lookup-macro (name)
244 "Lookup the macro NAME in the current macro expansion
245environment. Returns the macro and the parent macro environment of
246this macro."
3549e19d
MB
247 (unless (symbolp name)
248 (return-from lookup-macro nil))
8e198a08
MB
249 (do ((env *js-macro-env* (cdr env)))
250 ((null env) nil)
3549e19d 251 (let ((val (gethash (symbol-name name) (car env))))
8e198a08
MB
252 (when val
253 (return-from lookup-macro
254 (values val (or (cdr env)
255 (list *js-macro-toplevel*))))))))
256
257(defmacro defjsmacro (name args &rest body)
258 "Define a javascript macro, and store it in the toplevel macro environment."
3549e19d 259 (when (gethash (symbol-name name) *js-compiler-macros*)
8e198a08 260 (warn "Redefining compiler macro ~S" name)
3549e19d 261 (remhash (symbol-name name) *js-compiler-macros*))
8e198a08 262 (let ((lambda-list (gensym)))
3549e19d 263 `(setf (gethash ,(symbol-name name) *js-macro-toplevel*)
8e198a08
MB
264 #'(lambda (&rest ,lambda-list)
265 (destructuring-bind ,args ,lambda-list ,@body)))))
266
267(defun js-expand-form (expr)
268 "Expand a javascript form."
269 (cond ((atom expr)
270 (multiple-value-bind (js-macro macro-env)
271 (lookup-macro expr)
272 (if js-macro
273 (js-expand-form (let ((*js-macro-env* macro-env))
274 (funcall js-macro)))
275 expr)))
276
277 ((js-compiler-macro-form-p expr) expr)
278
279 ((equal (first expr) 'quote) expr)
280
281 (t (let ((js-macro (lookup-macro (car expr))))
282 (if js-macro
283 (js-expand-form (apply js-macro (cdr expr)))
284 expr)))))
285
286(defvar *var-counter* 0)
287
288(defun js-gensym (&optional (name "js"))
289 (intern (format nil "tmp-~A-~A" name (incf *var-counter*)) #.*package*))
290
291;;; literals
292
293(defmacro defjsliteral (name string)
294 "Define a Javascript literal that will expand to STRING."
295 `(define-js-compiler-macro ,name () (make-instance 'expression :value ,string)))
296
297(defjsliteral this "this")
298(defjsliteral t "true")
299(defjsliteral nil "null")
300(defjsliteral false "false")
301(defjsliteral undefined "undefined")
302
303(defmacro defjskeyword (name string)
304 "Define a Javascript keyword that will expand to STRING."
305 `(define-js-compiler-macro ,name () (make-instance 'statement :value ,string)))
306
307(defjskeyword break "break")
308(defjskeyword continue "continue")
309
310;;; array literals
311
312(defjsclass array-literal (expression)
313 ((values :initarg :values :accessor array-values)))
314
315(define-js-compiler-macro array (&rest values)
316 (make-instance 'array-literal
317 :values (mapcar #'js-compile-to-expression values)))
318
319(defjsmacro list (&rest values)
320 `(array ,@values))
321
322(defmethod js-to-strings ((array array-literal) start-pos)
323 (let ((value-string-lists
324 (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
325 (array-values array)))
326 (max-length (- 80 start-pos 2)))
327 (dwim-join value-string-lists max-length
328 :start "[ " :end " ]"
329 :join-after ",")))
330
331(defjsclass js-aref (expression)
332 ((array :initarg :array
333 :accessor aref-array)
334 (index :initarg :index
335 :accessor aref-index)))
336
337(define-js-compiler-macro aref (array &rest coords)
338 (make-instance 'js-aref
339 :array (js-compile-to-expression array)
340 :index (mapcar #'js-compile-to-expression coords)))
341
342(defmethod js-to-strings ((aref js-aref) start-pos)
343 (dwim-join (cons (js-to-strings (aref-array aref) start-pos)
344 (mapcar #'(lambda (x) (dwim-join (list (js-to-strings x (+ start-pos 2)))
345 (- 80 start-pos 2)
346 :start "[" :end "]"))
347 (aref-index aref)))
348 (- 80 start-pos 2) :separator ""
349 :white-space " "))
350
351(defjsmacro make-array (&rest inits)
352 `(new (*array ,@inits)))
353
c95a06f8
MB
354;;; object literals (maps and hash-tables)
355
356(defjsclass object-literal (expression)
357 ((values :initarg :values :accessor object-values)))
358
359(define-js-compiler-macro {} (&rest values)
360 (make-instance 'object-literal
361 :values (loop
362 for (key value) on values by #'cddr
363 collect (cons key (js-compile-to-expression value)))))
364
365(defmethod js-to-strings ((obj object-literal) start-pos)
366 (dwim-join (loop
367 for (key . value) in (object-values obj)
368 append (list
369 (dwim-join (list (list (format nil "~A:" (symbol-to-js key)))
370 (js-to-strings value (+ start-pos 2)))
371 (- 80 start-pos 2)
372 :start "" :end "" :join-after "")))
373 (- 80 start-pos 2)
374 :start "{ " :end " }"
375 :join-after ","))
376
8e198a08
MB
377;;; string literals
378
379(defjsclass string-literal (expression)
380 (value))
381
382(defmethod js-to-strings ((string string-literal) start-pos)
383 (declare (ignore start-pos))
02747605 384 (list (format nil "'~A'" (value string))))
8e198a08
MB
385
386;;; number literals
387
388(defjsclass number-literal (expression)
389 (value))
390
391;;; variables
392
393(defjsclass js-variable (expression)
394 (value))
395
396(defmethod js-to-strings ((v js-variable) start-form)
30b3e3eb 397 (declare (ignore start-form))
8e198a08
MB
398 (list (symbol-to-js (value v))))
399
400;;; arithmetic operators
401
402(eval-when (:compile-toplevel :load-toplevel :execute)
403
404 (defparameter *op-precedence-hash* (make-hash-table))
405
406 (defparameter *op-precedences*
407 '((aref)
408 (slot-value)
409 (! not ~)
410 (* / %)
411 (+ -)
412 (<< >>)
413 (>>>)
414 (< > <= >=)
415 (in if)
416 (eql == != =)
417 (=== !==)
418 (&)
419 (^)
420 (\|)
421 (\&\& and)
422 (\|\| or)
423 (setf *= /= %= += -= <<= >>= >>>= \&= ^= \|=)
424 (comma)))
425
426 ;;; generate the operator precedences from *OP-PRECEDENCES*
427 (let ((precedence 1))
428 (dolist (ops *op-precedences*)
429 (dolist (op ops)
430 (setf (gethash op *op-precedence-hash*) precedence))
431 (incf precedence))))
432
433(defun js-convert-op-name (op)
434 (case op
435 (and '\&\&)
436 (or '\|\|)
437 (not '!)
438 (eql '\=\=)
439 (= '\=\=)
440 (t op)))
441
442(defjsclass op-form (expression)
443 ((operator :initarg :operator :accessor operator)
444 (args :initarg :args :accessor op-args)))
445
446(defun op-form-p (form)
447 (and (listp form)
448 (not (js-compiler-macro-form-p form))
449 (not (null (gethash (first form) *op-precedence-hash*)))))
450
451(defun klammer (string-list)
452 (prepend-to-first string-list "(")
453 (append-to-last string-list ")")
454 string-list)
455
456(defmethod expression-precedence ((expression expression))
457 0)
458
459(defmethod expression-precedence ((form op-form))
460 (gethash (operator form) *op-precedence-hash*))
461
462(defmethod js-to-strings ((form op-form) start-pos)
463 (let* ((precedence (expression-precedence form))
464 (value-string-lists
465 (mapcar #'(lambda (x)
466 (let ((string-list (js-to-strings x (+ start-pos 2))))
467 (if (>= (expression-precedence x) precedence)
468 (klammer string-list)
469 string-list)))
470 (op-args form)))
471 (max-length (- 80 start-pos 2))
472 (op-string (format nil "~A " (operator form))))
473 (dwim-join value-string-lists max-length :join-before op-string)))
474
475(defjsmacro 1- (form)
476 `(- ,form 1))
477
478(defjsmacro 1+ (form)
479 `(+ ,form 1))
480
481(defjsclass one-op (expression)
482 ((pre-p :initarg :pre-p
483 :initform nil
484 :accessor one-op-pre-p)
485 (op :initarg :op
486 :accessor one-op)))
487
488(defmethod js-to-strings ((one-op one-op) start-pos)
489 (let* ((value (value one-op))
490 (value-strings (js-to-strings value start-pos)))
491 (when (typep value 'op-form)
492 (setf value-strings (klammer value-strings)))
493 (if (one-op-pre-p one-op)
494 (prepend-to-first value-strings
495 (one-op one-op))
496 (append-to-last value-strings
497 (one-op one-op)))))
498
499(define-js-compiler-macro incf (x)
500 (make-instance 'one-op :pre-p t :op "++"
501 :value (js-compile-to-expression x)))
502(define-js-compiler-macro ++ (x)
503 (make-instance 'one-op :pre-p nil :op "++"
504 :value (js-compile-to-expression x)))
505(define-js-compiler-macro decf (x)
506 (make-instance 'one-op :pre-p t :op "--"
507 :value (js-compile-to-expression x)))
508(define-js-compiler-macro -- (x)
509 (make-instance 'one-op :pre-p nil :op "--"
510 :value (js-compile-to-expression x)))
511
512
513(define-js-compiler-macro not (x)
514 (let ((value (js-compile-to-expression x)))
515 (if (and (typep value 'op-form)
516 (= (length (op-args value)) 2))
517 (let ((new-op (case (operator value)
518 (== '!=)
519 (< '>=)
520 (> '<=)
521 (<= '>)
522 (>= '<)
523 (!= '==)
524 (=== '!==)
525 (!== '===)
526 (t nil))))
527 (if new-op
528 (make-instance 'op-form :operator new-op
529 :args (op-args value))
530 (make-instance 'one-op :pre-p t :op "!"
531 :value value)))
532 (make-instance 'one-op :pre-p t :op "!"
533 :value value))))
534
535;;; function calls
536
537(defjsclass function-call (expression)
538 ((function :initarg :function :accessor f-function)
539 (args :initarg :args :accessor f-args)))
540
541(defun funcall-form-p (form)
542 (and (listp form)
543 (not (op-form-p form))
544 (not (js-compiler-macro-form-p form))))
545
546(defmethod js-to-strings ((form function-call) start-pos)
547 (let* ((value-string-lists
548 (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
549 (f-args form)))
550 (max-length (- 80 start-pos 2))
551 (args (dwim-join value-string-lists max-length
552 :start "(" :end ")" :join-after ",")))
5ae77c6e
MB
553 (etypecase (f-function form)
554 (js-lambda
555 (dwim-join (list (append (dwim-join (list (js-to-strings (f-function form) (+ start-pos 2)))
556 max-length
557 :start "(" :end ")" :separator "")
558 args))
559 max-length
560 :separator ""))
561 ((or js-variable js-aref js-slot-value)
562 (dwim-join (list (js-to-strings (f-function form) (+ start-pos 2))
563 args)
564 max-length
565 :separator "")))))
8e198a08
MB
566
567(defjsclass method-call (expression)
568 ((method :initarg :method :accessor m-method)
569 (object :initarg :object :accessor m-object)
570 (args :initarg :args :accessor m-args)))
571
572(defmethod js-to-strings ((form method-call) start-pos)
573 (let ((fname (dwim-join (list (js-to-strings (m-object form) (+ start-pos 2))
574 (list (symbol-to-js (m-method form))))
575 (- 80 start-pos 2)
576 :end "("
577 :separator "")))
578 (let ((butlast (butlast fname))
579 (last (car (last fname))))
580 (nconc butlast
581 (dwim-join (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
582 (m-args form))
583 (- 80 start-pos 2)
584 :start last
585 :end ")"
586 :join-after ",")))))
587
588(defun method-call-p (form)
589 (and (funcall-form-p form)
590 (symbolp (first form))
591 (eql (char (symbol-name (first form)) 0) #\.)))
592
593;;; body forms
594
595(defjsclass js-body (expression)
596 ((stmts :initarg :stmts :accessor b-stmts)
597 (indent :initarg :indent :initform "" :accessor b-indent)))
598
599(define-js-compiler-macro progn (&rest body)
600 (make-instance 'js-body
601 :stmts (mapcar #'js-compile-to-statement body)))
602
603(defmethod initialize-instance :after ((body js-body) &rest initargs)
604 (declare (ignore initargs))
605 (let* ((stmts (b-stmts body))
606 (last (last stmts))
607 (last-stmt (car last)))
608 (when (typep last-stmt 'js-body)
609 (setf (b-stmts body)
610 (nconc (butlast stmts)
611 (b-stmts last-stmt))))))
612
613
614(defmethod js-to-statement-strings ((body js-body) start-pos)
615 (dwim-join (mapcar #'(lambda (x) (js-to-statement-strings x (+ start-pos 2)))
616 (b-stmts body))
617 (- 80 start-pos 2)
618 :join-after ";"
619 :append-to-last #'special-append-to-last
620 :start (b-indent body) :collect nil
621 :end ";"))
622
623(defmethod js-to-strings ((body js-body) start-pos)
624 (dwim-join (mapcar #'(lambda (x) (js-to-strings x (+ start-pos 2)))
625 (b-stmts body))
626 (- 80 start-pos 2)
627 :append-to-last #'special-append-to-last
628 :join-after ","
629 :start (b-indent body)))
630
631(defjsclass js-sub-body (js-body)
632 (stmts indent))
633
634(defmethod js-to-statement-strings ((body js-sub-body) start-pos)
30b3e3eb 635 (declare (ignore start-pos))
8e198a08
MB
636 (nconc (list "{") (call-next-method) (list "}")))
637
638(defmethod expression-precedence ((body js-body))
639 (if (= (length (b-stmts body)) 1)
640 (expression-precedence (first (b-stmts body)))
641 (gethash 'comma *op-precedence-hash*)))
642
643;;; function definition
644
d3694c57
MB
645(defjsclass js-lambda (expression)
646 ((args :initarg :args :accessor lambda-args)
647 (body :initarg :body :accessor lambda-body)))
648
649(define-js-compiler-macro lambda (args &rest body)
650 (make-instance 'js-lambda
651 :args (mapcar #'js-compile-to-symbol args)
652 :body (make-instance 'js-body
653 :indent " "
654 :stmts (mapcar #'js-compile-to-statement body))))
655
656(defmethod js-to-strings ((lambda js-lambda) start-pos)
657 (let ((fun-header (dwim-join (mapcar #'(lambda (x)
658 (list (symbol-to-js x)))
659 (lambda-args lambda))
660 (- 80 start-pos 2)
661 :start (function-start-string lambda)
662 :end ") {" :join-after ","))
663 (fun-body (js-to-statement-strings (lambda-body lambda) (+ start-pos 2))))
664 (nconc fun-header fun-body (list "}"))))
665
666(defmethod function-start-string ((lambda js-lambda))
667 "function (")
668
669(defmethod js-to-statement-strings ((lambda js-lambda) start-pos)
670 (js-to-strings lambda start-pos))
671
672(defjsclass js-defun (js-lambda)
673 ((name :initarg :name :accessor defun-name)))
8e198a08
MB
674
675(define-js-compiler-macro defun (name args &rest body)
676 (make-instance 'js-defun
677 :name (js-compile-to-symbol name)
678 :args (mapcar #'js-compile-to-symbol args)
679 :body (make-instance 'js-body
680 :indent " "
681 :stmts (mapcar #'js-compile-to-statement body))))
682
d3694c57
MB
683(defmethod function-start-string ((defun js-defun))
684 (format nil "function ~A(" (symbol-to-js (defun-name defun))))
8e198a08
MB
685
686;;; object creation
687
688(defjsclass js-object (expression)
689 ((slots :initarg :slots
690 :accessor o-slots)))
691
692(define-js-compiler-macro create (&rest args)
693 (make-instance 'js-object
694 :slots (loop for (name val) on args by #'cddr
695 collect (list (js-compile-to-symbol name)
696 (js-compile-to-expression val)))))
697
698(defmethod js-to-strings ((object js-object) start-pos)
699 (let ((value-string-lists
700 (mapcar #'(lambda (slot)
701 (dwim-join (list (js-to-strings (second slot) (+ start-pos 4)))
702 (- 80 start-pos 2)
703 :start (concatenate 'string (symbol-to-js (first slot)) " : ")
704 :white-space " ")) (o-slots object)))
705 (max-length (- 80 start-pos 2)))
706 (dwim-join value-string-lists max-length
707 :start "{ "
708 :end " }"
709 :join-after ", "
710 :white-space " "
711 :collect nil)))
712
713(defjsclass js-slot-value (expression)
714 ((object :initarg :object
715 :accessor sv-object)
716 (slot :initarg :slot
717 :accessor sv-slot)))
718
719(define-js-compiler-macro slot-value (obj slot)
720 (make-instance 'js-slot-value :object (js-compile-to-expression obj)
721 :slot (js-compile-to-symbol slot)))
722
723(defmethod js-to-strings ((sv js-slot-value) start-pos)
724 (append-to-last (js-to-strings (sv-object sv) start-pos)
725 (format nil ".~A" (symbol-to-js (sv-slot sv)))))
726
727(defjsmacro with-slots (slots object &rest body)
728 `(symbol-macrolet ,(mapcar #'(lambda (slot)
729 `(,slot '(slot-value ,object ',slot)))
730 slots)
731 ,@body))
732
733;;; macros
734
735(define-js-compiler-macro macrolet (macros &rest body)
3549e19d 736 (let* ((macro-env (make-hash-table :test 'equal))
8e198a08
MB
737 (*js-macro-env* (cons macro-env *js-macro-env*)))
738 (dolist (macro macros)
739 (destructuring-bind (name arglist &rest body) macro
3549e19d 740 (setf (gethash (symbol-name name) macro-env)
8e198a08
MB
741 (compile nil `(lambda ,arglist ,@body)))))
742 (js-compile `(progn ,@body))))
743
744(defjsmacro symbol-macrolet (macros &rest body)
745 `(macrolet ,(mapcar #'(lambda (macro)
746 `(,(first macro) () ,@(rest macro))) macros)
747 ,@body))
748
749;;; lisp eval
750
751(defjsmacro lisp (&rest forms)
752 (eval (cons 'progn forms)))
753
754;;; if
755
756(defjsclass js-if (expression)
757 ((test :initarg :test
758 :accessor if-test)
759 (then :initarg :then
760 :accessor if-then)
761 (else :initarg :else
762 :accessor if-else)))
763
764(define-js-compiler-macro if (test then &optional else)
765 (make-instance 'js-if :test (js-compile-to-expression test)
766 :then (js-compile-to-body then :indent " ")
767 :else (when else
768 (js-compile-to-body else :indent " "))))
769
770(defmethod initialize-instance :after ((if js-if) &rest initargs)
771 (declare (ignore initargs))
772 (when (and (if-then if)
773 (typep (if-then if) 'js-sub-body))
774 (change-class (if-then if) 'js-body))
775 (when (and (if-else if)
776 (typep (if-else if) 'js-sub-body))
777 (change-class (if-else if) 'js-body)))
778
779(defmethod js-to-statement-strings ((if js-if) start-pos)
780 (let ((if-strings (dwim-join (list (js-to-strings (if-test if) 0))
781 (- 80 start-pos 2)
782 :start "if ("
783 :end ") {"))
784 (then-strings (js-to-statement-strings (if-then if) (+ start-pos 2)))
785 (else-strings (when (if-else if)
786 (js-to-statement-strings (if-else if)
787 (+ start-pos 2)))))
788 (nconc if-strings then-strings (if else-strings
789 (nconc (list "} else {") else-strings (list "}"))
790 (list "}")))))
791
792(defmethod expression-precedence ((if js-if))
793 (gethash 'if *op-precedence-hash*))
794
795(defmethod js-to-strings ((if js-if) start-pos)
796 (assert (typep (if-then if) 'expression))
797 (when (if-else if)
798 (assert (typep (if-else if) 'expression)))
799 (dwim-join (list (append-to-last (js-to-strings (if-test if) start-pos) " ?")
800 (let* ((new-then (make-instance 'js-body
801 :stmts (b-stmts (if-then if))
802 :indent ""))
803 (res (js-to-strings new-then start-pos)))
804 (if (>= (expression-precedence (if-then if))
805 (expression-precedence if))
806 (klammer res)
807 res))
808 (list ":")
809 (if (if-else if)
810 (let* ((new-else (make-instance 'js-body
811 :stmts (b-stmts (if-else if))
812 :indent ""))
813 (res (js-to-strings new-else start-pos)))
814 (if (>= (expression-precedence (if-else if))
815 (expression-precedence if))
816 (klammer res)
817 res))
818 (list "undefined")))
819 (- 80 start-pos 2)
820 :white-space " "))
821
822(defjsmacro when (test &rest body)
823 `(if ,test (progn ,@body)))
824
825(defjsmacro unless (test &rest body)
826 `(if (not ,test) (progn ,@body)))
827
828;;; single keyword expressions and statements
829
830(defmacro define-js-single-op (name &optional (superclass 'expression))
831 (let ((js-name (intern (concatenate 'string "JS-" (symbol-name name)) #.*package*)))
832 `(progn
833 (defjsclass ,js-name (,superclass)
834 (value))
835 (define-js-compiler-macro ,name (value)
836 (make-instance ',js-name :value (js-compile-to-expression value)))
837 (defmethod ,(if (eql superclass 'expression)
838 'js-to-strings
839 'js-to-statement-strings) ((,name ,js-name) start-pos)
840 (dwim-join (list (js-to-strings (value ,name) (+ start-pos 2)))
841 (- 80 start-pos 2)
842 :start ,(concatenate 'string (string-downcase (symbol-name name)) " ")
843 :white-space " ")))))
844
845
846(define-js-single-op return statement)
847(define-js-single-op throw statement)
848(define-js-single-op delete)
849(define-js-single-op void)
850(define-js-single-op typeof)
851(define-js-single-op instanceof)
852(define-js-single-op new)
853
854;;; assignment
855
856(defjsclass js-setf (expression)
857 ((lhs :initarg :lhs :accessor setf-lhs)
858 (rhsides :initarg :rhsides :accessor setf-rhsides)))
859
860(defun assignment-op (op)
861 (case op
862 (+ '+=)
863 (~ '~=)
864 (\& '\&=)
865 (\| '\|=)
866 (- '-=)
867 (* '*=)
868 (% '%=)
869 (>> '>>=)
870 (^ '^=)
871 (<< '<<=)
872 (>>> '>>>=)
873 (/ '/=)
874 (t nil)))
875
876(defun make-js-test (lhs rhs)
877 (if (and (typep rhs 'op-form)
878 (member lhs (op-args rhs) :test #'js-equal))
879 (let ((args-without (remove lhs (op-args rhs)
880 :count 1 :test #'js-equal))
881 (args-without-first (remove lhs (op-args rhs)
882 :count 1 :end 1
883 :test #'js-equal))
884 (one (list (make-instance 'number-literal :value 1))))
885 #+nil
886 (format t "OPERATOR: ~S, ARGS-WITHOUT: ~S, ARGS-WITHOUT-FIRST ~S~%"
887 (operator rhs)
888 args-without
889 args-without-first)
890 (cond ((and (js-equal args-without one)
891 (eql (operator rhs) '+))
892 (make-instance 'one-op :pre-p nil :op "++"
893 :value lhs))
894 ((and (js-equal args-without-first one)
895 (eql (operator rhs) '-))
896 (make-instance 'one-op :pre-p nil :op "--"
897 :value lhs))
898 ((and (assignment-op (operator rhs))
899 (member (operator rhs)
900 '(+ *)))
901 (make-instance 'op-form
902 :operator (assignment-op (operator rhs))
903 :args (list lhs (make-instance 'op-form
904 :operator (operator rhs)
905 :args args-without))))
906 ((and (assignment-op (operator rhs))
907 (js-equal (first (op-args rhs)) lhs))
908 (make-instance 'op-form
909 :operator (assignment-op (operator rhs))
910 :args (list lhs (make-instance 'op-form
911 :operator (operator rhs)
912 :args (cdr (op-args rhs))))))
913 (t (make-instance 'js-setf :lhs lhs :rhsides (list rhs)))))
914 (make-instance 'js-setf :lhs lhs :rhsides (list rhs))))
915
916(define-js-compiler-macro setf (&rest args)
917 (let ((assignments (loop for (lhs rhs) on args by #'cddr
918 for rexpr = (js-compile-to-expression rhs)
919 for lexpr = (js-compile-to-expression lhs)
920 collect (make-js-test lexpr rexpr))))
921 (if (= (length assignments) 1)
922 (first assignments)
923 (make-instance 'js-body :indent "" :stmts assignments))))
924
925(defmethod js-to-strings ((setf js-setf) start-pos)
926 (dwim-join (cons (js-to-strings (setf-lhs setf) start-pos)
927 (mapcar #'(lambda (x) (js-to-strings x start-pos)) (setf-rhsides setf)))
928 (- 80 start-pos 2)
929 :join-after " ="))
930
931(defmethod expression-precedence ((setf js-setf))
932 (gethash '= *op-precedence-hash*))
933
934;;; defvar
935
936(defjsclass js-defvar (statement)
937 ((names :initarg :names :accessor var-names)
938 (value :initarg :value :accessor var-value)))
939
940(define-js-compiler-macro defvar (name &optional value)
941 (make-instance 'js-defvar :names (list (js-compile-to-symbol name))
942 :value (when value (js-compile-to-expression value))))
943
944(defmethod js-to-statement-strings ((defvar js-defvar) start-pos)
945 (dwim-join (nconc (mapcar #'(lambda (x) (list (symbol-to-js x))) (var-names defvar))
946 (when (var-value defvar)
947 (list (js-to-strings (var-value defvar) start-pos))))
948 (- 80 start-pos 2)
949 :join-after " ="
950 :start "var " :end ";"))
951
952;;; let
953
954(define-js-compiler-macro let (decls &rest body)
955 (let ((single-defvar (make-instance 'js-defvar
956 :names (mapcar #'js-compile-to-symbol
957 (remove-if-not #'atom decls))
958 :value nil))
959 (defvars (mapcar #'(lambda (decl)
960 (let ((name (first decl))
961 (value (second decl)))
962 (make-instance 'js-defvar
963 :names (list (js-compile-to-symbol name))
964 :value (js-compile-to-expression value))))
965 (remove-if #'atom decls))))
966 (make-instance 'js-sub-body
967 :indent " "
968 :stmts (nconc (when (var-names single-defvar) (list single-defvar))
969 defvars
970 (mapcar #'js-compile-to-statement body)))))
971
972;;; iteration
973
974(defjsclass js-for (statement)
975 ((vars :initarg :vars :accessor for-vars)
976 (steps :initarg :steps :accessor for-steps)
977 (check :initarg :check :accessor for-check)
978 (body :initarg :body :accessor for-body)))
979
980(defun make-for-vars (decls)
981 (loop for decl in decls
982 for var = (if (atom decl) decl (first decl))
983 for init = (if (atom decl) nil (second decl))
984 collect (make-instance 'js-defvar :names (list (js-compile-to-symbol var))
985 :value (js-compile-to-expression init))))
986
987(defun make-for-steps (decls)
988 (loop for decl in decls
989 when (= (length decl) 3)
990 collect (js-compile-to-expression (third decl))))
991
992(define-js-compiler-macro do (decls termination &rest body)
993 (let ((vars (make-for-vars decls))
994 (steps (make-for-steps decls))
995 (check (js-compile-to-expression (list 'not (first termination))))
996 (body (js-compile-to-body (cons 'progn body) :indent " ")))
997 (make-instance 'js-for
998 :vars vars
999 :steps steps
1000 :check check
1001 :body body)))
1002
1003(defjsmacro dotimes (iter &rest body)
1004 (let ((var (first iter))
1005 (times (second iter)))
807be2bc
IT
1006 `(do ((,var 0 (1+ ,var)))
1007 ((>= ,var ,times))
8e198a08
MB
1008 ,@body)))
1009
1010(defjsmacro dolist (i-array &rest body)
1011 (let ((var (first i-array))
1012 (array (second i-array))
1013 (arrvar (js-gensym "arr"))
1014 (idx (js-gensym "i")))
1015 `(let ((,arrvar ,array))
807be2bc 1016 (do ((,idx 0 (1+ ,idx)))
8e198a08
MB
1017 ((>= ,idx (slot-value ,arrvar 'length)))
1018 (let ((,var (aref ,arrvar ,idx)))
1019 ,@body)))))
1020
1021(defmethod js-to-statement-strings ((for js-for) start-pos)
1022 (let* ((init (dwim-join (mapcar #'(lambda (x)
1023 (dwim-join (list (list (symbol-to-js (first (var-names x))))
1024 (js-to-strings (var-value x)
1025 (+ start-pos 2)))
1026 (- 80 start-pos 2)
1027 :join-after " ="))
1028 (for-vars for))
1029 (- 80 start-pos 2)
1030 :start "var " :join-after ","))
1031 (check (js-to-strings (for-check for) (+ start-pos 2)))
1032 (steps (dwim-join (mapcar #'(lambda (x var)
1033 (dwim-join
1034 (list (list (symbol-to-js (first (var-names var))))
1035 (js-to-strings x (- start-pos 2)))
1036 (- 80 start-pos 2)
1037 :join-after " ="))
1038 (for-steps for)
1039 (for-vars for))
1040 (- 80 start-pos 2)
1041 :join-after ","))
1042 (header (dwim-join (list init check steps)
1043 (- 80 start-pos 2)
1044 :start "for (" :end ") {"
1045 :join-after ";"))
1046 (body (js-to-statement-strings (for-body for) (+ start-pos 2))))
1047 (nconc header body (list "}"))))
1048
1049(defjsclass for-each (statement)
1050 ((name :initarg :name :accessor fe-name)
1051 (value :initarg :value :accessor fe-value)
1052 (body :initarg :body :accessor fe-body)))
1053
1054(define-js-compiler-macro doeach (decl &rest body)
1055 (make-instance 'for-each :name (js-compile-to-symbol (first decl))
1056 :value (js-compile-to-expression (second decl))
1057 :body (js-compile-to-body (cons 'progn body) :indent " ")))
1058
1059(defmethod js-to-statement-strings ((fe for-each) start-pos)
1060 (let ((header (dwim-join (list (list (symbol-to-js (fe-name fe)))
1061 (list "in")
1062 (js-to-strings (fe-value fe) (+ start-pos 2)))
1063 (- 80 start-pos 2)
1064 :start "for (var "
1065 :end ") {"))
1066 (body (js-to-statement-strings (fe-body fe) (+ start-pos 2))))
1067 (nconc header body (list "}"))))
1068
1069(defjsclass js-while (statement)
1070 ((check :initarg :check :accessor while-check)
1071 (body :initarg :body :accessor while-body)))
1072
1073(define-js-compiler-macro while (check &rest body)
1074 (make-instance 'js-while
1075 :check (js-compile-to-expression check)
1076 :body (js-compile-to-body (cons 'progn body) :indent " ")))
1077
1078(defmethod js-to-statement-strings ((while js-while) start-pos)
1079 (let ((header (dwim-join (list (js-to-strings (while-check while) (+ start-pos 2)))
1080 (- 80 start-pos 2)
1081 :start "while ("
1082 :end ") {"))
1083 (body (js-to-statement-strings (while-body while) (+ start-pos 2))))
1084 (nconc header body (list "}"))))
1085
1086;;; with
1087
1088(defjsclass js-with (statement)
1089 ((obj :initarg :obj :accessor with-obj)
1090 (body :initarg :body :accessor with-body)))
1091
1092(define-js-compiler-macro with (statement &rest body)
1093 (make-instance 'js-with
1094 :obj (js-compile-to-expression (first statement))
1095 :body (js-compile-to-body (cons 'progn body) :indent " ")))
1096
1097(defmethod js-to-statement-strings ((with js-with) start-pos)
1098 (nconc (dwim-join (list (js-to-strings (with-obj with) (+ start-pos 2)))
1099 (- 80 start-pos 2)
1100 :start "with (" :end ") {")
1101 (js-to-statement-strings (with-body with) (+ start-pos 2))
1102 (list "}")))
1103
1104;;; case
1105
1106(defjsclass js-case (statement)
1107 ((value :initarg :value :accessor case-value)
1108 (clauses :initarg :clauses :accessor case-clauses)))
1109
1110(define-js-compiler-macro case (value &rest clauses)
1111 (let ((clauses (mapcar #'(lambda (clause)
1112 (let ((val (first clause))
1113 (body (cdr clause)))
1114 (list (if (eql val 'default)
1115 'default
1116 (js-compile-to-expression val))
1117 (js-compile-to-body (cons 'progn body) :indent " "))))
1118 clauses))
1119 (check (js-compile-to-expression value)))
1120 (make-instance 'js-case :value check
1121 :clauses clauses)))
1122
1123(defmethod js-to-statement-strings ((case js-case) start-pos)
1124 (let ((body (mapcan #'(lambda (clause)
1125 (let ((val (car clause))
1126 (body (second clause)))
1127 (dwim-join (list (if (eql val 'default)
1128 (list "")
1129 (js-to-strings val (+ start-pos 2)))
1130 (js-to-statement-strings body (+ start-pos 2)))
1131 (- 80 start-pos 2)
1132 :start (if (eql val 'default) " default" " case ")
1133 :white-space " "
1134 :join-after ":"))) (case-clauses case))))
1135
1136 #+nil
1137 (format t "body: ~S~%" body)
1138 (nconc (dwim-join (list (js-to-strings (case-value case) (+ start-pos 2)))
1139 (- 80 start-pos 2)
1140 :start "switch (" :end ") {")
1141 body
1142 (list "}"))))
1143
1144;;; throw catch
1145
1146(defjsclass js-try (statement)
1147 ((body :initarg :body :accessor try-body)
1148 (catch :initarg :catch :accessor try-catch)
1149 (finally :initarg :finally :accessor try-finally)))
1150
1151(define-js-compiler-macro try (body &rest clauses)
1152 (let ((body (js-compile-to-body body :indent " "))
1153 (catch (cdr (assoc :catch clauses)))
1154 (finally (cdr (assoc :finally clauses))))
1155 (make-instance 'js-try
1156 :body body
1157 :catch (when catch (list (js-compile-to-symbol (caar catch))
1158 (js-compile-to-body (cons 'progn (cdr catch))
1159 :indent " ")))
1160 :finally (when finally (js-compile-to-body (cons 'progn finally)
1161 :indent " ")))))
1162
1163(defmethod js-to-statement-strings ((try js-try) start-pos)
1164 (let* ((catch (try-catch try))
1165 (finally (try-finally try))
1166 (catch-list (when catch
1167 (nconc
1168 (dwim-join (list (list (symbol-to-js (first catch))))
1169 (- 80 start-pos 2)
1170 :start "} catch ("
1171 :end ") {")
1172 (js-to-statement-strings (second catch) (+ start-pos 2)))))
1173 (finally-list (when finally
1174 (nconc (list "} finally {")
1175 (js-to-statement-strings finally (+ start-pos 2))))))
1176 (nconc (list "try {")
1177 (js-to-statement-strings (try-body try) (+ start-pos 2))
1178 catch-list
1179 finally-list
1180 (list "}"))))
1181
1182;;; regex
1183
1184(defjsclass regex (expression)
1185 (value))
1186
1187(define-js-compiler-macro regex (regex)
1188 (make-instance 'regex :value (string regex)))
1189
1190;;; conditional compilation
1191
1192(defjsclass cc-if ()
1193 ((test :initarg :test :accessor cc-if-test)
1194 (body :initarg :body :accessor cc-if-body)))
1195
1196(defmethod js-to-statement-strings ((cc cc-if) start-pos)
1197 (nconc (list (format nil "/*@if ~A" (cc-if-test cc)))
1198 (mapcan #'(lambda (x) (js-to-strings x start-pos)) (cc-if-body cc))
1199 (list "@end @*/")))
1200
1201(define-js-compiler-macro cc-if (test &rest body)
1202 (make-instance 'cc-if :test test
1203 :body (mapcar #'js-compile body)))
1204
1205;;; compiler
1206
1207(defun js-compile (form)
1208 (setf form (js-expand-form form))
1209 (cond ((stringp form)
1210 (make-instance 'string-literal :value form))
1211 ((numberp form)
1212 (make-instance 'number-literal :value form))
1213 ((symbolp form)
1214 (let ((c-macro (js-get-compiler-macro form)))
1215 (if c-macro
1216 (funcall c-macro)
1217 (make-instance 'js-variable :value form))))
1218 ((and (consp form)
1219 (eql (first form) 'quote))
1220 (second form))
1221 ((consp form)
1222 (js-compile-list form))
1223 (t (error "Unknown atomar expression ~S" form))))
1224
1225(defun js-compile-list (form)
1226 (let* ((name (car form))
1227 (args (cdr form))
1228 (js-form (js-get-compiler-macro name)))
1229 (cond (js-form
1230 (apply js-form args))
1231
1232 ((op-form-p form)
1233 (make-instance 'op-form
1234 :operator (js-convert-op-name (js-compile-to-symbol (first form)))
1235 :args (mapcar #'js-compile-to-expression (rest form))))
1236
1237 ((method-call-p form)
1238 (make-instance 'method-call
1239 :method (js-compile-to-symbol (first form))
1240 :object (js-compile-to-expression (second form))
1241 :args (mapcar #'js-compile-to-expression (cddr form))))
1242
1243 ((funcall-form-p form)
1244 (make-instance 'function-call
1245 :function (js-compile-to-expression (first form))
1246 :args (mapcar #'js-compile-to-expression (rest form))))
1247
1248 (t (error "Unknown form ~S" form)))))
1249
1250(defun js-compile-to-expression (form)
1251 (let ((res (js-compile form)))
1252 (assert (typep res 'expression))
1253 res))
1254
1255(defun js-compile-to-symbol (form)
1256 (let ((res (js-compile form)))
1257 (when (typep res 'js-variable )
1258 (setf res (value res)))
1259 (assert (symbolp res))
1260 res))
1261
1262(defun js-compile-to-statement (form)
1263 (let ((res (js-compile form)))
1264 (assert (typep res 'statement))
1265 res))
1266
1267(defun js-compile-to-body (form &key (indent ""))
1268 (let ((res (js-compile-to-statement form)))
1269 (if (typep res 'js-body)
1270 (progn (setf (b-indent res) indent)
1271 res)
1272 (make-instance 'js-body
1273 :indent indent
1274 :stmts (list res)))))
1275
1276;;; Math library
37e52bad 1277
8e198a08
MB
1278(defjsmacro floor (expr)
1279 `(*Math.floor ,expr))
1280
1281(defjsmacro random ()
1282 `(*Math.random))
1283
37e52bad
MB
1284;;; helper functions
1285
1286(defvar *gen-js-name-counter* 0)
1287
1288(defun gen-js-name (&key (prefix "parenscript_"))
2175ad09 1289 "Generate a new javascript identifier."
37e52bad
MB
1290 (intern (concatenate 'string
1291 prefix (princ-to-string (incf *gen-js-name-counter*)))
1292 (find-package :js)))
1293
1294(defmacro with-unique-js-names (symbols &body body)
2175ad09
MB
1295 "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
1296
1297Each element of SYMBOLS is either a symbol or a list of (symbol
1298prefix)."
37e52bad
MB
1299 `(let* ,(mapcar (lambda (symbol)
1300 (destructuring-bind (symbol &optional prefix)
1301 (if (consp symbol)
1302 symbol
1303 (list symbol))
1304 (if prefix
1305 `(,symbol (gen-js-name :prefix ,prefix))
1306 `(,symbol (gen-js-name)))))
1307 symbols)
1308 ,@body))
1309
8e198a08
MB
1310;;; helper macros
1311
1312(define-js-compiler-macro js (&rest body)
1313 (make-instance 'string-literal
1314 :value (string-join (js-to-statement-strings
1315 (js-compile (cons 'progn body)) 0) " ")))
1316
1317(define-js-compiler-macro js-inline (&rest body)
1318 (make-instance 'string-literal
1319 :value (concatenate
1320 'string
1321 "javascript:"
1322 (string-join (js-to-statement-strings
1323 (js-compile (cons 'progn body)) 0) " "))))
1324
1325
1326(defmacro js (&rest body)
f0b5d9e0 1327 `(js* '(progn ,@body)))
8e198a08 1328
d006f536 1329(defmacro js* (&rest body)
f0b5d9e0
MB
1330 "Return the javascript string representing BODY.
1331
1332Body is evaluated."
d006f536
MB
1333 `(string-join
1334 (js-to-statement-strings (js-compile (list 'progn ,@body)) 0)
1335 (string #\Newline)))
1336
8e198a08
MB
1337(defun js-to-string (expr)
1338 (string-join
1339 (js-to-statement-strings (js-compile expr) 0)
1340 (string #\Newline)))
1341
1342(defun js-to-line (expr)
1343 (string-join
1344 (js-to-statement-strings (js-compile expr) 0) " "))
1345
1346(defmacro js-file (&rest body)
1347 `(html
1348 (:princ
1349 (js ,@body))))
1350
1351(defmacro js-script (&rest body)
1352 `((:script :type "text/javascript")
1353 (:princ (format nil "~%// <![CDATA[~%"))
1354 (:princ (js ,@body))
1355 (:princ (format nil "~%// ]]>~%"))))
1356
1357(defmacro js-inline (&rest body)
f0b5d9e0 1358 `(js-inline '(progn ,@body)))
8e198a08 1359
d006f536
MB
1360(defmacro js-inline* (&rest body)
1361 "Just like JS-INLINE except that BODY is evaluated before being
1362converted to javascript."
1363 `(concatenate 'string "javascript:"
1364 (string-join (js-to-statement-strings (js-compile (list 'progn ,@body)) 0) " ")))
1365
1366