1 (in-package #:parenscript
)
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4 ;;; arithmetic and logic
6 (macrolet ((define-trivial-special-ops (&rest mappings
)
8 ,@(loop for
(form-name js-primitive
) on mappings by
#'cddr
10 `(define-expression-operator ,form-name
(&rest args
)
12 (mapcar #'compile-expression args
)))))))
13 (define-trivial-special-ops
26 ;; todo: ash for shifts
32 instanceof js
:instanceof
36 in js
:in
;; maybe rename to slot-boundp?
41 (define-expression-operator -
(&rest args
)
42 (let ((args (mapcar #'compile-expression args
)))
43 (cons (if (cdr args
) 'js
:-
'js
:negate
) args
)))
45 (defun fix-nary-comparison (operator objects
)
46 (let* ((tmp-var-forms (butlast (cdr objects
)))
47 (tmp-vars (loop repeat
(length tmp-var-forms
)
48 collect
(ps-gensym "_cmp")))
49 (all-comparisons (append (list (car objects
))
52 `(let ,(mapcar #'list tmp-vars tmp-var-forms
)
53 (and ,@(loop for x1 in all-comparisons
54 for x2 in
(cdr all-comparisons
)
55 collect
(list operator x1 x2
))))))
57 (macrolet ((define-nary-comparison-forms (&rest mappings
)
59 ,@(loop for
(form js-primitive
) on mappings by
#'cddr collect
60 `(define-expression-operator ,form
(&rest objects
)
63 (fix-nary-comparison ',form objects
))
65 (mapcar #'compile-expression objects
))))))))
66 (define-nary-comparison-forms
74 (define-expression-operator /= (a b
)
75 ;; for n>2, /= is finding duplicates in an array of numbers (ie -
76 ;; nontrivial runtime algorithm), so we restrict it to binary in PS
77 `(js:!== ,(compile-expression a
) ,(compile-expression b
)))
79 (define-expression-operator incf
(x &optional
(delta 1))
80 (let ((delta (ps-macroexpand delta
)))
82 `(js:++ ,(compile-expression x
))
83 `(js:+= ,(compile-expression x
) ,(compile-expression delta
)))))
85 (define-expression-operator decf
(x &optional
(delta 1))
86 (let ((delta (ps-macroexpand delta
)))
88 `(js:--
,(compile-expression x
))
89 `(js:-
= ,(compile-expression x
) ,(compile-expression delta
)))))
91 (let ((inverses (mapcan (lambda (x)
97 (define-expression-operator not
(x)
98 (let ((form (compile-expression x
)))
99 (acond ((and (listp form
) (eq (car form
) 'js
:!))
101 ((and (listp form
) (cadr (assoc (car form
) inverses
)))
103 (t `(js:! ,form
))))))
105 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
108 (defun compile-progn (body)
109 (labels ((flatten-blocks (body)
111 (if (and (listp (car body
)) (eq 'js
:block
(caar body
)))
112 (append (cdr (car body
)) (flatten-blocks (cdr body
)))
113 (cons (car body
) (flatten-blocks (cdr body
)))))))
114 (let ((block (flatten-blocks (remove nil
(mapcar #'ps-compile body
)))))
115 (append (remove-if #'constantp
(butlast block
))
116 (unless (and (eq *compilation-level
* :toplevel
)
117 (not (car (last block
))))
120 (define-expression-operator progn
(&rest body
)
122 `(js:|
,|
,@(compile-progn body
))
123 (compile-expression (car body
))))
125 (define-statement-operator progn
(&rest body
)
126 `(js:block
,@(compile-progn body
)))
128 (define-statement-operator label
(label &rest body
) ;; does label need to do symbol-macro expansion?
129 `(js:label
,label
,(compile-statement `(progn ,@body
))))
131 (define-statement-operator continue
(&optional label
)
132 `(js:continue
,label
))
134 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
137 (define-expression-operator if
(test then
&optional else
)
138 `(js:?
,(compile-expression test
) ,(compile-expression then
) ,(compile-expression else
)))
140 (define-statement-operator if
(test then
&optional else
)
141 `(js:if
,(compile-expression test
)
142 ,(compile-statement `(progn ,then
))
143 ,@(when else
`(:else
,(compile-statement `(progn ,else
))))))
145 (define-expression-operator cond
(&rest clauses
)
148 (destructuring-bind (test &rest body
) (car clauses
)
153 (cond ,@(cdr clauses
))))))))
155 (define-statement-operator cond
(&rest clauses
)
156 `(js:if
,(compile-expression (caar clauses
))
157 ,(compile-statement `(progn ,@(cdar clauses
)))
158 ,@(loop for
(test . body
) in
(cdr clauses
) appending
160 `(:else
,(compile-statement `(progn ,@body
)))
161 `(:else-if
,(compile-expression test
)
162 ,(compile-statement `(progn ,@body
)))))))
164 (define-statement-operator switch
(test-expr &rest clauses
)
165 `(js:switch
,(compile-expression test-expr
)
166 ,@(loop for
(val . body
) in clauses collect
167 (cons (if (eq val
'default
)
169 (compile-expression val
))
171 (let ((exp (compile-statement x
)))
172 (if (and (listp exp
) (eq 'js
:block
(car exp
)))
177 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
180 (defun nesting-depth (form) ;; some heuristics
183 (1+ (apply #'max
(mapcar #'nesting-depth form
)))))
185 (define-statement-operator return-from
(tag &optional form
)
186 (let ((form (ps-macroexpand form
)))
192 `(progn ,@(butlast (cdr form
)) (return-from ,tag
,(car (last (cdr form
))))))
194 `(switch ,(second form
)
195 ,@(loop for
(cvalue . cbody
) in
(cddr form
)
196 for remaining on
(cddr form
) collect
197 (let ((last-n (cond ((or (eq 'default cvalue
) (not (cdr remaining
)))
199 ((eq 'break
(car (last cbody
)))
202 (let ((result-form (car (last cbody last-n
))))
204 ,@(butlast cbody last-n
)
205 (return-from ,tag
,result-form
)
206 ,@(when (and (= last-n
2) (member 'if
(flatten result-form
))) '(break))))
207 (cons cvalue cbody
))))))
209 `(try (return-from ,tag
,(second form
))
210 ,@(let ((catch (cdr (assoc :catch
(cdr form
))))
211 (finally (assoc :finally
(cdr form
))))
213 `(:catch
,(car catch
)
214 ,@(butlast (cdr catch
))
215 (return-from ,tag
,(car (last (cdr catch
))))))
218 `(cond ,@(loop for clause in
(cdr form
) collect
220 (return-from ,tag
,(car (last clause
)))))))
221 ((with label let flet labels macrolet symbol-macrolet
) ;; implicit progn forms
222 `(,(first form
) ,(second form
)
223 ,@(butlast (cddr form
))
224 (return-from ,tag
,(car (last (cddr form
))))))
225 ((continue break throw
) ;; non-local exit
227 (return-from ;; this will go away someday
229 (warn 'simple-style-warning
230 :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."))
233 (aif (and (< (nesting-depth form
) 5) (handler-case (compile-expression form
) (compile-expression-error () nil
)))
234 (return-from expressionize
`(js:return
,it
))
236 (return-from ,tag
,(third form
))
237 ,@(when (fourth form
) `((%function-return
,(fourth form
)))))))
239 (if (gethash (car form
) *special-statement-operators
*)
240 form
;; by now only special forms that return nil should be left, so this is ok
241 (return-from expressionize
`(js:return
,(compile-expression form
))))))))
242 `(js:return
,(compile-expression form
)))))
244 (defmacro with-declaration-effects
(body-var &body body
)
245 `(let* ((local-specials (when (and (listp (car ,body-var
))
246 (eq (caar ,body-var
) 'declare
))
247 (cdr (find 'special
(cdar ,body-var
) :key
#'car
))))
248 (,body-var
(if local-specials
251 (*special-variables
* (append local-specials
*special-variables
*)))
254 (defun compile-function-definition (args body
)
255 (with-declaration-effects body
256 (let* ((*enclosing-lexical-block-declarations
* ())
257 (*ps-enclosing-lexicals
* (append args
*ps-enclosing-lexicals
*))
258 (body (compile-statement `(return-from %function-body
(progn ,@body
))))
259 (var-decls (compile-statement
260 `(progn ,@(mapcar (lambda (var) `(var ,var
))
261 (remove-duplicates *enclosing-lexical-block-declarations
*))))))
262 `(js:block
,@(cdr var-decls
) ,@(cdr body
)))))
264 (define-expression-operator %js-lambda
(args &rest body
)
265 `(js:lambda
,args
,(compile-function-definition args body
)))
267 (define-statement-operator %js-defun
(name args
&rest body
)
268 (let ((docstring (and (cdr body
) (stringp (car body
)) (car body
))))
269 `(js:defun
,name
,args
,docstring
270 ,(compile-function-definition args
271 (if docstring
(cdr body
) body
)))))
273 (defun parse-key-spec (key-spec)
274 "parses an &key parameter. Returns 5 values:
275 var, init-form, keyword-name, supplied-p-var, init-form-supplied-p.
278 [&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}*
280 (let* ((var (cond ((symbolp key-spec
) key-spec
)
281 ((and (listp key-spec
) (symbolp (first key-spec
))) (first key-spec
))
282 ((and (listp key-spec
) (listp (first key-spec
))) (second (first key-spec
)))))
283 (keyword-name (if (and (listp key-spec
) (listp (first key-spec
)))
284 (first (first key-spec
))
285 (intern (string var
) :keyword
)))
286 (init-form (if (listp key-spec
) (second key-spec
) nil
))
287 (init-form-supplied-p (if (listp key-spec
) t nil
))
288 (supplied-p-var (if (listp key-spec
) (third key-spec
) nil
)))
289 (values var init-form keyword-name supplied-p-var init-form-supplied-p
)))
291 (defun parse-optional-spec (spec)
292 "Parses an &optional parameter. Returns 3 values: var, init-form, supplied-p-var.
293 [&optional {var | (var [init-form [supplied-p-parameter]])}*] "
294 (let* ((var (cond ((symbolp spec
) spec
)
295 ((and (listp spec
) (first spec
)))))
296 (init-form (if (listp spec
) (second spec
)))
297 (supplied-p-var (if (listp spec
) (third spec
))))
298 (values var init-form supplied-p-var
)))
300 (defun parse-aux-spec (spec)
301 "Returns two values: variable and init-form"
302 ;; [&aux {var | (var [init-form])}*])
303 (values (if (symbolp spec
) spec
(first spec
))
304 (when (listp spec
) (second spec
))))
306 (defun parse-extended-function (lambda-list body
)
307 ;; The lambda list is transformed as follows:
309 ;; * standard and optional variables are the mapped directly into
310 ;; the js-lambda list
312 ;; * keyword variables are not included in the js-lambda list, but
313 ;; instead are obtained from the magic js ARGUMENTS
314 ;; pseudo-array. Code assigning values to keyword vars is
315 ;; prepended to the body of the function.
316 (multiple-value-bind (requireds optionals rest? rest keys? keys allow? aux?
317 aux more? more-context more-count key-object
)
318 (parse-lambda-list lambda-list
)
319 (declare (ignore allow? aux? aux more? more-context more-count key-object
))
320 (let* ( ;; optionals are of form (var default-value)
324 (mapcar #'parse-optional-spec optionals
))))
326 (mapcar (lambda (opt-spec)
327 (multiple-value-bind (name value suppl
)
328 (parse-optional-spec opt-spec
)
331 (var ,suppl
(not (eql ,name undefined
)))
332 (when (not ,suppl
) (setf ,name
,value
)))
333 `(when (eql ,name undefined
)
334 (setf ,name
,value
)))))
343 (multiple-value-bind (var init-form keyword-str suppl
)
345 (push `(var ,var
,init-form
) decls
)
346 (when suppl
(push `(var ,suppl nil
) decls
))
348 (setf ,var
(aref arguments
(1+ ,n
))
349 ,@(when suppl
`(,suppl t
))))
353 (loop for
,n from
,(length requireds
)
354 below
(length arguments
) by
2 do
355 (case (aref arguments
,n
) ,@assigns
)))))))
359 `(progn (var ,rest
(array))
360 (dotimes (,i
(- (getprop arguments
'length
)
361 ,(length effective-args
)))
365 (+ ,i
,(length effective-args
)))))))))
366 (docstring (when (stringp (first body
)) (first body
)))
367 (body-paren-forms (if docstring
(rest body
) body
))
368 (effective-body (append (when docstring
(list docstring
))
371 (awhen rest-form
(list it
))
373 (values effective-args effective-body
))))
375 (defun maybe-rename-local-function (fun-name)
376 (aif (getf *ps-local-function-names
* fun-name
)
380 (defun collect-function-names (fn-defs)
381 (loop for
(fn-name) in fn-defs
383 collect
(if (or (member fn-name
*ps-enclosing-lexicals
*)
384 (lookup-macro-def fn-name
*ps-symbol-macro-env
*))
388 (define-expression-operator flet
(fn-defs &rest body
)
389 (let* ((fn-renames (collect-function-names fn-defs
))
390 ;; the function definitions need to be compiled with previous lexical bindings
391 (fn-defs (loop for
(fn-name . def
) in fn-defs collect
392 (ps-compile `(var ,(getf fn-renames fn-name
) (lambda ,@def
)))))
393 ;; the flet body needs to be compiled with the extended lexical environment
394 (*ps-enclosing-lexicals
* (append fn-renames
*ps-enclosing-lexicals
*))
395 (*ps-local-function-names
* (append fn-renames
*ps-local-function-names
*)))
396 `(,(if compile-expression?
'js
:|
,|
'js
:block
)
398 ,@(compile-progn body
))))
400 (define-expression-operator labels
(fn-defs &rest body
)
401 (let* ((fn-renames (collect-function-names fn-defs
))
402 (*ps-local-function-names
*
403 (append fn-renames
*ps-local-function-names
*))
404 (*ps-enclosing-lexicals
*
405 (append fn-renames
*ps-enclosing-lexicals
*)))
407 `(progn ,@(loop for
(fn-name . def
) in fn-defs collect
408 `(var ,(getf *ps-local-function-names
* fn-name
)
412 (define-expression-operator function
(fn-name)
413 (ps-compile (maybe-rename-local-function fn-name
)))
415 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
418 (defmacro with-local-macro-environment
((var env
) &body body
)
419 `(let* ((,var
(make-macro-dictionary))
420 (,env
(cons ,var
,env
)))
423 (define-expression-operator macrolet
(macros &body body
)
424 (with-local-macro-environment (local-macro-dict *ps-macro-env
*)
425 (dolist (macro macros
)
426 (destructuring-bind (name arglist
&body body
)
428 (setf (gethash name local-macro-dict
)
429 (eval (make-ps-macro-function arglist body
)))))
430 (ps-compile `(progn ,@body
))))
432 (define-expression-operator symbol-macrolet
(symbol-macros &body body
)
433 (with-local-macro-environment (local-macro-dict *ps-symbol-macro-env
*)
434 (let (local-var-bindings)
435 (dolist (macro symbol-macros
)
436 (destructuring-bind (name expansion
)
438 (setf (gethash name local-macro-dict
) (lambda (x)
441 (push name local-var-bindings
)))
442 (let ((*ps-enclosing-lexicals
*
443 (append local-var-bindings
444 *ps-enclosing-lexicals
*)))
445 (ps-compile `(progn ,@body
))))))
447 (define-expression-operator defmacro
(name args
&body body
)
448 (eval `(defpsmacro ,name
,args
,@body
))
451 (define-expression-operator define-symbol-macro
(name expansion
)
452 (eval `(define-ps-symbol-macro ,name
,expansion
))
455 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
458 (define-expression-operator create
(&rest arrows
)
460 ,@(loop for
(key val-expr
) on arrows by
#'cddr collecting
462 (assert (or (stringp key
) (numberp key
) (symbolp key
))
464 "Slot key ~s is not one of symbol, string or number."
466 (cons (aif (and (symbolp key
) (reserved-symbol? key
)) it key
)
467 (compile-expression val-expr
))))))
469 (define-expression-operator %js-getprop
(obj slot
)
470 (let ((expanded-slot (ps-macroexpand slot
))
471 (obj (compile-expression obj
)))
472 (if (and (listp expanded-slot
)
473 (eq 'quote
(car expanded-slot
)))
474 (aif (or (reserved-symbol?
(second expanded-slot
))
475 (and (keywordp (second expanded-slot
)) (second expanded-slot
)))
477 `(js:getprop
,obj
,(second expanded-slot
)))
478 `(js:aref
,obj
,(compile-expression slot
)))))
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 var
(name &optional
(value (values) value?
) docstr
)
514 (declare (ignore docstr
))
515 (push name
*enclosing-lexical-block-declarations
*)
516 (when value?
(compile-expression `(setf ,name
,value
))))
518 (define-statement-operator var
(name &optional
(value (values) value?
) docstr
)
519 `(js:var
,(ps-macroexpand name
) ,@(when value?
(list (compile-expression value
) docstr
))))
521 (define-expression-operator let
(bindings &body body
)
522 (with-declaration-effects body
523 (let* ((lexical-bindings-introduced-here ())
528 (list (car x
) (ps-macroexpand (cadr x
)))))
530 (free-variables-in-binding-value-expressions
531 (mapcan (lambda (x) (flatten (cadr x
)))
532 normalized-bindings
)))
533 (flet ((maybe-rename-lexical-var (x)
534 (if (or (member x
*ps-enclosing-lexicals
*)
535 (lookup-macro-def x
*ps-symbol-macro-env
*)
536 (member x free-variables-in-binding-value-expressions
))
538 (progn (push x lexical-bindings-introduced-here
) nil
)))
539 (rename (x) (first x
))
542 (let* ((lexical-bindings
543 (loop for x in normalized-bindings
544 unless
(special-variable?
(car x
))
545 collect
(cons (maybe-rename-lexical-var (car x
)) x
)))
547 (loop for x in normalized-bindings
548 when
(special-variable?
(car x
))
549 collect
(cons (ps-gensym (format nil
"~A_~A" (car x
) 'tmp-stack
)) x
)))
550 (renamed-body `(symbol-macrolet ,(loop for x in lexical-bindings
551 when
(rename x
) collect
552 `(,(var x
) ,(rename x
)))
554 (*ps-enclosing-lexicals
*
555 (append lexical-bindings-introduced-here
556 *ps-enclosing-lexicals
*)))
559 ,@(mapcar (lambda (x) `(var ,(or (rename x
) (var x
)) ,(val x
)))
561 ,(if dynamic-bindings
562 `(progn ,@(mapcar (lambda (x) `(var ,(rename x
)))
565 (setf ,@(loop for x in dynamic-bindings append
566 `(,(rename x
) ,(var x
)
570 (setf ,@(mapcan (lambda (x) `(,(var x
) ,(rename x
)))
571 dynamic-bindings
)))))
574 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
577 (defun make-for-vars/inits
(init-forms)
579 (cons (ps-macroexpand (if (atom x
) x
(first x
)))
580 (compile-expression (if (atom x
) nil
(second x
)))))
583 (define-statement-operator for
(init-forms cond-forms step-forms
&body body
)
584 `(js:for
,(make-for-vars/inits init-forms
)
585 ,(mapcar #'compile-expression cond-forms
)
586 ,(mapcar #'compile-expression step-forms
)
587 ,(compile-statement `(progn ,@body
))))
589 (define-statement-operator for-in
((var object
) &rest body
)
590 `(js:for-in
,(compile-expression var
)
591 ,(compile-expression object
)
592 ,(compile-statement `(progn ,@body
))))
594 (define-statement-operator while
(test &rest body
)
595 `(js:while
,(compile-expression test
)
596 ,(compile-statement `(progn ,@body
))))
598 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
601 (define-statement-operator with
(expression &rest body
) ;; this should be deprecated
602 `(js:with
,(compile-expression expression
)
603 ,(compile-statement `(progn ,@body
))))
605 (define-statement-operator try
(form &rest clauses
)
606 (let ((catch (cdr (assoc :catch clauses
)))
607 (finally (cdr (assoc :finally clauses
))))
608 (assert (not (cdar catch
)) () "Sorry, currently only simple catch forms are supported.")
609 (assert (or catch finally
) () "Try form should have either a catch or a finally clause or both.")
610 `(js:try
,(compile-statement `(progn ,form
))
611 :catch
,(when catch
(list (caar catch
) (compile-statement `(progn ,@(cdr catch
)))))
612 :finally
,(when finally
(compile-statement `(progn ,@finally
))))))
614 (define-expression-operator regex
(regex)
615 `(js:regex
,(string regex
)))
617 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
620 (define-expression-operator quote
(x)
621 (flet ((quote%
(expr) (when expr
`',expr
)))
624 (cons `(array ,@(mapcar #'quote% x
)))
627 (symbol (symbol-to-js-string x
))
630 (vector `(array ,@(loop for el across x collect
(quote% el
))))))))
632 (define-expression-operator lisp
(lisp-form)
633 ;; (ps (foo (lisp bar))) is like (ps* `(foo ,bar))
634 ;; When called from inside of ps*, lisp-form has access to the
635 ;; dynamic environment only, analogous to eval.
637 (with-output-to-string (*psw-stream
*)
638 (let ((compile-expression?
,compile-expression?
))
639 (parenscript-print (ps-compile ,lisp-form
) t
)))))
641 (define-expression-operator eval-when
(situation-list &body body
)
642 "The body is evaluated only during the given situations. The
643 accepted situations are :load-toplevel, :compile-toplevel,
644 and :execute. The code in BODY is assumed to be Common Lisp code
645 in :compile-toplevel and :load-toplevel sitations, and Parenscript
647 (when (and (member :compile-toplevel situation-list
)
648 (member *compilation-level
* '(:toplevel
:inside-toplevel-form
)))
649 (eval `(progn ,@body
)))
650 (if (member :execute situation-list
)
651 (ps-compile `(progn ,@body
))
652 (ps-compile `(progn))))