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