Stopped abuse of set-difference implementation-dependent ordering in defsetf.
[clinton/parenscript.git] / src / ps-macrology.lisp
CommitLineData
5aa10005
RD
1(in-package :parenscript)
2
3;;;; The macrology of the Parenscript language. Special forms and macros.
4
5;;; parenscript gensyms
6(defvar *gen-script-name-counter* 0)
7
8(defun gen-script-name-string (&key (prefix "_js_"))
9 "Generates a unique valid javascript identifier ()"
10 (concatenate 'string
11 prefix (princ-to-string (incf *gen-script-name-counter*))))
12
905f534e 13(defun gen-script-name (&key (prefix ""))
5aa10005
RD
14 "Generate a new javascript identifier."
15 (intern (gen-script-name-string :prefix prefix)
905f534e 16 (find-package :parenscript.ps-gensyms)))
5aa10005 17
7590646c
VS
18(defmacro gen-ps-name (&rest args)
19 `(gen-script-name ,@args))
20
21(defmacro with-unique-ps-names (symbols &body body)
5aa10005
RD
22 "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
23
24Each element of SYMBOLS is either a symbol or a list of (symbol
25prefix)."
26 `(let* ,(mapcar (lambda (symbol)
27 (destructuring-bind (symbol &optional prefix)
28 (if (consp symbol)
29 symbol
30 (list symbol))
31 (if prefix
32 `(,symbol (gen-script-name :prefix ,prefix))
33 `(,symbol (gen-script-name)))))
34 symbols)
35 ,@body))
36
37(defvar *var-counter* 0)
38
39(defun script-gensym (&optional (name "js"))
40 (intern (format nil "tmp-~A-~A" name (incf *var-counter*)) #.*package*))
41
46f794a4 42(defscriptmacro defaultf (place value)
bbea4c83
RD
43 `(setf ,place (or (and (=== undefined ,place) ,value)
44 ,place)))
46f794a4 45
5aa10005
RD
46;;; array literals
47(defscriptmacro list (&rest values)
48 `(array ,@values))
49
50(defscriptmacro make-array (&rest inits)
51 `(new (*array ,@inits)))
52
53;;; eval-when
54(define-script-special-form eval-when (&rest args)
55 "(eval-when form-language? (situation*) form*)
56
57The given forms are evaluated only during the given SITUATION in the specified
58FORM-LANGUAGE (either :lisp or :parenscript, def, defaulting to :lisp during
59-toplevel and :parenscript during :execute). The accepted SITUATIONS are :execute,
60:scan-toplevel. :scan-toplevel is the phase of compilation when function definitions
61and the like are being added to the compilation environment. :execute is the phase when
62the code is being evaluated by a Javascript engine."
63 (multiple-value-bind (body-language situations subforms)
64 (process-eval-when-args args)
5aa10005
RD
65 (cond
66 ((and (compiler-in-situation-p *compilation-environment* :compile-toplevel)
67 (find :compile-toplevel situations))
68 (error "Should never be processing eval-when :COMPILE-TOPLEVEL forms from here."))
69
70 ((and (compiler-in-situation-p *compilation-environment* :execute)
71 (find :execute situations))
72 (when (eql body-language :parenscript)
73 (let ((form `(progn ,@subforms)))
5aa10005
RD
74 (compile-to-statement form)))))))
75
bbea4c83
RD
76;;; slot access
77(defscriptmacro slot-value (obj &rest slots)
78 (if (null (rest slots))
79 `(%js-slot-value ,obj ,(first slots))
80 `(slot-value (slot-value ,obj ,(first slots)) ,@(rest slots))))
81
82(defscriptmacro with-slots (slots object &rest body)
83 (flet ((slot-var (slot) (if (listp slot) (first slot) slot))
84 (slot-symbol (slot) (if (listp slot) (second slot) slot)))
85 `(symbol-macrolet ,(mapcar #'(lambda (slot)
86 `(,(slot-var slot) '(slot-value ,object ',(slot-symbol slot))))
87 slots)
88 ,@body)))
89
5aa10005
RD
90;;; script packages
91(defscriptmacro defpackage (name &rest options)
92 "Defines a Parenscript package."
93 (labels ((opt-name (opt) (if (listp opt) (car opt) opt)))
94 (let ((nicknames nil) (lisp-package nil) (secondary-lisp-packages nil)
95 (exports nil) (used-packages nil) (documentation nil))
96 (dolist (opt options)
97 (case (opt-name opt)
98 (:lisp-package (setf lisp-package (second opt)))
99 (:nicknames (setf nicknames (rest opt)))
100 (:secondary-lisp-packages secondary-lisp-packages t)
101 (:export (setf exports (rest opt)))
102 (:use (setf used-packages (rest opt)))
103 (:documentation (setf documentation (second opt)))
104 (t (error "Unknown option in DEFPACKAGE: ~A" (opt-name opt)))))
105 (create-script-package
106 *compilation-environment*
107 :name name
108 :nicknames nicknames
109 :secondary-lisp-packages secondary-lisp-packages
110 :used-packages used-packages
111 :lisp-package lisp-package
112 :exports exports
113 :documentation documentation)))
114 `(progn))
115
116(defscriptmacro in-package (package-designator)
117 "Changes the current script package in the parenscript compilation environment. This mostly
118affects the reader and how it interns non-prefixed symbols"
171bbab3
RD
119 (let ((script-package
120 (find-script-package package-designator *compilation-environment*)))
121 (when (null script-package)
122 (error "~A does not designate any script package. Available script package: ~A"
123 package-designator
124 (mapcar #'script-package-name (comp-env-script-packages *compilation-environment*))))
125 (setf (comp-env-current-package *compilation-environment*)
126 script-package)
127 `(progn)))
5aa10005
RD
128
129(defscriptmacro case (value &rest clauses)
130 (labels ((make-clause (val body more)
131 (cond ((listp val)
132 (append (mapcar #'list (butlast val))
133 (make-clause (first (last val)) body more)))
134 ((member val '(t otherwise))
135 (make-clause 'default body more))
136 (more `((,val ,@body break)))
137 (t `((,val ,@body))))))
138 `(switch ,value ,@(mapcon #'(lambda (x)
139 (make-clause (car (first x))
140 (cdr (first x))
141 (rest x)))
142 clauses))))
143
144;;; let
145(define-script-special-form let (decls &rest body)
146 (let ((defvars (mapcar #'(lambda (decl)
147 (if (atom decl)
148 (make-instance 'ps-js::js-defvar
149 :names (list (compile-to-symbol decl))
150 :value nil)
151 (let ((name (first decl))
152 (value (second decl)))
153 (make-instance 'ps-js::js-defvar
154 :names (list (compile-to-symbol name))
155 :value (compile-to-expression value)))))
156 decls)))
157 (make-instance 'ps-js::js-sub-block
158 :indent " "
159 :statements (nconc defvars
160 (mapcar #'compile-to-statement body)))))
161
162;;; iteration
163(defscriptmacro dotimes (iter &rest body)
164 (let ((var (first iter))
165 (times (second iter)))
166 `(do ((,var 0 (1+ ,var)))
167 ((>= ,var ,times))
168 ,@body)))
169
170(defscriptmacro dolist (i-array &rest body)
171 (let ((var (first i-array))
172 (array (second i-array))
173 (arrvar (script-gensym "arr"))
174 (idx (script-gensym "i")))
175 `(let ((,arrvar ,array))
176 (do ((,idx 0 (1+ ,idx)))
bbea4c83 177 ((>= ,idx (slot-value ,arrvar 'global::length)))
5aa10005
RD
178 (let ((,var (aref ,arrvar ,idx)))
179 ,@body)))))
180
181;;; macros
182(defmacro with-temp-macro-environment ((var) &body body)
183 `(let* ((,var (make-macro-env-dictionary))
184 (*script-macro-env* (cons ,var *script-macro-env*)))
185 ,@body))
186
187(define-script-special-form macrolet (macros &body body)
188 (with-temp-macro-environment (macro-env-dict)
189 (dolist (macro macros)
190 (destructuring-bind (name arglist &body body)
191 macro
192 (setf (get-macro-spec name macro-env-dict)
193 (cons nil (let ((args (gensym "ps-macrolet-args-")))
194 (compile nil `(lambda (&rest ,args)
195 (destructuring-bind ,arglist
196 ,args
197 ,@body))))))))
198 (compile-script-form `(progn ,@body))))
199
200(define-script-special-form symbol-macrolet (symbol-macros &body body)
201 (with-temp-macro-environment (macro-env-dict)
202 (dolist (macro symbol-macros)
203 (destructuring-bind (name &body expansion)
204 macro
205 (setf (get-macro-spec name macro-env-dict)
206 (cons t (compile nil `(lambda () ,@expansion))))))
207 (compile-script-form `(progn ,@body))))
208
d9fc64c9
VS
209(define-script-special-form defmacro (name args &body body)
210 (define-script-macro% name args body :symbol-macro-p nil)
211 (compile-script-form '(progn)))
5aa10005 212
d9fc64c9
VS
213(define-script-special-form define-symbol-macro (name &body body)
214 (define-script-macro% name () body :symbol-macro-p t)
215 (compile-script-form '(progn)))
46f794a4 216
5aa10005
RD
217(defscriptmacro lisp (&body forms)
218 "Evaluates the given forms in Common Lisp at ParenScript
219macro-expansion time. The value of the last form is treated as a
220ParenScript expression and is inserted into the generated Javascript
1b2da35c 221\(use nil for no-op)."
5aa10005
RD
222 (eval (cons 'progn forms)))
223
34896dae 224(defscriptmacro rebind (variables &body body)
5aa10005 225 "Creates a new js lexical environment and copies the given
34896dae
AL
226variable(s) there. Executes the body in the new environment. This
227has the same effect as a new (let () ...) form in lisp but works on
228the js side for js closures."
5aa10005
RD
229 (unless (listp variables)
230 (setf variables (list variables)))
231 `((lambda ()
232 (let ((new-context (new *object)))
233 ,@(loop for variable in variables
34896dae
AL
234 collect `(setf (slot-value new-context ,(symbol-to-js variable))
235 ,variable))
5aa10005 236 (with new-context
34896dae 237 ,@body)))))
46f794a4 238
46f794a4
RD
239(eval-when (:compile-toplevel :load-toplevel :execute)
240 (defun parse-function-body (body)
241 ;; (format t "parsing function body ~A~%" body)
242 (let* ((documentation
243 (when (stringp (first body))
244 (first body)))
245 (body-forms (if documentation (rest body) body)))
246 (values
247 body-forms
248 documentation)))
249
250 (defun parse-key-spec (key-spec)
251 "parses an &key parameter. Returns 4 values:
252var, init-form, keyword-name, supplied-p-var, init-form-supplied-p.
253
254Syntax of key spec:
255[&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}*
256"
257 (let* ((var (cond ((symbolp key-spec) key-spec)
258 ((and (listp key-spec) (symbolp (first key-spec))) (first key-spec))
259 ((and (listp key-spec) (listp (first key-spec))) (second key-spec))))
260 (keyword-name (if (and (listp key-spec) (listp (first key-spec)))
261 (first (first key-spec))
262 (intern (string var) :keyword)))
263 (init-form (if (listp key-spec) (second key-spec) nil))
264 (init-form-supplied-p (if (listp key-spec) t nil))
265 (supplied-p-var (if (listp key-spec) (third key-spec) nil)))
266 (values var init-form keyword-name supplied-p-var init-form-supplied-p)))
267
268 (defun parse-optional-spec (spec)
269 "Parses an &optional parameter. Returns 3 values: var, init-form, supplied-p-var.
270[&optional {var | (var [init-form [supplied-p-parameter]])}*] "
271 (let* ((var (cond ((symbolp spec) spec)
272 ((and (listp spec) (first spec)))))
273 (init-form (if (listp spec) (second spec)))
274 (supplied-p-var (if (listp spec) (third spec))))
275 (values var init-form supplied-p-var)))
276
277 (defun parse-aux-spec (spec)
278 "Returns two values: variable and init-form"
279;; [&aux {var | (var [init-form])}*])
280 (values (if (symbolp spec) spec (first spec))
281 (when (listp spec) (second spec))))
282
283 (defun parse-extended-function (lambda-list body &optional name)
284 "Returns two values: the effective arguments and body for a function with
285the given lambda-list and body."
286
287;; The lambda list is transformed as follows, since a javascript lambda list is just a
288;; list of variable names, and you have access to the arguments variable inside the function:
289;; * standard variables are the mapped directly into the js-lambda list
290;; * optional variables' variable names are mapped directly into the lambda list,
291;; and for each optional variable with name v and default value d, a form is produced
292;; (defaultf v d)
d989d711 293;; * when any keyword variables are in the lambda list, a single 'optional-args' variable is
46f794a4
RD
294;; appended to the js-lambda list as the last argument. WITH-SLOTS is used for all
295;; the variables with inside the body of the function,
d989d711 296 ;; a (with-slots ((var-name key-name)) optional-args ...)
46f794a4 297 (declare (ignore name))
bbea4c83
RD
298 (multiple-value-bind (requireds optionals rest? rest keys? keys allow? aux? aux
299 more? more-context more-count key-object)
46f794a4 300 (parse-lambda-list lambda-list)
bbea4c83 301 (declare (ignore allow? aux? aux more? more-context more-count))
d989d711 302 (let* ((options-var (or key-object 'optional-args))
46f794a4
RD
303 ;; optionals are of form (var default-value)
304 (effective-args
305 (remove-if
306 #'null
307 (append requireds
308 (mapcar #'parse-optional-spec optionals)
309 (when keys (list options-var)))))
310 ;; an alist of arg -> default val
311 (initform-pairs
312 (remove
313 nil
314 (append
315 ;; optional arguments first
316 (mapcar #'(lambda (opt-spec)
317 (multiple-value-bind (var val) (parse-optional-spec opt-spec)
318 (cons var val)))
319 optionals)
320 (if keys? (list (cons options-var '(create))))
321 (mapcar #'(lambda (key-spec)
322 (multiple-value-bind (var val x y specified?) (parse-key-spec key-spec)
323 (declare (ignore x y))
324 (when specified? (cons var val))))
325 keys))))
326 (body-paren-forms (parse-function-body body)) ;remove documentation
327 ;;
328 (initform-forms
329 (mapcar #'(lambda (default-pair)
330 `(defaultf ,(car default-pair) ,(cdr default-pair)))
331 initform-pairs))
332 (rest-form
333 (if rest?
334 `(defvar ,rest (:.slice (to-array arguments)
335 ,(length effective-args)))
336 `(progn)))
337 (effective-body (append initform-forms (list rest-form) body-paren-forms))
338 (effective-body
339 (if keys?
340 (list `(with-slots ,(mapcar #'(lambda (key-spec)
341 (multiple-value-bind (var x key-name)
342 (parse-key-spec key-spec)
343 (declare (ignore x))
344 (list var key-name)))
345 keys)
346 ,options-var
347 ,@effective-body))
348 effective-body)))
349 (values effective-args effective-body)))))
350
351(ps:defscriptmacro defun (name lambda-list &body body)
352 "An extended defun macro that allows cool things like keyword arguments.
353lambda-list::=
354 (var*
355 [&optional {var | (var [init-form [supplied-p-parameter]])}*]
356 [&rest var]
357 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]]
358 [&aux {var | (var [init-form])}*])"
359 (multiple-value-bind (effective-args effective-body)
360 (parse-extended-function lambda-list body name)
361 `(%js-defun ,name ,effective-args
362 ,@effective-body)))
363
364
365(ps:defscriptmacro lambda (lambda-list &body body)
366 "An extended defun macro that allows cool things like keyword arguments.
367lambda-list::=
368 (var*
369 [&optional {var | (var [init-form [supplied-p-parameter]])}*]
370 [&rest var]
371 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]]
372 [&aux {var | (var [init-form])}*])"
373 (multiple-value-bind (effective-args effective-body)
374 (parse-extended-function lambda-list body)
375 `(%js-lambda ,effective-args
72332f2a
VS
376 ,@effective-body)))
377
750651b0 378(defpsmacro defsetf-long (access-fn lambda-list (store-var) form)
72332f2a
VS
379 (setf (find-macro-spec access-fn *script-setf-expanders*)
380 (compile nil
cdf9ab0e 381 (let ((var-bindings (ordered-set-difference lambda-list lambda-list-keywords)))
72332f2a
VS
382 `(lambda (access-fn-args store-form)
383 (destructuring-bind ,lambda-list
384 access-fn-args
385 (let* ((,store-var (ps:gen-ps-name))
386 (gensymed-names (loop repeat ,(length var-bindings) collecting (ps:gen-ps-name)))
387 (gensymed-arg-bindings (mapcar #'list gensymed-names (list ,@var-bindings))))
388 (destructuring-bind ,var-bindings
389 gensymed-names
cdf9ab0e 390 `(let (,@gensymed-arg-bindings
03eedaa5 391 (,,store-var ,store-form))
72332f2a
VS
392 ,,form))))))))
393 nil)
394
750651b0
VS
395(defpsmacro defsetf-short (access-fn update-fn &optional docstring)
396 (declare (ignore docstring))
397 (setf (find-macro-spec access-fn *script-setf-expanders*)
398 (lambda (access-fn-args store-form)
399 `(,update-fn ,@access-fn-args ,store-form)))
400 nil)
401
402(defpsmacro defsetf (access-fn &rest args)
403 `(,(if (= (length args) 3) 'defsetf-long 'defsetf-short) ,access-fn ,@args))
404
72332f2a
VS
405(defpsmacro setf (&rest args)
406 (flet ((process-setf-clause (place value-form)
407 (if (and (listp place) (find-macro-spec (car place) *script-setf-expanders*))
408 (funcall (find-macro-spec (car place) *script-setf-expanders*) (cdr place) value-form)
409 (let ((exp-place (expand-script-form place)))
410 (if (and (listp exp-place) (find-macro-spec (car exp-place) *script-setf-expanders*))
411 (funcall (find-macro-spec (car exp-place) *script-setf-expanders*) (cdr exp-place) value-form)
412 `(parenscript.javascript::setf1% ,exp-place ,value-form))))))
413 (assert (evenp (length args)) ()
414 "~s does not have an even number of arguments." (cons 'setf args))
415 `(progn ,@(loop for (place value) on args by #'cddr collect (process-setf-clause place value)))))