Define lexenv with def!struct
[jscl.git] / ecmalisp.lisp
index 4285024..9fd5cba 100644 (file)
@@ -52,7 +52,7 @@
 
   (defconstant t 't)
   (defconstant nil 'nil)
-  (js-vset "nil" nil)
+  (%js-vset "nil" nil)
 
   (defmacro lambda (args &body body)
     `(function (lambda ,args ,@body)))
   (defun null (x)
     (eq x nil))
 
+  (defun endp (x)
+    (if (null x)
+        t
+        (if (consp x)
+            nil
+            (error "type-error"))))
+
   (defmacro return (&optional value)
     `(return-from nil ,value))
 
   (defun cadr (x) (car (cdr x)))
   (defun cdar (x) (cdr (car x)))
   (defun cddr (x) (cdr (cdr x)))
+  (defun cadar (x) (car (cdr (car x))))
   (defun caddr (x) (car (cdr (cdr x))))
   (defun cdddr (x) (cdr (cdr (cdr x))))
   (defun cadddr (x) (car (cdr (cdr (cdr x)))))
     `(setq ,x (- ,x ,delta)))
 
   (defmacro push (x place)
-    `(setq ,place (cons ,x ,place)))
+    (multiple-value-bind (dummies vals newval setter getter)
+        (get-setf-expansion place)
+      (let ((g (gensym)))
+        `(let* ((,g ,x)
+                ,@(mapcar #'list dummies vals)
+                (,(car newval) (cons ,g ,getter))
+                ,@(cdr newval))
+           ,setter))))
 
   (defmacro dolist (iter &body body)
     (let ((var (first iter))
       x
       (list x)))
 
-(defun !reduce (func list initial)
+(defun !reduce (func list &key initial-value)
   (if (null list)
-      initial
+      initial-value
       (!reduce func
                (cdr list)
-               (funcall func initial (car list)))))
+               :initial-value (funcall func initial-value (car list)))))
+
+(defmacro with-collect (&body body)
+  (let ((head (gensym))
+        (tail (gensym)))
+    `(let* ((,head (cons 'sentinel nil))
+            (,tail ,head))
+       (flet ((collect (x)
+                (rplacd ,tail (cons x nil))
+                (setq ,tail (cdr ,tail))
+                x))
+         ,@body)
+       (cdr ,head))))
 
 ;;; Go on growing the Lisp language in Ecmalisp, with more high
 ;;; level utilities as well as correct versions of other
               (append (cdr list1) list2))))
 
   (defun append (&rest lists)
-    (!reduce #'append-two lists '()))
+    (!reduce #'append-two lists))
 
   (defun revappend (list1 list2)
     (while list1
       (setq assignments (reverse assignments))
       ;;
       `(let ,(mapcar #'cdr assignments)
-        (setq ,@(!reduce #'append (mapcar #'butlast assignments) '())))))
+        (setq ,@(!reduce #'append (mapcar #'butlast assignments))))))
 
   (defmacro do (varlist endlist &body body)
     `(block nil
        (let ,(mapcar (lambda (x) (list (first x) (second x))) varlist)
         (while t
           (when ,(car endlist)
-            (return (progn ,(cdr endlist))))
+            (return (progn ,@(cdr endlist))))
           (tagbody ,@body)
           (psetq
            ,@(apply #'append
        (let* ,(mapcar (lambda (x) (list (first x) (second x))) varlist)
         (while t
           (when ,(car endlist)
-            (return (progn ,(cdr endlist))))
+            (return (progn ,@(cdr endlist))))
           (tagbody ,@body)
           (setq
            ,@(apply #'append
   (defun concat-two (s1 s2)
     (concat-two s1 s2))
 
-  (defmacro with-collect (&body body)
-    (let ((head (gensym))
-          (tail (gensym)))
-      `(let* ((,head (cons 'sentinel nil))
-              (,tail ,head))
-         (flet ((collect (x)
-                  (rplacd ,tail (cons x nil))
-                  (setq ,tail (cdr ,tail))
-                  x))
-           ,@body)
-         (cdr ,head))))
-
   (defun map1 (func list)
     (with-collect
-      (while list
-        (collect (funcall func (car list)))
-        (setq list (cdr list)))))
+        (while list
+          (collect (funcall func (car list)))
+          (setq list (cdr list)))))
 
   (defmacro loop (&body body)
     `(while t ,@body))
   (defun mapcar (func list &rest lists)
     (let ((lists (cons list lists)))
       (with-collect
-        (block loop
-          (loop
-             (let ((elems (map1 #'car lists)))
-               (do ((tail lists (cdr tail)))
-                   ((null tail))
-                 (when (null (car tail)) (return-from loop))
-                 (rplaca tail (cdar tail)))
-               (collect (apply func elems))))))))
+          (block loop
+            (loop
+               (let ((elems (map1 #'car lists)))
+                 (do ((tail lists (cdr tail)))
+                     ((null tail))
+                   (when (null (car tail)) (return-from loop))
+                   (rplaca tail (cdar tail)))
+                 (collect (apply func elems))))))))
 
   (defun identity (x) x)
 
   (defun copy-list (x)
     (mapcar #'identity x))
 
+  (defun list* (arg &rest others)
+    (cond ((null others) arg)
+          ((null (cdr others)) (cons arg (car others)))
+          (t (do ((x others (cdr x)))
+                 ((null (cddr x)) (rplacd x (cadr x))))
+             (cons arg others))))
+
   (defun code-char (x) x)
   (defun char-code (x) x)
   (defun char= (x y) (= x y))
         (return list))
       (setq list (cdr list))))
 
+  (defun find (item list &key key (test #'eql))
+    (dolist (x list)
+      (when (funcall test (funcall key x) item)
+        (return x))))
+
   (defun remove (x list)
     (cond
       ((null list)
       ((funcall func (car list))
        (remove-if func (cdr list)))
       (t
+       ;;
        (cons (car list) (remove-if func (cdr list))))))
 
   (defun remove-if-not (func list)
       (t
        (error "Unsupported argument."))))
 
+  (defmacro do-sequence (iteration &body body)
+    (let ((seq (gensym))
+          (index (gensym)))
+      `(let ((,seq ,(second iteration)))
+         (cond
+           ;; Strings
+           ((stringp ,seq)
+            (let ((,index 0))
+              (dotimes (,index (length ,seq))
+                (let ((,(first iteration)
+                       (char ,seq ,index)))
+                  ,@body))))
+           ;; Lists
+           ((listp ,seq)
+            (dolist (,(first iteration) ,seq)
+              ,@body))
+           (t
+            (error "type-error!"))))))
+
   (defun some (function seq)
-    (cond
-      ((stringp seq)
-       (let ((index 0)
-             (size (length seq)))
-         (while (< index size)
-           (when (funcall function (char seq index))
-             (return-from some t))
-           (incf index))
-         nil))
-      ((listp seq)
-       (dolist (x seq nil)
-         (when (funcall function x)
-           (return t))))
-      (t
-       (error "Unknown sequence."))))
+    (do-sequence (elt seq)
+      (when (funcall function elt)
+        (return-from some t))))
 
   (defun every (function seq)
-    (cond
-      ((stringp seq)
-       (let ((index 0)
-             (size (length seq)))
-         (while (< index size)
-           (unless (funcall function (char seq index))
-             (return-from every nil))
-           (incf index))
-         t))
-      ((listp seq)
-       (dolist (x seq t)
-         (unless (funcall function x)
-           (return))))
-      (t
-       (error "Unknown sequence."))))
+    (do-sequence (elt seq)
+      (unless (funcall function elt)
+        (return-from every nil)))
+    t)
+
+  (defun position (elt sequence)
+    (let ((pos 0))
+      (do-sequence (x seq)
+        (when (eq elt x)
+          (return))
+        (incf pos))
+      pos))
 
   (defun assoc (x alist)
     (while alist
     `(multiple-value-call #'list ,value-from))
 
 
-  ;;; Generalized references (SETF)
+;;; Generalized references (SETF)
 
   (defvar *setf-expanders* nil)
 
                   `(,value)
                   `(setq ,place ,value)
                   place))
-        (let* ((access-fn (car place))
-             (expander (cdr (assoc access-fn *setf-expanders*))))
-          (when (null expander)
-            (error "Unknown generalized reference."))
-          (apply expander (cdr place)))))
+        (let ((place (ls-macroexpand-1 place)))
+          (let* ((access-fn (car place))
+                 (expander (cdr (assoc access-fn *setf-expanders*))))
+            (when (null expander)
+              (error "Unknown generalized reference."))
+            (apply expander (cdr place))))))
 
   (defmacro define-setf-expander (access-fn lambda-list &body body)
     (unless (symbolp access-fn)
               `(progn (rplacd ,cons ,new-value) ,new-value)
               `(car ,cons))))
 
-  ;;; Packages
+  ;; Incorrect typecase, but used in NCONC.
+  (defmacro typecase (x &rest clausules)
+    (let ((value (gensym)))
+      `(let ((,value ,x))
+         (cond
+           ,@(mapcar (lambda (c)
+                       (if (eq (car c) t)
+                           `((t ,@(rest c)))
+                           `((,(ecase (car c)
+                                      (integer 'integerp)
+                                      (cons 'consp)
+                                      (string 'stringp)
+                                      (atom 'atom)
+                                      (null 'null))
+                               ,value)
+                             ,@(or (rest c)
+                                   (list nil)))))
+                     clausules)))))
+
+  ;; The NCONC function is based on the SBCL's one.
+  (defun nconc (&rest lists)
+    (flet ((fail (object)
+             (error "type-error in nconc")))
+      (do ((top lists (cdr top)))
+          ((null top) nil)
+        (let ((top-of-top (car top)))
+          (typecase top-of-top
+            (cons
+             (let* ((result top-of-top)
+                    (splice result))
+               (do ((elements (cdr top) (cdr elements)))
+                   ((endp elements))
+                 (let ((ele (car elements)))
+                   (typecase ele
+                     (cons (rplacd (last splice) ele)
+                           (setf splice ele))
+                     (null (rplacd (last splice) nil))
+                     (atom (if (cdr elements)
+                               (fail ele)
+                               (rplacd (last splice) ele))))))
+               (return result)))
+            (null)
+            (atom
+             (if (cdr top)
+                 (fail top-of-top)
+                 (return top-of-top))))))))
+
+  (defun nreconc (x y)
+    (do ((1st (cdr x) (if (endp 1st) 1st (cdr 1st)))
+         (2nd x 1st)                ; 2nd follows first down the list.
+         (3rd y 2nd))               ;3rd follows 2nd down the list.
+        ((atom 2nd) 3rd)
+      (rplacd 2nd 3rd)))
+
+  (defun notany (fn seq)
+    (not (some fn seq)))
+
+
+  ;; Packages
 
   (defvar *package-list* nil)
 
   (defvar *common-lisp-package*
     (make-package "CL"))
 
+  (defvar *js-package*
+    (make-package "JS"))
+
   (defvar *user-package*
     (make-package "CL-USER" :use (list *common-lisp-package*)))
 
                 (when (eq package *keyword-package*)
                   (oset symbol "value" symbol)
                   (export (list symbol) package))
+               (when (eq package (find-package "JS"))
+                 (let ((sym-name (symbol-name symbol))
+                        (args (gensym)))
+                    ;; Generate a trampoline to call the JS function
+                    ;; properly. This trampoline is very inefficient,
+                    ;; but it still works. Ideas to optimize this are
+                    ;; provide a special lambda keyword
+                    ;; cl::&rest-vector to avoid list argument
+                    ;; consing, as well as allow inline declarations.
+                   (fset symbol
+                          (eval `(lambda (&rest ,args)
+                                   (let ((,args (list-to-vector ,args)))
+                                     (%js-call (%js-vref ,sym-name) ,args)))))))
                 (oset symbols name symbol)
                 (values symbol nil)))))))
 
 
 (defvar *newline* (string (code-char 10)))
 
+#+ecmalisp
 (defun concat (&rest strs)
-  (!reduce #'concat-two strs ""))
+  (!reduce #'concat-two strs :initial-value ""))
+#+common-lisp
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (defun concat (&rest strs)
+    (apply #'concatenate 'string strs)))
 
 (defmacro concatf (variable &body form)
   `(setq ,variable (concat ,variable (progn ,@form))))
                      (concat (prin1-to-string (car last)) " . " (prin1-to-string (cdr last)))))
                ")"))
       ((arrayp form)
-       (concat "#" (prin1-to-string (vector-to-list form))))
+       (concat "#" (if (zerop (length form))
+                       "()"
+                       (prin1-to-string (vector-to-list form)))))
       ((packagep form)
-       (concat "#<PACKAGE " (package-name form) ">"))))
+       (concat "#<PACKAGE " (package-name form) ">"))
+      (t
+       (concat "#<javascript object>"))))
 
   (defun write-line (x)
     (write-string x)
     x))
 
 
+
 ;;;; Reader
 
 ;;; The Lisp reader, parse strings and return Lisp objects. The main
          (incf index))
        (setq name (subseq string index))))
     ;; Canonalize symbol name and package
-    (setq name (string-upcase name))
+    (when (not (eq package "JS"))
+      (setq name (string-upcase name)))
     (setq package (find-package package))
     ;; TODO: PACKAGE:SYMBOL should signal error if SYMBOL is not an
     ;; external symbol from PACKAGE.
-    (if (or internalp (eq package (find-package "KEYWORD")))
+    (if (or internalp
+            (eq package (find-package "KEYWORD"))
+            (eq package (find-package "JS")))
         (intern name package)
         (find-symbol name package))))
 
 (defun !parse-integer (string junk-allow)
   (block nil
     (let ((value 0)
-         (index 0)
-         (size (length string))
-         (sign 1))
-      (when (zerop size) (return (values nil 0)))
+          (index 0)
+          (size (length string))
+          (sign 1))
+      ;; Leading whitespace
+      (while (and (< index size)
+                  (whitespacep (char string index)))
+        (incf index))
+      (unless (< index size) (return (values nil 0)))
       ;; Optional sign
       (case (char string 0)
-       (#\+ (incf index))
-       (#\- (setq sign -1)
-            (incf index)))
+        (#\+ (incf index))
+        (#\- (setq sign -1)
+             (incf index)))
       ;; First digit
       (unless (and (< index size)
-                  (setq value (digit-char-p (char string index))))
-       (return (values nil index)))
+                   (setq value (digit-char-p (char string index))))
+        (return (values nil index)))
       (incf index)
       ;; Other digits
       (while (< index size)
-       (let ((digit (digit-char-p (char string index))))
-         (unless digit (return))
-         (setq value (+ (* value 10) digit))
-         (incf index)))
+        (let ((digit (digit-char-p (char string index))))
+          (unless digit (return))
+          (setq value (+ (* value 10) digit))
+          (incf index)))
+      ;; Trailing whitespace
+      (do ((i index (1+ i)))
+          ((or (= i size) (not (whitespacep (char string i))))
+           (and (= i size) (setq index i))))
       (if (or junk-allow
-             (= index size)
-             (char= (char string index) #\space))
-         (values (* sign value) index)
-         (values nil index)))))
+              (= index size))
+          (values (* sign value) index)
+          (values nil index)))))
 
 #+ecmalisp
 (defun parse-integer (string)
 ;;; function call.
 (defvar *multiple-value-p* nil)
 
-(defun make-binding (name type value &optional declarations)
-  (list name type value declarations))
-
-(defun binding-name (b) (first b))
-(defun binding-type (b) (second b))
-(defun binding-value (b) (third b))
-(defun binding-declarations (b) (fourth b))
-
-(defun set-binding-value (b value)
-  (rplaca (cddr b) value))
-
-(defun set-binding-declarations (b value)
-  (rplaca (cdddr b) value))
-
-(defun push-binding-declaration (decl b)
-  (set-binding-declarations b (cons decl (binding-declarations b))))
-
-
-(defun make-lexenv ()
-  (list nil nil nil nil))
+;; A very simple defstruct built on lists. It supports just slot with
+;; an optional default initform, and it will create a constructor,
+;; predicate and accessors for you.
+(defmacro def!struct (name &rest slots)
+  (unless (symbolp name)
+    (error "It is not a full defstruct implementation."))
+  (let* ((name-string (symbol-name name))
+         (slot-descriptions
+          (mapcar (lambda (sd)
+                    (cond
+                      ((symbolp sd)
+                       (list sd))
+                      ((and (listp sd) (car sd) (cddr sd))
+                       sd)
+                      (t
+                       (error "Bad slot accessor."))))
+                  slots))
+         (predicate (intern (concat name-string "-P"))))
+    `(progn
+       ;; Constructor
+       (defun ,(intern (concat "MAKE-" name-string)) (&key ,@slot-descriptions)
+         (list ',name ,@(mapcar #'car slot-descriptions)))
+       ;; Predicate
+       (defun ,predicate (x)
+         (and (consp x) (eq (car x) ',name)))
+       ;; Copier
+       (defun ,(intern (concat "COPY-" name-string)) (x)
+         (copy-list x))
+       ;; Slot accessors
+       ,@(with-collect
+          (let ((index 1))
+            (dolist (slot slot-descriptions)
+              (let* ((name (car slot))
+                     (accessor-name (intern (concat name-string "-" (string name)))))
+                (collect
+                    `(defun ,accessor-name (x)
+                       (unless (,predicate x)
+                         (error ,(concat "The object is not a type " name-string)))
+                       (nth ,index x)))
+                ;; TODO: Implement this with a higher level
+                ;; abstraction like defsetf or (defun (setf ..))
+                (collect
+                    `(define-setf-expander ,accessor-name (x)
+                       (let ((object (gensym))
+                             (new-value (gensym)))
+                         (values (list object)
+                                 (list x)
+                                 (list new-value)
+                                 `(progn
+                                    (rplaca (nthcdr ,',index ,object) ,new-value) 
+                                    ,new-value)
+                                 `(,',accessor-name ,object)))))
+                (incf index)))))
+       ',name)))
+
+(def!struct binding
+  name
+  type
+  value
+  declarations)
+
+(def!struct lexenv
+  variable
+  function
+  block
+  gotag)
 
-(defun copy-lexenv (lexenv)
-  (copy-list lexenv))
+(defun lookup-in-lexenv (name lexenv namespace)
+  (find name (ecase namespace
+                (variable (lexenv-variable lexenv))
+                (function (lexenv-function lexenv))
+                (block    (lexenv-block    lexenv))
+                (gotag    (lexenv-gotag    lexenv)))
+        :key #'binding-name))
 
 (defun push-to-lexenv (binding lexenv namespace)
   (ecase namespace
-    (variable   (rplaca        lexenv  (cons binding (car lexenv))))
-    (function   (rplaca   (cdr lexenv) (cons binding (cadr lexenv))))
-    (block      (rplaca  (cddr lexenv) (cons binding (caddr lexenv))))
-    (gotag      (rplaca (cdddr lexenv) (cons binding (cadddr lexenv))))))
+    (variable (push binding (lexenv-variable lexenv)))
+    (function (push binding (lexenv-function lexenv)))
+    (block    (push binding (lexenv-block    lexenv)))
+    (gotag    (push binding (lexenv-gotag    lexenv)))))
 
 (defun extend-lexenv (bindings lexenv namespace)
   (let ((env (copy-lexenv lexenv)))
     (dolist (binding (reverse bindings) env)
       (push-to-lexenv binding env namespace))))
 
-(defun lookup-in-lexenv (name lexenv namespace)
-  (assoc name (ecase namespace
-                (variable (first lexenv))
-                (function (second lexenv))
-                (block (third lexenv))
-                (gotag (fourth lexenv)))))
 
 (defvar *environment* (make-lexenv))
 
 (defvar *variable-counter* 0)
+
 (defun gvarname (symbol)
   (code "v" (incf *variable-counter*)))
 
 (defun extend-local-env (args)
   (let ((new (copy-lexenv *environment*)))
     (dolist (symbol args new)
-      (let ((b (make-binding symbol 'variable (gvarname symbol))))
+      (let ((b (make-binding :name symbol :type 'variable :value (gvarname symbol))))
         (push-to-lexenv b new 'variable)))))
 
 ;;; Toplevel compilations
 
 (defun %compile-defmacro (name lambda)
   (toplevel-compilation (ls-compile `',name))
-  (push-to-lexenv (make-binding name 'macro lambda) *environment* 'function)
+  (push-to-lexenv (make-binding :name name :type 'macro :value lambda) *environment* 'function)
   name)
 
 (defun global-binding (name type namespace)
   (or (lookup-in-lexenv name *environment* namespace)
-      (let ((b (make-binding name type nil)))
+      (let ((b (make-binding :name name :type type :value nil)))
         (push-to-lexenv b *environment* namespace)
         b)))
 
     (special
      (dolist (name (cdr decl))
        (let ((b (global-binding name 'variable 'variable)))
-         (push-binding-declaration 'special b))))
+         (push 'special (binding-declarations b)))))
     (notinline
      (dolist (name (cdr decl))
        (let ((b (global-binding name 'function 'function)))
-         (push-binding-declaration 'notinline b))))
+         (push 'notinline (binding-declarations b)))))
     (constant
      (dolist (name (cdr decl))
        (let ((b (global-binding name 'variable 'variable)))
-         (push-binding-declaration 'constant b))))))
+         (push 'constant (binding-declarations b)))))))
 
 #+ecmalisp
 (fset 'proclaim #'!proclaim)
         " : " (ls-compile false *multiple-value-p*)
         ")"))
 
-(defvar *lambda-list-keywords* '(&optional &rest &key))
+(defvar *ll-keywords* '(&optional &rest &key))
 
 (defun list-until-keyword (list)
-  (if (or (null list) (member (car list) *lambda-list-keywords*))
+  (if (or (null list) (member (car list) *ll-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 ll-section (keyword ll)
+  (list-until-keyword (cdr (member keyword ll))))
 
-(defun lambda-list-required-arguments (lambda-list)
-  (list-until-keyword lambda-list))
+(defun ll-required-arguments (ll)
+  (list-until-keyword ll))
 
-(defun lambda-list-optional-arguments-with-default (lambda-list)
-  (mapcar #'ensure-list (lambda-list-section '&optional lambda-list)))
+(defun ll-optional-arguments-canonical (ll)
+  (mapcar #'ensure-list (ll-section '&optional ll)))
 
-(defun lambda-list-optional-arguments (lambda-list)
-  (mapcar #'car (lambda-list-optional-arguments-with-default lambda-list)))
+(defun ll-optional-arguments (ll)
+  (mapcar #'car (ll-optional-arguments-canonical ll)))
 
-(defun lambda-list-rest-argument (lambda-list)
-  (let ((rest (lambda-list-section '&rest lambda-list)))
+(defun ll-rest-argument (ll)
+  (let ((rest (ll-section '&rest ll)))
     (when (cdr rest)
       (error "Bad lambda-list"))
     (car rest)))
 
-(defun lambda-list-keyword-arguments-canonical (lambda-list)
-  (flet ((canonalize (keyarg)
+(defun ll-keyword-arguments-canonical (ll)
+  (flet ((canonicalize (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)
+           (let ((arg (ensure-list keyarg)))
+             (cons (if (listp (car arg))
+                       (car arg)
+                       (list (intern (symbol-name (car arg)) "KEYWORD") (car arg)))
+                   (cdr arg)))))
+    (mapcar #'canonicalize (ll-section '&key ll))))
+
+(defun ll-keyword-arguments (ll)
   (mapcar (lambda (keyarg) (second (first keyarg)))
-         (lambda-list-keyword-arguments-canonical lambda-list)))
+         (ll-keyword-arguments-canonical ll)))
+
+(defun ll-svars (lambda-list)
+  (let ((args
+         (append
+          (ll-keyword-arguments-canonical lambda-list)
+          (ll-optional-arguments-canonical lambda-list))))
+    (remove nil (mapcar #'third args))))
 
 (defun lambda-docstring-wrapper (docstring &rest strs)
   (if docstring
        (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)))
+(defun compile-lambda-optional (ll)
+  (let* ((optional-arguments (ll-optional-arguments-canonical ll))
+        (n-required-arguments (length (ll-required-arguments ll)))
         (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)
+      (code (mapconcat (lambda (arg)
+                         (code "var " (translate-variable (first arg)) "; " *newline*
+                               (when (third arg)
+                                 (code "var " (translate-variable (third arg))
+                                       " = " (ls-compile t)
+                                       "; " *newline*))))
+                       optional-arguments)
+            "switch(arguments.length-1){" *newline*
+            (let ((cases nil)
                   (idx 0))
               (progn
                 (while (< idx n-optional-arguments)
-                  (let ((arg (nth idx optional-and-defaults)))
+                  (let ((arg (nth idx optional-arguments)))
                     (push (code "case " (+ idx n-required-arguments) ":" *newline*
-                                (translate-variable (car arg))
-                                "="
-                                (ls-compile (cadr arg))
-                                ";" *newline*)
+                                (indent (translate-variable (car arg))
+                                        "="
+                                        (ls-compile (cadr arg)) ";" *newline*)
+                                (when (third arg)
+                                  (indent (translate-variable (third arg))
+                                          "="
+                                          (ls-compile nil)
+                                          ";" *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)))
+(defun compile-lambda-rest (ll)
+  (let ((n-required-arguments (length (ll-required-arguments ll)))
+       (n-optional-arguments (length (ll-optional-arguments ll)))
+       (rest-argument (ll-rest-argument ll)))
     (when rest-argument
       (let ((js!rest (translate-variable rest-argument)))
         (code "var " js!rest "= " (ls-compile nil) ";" *newline*
               (indent js!rest " = {car: arguments[i], cdr: ") js!rest "};"
               *newline*)))))
 
-(defun compile-lambda-parse-keywords (lambda-list)
+(defun compile-lambda-parse-keywords (ll)
   (let ((n-required-arguments
-        (length (lambda-list-required-arguments lambda-list)))
+        (length (ll-required-arguments ll)))
        (n-optional-arguments
-        (length (lambda-list-optional-arguments lambda-list)))
+        (length (ll-optional-arguments ll)))
        (keyword-arguments
-        (lambda-list-keyword-arguments-canonical lambda-list)))
+        (ll-keyword-arguments-canonical ll)))
     (code
      ;; Declare variables
      (mapconcat (lambda (arg)
                  (let ((var (second (car arg))))
-                   (code "var " (translate-variable var) "; " *newline*)))
+                   (code "var " (translate-variable var) "; " *newline*
+                          (when (third arg)
+                            (code "var " (translate-variable (third arg))
+                                  " = " (ls-compile nil)
+                                  ";" *newline*)))))
                keyword-arguments)
      ;; Parse keywords
      (flet ((parse-keyword (keyarg)
                      (indent (translate-variable (cadr (car keyarg)))
                              " = arguments[i+1];"
                              *newline*
+                             (let ((svar (third keyarg)))
+                               (when svar
+                                 (code (translate-variable svar) " = " (ls-compile t) ";" *newline*)))
                              "break;" *newline*)
                      "}" *newline*)
                     "}" *newline*
                     ;; Default value
                     "if (i == arguments.length){" *newline*
-                    (indent
-                     (translate-variable (cadr (car keyarg)))
-                     " = "
-                     (ls-compile (cadr keyarg))
-                     ";" *newline*)
+                    (indent (translate-variable (cadr (car keyarg))) " = " (ls-compile (cadr keyarg)) ";" *newline*)
                     "}" *newline*)))
        (when keyword-arguments
          (code "var i;" *newline*
                       "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))
-       (keyword-arguments  (lambda-list-keyword-arguments  lambda-list))
-        (rest-argument      (lambda-list-rest-argument      lambda-list))
+(defun compile-lambda (ll body)
+  (let ((required-arguments (ll-required-arguments ll))
+        (optional-arguments (ll-optional-arguments ll))
+       (keyword-arguments  (ll-keyword-arguments  ll))
+        (rest-argument      (ll-rest-argument      ll))
         documentation)
     ;; Get the documentation string for the lambda function
     (when (and (stringp (car body))
                           (append (ensure-list rest-argument)
                                   required-arguments
                                   optional-arguments
-                                 keyword-arguments))))
+                                 keyword-arguments
+                                  (ll-svars ll)))))
       (lambda-docstring-wrapper
        documentation
        "(function ("
         (lambda-check-argument-count n-required-arguments
                                      n-optional-arguments
                                      (or rest-argument keyword-arguments))
-       (compile-lambda-optional lambda-list)
-       (compile-lambda-rest lambda-list)
-       (compile-lambda-parse-keywords lambda-list)
+       (compile-lambda-optional ll)
+       (compile-lambda-rest ll)
+       (compile-lambda-parse-keywords ll)
         (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)
+    (if (and (binding-p b)
+             (eq (binding-type b) 'variable)
              (not (member 'special (binding-declarations b)))
              (not (member 'constant (binding-declarations b))))
         (code (binding-value b) " = " (ls-compile val))
         (ls-compile `(set ',var ,val)))))
 
+
 (define-compilation setq (&rest pairs)
   (let ((result ""))
     (while t
         (setq pairs (cddr pairs)))))
     (code "(" result ")")))
 
-;;; FFI Variable accessors
-(define-compilation js-vref (var)
-  var)
-
-(define-compilation js-vset (var val)
-  (code "(" var " = " (ls-compile val) ")"))
-
 
 ;;; Literals
 (defun escape-string (string)
 
 
 (defun make-function-binding (fname)
-  (make-binding fname 'function (gvarname fname)))
+  (make-binding :name fname :type 'function :value (gvarname fname)))
 
 (defun compile-function-definition (list)
   (compile-lambda (car list) (cdr list)))
 
 (defun translate-function (name)
   (let ((b (lookup-in-lexenv name *environment* 'function)))
-    (binding-value b)))
+    (and b (binding-value b))))
 
 (define-compilation flet (definitions &rest body)
   (let* ((fnames (mapcar #'car definitions))
       (ls-compile-block body t))))
 
 
-
 (defvar *compiling-file* nil)
 (define-compilation eval-when-compile (&rest body)
   (if *compiling-file*
     (if (special-variable-p var)
         (code (ls-compile `(setq ,var ,value)) ";" *newline*)
         (let* ((v (gvarname var))
-               (b (make-binding var 'variable v)))
+               (b (make-binding :name var :type 'variable :value v)))
           (prog1 (code "var " v " = " (ls-compile value) ";" *newline*)
             (push-to-lexenv b *environment* 'variable))))))
 
 
 (define-compilation block (name &rest body)
   (let* ((tr (incf *block-counter*))
-         (b (make-binding name 'block tr)))
+         (b (make-binding :name name :type 'block :value tr)))
     (when *multiple-value-p*
-      (push-binding-declaration 'multiple-value b))
+      (push 'multiple-value (binding-declarations b)))
     (let* ((*environment* (extend-lexenv (list b) *environment* 'block))
            (cbody (ls-compile-block body t)))
       (if (member 'used (binding-declarations b))
          (multiple-value-p (member 'multiple-value (binding-declarations b))))
     (when (null b)
       (error (concat "Unknown block `" (symbol-name name) "'.")))
-    (push-binding-declaration 'used b)
+    (push 'used (binding-declarations b))
     (js!selfcall
       (when multiple-value-p (code "var values = mv;" *newline*))
       "throw ({"
   (let ((bindings
          (mapcar (lambda (label)
                    (let ((tagidx (integer-to-string (incf *go-tag-counter*))))
-                     (make-binding label 'gotag (list tbidx tagidx))))
+                     (make-binding :name label :type 'gotag :value (list tbidx tagidx))))
                  (remove-if-not #'go-tag-p body))))
     (extend-lexenv bindings *environment* 'gotag)))
 
     "return args;" *newline*))
 
 
+;;; Javascript FFI
 
-;;; A little backquote implementation without optimizations of any
-;;; kind for ecmalisp.
-(defun backquote-expand-1 (form)
-  (cond
-    ((symbolp form)
-     (list 'quote form))
-    ((atom form)
-     form)
-    ((eq (car form) 'unquote)
-     (car form))
-    ((eq (car form) 'backquote)
-     (backquote-expand-1 (backquote-expand-1 (cadr form))))
-    (t
-     (cons 'append
-           (mapcar (lambda (s)
-                     (cond
-                       ((and (listp s) (eq (car s) 'unquote))
-                        (list 'list (cadr s)))
-                       ((and (listp s) (eq (car s) 'unquote-splicing))
-                        (cadr s))
-                       (t
-                        (list 'list (backquote-expand-1 s)))))
-                   form)))))
-
-(defun backquote-expand (form)
-  (if (and (listp form) (eq (car form) 'backquote))
-      (backquote-expand-1 (cadr form))
-      form))
-
-(defmacro backquote (form)
-  (backquote-expand-1 form))
+(define-compilation %js-vref (var) var)
+
+(define-compilation %js-vset (var val)
+  (code "(" var " = " (ls-compile val) ")"))
+
+
+;;; Backquote implementation.
+;;;
+;;;    Author: Guy L. Steele Jr.     Date: 27 December 1985
+;;;    Tested under Symbolics Common Lisp and Lucid Common Lisp.
+;;;    This software is in the public domain.
+
+;;;    The following are unique tokens used during processing.
+;;;    They need not be symbols; they need not even be atoms.
+(defvar *comma* 'unquote)
+(defvar *comma-atsign* 'unquote-splicing)
+
+(defvar *bq-list* (make-symbol "BQ-LIST"))
+(defvar *bq-append* (make-symbol "BQ-APPEND"))
+(defvar *bq-list** (make-symbol "BQ-LIST*"))
+(defvar *bq-nconc* (make-symbol "BQ-NCONC"))
+(defvar *bq-clobberable* (make-symbol "BQ-CLOBBERABLE"))
+(defvar *bq-quote* (make-symbol "BQ-QUOTE"))
+(defvar *bq-quote-nil* (list *bq-quote* nil))
+
+;;; BACKQUOTE is an ordinary macro (not a read-macro) that processes
+;;; the expression foo, looking for occurrences of #:COMMA,
+;;; #:COMMA-ATSIGN, and #:COMMA-DOT.  It constructs code in strict
+;;; accordance with the rules on pages 349-350 of the first edition
+;;; (pages 528-529 of this second edition).  It then optionally
+;;; applies a code simplifier.
+
+;;; If the value of *BQ-SIMPLIFY* is non-NIL, then BACKQUOTE
+;;; processing applies the code simplifier.  If the value is NIL,
+;;; then the code resulting from BACKQUOTE is exactly that
+;;; specified by the official rules.
+(defparameter *bq-simplify* t)
+
+(defmacro backquote (x)
+  (bq-completely-process x))
+
+;;; Backquote processing proceeds in three stages:
+;;;
+;;; (1) BQ-PROCESS applies the rules to remove occurrences of
+;;; #:COMMA, #:COMMA-ATSIGN, and #:COMMA-DOT corresponding to
+;;; this level of BACKQUOTE.  (It also causes embedded calls to
+;;; BACKQUOTE to be expanded so that nesting is properly handled.)
+;;; Code is produced that is expressed in terms of functions
+;;; #:BQ-LIST, #:BQ-APPEND, and #:BQ-CLOBBERABLE.  This is done
+;;; so that the simplifier will simplify only list construction
+;;; functions actually generated by BACKQUOTE and will not involve
+;;; any user code in the simplification.  #:BQ-LIST means LIST,
+;;; #:BQ-APPEND means APPEND, and #:BQ-CLOBBERABLE means IDENTITY
+;;; but indicates places where "%." was used and where NCONC may
+;;; therefore be introduced by the simplifier for efficiency.
+;;;
+;;; (2) BQ-SIMPLIFY, if used, rewrites the code produced by
+;;; BQ-PROCESS to produce equivalent but faster code.  The
+;;; additional functions #:BQ-LIST* and #:BQ-NCONC may be
+;;; introduced into the code.
+;;;
+;;; (3) BQ-REMOVE-TOKENS goes through the code and replaces
+;;; #:BQ-LIST with LIST, #:BQ-APPEND with APPEND, and so on.
+;;; #:BQ-CLOBBERABLE is simply eliminated (a call to it being
+;;; replaced by its argument).  #:BQ-LIST* is replaced by either
+;;; LIST* or CONS (the latter is used in the two-argument case,
+;;; purely to make the resulting code a tad more readable).
+
+(defun bq-completely-process (x)
+  (let ((raw-result (bq-process x)))
+    (bq-remove-tokens (if *bq-simplify*
+                          (bq-simplify raw-result)
+                          raw-result))))
+
+(defun bq-process (x)
+  (cond ((atom x)
+         (list *bq-quote* x))
+        ((eq (car x) 'backquote)
+         (bq-process (bq-completely-process (cadr x))))
+        ((eq (car x) *comma*) (cadr x))
+        ((eq (car x) *comma-atsign*)
+         ;; (error ",@~S after `" (cadr x))
+         (error "ill-formed"))
+        ;; ((eq (car x) *comma-dot*)
+        ;;  ;; (error ",.~S after `" (cadr x))
+        ;;  (error "ill-formed"))
+        (t (do ((p x (cdr p))
+                (q '() (cons (bracket (car p)) q)))
+               ((atom p)
+                (cons *bq-append*
+                      (nreconc q (list (list *bq-quote* p)))))
+             (when (eq (car p) *comma*)
+               (unless (null (cddr p))
+                 ;; (error "Malformed ,~S" p)
+                 (error "Malformed"))
+               (return (cons *bq-append*
+                             (nreconc q (list (cadr p))))))
+             (when (eq (car p) *comma-atsign*)
+               ;; (error "Dotted ,@~S" p)
+               (error "Dotted"))
+             ;; (when (eq (car p) *comma-dot*)
+             ;;   ;; (error "Dotted ,.~S" p)
+             ;;   (error "Dotted"))
+             ))))
+
+;;; This implements the bracket operator of the formal rules.
+(defun bracket (x)
+  (cond ((atom x)
+         (list *bq-list* (bq-process x)))
+        ((eq (car x) *comma*)
+         (list *bq-list* (cadr x)))
+        ((eq (car x) *comma-atsign*)
+         (cadr x))
+        ;; ((eq (car x) *comma-dot*)
+        ;;  (list *bq-clobberable* (cadr x)))
+        (t (list *bq-list* (bq-process x)))))
+
+;;; This auxiliary function is like MAPCAR but has two extra
+;;; purposes: (1) it handles dotted lists; (2) it tries to make
+;;; the result share with the argument x as much as possible.
+(defun maptree (fn x)
+  (if (atom x)
+      (funcall fn x)
+      (let ((a (funcall fn (car x)))
+            (d (maptree fn (cdr x))))
+        (if (and (eql a (car x)) (eql d (cdr x)))
+            x
+            (cons a d)))))
+
+;;; This predicate is true of a form that when read looked
+;;; like %@foo or %.foo.
+(defun bq-splicing-frob (x)
+  (and (consp x)
+       (or (eq (car x) *comma-atsign*)
+           ;; (eq (car x) *comma-dot*)
+           )))
+
+;;; This predicate is true of a form that when read
+;;; looked like %@foo or %.foo or just plain %foo.
+(defun bq-frob (x)
+  (and (consp x)
+       (or (eq (car x) *comma*)
+           (eq (car x) *comma-atsign*)
+           ;; (eq (car x) *comma-dot*)
+           )))
+
+;;; The simplifier essentially looks for calls to #:BQ-APPEND and
+;;; tries to simplify them.  The arguments to #:BQ-APPEND are
+;;; processed from right to left, building up a replacement form.
+;;; At each step a number of special cases are handled that,
+;;; loosely speaking, look like this:
+;;;
+;;;  (APPEND (LIST a b c) foo) => (LIST* a b c foo)
+;;;       provided a, b, c are not splicing frobs
+;;;  (APPEND (LIST* a b c) foo) => (LIST* a b (APPEND c foo))
+;;;       provided a, b, c are not splicing frobs
+;;;  (APPEND (QUOTE (x)) foo) => (LIST* (QUOTE x) foo)
+;;;  (APPEND (CLOBBERABLE x) foo) => (NCONC x foo)
+(defun bq-simplify (x)
+  (if (atom x)
+      x
+      (let ((x (if (eq (car x) *bq-quote*)
+                   x
+                   (maptree #'bq-simplify x))))
+        (if (not (eq (car x) *bq-append*))
+            x
+            (bq-simplify-args x)))))
+
+(defun bq-simplify-args (x)
+  (do ((args (reverse (cdr x)) (cdr args))
+       (result
+         nil
+         (cond ((atom (car args))
+                (bq-attach-append *bq-append* (car args) result))
+               ((and (eq (caar args) *bq-list*)
+                     (notany #'bq-splicing-frob (cdar args)))
+                (bq-attach-conses (cdar args) result))
+               ((and (eq (caar args) *bq-list**)
+                     (notany #'bq-splicing-frob (cdar args)))
+                (bq-attach-conses
+                  (reverse (cdr (reverse (cdar args))))
+                  (bq-attach-append *bq-append*
+                                    (car (last (car args)))
+                                    result)))
+               ((and (eq (caar args) *bq-quote*)
+                     (consp (cadar args))
+                     (not (bq-frob (cadar args)))
+                     (null (cddar args)))
+                (bq-attach-conses (list (list *bq-quote*
+                                              (caadar args)))
+                                  result))
+               ((eq (caar args) *bq-clobberable*)
+                (bq-attach-append *bq-nconc* (cadar args) result))
+               (t (bq-attach-append *bq-append*
+                                    (car args)
+                                    result)))))
+      ((null args) result)))
+
+(defun null-or-quoted (x)
+  (or (null x) (and (consp x) (eq (car x) *bq-quote*))))
+
+;;; When BQ-ATTACH-APPEND is called, the OP should be #:BQ-APPEND
+;;; or #:BQ-NCONC.  This produces a form (op item result) but
+;;; some simplifications are done on the fly:
+;;;
+;;;  (op '(a b c) '(d e f g)) => '(a b c d e f g)
+;;;  (op item 'nil) => item, provided item is not a splicable frob
+;;;  (op item 'nil) => (op item), if item is a splicable frob
+;;;  (op item (op a b c)) => (op item a b c)
+(defun bq-attach-append (op item result)
+  (cond ((and (null-or-quoted item) (null-or-quoted result))
+         (list *bq-quote* (append (cadr item) (cadr result))))
+        ((or (null result) (equal result *bq-quote-nil*))
+         (if (bq-splicing-frob item) (list op item) item))
+        ((and (consp result) (eq (car result) op))
+         (list* (car result) item (cdr result)))
+        (t (list op item result))))
+
+;;; The effect of BQ-ATTACH-CONSES is to produce a form as if by
+;;; `(LIST* ,@items ,result) but some simplifications are done
+;;; on the fly.
+;;;
+;;;  (LIST* 'a 'b 'c 'd) => '(a b c . d)
+;;;  (LIST* a b c 'nil) => (LIST a b c)
+;;;  (LIST* a b c (LIST* d e f g)) => (LIST* a b c d e f g)
+;;;  (LIST* a b c (LIST d e f g)) => (LIST a b c d e f g)
+(defun bq-attach-conses (items result)
+  (cond ((and (every #'null-or-quoted items)
+              (null-or-quoted result))
+         (list *bq-quote*
+               (append (mapcar #'cadr items) (cadr result))))
+        ((or (null result) (equal result *bq-quote-nil*))
+         (cons *bq-list* items))
+        ((and (consp result)
+              (or (eq (car result) *bq-list*)
+                  (eq (car result) *bq-list**)))
+         (cons (car result) (append items (cdr result))))
+        (t (cons *bq-list** (append items (list result))))))
+
+;;; Removes funny tokens and changes (#:BQ-LIST* a b) into
+;;; (CONS a b) instead of (LIST* a b), purely for readability.
+(defun bq-remove-tokens (x)
+  (cond ((eq x *bq-list*) 'list)
+        ((eq x *bq-append*) 'append)
+        ((eq x *bq-nconc*) 'nconc)
+        ((eq x *bq-list**) 'list*)
+        ((eq x *bq-quote*) 'quote)
+        ((atom x) x)
+        ((eq (car x) *bq-clobberable*)
+         (bq-remove-tokens (cadr x)))
+        ((and (eq (car x) *bq-list**)
+              (consp (cddr x))
+              (null (cdddr x)))
+         (cons 'cons (maptree #'bq-remove-tokens (cdr x))))
+        (t (maptree #'bq-remove-tokens x))))
 
 (define-transformation backquote (form)
-  (backquote-expand-1 form))
+  (bq-completely-process form))
+
 
 ;;; Primitives
 
          *builtins*))
 
 (defmacro define-builtin (name args &body body)
-  `(progn
-     (define-raw-builtin ,name ,args
-       (let ,(mapcar (lambda (arg) `(,arg (ls-compile ,arg))) args)
-         ,@body))))
+  `(define-raw-builtin ,name ,args
+     (let ,(mapcar (lambda (arg) `(,arg (ls-compile ,arg))) args)
+       ,@body)))
 
 ;;; DECLS is a list of (JSVARNAME TYPE LISPFORM) declarations.
 (defmacro type-check (decls &body body)
     "string1.concat(string2)"))
 
 (define-raw-builtin funcall (func &rest args)
-  (code "(" (ls-compile func) ")("
-        (join (cons (if *multiple-value-p* "values" "pv")
-                    (mapcar #'ls-compile args))
-              ", ")
-        ")"))
+  (js!selfcall
+    "var f = " (ls-compile func) ";" *newline*
+    "return (typeof f === 'function'? f: f.fvalue)("
+    (join (cons (if *multiple-value-p* "values" "pv")
+                (mapcar #'ls-compile args))
+          ", ")
+    ")"))
 
 (define-raw-builtin apply (func &rest args)
   (if (null args)
           "    args.push(tail.car);" *newline*
           "    tail = tail.cdr;" *newline*
           "}" *newline*
-          "return f.apply(this, args);" *newline*))))
+          "return (typeof f === 'function'? f : f.fvalue).apply(this, args);" *newline*))))
 
 (define-builtin js-eval (string)
   (type-check (("string" "string" string))
       (code "values(" (join (mapcar #'ls-compile args) ", ") ")")
       (code "pv(" (join (mapcar #'ls-compile args) ", ") ")")))
 
+;; Receives the JS function as first argument as a literal string. The
+;; second argument is compiled and should evaluate to a vector of
+;; values to apply to the the function. The result returned.
+(define-builtin %js-call (fun args)
+  (code fun ".apply(this, " args ")"))
+
 (defun macro (x)
   (and (symbolp x)
        (let ((b (lookup-in-lexenv x *environment* 'function)))
-         (and (eq (binding-type b) 'macro)
-              b))))
+         (if (and b (eq (binding-type b) 'macro))
+             b
+             nil))))
 
 (defun ls-macroexpand-1 (form)
   (let ((macro-binding (macro (car form))))
               ;; us replace the list representation version of the
               ;; function with the compiled one.
               ;;
-              #+ecmalisp (set-binding-value macro-binding compiled)
+              #+ecmalisp (setf (binding-value macro-binding) compiled)
               (setq expander compiled)))
           (apply expander (cdr form)))
         form)))
            ((and b (not (member 'special (binding-declarations b))))
             (binding-value b))
            ((or (keywordp sexp)
-                (member 'constant (binding-declarations b)))
+                (and b (member 'constant (binding-declarations b))))
             (code (ls-compile `',sexp) ".value"))
            (t
             (ls-compile `(symbol-value ',sexp))))))
                 (ls-compile (ls-macroexpand-1 sexp) multiple-value-p)
                 (compile-funcall name args))))))
       (t
-       (error "How should I compile this?")))))
+       (error (concat "How should I compile " (prin1-to-string sexp) "?"))))))
+
+
+(defvar *compile-print-toplevels* nil)
+
+(defun truncate-string (string &optional (width 60))
+  (let ((n (or (position #\newline string)
+               (min width (length string)))))
+    (subseq string 0 n)))
 
 (defun ls-compile-toplevel (sexp &optional multiple-value-p)
   (let ((*toplevel-compilations* nil))
                            (cdr sexp))))
          (join (remove-if #'null-or-empty-p subs))))
       (t
+       (when *compile-print-toplevels*
+         (let ((form-string (prin1-to-string sexp)))
+           (write-string "Compiling ")
+           (write-string (truncate-string form-string))
+           (write-line "...")))
+
        (let ((code (ls-compile sexp multiple-value-p)))
          (code (join-trailing (get-toplevel-compilations)
                               (code ";" *newline*))
   (defun eval (x)
     (js-eval (ls-compile-toplevel x t)))
 
-  (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 setf
-           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 fdefinition find-package find-symbol first
+            flet fourth fset funcall function functionp gensym get-setf-expansion
+            get-universal-time go identity if in-package incf integerp integerp
+            intern keywordp labels lambda last length let let* char= code-char
+            cond cons consp constantly copy-list decf declaim define-setf-expander
+            defconstant defparameter defun defmacro defvar digit-char digit-char-p
+            disassemble do do* documentation dolist dotimes ecase eq eql equal
+            error eval every export list-all-packages list list* listp loop make-array
+            make-package make-symbol mapcar member minusp mod multiple-value-bind
+            multiple-value-call multiple-value-list multiple-value-prog1 nconc 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 nreconc remove remove-if remove-if-not return return-from
+            revappend reverse rplaca rplacd second set setf 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*)
 
   (js-eval "var lisp")
-  (js-vset "lisp" (new))
-  (js-vset "lisp.read" #'ls-read-from-string)
-  (js-vset "lisp.print" #'prin1-to-string)
-  (js-vset "lisp.eval" #'eval)
-  (js-vset "lisp.compile" (lambda (s) (ls-compile-toplevel s t)))
-  (js-vset "lisp.evalString" (lambda (str) (eval (ls-read-from-string str))))
-  (js-vset "lisp.compileString" (lambda (str) (ls-compile-toplevel (ls-read-from-string str) t)))
+  (%js-vset "lisp" (new))
+  (%js-vset "lisp.read" #'ls-read-from-string)
+  (%js-vset "lisp.print" #'prin1-to-string)
+  (%js-vset "lisp.eval" #'eval)
+  (%js-vset "lisp.compile" (lambda (s) (ls-compile-toplevel s t)))
+  (%js-vset "lisp.evalString" (lambda (str) (eval (ls-read-from-string str))))
+  (%js-vset "lisp.compileString" (lambda (str) (ls-compile-toplevel (ls-read-from-string str) t)))
 
   ;; Set the initial global environment to be equal to the host global
   ;; environment at this point of the compilation.
     (toplevel-compilation
      (ls-compile
       `(progn
-         ,@(mapcar (lambda (s) `(%intern-symbol (js-vref ,(cdr s))))
+         ,@(mapcar (lambda (s) `(%intern-symbol (%js-vref ,(cdr s))))
                    *literal-symbols*)
          (setq *literal-symbols* ',*literal-symbols*)
          (setq *variable-counter* ,*variable-counter*)
         (read-sequence seq in)
         seq)))
 
-  (defun ls-compile-file (filename output)
-    (let ((*compiling-file* t))
+  (defun ls-compile-file (filename output &key print)
+    (let ((*compiling-file* t)
+          (*compile-print-toplevels* print))
       (with-open-file (out output :direction :output :if-exists :supersede)
         (write-string (read-whole-file "prelude.js") out)
         (let* ((source (read-whole-file filename))
           *gensym-counter* 0
           *literal-counter* 0
           *block-counter* 0)
-    (ls-compile-file "ecmalisp.lisp" "ecmalisp.js")))
+    (ls-compile-file "ecmalisp.lisp" "ecmalisp.js" :print t)))