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