1 (in-package :parenscript
)
3 ;;;; The macrology of the Parenscript language. Special forms and macros.
5 ;;; parenscript gensyms
6 (defvar *ps-gensym-counter
* 0)
8 (defun ps-gensym (&optional
(prefix "_js"))
9 (make-symbol (format nil
"~A~A" prefix
(incf *ps-gensym-counter
*))))
11 (defmacro with-ps-gensyms
(symbols &body body
)
12 "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers.
14 Each element of SYMBOLS is either a symbol or a list of (symbol
15 gensym-prefix-string)."
16 `(let* ,(mapcar (lambda (symbol)
17 (destructuring-bind (symbol &optional prefix
)
22 `(,symbol
(ps-gensym ,prefix
))
23 `(,symbol
(ps-gensym)))))
27 (defun constant-literal-form-p (form)
31 (eql 'js-literal
(car form
)))))
33 (defpsmacro defaultf
(place value
)
34 `(setf ,place
(or (and (=== undefined
,place
) ,value
)
38 (defpsmacro list
(&rest values
)
41 (defpsmacro make-array
(&rest inits
)
42 `(new (*array
,@inits
)))
45 (defpsmacro slot-value
(obj &rest slots
)
46 (if (null (rest slots
))
47 `(%js-slot-value
,obj
,(first slots
))
48 `(slot-value (slot-value ,obj
,(first slots
)) ,@(rest slots
))))
50 (defpsmacro with-slots
(slots object
&rest body
)
51 (flet ((slot-var (slot) (if (listp slot
) (first slot
) slot
))
52 (slot-symbol (slot) (if (listp slot
) (second slot
) slot
)))
53 `(symbol-macrolet ,(mapcar #'(lambda (slot)
54 `(,(slot-var slot
) (slot-value ,object
',(slot-symbol slot
))))
58 (defpsmacro case
(value &rest clauses
)
59 (labels ((make-clause (val body more
)
61 (append (mapcar #'list
(butlast val
))
62 (make-clause (first (last val
)) body more
)))
63 ((member val
'(t otherwise
))
64 (make-clause 'default body more
))
65 (more `((,val
,@body break
)))
66 (t `((,val
,@body
))))))
67 `(switch ,value
,@(mapcon (lambda (clause)
68 (make-clause (car (first clause
))
73 (define-ps-special-form let
(expecting bindings
&rest body
)
74 (declare (ignore expecting
))
75 (let ((defvars (mapcar (lambda (binding) (if (atom binding
)
79 (compile-parenscript-form `(progn ,@defvars
,@body
))))
82 (defpsmacro dotimes
(iter &rest body
)
83 (let ((var (first iter
))
84 (times (second iter
)))
85 `(do ((,var
0 (1+ ,var
)))
89 (defpsmacro dolist
(i-array &rest body
)
90 (let ((var (first i-array
))
91 (array (second i-array
))
92 (arrvar (ps-gensym "tmp-arr"))
93 (idx (ps-gensym "tmp-i")))
94 `(let ((,arrvar
,array
))
95 (do ((,idx
0 (1+ ,idx
)))
96 ((>= ,idx
(slot-value ,arrvar
'length
)))
97 (let ((,var
(aref ,arrvar
,idx
)))
101 (defmacro with-temp-macro-environment
((var) &body body
)
102 `(let* ((,var
(make-macro-env-dictionary))
103 (*script-macro-env
* (cons ,var
*script-macro-env
*)))
106 (define-ps-special-form macrolet
(expecting macros
&body body
)
107 (declare (ignore expecting
))
108 (with-temp-macro-environment (macro-env-dict)
109 (dolist (macro macros
)
110 (destructuring-bind (name arglist
&body body
)
112 (setf (get-macro-spec name macro-env-dict
)
113 (cons nil
(make-ps-macro-function arglist body
)))))
114 (compile-parenscript-form `(progn ,@body
))))
116 (define-ps-special-form symbol-macrolet
(expecting symbol-macros
&body body
)
117 (declare (ignore expecting
))
118 (with-temp-macro-environment (macro-env-dict)
119 (dolist (macro symbol-macros
)
120 (destructuring-bind (name expansion
)
122 (setf (get-macro-spec name macro-env-dict
)
123 (cons t
(make-ps-macro-function () (list `',expansion
))))))
124 (compile-parenscript-form `(progn ,@body
))))
126 (define-ps-special-form defmacro
(expecting name args
&body body
)
127 (declare (ignore expecting
))
128 (define-script-macro% name args body
:symbol-macro-p nil
)
131 (define-ps-special-form define-symbol-macro
(expecting name expansion
)
132 (declare (ignore expecting
))
133 (define-script-macro% name
() (list `',expansion
) :symbol-macro-p t
)
136 (defpsmacro lisp
(&body forms
)
137 "Evaluates the given forms in Common Lisp at ParenScript
138 macro-expansion time. The value of the last form is treated as a
139 ParenScript expression and is inserted into the generated Javascript
140 \(use nil for no-op)."
141 (eval (cons 'progn forms
)))
143 (defpsmacro rebind
(variables &body body
)
144 "Creates a new js lexical environment and copies the given
145 variable(s) there. Executes the body in the new environment. This
146 has the same effect as a new (let () ...) form in lisp but works on
147 the js side for js closures."
148 (unless (listp variables
)
149 (setf variables
(list variables
)))
151 (let ((new-context (new *object
)))
152 ,@(loop for variable in variables
153 collect
`(setf (slot-value new-context
,(symbol-to-js variable
))
158 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
159 (defun parse-function-body (body)
160 ;; (format t "parsing function body ~A~%" body)
161 (let* ((documentation
162 (when (stringp (first body
))
164 (body-forms (if documentation
(rest body
) body
)))
169 (defun parse-key-spec (key-spec)
170 "parses an &key parameter. Returns 4 values:
171 var, init-form, keyword-name, supplied-p-var, init-form-supplied-p.
174 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}*
176 (let* ((var (cond ((symbolp key-spec
) key-spec
)
177 ((and (listp key-spec
) (symbolp (first key-spec
))) (first key-spec
))
178 ((and (listp key-spec
) (listp (first key-spec
))) (second key-spec
))))
179 (keyword-name (if (and (listp key-spec
) (listp (first key-spec
)))
180 (first (first key-spec
))
181 (intern (string var
) :keyword
)))
182 (init-form (if (listp key-spec
) (second key-spec
) nil
))
183 (init-form-supplied-p (if (listp key-spec
) t nil
))
184 (supplied-p-var (if (listp key-spec
) (third key-spec
) nil
)))
185 (values var init-form keyword-name supplied-p-var init-form-supplied-p
)))
187 (defun parse-optional-spec (spec)
188 "Parses an &optional parameter. Returns 3 values: var, init-form, supplied-p-var.
189 [&optional {var | (var [init-form [supplied-p-parameter]])}*] "
190 (let* ((var (cond ((symbolp spec
) spec
)
191 ((and (listp spec
) (first spec
)))))
192 (init-form (if (listp spec
) (second spec
)))
193 (supplied-p-var (if (listp spec
) (third spec
))))
194 (values var init-form supplied-p-var
)))
196 (defun parse-aux-spec (spec)
197 "Returns two values: variable and init-form"
198 ;; [&aux {var | (var [init-form])}*])
199 (values (if (symbolp spec
) spec
(first spec
))
200 (when (listp spec
) (second spec
))))
202 (defun parse-extended-function (lambda-list body
&optional name
)
203 "Returns two values: the effective arguments and body for a function with
204 the given lambda-list and body."
206 ;; The lambda list is transformed as follows, since a javascript lambda list is just a
207 ;; list of variable names, and you have access to the arguments variable inside the function:
208 ;; * standard variables are the mapped directly into the js-lambda list
209 ;; * optional variables' variable names are mapped directly into the lambda list,
210 ;; and for each optional variable with name v and default value d, a form is produced
212 ;; * when any keyword variables are in the lambda list, a single 'optional-args' variable is
213 ;; appended to the js-lambda list as the last argument. WITH-SLOTS is used for all
214 ;; the variables with inside the body of the function,
215 ;; a (with-slots ((var-name key-name)) optional-args ...)
216 (declare (ignore name
))
217 (multiple-value-bind (requireds optionals rest? rest keys? keys allow? aux? aux
218 more? more-context more-count key-object
)
219 (parse-lambda-list lambda-list
)
220 (declare (ignore allow? aux? aux more? more-context more-count
))
221 (let* ((options-var (or key-object
(ps-gensym)))
222 ;; optionals are of form (var default-value)
227 (mapcar #'parse-optional-spec optionals
)
228 (when keys
(list options-var
)))))
229 ;; an alist of arg -> default val
234 ;; optional arguments first
235 (mapcar #'(lambda (opt-spec)
236 (multiple-value-bind (var val
) (parse-optional-spec opt-spec
)
239 (if keys?
(list (cons options-var
'(create))))
240 (mapcar #'(lambda (key-spec)
241 (multiple-value-bind (var val x y specified?
) (parse-key-spec key-spec
)
242 (declare (ignore x y
))
243 (when specified?
(cons var val
))))
245 (body-paren-forms (parse-function-body body
)) ;remove documentation
248 (mapcar #'(lambda (default-pair)
249 `(defaultf ,(car default-pair
) ,(cdr default-pair
)))
254 `(progn (defvar ,rest array
)
255 (dotimes (,i
(- arguments.length
,(length effective-args
)))
256 (setf (aref ,rest
,i
) (aref arguments
(+ ,i
,(length effective-args
)))))))
258 (effective-body (append initform-forms
(list rest-form
) body-paren-forms
))
261 (list `(with-slots ,(mapcar #'(lambda (key-spec)
262 (multiple-value-bind (var x key-name
)
263 (parse-key-spec key-spec
)
265 (list var key-name
)))
270 (values effective-args effective-body
)))))
272 (defpsmacro defun
(name lambda-list
&body body
)
273 "An extended defun macro that allows cool things like keyword arguments.
276 [&optional {var | (var [init-form [supplied-p-parameter]])}*]
278 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]]
279 [&aux {var | (var [init-form])}*])"
281 `(defun-normal ,name
,lambda-list
,@body
)
282 (progn (assert (and (= (length name
) 2) (eql 'setf
(car name
))) ()
283 "(defun ~s ~s ...) needs to have a symbol or (setf symbol) for a name." name lambda-list
)
284 `(defun-setf ,name
,lambda-list
,@body
))))
286 (defpsmacro defun-normal
(name lambda-list
&body body
)
287 (multiple-value-bind (effective-args effective-body
)
288 (parse-extended-function lambda-list body name
)
289 `(%js-defun
,name
,effective-args
292 (defvar *defun-setf-name-prefix
* "__setf_")
294 (defpsmacro defun-setf
(setf-name lambda-list
&body body
)
295 (let ((mangled-function-name (intern (concatenate 'string
*defun-setf-name-prefix
* (symbol-name (second setf-name
)))
296 (symbol-package (second setf-name
))))
297 (function-args (cdr (ordered-set-difference lambda-list lambda-list-keywords
))))
298 `(progn (defsetf ,(second setf-name
) ,(cdr lambda-list
) (store-var)
299 `(,',mangled-function-name
,store-var
,@(list ,@function-args
)))
300 (defun ,mangled-function-name
,lambda-list
,@body
))))
302 (defpsmacro lambda
(lambda-list &body body
)
303 "An extended defun macro that allows cool things like keyword arguments.
306 [&optional {var | (var [init-form [supplied-p-parameter]])}*]
308 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]]
309 [&aux {var | (var [init-form])}*])"
310 (multiple-value-bind (effective-args effective-body
)
311 (parse-extended-function lambda-list body
)
312 `(%js-lambda
,effective-args
315 (defpsmacro defsetf-long
(access-fn lambda-list
(store-var) form
)
316 (setf (get-macro-spec access-fn
*script-setf-expanders
*)
318 (let ((var-bindings (ordered-set-difference lambda-list lambda-list-keywords
)))
319 `(lambda (access-fn-args store-form
)
320 (destructuring-bind ,lambda-list
322 (let* ((,store-var
(ps-gensym))
323 (gensymed-names (loop repeat
,(length var-bindings
) collecting
(ps-gensym)))
324 (gensymed-arg-bindings (mapcar #'list gensymed-names
(list ,@var-bindings
))))
325 (destructuring-bind ,var-bindings
327 `(let (,@gensymed-arg-bindings
328 (,,store-var
,store-form
))
332 (defpsmacro defsetf-short
(access-fn update-fn
&optional docstring
)
333 (declare (ignore docstring
))
334 (setf (get-macro-spec access-fn
*script-setf-expanders
*)
335 (lambda (access-fn-args store-form
)
336 `(,update-fn
,@access-fn-args
,store-form
)))
339 (defpsmacro defsetf
(access-fn &rest args
)
340 `(,(if (= (length args
) 3) 'defsetf-long
'defsetf-short
) ,access-fn
,@args
))
342 (defpsmacro setf
(&rest args
)
343 (flet ((process-setf-clause (place value-form
)
344 (if (and (listp place
) (get-macro-spec (car place
) *script-setf-expanders
*))
345 (funcall (get-macro-spec (car place
) *script-setf-expanders
*) (cdr place
) value-form
)
346 (let ((exp-place (ps-macroexpand place
)))
347 (if (and (listp exp-place
) (get-macro-spec (car exp-place
) *script-setf-expanders
*))
348 (funcall (get-macro-spec (car exp-place
) *script-setf-expanders
*) (cdr exp-place
) value-form
)
349 `(setf1%
,exp-place
,value-form
))))))
350 (assert (evenp (length args
)) ()
351 "~s does not have an even number of arguments." (cons 'setf args
))
352 `(progn ,@(loop for
(place value
) on args by
#'cddr collect
(process-setf-clause place value
)))))