Commit | Line | Data |
---|---|---|
5aa10005 RD |
1 | (in-package :parenscript) |
2 | ||
3 | ;;;; The macrology of the Parenscript language. Special forms and macros. | |
4 | ||
5 | ;;; parenscript gensyms | |
4a987e2b | 6 | (defvar *ps-gensym-counter* 0) |
5aa10005 | 7 | |
4a987e2b | 8 | (defun ps-gensym (&optional (prefix "_js")) |
0c542be0 | 9 | (make-symbol (format nil "~A~A" prefix (incf *ps-gensym-counter*)))) |
5aa10005 | 10 | |
4a987e2b VS |
11 | (defmacro with-ps-gensyms (symbols &body body) |
12 | "Evaluate BODY with SYMBOLS bound to unique ParenScript identifiers. | |
5aa10005 RD |
13 | |
14 | Each element of SYMBOLS is either a symbol or a list of (symbol | |
4a987e2b | 15 | gensym-prefix-string)." |
5aa10005 RD |
16 | `(let* ,(mapcar (lambda (symbol) |
17 | (destructuring-bind (symbol &optional prefix) | |
18 | (if (consp symbol) | |
19 | symbol | |
20 | (list symbol)) | |
21 | (if prefix | |
4a987e2b VS |
22 | `(,symbol (ps-gensym ,prefix)) |
23 | `(,symbol (ps-gensym))))) | |
5aa10005 RD |
24 | symbols) |
25 | ,@body)) | |
26 | ||
13b8268e VS |
27 | (defun constant-literal-form-p (form) |
28 | (or (numberp form) | |
29 | (stringp form) | |
30 | (and (listp form) | |
31 | (eql 'js-literal (car form))))) | |
32 | ||
4a987e2b | 33 | (defpsmacro defaultf (place value) |
bbea4c83 RD |
34 | `(setf ,place (or (and (=== undefined ,place) ,value) |
35 | ,place))) | |
46f794a4 | 36 | |
5aa10005 | 37 | ;;; array literals |
4a987e2b | 38 | (defpsmacro list (&rest values) |
5aa10005 RD |
39 | `(array ,@values)) |
40 | ||
4a987e2b | 41 | (defpsmacro make-array (&rest inits) |
5aa10005 RD |
42 | `(new (*array ,@inits))) |
43 | ||
bbea4c83 | 44 | ;;; slot access |
4a987e2b | 45 | (defpsmacro slot-value (obj &rest slots) |
bbea4c83 RD |
46 | (if (null (rest slots)) |
47 | `(%js-slot-value ,obj ,(first slots)) | |
48 | `(slot-value (slot-value ,obj ,(first slots)) ,@(rest slots)))) | |
49 | ||
4a987e2b | 50 | (defpsmacro with-slots (slots object &rest body) |
bbea4c83 RD |
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) | |
43a1d5c3 | 54 | `(,(slot-var slot) (slot-value ,object ',(slot-symbol slot)))) |
bbea4c83 RD |
55 | slots) |
56 | ,@body))) | |
57 | ||
4a987e2b | 58 | (defpsmacro case (value &rest clauses) |
5aa10005 RD |
59 | (labels ((make-clause (val body more) |
60 | (cond ((listp val) | |
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)))))) | |
4a987e2b VS |
67 | `(switch ,value ,@(mapcon (lambda (clause) |
68 | (make-clause (car (first clause)) | |
69 | (cdr (first clause)) | |
70 | (rest clause))) | |
5aa10005 RD |
71 | clauses)))) |
72 | ||
4a987e2b | 73 | (define-ps-special-form let (expecting bindings &rest body) |
e0032a96 VS |
74 | (ecase expecting |
75 | (:statement | |
76 | (let ((defvars (mapcar (lambda (binding) (if (atom binding) | |
77 | `(defvar ,binding) | |
78 | `(defvar ,@binding))) | |
79 | bindings))) | |
80 | (compile-parenscript-form `(progn ,@defvars ,@body) :expecting :statement))) | |
81 | (:expression | |
82 | (let ((declared-variables (mapcar (lambda (binding) (if (atom binding) binding (car binding))) bindings)) | |
83 | (variable-assignments (loop for b in bindings when (listp b) collect (cons 'setf b)))) | |
84 | (setf *enclosing-lexical-block-declarations* (append declared-variables *enclosing-lexical-block-declarations*)) | |
85 | (compile-parenscript-form `(progn ,@variable-assignments ,@body) :expecting :expression))))) | |
5aa10005 RD |
86 | |
87 | ;;; iteration | |
4a987e2b | 88 | (defpsmacro dotimes (iter &rest body) |
5aa10005 RD |
89 | (let ((var (first iter)) |
90 | (times (second iter))) | |
91 | `(do ((,var 0 (1+ ,var))) | |
92 | ((>= ,var ,times)) | |
93 | ,@body))) | |
94 | ||
4a987e2b | 95 | (defpsmacro dolist (i-array &rest body) |
5aa10005 RD |
96 | (let ((var (first i-array)) |
97 | (array (second i-array)) | |
4a987e2b VS |
98 | (arrvar (ps-gensym "tmp-arr")) |
99 | (idx (ps-gensym "tmp-i"))) | |
5aa10005 RD |
100 | `(let ((,arrvar ,array)) |
101 | (do ((,idx 0 (1+ ,idx))) | |
4a987e2b | 102 | ((>= ,idx (slot-value ,arrvar 'length))) |
5aa10005 RD |
103 | (let ((,var (aref ,arrvar ,idx))) |
104 | ,@body))))) | |
105 | ||
106 | ;;; macros | |
107 | (defmacro with-temp-macro-environment ((var) &body body) | |
108 | `(let* ((,var (make-macro-env-dictionary)) | |
109 | (*script-macro-env* (cons ,var *script-macro-env*))) | |
110 | ,@body)) | |
111 | ||
4a987e2b | 112 | (define-ps-special-form macrolet (expecting macros &body body) |
45f8fec1 | 113 | (declare (ignore expecting)) |
5aa10005 RD |
114 | (with-temp-macro-environment (macro-env-dict) |
115 | (dolist (macro macros) | |
116 | (destructuring-bind (name arglist &body body) | |
117 | macro | |
118 | (setf (get-macro-spec name macro-env-dict) | |
921f2e02 | 119 | (cons nil (make-ps-macro-function arglist body))))) |
4a987e2b | 120 | (compile-parenscript-form `(progn ,@body)))) |
5aa10005 | 121 | |
4a987e2b | 122 | (define-ps-special-form symbol-macrolet (expecting symbol-macros &body body) |
45f8fec1 | 123 | (declare (ignore expecting)) |
5aa10005 RD |
124 | (with-temp-macro-environment (macro-env-dict) |
125 | (dolist (macro symbol-macros) | |
43a1d5c3 | 126 | (destructuring-bind (name expansion) |
5aa10005 RD |
127 | macro |
128 | (setf (get-macro-spec name macro-env-dict) | |
7626bb83 | 129 | (cons t (make-ps-macro-function () (list `',expansion)))))) |
4a987e2b | 130 | (compile-parenscript-form `(progn ,@body)))) |
5aa10005 | 131 | |
4a987e2b | 132 | (define-ps-special-form defmacro (expecting name args &body body) |
45f8fec1 | 133 | (declare (ignore expecting)) |
d9fc64c9 | 134 | (define-script-macro% name args body :symbol-macro-p nil) |
4a987e2b | 135 | nil) |
5aa10005 | 136 | |
7626bb83 | 137 | (define-ps-special-form define-symbol-macro (expecting name expansion) |
45f8fec1 | 138 | (declare (ignore expecting)) |
7626bb83 | 139 | (define-script-macro% name () (list `',expansion) :symbol-macro-p t) |
4a987e2b | 140 | nil) |
46f794a4 | 141 | |
4a987e2b | 142 | (defpsmacro lisp (&body forms) |
5aa10005 RD |
143 | "Evaluates the given forms in Common Lisp at ParenScript |
144 | macro-expansion time. The value of the last form is treated as a | |
145 | ParenScript expression and is inserted into the generated Javascript | |
1b2da35c | 146 | \(use nil for no-op)." |
5aa10005 RD |
147 | (eval (cons 'progn forms))) |
148 | ||
4a987e2b | 149 | (defpsmacro rebind (variables &body body) |
5aa10005 | 150 | "Creates a new js lexical environment and copies the given |
34896dae AL |
151 | variable(s) there. Executes the body in the new environment. This |
152 | has the same effect as a new (let () ...) form in lisp but works on | |
153 | the js side for js closures." | |
5aa10005 RD |
154 | (unless (listp variables) |
155 | (setf variables (list variables))) | |
156 | `((lambda () | |
157 | (let ((new-context (new *object))) | |
158 | ,@(loop for variable in variables | |
34896dae AL |
159 | collect `(setf (slot-value new-context ,(symbol-to-js variable)) |
160 | ,variable)) | |
5aa10005 | 161 | (with new-context |
34896dae | 162 | ,@body))))) |
46f794a4 | 163 | |
46f794a4 RD |
164 | (eval-when (:compile-toplevel :load-toplevel :execute) |
165 | (defun parse-function-body (body) | |
166 | ;; (format t "parsing function body ~A~%" body) | |
167 | (let* ((documentation | |
168 | (when (stringp (first body)) | |
169 | (first body))) | |
170 | (body-forms (if documentation (rest body) body))) | |
171 | (values | |
172 | body-forms | |
173 | documentation))) | |
174 | ||
175 | (defun parse-key-spec (key-spec) | |
176 | "parses an &key parameter. Returns 4 values: | |
177 | var, init-form, keyword-name, supplied-p-var, init-form-supplied-p. | |
178 | ||
179 | Syntax of key spec: | |
180 | [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* | |
181 | " | |
182 | (let* ((var (cond ((symbolp key-spec) key-spec) | |
183 | ((and (listp key-spec) (symbolp (first key-spec))) (first key-spec)) | |
184 | ((and (listp key-spec) (listp (first key-spec))) (second key-spec)))) | |
185 | (keyword-name (if (and (listp key-spec) (listp (first key-spec))) | |
186 | (first (first key-spec)) | |
187 | (intern (string var) :keyword))) | |
188 | (init-form (if (listp key-spec) (second key-spec) nil)) | |
189 | (init-form-supplied-p (if (listp key-spec) t nil)) | |
190 | (supplied-p-var (if (listp key-spec) (third key-spec) nil))) | |
191 | (values var init-form keyword-name supplied-p-var init-form-supplied-p))) | |
192 | ||
193 | (defun parse-optional-spec (spec) | |
194 | "Parses an &optional parameter. Returns 3 values: var, init-form, supplied-p-var. | |
195 | [&optional {var | (var [init-form [supplied-p-parameter]])}*] " | |
196 | (let* ((var (cond ((symbolp spec) spec) | |
197 | ((and (listp spec) (first spec))))) | |
198 | (init-form (if (listp spec) (second spec))) | |
199 | (supplied-p-var (if (listp spec) (third spec)))) | |
200 | (values var init-form supplied-p-var))) | |
201 | ||
202 | (defun parse-aux-spec (spec) | |
203 | "Returns two values: variable and init-form" | |
204 | ;; [&aux {var | (var [init-form])}*]) | |
205 | (values (if (symbolp spec) spec (first spec)) | |
206 | (when (listp spec) (second spec)))) | |
207 | ||
208 | (defun parse-extended-function (lambda-list body &optional name) | |
209 | "Returns two values: the effective arguments and body for a function with | |
210 | the given lambda-list and body." | |
211 | ||
212 | ;; The lambda list is transformed as follows, since a javascript lambda list is just a | |
213 | ;; list of variable names, and you have access to the arguments variable inside the function: | |
214 | ;; * standard variables are the mapped directly into the js-lambda list | |
215 | ;; * optional variables' variable names are mapped directly into the lambda list, | |
216 | ;; and for each optional variable with name v and default value d, a form is produced | |
217 | ;; (defaultf v d) | |
d989d711 | 218 | ;; * when any keyword variables are in the lambda list, a single 'optional-args' variable is |
46f794a4 RD |
219 | ;; appended to the js-lambda list as the last argument. WITH-SLOTS is used for all |
220 | ;; the variables with inside the body of the function, | |
d989d711 | 221 | ;; a (with-slots ((var-name key-name)) optional-args ...) |
46f794a4 | 222 | (declare (ignore name)) |
bbea4c83 RD |
223 | (multiple-value-bind (requireds optionals rest? rest keys? keys allow? aux? aux |
224 | more? more-context more-count key-object) | |
46f794a4 | 225 | (parse-lambda-list lambda-list) |
bbea4c83 | 226 | (declare (ignore allow? aux? aux more? more-context more-count)) |
2e593e4c | 227 | (let* ((options-var (or key-object (ps-gensym))) |
46f794a4 RD |
228 | ;; optionals are of form (var default-value) |
229 | (effective-args | |
230 | (remove-if | |
231 | #'null | |
232 | (append requireds | |
233 | (mapcar #'parse-optional-spec optionals) | |
234 | (when keys (list options-var))))) | |
235 | ;; an alist of arg -> default val | |
236 | (initform-pairs | |
237 | (remove | |
238 | nil | |
239 | (append | |
240 | ;; optional arguments first | |
241 | (mapcar #'(lambda (opt-spec) | |
242 | (multiple-value-bind (var val) (parse-optional-spec opt-spec) | |
243 | (cons var val))) | |
244 | optionals) | |
245 | (if keys? (list (cons options-var '(create)))) | |
246 | (mapcar #'(lambda (key-spec) | |
247 | (multiple-value-bind (var val x y specified?) (parse-key-spec key-spec) | |
248 | (declare (ignore x y)) | |
249 | (when specified? (cons var val)))) | |
250 | keys)))) | |
251 | (body-paren-forms (parse-function-body body)) ;remove documentation | |
252 | ;; | |
253 | (initform-forms | |
254 | (mapcar #'(lambda (default-pair) | |
255 | `(defaultf ,(car default-pair) ,(cdr default-pair))) | |
256 | initform-pairs)) | |
257 | (rest-form | |
258 | (if rest? | |
44934751 VS |
259 | (with-ps-gensyms (i) |
260 | `(progn (defvar ,rest array) | |
261 | (dotimes (,i (- arguments.length ,(length effective-args))) | |
262 | (setf (aref ,rest ,i) (aref arguments (+ ,i ,(length effective-args))))))) | |
46f794a4 RD |
263 | `(progn))) |
264 | (effective-body (append initform-forms (list rest-form) body-paren-forms)) | |
265 | (effective-body | |
266 | (if keys? | |
267 | (list `(with-slots ,(mapcar #'(lambda (key-spec) | |
268 | (multiple-value-bind (var x key-name) | |
269 | (parse-key-spec key-spec) | |
270 | (declare (ignore x)) | |
271 | (list var key-name))) | |
272 | keys) | |
273 | ,options-var | |
274 | ,@effective-body)) | |
275 | effective-body))) | |
276 | (values effective-args effective-body))))) | |
277 | ||
4a987e2b | 278 | (defpsmacro defun (name lambda-list &body body) |
46f794a4 RD |
279 | "An extended defun macro that allows cool things like keyword arguments. |
280 | lambda-list::= | |
281 | (var* | |
282 | [&optional {var | (var [init-form [supplied-p-parameter]])}*] | |
283 | [&rest var] | |
284 | [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]] | |
285 | [&aux {var | (var [init-form])}*])" | |
dbb7017b | 286 | (if (symbolp name) |
e0032a96 | 287 | `(defun-function ,name ,lambda-list ,@body) |
dbb7017b VS |
288 | (progn (assert (and (= (length name) 2) (eql 'setf (car name))) () |
289 | "(defun ~s ~s ...) needs to have a symbol or (setf symbol) for a name." name lambda-list) | |
290 | `(defun-setf ,name ,lambda-list ,@body)))) | |
291 | ||
e0032a96 | 292 | (defpsmacro defun-function (name lambda-list &body body) |
46f794a4 RD |
293 | (multiple-value-bind (effective-args effective-body) |
294 | (parse-extended-function lambda-list body name) | |
295 | `(%js-defun ,name ,effective-args | |
296 | ,@effective-body))) | |
297 | ||
dbb7017b VS |
298 | (defvar *defun-setf-name-prefix* "__setf_") |
299 | ||
4a987e2b | 300 | (defpsmacro defun-setf (setf-name lambda-list &body body) |
dbb7017b VS |
301 | (let ((mangled-function-name (intern (concatenate 'string *defun-setf-name-prefix* (symbol-name (second setf-name))) |
302 | (symbol-package (second setf-name)))) | |
303 | (function-args (cdr (ordered-set-difference lambda-list lambda-list-keywords)))) | |
304 | `(progn (defsetf ,(second setf-name) ,(cdr lambda-list) (store-var) | |
305 | `(,',mangled-function-name ,store-var ,@(list ,@function-args))) | |
306 | (defun ,mangled-function-name ,lambda-list ,@body)))) | |
46f794a4 | 307 | |
4a987e2b | 308 | (defpsmacro lambda (lambda-list &body body) |
46f794a4 RD |
309 | "An extended defun macro that allows cool things like keyword arguments. |
310 | lambda-list::= | |
311 | (var* | |
312 | [&optional {var | (var [init-form [supplied-p-parameter]])}*] | |
313 | [&rest var] | |
314 | [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]] | |
315 | [&aux {var | (var [init-form])}*])" | |
316 | (multiple-value-bind (effective-args effective-body) | |
317 | (parse-extended-function lambda-list body) | |
318 | `(%js-lambda ,effective-args | |
72332f2a VS |
319 | ,@effective-body))) |
320 | ||
4a987e2b | 321 | (defpsmacro defsetf-long (access-fn lambda-list (store-var) form) |
06babcf5 | 322 | (setf (get-macro-spec access-fn *script-setf-expanders*) |
72332f2a | 323 | (compile nil |
cdf9ab0e | 324 | (let ((var-bindings (ordered-set-difference lambda-list lambda-list-keywords))) |
72332f2a VS |
325 | `(lambda (access-fn-args store-form) |
326 | (destructuring-bind ,lambda-list | |
327 | access-fn-args | |
4a987e2b VS |
328 | (let* ((,store-var (ps-gensym)) |
329 | (gensymed-names (loop repeat ,(length var-bindings) collecting (ps-gensym))) | |
72332f2a VS |
330 | (gensymed-arg-bindings (mapcar #'list gensymed-names (list ,@var-bindings)))) |
331 | (destructuring-bind ,var-bindings | |
332 | gensymed-names | |
cdf9ab0e | 333 | `(let (,@gensymed-arg-bindings |
03eedaa5 | 334 | (,,store-var ,store-form)) |
72332f2a VS |
335 | ,,form)))))))) |
336 | nil) | |
337 | ||
4a987e2b | 338 | (defpsmacro defsetf-short (access-fn update-fn &optional docstring) |
750651b0 | 339 | (declare (ignore docstring)) |
06babcf5 | 340 | (setf (get-macro-spec access-fn *script-setf-expanders*) |
750651b0 VS |
341 | (lambda (access-fn-args store-form) |
342 | `(,update-fn ,@access-fn-args ,store-form))) | |
343 | nil) | |
344 | ||
345 | (defpsmacro defsetf (access-fn &rest args) | |
346 | `(,(if (= (length args) 3) 'defsetf-long 'defsetf-short) ,access-fn ,@args)) | |
347 | ||
72332f2a VS |
348 | (defpsmacro setf (&rest args) |
349 | (flet ((process-setf-clause (place value-form) | |
06babcf5 VS |
350 | (if (and (listp place) (get-macro-spec (car place) *script-setf-expanders*)) |
351 | (funcall (get-macro-spec (car place) *script-setf-expanders*) (cdr place) value-form) | |
4a987e2b | 352 | (let ((exp-place (ps-macroexpand place))) |
06babcf5 VS |
353 | (if (and (listp exp-place) (get-macro-spec (car exp-place) *script-setf-expanders*)) |
354 | (funcall (get-macro-spec (car exp-place) *script-setf-expanders*) (cdr exp-place) value-form) | |
4a987e2b | 355 | `(setf1% ,exp-place ,value-form)))))) |
72332f2a VS |
356 | (assert (evenp (length args)) () |
357 | "~s does not have an even number of arguments." (cons 'setf args)) | |
358 | `(progn ,@(loop for (place value) on args by #'cddr collect (process-setf-clause place value))))) |