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