1 (in-package "PARENSCRIPT")
3 (defmacro with-local-macro-environment
((var env
) &body body
)
4 `(let* ((,var
(make-macro-dictionary))
5 (,env
(cons ,var
,env
)))
8 (macrolet ((define-trivial-special-forms (&rest mappings
)
10 ,@(loop for
(form-name js-primitive
) on mappings by
#'cddr
12 `(define-ps-special-form ,form-name
(&rest args
)
14 (mapcar #'compile-expression args
)))))))
15 (define-trivial-special-forms
28 ;; todo: ash for shifts
34 instanceof js
:instanceof
38 in js
:in
;; maybe rename to slot-boundp?
43 (define-ps-special-form -
(&rest args
)
44 (let ((args (mapcar #'compile-expression args
)))
45 (cons (if (cdr args
) 'js
:-
'js
:negate
) args
)))
47 (defun fix-nary-comparison (operator objects
)
48 (let* ((tmp-var-forms (butlast (cdr objects
)))
49 (tmp-vars (loop repeat
(length tmp-var-forms
)
50 collect
(ps-gensym "_cmp")))
51 (all-comparisons (append (list (car objects
))
54 `(let ,(mapcar #'list tmp-vars tmp-var-forms
)
55 (and ,@(loop for x1 in all-comparisons
56 for x2 in
(cdr all-comparisons
)
57 collect
(list operator x1 x2
))))))
59 (macrolet ((define-nary-comparison-forms (&rest mappings
)
61 ,@(loop for
(form js-primitive
) on mappings by
#'cddr collect
62 `(define-ps-special-form ,form
(&rest objects
)
65 (fix-nary-comparison ',form objects
))
67 (mapcar #'compile-expression objects
))))))))
68 (define-nary-comparison-forms
76 (define-ps-special-form /= (a b
)
77 ;; for n>2, /= is finding duplicates in an array of numbers (ie -
78 ;; nontrivial runtime algorithm), so we restrict it to binary in PS
79 `(js:!== ,(compile-expression a
) ,(compile-expression b
)))
81 (define-ps-special-form quote
(x)
82 (flet ((quote%
(expr) (when expr
`',expr
)))
85 (cons `(array ,@(mapcar #'quote% x
)))
88 (symbol (symbol-to-js-string x
))
91 (vector `(array ,@(loop :for el
:across x
:collect
(quote% el
))))))))
93 (defun ps-statement?
(exp)
101 (defun implicit-progn-form?
(form)
102 (member (car form
) '(with progn label let flet labels macrolet symbol-macrolet
)))
104 (define-ps-special-form return
(&optional value force-conditional?
)
105 (let ((value (ps-macroexpand value
)))
106 (if (ps-statement? value
)
107 (compile-statement value
)
109 (if (implicit-progn-form? value
)
110 (ps-compile (append (butlast value
)
111 `((return ,@(last value
)
112 ,force-conditional?
))))
118 `(switch ,(second value
)
119 ,@(loop for
(cvalue . cbody
) in
(cddr value
)
120 for remaining on
(cddr value
) collect
122 (cond ((or (eq 'default cvalue
)
123 (not (cdr remaining
)))
130 ,@(butlast cbody last-n
)
132 ,(car (last cbody last-n
))
134 (cons cvalue cbody
)))))))
137 `(try (return ,(second value
) t
)
138 ,@(let ((catch (cdr (assoc :catch
(cdr value
))))
139 (finally (assoc :finally
(cdr value
))))
141 `(:catch
,(car catch
)
142 ,@(butlast (cdr catch
))
143 (return ,@(last (cdr catch
)) t
)))
146 (ps-compile `(if ,(second value
)
147 (return ,(third value
) ,force-conditional?
)
148 ,@(acond ((fourth value
)
150 ,force-conditional?
)))
155 ,@(loop for clause in
(cdr value
) collect
157 (return ,@(last clause
)
158 ,force-conditional?
))))))
160 `(js:return
,(compile-expression value
)))))
161 `(js:return
,(compile-expression value
))))))
163 (define-ps-special-form incf
(x &optional
(delta 1))
164 (let ((delta (ps-macroexpand delta
)))
166 `(js:++ ,(compile-expression x
))
167 `(js:+= ,(compile-expression x
) ,(compile-expression delta
)))))
169 (define-ps-special-form decf
(x &optional
(delta 1))
170 (let ((delta (ps-macroexpand delta
)))
172 `(js:--
,(compile-expression x
))
173 `(js:-
= ,(compile-expression x
) ,(compile-expression delta
)))))
175 (let ((inverses (mapcan (lambda (x)
176 (list x
(reverse x
)))
181 (define-ps-special-form not
(x)
182 (let ((form (compile-expression x
)))
183 (acond ((and (listp form
) (eq (car form
) 'js
:!))
185 ((and (listp form
) (cadr (assoc (car form
) inverses
)))
187 (t `(js:! ,form
))))))
189 (defun flatten-blocks (body)
191 (if (and (listp (car body
))
192 (eq 'js
:block
(caar body
)))
193 (append (cdr (car body
)) (flatten-blocks (cdr body
)))
194 (cons (car body
) (flatten-blocks (cdr body
))))))
196 (define-ps-special-form progn
(&rest body
)
197 (let ((body (mapcar #'ps-macroexpand body
)))
198 (if (and compile-expression?
(= 1 (length body
)))
199 (compile-expression (car body
))
200 `(,(if compile-expression?
'js
:|
,|
'js
:block
)
201 ,@(let* ((block (flatten-blocks
202 (remove nil
(mapcar #'ps-compile body
))))
204 (append (remove-if #'constantp
(butlast block
))
205 (if (and (eq *ps-compilation-level
* :toplevel
)
210 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
213 (define-ps-special-form cond
(&rest clauses
)
214 (if compile-expression?
215 (make-cond-clauses-into-nested-ifs clauses
)
216 `(js:if
,(compile-expression (caar clauses
))
217 ,(compile-statement `(progn ,@(cdar clauses
)))
218 ,@(loop for
(test . body
) in
(cdr clauses
) appending
220 `(:else
,(compile-statement `(progn ,@body
)))
221 `(:else-if
,(compile-expression test
)
222 ,(compile-statement `(progn ,@body
))))))))
224 (defun make-cond-clauses-into-nested-ifs (clauses)
226 (destructuring-bind (test &rest body
)
229 (compile-expression `(progn ,@body
))
230 `(js:?
,(compile-expression test
)
231 ,(compile-expression `(progn ,@body
))
232 ,(make-cond-clauses-into-nested-ifs (cdr clauses
)))))
233 (compile-expression nil
)))
235 (define-ps-special-form if
(test then
&optional else
)
236 (if compile-expression?
237 `(js:?
,(compile-expression test
)
238 ,(compile-expression then
)
239 ,(compile-expression else
))
240 `(js:if
,(compile-expression test
)
241 ,(compile-statement `(progn ,then
))
242 ,@(when else
`(:else
,(compile-statement `(progn ,else
)))))))
244 (define-ps-special-form switch
(test-expr &rest clauses
)
245 `(js:switch
,(compile-expression test-expr
)
246 ,@(loop for
(val . body
) in clauses collect
247 (cons (if (eq val
'default
)
249 (compile-expression val
))
251 (let ((exp (compile-statement x
)))
252 (if (and (listp exp
) (eq 'js
:block
(car exp
)))
257 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
258 ;;; function definition
260 (defun add-implicit-return (fbody)
261 (let ((last-thing (car (last fbody
))))
262 (if (ps-statement? last-thing
)
264 (append (butlast fbody
)
265 `((return ,last-thing
))))))
267 (defmacro with-declaration-effects
(body-var &body body
)
268 `(let* ((local-specials (when (and (listp (car ,body-var
))
269 (eq (caar ,body-var
) 'declare
))
270 (cdr (find 'special
(cdar ,body-var
) :key
#'car
))))
271 (,body-var
(if local-specials
274 (*ps-special-variables
*
275 (append local-specials
*ps-special-variables
*)))
278 (defun compile-function-definition (args body
)
279 (with-declaration-effects body
281 (let* ((*enclosing-lexical-block-declarations
* ())
282 (*ps-enclosing-lexicals
*
283 (append args
*ps-enclosing-lexicals
*))
285 (compile-statement `(progn
286 ,@(add-implicit-return body
))))
290 ,@(mapcar (lambda (var)
292 *enclosing-lexical-block-declarations
*)))))
293 `(js:block
,@(cdr var-decls
) ,@(cdr body
))))))
295 (define-ps-special-form %js-lambda
(args &rest body
)
296 `(js:lambda
,@(compile-function-definition args body
)))
298 (define-ps-special-form %js-defun
(name args
&rest body
)
299 `(js:defun
,name
,@(compile-function-definition args body
)))
301 (defun parse-function-body (body)
302 (let* ((docstring (when (stringp (first body
))
304 (body-forms (if docstring
(rest body
) body
)))
305 (values body-forms docstring
)))
307 (defun parse-key-spec (key-spec)
308 "parses an &key parameter. Returns 5 values:
309 var, init-form, keyword-name, supplied-p-var, init-form-supplied-p.
312 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}*
314 (let* ((var (cond ((symbolp key-spec
) key-spec
)
315 ((and (listp key-spec
) (symbolp (first key-spec
))) (first key-spec
))
316 ((and (listp key-spec
) (listp (first key-spec
))) (second (first key-spec
)))))
317 (keyword-name (if (and (listp key-spec
) (listp (first key-spec
)))
318 (first (first key-spec
))
319 (intern (string var
) :keyword
)))
320 (init-form (if (listp key-spec
) (second key-spec
) nil
))
321 (init-form-supplied-p (if (listp key-spec
) t nil
))
322 (supplied-p-var (if (listp key-spec
) (third key-spec
) nil
)))
323 (values var init-form keyword-name supplied-p-var init-form-supplied-p
)))
325 (defun parse-optional-spec (spec)
326 "Parses an &optional parameter. Returns 3 values: var, init-form, supplied-p-var.
327 [&optional {var | (var [init-form [supplied-p-parameter]])}*] "
328 (let* ((var (cond ((symbolp spec
) spec
)
329 ((and (listp spec
) (first spec
)))))
330 (init-form (if (listp spec
) (second spec
)))
331 (supplied-p-var (if (listp spec
) (third spec
))))
332 (values var init-form supplied-p-var
)))
334 (defun parse-aux-spec (spec)
335 "Returns two values: variable and init-form"
336 ;; [&aux {var | (var [init-form])}*])
337 (values (if (symbolp spec
) spec
(first spec
))
338 (when (listp spec
) (second spec
))))
340 (defpsmacro defaultf
(name value suppl
)
342 ,@(when suppl
`((var ,suppl t
)))
343 (when (eql ,name undefined
)
344 (setf ,name
,value
,@(when suppl
(list suppl nil
))))))
346 (defun parse-extended-function (lambda-list body
)
347 "Returns two values: the effective arguments and body for a function with
348 the given lambda-list and body."
350 ;; The lambda list is transformed as follows, since a javascript
351 ;; lambda list is just a list of variable names, and you have access
352 ;; to the arguments variable inside the function:
354 ;; * standard variables are the mapped directly into the js-lambda
357 ;; * optional variables' variable names are mapped directly into the
358 ;; lambda list, and for each optional variable with name v,
359 ;; default value d, and supplied-p parameter s, a form is produced
362 ;; * keyword variables are not included in the js-lambda list, but
363 ;; instead are obtained from the magic js ARGUMENTS
364 ;; pseudo-array. Code assigning values to keyword vars is
365 ;; prepended to the body of the function. Defaults and supplied-p
366 ;; are handled using the same mechanism as with optional vars.
367 (multiple-value-bind (requireds optionals rest? rest keys? keys allow? aux?
368 aux more? more-context more-count key-object
)
369 (parse-lambda-list lambda-list
)
370 (declare (ignore allow? aux? aux more? more-context more-count key-object
))
371 (let* ( ;; optionals are of form (var default-value)
375 (mapcar #'parse-optional-spec optionals
))))
377 (mapcar (lambda (opt-spec)
378 (multiple-value-bind (var val suppl
)
379 (parse-optional-spec opt-spec
)
380 `(defaultf ,var
,val
,suppl
)))
384 (if (< *js-target-version
* 1.6)
391 (multiple-value-bind (var init-form
396 (push `(,keyword-str
(setf ,var
(aref arguments
(1+ ,n
))))
398 (push (list 'defaultf var init-form suppl
)
402 (loop for
,n from
,(length requireds
)
403 below
(length arguments
) by
2 do
404 (case (aref arguments
,n
) ,@assigns
))
408 (multiple-value-bind (var init-form keyword-str
)
411 `(let ((,x
((@ *Array prototype index-of call
)
412 arguments
,keyword-str
413 ,(length requireds
))))
414 (var ,var
(if (= -
1 ,x
)
416 (aref arguments
(1+ ,x
))))))))
421 `(progn (var ,rest
(array))
422 (dotimes (,i
(- (getprop arguments
'length
)
423 ,(length effective-args
)))
427 (+ ,i
,(length effective-args
)))))))))
428 (body-paren-forms (parse-function-body body
))
429 (effective-body (append opt-forms
431 (awhen rest-form
(list it
))
433 (values effective-args effective-body
))))
435 (defun maybe-rename-local-function (fun-name)
436 (aif (getf *ps-local-function-names
* fun-name
)
440 (defun collect-function-names (fn-defs)
441 (loop for
(fn-name) in fn-defs
443 collect
(if (or (member fn-name
*ps-enclosing-lexicals
*)
444 (lookup-macro-def fn-name
*ps-symbol-macro-env
*))
448 (define-ps-special-form flet
(fn-defs &rest body
)
449 (let* ((fn-renames (collect-function-names fn-defs
))
450 (fn-defs (loop for
(fn-name . def
) in fn-defs collect
451 (ps-compile `(var ,(getf fn-renames fn-name
)
453 (*ps-enclosing-lexicals
*
454 (append fn-renames
*ps-enclosing-lexicals
*))
455 (*ps-local-function-names
*
456 (append fn-renames
*ps-local-function-names
*)))
457 `(,(if compile-expression?
'js
:|
,|
'js
:block
)
459 ,@(flatten-blocks (mapcar #'ps-compile body
)))))
461 (define-ps-special-form labels
(fn-defs &rest body
)
462 (let* ((fn-renames (collect-function-names fn-defs
))
463 (*ps-local-function-names
*
464 (append fn-renames
*ps-local-function-names
*))
465 (*ps-enclosing-lexicals
*
466 (append fn-renames
*ps-enclosing-lexicals
*)))
468 `(progn ,@(loop for
(fn-name . def
) in fn-defs collect
469 `(var ,(getf *ps-local-function-names
* fn-name
)
473 (define-ps-special-form function
(fn-name)
474 (ps-compile (maybe-rename-local-function fn-name
)))
476 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
479 (define-ps-special-form macrolet
(macros &body body
)
480 (with-local-macro-environment (local-macro-dict *ps-macro-env
*)
481 (dolist (macro macros
)
482 (destructuring-bind (name arglist
&body body
)
484 (setf (gethash name local-macro-dict
)
485 (eval (make-ps-macro-function arglist body
)))))
486 (ps-compile `(progn ,@body
))))
488 (define-ps-special-form symbol-macrolet
(symbol-macros &body body
)
489 (with-local-macro-environment (local-macro-dict *ps-symbol-macro-env
*)
490 (let (local-var-bindings)
491 (dolist (macro symbol-macros
)
492 (destructuring-bind (name expansion
)
494 (setf (gethash name local-macro-dict
) (lambda (x)
497 (push name local-var-bindings
)))
498 (let ((*ps-enclosing-lexicals
*
499 (append local-var-bindings
500 *ps-enclosing-lexicals
*)))
501 (ps-compile `(progn ,@body
))))))
503 (define-ps-special-form defmacro
(name args
&body body
)
504 (eval `(defpsmacro ,name
,args
,@body
))
507 (define-ps-special-form define-symbol-macro
(name expansion
)
508 (eval `(define-ps-symbol-macro ,name
,expansion
))
511 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
514 (define-ps-symbol-macro {} (create))
516 (define-ps-special-form create
(&rest arrows
)
518 ,@(loop for
(key val-expr
) on arrows by
#'cddr collecting
520 (assert (or (stringp key
) (numberp key
) (symbolp key
))
522 "Slot key ~s is not one of symbol, string or number."
524 (cons (aif (and (symbolp key
) (ps-reserved-symbol? key
)) it key
)
525 (compile-expression val-expr
))))))
527 (define-ps-special-form %js-getprop
(obj slot
)
528 (let ((expanded-slot (ps-macroexpand slot
))
529 (obj (compile-expression obj
)))
530 (if (and (listp expanded-slot
)
531 (eq 'quote
(car expanded-slot
)))
532 (aif (or (ps-reserved-symbol?
(second expanded-slot
))
533 (and (keywordp (second expanded-slot
)) (second expanded-slot
)))
535 `(js:getprop
,obj
,(second expanded-slot
)))
536 `(js:aref
,obj
,(compile-expression slot
)))))
538 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
539 ;;; assignment and binding
541 (defun assignment-op (op)
556 (define-ps-special-form ps-assign
(lhs rhs
)
557 (let ((lhs (compile-expression lhs
))
558 (rhs (compile-expression rhs
)))
559 (aif (and (listp rhs
)
561 (equal lhs
(second rhs
))
562 (assignment-op (first rhs
)))
563 (list it lhs
(if (fourth rhs
)
564 (cons (first rhs
) (cddr rhs
))
566 (list 'js
:= lhs rhs
))))
568 (define-ps-special-form var
(name &optional
569 (value (values) value-provided?
)
571 (declare (ignore documentation
))
572 (let ((name (ps-macroexpand name
)))
573 (if compile-expression?
574 (progn (push name
*enclosing-lexical-block-declarations
*)
575 (when value-provided?
576 (compile-expression `(setf ,name
,value
))))
578 ,@(when value-provided?
579 (list (compile-expression value
)))))))
581 (define-ps-special-form let
(bindings &body body
)
582 (with-declaration-effects body
583 (let* ((lexical-bindings-introduced-here ())
588 (list (car x
) (ps-macroexpand (cadr x
)))))
590 (free-variables-in-binding-value-expressions
593 normalized-bindings
)))
594 (flet ((maybe-rename-lexical-var (x)
595 (if (or (member x
*ps-enclosing-lexicals
*)
596 (lookup-macro-def x
*ps-symbol-macro-env
*)
597 (member x free-variables-in-binding-value-expressions
))
599 (progn (push x lexical-bindings-introduced-here
) nil
)))
600 (rename (x) (first x
))
603 (let* ((lexical-bindings
604 (loop for x in normalized-bindings
605 unless
(ps-special-variable-p (car x
))
606 collect
(cons (maybe-rename-lexical-var (car x
)) x
)))
608 (loop for x in normalized-bindings
609 when
(ps-special-variable-p (car x
))
610 collect
(cons (ps-gensym (format nil
"~A_~A"
613 (renamed-body `(symbol-macrolet ,(loop for x in lexical-bindings
614 when
(rename x
) collect
615 `(,(var x
) ,(rename x
)))
617 (*ps-enclosing-lexicals
*
618 (append lexical-bindings-introduced-here
619 *ps-enclosing-lexicals
*)))
622 ,@(mapcar (lambda (x)
623 `(var ,(or (rename x
)
627 ,(if dynamic-bindings
628 `(progn ,@(mapcar (lambda (x)
632 (setf ,@(loop for x in dynamic-bindings append
633 `(,(rename x
) ,(var x
)
637 (setf ,@(mapcan (lambda (x)
638 `(,(var x
) ,(rename x
)))
639 dynamic-bindings
)))))
642 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
645 (defun make-for-vars/inits
(init-forms)
647 (cons (ps-macroexpand (if (atom x
) x
(first x
)))
648 (compile-expression (if (atom x
) nil
(second x
)))))
651 (define-ps-special-form for
(init-forms cond-forms step-forms
&body body
)
652 `(js:for
,(make-for-vars/inits init-forms
)
653 ,(mapcar #'compile-expression cond-forms
)
654 ,(mapcar #'compile-expression step-forms
)
655 ,(compile-statement `(progn ,@body
))))
657 (define-ps-special-form continue
(&optional label
)
658 `(js:continue
,label
))
660 (define-ps-special-form for-in
((var object
) &rest body
)
661 `(js:for-in
,(compile-expression var
)
662 ,(compile-expression object
)
663 ,(compile-statement `(progn ,@body
))))
665 (define-ps-special-form while
(test &rest body
)
666 `(js:while
,(compile-expression test
)
667 ,(compile-statement `(progn ,@body
))))
669 (define-ps-special-form label
(label &rest body
)
670 `(js:label
,label
,(compile-statement `(progn ,@body
))))
672 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
675 (define-ps-special-form with
(expression &rest body
)
676 `(js:with
,(compile-expression expression
)
677 ,(compile-statement `(progn ,@body
))))
679 (define-ps-special-form try
(form &rest clauses
)
680 (let ((catch (cdr (assoc :catch clauses
)))
681 (finally (cdr (assoc :finally clauses
))))
682 (assert (not (cdar catch
)) nil
"Sorry, currently only simple catch forms are supported.")
683 (assert (or catch finally
) ()
684 "Try form should have either a catch or a finally clause or both.")
685 `(js:try
,(compile-statement `(progn ,form
))
686 :catch
,(when catch
(list (caar catch
)
687 (compile-statement `(progn ,@(cdr catch
)))))
688 :finally
,(when finally
(compile-statement `(progn ,@finally
))))))
690 (define-ps-special-form regex
(regex)
691 `(js:regex
,(string regex
)))
693 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
696 (define-ps-special-form lisp
(lisp-form)
697 ;; (ps (foo (lisp bar))) is like (ps* `(foo ,bar))
698 ;; When called from inside of ps*, lisp-form has access to the
699 ;; dynamic environment only, analogoues to eval.
701 (with-output-to-string (*psw-stream
*)
702 (let ((compile-expression?
,compile-expression?
))
703 (parenscript-print (ps-compile ,lisp-form
) t
)))))
705 (define-ps-special-form eval-when
(situation-list &body body
)
706 "The body is evaluated only during the given situations. The
707 accepted situations are :load-toplevel, :compile-toplevel,
708 and :execute. The code in BODY is assumed to be Common-Lisp code
709 in :compile-toplevel and :load-toplevel sitations, and Parenscript
711 (when (and (member :compile-toplevel situation-list
)
712 (member *ps-compilation-level
* '(:toplevel
:inside-toplevel-form
)))
713 (eval `(progn ,@body
)))
714 (if (member :execute situation-list
)
715 (ps-compile `(progn ,@body
))
716 (ps-compile `(progn))))