`(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)))
(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-builtin new () "{}")
-(define-builtin oget* (object key)
- (js!selfcall
- "var tmp = " "(" object ")[xstring(" key ")];" *newline*
- "return tmp == undefined? " (ls-compile nil) ": tmp ;" *newline*))
-
-(define-builtin oset* (object key value)
- (code "((" object ")[xstring(" key ")] = " value ")"))
-
-(define-raw-builtin oget (object key &rest keys)
+(define-raw-builtin oget* (object key &rest keys)
(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*))
keys)
- "return tmp === undefined? " (ls-compile nil) " : js_to_lisp(tmp);" *newline*))
+ "return tmp === undefined? " (ls-compile nil) " : tmp;" *newline*))
+
+(define-raw-builtin oset* (value object key &rest keys)
+ (let ((keys (cons key keys)))
+ (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*)
+ (butlast keys))
+ "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)) ")"))
-(define-builtin oset (object key value)
- (code "((" object ")[xstring(" key ")] = lisp_to_js(" value "))"))
+(define-raw-builtin oset (value object key &rest keys)
+ (ls-compile `(oset* (lisp-to-js ,value) ,object ,key ,@keys)))
(define-builtin objectp (x)
(js!bool (code "(typeof (" x ") === 'object')")))
#+jscl (eq (symbol-package function) (find-package "COMMON-LISP"))
#-jscl t)
(code (ls-compile `',function) ".fvalue" arglist))
- #+jscl
- ((symbolp function)
+ #+jscl((symbolp function)
(code (ls-compile `#',function) arglist))
((and (consp function) (eq (car function) 'lambda))
(code (ls-compile `#',function) arglist))