Fixed js-with
[clinton/parenscript.git] / src / js.lisp
index 2b5ada2..296d704 100644 (file)
   (flet ((special-append (form elt)
           (let ((len (length form)))
             (if (and (> len 0)
-                     (member (char form (1- len))
-                             '(#\; #\, #\})))
+                      (string= (char form (1- len)) elt))
                 form
                 (concatenate 'string form elt)))))
     (cond ((stringp form)
   (defvar *js-compiler-macros* (make-hash-table :test 'equal)
         "*JS-COMPILER-MACROS* is a hash-table containing the functions corresponding
 to javascript special forms, indexed by their name. Javascript special
-forms are compiler macros for JS expressions."))
+forms are compiler macros for JS expressions.")
+
+  (defun undefine-js-compiler-macro (name)
+    (declare (type symbol name))
+    (when (gethash (symbol-name name) *js-compiler-macros*)
+      (warn "Redefining compiler macro ~S" name)
+      (remhash (symbol-name name) *js-compiler-macros*))))
 
 (defmacro define-js-compiler-macro (name lambda-list &rest body)
   "Define a javascript compiler macro NAME. Arguments are destructured
@@ -255,13 +260,20 @@ this macro."
 
 (defmacro defjsmacro (name args &rest body)
   "Define a javascript macro, and store it in the toplevel macro environment."
-  (when (gethash (symbol-name name) *js-compiler-macros*)
-    (warn "Redefining compiler macro ~S" name)
-    (remhash (symbol-name name) *js-compiler-macros*))
   (let ((lambda-list (gensym)))
+    (undefine-js-compiler-macro name)
     `(setf (gethash ,(symbol-name name) *js-macro-toplevel*)
       #'(lambda (&rest ,lambda-list)
-         (destructuring-bind ,args ,lambda-list ,@body)))))
+          (destructuring-bind ,args ,lambda-list ,@body)))))
+
+(defun import-macros-from-lisp (&rest names)
+  "Import the named lisp macros into the js macro expansion"
+  (dolist (name names)
+    (let ((name name))
+      (undefine-js-compiler-macro name)
+      (setf (gethash (symbol-name name) *js-macro-toplevel*)
+            (lambda (&rest args)
+              (macroexpand `(,name ,@args)))))))
 
 (defun js-expand-form (expr)
   "Expand a javascript form."
@@ -282,6 +294,50 @@ this macro."
                 (js-expand-form (apply js-macro (cdr expr)))
                 expr)))))
 
+(defvar *gen-js-name-counter* 0)
+
+(defun gen-js-name-string (&key (prefix "_ps_"))
+  "Generates a unique valid javascript identifier ()"
+  (concatenate 'string
+               prefix (princ-to-string (incf *gen-js-name-counter*))))
+
+(defun gen-js-name (&key (prefix "_ps_"))
+  "Generate a new javascript identifier."
+  (intern (gen-js-name-string :prefix prefix)
+          (find-package :js)))
+
+(defmacro with-unique-js-names (symbols &body body)
+  "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
+
+Each element of SYMBOLS is either a symbol or a list of (symbol
+prefix)."
+  `(let* ,(mapcar (lambda (symbol)
+                    (destructuring-bind (symbol &optional prefix)
+                        (if (consp symbol)
+                            symbol
+                            (list symbol))
+                      (if prefix
+                          `(,symbol (gen-js-name :prefix ,prefix))
+                          `(,symbol (gen-js-name)))))
+                  symbols)
+     ,@body))
+
+(defjsmacro rebind (variables expression)
+  ;; Creates a new js lexical environment and copies the given
+  ;; variable(s) there.  Executes the body in the new environment. This
+  ;; has the same effect as a new (let () ...) form in lisp but works on
+  ;; the js side for js closures."
+  
+  (unless (listp variables)
+    (setf variables (list variables)))
+  `((lambda ()
+      (let ((new-context (new *object)))
+        ,@(loop for variable in variables
+                do (setf variable (symbol-to-js variable))
+                collect `(setf (slot-value new-context ,variable) (slot-value this ,variable)))
+        (with new-context
+              (return ,expression))))))
+
 (defvar *var-counter* 0)
 
 (defun js-gensym (&optional (name "js"))
@@ -389,8 +445,8 @@ vice-versa.")
   (declare (ignore start-pos)
            (inline lisp-special-char-to-js))
   (list (with-output-to-string (escaped)
+          (write-char *js-quote-char*  escaped)
           (loop
-           initially (write-char *js-quote-char*  escaped)
            for char across (value string)
            for code = (char-code char)
            for special = (lisp-special-char-to-js char)
@@ -884,9 +940,28 @@ vice-versa.")
 (define-js-single-op delete)
 (define-js-single-op void)
 (define-js-single-op typeof)
-(define-js-single-op instanceof)
 (define-js-single-op new)
 
+;; TODO this may not be the best integrated implementation of
+;; instanceof into the rest of the code
+(defjsclass js-instanceof (expression)
+  ((value)
+   (type :initarg :type)))
+
+(define-js-compiler-macro instanceof (value type)
+  (make-instance 'js-instanceof
+                 :value (js-compile-to-expression value)
+                 :type (js-compile-to-expression type)))
+
+(defmethod js-to-strings ((instanceof js-instanceof) start-pos)
+  (dwim-join
+   (list (js-to-strings (value instanceof) (+ start-pos 2))
+         (list "instanceof")
+         (js-to-strings (slot-value instanceof 'type) (+ start-pos 2)))
+   (- 80 start-pos 2)
+   :white-space
+   "  "))
+
 ;;; assignment
 
 (defjsclass js-setf (expression)
@@ -1127,8 +1202,8 @@ vice-versa.")
 
 (define-js-compiler-macro with (statement &rest body)
   (make-instance 'js-with
-                :obj (js-compile-to-expression (first statement))
-                :body (js-compile-to-body (cons 'progn body) :indent "  ")))
+                 :obj (js-compile-to-expression statement)
+                 :body (js-compile-to-body (cons 'progn body) :indent "  ")))
 
 (defmethod js-to-statement-strings ((with js-with) start-pos)
   (nconc (dwim-join (list (js-to-strings (with-obj with) (+ start-pos 2)))
@@ -1238,9 +1313,14 @@ vice-versa.")
 (define-js-compiler-macro regex (regex)
   (make-instance 'regex :value (string regex)))
 
+(defun first-slash-p (string)
+  (and (> (length string) 0)
+       (eq (char string 0) '#\/)))
+
 (defmethod js-to-strings ((regex regex) start-pos)
-  (declare (ignore start-pos))
-  (list (format nil "/~A/" (value regex))))
+   (declare (ignore start-pos))
+   (let ((slash (if (first-slash-p (value regex)) nil "/")))
+     (list (format nil (concatenate 'string slash "~A" slash) (value regex)))))
 
 ;;; conditional compilation
 
@@ -1338,52 +1418,8 @@ vice-versa.")
 (defjsmacro random ()
   `(*Math.random))
 
-;;; helper functions
-
-(defvar *gen-js-name-counter* 0)
-
-(defun gen-js-name-string (&key (prefix "parenscript_"))
-  "Generates a unique valid javascript identifier ()"
-  (concatenate 'string
-               prefix (princ-to-string (incf *gen-js-name-counter*))))
-
-(defun gen-js-name (&key (prefix "parenscript_"))
-  "Generate a new javascript identifier."
-  (intern (gen-js-name-string :prefix prefix)
-          (find-package :js)))
-
-(defmacro with-unique-js-names (symbols &body body)
-  "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
-
-Each element of SYMBOLS is either a symbol or a list of (symbol
-prefix)."
-  `(let* ,(mapcar (lambda (symbol)
-                    (destructuring-bind (symbol &optional prefix)
-                        (if (consp symbol)
-                            symbol
-                            (list symbol))
-                      (if prefix
-                          `(,symbol (gen-js-name :prefix ,prefix))
-                          `(,symbol (gen-js-name)))))
-                  symbols)
-     ,@body))
-
 ;;; helper macros
 
-(defjsmacro rebind (variables expression)
-  "Creates a new js lexical environment and copies the given variable(s) there.
-Executes the body in the new environment. This has the same effect as a new
-(let () ...) form in lisp but works on the js side for js closures."
-  (unless (listp variables)
-    (setf variables (list variables)))
-  `((lambda ()
-      (let ((new-context (new *object)))
-        ,@(loop for variable in variables
-                do (setf variable (symbol-to-js variable))
-                collect `(setf (slot-value new-context ,variable) (slot-value this ,variable)))
-        (with (new-context)
-              (return ,expression))))))
-
 (define-js-compiler-macro js (&rest body)
   (make-instance 'string-literal
                 :value (string-join (js-to-statement-strings