Merge branch 'master' of github.com:davazp/jscl
[jscl.git] / src / compiler.lisp
index 02a3d43..ef66b1f 100644 (file)
   `(push (list ',name (lambda ,args (block ,name ,@body)))
          *compilations*))
 
-(define-compilation if (condition true false)
+(define-compilation if (condition true &optional false)
   (code "(" (ls-compile condition) " !== " (ls-compile nil)
         " ? " (ls-compile true *multiple-value-p*)
         " : " (ls-compile false *multiple-value-p*)
       (js!selfcall
         "var func = " (join strs) ";" *newline*
         (when name
-          (code "func.fname = \"" (escape-string name) "\";" *newline*))
+          (code "func.fname = " (js-escape-string name) ";" *newline*))
         (when docstring
-          (code "func.docstring = \"" (escape-string docstring) "\";" *newline*))
+          (code "func.docstring = " (js-escape-string docstring) ";" *newline*))
         "return func;" *newline*)
       (apply #'code strs)))
 
                (mapconcat #'parse-keyword keyword-arguments))))
      ;; Check for unknown keywords
      (when keyword-arguments
-       (code "for (i=" (+ n-required-arguments n-optional-arguments)
-             "; i<nargs; i+=2){" *newline*
+       (code "var start = " (+ n-required-arguments n-optional-arguments) ";" *newline*
+             "if ((nargs - start) % 2 == 1){" *newline*
+             (indent "throw 'Odd number of keyword arguments';" *newline*)
+             "}" *newline*
+             "for (i = start; i<nargs; i+=2){" *newline*
              (indent "if ("
                      (join (mapcar (lambda (x)
                                      (concat "arguments[i+2] !== " (ls-compile (caar x))))
                            " && ")
                      ")" *newline*
                      (indent
-                      "throw 'Unknown keyword argument ' + xstring(arguments[i].name);" *newline*))
+                      "throw 'Unknown keyword argument ' + xstring(arguments[i+2].name);" *newline*))
              "}" *newline*)))))
 
 (defun parse-lambda-list (ll)
 
 (define-compilation setq (&rest pairs)
   (let ((result ""))
+    (when (null pairs)
+      (return-from setq (ls-compile nil)))
     (while t
       (cond
-       ((null pairs) (return))
+       ((null pairs)
+         (return))
        ((null (cdr pairs))
         (error "Odd pairs in SETQ"))
        (t
 
 ;;; Compilation of literals an object dumping
 
-(defun escape-string (string)
+;;; Two seperate functions are needed for escaping strings:
+;;;  One for producing JavaScript string literals (which are singly or
+;;;   doubly quoted)
+;;;  And one for producing Lisp strings (which are only doubly quoted)
+;;;
+;;; The same function would suffice for both, but for javascript string
+;;; literals it is neater to use either depending on the context, e.g:
+;;;  foo's => "foo's"
+;;;  "foo" => '"foo"'
+;;; which avoids having to escape quotes where possible
+(defun js-escape-string (string)
+  (let ((index 0)
+        (size (length string))
+        (seen-single-quote nil)
+        (seen-double-quote nil))
+    (flet ((%js-escape-string (string escape-single-quote-p)
+             (let ((output "")
+                   (index 0))
+               (while (< index size)
+                 (let ((ch (char string index)))
+                   (when (char= ch #\\)
+                     (setq output (concat output "\\")))
+                   (when (and escape-single-quote-p (char= ch #\'))
+                     (setq output (concat output "\\")))
+                   (when (char= ch #\newline)
+                     (setq output (concat output "\\"))
+                     (setq ch #\n))
+                   (setq output (concat output (string ch))))
+                 (incf index))
+               output)))
+      ;; First, scan the string for single/double quotes
+      (while (< index size)
+        (let ((ch (char string index)))
+          (when (char= ch #\')
+            (setq seen-single-quote t))
+          (when (char= ch #\")
+            (setq seen-double-quote t)))
+        (incf index))
+      ;; Then pick the appropriate way to escape the quotes
+      (cond
+        ((not seen-single-quote)
+         (concat "'"   (%js-escape-string string nil) "'"))
+        ((not seen-double-quote)
+         (concat "\""  (%js-escape-string string nil) "\""))
+        (t (concat "'" (%js-escape-string string t)   "'"))))))
+
+(defun lisp-escape-string (string)
   (let ((output "")
         (index 0)
         (size (length string)))
           (setq ch #\n))
         (setq output (concat output (string ch))))
       (incf index))
-    output))
+    (concat "\"" output "\"")))
 
 ;;; BOOTSTRAP MAGIC: We record the macro definitions as lists during
 ;;; the bootstrap. Once everything is compiled, we want to dump the
     (concat "[" (join (mapcar #'literal elements) ", ") "]")))
 
 (defun dump-string (string)
-  (code "make_lisp_string(\"" (escape-string string) "\")"))
+  (code "make_lisp_string(" (js-escape-string string) ")"))
 
 (defun literal (sexp &optional recursive)
   (cond
     ((integerp sexp) (integer-to-string sexp))
     ((floatp sexp) (float-to-string sexp))
-    ((characterp sexp) (code "\"" (escape-string (string sexp)) "\""))
+    ((characterp sexp) (js-escape-string (string sexp)))
     (t
      (or (cdr (assoc sexp *literal-table* :test #'eql))
          (let ((dumped (typecase sexp
                   ",")
             ")")))
 
+(define-compilation macrolet (definitions &rest body)
+  (let ((*environment* (copy-lexenv *environment*)))
+    (dolist (def definitions)
+      (destructuring-bind (name lambda-list &body body) def
+        (let ((binding (make-binding :name name :type 'macro :value
+                                     (let ((g!form (gensym)))
+                                       `(lambda (,g!form)
+                                          (destructuring-bind ,lambda-list ,g!form
+                                            ,@body))))))
+          (push-to-lexenv binding  *environment* 'function))))
+    (ls-compile `(progn ,@body) *multiple-value-p*)))
+
+
 (defun special-variable-p (x)
   (and (claimp x 'variable 'special) t))
 
   (js!selfcall
     "var tmp = (" (ls-compile object) ")[xstring(" (ls-compile key) ")];" *newline*
     (mapconcat (lambda (key)
-                 (code "if (tmp === undefined) return " (ls-compile nil) ";" *newline*)
-                 (code "tmp = tmp[xstring(" (ls-compile key) ")];" *newline*))
+                 (code "if (tmp === undefined) return " (ls-compile nil) ";" *newline*
+                       "tmp = tmp[xstring(" (ls-compile key) ")];" *newline*))
                keys)
     "return tmp === undefined? " (ls-compile nil) " : tmp;" *newline*))
 
     (js!selfcall
       "var obj = " (ls-compile object) ";" *newline*
       (mapconcat (lambda (key)
-                   "obj = obj[xstring(" (ls-compile key) ")];"
-                   "if (obj === undefined) throw 'Impossible to set Javascript property.';" *newline*)
+                   (code "obj = obj[xstring(" (ls-compile key) ")];"
+                         "if (obj === undefined) throw 'Impossible to set Javascript property.';" *newline*))
                  (butlast keys))
-      "obj[xstring(" (ls-compile (car (last keys))) ")] = " (ls-compile value) ";" *newline*)))
+      "var tmp = obj[xstring(" (ls-compile (car (last keys))) ")] = " (ls-compile value) ";" *newline*
+      "return tmp === undefined? " (ls-compile nil) " : tmp;" *newline*)))
 
 (define-raw-builtin oget (object key &rest keys)
   (code "js_to_lisp(" (ls-compile `(oget* ,object ,key ,@keys)) ")"))