concat-two primitive function offered
[jscl.git] / lispstrack.lisp
index 3b96c35..98f7bc0 100644 (file)
@@ -1,5 +1,26 @@
-;;; Library
-
+;;; lispstrack.lisp ---
+
+;; Copyright (C) 2012 David Vazquez
+;; Copyright (C) 2012 Raimon Grau
+
+;; This program 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
+;; 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/>.
+
+;;; This code is executed when lispstrack compiles this file
+;;; itself. The compiler provides compilation of some special forms,
+;;; as well as funcalls and macroexpansion, but no functions. So, we
+;;; define the Lisp world from scratch. This code has to define enough
+;;; language to the compiler to be able to run.
 #+lispstrack
 (progn
  (eval-when-compile
                         `(eval-when-compile
                            (%compile-defmacro ',name '(lambda ,args ,@body))))))
 
- (defmacro defvar (name value)
+ (defmacro %defvar (name value)
    `(progn
       (eval-when-compile
         (%compile-defvar ',name))
       (setq ,name ,value)))
 
- (defmacro defun (name args &rest body)
+  (defmacro defvar (name value)
+    `(%defvar ,name ,value))
+
+ (defmacro %defun (name args &rest body)
    `(progn
       (eval-when-compile
         (%compile-defun ',name))
       (fsetq ,name (lambda ,args ,@body))))
 
+  (defmacro defun (name args &rest body)
+    `(%defun ,name ,args ,@body))
+
  (defvar *package* (new))
 
  (defvar nil (make-symbol "NIL"))
 
 #+lispstrack
 (progn
+  (defmacro defun (name args &rest body)
+    `(progn
+       (%defun ,name ,args ,@body)
+       ',name))
+
+  (defmacro defvar (name value)
+    `(progn
+       (%defvar ,name ,value)
+       ',name))
+
   (defun append-two (list1 list2)
     (if (null list1)
         list2
         (string-length seq)
         (list-length seq)))
 
+  (defun concat-two (s1 s2)
+    (concat-two s1 s2))
+
   (defun mapcar (func list)
     (if (null list)
         '()
   (defun <= (x y) (or (< x y) (= x y)))
   (defun >= (x y) (not (< x y)))
 
+  (defun plusp (x) (< 0 x))
+  (defun minusp (x) (< x 0))
+
   (defun listp (x)
     (or (consp x) (null x)))
 
     (equal s1 s2)))
 
 
+;;; The compiler offers some primitives and special forms which are
+;;; not found in Common Lisp, for instance, while. So, we grow Common
+;;; Lisp a bit to it can execute the rest of the file.
 #+common-lisp
 (progn
   (defmacro while (condition &body body)
          ((not ,condition))
        ,@body))
 
+  (defmacro eval-when-compile (&body body)
+    `(eval-when (:compile-toplevel :load-toplevel :execute)
+       ,@body))
+
   (defun concat-two (s1 s2)
     (concatenate 'string s1 s2))
 
     (setf (cdr cons) new)))
 
 
+;;; At this point, no matter if Common Lisp or lispstrack is compiling
+;;; from here, this code will compile on both. We define some helper
+;;; functions now for string manipulation and so on. They will be
+;;; useful in the compiler, mostly.
+
 (defvar *newline* (string (code-char 10)))
 
 (defun concat (&rest strs)
-  (!reduce (lambda (s1 s2) (concat-two s1 s2))
-           strs
-           ""))
+  (!reduce #'concat-two strs ""))
 
 ;;; Concatenate a list of strings, with a separator
 (defun join (list separator)
                       digits)
               ""))))
 
+(defun print-to-string (form)
+  (cond
+    ((symbolp form) (symbol-name form))
+    ((integerp form) (integer-to-string form))
+    ((stringp form) (concat "\"" (escape-string form) "\""))
+    ((functionp form) (concat "#<FUNCTION>"))
+    ((listp form)
+     (concat "("
+             (join (mapcar #'print-to-string form)
+                   " ")
+             ")"))))
 
 ;;;; Reader
 
-;;; It is a basic Lisp reader. It does not use advanced stuff
-;;; intentionally, because we want to use it to bootstrap a simple
-;;; Lisp. The main entry point is the function `ls-read', which
-;;; accepts a strings as argument and return the Lisp expression.
+;;; The Lisp reader, parse strings and return Lisp objects. The main
+;;; entry points are `ls-read' and `ls-read-from-string'.
+
 (defun make-string-stream (string)
   (cons string 0))
 
 
 ;;;; Compiler
 
+;;; Translate the Lisp code to Javascript. It will compile the special
+;;; forms. Some primitive functions are compiled as special forms
+;;; too. The respective real functions are defined in the target (see
+;;; the beginning of this file) as well as some primitive functions.
+
 (defvar *compilation-unit-checks* '())
 
 (defvar *env* '())
 (defun mark-binding-as-declared (b)
   (setcar (cdddr b) t))
 
-
 (defvar *variable-counter* 0)
 (defun gvarname (symbol)
   (concat "v" (integer-to-string (incf *variable-counter*))))
 (defun lookup-function-translation (symbol env)
   (binding-translation (lookup-function symbol env)))
 
-
 (defvar *toplevel-compilations* nil)
 
 (defun %compile-defvar (name)
 (defun %compile-defmacro (name lambda)
   (push (make-binding name 'macro lambda t) *fenv*))
 
-
 (defvar *compilations* nil)
 
 (defun ls-compile-block (sexps env fenv)
   (join-trailing
-   (remove nil (mapcar (lambda (x)
-                         (ls-compile x env fenv))
-                       sexps))
-                 ";
-"))
+   (remove (lambda (x)
+             (or (null x)
+                 (and (stringp x)
+                      (zerop (length x)))))
+           (mapcar (lambda (x) (ls-compile x env fenv))  sexps))
+   (concat ";" *newline*)))
+
 (defmacro define-compilation (name args &rest body)
   ;; Creates a new primitive `name' with parameters args and
   ;; @body. The body can access to the local environment through the
            (ls-compile val env fenv)))
 
 ;;; Literals
-
 (defun escape-string (string)
   (let ((output "")
         (index 0)
     ((symbolp x)
      (lookup-function-translation x fenv))))
 
-#+common-lisp
-(defmacro eval-when-compile (&body body)
-  `(eval-when (:compile-toplevel :load-toplevel :execute)
-     ,@body))
-
 (define-compilation eval-when-compile (&rest body)
   (eval (cons 'progn body))
-  nil)
+  "")
 
 (defmacro define-transformation (name args form)
   `(define-compilation ,name ,args
   (compile-bool
    (concat "(" (ls-compile key env fenv) " in " (ls-compile object env fenv) ")")))
 
+(define-compilation functionp (x)
+  (compile-bool
+   (concat "(typeof " (ls-compile x env fenv) " == 'function')")))
+
 
 (defun macrop (x)
   (and (symbolp x) (eq (binding-type (lookup-function x *fenv*)) 'macro)))
   (setq *toplevel-compilations* nil)
   (let ((code (ls-compile sexp nil nil)))
     (prog1
-        (concat  #+common-lisp (concat "/* " (princ-to-string sexp) " */")
-                (join (mapcar (lambda (x) (concat x ";" *newline*))
+        (concat (join (mapcar (lambda (x) (concat x ";" *newline*))
                               *toplevel-compilations*)
                "")
                 code)
       (setq *toplevel-compilations* nil))))
 
-;;; ----------------------------------------------------------
+
+;;; Once we have the compiler, we define the runtime environment and
+;;; interactive development (eval), which works calling the compiler
+;;; and evaluating the Javascript result globally.
 
 #+lispstrack
 (progn
               (ls-compile-toplevel x nil nil))))
      (js-eval code)))
 
-
  ;; Set the initial global environment to be equal to the host global
  ;; environment at this point of the compilation.
  (eval-when-compile
  (js-eval
   (concat "var lisp = {};"
           "lisp.read = " (lookup-function-translation 'ls-read-from-string nil) ";" *newline*
+          "lisp.print = " (lookup-function-translation 'print-to-string nil) ";" *newline*
           "lisp.eval = " (lookup-function-translation 'eval nil) ";" *newline*
           "lisp.compile = " (lookup-function-translation 'ls-compile-toplevel nil) ";" *newline*
           "lisp.evalString = function(str){" *newline*
           "   return lisp.compile(lisp.read(str));" *newline*
           "}" *newline*)))
 
+
+;;; Finally, we provide a couple of functions to easily bootstrap
+;;; this. It just calls the compiler with this file as input.
+
 #+common-lisp
 (progn
   (defun read-whole-file (filename)