Merge branch 'origin-master' into origin-format
authorRaimon Grau <raimon@3scale.net>
Thu, 2 May 2013 19:00:57 +0000 (21:00 +0200)
committerRaimon Grau <raimon@3scale.net>
Thu, 2 May 2013 19:00:57 +0000 (21:00 +0200)
Conflicts:
jscl.lisp
src/print.lisp
src/toplevel.lisp

25 files changed:
.gitignore
AUTHORS [deleted file]
CREDITS [new file with mode: 0644]
jscl.html
jscl.lisp
src/boot.lisp
src/compat.lisp
src/compiler.lisp
src/list.lisp [new file with mode: 0644]
src/package.lisp [new file with mode: 0644]
src/prelude.js
src/print.lisp
src/read.lisp
src/toplevel.lisp
src/utils.lisp
tests-report.lisp [new file with mode: 0644]
tests.lisp [new file with mode: 0644]
tests/conditionals.lisp [new file with mode: 0644]
tests/control.lisp [new file with mode: 0644]
tests/equal.lisp [new file with mode: 0644]
tests/iter-macros.lisp [new file with mode: 0644]
tests/list.lisp [new file with mode: 0644]
tests/read.lisp [new file with mode: 0644]
tests/tests-report.lisp [deleted file]
tests/tests.lisp [deleted file]

index 89b0bdb..3073aaa 100644 (file)
@@ -1,3 +1,4 @@
 *~
 *.fasl
-ecmalisp.js
+jscl.js
+tests.js
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644 (file)
index 37bd629..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,3 +0,0 @@
-David Vazquez           <davazp at gmail.com>
-Raimon Grau             <rgrau at gmail.com>
-Alfredo Beaumont        <alfredo.beaumont at gmail.com>
diff --git a/CREDITS b/CREDITS
new file mode 100644 (file)
index 0000000..71e38f1
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,6 @@
+David Vazquez           <davazp at gmail.com>
+Raimon Grau             <rgrau at gmail.com>
+Alfredo Beaumont        <alfredo.beaumont at gmail.com>
+Owen Rodley
+Andrea Griffini         <agriff at tin.it>
+Nikodemus Siivola       <nikodemus at random-state.net>
index 656af67..3cd1330 100644 (file)
--- a/jscl.html
+++ b/jscl.html
 
         var startPrompt = function () {
           // Start the prompt with history enabled.
-          jqconsole.Write(lisp.evalString(pv, '(CL:PACKAGE-NAME CL:*PACKAGE*)') + '> ', 'jqconsole-prompt');
+          jqconsole.Write(lisp.evalString(pv, 1, '(CL:PACKAGE-NAME CL:*PACKAGE*)') + '> ', 'jqconsole-prompt');
           jqconsole.Prompt(true, function (input) {
             // Output input with the class jqconsole-return.
             if (input[0] != ','){
                 try {
-                    var vs = lisp.evalInput(mv, input);
+                    var vs = lisp.evalInput(mv, 1, input);
                     for (var i=0; i<vs.length; i++){
-                       jqconsole.Write(lisp.print(pv, vs[i]) + '\n', 'jqconsole-return');
+                       jqconsole.Write(lisp.print(pv, 1, vs[i]) + '\n', 'jqconsole-return');
                     }
                 } catch(error) {
                     jqconsole.Write('ERROR: ' + (error.message || error) + '\n', 'jqconsole-error');
                 }
             } else {
-                jqconsole.Write(lisp.compileString(pv, input.slice(1)) + '\n', 'jqconsole-return');
+                jqconsole.Write(lisp.compileString(pv, 1, input.slice(1)) + '\n', 'jqconsole-return');
             }
             // Restart the prompt.
             startPrompt();
           }, function(input){
             try {
-                lisp.read(pv, input[0]==','? input.slice(1): input);
+                lisp.read(pv, 1, input[0]==','? input.slice(1): input);
             } catch(error) {
                 return 0;
             }
index 33b8f15..6d907a3 100644 (file)
--- a/jscl.lisp
+++ b/jscl.lisp
@@ -3,33 +3,29 @@
 ;; Copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 (defvar *source*
   '(("boot"      :target)
     ("compat"    :host)
     ("utils"     :both)
+    ("list"      :target)
     ("print"     :target)
+    ("package"   :target)
     ("read"      :both)
     ("compiler"  :both)
-    ("toplevel"  :target)
-    ;; Tests
-    ("tests"            :test)
-    ("format"           :test)
-    ("setf"             :test)
-    ("eval"             :test)
-    ("tests-report"     :test)))
+    ("toplevel"  :target)))
 
 (defun source-pathname
     (filename &key (directory '(:relative "src")) (type nil) (defaults filename))
 (with-compilation-unit ()
   (dolist (input *source*)
     (when (member (cadr input) '(:host :both))
-      (compile-file (source-pathname (car input))))))
+      (let ((fname (source-pathname (car input))))
+        (multiple-value-bind (fasl warn fail) (compile-file fname)
+          (declare (ignore fasl warn))
+          (when fail
+            (error "Compilation of ~A failed." fname)))))))
 
 ;;; Load jscl into the host
 (dolist (input *source*)
@@ -49,7 +49,7 @@
     (load (source-pathname (car input)))))
 
 (defun read-whole-file (filename)
-  (with-open-file (in filename)
+  (with-open-file (in filename :external-format :latin-1)
     (let ((seq (make-array (file-length in) :element-type 'character)))
       (read-sequence seq in)
       seq)))
 
 (defun bootstrap ()
   (setq *environment* (make-lexenv))
-  (setq *literal-symbols* nil)
+  (setq *literal-table* nil)
   (setq *variable-counter* 0
         *gensym-counter* 0
-        *literal-counter* 0
-        *block-counter* 0)
+        *literal-counter* 0)
   (with-open-file (out "jscl.js" :direction :output :if-exists :supersede)
     (write-string (read-whole-file (source-pathname "prelude.js")) out)
     (dolist (input *source*)
         (ls-compile-file (source-pathname (car input) :type "lisp") out))))
   ;; Tests
   (with-open-file (out "tests.js" :direction :output :if-exists :supersede)
-    (dolist (input *source*)
-      (when (member (cadr input) '(:test))
-        (ls-compile-file (source-pathname (car input)
-                                          :directory '(:relative "tests")
-                                          :type "lisp")
-                         out)))))
+    (dolist (input (append (directory "tests.lisp")
+                           (directory "tests/*.lisp")
+                           (directory "tests-report.lisp")))
+      (ls-compile-file input out))))
+
+
+;;; Run the tests in the host Lisp implementation. It is a quick way
+;;; to improve the level of trust of the tests.
+(defun run-tests-in-host ()
+  (load "tests.lisp")
+  (let ((*use-html-output-p* nil))
+    (declare (special *use-html-output-p*))
+    (dolist (input (directory "tests/*.lisp"))
+      (load input)))
+  (load "tests-report.lisp"))
index 0fc7a40..845b601 100644 (file)
@@ -3,18 +3,18 @@
 ;; Copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; This code is executed when JSCL compiles this file itself. The
 ;;; compiler provides compilation of some special forms, as well as
      ,@(when (stringp docstring) `((oset ',name "vardoc" ,docstring)))
      ',name))
 
-(defmacro named-lambda (name args &rest body)
-  (let ((x (gensym "FN")))
-    `(let ((,x (lambda ,args ,@body)))
-       (oset ,x "fname" ,name)
-       ,x)))
-
 (defmacro defun (name args &rest body)
   `(progn
-     (fset ',name
-           (named-lambda ,(symbol-name name) ,args
-             ,@(if (and (stringp (car body)) (not (null (cdr body))))
-                   `(,(car body) (block ,name ,@(cdr body)))
-                   `((block ,name ,@body)))))
+     (fset ',name #'(named-lambda ,name ,args ,@body))
      ',name))
 
-(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 1+ (x) (+ x 1))
 (defun 1- (x) (- x 1))
 (defun zerop (x) (= x 0))
-(defun truncate (x y) (floor (/ x y)))
+
+(defun truncate (x &optional (y 1))
+  (floor (/ x y)))
 
 (defun eql (x y) (eq x y))
 
 (defun not (x) (if x nil t))
 
-(defun cons (x y ) (cons x y))
-(defun consp (x) (consp x))
-
-(defun car (x)
-  "Return the CAR part of a cons, or NIL if X is null."
-  (car x))
-
-(defun cdr (x) (cdr x))
-(defun caar (x) (car (car x)))
-(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)))))
-(defun first (x) (car x))
-(defun second (x) (cadr x))
-(defun third (x) (caddr x))
-(defun fourth (x) (cadddr x))
-(defun rest (x) (cdr x))
-
-(defun list (&rest args) args)
-(defun atom (x)
-  (not (consp x)))
-
 ;; Basic macros
 
 (defmacro incf (place &optional (delta 1))
 
 (defmacro cond (&rest clausules)
   (if (null clausules)
-      nil
-      (if (eq (caar clausules) t)
-          `(progn ,@(cdar clausules))
-          `(if ,(caar clausules)
-               (progn ,@(cdar clausules))
-               (cond ,@(cdr clausules))))))
+    nil
+    (if (eq (caar clausules) t)
+      `(progn ,@(cdar clausules))
+      (let ((test-symbol (gensym)))
+        `(let ((,test-symbol ,(caar clausules)))
+           (if ,test-symbol
+             ,(if (null (cdar clausules))
+                test-symbol
+                `(progn ,@(cdar clausules)))
+             (cond ,@(cdr clausules))))))))
 
 (defmacro case (form &rest clausules)
   (let ((!form (gensym)))
     `(let ((,!form ,form))
        (cond
          ,@(mapcar (lambda (clausule)
-                     (if (eq (car clausule) t)
-                         clausule
+                     (if (or (eq (car clausule) t)
+                             (eq (car clausule) 'otherwise))
+                         `(t ,@(cdr clausule))
                          `((eql ,!form ',(car clausule))
                            ,@(cdr clausule))))
                    clausules)))))
          ,@body)
        (cdr ,head))))
 
-(defun map1 (func list)
-  (with-collect
-    (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))))))))
-
 (defun identity (x) x)
 
 (defun constantly (x)
   (lambda (&rest args)
     x))
 
-(defun copy-list (x)
-  (mapcar #'identity x))
+(defun code-char (x)
+  (code-char 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 char-code (x)
+  (char-code x))
 
-(defun code-char (x) x)
-(defun char-code (x) x)
-(defun char= (x y) (= x y))
+(defun char= (x y)
+  (eql x y))
 
 (defun integerp (x)
   (and (numberp x) (= (floor x) x)))
 (defun plusp (x) (< 0 x))
 (defun minusp (x) (< x 0))
 
-(defun listp (x)
-  (or (consp x) (null x)))
-
-(defun nthcdr (n list)
-  (while (and (plusp n) list)
-    (setq n (1- n))
-    (setq list (cdr list)))
-  list)
-
-(defun nth (n list)
-  (car (nthcdr n list)))
-
-(defun last (x)
-  (while (consp (cdr x))
-    (setq x (cdr x)))
-  x)
-
-(defun butlast (x)
-  (and (consp (cdr x))
-       (cons (car x) (butlast (cdr x)))))
-
-(defun member (x list)
-  (while list
-    (when (eql x (car list))
-      (return list))
-    (setq list (cdr list))))
+(defun atom (x)
+  (not (consp x)))
 
 (defun find (item list &key key (test #'eql))
   (dolist (x list)
      (remove-if-not func (cdr list)))))
 
 (defun digit-char-p (x)
-  (if (and (<= #\0 x) (<= x #\9))
-      (- x #\0)
+  (if (and (<= (char-code #\0) (char-code x) (char-code #\9)))
+      (- (char-code x) (char-code #\0))
       nil))
 
 (defun digit-char (weight)
       (incf pos))
     pos))
 
-(defun assoc (x alist)
-  (while alist
-    (if (eql x (caar alist))
-        (return)
-        (setq alist (cdr alist))))
-  (car alist))
-
 (defun string (x)
   (cond ((stringp x) x)
         ((symbolp x) (symbol-name x))
         (t (char-to-string x))))
 
+(defun equal (x y)
+  (cond
+    ((eql x y) t)
+    ((consp x)
+     (and (consp y)
+          (equal (car x) (car y))
+          (equal (cdr x) (cdr y))))
+    ((arrayp x)
+     (and (arrayp y)
+          (let ((n (length x)))
+            (when (= (length y) n)
+              (dotimes (i n)
+                (unless (equal (aref x i) (aref y i))
+                  (return-from equal nil)))
+              t))))
+    (t nil)))
+
 (defun string= (s1 s2)
   (equal s1 s2))
 
     ((null (cddr pairs))
      (let ((place (ls-macroexpand-1 (first pairs)))
            (value (second pairs)))
-       (multiple-value-bind (vars vals store-vars writer-form reader-form)
+       (multiple-value-bind (vars vals store-vars writer-form)
            (get-setf-expansion place)
          ;; TODO: Optimize the expansion a little bit to avoid let*
          ;; or multiple-value-bind when unnecesary.
               ((null pairs)
                (reverse result)))))))
 
-(define-setf-expander car (x)
-  (let ((cons (gensym))
-        (new-value (gensym)))
-    (values (list cons)
-            (list x)
-            (list new-value)
-            `(progn (rplaca ,cons ,new-value) ,new-value)
-            `(car ,cons))))
-
-(define-setf-expander cdr (x)
-  (let ((cons (gensym))
-        (new-value (gensym)))
-    (values (list cons)
-            (list x)
-            (list new-value)
-            `(progn (rplacd ,cons ,new-value) ,new-value)
-            `(car ,cons))))
-
 ;; Incorrect typecase, but used in NCONC.
 (defmacro typecase (x &rest clausules)
   (let ((value (gensym)))
                          `((,(ecase (car c)
                                     (integer 'integerp)
                                     (cons 'consp)
+                                    (symbol 'symbolp)
+                                    (array 'arrayp)
                                     (string 'stringp)
                                     (atom 'atom)
                                     (null 'null))
                                  (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)
-
-(defun list-all-packages ()
-  *package-list*)
-
-(defun make-package (name &key use)
-  (let ((package (new))
-        (use (mapcar #'find-package-or-fail use)))
-    (oset package "packageName" name)
-    (oset package "symbols" (new))
-    (oset package "exports" (new))
-    (oset package "use" use)
-    (push package *package-list*)
-    package))
-
-(defun packagep (x)
-  (and (objectp x) (in "symbols" x)))
-
-(defun find-package (package-designator)
-  (when (packagep package-designator)
-    (return-from find-package package-designator))
-  (let ((name (string package-designator)))
-    (dolist (package *package-list*)
-      (when (string= (package-name package) name)
-        (return package)))))
-
-(defun find-package-or-fail (package-designator)
-  (or (find-package package-designator)
-      (error "Package unknown.")))
-
-(defun package-name (package-designator)
-  (let ((package (find-package-or-fail package-designator)))
-    (oget package "packageName")))
+(defconstant internal-time-units-per-second 1000) 
 
-(defun %package-symbols (package-designator)
-  (let ((package (find-package-or-fail package-designator)))
-    (oget package "symbols")))
+(defun get-internal-real-time ()
+  (get-internal-real-time))
 
-(defun package-use-list (package-designator)
-  (let ((package (find-package-or-fail package-designator)))
-    (oget package "use")))
-
-(defun %package-external-symbols (package-designator)
-  (let ((package (find-package-or-fail package-designator)))
-    (oget package "exports")))
-
-(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*)))
-
-(defvar *keyword-package*
-  (make-package "KEYWORD"))
-
-(defun keywordp (x)
-  (and (symbolp x) (eq (symbol-package x) *keyword-package*)))
-
-(defvar *package* *common-lisp-package*)
-
-(defmacro in-package (package-designator)
-  `(eval-when-compile
-     (setq *package* (find-package-or-fail ,package-designator))))
-
-;; This function is used internally to initialize the CL package
-;; with the symbols built during bootstrap.
-(defun %intern-symbol (symbol)
-  (let* ((package
-          (if (in "package" symbol)
-              (find-package-or-fail (oget symbol "package"))
-              *common-lisp-package*))
-         (symbols (%package-symbols package)))
-    (oset symbol "package" package)
-    (when (eq package *keyword-package*)
-      (oset symbol "value" symbol))
-    (oset symbols (symbol-name symbol) symbol)))
-
-(defun find-symbol (name &optional (package *package*))
-  (let* ((package (find-package-or-fail package))
-         (externals (%package-external-symbols package))
-         (symbols (%package-symbols package)))
-    (cond
-      ((in name externals)
-       (values (oget externals name) :external))
-      ((in name symbols)
-       (values (oget symbols name) :internal))
-      (t
-       (dolist (used (package-use-list package) (values nil nil))
-         (let ((exports (%package-external-symbols used)))
-           (when (in name exports)
-             (return (values (oget exports name) :inherit)))))))))
-
-(defun intern (name &optional (package *package*))
-  (let ((package (find-package-or-fail package)))
-    (multiple-value-bind (symbol foundp)
-        (find-symbol name package)
-      (if foundp
-          (values symbol foundp)
-          (let ((symbols (%package-symbols package)))
-            (oget symbols name)
-            (let ((symbol (make-symbol name)))
-              (oset symbol "package" package)
-              (when (eq package *keyword-package*)
-                (oset symbol "value" symbol)
-                (export (list symbol) package))
-              (when (eq package *js-package*)
-                (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)))))
-                  ;; 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)))))))
-
-(defun symbol-package (symbol)
-  (unless (symbolp symbol)
-    (error "it is not a symbol"))
-  (oget symbol "package"))
-
-(defun export (symbols &optional (package *package*))
-  (let ((exports (%package-external-symbols package)))
-    (dolist (symb symbols t)
-      (oset exports (symbol-name symb) symb))))
+(defun get-unix-time ()
+  (truncate (/ (get-internal-real-time) 1000)))
 
 (defun get-universal-time ()
   (+ (get-unix-time) 2208988800))
index 184c5dd..a11aa5d 100644 (file)
@@ -3,18 +3,18 @@
 ;; Copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Duplicate from boot.lisp by now
 (defmacro with-collect (&body body)
index 171e0c8..eadbf39 100644 (file)
@@ -3,18 +3,18 @@
 ;; copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;;; Compiler
 
 (defvar *variable-counter* 0)
 
 (defun gvarname (symbol)
+  (declare (ignore symbol))
   (code "v" (incf *variable-counter*)))
 
 (defun translate-variable (symbol)
           (ll-optional-arguments-canonical lambda-list))))
     (remove nil (mapcar #'third args))))
 
-(defun lambda-docstring-wrapper (docstring &rest strs)
-  (if docstring
+(defun lambda-name/docstring-wrapper (name docstring &rest strs)
+  (if (or name docstring)
       (js!selfcall
         "var func = " (join strs) ";" *newline*
-        "func.docstring = '" docstring "';" *newline*
+        (when name
+          (code "func.fname = '" (escape-string name) "';" *newline*))
+        (when docstring
+          (code "func.docstring = '" (escape-string docstring) "';" *newline*))
         "return func;" *newline*)
       (apply #'code strs)))
 
     (n-required-arguments n-optional-arguments rest-p)
   ;; Note: Remember that we assume that the number of arguments of a
   ;; call is at least 1 (the values argument).
-  (let ((min (1+ n-required-arguments))
-        (max (if rest-p 'n/a (+ 1 n-required-arguments n-optional-arguments))))
+  (let ((min n-required-arguments)
+        (max (if rest-p 'n/a (+ n-required-arguments n-optional-arguments))))
     (block nil
       ;; Special case: a positive exact number of arguments.
-      (when (and (< 1 min) (eql min max))
-        (return (code "checkArgs(arguments, " min ");" *newline*)))
+      (when (and (< 0 min) (eql min max))
+        (return (code "checkArgs(nargs, " min ");" *newline*)))
       ;; General case:
       (code
-       (when (< 1 min)
-         (code "checkArgsAtLeast(arguments, " min ");" *newline*))
+       (when (< 0 min)
+         (code "checkArgsAtLeast(nargs, " min ");" *newline*))
        (when (numberp max)
-         (code "checkArgsAtMost(arguments, " max ");" *newline*))))))
+         (code "checkArgsAtMost(nargs, " max ");" *newline*))))))
 
 (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 (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*
+      (code "switch(nargs){" *newline*
             (let ((cases nil)
                   (idx 0))
               (progn
     (when rest-argument
       (let ((js!rest (translate-variable rest-argument)))
         (code "var " js!rest "= " (ls-compile nil) ";" *newline*
-              "for (var i = arguments.length-1; i>="
-              (+ 1 n-required-arguments n-optional-arguments)
+              "for (var i = nargs-1; i>=" (+ n-required-arguments n-optional-arguments)
               "; i--)" *newline*
-              (indent js!rest " = {car: arguments[i], cdr: ") js!rest "};"
-              *newline*)))))
+              (indent js!rest " = {car: arguments[i+2], cdr: " js!rest "};" *newline*))))))
 
 (defun compile-lambda-parse-keywords (ll)
   (let ((n-required-arguments
      ;; Parse keywords
      (flet ((parse-keyword (keyarg)
              ;; ((keyword-name var) init-form)
-             (code "for (i=" (+ 1 n-required-arguments n-optional-arguments)
-                    "; i<arguments.length; i+=2){" *newline*
+             (code "for (i=" (+ n-required-arguments n-optional-arguments)
+                    "; i<nargs; i+=2){" *newline*
                     (indent
-                     "if (arguments[i] === " (ls-compile (caar keyarg)) "){" *newline*
+                     "if (arguments[i+2] === " (ls-compile (caar keyarg)) "){" *newline*
                      (indent (translate-variable (cadr (car keyarg)))
-                             " = arguments[i+1];"
+                             " = arguments[i+3];"
                              *newline*
                              (let ((svar (third keyarg)))
                                (when svar
                      "}" *newline*)
                     "}" *newline*
                     ;; Default value
-                    "if (i == arguments.length){" *newline*
+                    "if (i == nargs){" *newline*
                     (indent (translate-variable (cadr (car keyarg))) " = " (ls-compile (cadr keyarg)) ";" *newline*)
                     "}" *newline*)))
        (when keyword-arguments
                (mapconcat #'parse-keyword keyword-arguments))))
      ;; Check for unknown keywords
      (when keyword-arguments
-       (code "for (i=" (+ 1 n-required-arguments n-optional-arguments)
-             "; i<arguments.length; i+=2){" *newline*
+       (code "for (i=" (+ n-required-arguments n-optional-arguments)
+             "; i<nargs; i+=2){" *newline*
              (indent "if ("
                      (join (mapcar (lambda (x)
-                                     (concat "arguments[i] !== " (ls-compile (caar x))))
+                                     (concat "arguments[i+2] !== " (ls-compile (caar x))))
                                    keyword-arguments)
                            " && ")
                      ")" *newline*
                       "throw 'Unknown keyword argument ' + arguments[i].name;" *newline*))
              "}" *newline*)))))
 
-(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))
+(defun parse-lambda-list (ll)
+  (values (ll-required-arguments ll)
+          (ll-optional-arguments ll)
+          (ll-keyword-arguments  ll)
+          (ll-rest-argument      ll)))
+
+;;; Process BODY for declarations and/or docstrings. Return as
+;;; multiple values the BODY without docstrings or declarations, the
+;;; list of declaration forms and the docstring.
+(defun parse-body (body &key declarations docstring)
+  (let ((value-declarations)
+        (value-docstring))
+    ;; Parse declarations
+    (when declarations
+      (do* ((rest body (cdr rest))
+            (form (car rest) (car rest)))
+           ((or (atom form) (not (eq (car form) 'declare)))
+            (setf body rest))
+        (push form value-declarations)))
+    ;; Parse docstring
+    (when (and docstring
+               (stringp (car body))
                (not (null (cdr body))))
-      (setq documentation (car body))
+      (setq value-docstring (car body))
       (setq body (cdr body)))
-    (let ((n-required-arguments (length required-arguments))
-          (n-optional-arguments (length optional-arguments))
-          (*environment* (extend-local-env
-                          (append (ensure-list rest-argument)
-                                  required-arguments
-                                  optional-arguments
-                                 keyword-arguments
-                                  (ll-svars ll)))))
-      (lambda-docstring-wrapper
-       documentation
-       "(function ("
-       (join (cons "values"
-                   (mapcar #'translate-variable
-                           (append required-arguments optional-arguments)))
-             ",")
-       "){" *newline*
-       (indent
-        ;; Check number of arguments
-        (lambda-check-argument-count n-required-arguments
-                                     n-optional-arguments
-                                     (or rest-argument keyword-arguments))
-       (compile-lambda-optional ll)
-       (compile-lambda-rest ll)
-       (compile-lambda-parse-keywords ll)
-        (let ((*multiple-value-p* t))
-         (ls-compile-block body t)))
-       "})"))))
+    (values body value-declarations value-docstring)))
+
+;;; Compile a lambda function with lambda list LL and body BODY. If
+;;; NAME is given, it should be a constant string and it will become
+;;; the name of the function. If BLOCK is non-NIL, a named block is
+;;; created around the body. NOTE: No block (even anonymous) is
+;;; created if BLOCk is NIL.
+(defun compile-lambda (ll body &key name block)
+  (multiple-value-bind (required-arguments
+                        optional-arguments
+                        keyword-arguments
+                        rest-argument)
+      (parse-lambda-list ll)
+    (multiple-value-bind (body decls documentation)
+        (parse-body body :declarations t :docstring t)
+      (declare (ignore decls))
+      (let ((n-required-arguments (length required-arguments))
+            (n-optional-arguments (length optional-arguments))
+            (*environment* (extend-local-env
+                            (append (ensure-list rest-argument)
+                                    required-arguments
+                                    optional-arguments
+                                    keyword-arguments
+                                    (ll-svars ll)))))
+        (lambda-name/docstring-wrapper name documentation
+         "(function ("
+         (join (list* "values"
+                      "nargs"
+                      (mapcar #'translate-variable
+                              (append required-arguments optional-arguments)))
+               ",")
+         "){" *newline*
+         (indent
+          ;; Check number of arguments
+          (lambda-check-argument-count n-required-arguments
+                                       n-optional-arguments
+                                       (or rest-argument keyword-arguments))
+                                        (compile-lambda-optional ll)
+                                        (compile-lambda-rest ll)
+                                        (compile-lambda-parse-keywords ll)
+                                        (let ((*multiple-value-p* t))
+                                          (if block
+                                              (ls-compile-block `((block ,block ,@body)) t)
+                                              (ls-compile-block body t))))
+         "})")))))
 
 
 (defun setq-pair (var val)
     output))
 
 
-(defvar *literal-symbols* nil)
+(defvar *literal-table* nil)
 (defvar *literal-counter* 0)
 
 (defun genlit ()
   (code "l" (incf *literal-counter*)))
 
+(defun dump-symbol (symbol)
+  #+common-lisp
+  (let ((package (symbol-package symbol)))
+    (if (eq package (find-package "KEYWORD"))
+        (code "{name: \"" (escape-string (symbol-name symbol))
+              "\", 'package': '" (package-name package) "'}")
+        (code "{name: \"" (escape-string (symbol-name symbol)) "\"}")))
+  #+jscl
+  (let ((package (symbol-package symbol)))
+    (if (null package)
+        (code "{name: \"" (escape-string (symbol-name symbol)) "\"}")
+        (ls-compile `(intern ,(symbol-name symbol) ,(package-name package))))))
+
+(defun dump-cons (cons)
+  (let ((head (butlast cons))
+        (tail (last cons)))
+    (code "QIList("
+          (join-trailing (mapcar (lambda (x) (literal x t)) head) ",")
+          (literal (car tail) t)
+          ","
+          (literal (cdr tail) t)
+          ")")))
+
+(defun dump-array (array)
+  (let ((elements (vector-to-list array)))
+    (concat "[" (join (mapcar #'literal elements) ", ") "]")))
+
 (defun literal (sexp &optional recursive)
   (cond
     ((integerp sexp) (integer-to-string sexp))
     ((floatp sexp) (float-to-string sexp))
+    ((characterp sexp) (code "\"" (escape-string (string sexp)) "\""))
     ((stringp sexp) (code "\"" (escape-string sexp) "\""))
-    ((symbolp sexp)
-     (or (cdr (assoc sexp *literal-symbols*))
-        (let ((v (genlit))
-              (s #+common-lisp
-                 (let ((package (symbol-package sexp)))
-                   (if (eq package (find-package "KEYWORD"))
-                       (code "{name: \"" (escape-string (symbol-name sexp))
-                             "\", 'package': '" (package-name package) "'}")
-                       (code "{name: \"" (escape-string (symbol-name sexp)) "\"}")))
-                 #+jscl
-                 (let ((package (symbol-package sexp)))
-                   (if (null package)
-                       (code "{name: \"" (escape-string (symbol-name sexp)) "\"}")
-                       (ls-compile `(intern ,(symbol-name sexp) ,(package-name package)))))))
-          (push (cons sexp v) *literal-symbols*)
-          (toplevel-compilation (code "var " v " = " s))
-          v)))
-    ((consp sexp)
-     (let* ((head (butlast sexp))
-            (tail (last sexp))
-            (c (code "QIList("
-                     (join-trailing (mapcar (lambda (x) (literal x t)) head) ",")
-                     (literal (car tail) t)
-                     ","
-                     (literal (cdr tail) t)
-                     ")")))
-       (if recursive
-          c
-          (let ((v (genlit)))
-             (toplevel-compilation (code "var " v " = " c))
-             v))))
-    ((arrayp sexp)
-     (let ((elements (vector-to-list sexp)))
-       (let ((c (concat "[" (join (mapcar #'literal elements) ", ") "]")))
-        (if recursive
-            c
-            (let ((v (genlit)))
-              (toplevel-compilation (code "var " v " = " c))
-              v)))))))
+    (t
+     (or (cdr (assoc sexp *literal-table*))
+         (let ((dumped (typecase sexp
+                         (symbol (dump-symbol sexp))
+                         (cons (dump-cons sexp))
+                         (array (dump-array sexp)))))
+           (if (and recursive (not (symbolp sexp)))
+               dumped
+               (let ((jsvar (genlit)))
+                 (push (cons sexp jsvar) *literal-table*)
+                 (toplevel-compilation (code "var " jsvar " = " dumped))
+                 jsvar)))))))
 
 (define-compilation quote (sexp)
   (literal sexp))
   (cond
     ((and (listp x) (eq (car x) 'lambda))
      (compile-lambda (cadr x) (cddr x)))
+    ((and (listp x) (eq (car x) 'named-lambda))
+     ;; TODO: destructuring-bind now! Do error checking manually is
+     ;; very annoying.
+     (let ((name (cadr x))
+           (ll (caddr x))
+           (body (cdddr x)))
+       (compile-lambda ll body
+                       :name (symbol-name name)
+                       :block name)))
     ((symbolp x)
      (let ((b (lookup-in-lexenv x *environment* 'function)))
        (if b
 
 (define-compilation flet (definitions &rest body)
   (let* ((fnames (mapcar #'car definitions))
-         (fbody  (mapcar #'cdr definitions))
-         (cfuncs (mapcar #'compile-function-definition fbody))
+         (cfuncs (mapcar (lambda (def)
+                           (compile-lambda (cadr def)
+                                           `((block ,(car def)
+                                               ,@(cddr def)))))
+                         definitions))
          (*environment*
           (extend-lexenv (mapcar #'make-function-binding fnames)
                          *environment*
     (js!selfcall
       (mapconcat (lambda (func)
                   (code "var " (translate-function (car func))
-                         " = " (compile-lambda (cadr func) (cddr func))
+                         " = " (compile-lambda (cadr func)
+                                               `((block ,(car func) ,@(cddr func))))
                          ";" *newline*))
                 definitions)
       (ls-compile-block body t))))
         (let*-binding-wrapper specials body)))))
 
 
-(defvar *block-counter* 0)
-
 (define-compilation block (name &rest body)
-  (let* ((tr (incf *block-counter*))
-         (b (make-binding :name name :type 'block :value tr)))
+  ;; We use Javascript exceptions to implement non local control
+  ;; transfer. Exceptions has dynamic scoping, so we use a uniquely
+  ;; generated object to identify the block. The instance of a empty
+  ;; array is used to distinguish between nested dynamic Javascript
+  ;; exceptions. See https://github.com/davazp/jscl/issues/64 for
+  ;; futher details.
+  (let* ((idvar (gvarname name))
+         (b (make-binding :name name :type 'block :value idvar)))
     (when *multiple-value-p*
       (push 'multiple-value (binding-declarations b)))
     (let* ((*environment* (extend-lexenv (list b) *environment* 'block))
       (if (member 'used (binding-declarations b))
           (js!selfcall
             "try {" *newline*
+            "var " idvar " = [];" *newline*
             (indent cbody)
             "}" *newline*
             "catch (cf){" *newline*
-            "    if (cf.type == 'block' && cf.id == " tr ")" *newline*
+            "    if (cf.type == 'block' && cf.id == " idvar ")" *newline*
             (if *multiple-value-p*
                 "        return values.apply(this, forcemv(cf.values));"
                 "        return cf.values;")
     (when (null b)
       (error (concat "Unknown block `" (symbol-name name) "'.")))
     (push 'used (binding-declarations b))
+    ;; The binding value is the name of a variable, whose value is the
+    ;; unique identifier of the block as exception. We can't use the
+    ;; variable name itself, because it could not to be unique, so we
+    ;; capture it in a closure.
     (js!selfcall
       (when multiple-value-p (code "var values = mv;" *newline*))
       "throw ({"
     "message: 'Throw uncatched.'"
     "})"))
 
-
-(defvar *tagbody-counter* 0)
-(defvar *go-tag-counter* 0)
-
 (defun go-tag-p (x)
   (or (integerp x) (symbolp x)))
 
 (defun declare-tagbody-tags (tbidx body)
-  (let ((bindings
-         (mapcar (lambda (label)
-                   (let ((tagidx (integer-to-string (incf *go-tag-counter*))))
-                     (make-binding :name label :type 'gotag :value (list tbidx tagidx))))
-                 (remove-if-not #'go-tag-p body))))
+  (let* ((go-tag-counter 0)
+         (bindings
+          (mapcar (lambda (label)
+                    (let ((tagidx (integer-to-string (incf go-tag-counter))))
+                      (make-binding :name label :type 'gotag :value (list tbidx tagidx))))
+                  (remove-if-not #'go-tag-p body))))
     (extend-lexenv bindings *environment* 'gotag)))
 
 (define-compilation tagbody (&rest body)
   (unless (go-tag-p (car body))
     (push (gensym "START") body))
   ;; Tagbody compilation
-  (let ((tbidx *tagbody-counter*))
+  (let ((branch (gvarname 'branch))
+        (tbidx (gvarname 'tbidx)))
     (let ((*environment* (declare-tagbody-tags tbidx body))
           initag)
       (let ((b (lookup-in-lexenv (first body) *environment* 'gotag)))
         (setq initag (second (binding-value b))))
       (js!selfcall
-        "var tagbody_" tbidx " = " initag ";" *newline*
+        ;; TAGBODY branch to take
+        "var " branch " = " initag ";" *newline*
+        "var " tbidx " = [];" *newline*
         "tbloop:" *newline*
         "while (true) {" *newline*
         (indent "try {" *newline*
                 (indent (let ((content ""))
-                          (code "switch(tagbody_" tbidx "){" *newline*
+                          (code "switch(" branch "){" *newline*
                                 "case " initag ":" *newline*
                                 (dolist (form (cdr body) content)
                                   (concatf content
                 "}" *newline*
                 "catch (jump) {" *newline*
                 "    if (jump.type == 'tagbody' && jump.id == " tbidx ")" *newline*
-                "        tagbody_" tbidx " = jump.label;" *newline*
+                "        " branch " = jump.label;" *newline*
                 "    else" *newline*
                 "        throw(jump);" *newline*
                 "}" *newline*)
 (define-compilation multiple-value-call (func-form &rest forms)
   (js!selfcall
     "var func = " (ls-compile func-form) ";" *newline*
-    "var args = [" (if *multiple-value-p* "values" "pv") "];" *newline*
+    "var args = [" (if *multiple-value-p* "values" "pv") ", 0];" *newline*
     "return "
     (js!selfcall
       "var values = mv;" *newline*
                          "else" *newline*
                          (indent "args.push(vs);" *newline*)))
                  forms)
+      "args[1] = args.length-2;" *newline*
       "return func.apply(window, args);" *newline*) ";" *newline*))
 
 (define-compilation multiple-value-prog1 (first-form &rest forms)
 (define-builtin lambda-code (x)
   (code "(" x ").toString()"))
 
-(define-builtin eq    (x y) (js!bool (code "(" x " === " y ")")))
-(define-builtin equal (x y) (js!bool (code "(" x  " == " y ")")))
+(define-builtin eq (x y)
+  (js!bool (code "(" x " === " y ")")))
+
+(define-builtin char-code (x)
+  (type-check (("x" "string" x))
+    "x.charCodeAt(0)"))
+
+(define-builtin code-char (x)
+  (type-check (("x" "number" x))
+    "String.fromCharCode(x)"))
+
+(define-builtin characterp (x)
+  (js!bool
+   (js!selfcall
+     "var x = " x ";" *newline*
+     "return (typeof(" x ") == \"string\") && x.length == 1;")))
 
 (define-builtin char-to-string (x)
   (type-check (("x" "number" x))
 (define-builtin char (string index)
   (type-check (("string" "string" string)
                ("index" "number" index))
-    "string.charCodeAt(index)"))
+    "string.charAt(index)"))
 
 (define-builtin concat-two (string1 string2)
   (type-check (("string1" "string" string1)
   (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))
+    (join (list* (if *multiple-value-p* "values" "pv")
+                 (integer-to-string (length args))
+                 (mapcar #'ls-compile args))
           ", ")
     ")"))
 
             (last (car (last args))))
         (js!selfcall
           "var f = " (ls-compile func) ";" *newline*
-          "var args = [" (join (cons (if *multiple-value-p* "values" "pv")
-                                     (mapcar #'ls-compile args))
+          "var args = [" (join (list* (if *multiple-value-p* "values" "pv")
+                                      (integer-to-string (length args))
+                                      (mapcar #'ls-compile args))
                                ", ")
           "];" *newline*
           "var tail = (" (ls-compile last) ");" *newline*
           "while (tail != " (ls-compile nil) "){" *newline*
           "    args.push(tail.car);" *newline*
+          "    args[1] += 1;" *newline*
           "    tail = tail.cdr;" *newline*
           "}" *newline*
           "return (typeof f === 'function'? f : f.fvalue).apply(this, args);" *newline*))))
   (type-check (("string" "string" string))
     (if *multiple-value-p*
         (js!selfcall
-          "var v = eval.apply(window, [string]);" *newline*
-          "if (typeof v !== 'object' || !('multiple-value' in v)){" *newline*
-          (indent "v = [v];" *newline*
-                  "v['multiple-value'] = true;" *newline*)
-          "}" *newline*
-          "return values.apply(this, v);" *newline*)
-        "eval.apply(window, [string])")))
+          "var v = globalEval(string);" *newline*
+          "return values.apply(this, forcemv(v));" *newline*)
+        "globalEval(string)")))
 
 (define-builtin error (string)
   (js!selfcall "throw " string ";" *newline*))
     "if (i < 0 || i >= x.length) throw 'Out of range';" *newline*
     "return x[i] = " value ";" *newline*))
 
-(define-builtin get-unix-time ()
-  (code "(Math.round(new Date() / 1000))"))
+(define-builtin get-internal-real-time ()
+  "(new Date()).getTime()")
 
 (define-builtin values-array (array)
   (if *multiple-value-p*
 
 (defun compile-funcall (function args)
   (let* ((values-funcs (if *multiple-value-p* "values" "pv"))
-         (arglist (concat "(" (join (cons values-funcs (mapcar #'ls-compile args)) ", ") ")")))
+         (arglist (concat "(" (join (list* values-funcs
+                                           (integer-to-string (length args))
+                                           (mapcar #'ls-compile args)) ", ") ")")))
     (unless (or (symbolp function)
                 (and (consp function)
                      (eq (car function) 'lambda)))
               (ls-compile `(symbol-value ',sexp))))))
         ((integerp sexp) (integer-to-string sexp))
         ((floatp sexp) (float-to-string sexp))
+        ((characterp sexp) (code "\"" (escape-string (string sexp)) "\""))
         ((stringp sexp) (code "\"" (escape-string sexp) "\""))
         ((arrayp sexp) (literal sexp))
         ((listp sexp)
diff --git a/src/list.lisp b/src/list.lisp
new file mode 100644 (file)
index 0000000..64c03c9
--- /dev/null
@@ -0,0 +1,224 @@
+;;; list.lisp --- 
+
+;; JSCL is free software: you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of the
+;; License, or (at your option) any later version.
+;;
+;; JSCL is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
+
+;;;; Various list functions
+
+(defun cons (x y ) (cons x y))
+(defun consp (x) (consp x))
+
+(defun listp (x)
+  (or (consp x) (null x)))
+
+(defun null (x)
+  (eq x nil))
+
+(defun endp (x)
+  (if (null x)
+      t
+      (if (consp x)
+          nil
+          (error "type-error"))))
+
+(defun car (x)
+  "Return the CAR part of a cons, or NIL if X is null."
+  (car x))
+
+(defun cdr (x) (cdr x))
+
+(defun first (x) (car x))
+(defun second (x) (cadr x))
+(defun third (x) (caddr x))
+(defun fourth (x) (cadddr x))
+(defun rest (x) (cdr x))
+
+(defun list (&rest args)
+  args)
+
+(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 nthcdr (n list)
+  (while (and (plusp n) list)
+    (setq n (1- n))
+    (setq list (cdr list)))
+  list)
+
+(defun nth (n list)
+  (car (nthcdr n list)))
+
+;;; The rest of the C[AD]*R functions; only a few were defined in boot.lisp
+(defun caar (x) (car (car x)))
+(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)))))
+
+(defun cadar  (x) (car (cdar  x)))
+(defun caaar  (x) (car (caar  x)))
+(defun caadr  (x) (car (cadr  x)))
+(defun cdaar  (x) (cdr (caar  x)))
+(defun cdadr  (x) (cdr (cadr  x)))
+(defun cddar  (x) (cdr (cdar  x)))
+(defun caaaar (x) (car (caaar x)))
+(defun caaadr (x) (car (caadr x)))
+(defun caadar (x) (car (cadar x)))
+(defun caaddr (x) (car (caddr x)))
+(defun cadaar (x) (car (cdaar x)))
+(defun cadadr (x) (car (cdadr x)))
+(defun caddar (x) (car (cddar x)))
+(defun cdaaar (x) (cdr (caaar x)))
+(defun cdaadr (x) (cdr (caadr x)))
+(defun cdadar (x) (cdr (cadar x)))
+(defun cdaddr (x) (cdr (caddr x)))
+(defun cddaar (x) (cdr (cdaar x)))
+(defun cddadr (x) (cdr (cdadr x)))
+(defun cdddar (x) (cdr (cddar x)))
+(defun cddddr (x) (cdr (cdddr x)))
+
+
+(defun copy-list (x)
+  (mapcar #'identity x))
+
+(defun copy-tree (tree)
+  (if (consp tree)
+    (cons (copy-tree (car tree))
+          (copy-tree (cdr tree)))
+    tree))
+
+(defun subst (new old tree &key (key #'identity) (test #'eql))
+  (cond 
+    ((funcall test (funcall key tree) (funcall key old))
+     new) 
+    ((consp tree)
+     (cons (subst new old (car tree) :key key :test test)
+           (subst new old (cdr tree) :key key :test test))) 
+    (t tree)))
+
+(defmacro pop (place)
+  (multiple-value-bind (dummies vals newval setter getter)
+    (get-setf-expansion place)
+    (let ((head (gensym)))
+      `(let* (,@(mapcar #'list dummies vals) 
+              (,head ,getter)
+              (,(car newval) (cdr ,head))
+              ,@(cdr newval)) 
+         ,setter
+         (car ,head)))))
+
+
+(defun map1 (func list)
+  (with-collect
+    (while list
+      (collect (funcall func (car list)))
+      (setq list (cdr list)))))
+
+(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))))))))
+
+(defun last (x)
+  (while (consp (cdr x))
+    (setq x (cdr x)))
+  x)
+
+(defun butlast (x)
+  (and (consp (cdr x))
+       (cons (car x) (butlast (cdr x)))))
+
+(defun member (x list)
+  (while list
+    (when (eql x (car list))
+      (return list))
+    (setq list (cdr list))))
+
+
+(defun assoc (x alist &key (test #'eql))
+  (while alist
+    (if (funcall test x (caar alist))
+        (return)
+        (setq alist (cdr alist))))
+  (car alist))
+
+
+
+(define-setf-expander car (x)
+  (let ((cons (gensym))
+        (new-value (gensym)))
+    (values (list cons)
+            (list x)
+            (list new-value)
+            `(progn (rplaca ,cons ,new-value) ,new-value)
+            `(car ,cons))))
+
+(define-setf-expander cdr (x)
+  (let ((cons (gensym))
+        (new-value (gensym)))
+    (values (list cons)
+            (list x)
+            (list new-value)
+            `(progn (rplacd ,cons ,new-value) ,new-value)
+            `(car ,cons))))
+
+
+;; 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)))
diff --git a/src/package.lisp b/src/package.lisp
new file mode 100644 (file)
index 0000000..7d54cc6
--- /dev/null
@@ -0,0 +1,151 @@
+;;; package.lisp ---
+
+;; JSCL is free software: you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of the
+;; License, or (at your option) any later version.
+;;
+;; JSCL is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
+
+(defvar *package-list* nil)
+
+(defun list-all-packages ()
+  *package-list*)
+
+(defun make-package (name &key use)
+  (let ((package (new))
+        (use (mapcar #'find-package-or-fail use)))
+    (oset package "packageName" name)
+    (oset package "symbols" (new))
+    (oset package "exports" (new))
+    (oset package "use" use)
+    (push package *package-list*)
+    package))
+
+(defun packagep (x)
+  (and (objectp x) (in "symbols" x)))
+
+(defun find-package (package-designator)
+  (when (packagep package-designator)
+    (return-from find-package package-designator))
+  (let ((name (string package-designator)))
+    (dolist (package *package-list*)
+      (when (string= (package-name package) name)
+        (return package)))))
+
+(defun find-package-or-fail (package-designator)
+  (or (find-package package-designator)
+      (error "Package unknown.")))
+
+(defun package-name (package-designator)
+  (let ((package (find-package-or-fail package-designator)))
+    (oget package "packageName")))
+
+(defun %package-symbols (package-designator)
+  (let ((package (find-package-or-fail package-designator)))
+    (oget package "symbols")))
+
+(defun package-use-list (package-designator)
+  (let ((package (find-package-or-fail package-designator)))
+    (oget package "use")))
+
+(defun %package-external-symbols (package-designator)
+  (let ((package (find-package-or-fail package-designator)))
+    (oget package "exports")))
+
+(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*)))
+
+(defvar *keyword-package*
+  (make-package "KEYWORD"))
+
+(defun keywordp (x)
+  (and (symbolp x) (eq (symbol-package x) *keyword-package*)))
+
+(defvar *package* *common-lisp-package*)
+
+(defmacro in-package (package-designator)
+  `(eval-when-compile
+     (setq *package* (find-package-or-fail ,package-designator))))
+
+;; This function is used internally to initialize the CL package
+;; with the symbols built during bootstrap.
+(defun %intern-symbol (symbol)
+  (let* ((package
+          (if (in "package" symbol)
+              (find-package-or-fail (oget symbol "package"))
+              *common-lisp-package*))
+         (symbols (%package-symbols package)))
+    (oset symbol "package" package)
+    (when (eq package *keyword-package*)
+      (oset symbol "value" symbol))
+    (oset symbols (symbol-name symbol) symbol)))
+
+(defun find-symbol (name &optional (package *package*))
+  (let* ((package (find-package-or-fail package))
+         (externals (%package-external-symbols package))
+         (symbols (%package-symbols package)))
+    (cond
+      ((in name externals)
+       (values (oget externals name) :external))
+      ((in name symbols)
+       (values (oget symbols name) :internal))
+      (t
+       (dolist (used (package-use-list package) (values nil nil))
+         (let ((exports (%package-external-symbols used)))
+           (when (in name exports)
+             (return (values (oget exports name) :inherit)))))))))
+
+(defun intern (name &optional (package *package*))
+  (let ((package (find-package-or-fail package)))
+    (multiple-value-bind (symbol foundp)
+        (find-symbol name package)
+      (if foundp
+          (values symbol foundp)
+          (let ((symbols (%package-symbols package)))
+            (oget symbols name)
+            (let ((symbol (make-symbol name)))
+              (oset symbol "package" package)
+              (when (eq package *keyword-package*)
+                (oset symbol "value" symbol)
+                (export (list symbol) package))
+              (when (eq package *js-package*)
+                (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)))))
+                  ;; 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)))))))
+
+(defun symbol-package (symbol)
+  (unless (symbolp symbol)
+    (error "it is not a symbol"))
+  (oget symbol "package"))
+
+(defun export (symbols &optional (package *package*))
+  (let ((exports (%package-external-symbols package)))
+    (dolist (symb symbols t)
+      (oset exports (symbol-name symb) symb))))
index 9a8d363..67b0427 100644 (file)
@@ -4,9 +4,7 @@
 var window = this;
 var nil;
 
-function globalEval (x) {
-    return eval.call (window, x);
-}
+globalEval = eval;  // Just an indirect eval
 
 function pv (x) { return x==undefined? nil: x; }
 
@@ -27,11 +25,11 @@ function forcemv (x) {
 var values = mv;
 
 function checkArgsAtLeast(args, n){
-    if (args.length < n) throw 'too few arguments';
+    if (args < n) throw 'too few arguments';
 }
 
 function checkArgsAtMost(args, n){
-    if (args.length > n) throw 'too many arguments';
+    if (args > n) throw 'too many arguments';
 }
 
 function checkArgs(args, n){
index 7453829..9db5354 100644 (file)
@@ -3,18 +3,18 @@
 ;; Copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Printer
 
 
 (defun write-to-string (form)
   (cond
-   ((symbolp form)
-    (multiple-value-bind (symbol foundp)
-       (find-symbol (symbol-name form) *package*)
-      (if (and foundp (eq symbol form))
-         (symbol-name form)
-       (let ((package (symbol-package form))
-             (name (symbol-name form)))
-         (concat (cond
-                  ((null package) "#")
-                  ((eq package (find-package "KEYWORD")) "")
-                  (t (package-name package)))
-                 ":" name)))))
-   ((integerp form) (integer-to-string form))
-   ((floatp form) (float-to-string form))
-   ((stringp form) (if *print-escape*
-                      (concat "\"" (escape-string form) "\"")
-                    form))
-   ((functionp form)
-    (let ((name (oget form "fname")))
-      (if name
-         (concat "#<FUNCTION " name ">")
-       (concat "#<FUNCTION>"))))
-   ((listp form)
-    (concat "("
-           (join-trailing (mapcar #'write-to-string (butlast form)) " ")
-           (let ((last (last form)))
-             (if (null (cdr last))
-                 (write-to-string (car last))
-               (concat (write-to-string (car last)) " . " (write-to-string (cdr last)))))
-           ")"))
-   ((arrayp form)
-    (concat "#" (if (zerop (length form))
-                   "()"
-                 (write-to-string (vector-to-list form)))))
-   ((packagep form)
-    (concat "#<PACKAGE " (package-name form) ">"))
-   (t
-    (concat "#<javascript object>"))))
+    ((symbolp form)
+     (multiple-value-bind (symbol foundp)
+         (find-symbol (symbol-name form) *package*)
+       (if (and foundp (eq symbol form))
+           (symbol-name form)
+           (let ((package (symbol-package form))
+                 (name (symbol-name form)))
+             (concat (cond
+                       ((null package) "#")
+                       ((eq package (find-package "KEYWORD")) "")
+                       (t (package-name package)))
+                     ":" name)))))
+    ((integerp form) (integer-to-string form))
+    ((floatp form) (float-to-string form))
+    ((characterp form)
+     (concat "#\\"
+             (case form
+               (#\newline "newline")
+               (#\space "space")
+               (otherwise (string form)))))
+    ((stringp form) (concat "\"" (escape-string form) "\""))
+    ((functionp form)
+     (let ((name (oget form "fname")))
+       (if name
+           (concat "#<FUNCTION " name ">")
+           (concat "#<FUNCTION>"))))
+    ((listp form)
+     (concat "("
+             (join-trailing (mapcar #'write-to-string (butlast form)) " ")
+             (let ((last (last form)))
+               (if (null (cdr last))
+                   (write-to-string (car last))
+                   (concat (write-to-string (car last)) " . " (write-to-string (cdr last)))))
+             ")"))
+    ((arrayp form)
+     (concat "#" (if (zerop (length form))
+                     "()"
+                     (write-to-string (vector-to-list form)))))
+    ((packagep form)
+     (concat "#<PACKAGE " (package-name form) ">"))
+    (t
+     (concat "#<javascript object>"))))
 
 (defun prin1-to-string (form)
   (let ((*print-escape* t))
index d513ef3..7478b37 100644 (file)
@@ -3,18 +3,18 @@
 ;; Copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 
 ;;;; Reader
       (setq ch (%peek-char stream)))
     string))
 
+(defun read-escaped-until (stream func)
+  (let ((string "")
+        (ch (%peek-char stream))
+        (multi-escape nil))
+    (while (and ch (or multi-escape (not (funcall func ch))))
+      (cond
+        ((char= ch #\|)
+         (if multi-escape
+             (setf multi-escape nil)
+             (setf multi-escape t)))
+        ((char= ch #\\)
+         (%read-char stream)
+         (setf ch (%peek-char stream))
+         (setf string (concat string "\\" (string ch))))
+        (t
+         (if multi-escape
+             (setf string (concat string "\\" (string ch)))
+             (setf string (concat string (string ch))))))
+      (%read-char stream)
+      (setf ch (%peek-char stream)))
+    string))
+
 (defun skip-whitespaces-and-comments (stream)
   (let (ch)
     (skip-whitespaces stream)
       ((char= ch #\))
        (%read-char stream)
        nil)
-      ((char= ch #\.)
-       (%read-char stream)
-       (prog1 (ls-read-1 stream)
-         (skip-whitespaces-and-comments stream)
-         (unless (char= (%read-char stream) #\))
-           (error "')' was expected."))))
       (t
-       (cons (ls-read-1 stream) (%read-list stream))))))
+       (let ((car (ls-read-1 stream)))
+         (skip-whitespaces-and-comments stream)
+         (cons car
+               (if (char= (%peek-char stream) #\.)
+                   (progn
+                     (%read-char stream)
+                     (if (terminalp (%peek-char stream))
+                         (ls-read-1 stream) ; Dotted pair notation
+                         (cons (let ((string (concat "." (read-escaped-until stream #'terminalp))))
+                                 (or (values (!parse-integer string nil))
+                                     (read-float string)
+                                     (read-symbol string)))
+                               (%read-list stream))))
+                   (%read-list stream))))))))
 
 (defun read-string (stream)
   (let ((string "")
             (concat (string (%read-char stream))
                     (read-until stream #'terminalp))))
        (cond
-         ((string= cname "space") (char-code #\space))
-         ((string= cname "tab") (char-code #\tab))
-         ((string= cname "newline") (char-code #\newline))
-         (t (char-code (char cname 0))))))
+         ((string= cname "space") #\space)
+         ((string= cname "tab") #\tab)
+         ((string= cname "newline") #\newline)
+         (t (char cname 0)))))
     (#\+
      (let ((feature (read-until stream #'terminalp)))
        (cond
          (t
           (error "Unknown reader form.")))))))
 
+(defun unescape (x)
+  (let ((result ""))
+    (dotimes (i (length x))
+      (unless (char= (char x i) #\\)
+        (setq result (concat result (string (char x i))))))
+    result))
+
+(defun escape-all (x)
+  (let ((result ""))
+    (dotimes (i (length x))
+      (setq result (concat result "\\"))
+      (setq result (concat result (string (char x i)))))
+    result))
+
+(defun string-upcase-noescaped (s)
+  (let ((result "")
+        (last-escape nil))
+    (dotimes (i (length s))
+      (let ((ch (char s i)))
+        (if last-escape
+           (progn
+              (setf last-escape nil)
+              (setf result (concat result (string ch))))
+            (if (char= ch #\\)
+                (setf last-escape t)
+                (setf result (concat result (string-upcase (string ch))))))))
+    result))
+
 ;;; Parse a string of the form NAME, PACKAGE:NAME or
 ;;; PACKAGE::NAME and return the name. If the string is of the
 ;;; form 1) or 3), but the symbol does not exist, it will be created
     (setq index 0)
     (while (and (< index size)
                 (not (char= (char string index) #\:)))
+      (when (char= (char string index) #\\)
+        (incf index))
       (incf index))
     (cond
       ;; No package prefix
        ;; Package prefix
        (if (zerop index)
            (setq package "KEYWORD")
-           (setq package (string-upcase (subseq string 0 index))))
+           (setq package (string-upcase-noescaped (subseq string 0 index))))
        (incf index)
        (when (char= (char string index) #\:)
          (setq internalp t)
          (incf index))
        (setq name (subseq string index))))
     ;; Canonalize symbol name and package
-    (when (not (eq package "JS"))
-      (setq name (string-upcase name)))
+    (setq name (if (equal package "JS")
+                   (setq name (unescape name))
+                   (setq name (string-upcase-noescaped 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"))
             (eq package (find-package "JS")))
         (intern name package)
-        (find-symbol name package))))
+        (multiple-value-bind (symbol external)
+            (find-symbol name package)
+          (if (eq external :external)
+              symbol
+              (error (concat "The symbol '" name "' is not external")))))))
+
+(defun read-integer (string)
+  (let ((sign 1)
+        (number nil)
+        (size (length string)))
+    (dotimes (i size)
+      (let ((elt (char string i)))
+        (cond
+          ((digit-char-p elt)
+           (setq number (+ (* (or number 0) 10) (digit-char-p elt))))
+          ((zerop i)
+           (case elt
+             (#\+ nil)
+             (#\- (setq sign -1))
+             (t (return-from read-integer))))
+          ((and (= i (1- size)) (char= elt #\.)) nil)
+          (t (return-from read-integer)))))
+    (and number (* sign number))))
 
 (defun read-float (string)
   (block nil
              (incf index)))
       (unless (< index size) (return))
       ;; Optional integer part
-      (let ((value (digit-char-p (char string index))))
-        (when value
-          (setq integer-part t)
-          (while (and (< index size)
-                      (setq value (digit-char-p (char string index))))
-            (setq number (+ (* number 10) value))
-            (incf index))))
+      (awhen (digit-char-p (char string index))
+        (setq integer-part t)
+        (while (and (< index size)
+                    (setq it (digit-char-p (char string index))))
+          (setq number (+ (* number 10) it))
+          (incf index)))
       (unless (< index size) (return))
       ;; Decimal point is mandatory if there's no integer part
       (unless (or integer-part (char= #\. (char string index))) (return))
       (when (char= #\. (char string index))
         (incf index)
         (unless (< index size) (return))
-        (let ((value (digit-char-p (char string index))))
-          (when value
-            (setq fractional-part t)
-            (while (and (< index size)
-                        (setq value (digit-char-p (char string index))))
-              (setq number (+ (* number 10) value))
-              (setq divisor (* divisor 10))
-              (incf index)))))
+        (awhen (digit-char-p (char string index))
+          (setq fractional-part t)
+          (while (and (< index size)
+                      (setq it (digit-char-p (char string index))))
+            (setq number (+ (* number 10) it))
+            (setq divisor (* divisor 10))
+            (incf index))))
       ;; Either left or right part of the dot must be present
       (unless (or integer-part fractional-part) (return))
       ;; Exponent is mandatory if there is no fractional part
             (incf index))))
       (unless (= index size) (return))
       ;; Everything went ok, we have a float
-      (/ (* sign (expt 10 (* exponent-sign exponent)) number) divisor))))
-
+      ;; XXX: Use FLOAT when implemented.
+      (/ (* sign (expt 10.0 (* exponent-sign exponent)) number) divisor))))
 
 (defun !parse-integer (string junk-allow)
   (block 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."))))
+    (if num
+        (values num index)
+        (error "junk detected."))))
 
 (defvar *eof* (gensym))
 (defun ls-read-1 (stream)
       ((char= ch #\#)
        (read-sharp stream))
       (t
-       (let ((string (read-until stream #'terminalp)))
-         (or (values (!parse-integer string nil))
+       (let ((string (read-escaped-until stream #'terminalp)))
+         (or (read-integer string)
              (read-float string)
              (read-symbol string)))))))
 
 
 (defun ls-read-from-string (string &optional (eof-error-p t) eof-value)
   (ls-read (make-string-stream string) eof-error-p eof-value))
+
+#+jscl
+(defun read-from-string (string &optional (eof-errorp t) eof-value)
+  (ls-read-from-string string eof-errorp eof-value))
index ba9b129..d6f8cb4 100644 (file)
@@ -3,18 +3,18 @@
 ;; Copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 
 (defun eval (x)
           *** **
           ** *
           * (car results)))
+  (unless (boundp '*)
+    ;; FIXME: Handle error
+    (setf * nil))
   (setf +++ ++
         ++ +
         + -)
   (values-list /))
 
 (export '(&body &key &optional &rest * ** *** *gensym-counter* *package* + ++
-          +++ - / // /// 1+ 1- < <= = = > >= and append apply aref
-          arrayp assoc atom block boundp butlast caar cadddr caddr
+          +++ - / // /// 1+ 1- < <= = = > >= and append apply aref arrayp
+          assoc atom block boundp butlast cadar caaar caadr cdaar cdadr
+          cddar caaaar caaadr caadar caaddr cadaar cadadr caddar cdaaar
+          cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr 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
+          copy-list copy-tree decf declaim declare 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 expt
           fdefinition find-package find-symbol first flet format fourth fset
-          funcall function functionp gensym get-setf-expansion
-          get-universal-time go identity if in-package incf integerp
-          intern keywordp labels lambda last length let let* list
+          funcall function functionp gensym get-internal-real-time
+          get-setf-expansion get-universal-time go identity if in-package
+          incf 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
+          nconc nil not nreconc nth nthcdr null numberp or otherwise
+          package-name package-use-list packagep parse-integer plusp pop
           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))
+          quote read-from-string remove remove-if remove-if-not return
+          return-from revappend reverse rplaca rplacd second set setf
+          setq some string string-upcase string= stringp subseq subst
+          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*)
 
    (ls-compile
     `(progn
        ,@(mapcar (lambda (s) `(%intern-symbol (%js-vref ,(cdr s))))
-                 *literal-symbols*)
-       (setq *literal-symbols* ',*literal-symbols*)
+                 *literal-table*)
+       (setq *literal-table* ',*literal-table*)
        (setq *variable-counter* ,*variable-counter*)
-       (setq *gensym-counter* ,*gensym-counter*)
-       (setq *block-counter* ,*block-counter*)))))
+       (setq *gensym-counter* ,*gensym-counter*)))))
 
 (eval-when-compile
   (toplevel-compilation
index 3cf0103..73fc551 100644 (file)
@@ -3,18 +3,18 @@
 ;; Copyright (C) 2012, 2013 David Vazquez
 ;; Copyright (C) 2012 Raimon Grau
 
-;; This program is free software: you can redistribute it and/or
+;; JSCL is free software: you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation, either version 3 of the
 ;; License, or (at your option) any later version.
 ;;
-;; This program is distributed in the hope that it will be useful, but
+;; JSCL is distributed in the hope that it will be useful, but
 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
 
 (defvar *newline* (string (code-char 10)))
 
 
 ;;; Concatenate a list of strings, with a separator
 (defun join (list &optional (separator ""))
-  (cond
-    ((null list)
-     "")
-    ((null (cdr list))
-     (car list))
-    (t
-     (concat (car list)
-             separator
-             (join (cdr list) separator)))))
+  (!reduce (lambda (s o) (concat s separator o))  
+           (cdr list) 
+           :initial-value (car list))) 
 
 (defun join-trailing (list &optional (separator ""))
   (if (null list)
diff --git a/tests-report.lisp b/tests-report.lisp
new file mode 100644 (file)
index 0000000..45c7f7c
--- /dev/null
@@ -0,0 +1,23 @@
+(write-line "")
+(write-string "Finished. The execution took ")
+(write-string (prin1-to-string (/ (- (get-internal-real-time) *timestamp*) internal-time-units-per-second 1.0)))
+(write-line " seconds.")
+
+(cond
+  ((= *passed-tests* *total-tests*)
+   (write-line "All the tests (")
+   (write-string (prin1-to-string *total-tests*))
+   (write-line ") passed successfully."))
+  (t
+   (write-string (prin1-to-string *passed-tests*))
+   (write-string "/")
+   (write-string (prin1-to-string *total-tests*))
+   (write-line " test(s) passed successfully.")))
+
+(unless (zerop *expected-failures*)
+  (write-string (prin1-to-string *expected-failures*))
+  (write-line " test(s) failed expectedly."))
+
+(unless (zerop *unexpected-passes*)
+  (write-string (prin1-to-string *unexpected-passes*))
+  (write-line " test(s) passed unexpectedly."))
diff --git a/tests.lisp b/tests.lisp
new file mode 100644 (file)
index 0000000..65b4ae2
--- /dev/null
@@ -0,0 +1,45 @@
+(defparameter *total-tests* 0)
+(defparameter *passed-tests* 0)
+(defparameter *failed-tests* 0)
+(defparameter *expected-failures* 0)
+(defparameter *unexpected-passes* 0)
+
+(defvar *use-html-output-p* t)
+(defun if-html (string) (if *use-html-output-p* string ""))
+
+(defvar *timestamp* nil)
+
+(defmacro test (condition)
+  `(progn
+     (cond
+       (,condition
+        (write-line ,(concat "Test `" (prin1-to-string condition) "' passed"))
+        (incf *passed-tests*))
+       (t
+        (write-line (concat (if-html "<font color=red>")
+                            "Test `"
+                            ,(prin1-to-string condition) 
+                            "' failed."
+                            (if-html "</font>")))
+        (incf *failed-tests*)))
+     (incf *total-tests*)))
+
+(defmacro expected-failure (condition)
+  `(progn
+     (cond
+       (,condition
+         (write-line (concat (if-html "<font color=orange>")
+                             "Test `"
+                             ,(prin1-to-string condition)
+                             "' passed unexpectedly!"
+                             (if-html "</font>")))
+        (incf *unexpected-passes*))
+       (t
+        (write-line ,(concat "Test `" (prin1-to-string condition) "' failed expectedly."))
+        (incf *expected-failures*)))
+     (incf *total-tests*)))
+
+(write-line "Running tests...")
+(write-line "")
+
+(setq *timestamp* (get-internal-real-time))
diff --git a/tests/conditionals.lisp b/tests/conditionals.lisp
new file mode 100644 (file)
index 0000000..af71104
--- /dev/null
@@ -0,0 +1,24 @@
+; Tests for conditional forms
+; Boolean operators
+(test (eql (and nil 1) nil))
+(test (=   (and 1   2)   2))
+
+(test (= (or  nil 1)   1))
+(test (= (or  1   2)   1))
+
+; COND
+(test (eql nil (cond)))
+(test (=   1   (cond (1))))
+(test (= 1
+         (let ((x 0))
+           (cond ((incf x))))))
+(test (=   2   (cond (1 2))))
+(test (=   3   (cond (nil 1) (2 3))))
+(test (eql nil (cond (nil 1) (nil 2))))
+
+; CASE
+
+(test (= (case 1 (2 3) (otherwise 42)) 42))
+(test (= (case 1 (2 3) (t 42)) 42))
+(test (= (case 1 (2 3) (1 42)) 42))
+(test (null (case 1 (2 3))))
diff --git a/tests/control.lisp b/tests/control.lisp
new file mode 100644 (file)
index 0000000..5e010e3
--- /dev/null
@@ -0,0 +1,44 @@
+
+;;; Returning from a "dynamically" nested non local exists
+
+(defun foo (x)
+  (when x (funcall x))
+  (foo (lambda () (return-from foo 1)))
+  (return-from foo 2))
+
+(test (= (foo nil) 1))
+
+(defun foo-2 (x)
+  (let (value)
+    (tagbody
+       (when x (funcall x))
+       (foo-2 (lambda () (go exit-2)))
+       (go end)
+     exit-2
+       (setq value t)
+     end)
+    value))
+
+(test (foo-2 nil))
+
+
+(test (equal (flet ((foo () (return-from foo 42)))
+               (foo))
+             42))
+
+(test (equal (let ((out (list)))
+               (labels ((zfoo (n rf i)
+                          (if (> n 0)
+                              (progn
+                                (push (lambda () (return-from zfoo n)) rf)
+                                (push n out)
+                                (zfoo (1- n) rf i)
+                                (push (- n) out))
+                              (progn
+                                (push 999 out)
+                                (funcall (nth i (reverse rf)))
+                                (push -999 out)))))
+                 (let ((rf (list)))
+                   (zfoo 5 rf 3)
+                   out)))
+             '(-5 -4 -3 999 1 2 3 4 5)))
diff --git a/tests/equal.lisp b/tests/equal.lisp
new file mode 100644 (file)
index 0000000..856a155
--- /dev/null
@@ -0,0 +1,5 @@
+(test (equal '(1 2) '(1 2)))
+(test (equal 1 1))
+(test (equal "abc" "abc"))
+(test (not (equal "abc" "def")))
+(test (not (equal "Abc" "abc")))
diff --git a/tests/iter-macros.lisp b/tests/iter-macros.lisp
new file mode 100644 (file)
index 0000000..a19fd16
--- /dev/null
@@ -0,0 +1,26 @@
+; Tests for macros implementing iteration constructs
+; DOTIMES
+(test (let ((total 0))
+        (dotimes (n 6)
+          (incf total n))
+        (= total 15)))
+
+; DOLIST
+(test (let ((total 0))
+        (dolist (n '(1 2 3 4 5))
+          (incf total n))
+        (= total 15)))
+
+; DO
+(test (do ((a 0 b)                                 
+           (b 1 (+ a b))                                                          
+           (n 0 (1+ n)))                                                          
+        ((= n 10) 
+         (= a 55))))
+
+; DO*
+(test (do* ((a 0 b)                                 
+            (b 1 (+ a b))                                                          
+            (n 0 (1+ n)))                                                          
+        ((= n 10) 
+         (= a 512))))
diff --git a/tests/list.lisp b/tests/list.lisp
new file mode 100644 (file)
index 0000000..3ff5515
--- /dev/null
@@ -0,0 +1,22 @@
+;; Tests for list functions
+
+;; TODO: EQUAL doesn't compare lists correctly at the moment.
+;; Once it does the lists can be compared directly in many of these tests
+
+; COPY-TREE
+(test (let* ((foo (list '(1 2) '(3 4)))
+             (bar (copy-tree foo)))
+        ;; (SETF (CAR (CAR FOO)) 0) doesn't work in the test for some reason,
+        ;; despite working fine in the REPL
+        (rplaca (car foo) 0)
+        (not (= (car (car foo))
+                (car (car bar))))))
+
+; SUBST
+; Can't really test this until EQUAL works properly on lists
+
+; POP
+(test (let* ((foo '(1 2 3))
+             (bar (pop foo)))
+        (and (= bar 1)
+             (= (car foo) 2))))
diff --git a/tests/read.lisp b/tests/read.lisp
new file mode 100644 (file)
index 0000000..d8cadf4
--- /dev/null
@@ -0,0 +1,15 @@
+;; TODO: Uncomment when either read-from-string supports all these parameters
+;; or when test macro supports error handling, whichever comes first
+;; (test (equal (read-from-string " 1 3 5" t nil :start 2) (values 3 5)))
+(expected-failure
+ (equal (multiple-value-list (read-from-string "(a b c)"))
+        '((A B C) 7)))
+
+(test (equal (symbol-name (read-from-string "js:alert")) "alert"))
+(test (equal (symbol-name (read-from-string "cl:cond")) "COND"))
+(test (equal (symbol-name (read-from-string "co|N|d")) "COND"))
+(test (equal (symbol-name (read-from-string "abc\\def")) "ABCdEF"))
+(test (equal (symbol-name (read-from-string "|.|")) "."))
+(test (equal (read-from-string "(1 .25)") '(1 0.25)))
+(test (equal (read-from-string ".25") 0.25))
+(test (equal (read-from-string "(1 . 25)") '(1 . 25)))
diff --git a/tests/tests-report.lisp b/tests/tests-report.lisp
deleted file mode 100644 (file)
index c1b9346..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-(write-line "")
-(write-string "Finished. The execution took ")
-(write-string (prin1-to-string (- (get-universal-time) *timestamp*)))
-(write-line " seconds.")
-
-(cond
-  ((zerop *failed-tets*)
-   (write-string "All tests (")
-   (write-string (prin1-to-string *passed-tets*))
-   (write-line ") passed successfully"))
-  (t
-   (write-string (prin1-to-string *failed-tets*))
-   (write-string "/")
-   (write-string (prin1-to-string (+ *passed-tets* *failed-tets*)))
-   (write-line " failed.")))
diff --git a/tests/tests.lisp b/tests/tests.lisp
deleted file mode 100644 (file)
index 2477bf6..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-(defvar *passed-tets* 0)
-(defvar *failed-tets* 0)
-(defvar *timestamp* (get-universal-time))
-
-(defmacro test (condition)
-  `(cond
-     (,condition
-      (write-line ,(concat "Test `" (prin1-to-string condition) "' passed"))
-      (incf *passed-tets*))
-     (t
-      (write-line ,(concat "Test `" (prin1-to-string condition) "' failed."))
-      (incf *failed-tets*))))
-
-(write-line "Running tests...")
-(write-line "")