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