Changed op-precedence back to a memoized table (performance tweaks).
[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
9da682ca 71(defun funcall-form-p (form)
5a69278c
VS
72 (and form
73 (listp form)
4a987e2b
VS
74 (not (op-form-p form))
75 (not (ps-special-form-p form))))
cc4f1551 76
9da682ca 77;;; macro expansion
cc4f1551 78(eval-when (:compile-toplevel :load-toplevel :execute)
5a69278c 79 (defun make-macro-dictionary ()
72044f33 80 (make-hash-table :test 'eq))
5a69278c
VS
81
82 (defvar *ps-macro-toplevel* (make-macro-dictionary)
83 "Toplevel macro environment dictionary.")
8877a380 84
462ca010 85 (defvar *ps-macro-env* (list *ps-macro-toplevel*)
171bbab3 86 "Current macro environment.")
72332f2a 87
5a69278c
VS
88 (defvar *ps-symbol-macro-toplevel* (make-macro-dictionary))
89
90 (defvar *ps-symbol-macro-env* (list *ps-symbol-macro-toplevel*))
91
92 (defvar *ps-local-function-names* ())
93
94 (defvar *ps-setf-expanders* (make-macro-dictionary)
72332f2a
VS
95 "Setf expander dictionary. Key is the symbol of the access
96function of the place, value is an expansion function that takes the
97arguments of the access functions as a first value and the form to be
98stored as the second value.")
8877a380 99
5a69278c 100 (defparameter *ps-compilation-level* :toplevel
8877a380
VS
101 "This value takes on the following values:
102:toplevel indicates that we are traversing toplevel forms.
4e6c3ba1 103:inside-toplevel-form indicates that we are inside a call to ps-compile-*
5a69278c
VS
104nil indicates we are no longer toplevel-related."))
105
106(defun lookup-macro-def (name env)
107 (loop for e in env thereis (gethash name e)))
cc4f1551 108
8cfc6fe9
VS
109(defun make-ps-macro-function (args body)
110 (let* ((whole-var (when (eql '&whole (first args)) (second args)))
111 (effective-lambda-list (if whole-var (cddr args) args))
112 (whole-arg (or whole-var (gensym "ps-macro-form-arg-"))))
113 `(lambda (,whole-arg)
114 (destructuring-bind ,effective-lambda-list
115 (cdr ,whole-arg)
116 ,@body))))
d9fc64c9 117
4a987e2b 118(defmacro defpsmacro (name args &body body)
8cfc6fe9 119 `(progn (undefine-ps-special-form ',name)
5a69278c 120 (setf (gethash ',name *ps-macro-toplevel*) ,(make-ps-macro-function args body))
8cfc6fe9 121 ',name))
cc4f1551 122
8cfc6fe9 123(defmacro define-ps-symbol-macro (symbol expansion)
fb469285
VS
124 (let ((x (gensym)))
125 `(progn (undefine-ps-special-form ',symbol)
5a69278c 126 (setf (gethash ',symbol *ps-symbol-macro-toplevel*) (lambda (,x) (declare (ignore ,x)) ',expansion))
fb469285 127 ',symbol)))
b5369cb1 128
7590646c
VS
129(defun import-macros-from-lisp (&rest names)
130 "Import the named Lisp macros into the ParenScript macro
131environment. When the imported macro is macroexpanded by ParenScript,
132it is first fully macroexpanded in the Lisp macro environment, and
133then that expansion is further expanded by ParenScript."
134 (dolist (name names)
8cfc6fe9
VS
135 (eval `(defpsmacro ,name (&rest args)
136 (macroexpand `(,',name ,@args))))))
7590646c 137
f016e033 138(defmacro defmacro/ps (name args &body body)
7590646c
VS
139 "Define a Lisp macro and import it into the ParenScript macro environment."
140 `(progn (defmacro ,name ,args ,@body)
8cfc6fe9 141 (import-macros-from-lisp ',name)))
7590646c 142
f016e033 143(defmacro defmacro+ps (name args &body body)
8cfc6fe9
VS
144 "Define a Lisp macro and a ParenScript macro with the same macro
145function (ie - the same result from macroexpand-1), for cases when the
146two have different full macroexpansions (for example if the CL macro
147contains implementation-specific code when macroexpanded fully in the
148CL environment)."
7590646c 149 `(progn (defmacro ,name ,args ,@body)
4a987e2b
VS
150 (defpsmacro ,name ,args ,@body)))
151
152(defun ps-macroexpand (form)
62baa0d8 153 (aif (or (and (symbolp form) (lookup-macro-def form *ps-symbol-macro-env*))
5a69278c
VS
154 (and (consp form) (lookup-macro-def (car form) *ps-macro-env*)))
155 (values (ps-macroexpand (funcall it form)) t)
156 form))
157
158(defun maybe-rename-local-function (fun-name)
159 (aif (lookup-macro-def fun-name *ps-local-function-names*)
160 it
161 fun-name))
4a987e2b
VS
162
163;;;; compiler interface
5a69278c 164(defun adjust-ps-compilation-level (form level)
0f5e99ff
RD
165 "Given the current *ps-compilation-level*, LEVEL, and the fully macroexpanded
166form, FORM, returns the new value for *ps-compilation-level*."
167 (cond ((or (and (consp form) (member (car form)
168 '(progn locally macrolet symbol-macrolet compile-file)))
169 (and (symbolp form) (eq :toplevel level)))
170 level)
171 ((eq :toplevel level) :inside-toplevel-form)))
172
8877a380 173
6f79326b 174(defun ps-compile-symbol (form)
4a987e2b
VS
175 "Compiles the given Parenscript form and guarantees that the
176resultant symbol has an associated script-package. Raises an error if
177the form cannot be compiled to a symbol."
4e6c3ba1 178 (let ((exp (ps-compile-expression form)))
0ce67a33 179 (when (eq (first exp) 'js:variable)
4a987e2b
VS
180 (setf exp (second exp)))
181 (assert (symbolp exp) ()
182 "~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)
183 exp))
184
4e6c3ba1 185(defmethod ps-compile (form)
4a987e2b
VS
186 (error "The object ~S cannot be compiled by ParenScript." form))
187
4e6c3ba1 188(defmethod ps-compile ((form number))
4a987e2b
VS
189 form)
190
4e6c3ba1 191(defmethod ps-compile ((form string))
4a987e2b
VS
192 form)
193
4e6c3ba1
VS
194(defmethod ps-compile ((form character))
195 (ps-compile (string form)))
4a987e2b 196
4e6c3ba1 197(defmethod ps-compile ((symbol symbol))
5a69278c
VS
198 (when (eq *ps-compilation-level* :toplevel)
199 (multiple-value-bind (expansion expanded-p)
200 (ps-macroexpand symbol)
201 (when expanded-p
4e6c3ba1 202 (return-from ps-compile (ps-compile expansion)))))
f2bb932e
VS
203 (cond ((keywordp symbol) symbol)
204 ((ps-special-form-p (list symbol))
837bcc37 205 (if (ps-reserved-symbol-p symbol)
4e6c3ba1 206 (funcall (get-ps-special-form symbol))
f326f929 207 (error "Attempting to use Parenscript special form ~a as variable" symbol)))
0ce67a33 208 (t `(js:variable ,symbol))))
4a987e2b 209
a14fb2cb
VS
210;;; operators
211
4a56eb79
VS
212(let ((precedence-table (make-hash-table :test 'eq)))
213 (loop for level in '((js:new js:slot-value js:aref)
214 (postfix++ postfix--)
215 (delete void typeof ++ -- unary+ unary- ~ !)
216 (* / %)
217 (+ -)
218 (<< >> >>>)
219 (< > <= >= js:instanceof js:in)
220 (== != === !==)
221 (&)
222 (^)
223 (\|)
224 (\&\& and)
225 (\|\| or)
226 (js:?)
227 (= *= /= %= += -= <<= >>= >>>= \&\= ^= \|=)
228 (comma))
229 for i from 0
230 do (mapcar (lambda (symbol)
231 (setf (gethash symbol precedence-table) i))
232 level))
233 (defun op-precedence (op)
234 (gethash op precedence-table)))
a14fb2cb 235
3b16a7f3 236(defun ps-convert-op-name (op)
b39a6394 237 (case op
3b16a7f3
TC
238 (and '\&\&)
239 (or '\|\|)
240 (not '!)
241 (eql '\=\=)
242 (= '\=\=)
4e6c3ba1 243 (t op)))
3b16a7f3 244
a14fb2cb
VS
245(defun maybe-fix-nary-comparison-form (form)
246 (if (< 2 (length (cdr form)))
247 (values
248 (let* ((operator (car form))
249 (tmp-var-forms (butlast (cddr form)))
250 (tmp-vars (loop repeat (length tmp-var-forms)
251 collect (ps-gensym "_cmp")))
252 (all-comparisons (append (list (cadr form))
253 tmp-vars
254 (last form))))
255 `(let ,(mapcar #'list tmp-vars tmp-var-forms)
256 (and ,@(loop for x1 in all-comparisons
257 for x2 in (cdr all-comparisons)
258 collect (list operator x1 x2)))))
259 t)
260 form))
261
262(defun compile-op-form (form)
6f79326b 263 `(js:operator ,(ps-convert-op-name (ps-compile-symbol (car form)))
a14fb2cb 264 ,@(mapcar (lambda (form)
4e6c3ba1 265 (ps-compile-expression (ps-macroexpand form)))
a14fb2cb
VS
266 (cdr form))))
267
268(defun compile-funcall-form (form)
269 `(js:funcall
4e6c3ba1
VS
270 ,(ps-compile-expression (if (symbolp (car form))
271 (maybe-rename-local-function (car form))
272 (ps-macroexpand (car form))))
273 ,@(mapcar #'ps-compile-expression (cdr form))))
274
275(defvar compile-expression?)
276
277(defmethod ps-compile ((form cons))
5a69278c
VS
278 (multiple-value-bind (form expanded-p)
279 (ps-macroexpand form)
a14fb2cb
VS
280 (let ((*ps-compilation-level*
281 (if expanded-p
282 *ps-compilation-level*
283 (adjust-ps-compilation-level form *ps-compilation-level*))))
284 (cond (expanded-p
4e6c3ba1 285 (ps-compile form))
a14fb2cb 286 ((ps-special-form-p form)
4e6c3ba1 287 (apply (get-ps-special-form (car form)) (cdr form)))
a14fb2cb
VS
288 ((comparison-form-p form)
289 (multiple-value-bind (form fixed?)
290 (maybe-fix-nary-comparison-form form)
291 (if fixed?
4e6c3ba1 292 (ps-compile form)
a14fb2cb 293 (compile-op-form form))))
4e6c3ba1
VS
294 ((op-form-p form)
295 (compile-op-form form))
296 ((funcall-form-p form)
297 (compile-funcall-form form))
a14fb2cb 298 (t (error "Cannot compile ~S to a ParenScript form." form))))))
cc4f1551 299
4e6c3ba1
VS
300(defun ps-compile-statement (form)
301 (let ((compile-expression? nil))
302 (ps-compile form)))
303
304(defun ps-compile-expression (form)
305 (let ((compile-expression? t))
306 (ps-compile form)))
307
18dd299a
VS
308(defvar *ps-gensym-counter* 0)
309
310(defun ps-gensym (&optional (prefix "_js"))
5ffb1eba
VS
311 (let ((prefix (if (stringp prefix) prefix (symbol-to-js-string prefix nil))))
312 (make-symbol (format nil "~A~:[~;_~]~A" prefix
313 (digit-char-p (char prefix (1- (length prefix))))
314 (incf *ps-gensym-counter*)))))
18dd299a
VS
315
316(defmacro with-ps-gensyms (symbols &body body)
317 "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers.
318
319Each element of SYMBOLS is either a symbol or a list of (symbol
320gensym-prefix-string)."
321 `(let* ,(mapcar (lambda (symbol)
322 (destructuring-bind (symbol &optional prefix)
323 (if (consp symbol)
324 symbol
325 (list symbol))
326 (if prefix
327 `(,symbol (ps-gensym ,prefix))
6274a448 328 `(,symbol (ps-gensym ,(symbol-to-js-string symbol))))))
18dd299a
VS
329 symbols)
330 ,@body))
6ae06336
TC
331
332(defun %check-once-only-vars (vars)
333 (let ((bad-var (find-if (lambda (x) (or (not (symbolp x)) (keywordp x))) vars)))
334 (when bad-var
335 (error "PS-ONLY-ONCE expected a non-keyword symbol but got ~s" bad-var))))
336
337(defmacro ps-once-only ((&rest vars) &body body)
338 (%check-once-only-vars vars)
339 (let ((gensyms (mapcar (lambda (x) (ps-gensym (string x))) vars)))
340 `(let ,(mapcar (lambda (g v) `(,g (ps-gensym ,(string v)))) gensyms vars)
341 `(let* (,,@(mapcar (lambda (g v) ``(,,g ,,v)) gensyms vars))
342 ,(let ,(mapcar (lambda (g v) `(,v ,g)) gensyms vars)
343 ,@body)))))