1 (in-package "PARENSCRIPT")
3 ;;; reserved symbols/literals
5 (defvar *ps-reserved-symbol-names
* ()) ;; symbol names reserved for PS/JS literals
7 (defun add-ps-literal (name)
8 (push (symbol-name name
) *ps-reserved-symbol-names
*))
10 (defun ps-literal-p (symbol)
11 (find (symbol-name symbol
) *ps-reserved-symbol-names
* :test
#'equalp
))
15 (defvar *ps-special-forms
* (make-hash-table :test
'eq
))
17 (defun get-ps-special-form (name)
18 (gethash name
*ps-special-forms
*))
20 (defmacro define-ps-special-form
(name lambda-list
&rest body
)
21 "Define a special form NAME. The first argument (an anaphor called
22 'expecting' automatically added to the arglist) to the special form is
23 a keyword indicating whether the form is expected to produce
24 an :expression or a :statement."
25 (let ((args (gensym "ps-arglist-")))
26 `(setf (gethash ',name
*ps-special-forms
*)
28 (destructuring-bind ,(cons 'expecting lambda-list
)
30 (declare (ignorable expecting
))
33 (defun undefine-ps-special-form (name)
34 (remhash name
*ps-special-forms
*))
36 (defun ps-special-form-p (form)
39 (gethash (car form
) *ps-special-forms
*)))
43 (defvar *enclosing-lexical-block-declarations
* ()
44 "This special variable is expected to be bound to a fresh list by
45 special forms that introduce a new JavaScript lexical block (currently
46 function definitions and lambdas). Enclosed special forms are expected
47 to push variable declarations onto the list when the variables
48 declaration cannot be made by the enclosed form \(for example, a
49 \(x,y,z\) expression progn\). It is then the responsibility of the
50 enclosing special form to introduce the variable bindings in its
53 (defvar *ps-special-variables
* ())
57 (defun op-form-p (form)
59 (not (ps-special-form-p form
))
60 (not (null (op-precedence (first form
))))))
62 (defun funcall-form-p (form)
64 (not (op-form-p form
))
65 (not (ps-special-form-p form
))))
68 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
69 (defun make-macro-env-dictionary ()
70 (make-hash-table :test
'eq
))
71 (defvar *ps-macro-toplevel
* (make-macro-env-dictionary)
72 "Toplevel macro environment dictionary. Key is the symbol name of
73 the macro, value is (symbol-macro-p . expansion-function).")
74 (defvar *ps-macro-env
* (list *ps-macro-toplevel
*)
75 "Current macro environment.")
77 (defvar *ps-setf-expanders
* (make-macro-env-dictionary)
78 "Setf expander dictionary. Key is the symbol of the access
79 function of the place, value is an expansion function that takes the
80 arguments of the access functions as a first value and the form to be
81 stored as the second value.")
83 (defun get-macro-spec (name env-dict
)
84 "Retrieves the macro spec of the given name with the given environment dictionary.
85 SPEC is of the form (symbol-macro-p . expansion-function)."
86 (gethash name env-dict
))
87 (defsetf get-macro-spec
(name env-dict
)
89 `(setf (gethash ,name
,env-dict
) ,spec
)))
91 (defun lookup-macro-spec (name &optional
(environment *ps-macro-env
*))
92 "Looks up the macro spec associated with NAME in the given environment. A
93 macro spec is of the form (symbol-macro-p . function). Returns two values:
94 the SPEC and the parent macro environment.
96 NAME must be a symbol."
98 (do ((env environment
(cdr env
)))
100 (let ((val (get-macro-spec name
(car env
))))
102 (return-from lookup-macro-spec
103 (values val
(or (cdr env
)
104 (list *ps-macro-toplevel
*)))))))))
106 (defun ps-symbol-macro-p (name &optional
(environment *ps-macro-env
*))
107 "True if there is a Parenscript symbol macro named by the symbol NAME."
108 (and (symbolp name
) (car (lookup-macro-spec name environment
))))
110 (defun ps-macro-p (name &optional
(environment *ps-macro-env
*))
111 "True if there is a Parenscript macro named by the symbol NAME."
113 (let ((macro-spec (lookup-macro-spec name environment
)))
114 (and macro-spec
(not (car macro-spec
))))))
116 (defun lookup-macro-expansion-function (name &optional
(environment *ps-macro-env
*))
117 "Lookup NAME in the given macro expansion environment (which
118 defaults to the current macro environment). Returns the expansion
119 function 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
)))
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
133 (defmacro defpsmacro
(name args
&body body
)
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
)))
139 (defmacro define-ps-symbol-macro
(symbol expansion
)
141 `(progn (undefine-ps-special-form ',symbol
)
142 (setf (get-macro-spec ',symbol
*ps-macro-toplevel
*) (cons t
(lambda (,x
) (declare (ignore ,x
)) ',expansion
)))
145 (defun import-macros-from-lisp (&rest names
)
146 "Import the named Lisp macros into the ParenScript macro
147 environment. When the imported macro is macroexpanded by ParenScript,
148 it is first fully macroexpanded in the Lisp macro environment, and
149 then that expansion is further expanded by ParenScript."
151 (eval `(defpsmacro ,name
(&rest args
)
152 (macroexpand `(,',name
,@args
))))))
154 (defmacro defmacro
/ps
(name args
&body body
)
155 "Define a Lisp macro and import it into the ParenScript macro environment."
156 `(progn (defmacro ,name
,args
,@body
)
157 (import-macros-from-lisp ',name
)))
159 (defmacro defmacro
+ps
(name args
&body body
)
160 "Define a Lisp macro and a ParenScript macro with the same macro
161 function (ie - the same result from macroexpand-1), for cases when the
162 two have different full macroexpansions (for example if the CL macro
163 contains implementation-specific code when macroexpanded fully in the
165 `(progn (defmacro ,name
,args
,@body
)
166 (defpsmacro ,name
,args
,@body
)))
168 (defun ps-macroexpand (form)
169 "Recursively macroexpands ParenScript macros and symbol-macros in
170 the given ParenScript form. Returns two values: the expanded form, and
171 whether any expansion was performed on the form or not."
172 (let ((macro-function (cond ((ps-symbol-macro-p form
) form
)
173 ((and (consp form
) (ps-macro-p (car form
))) (car form
)))))
175 (values (ps-macroexpand (funcall (lookup-macro-expansion-function macro-function
) form
)) t
)
178 ;;;; compiler interface
179 (defgeneric compile-parenscript-form
(form &key expecting
)
180 (:documentation
"Compiles a ParenScript form to the intermediate
181 ParenScript representation. :expecting determines whether the form is
182 compiled to an :expression (the default), a :statement, or a
185 (defmethod compile-parenscript-form :around
(form &key expecting
)
186 (assert (if expecting
(member expecting
'(:expression
:statement
:symbol
)) t
))
187 (if (eql expecting
:symbol
)
188 (compile-to-symbol form
)
189 (multiple-value-bind (expanded-form expanded-p
)
190 (ps-macroexpand form
)
192 (compile-parenscript-form expanded-form
:expecting expecting
)
193 (call-next-method)))))
195 (defun compile-to-symbol (form)
196 "Compiles the given Parenscript form and guarantees that the
197 resultant symbol has an associated script-package. Raises an error if
198 the form cannot be compiled to a symbol."
199 (let ((exp (compile-parenscript-form form
)))
200 (when (eq (first exp
) 'js
:variable
)
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
)
206 (defmethod compile-parenscript-form (form &key expecting
)
207 (declare (ignore expecting
))
208 (error "The object ~S cannot be compiled by ParenScript." form
))
210 (defmethod compile-parenscript-form ((form number
) &key expecting
)
211 (declare (ignore expecting
))
214 (defmethod compile-parenscript-form ((form string
) &key expecting
)
215 (declare (ignore expecting
))
218 (defmethod compile-parenscript-form ((form character
) &key expecting
)
219 (declare (ignore expecting
))
220 (compile-parenscript-form (string form
)))
222 (defmethod compile-parenscript-form ((symbol symbol
) &key expecting
)
223 (declare (ignore expecting
))
224 (cond ((keywordp symbol
) symbol
)
225 ((ps-special-form-p (list symbol
))
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
)))
229 (t `(js:variable
,symbol
))))
231 (defun ps-convert-op-name (op)
232 (case (ensure-ps-symbol op
)
240 (defmethod compile-parenscript-form ((form cons
) &key
(expecting :statement
))
241 (let* ((name (car form
))
243 (cond ((ps-special-form-p form
) (apply (get-ps-special-form name
) (cons expecting args
)))
246 ,(ps-convert-op-name (compile-parenscript-form (first form
) :expecting
:symbol
))
247 ,@(mapcar (lambda (form) (compile-parenscript-form form
:expecting
:expression
)) (rest form
))))
248 ((funcall-form-p form
)
249 `(js:funcall
,(compile-parenscript-form name
:expecting
:expression
)
250 ,@(mapcar (lambda (arg) (compile-parenscript-form arg
:expecting
:expression
)) args
)))
251 (t (error "Cannot compile ~S to a ParenScript form." form
)))))
253 (defvar *ps-gensym-counter
* 0)
255 (defun ps-gensym (&optional
(prefix "_js"))
256 (make-symbol (format nil
"~A~A" prefix
(incf *ps-gensym-counter
*))))
258 (defmacro with-ps-gensyms
(symbols &body body
)
259 "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers.
261 Each element of SYMBOLS is either a symbol or a list of (symbol
262 gensym-prefix-string)."
263 `(let* ,(mapcar (lambda (symbol)
264 (destructuring-bind (symbol &optional prefix
)
269 `(,symbol
(ps-gensym ,prefix
))
270 `(,symbol
(ps-gensym ,(symbol-to-js-string symbol
))))))
274 (defun %check-once-only-vars
(vars)
275 (let ((bad-var (find-if (lambda (x) (or (not (symbolp x
)) (keywordp x
))) vars
)))
277 (error "PS-ONLY-ONCE expected a non-keyword symbol but got ~s" bad-var
))))
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
)