s/js:funcall/js::funcall/
[clinton/parenscript.git] / src / compiler.lisp
CommitLineData
e8fdcce7 1(in-package "PARENSCRIPT")
cc4f1551 2
72044f33 3;;; reserved symbols/literals
c88be949 4
b934060d
VS
5(defvar *ps-reserved-symbol-names*
6 (list "break" "case" "catch" "continue" "default" "delete" "do" "else"
7 "finally" "for" "function" "if" "in" "instanceof" "new" "return"
8 "switch" "this" "throw" "try" "typeof" "var" "void" "while" "with"
9 "abstract" "boolean" "byte" "char" "class" "const" "debugger" "double"
10 "enum" "export" "extends" "final" "float" "goto" "implements" "import"
11 "int" "interface" "long" "native" "package" "private" "protected"
12 "public" "short" "static" "super" "synchronized" "throws" "transient"
13 "volatile"))
c88be949 14
837bcc37 15(defun add-ps-reserved-symbol (name)
b934060d 16 (pushnew (symbol-name-to-js-string name) *ps-reserved-symbol-names* :test #'equalp))
c88be949 17
837bcc37 18(defun ps-reserved-symbol-p (symbol)
fc772f72
VS
19 (when (symbolp symbol)
20 (find (symbol-name-to-js-string symbol) *ps-reserved-symbol-names* :test #'equalp)))
8cf7de80 21
72044f33 22;;; special forms
f326f929 23
72044f33 24(defvar *ps-special-forms* (make-hash-table :test 'eq))
4577df1c 25
72044f33
VS
26(defun get-ps-special-form (name)
27 (gethash name *ps-special-forms*))
cc4f1551 28
4a987e2b 29(defmacro define-ps-special-form (name lambda-list &rest body)
4e6c3ba1
VS
30 `(setf (gethash ',name *ps-special-forms*)
31 (lambda (&rest whole)
32 (destructuring-bind ,lambda-list
33 whole
34 ,@body))))
9da682ca 35
72044f33
VS
36(defun undefine-ps-special-form (name)
37 (remhash name *ps-special-forms*))
38
39(defun ps-special-form-p (form)
40 (and (consp form)
41 (symbolp (car form))
42 (gethash (car form) *ps-special-forms*)))
43
44;;; scoping
45
e0032a96
VS
46(defvar *enclosing-lexical-block-declarations* ()
47 "This special variable is expected to be bound to a fresh list by
48special forms that introduce a new JavaScript lexical block (currently
49function definitions and lambdas). Enclosed special forms are expected
50to push variable declarations onto the list when the variables
83b5a0cc
TC
51declaration cannot be made by the enclosed form \(for example, a
52\(x,y,z\) expression progn\). It is then the responsibility of the
e0032a96
VS
53enclosing special form to introduce the variable bindings in its
54lexical block.")
55
58c4ef4f
VS
56(defvar *ps-special-variables* ())
57
5ffb1eba
VS
58(defun ps-special-variable-p (sym)
59 (member sym *ps-special-variables*))
60
72044f33 61;;; form predicates
4a987e2b 62
a14fb2cb
VS
63(defun comparison-form-p (form)
64 (member (car form) '(< > <= >= == != === !==)))
65
4a987e2b
VS
66(defun op-form-p (form)
67 (and (listp form)
68 (not (ps-special-form-p form))
69 (not (null (op-precedence (first form))))))
cc4f1551 70
11cba1a7
CE
71(defun method-call-form-p (form)
72 (and (listp form)
73 (symbolp (car form))
74 (char= #\. (char (symbol-name (car form)) 0))))
75
9da682ca 76(defun funcall-form-p (form)
5a69278c
VS
77 (and form
78 (listp form)
4a987e2b
VS
79 (not (op-form-p form))
80 (not (ps-special-form-p form))))
cc4f1551 81
9da682ca 82;;; macro expansion
cc4f1551 83(eval-when (:compile-toplevel :load-toplevel :execute)
5a69278c 84 (defun make-macro-dictionary ()
72044f33 85 (make-hash-table :test 'eq))
5a69278c
VS
86
87 (defvar *ps-macro-toplevel* (make-macro-dictionary)
88 "Toplevel macro environment dictionary.")
8877a380 89
462ca010 90 (defvar *ps-macro-env* (list *ps-macro-toplevel*)
171bbab3 91 "Current macro environment.")
72332f2a 92
5a69278c
VS
93 (defvar *ps-symbol-macro-toplevel* (make-macro-dictionary))
94
95 (defvar *ps-symbol-macro-env* (list *ps-symbol-macro-toplevel*))
96
97 (defvar *ps-local-function-names* ())
98
99 (defvar *ps-setf-expanders* (make-macro-dictionary)
72332f2a
VS
100 "Setf expander dictionary. Key is the symbol of the access
101function of the place, value is an expansion function that takes the
102arguments of the access functions as a first value and the form to be
103stored as the second value.")
8877a380 104
5a69278c 105 (defparameter *ps-compilation-level* :toplevel
8877a380
VS
106 "This value takes on the following values:
107:toplevel indicates that we are traversing toplevel forms.
4e6c3ba1 108:inside-toplevel-form indicates that we are inside a call to ps-compile-*
5a69278c
VS
109nil indicates we are no longer toplevel-related."))
110
111(defun lookup-macro-def (name env)
112 (loop for e in env thereis (gethash name e)))
cc4f1551 113
8cfc6fe9
VS
114(defun make-ps-macro-function (args body)
115 (let* ((whole-var (when (eql '&whole (first args)) (second args)))
116 (effective-lambda-list (if whole-var (cddr args) args))
117 (whole-arg (or whole-var (gensym "ps-macro-form-arg-"))))
118 `(lambda (,whole-arg)
119 (destructuring-bind ,effective-lambda-list
120 (cdr ,whole-arg)
121 ,@body))))
d9fc64c9 122
4a987e2b 123(defmacro defpsmacro (name args &body body)
8cfc6fe9 124 `(progn (undefine-ps-special-form ',name)
5a69278c 125 (setf (gethash ',name *ps-macro-toplevel*) ,(make-ps-macro-function args body))
8cfc6fe9 126 ',name))
cc4f1551 127
8cfc6fe9 128(defmacro define-ps-symbol-macro (symbol expansion)
fb469285
VS
129 (let ((x (gensym)))
130 `(progn (undefine-ps-special-form ',symbol)
5a69278c 131 (setf (gethash ',symbol *ps-symbol-macro-toplevel*) (lambda (,x) (declare (ignore ,x)) ',expansion))
fb469285 132 ',symbol)))
b5369cb1 133
7590646c
VS
134(defun import-macros-from-lisp (&rest names)
135 "Import the named Lisp macros into the ParenScript macro
136environment. When the imported macro is macroexpanded by ParenScript,
137it is first fully macroexpanded in the Lisp macro environment, and
138then that expansion is further expanded by ParenScript."
139 (dolist (name names)
8cfc6fe9
VS
140 (eval `(defpsmacro ,name (&rest args)
141 (macroexpand `(,',name ,@args))))))
7590646c 142
f016e033 143(defmacro defmacro/ps (name args &body body)
7590646c
VS
144 "Define a Lisp macro and import it into the ParenScript macro environment."
145 `(progn (defmacro ,name ,args ,@body)
8cfc6fe9 146 (import-macros-from-lisp ',name)))
7590646c 147
f016e033 148(defmacro defmacro+ps (name args &body body)
8cfc6fe9
VS
149 "Define a Lisp macro and a ParenScript macro with the same macro
150function (ie - the same result from macroexpand-1), for cases when the
151two have different full macroexpansions (for example if the CL macro
152contains implementation-specific code when macroexpanded fully in the
153CL environment)."
7590646c 154 `(progn (defmacro ,name ,args ,@body)
4a987e2b
VS
155 (defpsmacro ,name ,args ,@body)))
156
157(defun ps-macroexpand (form)
62baa0d8 158 (aif (or (and (symbolp form) (lookup-macro-def form *ps-symbol-macro-env*))
5a69278c
VS
159 (and (consp form) (lookup-macro-def (car form) *ps-macro-env*)))
160 (values (ps-macroexpand (funcall it form)) t)
161 form))
162
163(defun maybe-rename-local-function (fun-name)
164 (aif (lookup-macro-def fun-name *ps-local-function-names*)
165 it
166 fun-name))
4a987e2b
VS
167
168;;;; compiler interface
5a69278c 169(defun adjust-ps-compilation-level (form level)
0f5e99ff
RD
170 "Given the current *ps-compilation-level*, LEVEL, and the fully macroexpanded
171form, FORM, returns the new value for *ps-compilation-level*."
172 (cond ((or (and (consp form) (member (car form)
173 '(progn locally macrolet symbol-macrolet compile-file)))
174 (and (symbolp form) (eq :toplevel level)))
175 level)
176 ((eq :toplevel level) :inside-toplevel-form)))
177
8877a380 178
6f79326b 179(defun ps-compile-symbol (form)
4a987e2b
VS
180 "Compiles the given Parenscript form and guarantees that the
181resultant symbol has an associated script-package. Raises an error if
182the form cannot be compiled to a symbol."
4e6c3ba1 183 (let ((exp (ps-compile-expression form)))
0ce67a33 184 (when (eq (first exp) 'js:variable)
4a987e2b
VS
185 (setf exp (second exp)))
186 (assert (symbolp exp) ()
187 "~a is expected to be a symbol, but compiles to ~a (the ParenScript output for ~a alone is \"~a\"). This could be due to ~a being a special form." form exp form (ps* form) form)
188 exp))
189
4e6c3ba1 190(defmethod ps-compile (form)
4a987e2b
VS
191 (error "The object ~S cannot be compiled by ParenScript." form))
192
4e6c3ba1 193(defmethod ps-compile ((form number))
4a987e2b
VS
194 form)
195
4e6c3ba1 196(defmethod ps-compile ((form string))
4a987e2b
VS
197 form)
198
4e6c3ba1
VS
199(defmethod ps-compile ((form character))
200 (ps-compile (string form)))
4a987e2b 201
0c804d25
CE
202(defun compound-symbol-p (symbol)
203 (let ((split (split-sequence:split-sequence #\. (symbol-name symbol))))
0c804d25
CE
204 (if (cdr split)
205 (reduce
206 (lambda (&optional slot-name object-exp)
207 `(js:slot-value ,object-exp ,(make-symbol slot-name)))
208 (reverse (cddr split))
209 :initial-value `(slot-value
210 (js:variable ,(ps-macroexpand
211 (intern (car split)
212 (symbol-package symbol))))
213 ,(make-symbol (cadr split)))
214 :from-end t)
215 nil)))
216
4e6c3ba1 217(defmethod ps-compile ((symbol symbol))
87857891
VS
218 (multiple-value-bind (expansion expanded?)
219 (ps-macroexpand symbol)
220 (if expanded?
221 (ps-compile expansion)
222 (cond ((keywordp symbol) symbol)
223 ((ps-special-form-p (list symbol))
224 (if (ps-reserved-symbol-p symbol)
225 (funcall (get-ps-special-form symbol))
226 (error "Attempting to use Parenscript special form ~a as variable" symbol)))
c2c51a3d
CE
227 (t (aif (compound-symbol-p symbol)
228 it
229 `(js:variable ,symbol)))))))
4a987e2b 230
a14fb2cb
VS
231;;; operators
232
4a56eb79
VS
233(let ((precedence-table (make-hash-table :test 'eq)))
234 (loop for level in '((js:new js:slot-value js:aref)
235 (postfix++ postfix--)
236 (delete void typeof ++ -- unary+ unary- ~ !)
237 (* / %)
238 (+ -)
239 (<< >> >>>)
240 (< > <= >= js:instanceof js:in)
241 (== != === !==)
242 (&)
243 (^)
244 (\|)
245 (\&\& and)
246 (\|\| or)
247 (js:?)
248 (= *= /= %= += -= <<= >>= >>>= \&\= ^= \|=)
249 (comma))
250 for i from 0
251 do (mapcar (lambda (symbol)
252 (setf (gethash symbol precedence-table) i))
253 level))
254 (defun op-precedence (op)
255 (gethash op precedence-table)))
a14fb2cb 256
3b16a7f3 257(defun ps-convert-op-name (op)
b39a6394 258 (case op
3b16a7f3
TC
259 (and '\&\&)
260 (or '\|\|)
261 (not '!)
262 (eql '\=\=)
263 (= '\=\=)
4e6c3ba1 264 (t op)))
3b16a7f3 265
a14fb2cb
VS
266(defun maybe-fix-nary-comparison-form (form)
267 (if (< 2 (length (cdr form)))
268 (values
269 (let* ((operator (car form))
270 (tmp-var-forms (butlast (cddr form)))
271 (tmp-vars (loop repeat (length tmp-var-forms)
272 collect (ps-gensym "_cmp")))
273 (all-comparisons (append (list (cadr form))
274 tmp-vars
275 (last form))))
276 `(let ,(mapcar #'list tmp-vars tmp-var-forms)
277 (and ,@(loop for x1 in all-comparisons
278 for x2 in (cdr all-comparisons)
279 collect (list operator x1 x2)))))
280 t)
281 form))
282
283(defun compile-op-form (form)
6f79326b 284 `(js:operator ,(ps-convert-op-name (ps-compile-symbol (car form)))
a14fb2cb 285 ,@(mapcar (lambda (form)
4e6c3ba1 286 (ps-compile-expression (ps-macroexpand form)))
a14fb2cb
VS
287 (cdr form))))
288
11cba1a7
CE
289(defun compile-method-call-form (form)
290 (compile-funcall-form
291 `((js:slot-value ,(second form)
292 ',(make-symbol (subseq (symbol-name (first form)) 1)))
293 ,@(cddr form))))
294
c2c51a3d
CE
295(defun function-name->js-expression (name)
296 (aif (compound-symbol-p name)
297 it
298 `(js:variable ,(maybe-rename-local-function name))))
299
a14fb2cb 300(defun compile-funcall-form (form)
1e8ddaee 301 `(js::funcall
87857891 302 ,(if (symbolp (car form))
c2c51a3d 303 (function-name->js-expression (car form))
87857891 304 (ps-compile-expression (ps-macroexpand (car form))))
4e6c3ba1
VS
305 ,@(mapcar #'ps-compile-expression (cdr form))))
306
307(defvar compile-expression?)
308
309(defmethod ps-compile ((form cons))
5a69278c
VS
310 (multiple-value-bind (form expanded-p)
311 (ps-macroexpand form)
a14fb2cb
VS
312 (let ((*ps-compilation-level*
313 (if expanded-p
314 *ps-compilation-level*
315 (adjust-ps-compilation-level form *ps-compilation-level*))))
316 (cond (expanded-p
4e6c3ba1 317 (ps-compile form))
a14fb2cb 318 ((ps-special-form-p form)
4e6c3ba1 319 (apply (get-ps-special-form (car form)) (cdr form)))
a14fb2cb
VS
320 ((comparison-form-p form)
321 (multiple-value-bind (form fixed?)
322 (maybe-fix-nary-comparison-form form)
323 (if fixed?
4e6c3ba1 324 (ps-compile form)
a14fb2cb 325 (compile-op-form form))))
4e6c3ba1
VS
326 ((op-form-p form)
327 (compile-op-form form))
11cba1a7
CE
328 ((method-call-form-p form)
329 (compile-method-call-form form))
4e6c3ba1
VS
330 ((funcall-form-p form)
331 (compile-funcall-form form))
11cba1a7 332 (t (error "Cannot compile ~S to a ParenScript form." form))))))
cc4f1551 333
4e6c3ba1
VS
334(defun ps-compile-statement (form)
335 (let ((compile-expression? nil))
336 (ps-compile form)))
337
338(defun ps-compile-expression (form)
339 (let ((compile-expression? t))
340 (ps-compile form)))
341
18dd299a
VS
342(defvar *ps-gensym-counter* 0)
343
344(defun ps-gensym (&optional (prefix "_js"))
5ffb1eba
VS
345 (let ((prefix (if (stringp prefix) prefix (symbol-to-js-string prefix nil))))
346 (make-symbol (format nil "~A~:[~;_~]~A" prefix
347 (digit-char-p (char prefix (1- (length prefix))))
348 (incf *ps-gensym-counter*)))))
18dd299a
VS
349
350(defmacro with-ps-gensyms (symbols &body body)
351 "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers.
352
353Each element of SYMBOLS is either a symbol or a list of (symbol
354gensym-prefix-string)."
355 `(let* ,(mapcar (lambda (symbol)
356 (destructuring-bind (symbol &optional prefix)
357 (if (consp symbol)
358 symbol
359 (list symbol))
360 (if prefix
361 `(,symbol (ps-gensym ,prefix))
6274a448 362 `(,symbol (ps-gensym ,(symbol-to-js-string symbol))))))
18dd299a
VS
363 symbols)
364 ,@body))
6ae06336
TC
365
366(defun %check-once-only-vars (vars)
367 (let ((bad-var (find-if (lambda (x) (or (not (symbolp x)) (keywordp x))) vars)))
368 (when bad-var
369 (error "PS-ONLY-ONCE expected a non-keyword symbol but got ~s" bad-var))))
370
371(defmacro ps-once-only ((&rest vars) &body body)
372 (%check-once-only-vars vars)
373 (let ((gensyms (mapcar (lambda (x) (ps-gensym (string x))) vars)))
374 `(let ,(mapcar (lambda (g v) `(,g (ps-gensym ,(string v)))) gensyms vars)
375 `(let* (,,@(mapcar (lambda (g v) ``(,,g ,,v)) gensyms vars))
376 ,(let ,(mapcar (lambda (g v) `(,v ,g)) gensyms vars)
377 ,@body)))))