Removed some unnecessary code that ignored an anaphor in define-ps-special-form ...
[clinton/parenscript.git] / src / compiler.lisp
CommitLineData
e8fdcce7 1(in-package "PARENSCRIPT")
cc4f1551 2
72044f33 3;;; reserved symbols/literals
c88be949 4
72044f33 5(defvar *ps-reserved-symbol-names* ()) ;; symbol names reserved for PS/JS literals
c88be949 6
72044f33
VS
7(defun add-ps-literal (name)
8 (push (symbol-name name) *ps-reserved-symbol-names*))
c88be949 9
72044f33
VS
10(defun ps-literal-p (symbol)
11 (find (symbol-name symbol) *ps-reserved-symbol-names* :test #'equalp))
8cf7de80 12
72044f33 13;;; special forms
f326f929 14
72044f33 15(defvar *ps-special-forms* (make-hash-table :test 'eq))
4577df1c 16
72044f33
VS
17(defun get-ps-special-form (name)
18 (gethash name *ps-special-forms*))
cc4f1551 19
4a987e2b 20(defmacro define-ps-special-form (name lambda-list &rest body)
e8fdcce7
VS
21 "Define a special form NAME. The first argument (an anaphor called
22'expecting' automatically added to the arglist) to the special form is
23a keyword indicating whether the form is expected to produce
24an :expression or a :statement."
a8b6752e 25 (let ((args (gensym "ps-arglist-")))
72044f33 26 `(setf (gethash ',name *ps-special-forms*)
e8fdcce7
VS
27 (lambda (&rest ,args)
28 (destructuring-bind ,(cons 'expecting lambda-list)
29 ,args
a8b6752e 30 (declare (ignorable expecting))
c88be949 31 ,@body)))))
9da682ca 32
72044f33
VS
33(defun undefine-ps-special-form (name)
34 (remhash name *ps-special-forms*))
35
36(defun ps-special-form-p (form)
37 (and (consp form)
38 (symbolp (car form))
39 (gethash (car form) *ps-special-forms*)))
40
41;;; scoping
42
e0032a96
VS
43(defvar *enclosing-lexical-block-declarations* ()
44 "This special variable is expected to be bound to a fresh list by
45special forms that introduce a new JavaScript lexical block (currently
46function definitions and lambdas). Enclosed special forms are expected
47to push variable declarations onto the list when the variables
83b5a0cc
TC
48declaration cannot be made by the enclosed form \(for example, a
49\(x,y,z\) expression progn\). It is then the responsibility of the
e0032a96
VS
50enclosing special form to introduce the variable bindings in its
51lexical block.")
52
58c4ef4f
VS
53(defvar *ps-special-variables* ())
54
72044f33 55;;; form predicates
4a987e2b
VS
56
57(defun op-form-p (form)
58 (and (listp form)
59 (not (ps-special-form-p form))
60 (not (null (op-precedence (first form))))))
cc4f1551 61
9da682ca
RD
62(defun funcall-form-p (form)
63 (and (listp form)
4a987e2b
VS
64 (not (op-form-p form))
65 (not (ps-special-form-p form))))
cc4f1551 66
9da682ca 67;;; macro expansion
cc4f1551
RD
68(eval-when (:compile-toplevel :load-toplevel :execute)
69 (defun make-macro-env-dictionary ()
72044f33 70 (make-hash-table :test 'eq))
462ca010 71 (defvar *ps-macro-toplevel* (make-macro-env-dictionary)
72044f33
VS
72 "Toplevel macro environment dictionary. Key is the symbol name of
73 the macro, value is (symbol-macro-p . expansion-function).")
462ca010 74 (defvar *ps-macro-env* (list *ps-macro-toplevel*)
171bbab3 75 "Current macro environment.")
72332f2a 76
462ca010 77 (defvar *ps-setf-expanders* (make-macro-env-dictionary)
72332f2a
VS
78 "Setf expander dictionary. Key is the symbol of the access
79function of the place, value is an expansion function that takes the
80arguments of the access functions as a first value and the form to be
81stored as the second value.")
171bbab3 82
06babcf5
VS
83 (defun get-macro-spec (name env-dict)
84 "Retrieves the macro spec of the given name with the given environment dictionary.
72332f2a 85SPEC is of the form (symbol-macro-p . expansion-function)."
72044f33 86 (gethash name env-dict))
06babcf5
VS
87 (defsetf get-macro-spec (name env-dict)
88 (spec)
72044f33 89 `(setf (gethash ,name ,env-dict) ,spec)))
9da682ca 90
462ca010 91(defun lookup-macro-spec (name &optional (environment *ps-macro-env*))
9da682ca 92 "Looks up the macro spec associated with NAME in the given environment. A
905f534e 93macro spec is of the form (symbol-macro-p . function). Returns two values:
9da682ca 94the SPEC and the parent macro environment.
cc4f1551 95
9da682ca 96NAME must be a symbol."
cc4f1551
RD
97 (when (symbolp name)
98 (do ((env environment (cdr env)))
99 ((null env) nil)
100 (let ((val (get-macro-spec name (car env))))
101 (when val
102 (return-from lookup-macro-spec
103 (values val (or (cdr env)
462ca010 104 (list *ps-macro-toplevel*)))))))))
cc4f1551 105
462ca010 106(defun ps-symbol-macro-p (name &optional (environment *ps-macro-env*))
9da682ca 107 "True if there is a Parenscript symbol macro named by the symbol NAME."
cc4f1551
RD
108 (and (symbolp name) (car (lookup-macro-spec name environment))))
109
462ca010 110(defun ps-macro-p (name &optional (environment *ps-macro-env*))
9da682ca
RD
111 "True if there is a Parenscript macro named by the symbol NAME."
112 (and (symbolp name)
113 (let ((macro-spec (lookup-macro-spec name environment)))
b508414b 114 (and macro-spec (not (car macro-spec))))))
cc4f1551 115
462ca010 116(defun lookup-macro-expansion-function (name &optional (environment *ps-macro-env*))
cc4f1551
RD
117 "Lookup NAME in the given macro expansion environment (which
118defaults to the current macro environment). Returns the expansion
119function and the parent macro environment of the macro."
120 (multiple-value-bind (macro-spec parent-env)
121 (lookup-macro-spec name environment)
122 (values (cdr macro-spec) parent-env)))
123
8cfc6fe9
VS
124(defun make-ps-macro-function (args body)
125 (let* ((whole-var (when (eql '&whole (first args)) (second args)))
126 (effective-lambda-list (if whole-var (cddr args) args))
127 (whole-arg (or whole-var (gensym "ps-macro-form-arg-"))))
128 `(lambda (,whole-arg)
129 (destructuring-bind ,effective-lambda-list
130 (cdr ,whole-arg)
131 ,@body))))
d9fc64c9 132
4a987e2b 133(defmacro defpsmacro (name args &body body)
8cfc6fe9
VS
134 `(progn (undefine-ps-special-form ',name)
135 (setf (get-macro-spec ',name *ps-macro-toplevel*)
136 (cons nil ,(make-ps-macro-function args body)))
137 ',name))
cc4f1551 138
8cfc6fe9 139(defmacro define-ps-symbol-macro (symbol expansion)
fb469285
VS
140 (let ((x (gensym)))
141 `(progn (undefine-ps-special-form ',symbol)
142 (setf (get-macro-spec ',symbol *ps-macro-toplevel*) (cons t (lambda (,x) (declare (ignore ,x)) ',expansion)))
143 ',symbol)))
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)
8cfc6fe9
VS
151 (eval `(defpsmacro ,name (&rest args)
152 (macroexpand `(,',name ,@args))))))
7590646c 153
f016e033 154(defmacro defmacro/ps (name args &body body)
7590646c
VS
155 "Define a Lisp macro and import it into the ParenScript macro environment."
156 `(progn (defmacro ,name ,args ,@body)
8cfc6fe9 157 (import-macros-from-lisp ',name)))
7590646c 158
f016e033 159(defmacro defmacro+ps (name args &body body)
8cfc6fe9
VS
160 "Define a Lisp macro and a ParenScript macro with the same macro
161function (ie - the same result from macroexpand-1), for cases when the
162two have different full macroexpansions (for example if the CL macro
163contains implementation-specific code when macroexpanded fully in the
164CL environment)."
7590646c 165 `(progn (defmacro ,name ,args ,@body)
4a987e2b
VS
166 (defpsmacro ,name ,args ,@body)))
167
168(defun ps-macroexpand (form)
169 "Recursively macroexpands ParenScript macros and symbol-macros in
170the given ParenScript form. Returns two values: the expanded form, and
171whether any expansion was performed on the form or not."
fb469285
VS
172 (let ((macro-function (cond ((ps-symbol-macro-p form) form)
173 ((and (consp form) (ps-macro-p (car form))) (car form)))))
174 (if macro-function
175 (values (ps-macroexpand (funcall (lookup-macro-expansion-function macro-function) form)) t)
176 (values form nil))))
4a987e2b
VS
177
178;;;; compiler interface
179(defgeneric compile-parenscript-form (form &key expecting)
180 (:documentation "Compiles a ParenScript form to the intermediate
181ParenScript representation. :expecting determines whether the form is
182compiled to an :expression (the default), a :statement, or a
183:symbol."))
184
185(defmethod compile-parenscript-form :around (form &key expecting)
e0032a96 186 (assert (if expecting (member expecting '(:expression :statement :symbol)) t))
4a987e2b
VS
187 (if (eql expecting :symbol)
188 (compile-to-symbol form)
189 (multiple-value-bind (expanded-form expanded-p)
190 (ps-macroexpand form)
191 (if expanded-p
a589bb43 192 (compile-parenscript-form expanded-form :expecting expecting)
4a987e2b
VS
193 (call-next-method)))))
194
195(defun compile-to-symbol (form)
196 "Compiles the given Parenscript form and guarantees that the
197resultant symbol has an associated script-package. Raises an error if
198the form cannot be compiled to a symbol."
199 (let ((exp (compile-parenscript-form form)))
0ce67a33 200 (when (eq (first exp) 'js:variable)
4a987e2b
VS
201 (setf exp (second exp)))
202 (assert (symbolp exp) ()
203 "~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)
204 exp))
205
206(defmethod compile-parenscript-form (form &key expecting)
207 (declare (ignore expecting))
208 (error "The object ~S cannot be compiled by ParenScript." form))
209
210(defmethod compile-parenscript-form ((form number) &key expecting)
211 (declare (ignore expecting))
212 form)
213
214(defmethod compile-parenscript-form ((form string) &key expecting)
215 (declare (ignore expecting))
216 form)
217
218(defmethod compile-parenscript-form ((form character) &key expecting)
219 (declare (ignore expecting))
220 (compile-parenscript-form (string form)))
221
222(defmethod compile-parenscript-form ((symbol symbol) &key expecting)
223 (declare (ignore expecting))
f2bb932e
VS
224 (cond ((keywordp symbol) symbol)
225 ((ps-special-form-p (list symbol))
f326f929
VS
226 (if (ps-literal-p symbol)
227 (funcall (get-ps-special-form symbol) :symbol)
228 (error "Attempting to use Parenscript special form ~a as variable" symbol)))
0ce67a33 229 (t `(js:variable ,symbol))))
4a987e2b 230
3b16a7f3
TC
231(defun ps-convert-op-name (op)
232 (case (ensure-ps-symbol op)
233 (and '\&\&)
234 (or '\|\|)
235 (not '!)
236 (eql '\=\=)
237 (= '\=\=)
238 (t op)))
239
4a987e2b 240(defmethod compile-parenscript-form ((form cons) &key (expecting :statement))
46f794a4 241 (let* ((name (car form))
b508414b 242 (args (cdr form)))
fb469285 243 (cond ((ps-special-form-p form) (apply (get-ps-special-form name) (cons expecting args)))
4a987e2b 244 ((op-form-p form)
0ce67a33
VS
245 `(js:operator
246 ,(ps-convert-op-name (compile-parenscript-form (first form) :expecting :symbol))
247 ,@(mapcar (lambda (form) (compile-parenscript-form form :expecting :expression)) (rest form))))
4a987e2b 248 ((funcall-form-p form)
0ce67a33
VS
249 `(js:funcall ,(compile-parenscript-form name :expecting :expression)
250 ,@(mapcar (lambda (arg) (compile-parenscript-form arg :expecting :expression)) args)))
4a987e2b 251 (t (error "Cannot compile ~S to a ParenScript form." form)))))
cc4f1551 252
18dd299a
VS
253(defvar *ps-gensym-counter* 0)
254
255(defun ps-gensym (&optional (prefix "_js"))
256 (make-symbol (format nil "~A~A" prefix (incf *ps-gensym-counter*))))
257
258(defmacro with-ps-gensyms (symbols &body body)
259 "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers.
260
261Each element of SYMBOLS is either a symbol or a list of (symbol
262gensym-prefix-string)."
263 `(let* ,(mapcar (lambda (symbol)
264 (destructuring-bind (symbol &optional prefix)
265 (if (consp symbol)
266 symbol
267 (list symbol))
268 (if prefix
269 `(,symbol (ps-gensym ,prefix))
6274a448 270 `(,symbol (ps-gensym ,(symbol-to-js-string symbol))))))
18dd299a
VS
271 symbols)
272 ,@body))
6ae06336
TC
273
274(defun %check-once-only-vars (vars)
275 (let ((bad-var (find-if (lambda (x) (or (not (symbolp x)) (keywordp x))) vars)))
276 (when bad-var
277 (error "PS-ONLY-ONCE expected a non-keyword symbol but got ~s" bad-var))))
278
279(defmacro ps-once-only ((&rest vars) &body body)
280 (%check-once-only-vars vars)
281 (let ((gensyms (mapcar (lambda (x) (ps-gensym (string x))) vars)))
282 `(let ,(mapcar (lambda (g v) `(,g (ps-gensym ,(string v)))) gensyms vars)
283 `(let* (,,@(mapcar (lambda (g v) ``(,,g ,,v)) gensyms vars))
284 ,(let ,(mapcar (lambda (g v) `(,v ,g)) gensyms vars)
285 ,@body)))))