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