Conses are working
[jscl.git] / lispstrack.lisp
1 ;;; Utils
2
3 (defmacro while (condition &body body)
4   `(do ()
5        ((not ,condition))
6      ,@body))
7
8 ;;; simplify me, please
9 (defun concat (&rest strs)
10   (reduce (lambda (s1 s2) (concatenate 'string s1 s2))
11           strs
12           :initial-value ""))
13
14
15 (let ((counter 0))
16   (defun make-var-binding (symbol)
17     (cons symbol (format nil "v~d" (incf counter)))))
18
19 (let ((counter 0))
20   (defun make-func-binding (symbol)
21     (cons symbol (format nil "f~d" (incf counter)))))
22
23
24 ;;; Concatenate a list of strings, with a separator
25 (defun join (list separator)
26   (cond
27     ((null list)
28      "")
29     ((null (cdr list))
30      (car list))
31     (t
32      (concat (car list)
33              separator
34              (join (cdr list) separator)))))
35
36 ;;; Compiler
37
38 (defvar *compilations* nil)
39
40 (defun ls-compile-block (sexps env fenv)
41   (concat (join (mapcar (lambda (x)
42                           (concat (ls-compile x env fenv) ";"))
43                         sexps)
44                 ";
45 ")))
46
47 (defun extend-env (args env)
48   (append (mapcar #'make-var-binding args) env))
49
50 (defparameter *env* '())
51 (defparameter *fenv* '())
52
53 (defun ls-lookup (symbol env)
54   (let ((binding (assoc symbol env)))
55     (and binding (format nil "~a" (cdr binding)))))
56
57 (defun lookup-variable (symbol env)
58   (or (ls-lookup symbol env)
59       (ls-lookup symbol *env*)
60       (error "Undefined variable `~a'"  symbol)))
61
62 (defun lookup-function (symbol env)
63   (or (ls-lookup symbol env)
64       (ls-lookup symbol *fenv*)
65       (error "Undefined function `~a'"  symbol)))
66
67 (defmacro define-compilation (name args &body body)
68   ;; Creates a new primitive `name' with parameters args and
69   ;; @body. The body can access to the local environment through the
70   ;; variable ENV.
71   `(push (list ',name (lambda (env fenv ,@args) ,@body))
72          *compilations*))
73
74 (define-compilation if (condition true false)
75   (format nil "((~a)? (~a) : (~a))"
76           (ls-compile condition env fenv)
77           (ls-compile true env fenv)
78           (ls-compile false env fenv)))
79
80 ;;; Return the required args of a lambda list
81 (defun lambda-list-required-argument (lambda-list)
82   (if (or (null lambda-list) (eq (car lambda-list) '&rest))
83       nil
84       (cons (car lambda-list) (lambda-list-required-argument (cdr lambda-list)))))
85
86 (defun lambda-list-rest-argument (lambda-list)
87   (second (member '&rest lambda-list)))
88
89 (define-compilation lambda (lambda-list &rest body)
90   (let ((required-arguments (lambda-list-required-argument lambda-list))
91         (rest-argument (lambda-list-rest-argument lambda-list)))
92     (let ((new-env (extend-env (cons rest-argument required-arguments) env)))
93       (concat "(function ("
94               (join (mapcar (lambda (x) (lookup-variable x new-env))
95                             required-arguments)
96                     ",")
97               "){
98 "
99               (if rest-argument
100                   (concat "var " (lookup-variable rest-argument new-env)
101                           " = arguments.slice("
102                           (prin1-to-string (length required-arguments)) ");
103 ")
104                   "")
105
106               (concat (ls-compile-block (butlast body) new-env fenv)
107                       "return " (ls-compile (car (last body)) new-env fenv) ";")
108               "
109 })"))))
110
111 (define-compilation fsetq (var val)
112   (format nil "~a = ~a" (lookup-function var fenv) (ls-compile val env fenv)))
113
114 (define-compilation setq (var val)
115   (format nil "~a = ~a" (lookup-variable var env) (ls-compile val env fenv)))
116
117
118 ;;; Literals
119
120 (defvar *literals* '())
121
122 (defun literal->js (sexp)
123   (cond
124     ((null sexp) "undefined")
125     ((integerp sexp) (format nil "~a" sexp))
126     ((stringp sexp) (format nil "\"~a\"" sexp))
127     ((consp sexp) (concat "{car: "
128                           (literal->js (car sexp))
129                           ", cdr: "
130                           (literal->js (cdr sexp)) "}"))))
131
132 (let ((counter 0))
133   (defun literal (form)
134     (let ((var (format nil "l~d" (incf counter))))
135       (push (cons var (literal->js form)) *literals*)
136       var)))
137
138 (define-compilation quote (sexp)
139   (literal sexp))
140
141 (define-compilation debug (form)
142   (format nil "console.log(~a)" (ls-compile form env fenv)))
143
144 (define-compilation while (pred &rest body)
145   (format nil "(function(){while(~a){~a}})() "
146           (ls-compile pred env fenv)
147           (ls-compile-block body env fenv)))
148
149 (defmacro eval-when-compile (&body body)
150   `(eval-when (:compile-toplevel :execute)
151      ,@body))
152
153 (defvar *eval-when-compilations*)
154 (define-compilation eval-when-compile (&rest body)
155   (setq *eval-when-compilations* "")
156   (eval (cons 'progn body))
157   (if (string= *eval-when-compilations* "")
158       nil
159       *eval-when-compilations*))
160
161 ;;; aritmetic primitives
162 (define-compilation + (x y)
163   (concat "((" (ls-compile x env fenv) ") + (" (ls-compile y env fenv) "))"))
164
165 (define-compilation - (x y)
166   (concat "((" (ls-compile x env fenv) ") - (" (ls-compile y env fenv) "))"))
167
168 (define-compilation * (x y)
169   (concat "((" (ls-compile x env fenv) ") * (" (ls-compile y env fenv) "))"))
170
171 (define-compilation / (x y)
172   (concat "((" (ls-compile x env fenv) ") / (" (ls-compile y env fenv) "))"))
173
174 (define-compilation = (x y)
175   (concat "((" (ls-compile x env fenv) ") == (" (ls-compile y env fenv) "))"))
176
177 (define-compilation cons (x y)
178   (concat "{car: " (ls-compile x env fenv) ", cdr: " (ls-compile y env fenv) "}"))
179
180 (define-compilation car (x)
181   (concat "(" (ls-compile x env fenv) ").car"))
182
183 (define-compilation cdr (x)
184   (concat "(" (ls-compile x env fenv) ").cdr"))
185
186 (defmacro with-eval-when-compilation (&body body)
187   `(setq *eval-when-compilations*
188          (concat *eval-when-compilations* (progn ,@body))))
189
190 (defun %compile-defvar (name)
191   (push (make-var-binding name) *env*)
192   (with-eval-when-compilation
193     (format nil "var ~a" (lookup-variable name *env*))))
194
195 (defun %compile-defun (name)
196   (push (make-func-binding name) *fenv*)
197   (with-eval-when-compilation
198     (format nil "var ~a" (lookup-variable name *fenv*))))
199
200 (defun %compile-defmacro (name lambda)
201   (push (cons name (cons 'macro lambda)) *fenv*))
202
203 (defun compile-funcall (name args env fenv)
204   (format nil "~a(~{~a~^, ~})"
205           (lookup-function name fenv)
206           (mapcar (lambda (x) (ls-compile x env fenv)) args)))
207
208 (defun ls-macroexpand-1 (form &optional env fenv)
209   (let ((function (cdr (assoc (car form) *fenv*))))
210     (if (and (listp function) (eq (car function) 'macro))
211         (apply (eval (cdr function)) (cdr form))
212         form)))
213
214 (defun ls-compile (sexp &optional env fenv)
215   (cond
216     ((symbolp sexp) (lookup-variable sexp env))
217     ((integerp sexp) (format nil "~a" sexp))
218     ((stringp sexp) (format nil "\"~a\"" sexp))
219     ((listp sexp)
220      (let ((sexp (ls-macroexpand-1 sexp env fenv)))
221        (let ((compiler-func (second (assoc (car sexp) *compilations*))))
222          (if compiler-func
223              (apply compiler-func env fenv (cdr sexp))
224              (compile-funcall (car sexp) (cdr sexp) env fenv)))))))
225
226 (defun ls-compile-toplevel (sexp)
227   (setq *literals* nil)
228   (let ((code (ls-compile sexp)))
229     (prog1
230         (concat (join (mapcar (lambda (lit)
231                                 (concat "var " (car lit) " = " (cdr lit) ";
232 "))
233                               *literals*)
234                       "")
235                 code)
236       (setq *literals* nil))))
237
238
239 (defun ls-compile-file (filename output)
240   (with-open-file (in filename)
241     (with-open-file (out output :direction :output :if-exists :supersede)
242       (loop
243          for x = (read in nil) while x
244          for compilation = (ls-compile-toplevel x)
245          when compilation do (write-line (concat compilation "; ") out)))))
246
247
248 ;;; Testing
249
250 (defun compile-test ()
251   (ls-compile-file "test.lisp" "test.js"))