Smarter string escaping. Fixes #114
authorOwen Rodley <Strigoides@gmail.com>
Tue, 4 Jun 2013 04:42:44 +0000 (16:42 +1200)
committerOwen Rodley <Strigoides@gmail.com>
Tue, 4 Jun 2013 04:52:51 +0000 (16:52 +1200)
If the string contains no single quotes, use a single-quoted string.
If the string contains no double quotes, use a double-quoted string.
If it contains both, single-quote it and escape the single-quotes.
In all cases backslashes and newlines are escaped.

src/compiler.lisp
src/print.lisp

index e2f9fca..fb9f62d 100644 (file)
       (js!selfcall
         "var func = " (join strs) ";" *newline*
         (when name
-          (code "func.fname = \"" (escape-string name) "\";" *newline*))
+          (code "func.fname = " (escape-string name) ";" *newline*))
         (when docstring
-          (code "func.docstring = \"" (escape-string docstring) "\";" *newline*))
+          (code "func.docstring = " (escape-string docstring) ";" *newline*))
         "return func;" *newline*)
       (apply #'code strs)))
 
 
 
 ;;; Compilation of literals an object dumping
-
 (defun escape-string (string)
-  (let ((output "")
-        (index 0)
-        (size (length string)))
-    (while (< index size)
-      (let ((ch (char string index)))
-        (when (or (char= ch #\") (char= ch #\\))
-          (setq output (concat output "\\")))
-        (when (or (char= ch #\newline))
-          (setq output (concat output "\\"))
-          (setq ch #\n))
-        (setq output (concat output (string ch))))
-      (incf index))
-    output))
+  (let ((index 0)
+        (size (length string))
+        (seen-single-quote nil)
+        (seen-double-quote nil))
+    (flet ((%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 "'"   (%escape-string string nil) "'"))
+        ((not seen-double-quote)
+         (concat "\""  (%escape-string string nil) "\""))
+        (t (concat "'" (%escape-string string t)   "'"))))))
 
 ;;; 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(" (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) (escape-string (string sexp)))
     (t
      (or (cdr (assoc sexp *literal-table* :test #'eql))
          (let ((dumped (typecase sexp
index 7daa143..f5e3f3b 100644 (file)
                          (#\space "space")
                          (otherwise (string form)))))
               ((stringp form) (if *print-escape*
-                                  (concat "\"" (escape-string form) "\"")
+                                  (escape-string form)
                                   form))
               ((functionp form)
                (let ((name (oget form "fname")))