Got rid of js-keyword form.
[clinton/parenscript.git] / src / compiler.lisp
CommitLineData
cc4f1551
RD
1(in-package :parenscript)
2
cc4f1551 3(eval-when (:compile-toplevel :load-toplevel :execute)
b506b81b 4 (defvar *toplevel-special-forms* (make-hash-table :test #'equal)
9da682ca
RD
5 "A hash-table containing functions that implement Parenscript special forms,
6indexed by name (as symbols)")
4a987e2b 7 (defun undefine-ps-special-form (name)
9da682ca 8 "Undefines the special form with the given name (name is a symbol)."
06babcf5 9 (remhash (lisp-symbol-to-ps-identifier name :special-form) *toplevel-special-forms*)))
cc4f1551 10
4a987e2b
VS
11(defmacro define-ps-special-form (name lambda-list &rest body)
12 "Define a special form NAME. The first argument given to the special
13form is a keyword indicating whether the form is expected to produce
14an :expression or a :statement. The resulting Parenscript language
15types are appended to the ongoing javascript compilation."
b506b81b 16 (let ((arglist (gensym "ps-arglist-")))
06babcf5 17 `(setf (gethash (lisp-symbol-to-ps-identifier ',name :special-form) *toplevel-special-forms*)
b506b81b
VS
18 (lambda (&rest ,arglist)
19 (destructuring-bind ,lambda-list
20 ,arglist
21 ,@body)))))
9da682ca 22
4a987e2b 23(defun get-ps-special-form (name)
9da682ca 24 "Returns the special form function corresponding to the given name."
06babcf5 25 (gethash (lisp-symbol-to-ps-identifier name :special-form) *toplevel-special-forms*))
cc4f1551 26
e0032a96
VS
27(defvar *enclosing-lexical-block-declarations* ()
28 "This special variable is expected to be bound to a fresh list by
29special forms that introduce a new JavaScript lexical block (currently
30function definitions and lambdas). Enclosed special forms are expected
31to push variable declarations onto the list when the variables
32declaration cannot be made by the enclosed form (for example, a
33(x,y,z) expression progn). It is then the responsibility of the
34enclosing special form to introduce the variable bindings in its
35lexical block.")
36
4a987e2b
VS
37;;; ParenScript form predicates
38(defun ps-special-form-p (form)
cc4f1551
RD
39 (and (consp form)
40 (symbolp (car form))
4a987e2b
VS
41 (get-ps-special-form (car form))))
42
43(defun op-form-p (form)
44 (and (listp form)
45 (not (ps-special-form-p form))
46 (not (null (op-precedence (first form))))))
cc4f1551 47
9da682ca
RD
48(defun funcall-form-p (form)
49 (and (listp form)
4a987e2b
VS
50 (not (op-form-p form))
51 (not (ps-special-form-p form))))
cc4f1551 52
9da682ca
RD
53(defun method-call-p (form)
54 (and (funcall-form-p form)
55 (symbolp (first form))
56 (eql (char (symbol-name (first form)) 0) #\.)))
cc4f1551 57
9da682ca 58;;; macro expansion
cc4f1551
RD
59(eval-when (:compile-toplevel :load-toplevel :execute)
60 (defun make-macro-env-dictionary ()
9da682ca 61 "Creates a standard macro dictionary."
06babcf5 62 (make-hash-table :test #'equal))
9da682ca 63 (defvar *script-macro-toplevel* (make-macro-env-dictionary)
72332f2a
VS
64 "Toplevel macro environment dictionary. Key is the symbol of the
65macro, value is (symbol-macro-p . expansion-function).")
06babcf5 66 (defvar *script-macro-env* (list *script-macro-toplevel*)
171bbab3 67 "Current macro environment.")
72332f2a
VS
68
69 (defvar *script-setf-expanders* (make-macro-env-dictionary)
70 "Setf expander dictionary. Key is the symbol of the access
71function of the place, value is an expansion function that takes the
72arguments of the access functions as a first value and the form to be
73stored as the second value.")
171bbab3 74
06babcf5
VS
75 (defun get-macro-spec (name env-dict)
76 "Retrieves the macro spec of the given name with the given environment dictionary.
72332f2a 77SPEC is of the form (symbol-macro-p . expansion-function)."
06babcf5
VS
78 (gethash (lisp-symbol-to-ps-identifier name :macro) env-dict))
79 (defsetf get-macro-spec (name env-dict)
80 (spec)
81 `(setf (gethash (lisp-symbol-to-ps-identifier ,name :macro) ,env-dict) ,spec)))
9da682ca
RD
82
83(defun lookup-macro-spec (name &optional (environment *script-macro-env*))
84 "Looks up the macro spec associated with NAME in the given environment. A
905f534e 85macro spec is of the form (symbol-macro-p . function). Returns two values:
9da682ca 86the SPEC and the parent macro environment.
cc4f1551 87
9da682ca 88NAME must be a symbol."
cc4f1551
RD
89 (when (symbolp name)
90 (do ((env environment (cdr env)))
91 ((null env) nil)
92 (let ((val (get-macro-spec name (car env))))
93 (when val
94 (return-from lookup-macro-spec
95 (values val (or (cdr env)
9da682ca 96 (list *script-macro-toplevel*)))))))))
cc4f1551 97
9da682ca
RD
98(defun script-symbol-macro-p (name &optional (environment *script-macro-env*))
99 "True if there is a Parenscript symbol macro named by the symbol NAME."
cc4f1551
RD
100 (and (symbolp name) (car (lookup-macro-spec name environment))))
101
9da682ca
RD
102(defun script-macro-p (name &optional (environment *script-macro-env*))
103 "True if there is a Parenscript macro named by the symbol NAME."
104 (and (symbolp name)
105 (let ((macro-spec (lookup-macro-spec name environment)))
106 (and macro-spec (not (car macro-spec))))))
cc4f1551 107
9da682ca 108(defun lookup-macro-expansion-function (name &optional (environment *script-macro-env*))
cc4f1551
RD
109 "Lookup NAME in the given macro expansion environment (which
110defaults to the current macro environment). Returns the expansion
111function and the parent macro environment of the macro."
112 (multiple-value-bind (macro-spec parent-env)
113 (lookup-macro-spec name environment)
114 (values (cdr macro-spec) parent-env)))
115
e22d923b 116(eval-when (:compile-toplevel :load-toplevel :execute)
921f2e02
VS
117 (defun make-ps-macro-function (args body)
118 (let* ((whole-var (when (eql '&whole (first args)) (second args)))
119 (effective-lambda-list (if whole-var (cddr args) args))
120 (form-arg (or whole-var (gensym "ps-macro-form-arg-")))
121 (body (if (and (cdr body) (stringp (first body))) (rest body) body))) ;; drop docstring
122 (compile nil `(lambda (,form-arg)
123 (destructuring-bind ,effective-lambda-list
124 (cdr ,form-arg)
125 ,@body)))))
126
e22d923b 127 (defun define-script-macro% (name args body &key symbol-macro-p)
921f2e02
VS
128 (undefine-ps-special-form name)
129 (setf (get-macro-spec name *script-macro-toplevel*)
130 (cons symbol-macro-p (make-ps-macro-function args body)))
131 nil))
d9fc64c9 132
4a987e2b 133(defmacro defpsmacro (name args &body body)
d9fc64c9
VS
134 "Define a ParenScript macro, and store it in the toplevel ParenScript
135macro environment."
e22d923b 136 `(define-script-macro% ',name ',args ',body :symbol-macro-p nil))
cc4f1551 137
46f794a4 138(defmacro define-script-symbol-macro (name &body body)
b5369cb1 139 "Define a ParenScript symbol macro, and store it in the toplevel ParenScript
46f794a4 140macro environment. BODY is a Lisp form that should return a ParenScript form."
e22d923b 141 `(define-script-macro% ',name () ',body :symbol-macro-p t))
b5369cb1 142
7590646c
VS
143(defun import-macros-from-lisp (&rest names)
144 "Import the named Lisp macros into the ParenScript macro
145environment. When the imported macro is macroexpanded by ParenScript,
146it is first fully macroexpanded in the Lisp macro environment, and
147then that expansion is further expanded by ParenScript."
148 (dolist (name names)
e22d923b
RD
149 (define-script-macro% name '(&rest args)
150 (list `(common-lisp:macroexpand `(,',name ,@args)))
151 :symbol-macro-p nil)))
7590646c 152
f016e033 153(defmacro defmacro/ps (name args &body body)
7590646c
VS
154 "Define a Lisp macro and import it into the ParenScript macro environment."
155 `(progn (defmacro ,name ,args ,@body)
f016e033 156 (ps:import-macros-from-lisp ',name)))
7590646c 157
f016e033 158(defmacro defmacro+ps (name args &body body)
7590646c
VS
159 "Define a Lisp macro and a ParenScript macro in their respective
160macro environments. This function should be used when you want to use
161the same macro in both Lisp and ParenScript, but the 'macroexpand' of
162that macro in Lisp makes the Lisp macro unsuitable to be imported into
163the ParenScript macro environment."
164 `(progn (defmacro ,name ,args ,@body)
4a987e2b
VS
165 (defpsmacro ,name ,args ,@body)))
166
167(defun ps-macroexpand (form)
168 "Recursively macroexpands ParenScript macros and symbol-macros in
169the given ParenScript form. Returns two values: the expanded form, and
170whether any expansion was performed on the form or not."
171 (if (consp form)
172 (let ((op (car form))
173 (args (cdr form)))
43a1d5c3
VS
174 (cond ((equal op 'quote) (values (if (equalp '(nil) args) nil form) ;; leave quotes alone, unless it's a quoted nil
175 nil))
176 ((script-macro-p op) (values (ps-macroexpand (funcall (lookup-macro-expansion-function op) form)) t))
4a987e2b 177 (t (values form nil))))
7626bb83 178 (cond ((script-symbol-macro-p form) (values (ps-macroexpand (funcall (lookup-macro-expansion-function form) (list form))) t))
4a987e2b
VS
179 (t (values form nil)))))
180
181;;;; compiler interface
182(defgeneric compile-parenscript-form (form &key expecting)
183 (:documentation "Compiles a ParenScript form to the intermediate
184ParenScript representation. :expecting determines whether the form is
185compiled to an :expression (the default), a :statement, or a
186:symbol."))
187
188(defmethod compile-parenscript-form :around (form &key expecting)
e0032a96 189 (assert (if expecting (member expecting '(:expression :statement :symbol)) t))
4a987e2b
VS
190 (if (eql expecting :symbol)
191 (compile-to-symbol form)
192 (multiple-value-bind (expanded-form expanded-p)
193 (ps-macroexpand form)
194 (if expanded-p
a589bb43 195 (compile-parenscript-form expanded-form :expecting expecting)
4a987e2b
VS
196 (call-next-method)))))
197
198(defun compile-to-symbol (form)
199 "Compiles the given Parenscript form and guarantees that the
200resultant symbol has an associated script-package. Raises an error if
201the form cannot be compiled to a symbol."
202 (let ((exp (compile-parenscript-form form)))
203 (when (or (eql (first exp) 'js-variable)
204 (eql (first exp) 'script-quote))
205 (setf exp (second exp)))
206 (assert (symbolp exp) ()
207 "~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)
208 exp))
209
210(defmethod compile-parenscript-form (form &key expecting)
211 (declare (ignore expecting))
212 (error "The object ~S cannot be compiled by ParenScript." form))
213
214(defmethod compile-parenscript-form ((form number) &key expecting)
215 (declare (ignore expecting))
216 form)
217
218(defmethod compile-parenscript-form ((form string) &key expecting)
219 (declare (ignore expecting))
220 form)
221
222(defmethod compile-parenscript-form ((form character) &key expecting)
223 (declare (ignore expecting))
224 (compile-parenscript-form (string form)))
225
226(defmethod compile-parenscript-form ((symbol symbol) &key expecting)
227 (declare (ignore expecting))
46f794a4 228 ;; is this the correct behavior?
4a987e2b
VS
229 (let ((special-symbol (get-ps-special-form symbol)))
230 (cond (special-symbol (funcall special-symbol :symbol))
4a987e2b
VS
231 (t (list 'js-variable symbol)))))
232
233(defun compile-function-argument-forms (arg-forms)
46f794a4
RD
234 "Compiles a bunch of Parenscript forms from a funcall form to an effective set of
235Javascript arguments. The only extra processing this does is makes :keyword arguments
236into a single options argument via CREATE."
237 (flet ((keyword-arg (arg)
238 "If the given compiled expression is supposed to be a keyword argument, returns
239the keyword for it."
4a987e2b 240 (when (and (listp arg) (eql (first arg) 'script-quote)) (second arg))))
e5253c5b
VS
241 (let ((compiled-args (mapcar (lambda (arg) (compile-parenscript-form arg :expecting :expression))
242 arg-forms)))
4a987e2b
VS
243 (do ((effective-expressions nil)
244 (expressions-subl compiled-args))
245 ((not expressions-subl) (reverse effective-expressions))
246 (let ((arg-expr (first expressions-subl)))
247 (if (keyword-arg arg-expr)
248 (progn (when (oddp (length expressions-subl))
249 (error "Odd number of keyword arguments: ~A." arg-forms))
250 (push (list 'js-object (loop for (name val) on expressions-subl by #'cddr
251 collect (list name val)))
252 effective-expressions)
253 (setf expressions-subl nil))
254 (progn (push arg-expr effective-expressions)
255 (setf expressions-subl (rest expressions-subl)))))))))
256
257(defmethod compile-parenscript-form ((form cons) &key (expecting :statement))
46f794a4 258 (let* ((name (car form))
4a987e2b
VS
259 (args (cdr form)))
260 (cond ((eql name 'quote)
261 (assert (= 1 (length args)) () "Wrong number of arguments to quote: ~s" args)
262 (list 'script-quote (first args)))
263 ((ps-special-form-p form) (apply (get-ps-special-form name) (cons expecting args)))
264 ((op-form-p form)
265 (list 'operator
266 (script-convert-op-name (compile-parenscript-form (first form) :expecting :symbol))
267 (mapcar (lambda (form) (compile-parenscript-form form :expecting :expression)) (rest form))))
268 ((method-call-p form)
269 (list 'js-method-call
270 (compile-parenscript-form name :expecting :symbol)
271 (compile-parenscript-form (first args) :expecting :expression)
272 (compile-function-argument-forms (rest args))))
273 ((funcall-form-p form)
274 (list 'js-funcall
275 (compile-parenscript-form name :expecting :expression)
276 (compile-function-argument-forms args)))
277 (t (error "Cannot compile ~S to a ParenScript form." form)))))
cc4f1551 278
18dd299a
VS
279(defvar *ps-gensym-counter* 0)
280
281(defun ps-gensym (&optional (prefix "_js"))
282 (make-symbol (format nil "~A~A" prefix (incf *ps-gensym-counter*))))
283
284(defmacro with-ps-gensyms (symbols &body body)
285 "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers.
286
287Each element of SYMBOLS is either a symbol or a list of (symbol
288gensym-prefix-string)."
289 `(let* ,(mapcar (lambda (symbol)
290 (destructuring-bind (symbol &optional prefix)
291 (if (consp symbol)
292 symbol
293 (list symbol))
294 (if prefix
295 `(,symbol (ps-gensym ,prefix))
296 `(,symbol (ps-gensym)))))
297 symbols)
298 ,@body))