1 (in-package #:parenscript
)
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4 ;;; arithmetic and logic
6 (defmacro define-trivial-special-ops
(&rest mappings
)
7 `(progn ,@(loop for
(form-name js-primitive
) on mappings by
#'cddr collect
8 `(define-expression-operator ,form-name
(&rest args
)
9 (cons ',js-primitive
(mapcar #'compile-expression args
))))))
11 (define-trivial-special-ops
24 ;; todo: ash for shifts
32 (define-expression-operator -
(&rest args
)
33 (let ((args (mapcar #'compile-expression args
)))
34 (cons (if (cdr args
) 'js
:-
'js
:negate
) args
)))
36 (defun fix-nary-comparison (operator objects
)
37 (let* ((tmp-var-forms (butlast (cdr objects
)))
38 (tmp-vars (loop repeat
(length tmp-var-forms
)
39 collect
(ps-gensym "_cmp")))
40 (all-comparisons (append (list (car objects
))
43 `(let ,(mapcar #'list tmp-vars tmp-var-forms
)
44 (and ,@(loop for x1 in all-comparisons
45 for x2 in
(cdr all-comparisons
)
46 collect
(list operator x1 x2
))))))
48 (macrolet ((define-nary-comparison-forms (&rest mappings
)
50 ,@(loop for
(form js-primitive
) on mappings by
#'cddr collect
51 `(define-expression-operator ,form
(&rest objects
)
54 (fix-nary-comparison ',form objects
))
56 (mapcar #'compile-expression objects
))))))))
57 (define-nary-comparison-forms
65 (define-expression-operator /= (a b
)
66 ;; for n>2, /= is finding duplicates in an array of numbers (ie -
67 ;; nontrivial runtime algorithm), so we restrict it to binary in PS
68 `(js:!== ,(compile-expression a
) ,(compile-expression b
)))
70 (define-expression-operator incf
(x &optional
(delta 1))
71 (let ((delta (ps-macroexpand delta
)))
73 `(js:++ ,(compile-expression x
))
74 `(js:+= ,(compile-expression x
) ,(compile-expression delta
)))))
76 (define-expression-operator decf
(x &optional
(delta 1))
77 (let ((delta (ps-macroexpand delta
)))
79 `(js:--
,(compile-expression x
))
80 `(js:-
= ,(compile-expression x
) ,(compile-expression delta
)))))
82 (let ((inverses (mapcan (lambda (x)
88 (define-expression-operator not
(x)
89 (let ((form (compile-expression x
)))
90 (acond ((and (listp form
) (eq (car form
) 'js
:!))
92 ((and (listp form
) (cadr (assoc (car form
) inverses
)))
96 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
97 ;;; blocks and control flow
99 (defun compile-progn (body)
100 (labels ((flatten-blocks (body)
102 (if (and (listp (car body
)) (eq 'js
:block
(caar body
)))
103 (append (cdr (car body
)) (flatten-blocks (cdr body
)))
104 (cons (car body
) (flatten-blocks (cdr body
)))))))
105 (let ((block (flatten-blocks (remove nil
(mapcar #'ps-compile body
)))))
106 (append (remove-if #'constantp
(butlast block
))
107 (unless (and (eq *compilation-level
* :toplevel
)
108 (not (car (last block
))))
111 (define-expression-operator progn
(&rest body
)
113 `(js:|
,|
,@(compile-progn body
))
114 (compile-expression (car body
))))
116 (define-statement-operator progn
(&rest body
)
117 `(js:block
,@(compile-progn body
)))
119 (defun wrap-block-for-dynamic-return (tag body
)
120 (if (member tag
*tags-that-return-throws-to
*)
123 :catch
(err ,(compile-statement `(progn (if (and err
(eql ',tag
(getprop err
:ps-block-tag
)))
124 ;; FIXME make this a multiple-value return
125 (getprop err
:ps-return-value
)
130 (define-statement-operator block
(name &rest body
)
131 (let* ((name (or name
'nilBlock
))
132 (*lexical-extent-return-tags
* (cons name
*lexical-extent-return-tags
*))
133 (*tags-that-return-throws-to
* ()))
134 `(js:label
,name
,(wrap-block-for-dynamic-return name
(compile-statement `(progn ,@body
))))))
136 (defun nesting-depth (form)
138 (max (1+ (nesting-depth (car form
))) (nesting-depth (cdr form
)))
141 (define-statement-operator return-from
(tag &optional result
)
146 (warn "Trying to (RETURN ~A) from inside a loop with an implicit nil block (DO, DOLIST, DOTIMES, etc.). Parenscript doesn't support returning values this way from inside a loop yet!" result
))
148 (ps-compile `(return-from nilBlock
,result
)))
149 (let ((form (ps-macroexpand result
)))
150 (flet ((return-exp (value) ;; this stuff needs to be fixed to handle multiple-value returns, too
151 (let ((value (compile-expression value
)))
152 (cond ((or (eql '%function-body tag
) (eql *function-block-name
* tag
))
154 ((member tag
*lexical-extent-return-tags
*)
156 (warn "Trying to (RETURN-FROM ~A ~A) a value from a block. Parenscript doesn't support returning values this way from blocks yet!" tag result
))
158 ((member tag
*dynamic-extent-return-tags
*)
159 (push tag
*tags-that-return-throws-to
*)
160 (ps-compile `(throw (create :ps-block-tag
',tag
:ps-return-value
,value
))))
161 (t (warn "Returning from unknown block ~A" tag
)
162 `(js:return
,value
)))))) ;; for backwards-compatibility
168 `(progn ,@(butlast (cdr form
)) (return-from ,tag
,(car (last (cdr form
))))))
170 `(switch ,(second form
)
171 ,@(loop for
(cvalue . cbody
) in
(cddr form
)
172 for remaining on
(cddr form
) collect
173 (let ((last-n (cond ((or (eq 'default cvalue
) (not (cdr remaining
)))
175 ((eq 'break
(car (last cbody
)))
178 (let ((result-form (car (last cbody last-n
))))
180 ,@(butlast cbody last-n
)
181 (return-from ,tag
,result-form
)
182 ,@(when (and (= last-n
2) (member 'if
(flatten result-form
))) '(break))))
183 (cons cvalue cbody
))))))
185 `(try (return-from ,tag
,(second form
))
186 ,@(let ((catch (cdr (assoc :catch
(cdr form
))))
187 (finally (assoc :finally
(cdr form
))))
189 `(:catch
,(car catch
)
190 ,@(butlast (cdr catch
))
191 (return-from ,tag
,(car (last (cdr catch
))))))
194 `(cond ,@(loop for clause in
(cdr form
) collect
196 (return-from ,tag
,(car (last clause
)))))))
197 ((with label let flet labels macrolet symbol-macrolet
) ;; implicit progn forms
198 `(,(first form
) ,(second form
)
199 ,@(butlast (cddr form
))
200 (return-from ,tag
,(car (last (cddr form
))))))
201 ((continue break throw
) ;; non-local exit
203 (return-from ;; this will go away someday
205 (warn 'simple-style-warning
206 :format-control
"Trying to RETURN a RETURN without a block tag specified. Perhaps you're still returning values from functions by hand? Parenscript now implements implicit return, update your code! Things like (lambda () (return x)) are not valid Common Lisp and may not be supported in future versions of Parenscript."))
209 (aif (and (<= (nesting-depth form
) 3) (handler-case (compile-expression form
) (compile-expression-error () nil
)))
210 (return-from expressionize
`(js:return
,it
))
212 (return-from ,tag
,(third form
))
213 ,@(when (fourth form
) `((return-from ,tag
,(fourth form
)))))))
215 (if (gethash (car form
) *special-statement-operators
*)
216 form
;; by now only special forms that return nil should be left, so this is ok for implicit return
217 (return-from expressionize
(return-exp form
)))))))
218 (return-exp form
))))))
220 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
223 (define-expression-operator if
(test then
&optional else
)
224 `(js:?
,(compile-expression test
) ,(compile-expression then
) ,(compile-expression else
)))
226 (define-statement-operator if
(test then
&optional else
)
227 `(js:if
,(compile-expression test
)
228 ,(compile-statement `(progn ,then
))
229 ,@(when else
`(:else
,(compile-statement `(progn ,else
))))))
231 (define-expression-operator cond
(&rest clauses
)
234 (destructuring-bind (test &rest body
) (car clauses
)
239 (cond ,@(cdr clauses
))))))))
241 (define-statement-operator cond
(&rest clauses
)
242 `(js:if
,(compile-expression (caar clauses
))
243 ,(compile-statement `(progn ,@(cdar clauses
)))
244 ,@(loop for
(test . body
) in
(cdr clauses
) appending
246 `(:else
,(compile-statement `(progn ,@body
)))
247 `(:else-if
,(compile-expression test
)
248 ,(compile-statement `(progn ,@body
)))))))
250 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
253 (defmacro with-declaration-effects
(body-var &body body
)
254 `(let* ((local-specials (when (and (listp (car ,body-var
))
255 (eq (caar ,body-var
) 'declare
))
256 (cdr (find 'special
(cdar ,body-var
) :key
#'car
))))
257 (,body-var
(if local-specials
260 (*special-variables
* (append local-specials
*special-variables
*)))
263 (defun compile-function-definition (args body
)
264 (with-declaration-effects body
265 (let* ((*enclosing-lexical-block-declarations
* ())
266 (*enclosing-lexicals
* (append args
*enclosing-lexicals
*))
267 (body (let ((in-loop-scope? nil
)
268 (*loop-scope-lexicals
* ())
269 (*loop-scope-lexicals-captured
* ()))
270 (compile-statement `(return-from %function-body
(progn ,@body
)))))
271 (var-decls (compile-statement
272 `(progn ,@(mapcar (lambda (var) `(var ,var
))
273 (remove-duplicates *enclosing-lexical-block-declarations
*))))))
274 (when in-loop-scope?
;; this is probably broken when it comes to let-renaming
275 (setf *loop-scope-lexicals-captured
* (append (intersection (flatten body
) *loop-scope-lexicals
*)
276 *loop-scope-lexicals-captured
*)))
277 `(js:block
,@(cdr var-decls
) ,@(cdr body
)))))
279 (define-expression-operator %js-lambda
(args &rest body
)
280 (let ((*function-block-name
* nil
)
281 (*dynamic-extent-return-tags
* (append (when *function-block-name
* (list *function-block-name
*))
282 *lexical-extent-return-tags
*
283 *dynamic-extent-return-tags
*))
284 (*lexical-extent-return-tags
* ()))
285 `(js:lambda
,args
,(compile-function-definition args body
))))
287 (define-statement-operator %js-defun
(name args
&rest body
)
288 (let ((docstring (and (cdr body
) (stringp (car body
)) (car body
)))
289 (*enclosing-lexicals
* (cons name
*enclosing-lexicals
*))
290 (*function-block-name
* name
)
291 (*lexical-extent-return-tags
* ())
292 (*dynamic-extent-return-tags
* ())
293 (*tags-that-return-throws-to
* ()))
294 `(js:defun
,name
,args
,docstring
295 ,(wrap-block-for-dynamic-return name
(compile-function-definition args
(if docstring
(cdr body
) body
))))))
297 (defun parse-key-spec (key-spec)
298 "parses an &key parameter. Returns 5 values:
299 var, init-form, keyword-name, supplied-p-var, init-form-supplied-p.
302 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}*
304 (let* ((var (cond ((symbolp key-spec
) key-spec
)
305 ((and (listp key-spec
) (symbolp (first key-spec
))) (first key-spec
))
306 ((and (listp key-spec
) (listp (first key-spec
))) (second (first key-spec
)))))
307 (keyword-name (if (and (listp key-spec
) (listp (first key-spec
)))
308 (first (first key-spec
))
309 (intern (string var
) :keyword
)))
310 (init-form (if (listp key-spec
) (second key-spec
) nil
))
311 (init-form-supplied-p (if (listp key-spec
) t nil
))
312 (supplied-p-var (if (listp key-spec
) (third key-spec
) nil
)))
313 (values var init-form keyword-name supplied-p-var init-form-supplied-p
)))
315 (defun parse-optional-spec (spec)
316 "Parses an &optional parameter. Returns 3 values: var, init-form, supplied-p-var.
317 [&optional {var | (var [init-form [supplied-p-parameter]])}*] "
318 (let* ((var (cond ((symbolp spec
) spec
)
319 ((and (listp spec
) (first spec
)))))
320 (init-form (if (listp spec
) (second spec
)))
321 (supplied-p-var (if (listp spec
) (third spec
))))
322 (values var init-form supplied-p-var
)))
324 (defun parse-extended-function (lambda-list body
)
325 ;; The lambda list is transformed as follows:
327 ;; * standard and optional variables are the mapped directly into
328 ;; the js-lambda list
330 ;; * keyword variables are not included in the js-lambda list, but
331 ;; instead are obtained from the magic js ARGUMENTS
332 ;; pseudo-array. Code assigning values to keyword vars is
333 ;; prepended to the body of the function.
334 (multiple-value-bind (requireds optionals rest? rest keys? keys allow? aux?
335 aux more? more-context more-count key-object
)
336 (parse-lambda-list lambda-list
)
337 (declare (ignore allow? aux? aux more? more-context more-count key-object
))
338 (let* ( ;; optionals are of form (var default-value)
342 (mapcar #'parse-optional-spec optionals
))))
344 (mapcar (lambda (opt-spec)
345 (multiple-value-bind (name value suppl
)
346 (parse-optional-spec opt-spec
)
349 (var ,suppl
(not (eql ,name undefined
)))
351 `((when (not ,suppl
) (setf ,name
,value
)))))
353 `(when (eql ,name undefined
)
354 (setf ,name
,value
))))))
363 (multiple-value-bind (var init-form keyword-str suppl
)
365 (push `(var ,var
,init-form
) decls
)
366 (when suppl
(push `(var ,suppl nil
) decls
))
368 (setf ,var
(aref arguments
(1+ ,n
))
369 ,@(when suppl
`(,suppl t
))))
373 (loop for
,n from
,(length requireds
)
374 below
(length arguments
) by
2 do
375 (case (aref arguments
,n
) ,@assigns
)))))))
379 `(progn (var ,rest
(array))
380 (dotimes (,i
(- (getprop arguments
'length
)
381 ,(length effective-args
)))
385 (+ ,i
,(length effective-args
)))))))))
386 (docstring (when (stringp (first body
)) (first body
)))
387 (body-paren-forms (if docstring
(rest body
) body
))
388 (effective-body (append (when docstring
(list docstring
))
391 (awhen rest-form
(list it
))
393 (values effective-args effective-body
))))
395 (defun maybe-rename-local-function (fun-name)
396 (aif (getf *local-function-names
* fun-name
)
400 (defun collect-function-names (fn-defs)
401 (loop for
(fn-name) in fn-defs
403 collect
(if (or (member fn-name
*enclosing-lexicals
*) (lookup-macro-def fn-name
*symbol-macro-env
*))
407 (define-expression-operator flet
(fn-defs &rest body
)
408 (let* ((fn-renames (collect-function-names fn-defs
))
409 ;; the function definitions need to be compiled with previous lexical bindings
410 (fn-defs (loop for
(fn-name .
(args . body
)) in fn-defs collect
411 (progn (when compile-expression?
412 (push (getf fn-renames fn-name
) *enclosing-lexical-block-declarations
*))
413 `(,(if compile-expression?
'js
:= 'js
:var
)
414 ,(getf fn-renames fn-name
)
416 ,(let ((*function-block-name
* fn-name
))
417 (compile-function-definition args body
)))))))
418 ;; the flet body needs to be compiled with the extended lexical environment
419 (*enclosing-lexicals
* (append fn-renames
*enclosing-lexicals
*))
420 (*loop-scope-lexicals
* (when in-loop-scope?
(append fn-renames
*loop-scope-lexicals
*)))
421 (*local-function-names
* (append fn-renames
*local-function-names
*)))
422 `(,(if compile-expression?
'js
:|
,|
'js
:block
)
424 ,@(compile-progn body
))))
426 (define-expression-operator labels
(fn-defs &rest body
)
427 (let* ((fn-renames (collect-function-names fn-defs
))
428 (*local-function-names
* (append fn-renames
*local-function-names
*))
429 (*enclosing-lexicals
* (append fn-renames
*enclosing-lexicals
*))
430 (*loop-scope-lexicals
* (when in-loop-scope?
(append fn-renames
*loop-scope-lexicals
*))))
431 `(,(if compile-expression?
'js
:|
,|
'js
:block
)
432 ,@(loop for
(fn-name .
(args . body
)) in fn-defs collect
433 (progn (when compile-expression?
434 (push (getf *local-function-names
* fn-name
) *enclosing-lexical-block-declarations
*))
435 `(,(if compile-expression?
'js
:= 'js
:var
)
436 ,(getf *local-function-names
* fn-name
)
438 ,(let ((*function-block-name
* fn-name
))
439 (compile-function-definition args body
))))))
440 ,@(compile-progn body
))))
442 (define-expression-operator function
(fn-name)
443 (ps-compile (maybe-rename-local-function fn-name
)))
445 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
448 (defmacro with-local-macro-environment
((var env
) &body body
)
449 `(let* ((,var
(make-macro-dictionary))
450 (,env
(cons ,var
,env
)))
453 (define-expression-operator macrolet
(macros &body body
)
454 (with-local-macro-environment (local-macro-dict *macro-env
*)
455 (dolist (macro macros
)
456 (destructuring-bind (name arglist
&body body
)
458 (setf (gethash name local-macro-dict
)
459 (eval (make-ps-macro-function arglist body
)))))
460 (ps-compile `(progn ,@body
))))
462 (define-expression-operator symbol-macrolet
(symbol-macros &body body
)
463 (with-local-macro-environment (local-macro-dict *symbol-macro-env
*)
464 (let (local-var-bindings)
465 (dolist (macro symbol-macros
)
466 (destructuring-bind (name expansion
) macro
467 (setf (gethash name local-macro-dict
) (lambda (x) (declare (ignore x
)) expansion
))
468 (push name local-var-bindings
)))
469 (let ((*enclosing-lexicals
* (append local-var-bindings
*enclosing-lexicals
*)))
470 (ps-compile `(progn ,@body
))))))
472 (define-expression-operator defmacro
(name args
&body body
)
473 (eval `(defpsmacro ,name
,args
,@body
))
476 (define-expression-operator define-symbol-macro
(name expansion
)
477 (eval `(define-ps-symbol-macro ,name
,expansion
))
480 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
481 ;;; assignment and binding
483 (defun assignment-op (op)
498 (define-expression-operator ps-assign
(lhs rhs
)
499 (let ((rhs (ps-macroexpand rhs
)))
500 (if (and (listp rhs
) (eq (car rhs
) 'progn
))
501 (ps-compile `(progn ,@(butlast (cdr rhs
)) (ps-assign ,lhs
,(car (last (cdr rhs
))))))
502 (let ((lhs (compile-expression lhs
))
503 (rhs (compile-expression rhs
)))
504 (aif (and (listp rhs
)
506 (equal lhs
(second rhs
))
507 (assignment-op (first rhs
)))
508 (list it lhs
(if (fourth rhs
)
509 (cons (first rhs
) (cddr rhs
))
511 (list 'js
:= lhs rhs
))))))
513 (define-expression-operator let
(bindings &body body
)
514 (with-declaration-effects body
515 (let* ((lexical-bindings-introduced-here ())
516 (normalized-bindings (mapcar (lambda (x)
519 (list (car x
) (ps-macroexpand (cadr x
)))))
521 (free-variables-in-binding-value-expressions (mapcan (lambda (x) (flatten (cadr x
)))
522 normalized-bindings
)))
523 (flet ((maybe-rename-lexical-var (x)
524 (if (or (member x
*enclosing-lexicals
*)
525 (lookup-macro-def x
*symbol-macro-env
*)
526 (member x free-variables-in-binding-value-expressions
))
528 (progn (push x lexical-bindings-introduced-here
) nil
)))
529 (rename (x) (first x
))
532 (let* ((lexical-bindings (loop for x in normalized-bindings
533 unless
(special-variable?
(car x
))
534 collect
(cons (maybe-rename-lexical-var (car x
)) x
)))
535 (dynamic-bindings (loop for x in normalized-bindings
536 when
(special-variable?
(car x
))
537 collect
(cons (ps-gensym (format nil
"~A_~A" (car x
) 'tmp-stack
)) x
)))
538 (renamed-body `(symbol-macrolet ,(loop for x in lexical-bindings
539 when
(rename x
) collect
540 `(,(var x
) ,(rename x
)))
542 (*enclosing-lexicals
* (append lexical-bindings-introduced-here
*enclosing-lexicals
*))
543 (*loop-scope-lexicals
* (when in-loop-scope?
(append lexical-bindings-introduced-here
*loop-scope-lexicals
*))))
546 ,@(mapcar (lambda (x) `(var ,(or (rename x
) (var x
)) ,(val x
)))
548 ,(if dynamic-bindings
549 `(progn ,@(mapcar (lambda (x) `(var ,(rename x
)))
552 (setf ,@(loop for x in dynamic-bindings append
553 `(,(rename x
) ,(var x
)
557 (setf ,@(mapcan (lambda (x) `(,(var x
) ,(rename x
)))
558 dynamic-bindings
)))))
561 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
564 (defun make-for-vars/inits
(init-forms)
566 (cons (ps-macroexpand (if (atom x
) x
(first x
)))
567 (compile-expression (if (atom x
) nil
(second x
)))))
570 (defun compile-loop-body (loop-vars body
)
571 (let* ((in-loop-scope? t
)
572 (*loop-scope-lexicals
* loop-vars
)
573 (*loop-scope-lexicals-captured
* ())
574 (*ps-gensym-counter
* *ps-gensym-counter
*)
575 (compiled-body (compile-statement `(progn ,@body
))))
576 (aif (remove-duplicates *loop-scope-lexicals-captured
*)
578 (js:with
,(compile-expression
579 `(create ,@(loop for x in it
581 collect
(when (member x loop-vars
) x
))))
585 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
588 (define-expression-operator quote
(x)
589 (flet ((quote%
(expr) (when expr
`',expr
)))
592 (cons `(array ,@(mapcar #'quote% x
)))
595 (symbol (symbol-to-js-string x
))
598 (vector `(array ,@(loop for el across x collect
(quote% el
))))))))
600 (define-expression-operator eval-when
(situation-list &body body
)
601 "The body is evaluated only during the given situations. The
602 accepted situations are :load-toplevel, :compile-toplevel,
603 and :execute. The code in BODY is assumed to be Common Lisp code
604 in :compile-toplevel and :load-toplevel sitations, and Parenscript
606 (when (and (member :compile-toplevel situation-list
)
607 (member *compilation-level
* '(:toplevel
:inside-toplevel-form
)))
608 (eval `(progn ,@body
)))
609 (if (member :execute situation-list
)
610 (ps-compile `(progn ,@body
))
611 (ps-compile `(progn))))