3 ;;; ecmascript standard:
4 ;;; http://www.ecma-international.org/publications/standards/Ecma-262.htm
6 ;;; javascript name conversion
8 (defparameter *special-chars
*
19 (defun string-chars (string)
20 (coerce string
'list
))
22 (defun constant-string-p (string)
23 (let ((len (length string
))
24 (constant-chars '(#\
+ #\
*)))
26 (member (char string
0) constant-chars
)
27 (member (char string
(1- len
)) constant-chars
))))
29 (defun first-uppercase-p (string)
30 (and (> (length string
) 1)
31 (member (char string
0) '(#\
+ #\
*))))
33 (defun untouchable-string-p (string)
34 (and (> (length string
) 1)
35 (char= #\
: (char string
0))))
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)
47 (cond ((constant-string-p symbol
)
49 symbol
(subseq symbol
1 (1- (length symbol
)))))
50 ((first-uppercase-p symbol
)
52 symbol
(subseq symbol
1)))
53 ((untouchable-string-p symbol
)
55 symbol
(subseq symbol
1))))
59 ((and lowercase
(not all-uppercase
))
64 (dotimes (i (length symbol
))
65 (let ((c (char symbol i
)))
68 (setf lowercase
(not lowercase
)))
69 ((assoc c
*special-chars
*)
70 (dolist (i (coerce (cdr (assoc c
*special-chars
*)) 'list
))
73 (coerce (nreverse res
) 'string
)))
74 (t (string-join (mapcar #'symbol-to-js symbols
) ".")))))
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
))
85 (defmacro defjsclass
(name superclasses slots
&rest class-options
)
86 (let ((slot-names (mapcar #'(lambda (slot) (if (atom slot
) slot
(first slot
))) slots
)))
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
)))
96 (defjsclass statement
()
97 ((value :initarg
:value
:accessor value
:initform nil
)))
99 (defjsclass expression
(statement)
104 (defun special-append-to-last (form elt
)
105 (flet ((special-append (form elt
)
106 (let ((len (length form
)))
108 (member (char form
(1- len
))
111 (concatenate 'string form elt
)))))
112 (cond ((stringp form
)
113 (special-append form elt
))
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
))
120 (t (error "unsupported form ~S" form
)))))
122 (defun dwim-join (value-string-lists max-length
124 join-before join-after
125 white-space
(separator " ")
126 (append-to-last #'append-to-last
)
129 (format t
"value-string-lists: ~S~%" value-string-lists
)
135 (setf join-before
""))
137 ;;; collect single value-string-lists until line full
139 (do* ((string-lists value-string-lists
(cdr string-lists
))
140 (string-list (car string-lists
) (car string-lists
))
143 (white-space (or white-space
(make-string (length start
) :initial-element
#\Space
)))
149 (list (concatenate 'string start end
))
153 (funcall append-to-last
(first res
) end
)))
157 (format t
"string-list: ~S~%" string-list
)
160 (unless (null (cdr string-lists
))
161 (funcall append-to-last string-list join-after
)))
163 (if (and collect
(= (length string-list
) 1))
166 (format t
"cur-elt: ~S line-length ~D, max-length ~D, string: ~S~%"
168 (+ (length (first string-list
))
173 (< (+ (length (first string-list
))
174 (length cur-elt
)) max-length
))
176 (concatenate 'string cur-elt
177 (if cur-empty
"" (concatenate 'string separator join-before
))
182 (setf cur-elt
(concatenate 'string white-space
183 join-before
(first string-list
))
189 (setf cur-elt white-space
191 (setf res
(nconc (nreverse
192 (cons (concatenate 'string
193 cur-elt
(if (null res
)
196 (mapcar #'(lambda (x) (concatenate 'string white-space x
))
197 (cdr string-list
)))) res
))
198 (setf cur-elt white-space cur-empty t
)))))
200 (defmethod js-to-strings ((expression expression
) start-pos
)
201 (declare (ignore start-pos
))
202 (list (princ-to-string (value expression
))))
204 (defmethod js-to-statement-strings ((expression expression
) start-pos
)
205 (js-to-strings expression start-pos
))
207 (defmethod js-to-statement-strings ((statement statement
) start-pos
)
208 (declare (ignore start-pos
))
209 (list (princ-to-string (value statement
))))
213 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
214 (defvar *js-compiler-macros
* (make-hash-table)
215 "*JS-COMPILER-MACROS* is a hash-table containing the functions corresponding
216 to javascript special forms, indexed by their name. Javascript special
217 forms are compiler macros for JS expressions."))
219 (defmacro define-js-compiler-macro
(name lambda-list
&rest body
)
220 "Define a javascript compiler macro NAME. Arguments are destructured
221 according to LAMBDA-LIST. The resulting JS language types are appended
222 to 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
)
226 (setf (gethash ',name
*js-compiler-macros
*) #',js-name
))))
228 (defun js-compiler-macro-form-p (form)
229 (when (gethash (car form
) *js-compiler-macros
*)
232 (defun js-get-compiler-macro (name)
233 (gethash name
*js-compiler-macros
*))
237 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
238 (defvar *js-macro-toplevel
* (make-hash-table)
239 "Toplevel of macro expansion, holds all the toplevel javascript macros.")
240 (defvar *js-macro-env
* (list *js-macro-toplevel
*)
241 "Current macro environment."))
243 (defun lookup-macro (name)
244 "Lookup the macro NAME in the current macro expansion
245 environment. Returns the macro and the parent macro environment of
247 (do ((env *js-macro-env
* (cdr env
)))
249 (let ((val (gethash name
(car env
))))
251 (return-from lookup-macro
252 (values val
(or (cdr env
)
253 (list *js-macro-toplevel
*))))))))
255 (defmacro defjsmacro
(name args
&rest body
)
256 "Define a javascript macro, and store it in the toplevel macro environment."
257 (when (gethash name
*js-compiler-macros
*)
258 (warn "Redefining compiler macro ~S" name
)
259 (remhash name
*js-compiler-macros
*))
260 (let ((lambda-list (gensym)))
261 `(setf (gethash ',name
*js-macro-toplevel
*)
262 #'(lambda (&rest
,lambda-list
)
263 (destructuring-bind ,args
,lambda-list
,@body
)))))
265 (defun js-expand-form (expr)
266 "Expand a javascript form."
268 (multiple-value-bind (js-macro macro-env
)
271 (js-expand-form (let ((*js-macro-env
* macro-env
))
275 ((js-compiler-macro-form-p expr
) expr
)
277 ((equal (first expr
) 'quote
) expr
)
279 (t (let ((js-macro (lookup-macro (car expr
))))
281 (js-expand-form (apply js-macro
(cdr expr
)))
284 (defvar *var-counter
* 0)
286 (defun js-gensym (&optional
(name "js"))
287 (intern (format nil
"tmp-~A-~A" name
(incf *var-counter
*)) #.
*package
*))
291 (defmacro defjsliteral
(name string
)
292 "Define a Javascript literal that will expand to STRING."
293 `(define-js-compiler-macro ,name
() (make-instance 'expression
:value
,string
)))
295 (defjsliteral this
"this")
296 (defjsliteral t
"true")
297 (defjsliteral nil
"null")
298 (defjsliteral false
"false")
299 (defjsliteral undefined
"undefined")
301 (defmacro defjskeyword
(name string
)
302 "Define a Javascript keyword that will expand to STRING."
303 `(define-js-compiler-macro ,name
() (make-instance 'statement
:value
,string
)))
305 (defjskeyword break
"break")
306 (defjskeyword continue
"continue")
310 (defjsclass array-literal
(expression)
311 ((values :initarg
:values
:accessor array-values
)))
313 (define-js-compiler-macro array
(&rest values
)
314 (make-instance 'array-literal
315 :values
(mapcar #'js-compile-to-expression values
)))
317 (defjsmacro list
(&rest values
)
320 (defmethod js-to-strings ((array array-literal
) start-pos
)
321 (let ((value-string-lists
322 (mapcar #'(lambda (x) (js-to-strings x
(+ start-pos
2)))
323 (array-values array
)))
324 (max-length (- 80 start-pos
2)))
325 (dwim-join value-string-lists max-length
326 :start
"[ " :end
" ]"
329 (defjsclass js-aref
(expression)
330 ((array :initarg
:array
331 :accessor aref-array
)
332 (index :initarg
:index
333 :accessor aref-index
)))
335 (define-js-compiler-macro aref
(array &rest coords
)
336 (make-instance 'js-aref
337 :array
(js-compile-to-expression array
)
338 :index
(mapcar #'js-compile-to-expression coords
)))
340 (defmethod js-to-strings ((aref js-aref
) start-pos
)
341 (dwim-join (cons (js-to-strings (aref-array aref
) start-pos
)
342 (mapcar #'(lambda (x) (dwim-join (list (js-to-strings x
(+ start-pos
2)))
344 :start
"[" :end
"]"))
346 (- 80 start-pos
2) :separator
""
349 (defjsmacro make-array
(&rest inits
)
350 `(new (*array
,@inits
)))
354 (defjsclass string-literal
(expression)
357 (defmethod js-to-strings ((string string-literal
) start-pos
)
358 (declare (ignore start-pos
))
359 (list (format nil
"'~A'" (value string
))))
363 (defjsclass number-literal
(expression)
368 (defjsclass js-variable
(expression)
371 (defmethod js-to-strings ((v js-variable
) start-form
)
372 (declare (ignore start-form
))
373 (list (symbol-to-js (value v
))))
375 ;;; arithmetic operators
377 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
379 (defparameter *op-precedence-hash
* (make-hash-table))
381 (defparameter *op-precedences
*
398 (setf *= /= %
= += -
= <<= >>= >>>= \
&= ^
= \|
=)
401 ;;; generate the operator precedences from *OP-PRECEDENCES*
402 (let ((precedence 1))
403 (dolist (ops *op-precedences
*)
405 (setf (gethash op
*op-precedence-hash
*) precedence
))
408 (defun js-convert-op-name (op)
417 (defjsclass op-form
(expression)
418 ((operator :initarg
:operator
:accessor operator
)
419 (args :initarg
:args
:accessor op-args
)))
421 (defun op-form-p (form)
423 (not (js-compiler-macro-form-p form
))
424 (not (null (gethash (first form
) *op-precedence-hash
*)))))
426 (defun klammer (string-list)
427 (prepend-to-first string-list
"(")
428 (append-to-last string-list
")")
431 (defmethod expression-precedence ((expression expression
))
434 (defmethod expression-precedence ((form op-form
))
435 (gethash (operator form
) *op-precedence-hash
*))
437 (defmethod js-to-strings ((form op-form
) start-pos
)
438 (let* ((precedence (expression-precedence form
))
440 (mapcar #'(lambda (x)
441 (let ((string-list (js-to-strings x
(+ start-pos
2))))
442 (if (>= (expression-precedence x
) precedence
)
443 (klammer string-list
)
446 (max-length (- 80 start-pos
2))
447 (op-string (format nil
"~A " (operator form
))))
448 (dwim-join value-string-lists max-length
:join-before op-string
)))
450 (defjsmacro 1-
(form)
453 (defjsmacro 1+ (form)
456 (defjsclass one-op
(expression)
457 ((pre-p :initarg
:pre-p
459 :accessor one-op-pre-p
)
463 (defmethod js-to-strings ((one-op one-op
) start-pos
)
464 (let* ((value (value one-op
))
465 (value-strings (js-to-strings value start-pos
)))
466 (when (typep value
'op-form
)
467 (setf value-strings
(klammer value-strings
)))
468 (if (one-op-pre-p one-op
)
469 (prepend-to-first value-strings
471 (append-to-last value-strings
474 (define-js-compiler-macro incf
(x)
475 (make-instance 'one-op
:pre-p t
:op
"++"
476 :value
(js-compile-to-expression x
)))
477 (define-js-compiler-macro ++ (x)
478 (make-instance 'one-op
:pre-p nil
:op
"++"
479 :value
(js-compile-to-expression x
)))
480 (define-js-compiler-macro decf
(x)
481 (make-instance 'one-op
:pre-p t
:op
"--"
482 :value
(js-compile-to-expression x
)))
483 (define-js-compiler-macro --
(x)
484 (make-instance 'one-op
:pre-p nil
:op
"--"
485 :value
(js-compile-to-expression x
)))
488 (define-js-compiler-macro not
(x)
489 (let ((value (js-compile-to-expression x
)))
490 (if (and (typep value
'op-form
)
491 (= (length (op-args value
)) 2))
492 (let ((new-op (case (operator value
)
503 (make-instance 'op-form
:operator new-op
504 :args
(op-args value
))
505 (make-instance 'one-op
:pre-p t
:op
"!"
507 (make-instance 'one-op
:pre-p t
:op
"!"
512 (defjsclass function-call
(expression)
513 ((function :initarg
:function
:accessor f-function
)
514 (args :initarg
:args
:accessor f-args
)))
516 (defun funcall-form-p (form)
518 (not (op-form-p form
))
519 (not (js-compiler-macro-form-p form
))))
521 (defmethod js-to-strings ((form function-call
) start-pos
)
522 (let* ((value-string-lists
523 (mapcar #'(lambda (x) (js-to-strings x
(+ start-pos
2)))
525 (max-length (- 80 start-pos
2))
526 (args (dwim-join value-string-lists max-length
527 :start
"(" :end
")" :join-after
",")))
528 (etypecase (f-function form
)
530 (dwim-join (list (append (dwim-join (list (js-to-strings (f-function form
) (+ start-pos
2)))
532 :start
"(" :end
")" :separator
"")
536 ((or js-variable js-aref js-slot-value
)
537 (dwim-join (list (js-to-strings (f-function form
) (+ start-pos
2))
542 (defjsclass method-call
(expression)
543 ((method :initarg
:method
:accessor m-method
)
544 (object :initarg
:object
:accessor m-object
)
545 (args :initarg
:args
:accessor m-args
)))
547 (defmethod js-to-strings ((form method-call
) start-pos
)
548 (let ((fname (dwim-join (list (js-to-strings (m-object form
) (+ start-pos
2))
549 (list (symbol-to-js (m-method form
))))
553 (let ((butlast (butlast fname
))
554 (last (car (last fname
))))
556 (dwim-join (mapcar #'(lambda (x) (js-to-strings x
(+ start-pos
2)))
563 (defun method-call-p (form)
564 (and (funcall-form-p form
)
565 (symbolp (first form
))
566 (eql (char (symbol-name (first form
)) 0) #\.
)))
570 (defjsclass js-body
(expression)
571 ((stmts :initarg
:stmts
:accessor b-stmts
)
572 (indent :initarg
:indent
:initform
"" :accessor b-indent
)))
574 (define-js-compiler-macro progn
(&rest body
)
575 (make-instance 'js-body
576 :stmts
(mapcar #'js-compile-to-statement body
)))
578 (defmethod initialize-instance :after
((body js-body
) &rest initargs
)
579 (declare (ignore initargs
))
580 (let* ((stmts (b-stmts body
))
582 (last-stmt (car last
)))
583 (when (typep last-stmt
'js-body
)
585 (nconc (butlast stmts
)
586 (b-stmts last-stmt
))))))
589 (defmethod js-to-statement-strings ((body js-body
) start-pos
)
590 (dwim-join (mapcar #'(lambda (x) (js-to-statement-strings x
(+ start-pos
2)))
594 :append-to-last
#'special-append-to-last
595 :start
(b-indent body
) :collect nil
598 (defmethod js-to-strings ((body js-body
) start-pos
)
599 (dwim-join (mapcar #'(lambda (x) (js-to-strings x
(+ start-pos
2)))
602 :append-to-last
#'special-append-to-last
604 :start
(b-indent body
)))
606 (defjsclass js-sub-body
(js-body)
609 (defmethod js-to-statement-strings ((body js-sub-body
) start-pos
)
610 (declare (ignore start-pos
))
611 (nconc (list "{") (call-next-method) (list "}")))
613 (defmethod expression-precedence ((body js-body
))
614 (if (= (length (b-stmts body
)) 1)
615 (expression-precedence (first (b-stmts body
)))
616 (gethash 'comma
*op-precedence-hash
*)))
618 ;;; function definition
620 (defjsclass js-lambda
(expression)
621 ((args :initarg
:args
:accessor lambda-args
)
622 (body :initarg
:body
:accessor lambda-body
)))
624 (define-js-compiler-macro lambda
(args &rest body
)
625 (make-instance 'js-lambda
626 :args
(mapcar #'js-compile-to-symbol args
)
627 :body
(make-instance 'js-body
629 :stmts
(mapcar #'js-compile-to-statement body
))))
631 (defmethod js-to-strings ((lambda js-lambda
) start-pos
)
632 (let ((fun-header (dwim-join (mapcar #'(lambda (x)
633 (list (symbol-to-js x
)))
634 (lambda-args lambda
))
636 :start
(function-start-string lambda
)
637 :end
") {" :join-after
","))
638 (fun-body (js-to-statement-strings (lambda-body lambda
) (+ start-pos
2))))
639 (nconc fun-header fun-body
(list "}"))))
641 (defmethod function-start-string ((lambda js-lambda
))
644 (defmethod js-to-statement-strings ((lambda js-lambda
) start-pos
)
645 (js-to-strings lambda start-pos
))
647 (defjsclass js-defun
(js-lambda)
648 ((name :initarg
:name
:accessor defun-name
)))
650 (define-js-compiler-macro defun
(name args
&rest body
)
651 (make-instance 'js-defun
652 :name
(js-compile-to-symbol name
)
653 :args
(mapcar #'js-compile-to-symbol args
)
654 :body
(make-instance 'js-body
656 :stmts
(mapcar #'js-compile-to-statement body
))))
658 (defmethod function-start-string ((defun js-defun))
659 (format nil
"function ~A(" (symbol-to-js (defun-name defun
))))
663 (defjsclass js-object
(expression)
664 ((slots :initarg
:slots
667 (define-js-compiler-macro create
(&rest args
)
668 (make-instance 'js-object
669 :slots
(loop for
(name val
) on args by
#'cddr
670 collect
(list (js-compile-to-symbol name
)
671 (js-compile-to-expression val
)))))
673 (defmethod js-to-strings ((object js-object
) start-pos
)
674 (let ((value-string-lists
675 (mapcar #'(lambda (slot)
676 (dwim-join (list (js-to-strings (second slot
) (+ start-pos
4)))
678 :start
(concatenate 'string
(symbol-to-js (first slot
)) " : ")
679 :white-space
" ")) (o-slots object
)))
680 (max-length (- 80 start-pos
2)))
681 (dwim-join value-string-lists max-length
688 (defjsclass js-slot-value
(expression)
689 ((object :initarg
:object
694 (define-js-compiler-macro slot-value
(obj slot
)
695 (make-instance 'js-slot-value
:object
(js-compile-to-expression obj
)
696 :slot
(js-compile-to-symbol slot
)))
698 (defmethod js-to-strings ((sv js-slot-value
) start-pos
)
699 (append-to-last (js-to-strings (sv-object sv
) start-pos
)
700 (format nil
".~A" (symbol-to-js (sv-slot sv
)))))
702 (defjsmacro with-slots
(slots object
&rest body
)
703 `(symbol-macrolet ,(mapcar #'(lambda (slot)
704 `(,slot
'(slot-value ,object
',slot
)))
710 (define-js-compiler-macro macrolet
(macros &rest body
)
711 (let* ((macro-env (make-hash-table))
712 (*js-macro-env
* (cons macro-env
*js-macro-env
*)))
713 (dolist (macro macros
)
714 (destructuring-bind (name arglist
&rest body
) macro
715 (setf (gethash name macro-env
)
716 (compile nil
`(lambda ,arglist
,@body
)))))
717 (js-compile `(progn ,@body
))))
719 (defjsmacro symbol-macrolet
(macros &rest body
)
720 `(macrolet ,(mapcar #'(lambda (macro)
721 `(,(first macro
) () ,@(rest macro
))) macros
)
726 (defjsmacro lisp
(&rest forms
)
727 (eval (cons 'progn forms
)))
731 (defjsclass js-if
(expression)
732 ((test :initarg
:test
739 (define-js-compiler-macro if
(test then
&optional else
)
740 (make-instance 'js-if
:test
(js-compile-to-expression test
)
741 :then
(js-compile-to-body then
:indent
" ")
743 (js-compile-to-body else
:indent
" "))))
745 (defmethod initialize-instance :after
((if js-if
) &rest initargs
)
746 (declare (ignore initargs
))
747 (when (and (if-then if
)
748 (typep (if-then if
) 'js-sub-body
))
749 (change-class (if-then if
) 'js-body
))
750 (when (and (if-else if
)
751 (typep (if-else if
) 'js-sub-body
))
752 (change-class (if-else if
) 'js-body
)))
754 (defmethod js-to-statement-strings ((if js-if
) start-pos
)
755 (let ((if-strings (dwim-join (list (js-to-strings (if-test if
) 0))
759 (then-strings (js-to-statement-strings (if-then if
) (+ start-pos
2)))
760 (else-strings (when (if-else if
)
761 (js-to-statement-strings (if-else if
)
763 (nconc if-strings then-strings
(if else-strings
764 (nconc (list "} else {") else-strings
(list "}"))
767 (defmethod expression-precedence ((if js-if
))
768 (gethash 'if
*op-precedence-hash
*))
770 (defmethod js-to-strings ((if js-if
) start-pos
)
771 (assert (typep (if-then if
) 'expression
))
773 (assert (typep (if-else if
) 'expression
)))
774 (dwim-join (list (append-to-last (js-to-strings (if-test if
) start-pos
) " ?")
775 (let* ((new-then (make-instance 'js-body
776 :stmts
(b-stmts (if-then if
))
778 (res (js-to-strings new-then start-pos
)))
779 (if (>= (expression-precedence (if-then if
))
780 (expression-precedence if
))
785 (let* ((new-else (make-instance 'js-body
786 :stmts
(b-stmts (if-else if
))
788 (res (js-to-strings new-else start-pos
)))
789 (if (>= (expression-precedence (if-else if
))
790 (expression-precedence if
))
797 (defjsmacro when
(test &rest body
)
798 `(if ,test
(progn ,@body
)))
800 (defjsmacro unless
(test &rest body
)
801 `(if (not ,test
) (progn ,@body
)))
803 ;;; single keyword expressions and statements
805 (defmacro define-js-single-op
(name &optional
(superclass 'expression
))
806 (let ((js-name (intern (concatenate 'string
"JS-" (symbol-name name
)) #.
*package
*)))
808 (defjsclass ,js-name
(,superclass
)
810 (define-js-compiler-macro ,name
(value)
811 (make-instance ',js-name
:value
(js-compile-to-expression value
)))
812 (defmethod ,(if (eql superclass
'expression
)
814 'js-to-statement-strings
) ((,name
,js-name
) start-pos
)
815 (dwim-join (list (js-to-strings (value ,name
) (+ start-pos
2)))
817 :start
,(concatenate 'string
(string-downcase (symbol-name name
)) " ")
818 :white-space
" ")))))
821 (define-js-single-op return statement
)
822 (define-js-single-op throw statement
)
823 (define-js-single-op delete
)
824 (define-js-single-op void
)
825 (define-js-single-op typeof
)
826 (define-js-single-op instanceof
)
827 (define-js-single-op new
)
831 (defjsclass js-setf
(expression)
832 ((lhs :initarg
:lhs
:accessor setf-lhs
)
833 (rhsides :initarg
:rhsides
:accessor setf-rhsides
)))
835 (defun assignment-op (op)
851 (defun make-js-test (lhs rhs
)
852 (if (and (typep rhs
'op-form
)
853 (member lhs
(op-args rhs
) :test
#'js-equal
))
854 (let ((args-without (remove lhs
(op-args rhs
)
855 :count
1 :test
#'js-equal
))
856 (args-without-first (remove lhs
(op-args rhs
)
859 (one (list (make-instance 'number-literal
:value
1))))
861 (format t
"OPERATOR: ~S, ARGS-WITHOUT: ~S, ARGS-WITHOUT-FIRST ~S~%"
865 (cond ((and (js-equal args-without one
)
866 (eql (operator rhs
) '+))
867 (make-instance 'one-op
:pre-p nil
:op
"++"
869 ((and (js-equal args-without-first one
)
870 (eql (operator rhs
) '-
))
871 (make-instance 'one-op
:pre-p nil
:op
"--"
873 ((and (assignment-op (operator rhs
))
874 (member (operator rhs
)
876 (make-instance 'op-form
877 :operator
(assignment-op (operator rhs
))
878 :args
(list lhs
(make-instance 'op-form
879 :operator
(operator rhs
)
880 :args args-without
))))
881 ((and (assignment-op (operator rhs
))
882 (js-equal (first (op-args rhs
)) lhs
))
883 (make-instance 'op-form
884 :operator
(assignment-op (operator rhs
))
885 :args
(list lhs
(make-instance 'op-form
886 :operator
(operator rhs
)
887 :args
(cdr (op-args rhs
))))))
888 (t (make-instance 'js-setf
:lhs lhs
:rhsides
(list rhs
)))))
889 (make-instance 'js-setf
:lhs lhs
:rhsides
(list rhs
))))
891 (define-js-compiler-macro setf
(&rest args
)
892 (let ((assignments (loop for
(lhs rhs
) on args by
#'cddr
893 for rexpr
= (js-compile-to-expression rhs
)
894 for lexpr
= (js-compile-to-expression lhs
)
895 collect
(make-js-test lexpr rexpr
))))
896 (if (= (length assignments
) 1)
898 (make-instance 'js-body
:indent
"" :stmts assignments
))))
900 (defmethod js-to-strings ((setf js-setf
) start-pos
)
901 (dwim-join (cons (js-to-strings (setf-lhs setf
) start-pos
)
902 (mapcar #'(lambda (x) (js-to-strings x start-pos
)) (setf-rhsides setf
)))
906 (defmethod expression-precedence ((setf js-setf
))
907 (gethash '= *op-precedence-hash
*))
911 (defjsclass js-defvar
(statement)
912 ((names :initarg
:names
:accessor var-names
)
913 (value :initarg
:value
:accessor var-value
)))
915 (define-js-compiler-macro defvar
(name &optional value
)
916 (make-instance 'js-defvar
:names
(list (js-compile-to-symbol name
))
917 :value
(when value
(js-compile-to-expression value
))))
919 (defmethod js-to-statement-strings ((defvar js-defvar
) start-pos
)
920 (dwim-join (nconc (mapcar #'(lambda (x) (list (symbol-to-js x
))) (var-names defvar
))
921 (when (var-value defvar
)
922 (list (js-to-strings (var-value defvar
) start-pos
))))
925 :start
"var " :end
";"))
929 (define-js-compiler-macro let
(decls &rest body
)
930 (let ((single-defvar (make-instance 'js-defvar
931 :names
(mapcar #'js-compile-to-symbol
932 (remove-if-not #'atom decls
))
934 (defvars (mapcar #'(lambda (decl)
935 (let ((name (first decl
))
936 (value (second decl
)))
937 (make-instance 'js-defvar
938 :names
(list (js-compile-to-symbol name
))
939 :value
(js-compile-to-expression value
))))
940 (remove-if #'atom decls
))))
941 (make-instance 'js-sub-body
943 :stmts
(nconc (when (var-names single-defvar
) (list single-defvar
))
945 (mapcar #'js-compile-to-statement body
)))))
949 (defjsclass js-for
(statement)
950 ((vars :initarg
:vars
:accessor for-vars
)
951 (steps :initarg
:steps
:accessor for-steps
)
952 (check :initarg
:check
:accessor for-check
)
953 (body :initarg
:body
:accessor for-body
)))
955 (defun make-for-vars (decls)
956 (loop for decl in decls
957 for var
= (if (atom decl
) decl
(first decl
))
958 for init
= (if (atom decl
) nil
(second decl
))
959 collect
(make-instance 'js-defvar
:names
(list (js-compile-to-symbol var
))
960 :value
(js-compile-to-expression init
))))
962 (defun make-for-steps (decls)
963 (loop for decl in decls
964 when
(= (length decl
) 3)
965 collect
(js-compile-to-expression (third decl
))))
967 (define-js-compiler-macro do
(decls termination
&rest body
)
968 (let ((vars (make-for-vars decls
))
969 (steps (make-for-steps decls
))
970 (check (js-compile-to-expression (list 'not
(first termination
))))
971 (body (js-compile-to-body (cons 'progn body
) :indent
" ")))
972 (make-instance 'js-for
978 (defjsmacro dotimes
(iter &rest body
)
979 (let ((var (first iter
))
980 (times (second iter
)))
981 `(do ((,var
0 (++ ,var
)))
985 (defjsmacro dolist
(i-array &rest body
)
986 (let ((var (first i-array
))
987 (array (second i-array
))
988 (arrvar (js-gensym "arr"))
989 (idx (js-gensym "i")))
990 `(let ((,arrvar
,array
))
991 (do ((,idx
0 (++ ,idx
)))
992 ((>= ,idx
(slot-value ,arrvar
'length
)))
993 (let ((,var
(aref ,arrvar
,idx
)))
996 (defmethod js-to-statement-strings ((for js-for
) start-pos
)
997 (let* ((init (dwim-join (mapcar #'(lambda (x)
998 (dwim-join (list (list (symbol-to-js (first (var-names x
))))
999 (js-to-strings (var-value x
)
1005 :start
"var " :join-after
","))
1006 (check (js-to-strings (for-check for
) (+ start-pos
2)))
1007 (steps (dwim-join (mapcar #'(lambda (x var
)
1009 (list (list (symbol-to-js (first (var-names var
))))
1010 (js-to-strings x
(- start-pos
2)))
1017 (header (dwim-join (list init check steps
)
1019 :start
"for (" :end
") {"
1021 (body (js-to-statement-strings (for-body for
) (+ start-pos
2))))
1022 (nconc header body
(list "}"))))
1024 (defjsclass for-each
(statement)
1025 ((name :initarg
:name
:accessor fe-name
)
1026 (value :initarg
:value
:accessor fe-value
)
1027 (body :initarg
:body
:accessor fe-body
)))
1029 (define-js-compiler-macro doeach
(decl &rest body
)
1030 (make-instance 'for-each
:name
(js-compile-to-symbol (first decl
))
1031 :value
(js-compile-to-expression (second decl
))
1032 :body
(js-compile-to-body (cons 'progn body
) :indent
" ")))
1034 (defmethod js-to-statement-strings ((fe for-each
) start-pos
)
1035 (let ((header (dwim-join (list (list (symbol-to-js (fe-name fe
)))
1037 (js-to-strings (fe-value fe
) (+ start-pos
2)))
1041 (body (js-to-statement-strings (fe-body fe
) (+ start-pos
2))))
1042 (nconc header body
(list "}"))))
1044 (defjsclass js-while
(statement)
1045 ((check :initarg
:check
:accessor while-check
)
1046 (body :initarg
:body
:accessor while-body
)))
1048 (define-js-compiler-macro while
(check &rest body
)
1049 (make-instance 'js-while
1050 :check
(js-compile-to-expression check
)
1051 :body
(js-compile-to-body (cons 'progn body
) :indent
" ")))
1053 (defmethod js-to-statement-strings ((while js-while
) start-pos
)
1054 (let ((header (dwim-join (list (js-to-strings (while-check while
) (+ start-pos
2)))
1058 (body (js-to-statement-strings (while-body while
) (+ start-pos
2))))
1059 (nconc header body
(list "}"))))
1063 (defjsclass js-with
(statement)
1064 ((obj :initarg
:obj
:accessor with-obj
)
1065 (body :initarg
:body
:accessor with-body
)))
1067 (define-js-compiler-macro with
(statement &rest body
)
1068 (make-instance 'js-with
1069 :obj
(js-compile-to-expression (first statement
))
1070 :body
(js-compile-to-body (cons 'progn body
) :indent
" ")))
1072 (defmethod js-to-statement-strings ((with js-with
) start-pos
)
1073 (nconc (dwim-join (list (js-to-strings (with-obj with
) (+ start-pos
2)))
1075 :start
"with (" :end
") {")
1076 (js-to-statement-strings (with-body with
) (+ start-pos
2))
1081 (defjsclass js-case
(statement)
1082 ((value :initarg
:value
:accessor case-value
)
1083 (clauses :initarg
:clauses
:accessor case-clauses
)))
1085 (define-js-compiler-macro case
(value &rest clauses
)
1086 (let ((clauses (mapcar #'(lambda (clause)
1087 (let ((val (first clause
))
1088 (body (cdr clause
)))
1089 (list (if (eql val
'default
)
1091 (js-compile-to-expression val
))
1092 (js-compile-to-body (cons 'progn body
) :indent
" "))))
1094 (check (js-compile-to-expression value
)))
1095 (make-instance 'js-case
:value check
1098 (defmethod js-to-statement-strings ((case js-case
) start-pos
)
1099 (let ((body (mapcan #'(lambda (clause)
1100 (let ((val (car clause
))
1101 (body (second clause
)))
1102 (dwim-join (list (if (eql val
'default
)
1104 (js-to-strings val
(+ start-pos
2)))
1105 (js-to-statement-strings body
(+ start-pos
2)))
1107 :start
(if (eql val
'default
) " default" " case ")
1109 :join-after
":"))) (case-clauses case
))))
1112 (format t
"body: ~S~%" body
)
1113 (nconc (dwim-join (list (js-to-strings (case-value case
) (+ start-pos
2)))
1115 :start
"switch (" :end
") {")
1121 (defjsclass js-try
(statement)
1122 ((body :initarg
:body
:accessor try-body
)
1123 (catch :initarg
:catch
:accessor try-catch
)
1124 (finally :initarg
:finally
:accessor try-finally
)))
1126 (define-js-compiler-macro try
(body &rest clauses
)
1127 (let ((body (js-compile-to-body body
:indent
" "))
1128 (catch (cdr (assoc :catch clauses
)))
1129 (finally (cdr (assoc :finally clauses
))))
1130 (make-instance 'js-try
1132 :catch
(when catch
(list (js-compile-to-symbol (caar catch
))
1133 (js-compile-to-body (cons 'progn
(cdr catch
))
1135 :finally
(when finally
(js-compile-to-body (cons 'progn finally
)
1138 (defmethod js-to-statement-strings ((try js-try
) start-pos
)
1139 (let* ((catch (try-catch try
))
1140 (finally (try-finally try
))
1141 (catch-list (when catch
1143 (dwim-join (list (list (symbol-to-js (first catch
))))
1147 (js-to-statement-strings (second catch
) (+ start-pos
2)))))
1148 (finally-list (when finally
1149 (nconc (list "} finally {")
1150 (js-to-statement-strings finally
(+ start-pos
2))))))
1151 (nconc (list "try {")
1152 (js-to-statement-strings (try-body try
) (+ start-pos
2))
1159 (defjsclass regex
(expression)
1162 (define-js-compiler-macro regex
(regex)
1163 (make-instance 'regex
:value
(string regex
)))
1165 ;;; conditional compilation
1167 (defjsclass cc-if
()
1168 ((test :initarg
:test
:accessor cc-if-test
)
1169 (body :initarg
:body
:accessor cc-if-body
)))
1171 (defmethod js-to-statement-strings ((cc cc-if
) start-pos
)
1172 (nconc (list (format nil
"/*@if ~A" (cc-if-test cc
)))
1173 (mapcan #'(lambda (x) (js-to-strings x start-pos
)) (cc-if-body cc
))
1176 (define-js-compiler-macro cc-if
(test &rest body
)
1177 (make-instance 'cc-if
:test test
1178 :body
(mapcar #'js-compile body
)))
1182 (defun js-compile (form)
1183 (setf form
(js-expand-form form
))
1184 (cond ((stringp form
)
1185 (make-instance 'string-literal
:value form
))
1187 (make-instance 'number-literal
:value form
))
1189 (let ((c-macro (js-get-compiler-macro form
)))
1192 (make-instance 'js-variable
:value form
))))
1194 (eql (first form
) 'quote
))
1197 (js-compile-list form
))
1198 (t (error "Unknown atomar expression ~S" form
))))
1200 (defun js-compile-list (form)
1201 (let* ((name (car form
))
1203 (js-form (js-get-compiler-macro name
)))
1205 (apply js-form args
))
1208 (make-instance 'op-form
1209 :operator
(js-convert-op-name (js-compile-to-symbol (first form
)))
1210 :args
(mapcar #'js-compile-to-expression
(rest form
))))
1212 ((method-call-p form
)
1213 (make-instance 'method-call
1214 :method
(js-compile-to-symbol (first form
))
1215 :object
(js-compile-to-expression (second form
))
1216 :args
(mapcar #'js-compile-to-expression
(cddr form
))))
1218 ((funcall-form-p form
)
1219 (make-instance 'function-call
1220 :function
(js-compile-to-expression (first form
))
1221 :args
(mapcar #'js-compile-to-expression
(rest form
))))
1223 (t (error "Unknown form ~S" form
)))))
1225 (defun js-compile-to-expression (form)
1226 (let ((res (js-compile form
)))
1227 (assert (typep res
'expression
))
1230 (defun js-compile-to-symbol (form)
1231 (let ((res (js-compile form
)))
1232 (when (typep res
'js-variable
)
1233 (setf res
(value res
)))
1234 (assert (symbolp res
))
1237 (defun js-compile-to-statement (form)
1238 (let ((res (js-compile form
)))
1239 (assert (typep res
'statement
))
1242 (defun js-compile-to-body (form &key
(indent ""))
1243 (let ((res (js-compile-to-statement form
)))
1244 (if (typep res
'js-body
)
1245 (progn (setf (b-indent res
) indent
)
1247 (make-instance 'js-body
1249 :stmts
(list res
)))))
1252 (defjsmacro floor
(expr)
1253 `(*Math.floor
,expr
))
1255 (defjsmacro random
()
1260 (define-js-compiler-macro js
(&rest body
)
1261 (make-instance 'string-literal
1262 :value
(string-join (js-to-statement-strings
1263 (js-compile (cons 'progn body
)) 0) " ")))
1265 (define-js-compiler-macro js-inline
(&rest body
)
1266 (make-instance 'string-literal
1270 (string-join (js-to-statement-strings
1271 (js-compile (cons 'progn body
)) 0) " "))))
1274 (defmacro js
(&rest body
)
1275 `(js* '(progn ,@body
)))
1277 (defmacro js
* (&rest body
)
1278 "Return the javascript string representing BODY.
1282 (js-to-statement-strings (js-compile (list 'progn
,@body
)) 0)
1283 (string #\Newline
)))
1285 (defun js-to-string (expr)
1287 (js-to-statement-strings (js-compile expr
) 0)
1288 (string #\Newline
)))
1290 (defun js-to-line (expr)
1292 (js-to-statement-strings (js-compile expr
) 0) " "))
1294 (defmacro js-file
(&rest body
)
1299 (defmacro js-script
(&rest body
)
1300 `((:script
:type
"text/javascript")
1301 (:princ
(format nil
"~%// <![CDATA[~%"))
1302 (:princ
(js ,@body
))
1303 (:princ
(format nil
"~%// ]]>~%"))))
1305 (defmacro js-inline
(&rest body
)
1306 `(js-inline '(progn ,@body
)))
1308 (defmacro js-inline
* (&rest body
)
1309 "Just like JS-INLINE except that BODY is evaluated before being
1310 converted to javascript."
1311 `(concatenate 'string
"javascript:"
1312 (string-join (js-to-statement-strings (js-compile (list 'progn
,@body
)) 0) " ")))