Use a CODE function instead of CONCAT
[jscl.git] / ecmalisp.lisp
index cdf33d0..2777a90 100644 (file)
     `(eval-when-compile
        ,@(mapcar (lambda (decl) `(!proclaim ',decl)) decls)))
 
-  (declaim (constant nil t) (special t nil))
-  (setq nil 'nil)
+  (defmacro defconstant (name value &optional docstring)
+    `(progn
+       (declaim (special ,name))
+       (declaim (constant ,name))
+       (setq ,name ,value)
+       ,@(when (stringp docstring) `((oset ',name "vardoc" ,docstring)))
+       ',name))
+
+  (defconstant t 't)
+  (defconstant nil 'nil)
   (js-vset "nil" nil)
-  (setq t 't)
 
   (defmacro lambda (args &body body)
     `(function (lambda ,args ,@body)))
 
   (defun identity (x) x)
 
+  (defun constantly (x)
+    (lambda (&rest args)
+      x))
+
   (defun copy-list (x)
     (mapcar #'identity x))
 
 
   (defun digit-char (weight)
     (and (<= 0 weight 9)
-        (char "0123456789" weight)))  
+        (char "0123456789" weight)))
 
   (defun subseq (seq a &optional b)
     (cond
   (defun values (&rest args)
     (values-list args)))
 
-
-;;; Like CONCAT, but prefix each line with four spaces. Two versions
-;;; of this function are available, because the Ecmalisp version is
-;;; very slow and bootstraping was annoying.
-
-#+ecmalisp
-(defun indent (&rest string)
-  (let ((input (join string)))
-    (let ((output "")
-          (index 0)
-          (size (length input)))
-      (when (plusp (length input)) (concatf output "    "))
-      (while (< index size)
-        (let ((str
-               (if (and (char= (char input index) #\newline)
-                        (< index (1- size))
-                        (not (char= (char input (1+ index)) #\newline)))
-                   (concat (string #\newline) "    ")
-                   (string (char input index)))))
-          (concatf output str))
-        (incf index))
-      output)))
-
-#+common-lisp
-(defun indent (&rest string)
-  (with-output-to-string (*standard-output*)
-    (with-input-from-string (input (join string))
-      (loop
-         for line = (read-line input nil)
-         while line
-         do (write-string "    ")
-         do (write-line line)))))
-
 (defun integer-to-string (x)
   (cond
     ((zerop x)
                  digits)))))
 
 
-;;; Wrap X with a Javascript code to convert the result from
-;;; Javascript generalized booleans to T or NIL.
-(defun js!bool (x)
-  (concat "(" x "?" (ls-compile t) ": " (ls-compile nil) ")"))
-
-;;; Concatenate the arguments and wrap them with a self-calling
-;;; Javascript anonymous function. It is used to make some Javascript
-;;; statements valid expressions and provide a private scope as well.
-;;; It could be defined as function, but we could do some
-;;; preprocessing in the future.
-(defmacro js!selfcall (&body body)
-  `(concat "(function(){" *newline* (indent ,@body) "})()"))
-
-
 ;;; Printer
 
 #+ecmalisp
 ;;; too. The respective real functions are defined in the target (see
 ;;; the beginning of this file) as well as some primitive functions.
 
+(defun code (&rest args)
+  (mapconcat (lambda (arg)
+               (cond
+                 ((null arg) "")
+                 ((integerp arg) (integer-to-string arg))
+                 ((stringp arg) arg)
+                 (t (error "Unknown argument."))))
+             args))
+
+;;; Wrap X with a Javascript code to convert the result from
+;;; Javascript generalized booleans to T or NIL.
+(defun js!bool (x)
+  (code "(" x "?" (ls-compile t) ": " (ls-compile nil) ")"))
+
+;;; Concatenate the arguments and wrap them with a self-calling
+;;; Javascript anonymous function. It is used to make some Javascript
+;;; statements valid expressions and provide a private scope as well.
+;;; It could be defined as function, but we could do some
+;;; preprocessing in the future.
+(defmacro js!selfcall (&body body)
+  `(code "(function(){" *newline* (indent ,@body) "})()"))
+
+;;; Like CODE, but prefix each line with four spaces. Two versions
+;;; of this function are available, because the Ecmalisp version is
+;;; very slow and bootstraping was annoying.
+
+#+ecmalisp
+(defun indent (&rest string)
+  (let ((input (apply #'code string)))
+    (let ((output "")
+          (index 0)
+          (size (length input)))
+      (when (plusp (length input)) (concatf output "    "))
+      (while (< index size)
+        (let ((str
+               (if (and (char= (char input index) #\newline)
+                        (< index (1- size))
+                        (not (char= (char input (1+ index)) #\newline)))
+                   (concat (string #\newline) "    ")
+                   (string (char input index)))))
+          (concatf output str))
+        (incf index))
+      output)))
+
+#+common-lisp
+(defun indent (&rest string)
+  (with-output-to-string (*standard-output*)
+    (with-input-from-string (input (apply #'code string))
+      (loop
+         for line = (read-line input nil)
+         while line
+         do (write-string "    ")
+         do (write-line line)))))
+
+
 ;;; A Form can return a multiple values object calling VALUES, like
 ;;; values(arg1, arg2, ...). It will work in any context, as well as
 ;;; returning an individual object. However, if the special variable
 ;;; function call.
 (defvar *multiple-value-p* nil)
 
-
 (defun make-binding (name type value &optional declarations)
   (list name type value declarations))
 
 
 (defvar *variable-counter* 0)
 (defun gvarname (symbol)
-  (concat "v" (integer-to-string (incf *variable-counter*))))
+  (code "v" (incf *variable-counter*)))
 
 (defun translate-variable (symbol)
   (binding-value (lookup-in-lexenv symbol *environment* 'variable)))
          *compilations*))
 
 (define-compilation if (condition true false)
-  (concat "(" (ls-compile condition) " !== " (ls-compile nil)
-          " ? " (ls-compile true *multiple-value-p*)
-          " : " (ls-compile false *multiple-value-p*)
-          ")"))
+  (code "(" (ls-compile condition) " !== " (ls-compile nil)
+        " ? " (ls-compile true *multiple-value-p*)
+        " : " (ls-compile false *multiple-value-p*)
+        ")"))
 
-(defvar *lambda-list-keywords* '(&optional &rest))
+(defvar *lambda-list-keywords* '(&optional &rest &key))
 
 (defun list-until-keyword (list)
   (if (or (null list) (member (car list) *lambda-list-keywords*))
       nil
       (cons (car list) (list-until-keyword (cdr list)))))
 
+(defun lambda-list-section (keyword lambda-list)
+  (list-until-keyword (cdr (member keyword lambda-list))))
+
 (defun lambda-list-required-arguments (lambda-list)
   (list-until-keyword lambda-list))
 
 (defun lambda-list-optional-arguments-with-default (lambda-list)
-  (mapcar #'ensure-list (list-until-keyword (cdr (member '&optional lambda-list)))))
+  (mapcar #'ensure-list (lambda-list-section '&optional lambda-list)))
 
 (defun lambda-list-optional-arguments (lambda-list)
   (mapcar #'car (lambda-list-optional-arguments-with-default lambda-list)))
 
 (defun lambda-list-rest-argument (lambda-list)
-  (let ((rest (list-until-keyword (cdr (member '&rest lambda-list)))))
+  (let ((rest (lambda-list-section '&rest lambda-list)))
     (when (cdr rest)
       (error "Bad lambda-list"))
     (car rest)))
 
+(defun lambda-list-keyword-arguments-canonical (lambda-list)
+  (flet ((canonalize (keyarg)
+          ;; Build a canonical keyword argument descriptor, filling
+          ;; the optional fields. The result is a list of the form
+          ;; ((keyword-name var) init-form).
+          (let* ((arg (ensure-list keyarg))
+                 (init-form (cadr arg))
+                 var
+                 keyword-name)
+            (if (listp (car arg))
+                (setq var (cadr (car arg))
+                      keyword-name (car (car arg)))
+                (setq var (car arg)
+                      keyword-name (intern (symbol-name (car arg)) "KEYWORD")))
+            `((,keyword-name ,var) ,init-form))))
+    (mapcar #'canonalize (lambda-list-section '&key lambda-list))))
+
+(defun lambda-list-keyword-arguments (lambda-list)
+  (mapcar (lambda (keyarg) (second (first keyarg)))
+         (lambda-list-keyword-arguments-canonical lambda-list)))
+
 (defun lambda-docstring-wrapper (docstring &rest strs)
   (if docstring
       (js!selfcall
         "var func = " (join strs) ";" *newline*
         "func.docstring = '" docstring "';" *newline*
         "return func;" *newline*)
-      (join strs)))
+      (apply #'code strs)))
 
 (defun lambda-check-argument-count
     (n-required-arguments n-optional-arguments rest-p)
     (block nil
       ;; Special case: a positive exact number of arguments.
       (when (and (< 1 min) (eql min max))
-        (return (concat "checkArgs(arguments, " (integer-to-string min) ");" *newline*)))
+        (return (code "checkArgs(arguments, " min ");" *newline*)))
       ;; General case:
-      (concat
-       (if (< 1 min)
-           (concat "checkArgsAtLeast(arguments, " (integer-to-string min) ");" *newline*)
-           "")
-       (if (numberp max)
-           (concat "checkArgsAtMost(arguments, " (integer-to-string max) ");" *newline*)
-           "")))))
+      (code
+       (when (< 1 min)
+         (code "checkArgsAtLeast(arguments, " min ");" *newline*))
+       (when (numberp max)
+         (code "checkArgsAtMost(arguments, " max ");" *newline*))))))
+
+(defun compile-lambda-optional (lambda-list)
+  (let* ((optional-arguments (lambda-list-optional-arguments lambda-list))
+        (n-required-arguments (length (lambda-list-required-arguments lambda-list)))
+        (n-optional-arguments (length optional-arguments)))
+    (when optional-arguments
+      (code "switch(arguments.length-1){" *newline*
+            (let ((optional-and-defaults
+                   (lambda-list-optional-arguments-with-default lambda-list))
+                  (cases nil)
+                  (idx 0))
+              (progn
+                (while (< idx n-optional-arguments)
+                  (let ((arg (nth idx optional-and-defaults)))
+                    (push (code "case " (+ idx n-required-arguments) ":" *newline*
+                                (translate-variable (car arg))
+                                "="
+                                (ls-compile (cadr arg))
+                                ";" *newline*)
+                          cases)
+                    (incf idx)))
+                (push (code "default: break;" *newline*) cases)
+                (join (reverse cases))))
+            "}" *newline*))))
+
+(defun compile-lambda-rest (lambda-list)
+  (let ((n-required-arguments (length (lambda-list-required-arguments lambda-list)))
+       (n-optional-arguments (length (lambda-list-optional-arguments lambda-list)))
+       (rest-argument (lambda-list-rest-argument lambda-list)))
+    (when rest-argument
+      (let ((js!rest (translate-variable rest-argument)))
+        (code "var " js!rest "= " (ls-compile nil) ";" *newline*
+              "for (var i = arguments.length-1; i>="
+              (+ 1 n-required-arguments n-optional-arguments)
+              "; i--)" *newline*
+              (indent js!rest " = {car: arguments[i], cdr: ") js!rest "};"
+              *newline*)))))
+
+(defun compile-lambda-parse-keywords (lambda-list)
+  (let ((n-required-arguments
+        (length (lambda-list-required-arguments lambda-list)))
+       (n-optional-arguments
+        (length (lambda-list-optional-arguments lambda-list)))
+       (keyword-arguments
+        (lambda-list-keyword-arguments-canonical lambda-list)))
+    (code
+     "var i;" *newline*
+     ;; Declare variables
+     (mapconcat (lambda (arg)
+                 (let ((var (second (car arg))))
+                   (code "var " (translate-variable var) "; " *newline*)))
+               keyword-arguments)
+     ;; Parse keywords
+     (flet ((parse-keyword (keyarg)
+             ;; ((keyword-name var) init-form)
+             (code "for (i=" (+ 1 n-required-arguments n-optional-arguments)
+                    "; i<arguments.length; i+=2){" *newline*
+                    (indent
+                     "if (arguments[i] === " (ls-compile (caar keyarg)) "){" *newline*
+                     (indent (translate-variable (cadr (car keyarg)))
+                             " = arguments[i+1];"
+                             *newline*
+                             "break;" *newline*)
+                     "}" *newline*)
+                    "}" *newline*
+                    ;; Default value
+                    "if (i == arguments.length){" *newline*
+                    (indent
+                     (translate-variable (cadr (car keyarg)))
+                     " = "
+                     (ls-compile (cadr keyarg))
+                     ";" *newline*)
+                    "}" *newline*)))
+       (mapconcat #'parse-keyword keyword-arguments))
+     ;; Check for unknown keywords
+     (when keyword-arguments
+       (code "for (i=" (+ 1 n-required-arguments n-optional-arguments)
+             "; i<arguments.length; i+=2){" *newline*
+             (indent "if ("
+                     (join (mapcar (lambda (x)
+                                     (concat "arguments[i] !== " (ls-compile (caar x))))
+                                   keyword-arguments)
+                           " && ")
+                     ")" *newline*
+                     (indent
+                      "throw 'Unknown keyword argument ' + arguments[i].name;" *newline*))
+             "}" *newline*)))))
 
 (defun compile-lambda (lambda-list body)
   (let ((required-arguments (lambda-list-required-arguments lambda-list))
         (optional-arguments (lambda-list-optional-arguments lambda-list))
-        (rest-argument (lambda-list-rest-argument lambda-list))
+       (keyword-arguments  (lambda-list-keyword-arguments  lambda-list))
+        (rest-argument      (lambda-list-rest-argument      lambda-list))
         documentation)
     ;; Get the documentation string for the lambda function
     (when (and (stringp (car body))
           (*environment* (extend-local-env
                           (append (ensure-list rest-argument)
                                   required-arguments
-                                  optional-arguments))))
+                                  optional-arguments
+                                 keyword-arguments))))
       (lambda-docstring-wrapper
        documentation
        "(function ("
         ;; Check number of arguments
         (lambda-check-argument-count n-required-arguments
                                      n-optional-arguments
-                                     rest-argument)
-        ;; Optional arguments
-        (if optional-arguments
-            (concat "switch(arguments.length-1){" *newline*
-                    (let ((optional-and-defaults
-                           (lambda-list-optional-arguments-with-default lambda-list))
-                          (cases nil)
-                          (idx 0))
-                      (progn
-                        (while (< idx n-optional-arguments)
-                          (let ((arg (nth idx optional-and-defaults)))
-                            (push (concat "case "
-                                          (integer-to-string (+ idx n-required-arguments)) ":" *newline*
-                                          (translate-variable (car arg))
-                                          "="
-                                          (ls-compile (cadr arg))
-                                          ";" *newline*)
-                                  cases)
-                            (incf idx)))
-                        (push (concat "default: break;" *newline*) cases)
-                        (join (reverse cases))))
-                    "}" *newline*)
-            "")
-        ;; &rest/&body argument
-        (if rest-argument
-            (let ((js!rest (translate-variable rest-argument)))
-              (concat "var " js!rest "= " (ls-compile nil) ";" *newline*
-                      "for (var i = arguments.length-1; i>="
-                      (integer-to-string (+ 1 n-required-arguments n-optional-arguments))
-                      "; i--)" *newline*
-                      (indent js!rest " = "
-                              "{car: arguments[i], cdr: ") js!rest "};"
-                      *newline*))
-            "")
-        ;; Body
-        (let ((*multiple-value-p* t)) (ls-compile-block body t)))
+                                     (or rest-argument keyword-arguments))
+       (compile-lambda-optional lambda-list)
+       (compile-lambda-rest lambda-list)
+       (compile-lambda-parse-keywords lambda-list)
+        (let ((*multiple-value-p* t))
+         (ls-compile-block body t)))
        "})"))))
 
 
+
 (defun setq-pair (var val)
   (let ((b (lookup-in-lexenv var *environment* 'variable)))
     (if (and (eq (binding-type b) 'variable)
              (not (member 'special (binding-declarations b)))
              (not (member 'constant (binding-declarations b))))
-        (concat (binding-value b) " = " (ls-compile val))
+        (code (binding-value b) " = " (ls-compile val))
         (ls-compile `(set ',var ,val)))))
 
 (define-compilation setq (&rest pairs)
           (concat (setq-pair (car pairs) (cadr pairs))
                   (if (null (cddr pairs)) "" ", ")))
         (setq pairs (cddr pairs)))))
-    (concat "(" result ")")))
+    (code "(" result ")")))
 
 ;;; FFI Variable accessors
 (define-compilation js-vref (var)
   var)
 
 (define-compilation js-vset (var val)
-  (concat "(" var " = " (ls-compile val) ")"))
-
+  (code "(" var " = " (ls-compile val) ")"))
 
 
 ;;; Literals
 (defvar *literal-counter* 0)
 
 (defun genlit ()
-  (concat "l" (integer-to-string (incf *literal-counter*))))
+  (code "l" (incf *literal-counter*)))
 
 (defun literal (sexp &optional recursive)
   (cond
     ((integerp sexp) (integer-to-string sexp))
-    ((stringp sexp) (concat "\"" (escape-string sexp) "\""))
+    ((stringp sexp) (code "\"" (escape-string sexp) "\""))
     ((symbolp sexp)
      (or (cdr (assoc sexp *literal-symbols*))
         (let ((v (genlit))
               (s #+common-lisp
                  (let ((package (symbol-package sexp)))
                    (if (eq package (find-package "KEYWORD"))
-                       (concat "{name: \"" (escape-string (symbol-name sexp))
-                               "\", 'package': '" (package-name package) "'}")
-                       (concat "{name: \"" (escape-string (symbol-name sexp)) "\"}")))
+                       (code "{name: \"" (escape-string (symbol-name sexp))
+                             "\", 'package': '" (package-name package) "'}")
+                       (code "{name: \"" (escape-string (symbol-name sexp)) "\"}")))
                  #+ecmalisp
                  (let ((package (symbol-package sexp)))
                    (if (null package)
-                       (concat "{name: \"" (escape-string (symbol-name sexp)) "\"}")
+                       (code "{name: \"" (escape-string (symbol-name sexp)) "\"}")
                        (ls-compile `(intern ,(symbol-name sexp) ,(package-name package)))))))
           (push (cons sexp v) *literal-symbols*)
-          (toplevel-compilation (concat "var " v " = " s))
+          (toplevel-compilation (code "var " v " = " s))
           v)))
     ((consp sexp)
      (let* ((head (butlast sexp))
             (tail (last sexp))
-            (c (concat "QIList("
-                       (join-trailing (mapcar (lambda (x) (literal x t)) head) ",")
-                       (literal (car tail) t)
-                       ","
-                       (literal (cdr tail) t)
-                       ")")))
+            (c (code "QIList("
+                     (join-trailing (mapcar (lambda (x) (literal x t)) head) ",")
+                     (literal (car tail) t)
+                     ","
+                     (literal (cdr tail) t)
+                     ")")))
        (if recursive
           c
           (let ((v (genlit)))
-             (toplevel-compilation (concat "var " v " = " c))
+             (toplevel-compilation (code "var " v " = " c))
              v))))
     ((arrayp sexp)
      (let ((elements (vector-to-list sexp)))
         (if recursive
             c
             (let ((v (genlit)))
-              (toplevel-compilation (concat "var " v " = " c))
+              (toplevel-compilation (code "var " v " = " c))
               v)))))))
 
 (define-compilation quote (sexp)
           (extend-lexenv (mapcar #'make-function-binding fnames)
                          *environment*
                          'function)))
-    (concat "(function("
-            (join (mapcar #'translate-function fnames) ",")
-            "){" *newline*
-            (let ((body (ls-compile-block body t)))
-              (indent body))
-            "})(" (join cfuncs ",") ")")))
+    (code "(function("
+          (join (mapcar #'translate-function fnames) ",")
+          "){" *newline*
+          (let ((body (ls-compile-block body t)))
+            (indent body))
+          "})(" (join cfuncs ",") ")")))
 
 (define-compilation labels (definitions &rest body)
   (let* ((fnames (mapcar #'car definitions))
                          'function)))
     (js!selfcall
       (mapconcat (lambda (func)
-                  (concat "var " (translate-function (car func))
-                          " = " (compile-lambda (cadr func) (cddr func))
-                          ";" *newline*))
+                  (code "var " (translate-function (car func))
+                         " = " (compile-lambda (cadr func) (cddr func))
+                         ";" *newline*))
                 definitions)
       (ls-compile-block body t))))
 
 (defun let-binding-wrapper (bindings body)
   (when (null bindings)
     (return-from let-binding-wrapper body))
-  (concat
+  (code
    "try {" *newline*
    (indent "var tmp;" *newline*
            (mapconcat
             (lambda (b)
               (let ((s (ls-compile `(quote ,(car b)))))
-                (concat "tmp = " s ".value;" *newline*
-                        s ".value = " (cdr b) ";" *newline*
-                        (cdr b) " = tmp;" *newline*)))
+                (code "tmp = " s ".value;" *newline*
+                      s ".value = " (cdr b) ";" *newline*
+                      (cdr b) " = tmp;" *newline*)))
             bindings)
            body *newline*)
    "}" *newline*
    (indent
     (mapconcat (lambda (b)
                  (let ((s (ls-compile `(quote ,(car b)))))
-                   (concat s ".value" " = " (cdr b) ";" *newline*)))
+                   (code s ".value" " = " (cdr b) ";" *newline*)))
                bindings))
    "}" *newline*))
 
          (cvalues (mapcar #'ls-compile (mapcar #'second bindings)))
          (*environment* (extend-local-env (remove-if #'special-variable-p variables)))
          (dynamic-bindings))
-    (concat "(function("
-            (join (mapcar (lambda (x)
-                            (if (special-variable-p x)
-                                (let ((v (gvarname x)))
-                                  (push (cons x v) dynamic-bindings)
-                                  v)
-                                (translate-variable x)))
-                          variables)
-                  ",")
-            "){" *newline*
-            (let ((body (ls-compile-block body t)))
-              (indent (let-binding-wrapper dynamic-bindings body)))
-            "})(" (join cvalues ",") ")")))
+    (code "(function("
+          (join (mapcar (lambda (x)
+                          (if (special-variable-p x)
+                              (let ((v (gvarname x)))
+                                (push (cons x v) dynamic-bindings)
+                                v)
+                              (translate-variable x)))
+                        variables)
+                ",")
+          "){" *newline*
+          (let ((body (ls-compile-block body t)))
+            (indent (let-binding-wrapper dynamic-bindings body)))
+          "})(" (join cvalues ",") ")")))
 
 
 ;;; Return the code to initialize BINDING, and push it extending the
   (let ((var (first binding))
         (value (second binding)))
     (if (special-variable-p var)
-        (concat (ls-compile `(setq ,var ,value)) ";" *newline*)
+        (code (ls-compile `(setq ,var ,value)) ";" *newline*)
         (let* ((v (gvarname var))
                (b (make-binding var 'variable v)))
-          (prog1 (concat "var " v " = " (ls-compile value) ";" *newline*)
+          (prog1 (code "var " v " = " (ls-compile value) ";" *newline*)
             (push-to-lexenv b *environment* 'variable))))))
 
 ;;; Wrap BODY to restore the symbol values of SYMBOLS after body. It
     (return-from let*-binding-wrapper body))
   (let ((store (mapcar (lambda (s) (cons s (gvarname s)))
                        (remove-if-not #'special-variable-p symbols))))
-    (concat
+    (code
      "try {" *newline*
      (indent
       (mapconcat (lambda (b)
                    (let ((s (ls-compile `(quote ,(car b)))))
-                     (concat "var " (cdr b) " = " s ".value;" *newline*)))
+                     (code "var " (cdr b) " = " s ".value;" *newline*)))
                  store)
       body)
      "}" *newline*
      (indent
       (mapconcat (lambda (b)
                    (let ((s (ls-compile `(quote ,(car b)))))
-                     (concat s ".value" " = " (cdr b) ";" *newline*)))
+                     (code s ".value" " = " (cdr b) ";" *newline*)))
                  store))
      "}" *newline*)))
 
 (defvar *block-counter* 0)
 
 (define-compilation block (name &rest body)
-  (let* ((tr (integer-to-string (incf *block-counter*)))
+  (let* ((tr (incf *block-counter*))
          (b (make-binding name 'block tr)))
     (when *multiple-value-p*
       (push-binding-declaration 'multiple-value b))
       (error (concat "Unknown block `" (symbol-name name) "'.")))
     (push-binding-declaration 'used b)
     (js!selfcall
-      (if multiple-value-p
-          (concat "var values = mv;" *newline*)
-          "")
+      (when multiple-value-p (code "var values = mv;" *newline*))
       "throw ({"
       "type: 'block', "
       "id: " (binding-value b) ", "
   (unless (go-tag-p (car body))
     (push (gensym "START") body))
   ;; Tagbody compilation
-  (let ((tbidx (integer-to-string *tagbody-counter*)))
+  (let ((tbidx *tagbody-counter*))
     (let ((*environment* (declare-tagbody-tags tbidx body))
           initag)
       (let ((b (lookup-in-lexenv (first body) *environment* 'gotag)))
         "while (true) {" *newline*
         (indent "try {" *newline*
                 (indent (let ((content ""))
-                          (concat "switch(tagbody_" tbidx "){" *newline*
-                                  "case " initag ":" *newline*
-                                  (dolist (form (cdr body) content)
-                                    (concatf content
-                                      (if (not (go-tag-p form))
-                                          (indent (ls-compile form) ";" *newline*)
-                                          (let ((b (lookup-in-lexenv form *environment* 'gotag)))
-                                            (concat "case " (second (binding-value b)) ":" *newline*)))))
-                                  "default:" *newline*
-                                  "    break tbloop;" *newline*
-                                  "}" *newline*)))
+                          (code "switch(tagbody_" tbidx "){" *newline*
+                                "case " initag ":" *newline*
+                                (dolist (form (cdr body) content)
+                                  (concatf content
+                                    (if (not (go-tag-p form))
+                                        (indent (ls-compile form) ";" *newline*)
+                                        (let ((b (lookup-in-lexenv form *environment* 'gotag)))
+                                          (code "case " (second (binding-value b)) ":" *newline*)))))
+                                "default:" *newline*
+                                "    break tbloop;" *newline*
+                                "}" *newline*)))
                 "}" *newline*
                 "catch (jump) {" *newline*
                 "    if (jump.type == 'tagbody' && jump.id == " tbidx ")" *newline*
         (n (cond
              ((symbolp label) (symbol-name label))
              ((integerp label) (integer-to-string label)))))
-    (if b
-        (js!selfcall
-          "throw ({"
-          "type: 'tagbody', "
-          "id: " (first (binding-value b)) ", "
-          "label: " (second (binding-value b)) ", "
-          "message: 'Attempt to GO to non-existing tag " n "'"
-          "})" *newline*)
-        (error (concat "Unknown tag `" n "'.")))))
+    (when (null b)
+      (error (concat "Unknown tag `" n "'.")))
+    (js!selfcall
+      "throw ({"
+      "type: 'tagbody', "
+      "id: " (first (binding-value b)) ", "
+      "label: " (second (binding-value b)) ", "
+      "message: 'Attempt to GO to non-existing tag " n "'"
+      "})" *newline*)))
 
 (define-compilation unwind-protect (form &rest clean-up)
   (js!selfcall
       "var values = mv;" *newline*
       "var vs;" *newline*
       (mapconcat (lambda (form)
-                   (concat "vs = " (ls-compile form t) ";" *newline*
-                           "if (typeof vs === 'object' && 'multiple-value' in vs)" *newline*
-                           (indent "args = args.concat(vs);" *newline*)
-                           "else" *newline*
-                           (indent "args.push(vs);" *newline*)))
+                   (code "vs = " (ls-compile form t) ";" *newline*
+                         "if (typeof vs === 'object' && 'multiple-value' in vs)" *newline*
+                         (indent "args = args.concat(vs);" *newline*)
+                         "else" *newline*
+                         (indent "args.push(vs);" *newline*)))
                  forms)
       "return func.apply(window, args);" *newline*) ";" *newline*))
 
 (defmacro type-check (decls &body body)
   `(js!selfcall
      ,@(mapcar (lambda (decl)
-                   `(concat "var " ,(first decl) " = " ,(third decl) ";" *newline*))
-                 decls)
+                 `(code "var " ,(first decl) " = " ,(third decl) ";" *newline*))
+               decls)
      ,@(mapcar (lambda (decl)
-                 `(concat "if (typeof " ,(first decl) " != '" ,(second decl) "')" *newline*
-                          (indent "throw 'The value ' + "
-                                  ,(first decl)
-                                  " + ' is not a type "
-                                  ,(second decl)
-                                  ".';"
-                                  *newline*)))
+                 `(code "if (typeof " ,(first decl) " != '" ,(second decl) "')" *newline*
+                        (indent "throw 'The value ' + "
+                                ,(first decl)
+                                " + ' is not a type "
+                                ,(second decl)
+                                ".';"
+                                *newline*)))
                decls)
-     (concat "return " (progn ,@body) ";" *newline*)))
+     (code "return " (progn ,@body) ";" *newline*)))
 
 ;;; VARIABLE-ARITY compiles variable arity operations. ARGS stands for
 ;;; a variable which holds a list of forms. It will compile them and
         (variables '())
         (prelude ""))
     (dolist (x args)
-      (let ((v (concat "x" (integer-to-string (incf counter)))))
+      (let ((v (code "x" (incf counter))))
         (push v variables)
         (concatf prelude
-                 (concat "var " v " = " (ls-compile x) ";" *newline*
-                         "if (typeof " v " !== 'number') throw 'Not a number!';"
-                         *newline*))))
+          (code "var " v " = " (ls-compile x) ";" *newline*
+                "if (typeof " v " !== 'number') throw 'Not a number!';"
+                *newline*))))
     (js!selfcall prelude (funcall function (reverse variables)))))
 
 
 
 (defun num-op-num (x op y)
   (type-check (("x" "number" x) ("y" "number" y))
-    (concat "x" op "y")))
+    (code "x" op "y")))
 
 (define-raw-builtin + (&rest numbers)
   (if (null numbers)
 (define-builtin-comparison = "==")
 
 (define-builtin numberp (x)
-  (js!bool (concat "(typeof (" x ") == \"number\")")))
+  (js!bool (code "(typeof (" x ") == \"number\")")))
 
 (define-builtin floor (x)
   (type-check (("x" "number" x))
     "Math.floor(x)"))
 
 (define-builtin cons (x y)
-  (concat "({car: " x ", cdr: " y "})"))
+  (code "({car: " x ", cdr: " y "})"))
 
 (define-builtin consp (x)
   (js!bool
 
 (define-builtin rplaca (x new)
   (type-check (("x" "object" x))
-    (concat "(x.car = " new ", x)")))
+    (code "(x.car = " new ", x)")))
 
 (define-builtin rplacd (x new)
   (type-check (("x" "object" x))
-    (concat "(x.cdr = " new ", x)")))
+    (code "(x.cdr = " new ", x)")))
 
 (define-builtin symbolp (x)
   (js!bool
     "({name: name})"))
 
 (define-builtin symbol-name (x)
-  (concat "(" x ").name"))
+  (code "(" x ").name"))
 
 (define-builtin set (symbol value)
-  (concat "(" symbol ").value = " value))
+  (code "(" symbol ").value = " value))
 
 (define-builtin fset (symbol value)
-  (concat "(" symbol ").fvalue = " value))
+  (code "(" symbol ").fvalue = " value))
 
 (define-builtin boundp (x)
-  (js!bool (concat "(" x ".value !== undefined)")))
+  (js!bool (code "(" x ".value !== undefined)")))
 
 (define-builtin symbol-value (x)
   (js!selfcall
     "return func;" *newline*))
 
 (define-builtin symbol-plist (x)
-  (concat "((" x ").plist || " (ls-compile nil) ")"))
+  (code "((" x ").plist || " (ls-compile nil) ")"))
 
 (define-builtin lambda-code (x)
-  (concat "(" x ").toString()"))
+  (code "(" x ").toString()"))
 
-(define-builtin eq    (x y) (js!bool (concat "(" x " === " y ")")))
-(define-builtin equal (x y) (js!bool (concat "(" x  " == " y ")")))
+(define-builtin eq    (x y) (js!bool (code "(" x " === " y ")")))
+(define-builtin equal (x y) (js!bool (code "(" x  " == " y ")")))
 
 (define-builtin char-to-string (x)
   (type-check (("x" "number" x))
     "String.fromCharCode(x)"))
 
 (define-builtin stringp (x)
-  (js!bool (concat "(typeof(" x ") == \"string\")")))
+  (js!bool (code "(typeof(" x ") == \"string\")")))
 
 (define-builtin string-upcase (x)
   (type-check (("x" "string" x))
     "var str = " (ls-compile string) ";" *newline*
     "var a = " (ls-compile a) ";" *newline*
     "var b;" *newline*
-    (if b
-        (concat "b = " (ls-compile b) ";" *newline*)
-        "")
+    (when b (code "b = " (ls-compile b) ";" *newline*))
     "return str.slice(a,b);" *newline*))
 
 (define-builtin char (string index)
     "string1.concat(string2)"))
 
 (define-raw-builtin funcall (func &rest args)
-  (concat "(" (ls-compile func) ")("
-          (join (cons (if *multiple-value-p* "values" "pv")
-                      (mapcar #'ls-compile args))
-                ", ")
-          ")"))
+  (code "(" (ls-compile func) ")("
+        (join (cons (if *multiple-value-p* "values" "pv")
+                    (mapcar #'ls-compile args))
+              ", ")
+        ")"))
 
 (define-raw-builtin apply (func &rest args)
   (if (null args)
-      (concat "(" (ls-compile func) ")()")
+      (code "(" (ls-compile func) ")()")
       (let ((args (butlast args))
             (last (car (last args))))
         (js!selfcall
 (define-builtin new () "{}")
 
 (define-builtin objectp (x)
-  (js!bool (concat "(typeof (" x ") === 'object')")))
+  (js!bool (code "(typeof (" x ") === 'object')")))
 
 (define-builtin oget (object key)
   (js!selfcall
     "return tmp == undefined? " (ls-compile nil) ": tmp ;" *newline*))
 
 (define-builtin oset (object key value)
-  (concat "((" object ")[" key "] = " value ")"))
+  (code "((" object ")[" key "] = " value ")"))
 
 (define-builtin in (key object)
-  (js!bool (concat "((" key ") in (" object "))")))
+  (js!bool (code "((" key ") in (" object "))")))
 
 (define-builtin functionp (x)
-  (js!bool (concat "(typeof " x " == 'function')")))
+  (js!bool (code "(typeof " x " == 'function')")))
 
 (define-builtin write-string (x)
   (type-check (("x" "string" x))
     "return x[i] = " value ";" *newline*))
 
 (define-builtin get-unix-time ()
-  (concat "(Math.round(new Date() / 1000))"))
+  (code "(Math.round(new Date() / 1000))"))
 
 (define-builtin values-array (array)
   (if *multiple-value-p*
-      (concat "values.apply(this, " array ")")
-      (concat "pv.apply(this, " array ")")))
+      (code "values.apply(this, " array ")")
+      (code "pv.apply(this, " array ")")))
 
 (define-raw-builtin values (&rest args)
   (if *multiple-value-p*
-      (concat "values(" (join (mapcar #'ls-compile args) ", ") ")")
-      (concat "pv(" (join (mapcar #'ls-compile args) ", ") ")")))
+      (code "values(" (join (mapcar #'ls-compile args) ", ") ")")
+      (code "pv(" (join (mapcar #'ls-compile args) ", ") ")")))
 
 (defun macro (x)
   (and (symbolp x)
       ((and (symbolp function)
             #+ecmalisp (eq (symbol-package function) (find-package "COMMON-LISP"))
             #+common-lisp t)
-       (concat (ls-compile `',function) ".fvalue" arglist))
+       (code (ls-compile `',function) ".fvalue" arglist))
       (t
-       (concat (ls-compile `#',function) arglist)))))
+       (code (ls-compile `#',function) arglist)))))
 
 (defun ls-compile-block (sexps &optional return-last-p)
   (if return-last-p
-      (concat (ls-compile-block (butlast sexps))
-              "return " (ls-compile (car (last sexps)) *multiple-value-p*) ";")
+      (code (ls-compile-block (butlast sexps))
+            "return " (ls-compile (car (last sexps)) *multiple-value-p*) ";")
       (join-trailing
        (remove-if #'null-or-empty-p (mapcar #'ls-compile sexps))
        (concat ";" *newline*))))
             (binding-value b))
            ((or (keywordp sexp)
                 (member 'constant (binding-declarations b)))
-            (concat (ls-compile `',sexp) ".value"))
+            (code (ls-compile `',sexp) ".value"))
            (t
             (ls-compile `(symbol-value ',sexp))))))
       ((integerp sexp) (integer-to-string sexp))
-      ((stringp sexp) (concat "\"" (escape-string sexp) "\""))
+      ((stringp sexp) (code "\"" (escape-string sexp) "\""))
       ((arrayp sexp) (literal sexp))
       ((listp sexp)
        (let ((name (car sexp))
          (join (remove-if #'null-or-empty-p subs))))
       (t
        (let ((code (ls-compile sexp multiple-value-p)))
-         (concat (join-trailing (get-toplevel-compilations)
-                                (concat ";" *newline*))
-                 (if code
-                     (concat code ";" *newline*)
-                     "")))))))
+         (code (join-trailing (get-toplevel-compilations)
+                              (code ";" *newline*))
+               (when code
+                 (code code ";" *newline*))))))))
 
 
 ;;; Once we have the compiler, we define the runtime environment and
   (defun eval (x)
     (js-eval (ls-compile-toplevel x t)))
 
-  (export '(&rest &optional &body * *gensym-counter* *package* + - / 1+ 1- < <= =
-            = > >= and append apply aref arrayp aset assoc atom block boundp
-            boundp butlast caar cadddr caddr cadr car car case catch cdar cdddr
-            cddr cdr cdr char char-code char= code-char cond cons consp copy-list
-            decf declaim defparameter defun defmacro defvar digit-char digit-char-p
-            disassemble do do* documentation dolist dotimes ecase eq eql equal
-           error eval every export fdefinition find-package find-symbol first
-           flet fourth fset funcall function functionp gensym get-universal-time
-            go identity if in-package incf integerp integerp intern keywordp labels
-           lambda last length let let* list-all-packages list listp make-array
-           make-package make-symbol mapcar member minusp mod multiple-value-bind
-            multiple-value-call multiple-value-list multiple-value-prog1 nil not
-            nth nthcdr null numberp or package-name package-use-list packagep
-            parse-integer plusp prin1-to-string print proclaim prog1 prog2 progn
-           psetq push quote remove remove-if remove-if-not return return-from
-           revappend reverse rplaca rplacd second set setq some string-upcase
-           string string= stringp subseq symbol-function symbol-name symbol-package
-            symbol-plist symbol-value symbolp t tagbody third throw truncate
-            unless unwind-protect values values-list variable warn when write-line
-            write-string zerop))
+  (export '(&rest &key &optional &body * *gensym-counter* *package* +
+           - / 1+ 1- < <= = = > >= and append apply aref arrayp assoc
+           atom block boundp boundp butlast caar cadddr caddr cadr
+           car car case catch cdar cdddr cddr cdr cdr char char-code
+           char= code-char cond cons consp constantly copy-list decf
+           declaim defconstant defparameter defun defmacro defvar
+           digit-char digit-char-p disassemble do do* documentation
+           dolist dotimes ecase eq eql equal error eval every export
+           fdefinition find-package find-symbol first flet fourth
+           fset funcall function functionp gensym get-universal-time
+           go identity if in-package incf integerp integerp intern
+           keywordp labels lambda last length let let*
+           list-all-packages list listp make-array make-package
+           make-symbol mapcar member minusp mod multiple-value-bind
+           multiple-value-call multiple-value-list
+           multiple-value-prog1 nil not nth nthcdr null numberp or
+           package-name package-use-list packagep parse-integer plusp
+           prin1-to-string print proclaim prog1 prog2 progn psetq
+           push quote remove remove-if remove-if-not return
+           return-from revappend reverse rplaca rplacd second set
+           setq some string-upcase string string= stringp subseq
+           symbol-function symbol-name symbol-package symbol-plist
+           symbol-value symbolp t tagbody third throw truncate unless
+           unwind-protect values values-list variable warn when
+           write-line write-string zerop))
 
   (setq *package* *user-package*)