Remove global lexical variables
[jscl.git] / ecmalisp.lisp
index 30649d2..80a5479 100644 (file)
 
 #+ecmalisp
 (progn
+
+  'defmacro
   (eval-when-compile
     (%compile-defmacro 'defmacro
                        '(lambda (name args &rest body)
-                         `(eval-when-compile
-                            (%compile-defmacro ',name
-                                              '(lambda ,(mapcar (lambda (x)
-                                                                   (if (eq x '&body)
-                                                                       '&rest
-                                                                       x))
-                                                                 args)
-                                                 ,@body))))))
-
-  (defmacro %defvar (name value)
+                         `(progn
+                            (eval-when-compile
+                              (%compile-defmacro ',name
+                                                 '(lambda ,(mapcar (lambda (x)
+                                                                     (if (eq x '&body)
+                                                                         '&rest
+                                                                         x))
+                                                                   args)
+                                                   ,@body)))
+                            ',name))))
+
+  (defmacro defvar (name value)
     `(progn
-       (eval-when-compile
-         (%compile-defvar ',name))
-       (setq ,name ,value)))
-
-  (defmacro defvar (name &optional value)
-    `(%defvar ,name ,value))
+       (setq ,name ,value)
+       ',name))
 
-  (defmacro named-lambda (name args &rest body)
+  (defmacro named-lambda (name args &body body)
     (let ((x (gensym "FN")))
       `(let ((,x (lambda ,args ,@body)))
          (oset ,x "fname" ,name)
          ,x)))
 
-  (defmacro %defun (name args &rest body)
+  (defmacro defun (name args &body body)
     `(progn
        (eval-when-compile
          (%compile-defun ',name))
        (fsetq ,name (named-lambda ,(symbol-name name) ,args
-                      (block ,name ,@body)))))
-
-  (defmacro defun (name args &rest body)
-    `(%defun ,name ,args ,@body))
+                      (block ,name ,@body)))
+       ',name))
 
   (defvar *package* (new))
 
          ,value)))
 
   (defmacro prog2 (form1 result &body body)
-    `(prog1 (progn ,form1 ,result) ,@body))
-
+    `(prog1 (progn ,form1 ,result) ,@body)))
 
 
-)
 
 ;;; This couple of helper functions will be defined in both Common
 ;;; Lisp and in Ecmalisp.
 ;;; constructions.
 #+ecmalisp
 (progn
-  (defmacro defun (name args &body body)
-    `(progn
-       (%defun ,name ,args ,@body)
-       ',name))
-
-  (defmacro defvar (name &optional value)
-    `(progn
-       (%defvar ,name ,value)
-       ',name))
-
   (defun append-two (list1 list2)
     (if (null list1)
         list2
     (write-string *newline*)
     x)
 
+  (defun warn (string)
+    (write-string "WARNING: ")
+    (write-line string))
+
   (defun print (x)
     (write-line (prin1-to-string x))
     x))
 (defun gvarname (symbol)
   (concat "v" (integer-to-string (incf *variable-counter*))))
 
-(defun lookup-variable (symbol env)
-  (or (lookup-in-lexenv symbol env 'variable)
-      (lookup-in-lexenv symbol *environment* 'variable)
-      (let ((name (symbol-name symbol))
-            (binding (make-binding symbol 'special-variable (gvarname symbol) nil)))
-        (push-to-lexenv binding *environment* 'variable)
-        (push (lambda ()
-               (let ((b (lookup-in-lexenv symbol *environment* 'variable)))
-                 (unless (binding-declared b)
-                     (error (concat "Undefined variable `" name "'")))))
-              *compilation-unit-checks*)
-        binding)))
-
-(defun lookup-variable-translation (symbol env)
-  (binding-translation (lookup-variable symbol env)))
+(defun translate-variable (symbol env)
+  (binding-translation (lookup-in-lexenv symbol env 'variable)))
 
 (defun extend-local-env (args env)
   (let ((new (copy-lexenv env)))
 (defun lookup-function-translation (symbol env)
   (binding-translation (lookup-function symbol env)))
 
+;;; Toplevel compilations
 (defvar *toplevel-compilations* nil)
 
-(defun %compile-defvar (name)
-  (let ((b (lookup-variable name *environment*)))
-    (mark-binding-as-declared b)
-    (push (concat "var " (binding-translation b)) *toplevel-compilations*)))
+(defun toplevel-compilation (string)
+  (push string *toplevel-compilations*))
+
+(defun null-or-empty-p (x)
+  (zerop (length x)))
+
+(defun get-toplevel-compilations ()
+  (reverse (remove-if #'null-or-empty-p *toplevel-compilations*)))
 
 (defun %compile-defun (name)
   (let ((b (lookup-function name *environment*)))
     (mark-binding-as-declared b)
-    (push (concat "var " (binding-translation b)) *toplevel-compilations*)))
+    (toplevel-compilation (concat "var " (binding-translation b)))))
 
 (defun %compile-defmacro (name lambda)
   (push-to-lexenv (make-binding name 'macro lambda t) *environment* 'function))
 
 (defun ls-compile-block (sexps env)
   (join-trailing
-   (remove-if (lambda (x)
-                (or (null x)
-                    (and (stringp x)
-                         (zerop (length x)))))
+   (remove-if #'null
               (mapcar (lambda (x) (ls-compile x env))  sexps))
    (concat ";" *newline*)))
 
                     env)))
       (concat "(function ("
               (join (mapcar (lambda (x)
-                              (lookup-variable-translation x new-env))
+                              (translate-variable x new-env))
                             (append required-arguments optional-arguments))
                     ",")
               "){" *newline*
                                  (let ((arg (nth idx optional-and-defaults)))
                                    (push (concat "case "
                                                  (integer-to-string (+ idx n-required-arguments)) ":" *newline*
-                                                 (lookup-variable-translation (car arg) new-env)
+                                                 (translate-variable (car arg) new-env)
                                                  "="
                                                  (ls-compile (cadr arg) new-env)
                                                  ";" *newline*)
                    "")
                ;; &rest/&body argument
                (if rest-argument
-                   (let ((js!rest (lookup-variable-translation rest-argument new-env)))
+                   (let ((js!rest (translate-variable rest-argument new-env)))
                      (concat "var " js!rest "= " (ls-compile nil) ";" *newline*
                              "for (var i = arguments.length-1; i>="
                              (integer-to-string (+ n-required-arguments n-optional-arguments))
           (ls-compile val env)))
 
 (define-compilation setq (var val)
-  (let ((b (lookup-variable var env)))
-    (ecase (binding-type b)
-      (lexical-variable (concat (binding-translation b) " = " (ls-compile val env)))
-      (special-variable (ls-compile `(set ',var ,val) env)))))
+  (let ((b (lookup-in-lexenv var env 'variable)))
+    (if (eq (binding-type b) 'lexical-variable)
+        (concat (binding-translation b) " = " (ls-compile val env))
+        (ls-compile `(set ',var ,val) env))))
 
 ;;; FFI Variable accessors
 (define-compilation js-vref (var)
   var)
+
 (define-compilation js-vset (var val)
   (concat "(" var " = " (ls-compile val env) ")"))
 
         (let ((v (genlit))
               (s (concat "{name: \"" (escape-string (symbol-name sexp)) "\"}")))
           (push (cons sexp v) *literal-symbols*)
-          (push (concat "var " v " = " s) *toplevel-compilations*)
+          (toplevel-compilation (concat "var " v " = " s))
           v))
      #+ecmalisp
-     (let ((v (genlit)))
-       (push (concat "var " v " = " (ls-compile `(intern ,(symbol-name sexp))))
-             *toplevel-compilations*)
+     (let ((v (genlit))
+           (s (ls-compile `(intern ,(symbol-name sexp)))))
+       (toplevel-compilation (concat "var " v " = " s))
        v))
     ((consp sexp)
      (let ((c (concat "{car: " (literal (car sexp) t) ", "
        (if recursive
           c
           (let ((v (genlit)))
-            (push (concat "var " v " = " c) *toplevel-compilations*)
+            (toplevel-compilation (concat "var " v " = " c))
             v))))))
 
 (define-compilation quote (sexp)
 
 (define-compilation eval-when-compile (&rest body)
   (eval (cons 'progn body))
-  "")
+  nil)
 
 (defmacro define-transformation (name args form)
   `(define-compilation ,name ,args
       (let ((new-env (extend-local-env variables env)))
         (concat "(function("
                 (join (mapcar (lambda (x)
-                                (lookup-variable-translation x new-env))
+                                (translate-variable x new-env))
                               variables)
                       ",")
                 "){" *newline*
   (type-check (("x" "number" x))
     "Math.floor(x)"))
 
-(define-builtin cons (x y) (concat "({car: " x ", cdr: " y "})"))
+(define-builtin cons (x y)
+  (concat "({car: " x ", cdr: " y "})"))
+
 (define-builtin consp (x)
   (js!bool
    (js!selfcall
   (concat "(" symbol ").value =" value))
 
 (define-builtin symbol-value (x)
-  (concat "(" x ").value"))
+  (js!selfcall
+    "var symbol = " x ";" *newline*
+    "var value = symbol.value;" *newline*
+    "if (value === undefined) throw \"Variable `\" + symbol.name + \"' is unbound.\";" *newline*
+    "return value;" *newline*))
 
 (define-builtin symbol-function (x)
   (concat "(" x ").function"))
 (defun ls-compile (sexp &optional (env (make-lexenv)))
   (cond
     ((symbolp sexp)
-     (let ((b (lookup-variable sexp env)))
-       (ecase (binding-type b)
-        (lexical-variable
-         (binding-translation b))
-        (special-variable
-          (ls-compile `(symbol-value ',sexp) env)))))
+     (let ((b (lookup-in-lexenv sexp env 'variable)))
+       (if (eq (binding-type b) 'lexical-variable)
+           (binding-translation b)
+           (ls-compile `(symbol-value ',sexp) env))))
     ((integerp sexp) (integer-to-string sexp))
     ((stringp sexp) (concat "\"" (escape-string sexp) "\""))
     ((listp sexp)
              (compile-funcall (car sexp) (cdr sexp) env))))))
 
 (defun ls-compile-toplevel (sexp)
+  (setq *toplevel-compilations* nil)
   (cond
     ((and (consp sexp) (eq (car sexp) 'progn))
-     (let ((subs (mapcar 'ls-compile-toplevel (cdr sexp))))
-       (join-trailing
-       (remove-if (lambda (s) (or (null s) (equal s "")))
-                  subs)
-       (concat ";" *newline*))))
+     (let ((subs (mapcar #'ls-compile-toplevel (cdr sexp))))
+       (join (remove-if #'null-or-empty-p subs))))
     (t
-     (setq *toplevel-compilations* nil)
      (let ((code (ls-compile sexp)))
        (prog1
-          (concat (join-trailing *toplevel-compilations*
-                                 (concat ";" *newline*))
-                  code)
-        (setq *toplevel-compilations* nil))))))
+           (concat (join-trailing (get-toplevel-compilations) (concat ";" *newline*))
+                   (if code
+                       (concat code ";" *newline*)
+                       ""))
+         (setq *toplevel-compilations* nil))))))
 
 
 ;;; Once we have the compiler, we define the runtime environment and
                (ls-compile-toplevel x))))
       (js-eval code)))
 
-  ;; Set the initial global environment to be equal to the host global
-  ;; environment at this point of the compilation.
-  (eval-when-compile
-    (let ((tmp (ls-compile
-                `(progn
-                   (setq *environment* ',*environment*)
-                   (setq *variable-counter* ',*variable-counter*)
-                   (setq *function-counter* ',*function-counter*)
-                   (setq *literal-counter* ',*literal-counter*)
-                   (setq *gensym-counter* ',*gensym-counter*)
-                   (setq *block-counter* ',*block-counter*)
-                  ,@(mapcar (lambda (s)
-                              `(oset *package* ,(symbol-name (car s))
-                                     (js-vref ,(cdr s))))
-                            *literal-symbols*)))))
-      (setq *toplevel-compilations*
-            (append *toplevel-compilations* (list tmp)))))
-
   (js-eval "var lisp")
   (js-vset "lisp" (new))
   (js-vset "lisp.read" #'ls-read-from-string)
   (js-vset "lisp.eval" #'eval)
   (js-vset "lisp.compile" #'ls-compile-toplevel)
   (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)))))
+  (js-vset "lisp.compileString" (lambda (str) (ls-compile-toplevel (ls-read-from-string str))))
+
+  ;; Set the initial global environment to be equal to the host global
+  ;; environment at this point of the compilation.
+  (eval-when-compile
+    (toplevel-compilation
+     (ls-compile
+      `(progn
+         ,@(mapcar (lambda (s)
+                     `(oset *package* ,(symbol-name (car s))
+                            (js-vref ,(cdr s))))
+                   *literal-symbols*)
+         (setq *environment* ',*environment*)
+         (setq *variable-counter* ,*variable-counter*)
+         (setq *function-counter* ,*function-counter*)
+         (setq *gensym-counter* ,*gensym-counter*)
+         (setq *block-counter* ,*block-counter*)))))
+
+  (eval-when-compile
+    (toplevel-compilation
+     (ls-compile `(setq *literal-counter* ,*literal-counter*)))))
 
 
 ;;; Finally, we provide a couple of functions to easily bootstrap
            until (eq x *eof*)
            for compilation = (ls-compile-toplevel x)
            when (plusp (length compilation))
-           do (write-line (concat compilation "; ") out))
+           do (write-string compilation out))
         (dolist (check *compilation-unit-checks*)
           (funcall check))
         (setq *compilation-unit-checks* nil))))