1 (in-package #:parenscript
)
2 (in-readtable :parenscript
)
4 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5 ;;; arithmetic and logic
7 (define-trivial-special-ops
25 (define-expression-operator / (&rest args
)
26 `(ps-js:/ ,@(unless (cdr args
) (list 1)) ,@(mapcar #'compile-expression args
)))
28 (define-expression-operator -
(&rest args
)
29 (let ((args (mapcar #'compile-expression args
)))
30 (cons (if (cdr args
) 'ps-js
:-
'ps-js
:negate
) args
)))
32 (defun fix-nary-comparison (operator objects
)
33 (let* ((tmp-var-forms (butlast (cdr objects
)))
34 (tmp-vars (loop repeat
(length tmp-var-forms
)
35 collect
(ps-gensym "_CMP")))
36 (all-comparisons (append (list (car objects
))
39 `(let ,(mapcar #'list tmp-vars tmp-var-forms
)
40 (and ,@(loop for x1 in all-comparisons
41 for x2 in
(cdr all-comparisons
)
42 collect
(list operator x1 x2
))))))
44 (macrolet ((define-nary-comparison-forms (&rest mappings
)
46 ,@(loop for
(form js-primitive
) on mappings by
#'cddr collect
47 `(define-expression-operator ,form
(&rest objects
)
50 (fix-nary-comparison ',form objects
))
52 (mapcar #'compile-expression objects
))))))))
53 (define-nary-comparison-forms
61 (define-expression-operator /= (a b
)
62 ;; for n>2, /= is finding duplicates in an array of numbers (ie -
63 ;; nontrivial runtime algorithm), so we restrict it to binary in PS
64 `(ps-js:!== ,(compile-expression a
) ,(compile-expression b
)))
66 (define-expression-operator incf
(x &optional
(delta 1))
67 (let ((delta (ps-macroexpand delta
)))
69 `(ps-js:++ ,(compile-expression x
))
70 `(ps-js:+= ,(compile-expression x
) ,(compile-expression delta
)))))
72 (define-expression-operator decf
(x &optional
(delta 1))
73 (let ((delta (ps-macroexpand delta
)))
75 `(ps-js:--
,(compile-expression x
))
76 `(ps-js:-
= ,(compile-expression x
) ,(compile-expression delta
)))))
78 (let ((inverses (mapcan (lambda (x)
80 '((ps-js:=== ps-js
:!==)
83 (ps-js:> ps-js
:<=)))))
84 (define-expression-operator not
(x)
85 (let ((form (compile-expression x
)))
86 (acond ((and (listp form
) (eq (car form
) 'ps-js
:!))
88 ((and (listp form
) (cadr (assoc (car form
) inverses
)))
90 (t `(ps-js:! ,form
))))))
92 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
93 ;;; blocks and control flow
95 (defun flatten-blocks (body)
97 (if (and (listp (car body
)) (eq 'ps-js
:block
(caar body
)))
98 (append (cdr (car body
)) (flatten-blocks (cdr body
)))
99 (cons (car body
) (flatten-blocks (cdr body
))))))
101 (defun compile-progn (body)
102 (let ((block (flatten-blocks (mapcar #'ps-compile body
))))
103 (append (remove-if #'constantp
(butlast block
))
104 (unless (and (or (eq *compilation-level
* :toplevel
)
105 (not compile-expression?
))
106 (not (car (last block
))))
109 (define-expression-operator progn
(&rest body
)
111 `(ps-js:|
,|
,@(compile-progn body
))
112 (compile-expression (car body
))))
114 (define-statement-operator progn
(&rest body
)
115 `(ps-js:block
,@(compile-progn body
)))
117 (defun wrap-block-for-dynamic-return (tag body
)
118 (if (member tag
*tags-that-return-throws-to
*)
121 :catch
(err ,(compile-statement `(progn (if (and err
(eql ',tag
(getprop err
:ps-block-tag
)))
122 ;; FIXME make this a multiple-value return
123 (getprop err
:ps-return-value
)
128 (define-statement-operator block
(name &rest body
)
129 (let* ((name (or name
'nilBlock
))
130 (*lexical-extent-return-tags
* (cons name
*lexical-extent-return-tags
*))
131 (*tags-that-return-throws-to
* ()))
132 `(ps-js:label
,name
,(wrap-block-for-dynamic-return name
(compile-statement `(progn ,@body
))))))
134 (defun try-expressionize-if?
(form)
135 (< (count #\Newline
(with-output-to-string (*psw-stream
*)
136 (let ((*ps-print-pretty
* t
))
137 (parenscript-print (compile-statement form
) t
))))
138 (if (= (length form
) 4) 5 4)))
140 (define-statement-operator return-from
(tag &optional result
)
145 (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
))
147 (ps-compile `(return-from nilBlock
,result
)))
148 (let ((form (ps-macroexpand result
)))
149 (flet ((return-exp (value) ;; this stuff needs to be fixed to handle multiple-value returns, too
150 (let ((value (compile-expression value
)))
151 (cond ((member tag
*lexical-extent-return-tags
*)
153 (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
))
155 ((or (eql '%function-body tag
) (member tag
*function-block-names
*))
156 `(ps-js:return
,value
))
157 ((member tag
*dynamic-extent-return-tags
*)
158 (push tag
*tags-that-return-throws-to
*)
159 (ps-compile `(throw (create :ps-block-tag
',tag
:ps-return-value
,value
))))
160 (t (warn "Returning from unknown block ~A" tag
)
161 `(ps-js:return
,value
)))))) ;; for backwards-compatibility
167 `(progn ,@(butlast (cdr form
)) (return-from ,tag
,(car (last (cdr form
))))))
169 `(switch ,(second form
)
170 ,@(loop for
(cvalue . cbody
) in
(cddr form
)
171 for remaining on
(cddr form
) collect
172 (let ((last-n (cond ((or (eq 'default cvalue
) (not (cdr remaining
)))
174 ((eq 'break
(car (last cbody
)))
177 (let ((result-form (ps-macroexpand (car (last cbody last-n
)))))
179 ,@(butlast cbody last-n
)
180 (return-from ,tag
,result-form
)
181 ,@(when (and (= last-n
2)
182 (find-if (lambda (x) (or (eq x
'if
) (eq x
'cond
)))
183 (flatten result-form
)))
185 (cons cvalue cbody
))))))
187 `(try (return-from ,tag
,(second form
))
188 ,@(let ((catch (cdr (assoc :catch
(cdr form
))))
189 (finally (assoc :finally
(cdr form
))))
191 `(:catch
,(car catch
)
192 ,@(butlast (cdr catch
))
193 (return-from ,tag
,(car (last (cdr catch
))))))
196 `(cond ,@(loop for clause in
(cdr form
) collect
197 `(,@(butlast clause
) (return-from ,tag
,(car (last clause
)))))))
198 ((with label let flet labels macrolet symbol-macrolet
) ;; implicit progn forms
199 `(,(first form
) ,(second form
)
200 ,@(butlast (cddr form
))
201 (return-from ,tag
,(car (last (cddr form
))))))
202 ((continue break throw
) ;; non-local exit
204 (return-from ;; this will go away someday
206 (warn 'simple-style-warning
207 :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."))
210 (aif (and (try-expressionize-if? form
)
211 (handler-case (compile-expression form
)
212 (compile-expression-error () nil
)))
213 (return-from expressionize
`(ps-js:return
,it
))
215 (return-from ,tag
,(third form
))
216 ,@(when (fourth form
) `((return-from ,tag
,(fourth form
)))))))
218 (if (gethash (car form
) *special-statement-operators
*)
219 form
;; by now only special forms that return nil should be left, so this is ok for implicit return
220 (return-from expressionize
(return-exp form
)))))))
221 (return-exp form
))))))
223 (define-statement-operator throw
(&rest args
)
224 `(ps-js:throw
,@(mapcar #'compile-expression args
)))
226 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
229 (define-expression-operator if
(test then
&optional else
)
230 `(ps-js:?
,(compile-expression test
) ,(compile-expression then
) ,(compile-expression else
)))
232 (define-statement-operator if
(test then
&optional else
)
233 `(ps-js:if
,(compile-expression test
)
234 ,(compile-statement `(progn ,then
))
235 ,@(when else
`(:else
,(compile-statement `(progn ,else
))))))
237 (define-expression-operator cond
(&rest clauses
)
240 (destructuring-bind (test &rest body
) (car clauses
)
245 (cond ,@(cdr clauses
))))))))
247 (define-statement-operator cond
(&rest clauses
)
248 `(ps-js:if
,(compile-expression (caar clauses
))
249 ,(compile-statement `(progn ,@(cdar clauses
)))
250 ,@(loop for
(test . body
) in
(cdr clauses
) appending
252 `(:else
,(compile-statement `(progn ,@body
)))
253 `(:else-if
,(compile-expression test
)
254 ,(compile-statement `(progn ,@body
)))))))
256 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
259 (defmacro with-local-macro-environment
((var env
) &body body
)
260 `(let* ((,var
(make-macro-dictionary))
261 (,env
(cons ,var
,env
)))
264 (define-expression-operator macrolet
(macros &body body
)
265 (with-local-macro-environment (local-macro-dict *macro-env
*)
266 (dolist (macro macros
)
267 (destructuring-bind (name arglist
&body body
)
269 (setf (gethash name local-macro-dict
)
270 (eval (make-ps-macro-function arglist body
)))))
271 (ps-compile `(progn ,@body
))))
273 (define-expression-operator symbol-macrolet
(symbol-macros &body body
)
274 (with-local-macro-environment (local-macro-dict *symbol-macro-env
*)
275 (let (local-var-bindings)
276 (dolist (macro symbol-macros
)
277 (destructuring-bind (name expansion
) macro
278 (setf (gethash name local-macro-dict
) (lambda (x) (declare (ignore x
)) expansion
))
279 (push name local-var-bindings
)))
280 (let ((*enclosing-lexicals
* (append local-var-bindings
*enclosing-lexicals
*)))
281 (ps-compile `(progn ,@body
))))))
283 (define-expression-operator defmacro
(name args
&body body
)
284 (eval `(defpsmacro ,name
,args
,@body
))
287 (define-expression-operator define-symbol-macro
(name expansion
)
288 (eval `(define-ps-symbol-macro ,name
,expansion
))
291 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
294 (defun assignment-op (op)
295 (getf '(ps-js:+ ps-js
:+=
309 (define-expression-operator ps-assign
(lhs rhs
)
310 (let ((rhs (ps-macroexpand rhs
)))
311 (if (and (listp rhs
) (eq (car rhs
) 'progn
))
312 (ps-compile `(progn ,@(butlast (cdr rhs
)) (ps-assign ,lhs
,(car (last (cdr rhs
))))))
313 (let ((lhs (compile-expression lhs
))
314 (rhs (compile-expression rhs
)))
315 (aif (and (listp rhs
)
317 (equal lhs
(second rhs
))
318 (assignment-op (first rhs
)))
319 (list it lhs
(if (fourth rhs
)
320 (cons (first rhs
) (cddr rhs
))
322 (list 'ps-js
:= lhs rhs
))))))
324 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
327 (defmacro with-declaration-effects
((var block
) &body body
)
328 (let ((declarations (gensym)))
329 `(let* ((,var
,block
)
330 (,declarations
(and (listp (car ,var
))
331 (eq (caar ,var
) 'declare
)
333 (,var
(if ,declarations
336 (*special-variables
* (append (cdr (find 'special
,declarations
:key
#'car
)) *special-variables
*)))
339 (define-expression-operator let
(bindings &body body
)
340 (with-declaration-effects (body body
)
341 (let* ((lexical-bindings-introduced-here ())
342 (normalized-bindings (mapcar (lambda (x)
345 (list (car x
) (ps-macroexpand (cadr x
)))))
347 (free-variables-in-binding-value-expressions (mapcan (lambda (x) (flatten (cadr x
)))
348 normalized-bindings
)))
349 (flet ((maybe-rename-lexical-var (x)
350 (if (or (member x
*enclosing-lexicals
*)
351 (member x
*enclosing-function-arguments
*)
352 (lookup-macro-def x
*symbol-macro-env
*)
353 (member x free-variables-in-binding-value-expressions
))
354 (ps-gensym (string x
))
355 (progn (push x lexical-bindings-introduced-here
) nil
)))
356 (rename (x) (first x
))
359 (let* ((lexical-bindings (loop for x in normalized-bindings
360 unless
(special-variable?
(car x
))
361 collect
(cons (maybe-rename-lexical-var (car x
)) x
)))
362 (dynamic-bindings (loop for x in normalized-bindings
363 when
(special-variable?
(car x
))
364 collect
(cons (ps-gensym (format nil
"~A_~A" (car x
) 'tmp-stack
)) x
)))
365 (renamed-body `(symbol-macrolet ,(loop for x in lexical-bindings
366 when
(rename x
) collect
367 `(,(var x
) ,(rename x
)))
369 (*enclosing-lexicals
* (append lexical-bindings-introduced-here
*enclosing-lexicals
*))
370 (*loop-scope-lexicals
* (when in-loop-scope?
(append lexical-bindings-introduced-here
*loop-scope-lexicals
*))))
373 ,@(mapcar (lambda (x) `(var ,(or (rename x
) (var x
)) ,(val x
)))
375 ,(if dynamic-bindings
376 `(progn ,@(mapcar (lambda (x) `(var ,(rename x
)))
379 (setf ,@(loop for x in dynamic-bindings append
380 `(,(rename x
) ,(var x
)
384 (setf ,@(mapcan (lambda (x) `(,(var x
) ,(rename x
)))
385 dynamic-bindings
)))))
388 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
391 (defun make-for-vars/inits
(init-forms)
393 (cons (ps-macroexpand (if (atom x
) x
(first x
)))
394 (compile-expression (if (atom x
) nil
(second x
)))))
397 (defun compile-loop-body (loop-vars body
)
398 (let* ((in-loop-scope? t
)
399 (*loop-scope-lexicals
* loop-vars
)
400 (*loop-scope-lexicals-captured
* ())
401 (*ps-gensym-counter
* *ps-gensym-counter
*)
402 (compiled-body (compile-statement `(progn ,@body
))))
403 ;; the sort is there to make order for output-tests consistent across implementations
404 (aif (sort (remove-duplicates *loop-scope-lexicals-captured
*) #'string
< :key
#'symbol-name
)
406 (ps-js:with
,(compile-expression
407 `(create ,@(loop for x in it
409 collect
(when (member x loop-vars
) x
))))
413 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
416 (define-expression-operator quote
(x)
417 (flet ((quote%
(expr) (when expr
`',expr
)))
420 (cons `(array ,@(mapcar #'quote% x
)))
423 (symbol (symbol-to-js-string x
))
426 (vector `(array ,@(loop for el across x collect
(quote% el
))))))))
428 (define-expression-operator eval-when
(situation-list &body body
)
429 "The body is evaluated only during the given situations. The
430 accepted situations are :load-toplevel, :compile-toplevel,
431 and :execute. The code in BODY is assumed to be Common Lisp code
432 in :compile-toplevel and :load-toplevel sitations, and Parenscript
434 (when (and (member :compile-toplevel situation-list
)
435 (member *compilation-level
* '(:toplevel
:inside-toplevel-form
)))
436 (eval `(progn ,@body
)))
437 (if (member :execute situation-list
)
438 (ps-compile `(progn ,@body
))
439 (ps-compile `(progn))))