Merge branch 'master' into js-ffi
[jscl.git] / ecmalisp.lisp
index 7b82d82..6489407 100644 (file)
                (cdr 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
 ;;; constructions.
   (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
         (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)
                 (when (eq package *keyword-package*)
                   (oset symbol "value" symbol)
                   (export (list symbol) package))
-               (when (eq package (find-package "JS"))
+               (when (eq package *js-package*)
                  (let ((sym-name (symbol-name symbol))
                         (args (gensym)))
                     ;; Generate a trampoline to call the JS function
                    (fset symbol
                           (eval `(lambda (&rest ,args)
                                    (let ((,args (list-to-vector ,args)))
-                                     (%js-call (%js-vref ,sym-name) ,args)))))))
+                                     (%js-call (%js-vref ,sym-name) ,args)))))
+                    ;; Define it as a symbol macro to access to the
+                    ;; Javascript variable literally.
+                    (%define-symbol-macro symbol `(%js-vref ,(string symbol)))))
                 (oset symbols name symbol)
                 (values symbol nil)))))))
 
 
 (defvar *newline* (string (code-char 10)))
 
+#+ecmalisp
 (defun concat (&rest 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))))
       (aset v i x)
       (incf i))))
 
+(defmacro awhen (condition &body body)
+  `(let ((it ,condition))
+     (when it ,@body)))
+
 #+ecmalisp
 (progn
   (defun values-list (list)
                        "()"
                        (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)
 (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)
-  (!parse-integer string nil))
+(defun parse-integer (string &key junk-allowed)
+  (multiple-value-bind (num index)
+      (!parse-integer string junk-allowed)
+    (when num
+      (values num index)
+      (error "junk detected."))))
 
 (defvar *eof* (gensym))
 (defun ls-read (stream)
 ;;; 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)))
+
+
+;;; Environment
+
+(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))
 
   (code "v" (incf *variable-counter*)))
 
 (defun translate-variable (symbol)
-  (binding-value (lookup-in-lexenv symbol *environment* 'variable)))
+  (awhen (lookup-in-lexenv symbol *environment* 'variable)
+    (binding-value it)))
 
 (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)
+  (let ((binding (make-binding :name name :type 'macro :value lambda)))
+    (push-to-lexenv binding  *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)
 
+(defun %define-symbol-macro (name expansion)
+  (let ((b (make-binding :name name :type 'macro :value expansion)))
+    (push-to-lexenv b *environment* 'variable)
+    name))
+
+#+ecmalisp
+(defmacro define-symbol-macro (name expansion)
+  `(%define-symbol-macro ',name ',expansion))
+
+
 ;;; Special forms
 
 (defvar *compilations* nil)
 
 (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))
 
 
 (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))
     (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)))
 
 (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))))
-    (if macro-binding
-        (let ((expander (binding-value macro-binding)))
-          (when (listp expander)
-            (let ((compiled (eval expander)))
-              ;; The list representation are useful while
-              ;; bootstrapping, as we can dump the definition of the
-              ;; macros easily, but they are slow because we have to
-              ;; evaluate them and compile them now and again. So, let
-              ;; us replace the list representation version of the
-              ;; function with the compiled one.
-              ;;
-              #+ecmalisp (set-binding-value macro-binding compiled)
-              (setq expander compiled)))
-          (apply expander (cdr form)))
-        form)))
+  (cond
+    ((symbolp form)
+     (let ((b (lookup-in-lexenv form *environment* 'variable)))
+       (if (and b (eq (binding-type b) 'macro))
+           (values (binding-value b) t)
+           (values form nil))))
+    ((consp form)
+     (let ((macro-binding (macro (car form))))
+       (if macro-binding
+           (let ((expander (binding-value macro-binding)))
+             (when (listp expander)
+               (let ((compiled (eval expander)))
+                 ;; The list representation are useful while
+                 ;; bootstrapping, as we can dump the definition of the
+                 ;; macros easily, but they are slow because we have to
+                 ;; evaluate them and compile them now and again. So, let
+                 ;; us replace the list representation version of the
+                 ;; function with the compiled one.
+                 ;;
+                 #+ecmalisp (setf (binding-value macro-binding) compiled)
+                 (setq expander compiled)))
+             (values (apply expander (cdr form)) t))
+           (values form nil))))
+    (t
+     (values form nil))))
 
 (defun compile-funcall (function args)
   (let* ((values-funcs (if *multiple-value-p* "values" "pv"))
        (concat ";" *newline*))))
 
 (defun ls-compile (sexp &optional multiple-value-p)
-  (let ((*multiple-value-p* multiple-value-p))
-    (cond
-      ((symbolp sexp)
-       (let ((b (lookup-in-lexenv sexp *environment* 'variable)))
-         (cond
-           ((and b (not (member 'special (binding-declarations b))))
-            (binding-value b))
-           ((or (keywordp sexp)
-                (member 'constant (binding-declarations b)))
-            (code (ls-compile `',sexp) ".value"))
-           (t
-            (ls-compile `(symbol-value ',sexp))))))
-      ((integerp sexp) (integer-to-string sexp))
-      ((stringp sexp) (code "\"" (escape-string sexp) "\""))
-      ((arrayp sexp) (literal sexp))
-      ((listp sexp)
-       (let ((name (car sexp))
-             (args (cdr sexp)))
-         (cond
-           ;; Special forms
-           ((assoc name *compilations*)
-            (let ((comp (second (assoc name *compilations*))))
-              (apply comp args)))
-           ;; Built-in functions
-           ((and (assoc name *builtins*)
-                 (not (claimp name 'function 'notinline)))
-            (let ((comp (second (assoc name *builtins*))))
-              (apply comp args)))
-           (t
-            (if (macro name)
-                (ls-compile (ls-macroexpand-1 sexp) multiple-value-p)
-                (compile-funcall name args))))))
-      (t
-       (error (concat "How should I compile " (prin1-to-string sexp) "?"))))))
+  (multiple-value-bind (sexp expandedp) (ls-macroexpand-1 sexp)
+    (when expandedp
+      (return-from ls-compile (ls-compile sexp multiple-value-p)))
+    ;; The expression has been macroexpanded. Now compile it!
+    (let ((*multiple-value-p* multiple-value-p))
+      (cond
+        ((symbolp sexp)
+         (let ((b (lookup-in-lexenv sexp *environment* 'variable)))
+           (cond
+             ((and b (not (member 'special (binding-declarations b))))
+              (binding-value b))
+             ((or (keywordp sexp)
+                  (and b (member 'constant (binding-declarations b))))
+              (code (ls-compile `',sexp) ".value"))
+             (t
+              (ls-compile `(symbol-value ',sexp))))))
+        ((integerp sexp) (integer-to-string sexp))
+        ((stringp sexp) (code "\"" (escape-string sexp) "\""))
+        ((arrayp sexp) (literal sexp))
+        ((listp sexp)
+         (let ((name (car sexp))
+               (args (cdr sexp)))
+           (cond
+             ;; Special forms
+             ((assoc name *compilations*)
+              (let ((comp (second (assoc name *compilations*))))
+                (apply comp args)))
+             ;; Built-in functions
+             ((and (assoc name *builtins*)
+                   (not (claimp name 'function 'notinline)))
+              (let ((comp (second (assoc name *builtins*))))
+                (apply comp args)))
+             (t
+              (compile-funcall name args)))))
+        (t
+         (error (concat "How should I compile " (prin1-to-string sexp) "?")))))))
 
 
 (defvar *compile-print-toplevels* nil)
 
 (defun truncate-string (string &optional (width 60))
-    (let ((size (length string))
-          (n (or (position #\newline string)
-                 (min width (length string)))))
-      (subseq string 0 n)))
+  (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))
   (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 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))
+  (export '(&body &key &optional &rest * *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 define-setf-expander
+            define-symbol-macro defmacro defparameter defun 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-setf-expansion
+            get-universal-time go identity if in-package incf integerp
+            integerp intern keywordp labels lambda last length let
+            let* list list* list-all-packages 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
+            nreconc 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 string-upcase 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*)