diff options
| author | Daniel Colascione | 2015-03-01 23:57:51 -0800 |
|---|---|---|
| committer | Daniel Colascione | 2015-03-02 15:42:09 -0800 |
| commit | f6b5db6c45b773f86e203368aee9153ec8527205 (patch) | |
| tree | 360651305d19be6356d2912d29bbaad88f4529f9 | |
| parent | 9d8d0658147dfe5a90e2fb07ff666f35b1162d6e (diff) | |
| download | emacs-f6b5db6c45b773f86e203368aee9153ec8527205.tar.gz emacs-f6b5db6c45b773f86e203368aee9153ec8527205.zip | |
Add support for generators
diff --git a/doc/lispref/ChangeLog b/doc/lispref/ChangeLog
index 78f7e34..e7d79d5 100644
--- a/doc/lispref/ChangeLog
+++ b/doc/lispref/ChangeLog
@@ -1,3 +1,8 @@
+2015-03-02 Daniel Colascione <dancol@dancol.org>
+
+ * control.texi (Generators): New section
+ * elisp.text: Reference new section.
+
2015-02-28 Eli Zaretskii <eliz@gnu.org>
* searching.texi (Char Classes): Update the documentation of
diff --git a/doc/misc/ChangeLog b/doc/misc/ChangeLog
index 448c7f2..4e9c119 100644
--- a/doc/misc/ChangeLog
+++ b/doc/misc/ChangeLog
@@ -1,3 +1,7 @@
+2015-03-02 Daniel Colascione <dancol@dancol.org>
+
+ * cl.texi (Iteration Clauses): Mention iterator support.
+
2015-02-25 Tassilo Horn <tsdh@gnu.org>
* reftex.texi (Multifile Documents): Document
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 7ce2e81..4ab4406 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,6 +1,8 @@
2015-03-02 Daniel Colascione <dancol@dancol.org>
- * vc/vc.el (vc-responsible-backend): Add autoload cooking for
+ * emacs-lisp/generator.el: New file.
+
+ * vc/vc.el (vc-responsible-backend): Add autoload cookie for
`vc-responsible-backend'.
2015-03-01 Michael Albinus <michael.albinus@gmx.de>
diff --git a/test/ChangeLog b/test/ChangeLog
index 684e98f..64ad851 100644
--- a/test/ChangeLog
+++ b/test/ChangeLog
@@ -1,5 +1,7 @@
2015-03-02 Daniel Colascione <dancol@dancol.org>
+ * automated/generator-tests.el: New tests
+
* automated/finalizer-tests.el (finalizer-basic)
(finalizer-circular-reference, finalizer-cross-reference)
(finalizer-error): New tests.
| -rw-r--r-- | doc/lispref/ChangeLog | 5 | ||||
| -rw-r--r-- | doc/lispref/control.texi | 116 | ||||
| -rw-r--r-- | doc/lispref/elisp.texi | 1 | ||||
| -rw-r--r-- | doc/misc/ChangeLog | 4 | ||||
| -rw-r--r-- | doc/misc/cl.texi | 5 | ||||
| -rw-r--r-- | etc/NEWS | 2 | ||||
| -rw-r--r-- | lisp/ChangeLog | 4 | ||||
| -rw-r--r-- | lisp/emacs-lisp/generator.el | 789 | ||||
| -rw-r--r-- | test/ChangeLog | 2 | ||||
| -rw-r--r-- | test/automated/generator-tests.el | 288 |
10 files changed, 1215 insertions, 1 deletions
diff --git a/doc/lispref/ChangeLog b/doc/lispref/ChangeLog index 78f7e34ca01..e7d79d55c7e 100644 --- a/doc/lispref/ChangeLog +++ b/doc/lispref/ChangeLog | |||
| @@ -1,3 +1,8 @@ | |||
| 1 | 2015-03-02 Daniel Colascione <dancol@dancol.org> | ||
| 2 | |||
| 3 | * control.texi (Generators): New section | ||
| 4 | * elisp.text: Reference new section. | ||
| 5 | |||
| 1 | 2015-02-28 Eli Zaretskii <eliz@gnu.org> | 6 | 2015-02-28 Eli Zaretskii <eliz@gnu.org> |
| 2 | 7 | ||
| 3 | * searching.texi (Char Classes): Update the documentation of | 8 | * searching.texi (Char Classes): Update the documentation of |
diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi index d21292348a4..bec2bc92ac4 100644 --- a/doc/lispref/control.texi +++ b/doc/lispref/control.texi | |||
| @@ -39,6 +39,7 @@ structure constructs (@pxref{Macros}). | |||
| 39 | * Conditionals:: @code{if}, @code{cond}, @code{when}, @code{unless}. | 39 | * Conditionals:: @code{if}, @code{cond}, @code{when}, @code{unless}. |
| 40 | * Combining Conditions:: @code{and}, @code{or}, @code{not}. | 40 | * Combining Conditions:: @code{and}, @code{or}, @code{not}. |
| 41 | * Iteration:: @code{while} loops. | 41 | * Iteration:: @code{while} loops. |
| 42 | * Generators:: Generic sequences and coroutines. | ||
| 42 | * Nonlocal Exits:: Jumping out of a sequence. | 43 | * Nonlocal Exits:: Jumping out of a sequence. |
| 43 | @end menu | 44 | @end menu |
| 44 | 45 | ||
| @@ -620,6 +621,121 @@ Here is an example of using @code{dotimes} to do something 100 times: | |||
| 620 | @end example | 621 | @end example |
| 621 | @end defmac | 622 | @end defmac |
| 622 | 623 | ||
| 624 | @node Generators | ||
| 625 | @section Generators | ||
| 626 | @cindex generators | ||
| 627 | |||
| 628 | A @dfn{generator} is a function that produces a potentially-infinite | ||
| 629 | stream of values. Each time the function produces a value, it | ||
| 630 | suspends itself and waits for a caller to request the next value. | ||
| 631 | |||
| 632 | @defmac iter-defun name args [doc] [declare] [interactive] body@dots{} | ||
| 633 | @code{iter-defun} defines a generator function. A generator function | ||
| 634 | has the same signature as a normal function, but works differently. | ||
| 635 | Instead of executing @var{body} when called, a generator function | ||
| 636 | returns an iterator object. That iterator runs @var{body} to generate | ||
| 637 | values, emitting a value and pausing where @code{iter-yield} or | ||
| 638 | @code{iter-yield-from} appears. When @var{body} returns normally, | ||
| 639 | @code{iter-next} signals @code{iter-end-of-sequence} with @var{body}'s | ||
| 640 | result as its condition data. | ||
| 641 | |||
| 642 | Any kind of Lisp code is valid inside @var{body}, but | ||
| 643 | @code{iter-yield} and @code{iter-yield-from} cannot appear inside | ||
| 644 | @code{unwind-protect} forms. | ||
| 645 | |||
| 646 | @end defmac | ||
| 647 | |||
| 648 | @defmac iter-lambda args [doc] [interactive] body@dots{} | ||
| 649 | @code{iter-lambda} produces an unnamed generator function that works | ||
| 650 | just like a generator function produced with @code{iter-defun}. | ||
| 651 | @end defmac | ||
| 652 | |||
| 653 | @defmac iter-yield value | ||
| 654 | When it appears inside a generator function, @code{iter-yield} | ||
| 655 | indicates that the current iterator should pause and return | ||
| 656 | @var{value} from @code{iter-next}. @code{iter-yield} evaluates to the | ||
| 657 | @code{value} parameter of next call to @code{iter-next}. | ||
| 658 | @end defmac | ||
| 659 | |||
| 660 | @defmac iter-yield-from iterator | ||
| 661 | @code{iter-yield-from} yields all the values that @var{iterator} | ||
| 662 | produces and evaluates to the value that @var{iterator}'s generator | ||
| 663 | function returns normally. While it has control, @var{iterator} | ||
| 664 | receives sent to the iterator using @code{iter-next}. | ||
| 665 | @end defmac | ||
| 666 | |||
| 667 | To use a generator function, first call it normally, producing a | ||
| 668 | @dfn{iterator} object. An iterator is a specific instance of a | ||
| 669 | generator. Then use @code{iter-next} to retrieve values from this | ||
| 670 | iterator. When there are no more values to pull from an iterator, | ||
| 671 | @code{iter-next} raises an @code{iter-end-of-sequence} condition with | ||
| 672 | the iterator's final value. | ||
| 673 | |||
| 674 | It's important to note that generator function bodies only execute | ||
| 675 | inside calls to @code{iter-next}. A call to a function defined with | ||
| 676 | @code{iter-defun} produces an iterator; you must ``drive'' this | ||
| 677 | iterator with @code{iter-next} for anything interesting to happen. | ||
| 678 | Each call to a generator function produces a @emph{different} | ||
| 679 | iterator, each with its own state. | ||
| 680 | |||
| 681 | @defun iter-next iterator value | ||
| 682 | Retrieve the next value from @var{iterator}. If there are no more | ||
| 683 | values to be generated (because @var{iterator}'s generator function | ||
| 684 | returned), @code{iter-next} signals the @code{iter-end-of-sequence} | ||
| 685 | condition; the data value associated with this condition is the value | ||
| 686 | with which @var{iterator}'s generator function returned. | ||
| 687 | |||
| 688 | @var{value} is sent into the iterator and becomes the value to which | ||
| 689 | @code{iter-yield} evaluates. @var{value} is ignored for the first | ||
| 690 | @code{iter-next} call to a given iterator, since at the start of | ||
| 691 | @var{iterator}'s generator function, the generator function is not | ||
| 692 | evaluating any @code{iter-yield} form. | ||
| 693 | @end defun | ||
| 694 | |||
| 695 | @defun iter-close iterator | ||
| 696 | If @var{iterator} is suspended inside a @code{unwind-protect} and | ||
| 697 | becomes unreachable, Emacs will eventually run unwind handlers after a | ||
| 698 | garbage collection pass. To ensure that these handlers are run before | ||
| 699 | then, use @code{iter-close}. | ||
| 700 | @end defun | ||
| 701 | |||
| 702 | Some convenience functions are provided to make working with | ||
| 703 | iterators easier: | ||
| 704 | |||
| 705 | @defmac iter-do (var iterator) body @dots{} | ||
| 706 | Run @var{body} with @var{var} bound to each value that | ||
| 707 | @var{iterator} produces. | ||
| 708 | @end defmac | ||
| 709 | |||
| 710 | The Common Lisp loop facility also contains features for working with | ||
| 711 | iterators. See @xref{Loop Facility,,,cl,Common Lisp Extensions}. | ||
| 712 | |||
| 713 | The following piece of code demonstrates some important principles of | ||
| 714 | working with iterators. | ||
| 715 | |||
| 716 | @example | ||
| 717 | (iter-defun my-iter (x) | ||
| 718 | (iter-yield (1+ (iter-yield (1+ x)))) | ||
| 719 | -1 ;; Return normally | ||
| 720 | ) | ||
| 721 | |||
| 722 | (let* ((iter (my-iter 5)) | ||
| 723 | (iter2 (my-iter 0))) | ||
| 724 | ;; Prints 6 | ||
| 725 | (print (iter-next iter)) | ||
| 726 | ;; Prints 9 | ||
| 727 | (print (iter-next iter 8)) | ||
| 728 | ;; Prints 1; iter and iter2 have distinct states | ||
| 729 | (print (iter-next iter2 nil)) | ||
| 730 | |||
| 731 | ;; We expect the iter sequence to end now | ||
| 732 | (condition-case x | ||
| 733 | (iter-next iter) | ||
| 734 | (iter-end-of-sequence | ||
| 735 | ;; Prints -1, which my-iter returned normally | ||
| 736 | (print (cdr x))))) | ||
| 737 | @end example | ||
| 738 | |||
| 623 | @node Nonlocal Exits | 739 | @node Nonlocal Exits |
| 624 | @section Nonlocal Exits | 740 | @section Nonlocal Exits |
| 625 | @cindex nonlocal exits | 741 | @cindex nonlocal exits |
diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index cdc443f07d5..3802e49ec3d 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi | |||
| @@ -464,6 +464,7 @@ Control Structures | |||
| 464 | * Conditionals:: @code{if}, @code{cond}, @code{when}, @code{unless}. | 464 | * Conditionals:: @code{if}, @code{cond}, @code{when}, @code{unless}. |
| 465 | * Combining Conditions:: @code{and}, @code{or}, @code{not}. | 465 | * Combining Conditions:: @code{and}, @code{or}, @code{not}. |
| 466 | * Iteration:: @code{while} loops. | 466 | * Iteration:: @code{while} loops. |
| 467 | * Generators:: Generic sequences and coroutines. | ||
| 467 | * Nonlocal Exits:: Jumping out of a sequence. | 468 | * Nonlocal Exits:: Jumping out of a sequence. |
| 468 | 469 | ||
| 469 | Nonlocal Exits | 470 | Nonlocal Exits |
diff --git a/doc/misc/ChangeLog b/doc/misc/ChangeLog index 448c7f26c1a..4e9c119379d 100644 --- a/doc/misc/ChangeLog +++ b/doc/misc/ChangeLog | |||
| @@ -1,3 +1,7 @@ | |||
| 1 | 2015-03-02 Daniel Colascione <dancol@dancol.org> | ||
| 2 | |||
| 3 | * cl.texi (Iteration Clauses): Mention iterator support. | ||
| 4 | |||
| 1 | 2015-02-25 Tassilo Horn <tsdh@gnu.org> | 5 | 2015-02-25 Tassilo Horn <tsdh@gnu.org> |
| 2 | 6 | ||
| 3 | * reftex.texi (Multifile Documents): Document | 7 | * reftex.texi (Multifile Documents): Document |
diff --git a/doc/misc/cl.texi b/doc/misc/cl.texi index 66776029353..052ca6bd786 100644 --- a/doc/misc/cl.texi +++ b/doc/misc/cl.texi | |||
| @@ -2237,6 +2237,11 @@ This clause is like @code{always}, except that the loop returns | |||
| 2237 | This clause stops the loop when the specified form is non-@code{nil}; | 2237 | This clause stops the loop when the specified form is non-@code{nil}; |
| 2238 | in this case, it returns that non-@code{nil} value. If all the | 2238 | in this case, it returns that non-@code{nil} value. If all the |
| 2239 | values were @code{nil}, the loop returns @code{nil}. | 2239 | values were @code{nil}, the loop returns @code{nil}. |
| 2240 | |||
| 2241 | @item iter-by @var{iterator} | ||
| 2242 | This clause iterates over the values from the specified form, an | ||
| 2243 | iterator object. See (@pxref{Generators,,,elisp,GNU Emacs Lisp | ||
| 2244 | Reference Manual}). | ||
| 2240 | @end table | 2245 | @end table |
| 2241 | 2246 | ||
| 2242 | @node Accumulation Clauses | 2247 | @node Accumulation Clauses |
| @@ -621,6 +621,8 @@ word syntax, use `\sw' instead. | |||
| 621 | 621 | ||
| 622 | * Lisp Changes in Emacs 25.1 | 622 | * Lisp Changes in Emacs 25.1 |
| 623 | 623 | ||
| 624 | ** Emacs Lisp now supports generators. | ||
| 625 | |||
| 624 | ** New finalizer facility for running code when objects | 626 | ** New finalizer facility for running code when objects |
| 625 | become unreachable. | 627 | become unreachable. |
| 626 | 628 | ||
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 7ce2e816d45..4ab4406dba1 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,6 +1,8 @@ | |||
| 1 | 2015-03-02 Daniel Colascione <dancol@dancol.org> | 1 | 2015-03-02 Daniel Colascione <dancol@dancol.org> |
| 2 | 2 | ||
| 3 | * vc/vc.el (vc-responsible-backend): Add autoload cooking for | 3 | * emacs-lisp/generator.el: New file. |
| 4 | |||
| 5 | * vc/vc.el (vc-responsible-backend): Add autoload cookie for | ||
| 4 | `vc-responsible-backend'. | 6 | `vc-responsible-backend'. |
| 5 | 7 | ||
| 6 | 2015-03-01 Michael Albinus <michael.albinus@gmx.de> | 8 | 2015-03-01 Michael Albinus <michael.albinus@gmx.de> |
diff --git a/lisp/emacs-lisp/generator.el b/lisp/emacs-lisp/generator.el new file mode 100644 index 00000000000..4e21e792406 --- /dev/null +++ b/lisp/emacs-lisp/generator.el | |||
| @@ -0,0 +1,789 @@ | |||
| 1 | ;;; generator.el --- generators -*- lexical-binding: t -*- | ||
| 2 | |||
| 3 | ;;; Copyright (C) 2015 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Daniel Colascione <dancol@dancol.org> | ||
| 6 | ;; Keywords: extensions, elisp | ||
| 7 | ;; Package: emacs | ||
| 8 | |||
| 9 | ;; Copyright (C) Daniel Colascione | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;; This package implements generators for Emacs Lisp through a | ||
| 27 | ;; continuation-passing transformation. It provides essentially the | ||
| 28 | ;; same generator API and iterator facilties that Python and | ||
| 29 | ;; JavaScript ES6 provide. | ||
| 30 | ;; | ||
| 31 | ;; `iter-lambda' and `iter-defun' work like `lambda' and `defun', | ||
| 32 | ;; except that they evaluate to or define, respectively, generator | ||
| 33 | ;; functions. These functions, when called, return an iterator. | ||
| 34 | ;; An iterator is an opaque object that generates a sequence of | ||
| 35 | ;; values. Callers use `iter-next' to retrieve the next value from | ||
| 36 | ;; the sequence; when the sequence is exhausted, `iter-next' will | ||
| 37 | ;; raise the `iter-end-of-sequence' condition. | ||
| 38 | ;; | ||
| 39 | ;; Generator functions are written like normal functions, except that | ||
| 40 | ;; they can invoke `iter-yield' to suspend themselves and return a | ||
| 41 | ;; value to callers; this value becomes the return value of | ||
| 42 | ;; `iter-next'. On the next call to `iter-next', execution of the | ||
| 43 | ;; generator function resumes where it left off. When a generator | ||
| 44 | ;; function returns normally, the `iter-next' raises | ||
| 45 | ;; `iter-end-of-sequence' with the value the function returned. | ||
| 46 | ;; | ||
| 47 | ;; `iter-yield-from' yields all the values from another iterator; it | ||
| 48 | ;; then evaluates to the value the sub-iterator returned normally. | ||
| 49 | ;; This facility is useful for functional composition of generators | ||
| 50 | ;; and for implementing coroutines. | ||
| 51 | ;; | ||
| 52 | ;; `iter-yield' is illegal inside the UNWINDFORMS of an | ||
| 53 | ;; `unwind-protect' for various sordid internal reasons documented in | ||
| 54 | ;; the code. | ||
| 55 | ;; | ||
| 56 | ;; N.B. Each call to a generator function generates a *new* iterator, | ||
| 57 | ;; and each iterator maintains its own internal state. | ||
| 58 | ;; | ||
| 59 | ;; This raw form of iteration is general, but a bit awkward to use, so | ||
| 60 | ;; this library also provides soem convenience functions: | ||
| 61 | ;; | ||
| 62 | ;; `iter-do' is like `cl-do', except that instead of walking a list, | ||
| 63 | ;; it walks an iterator. `cl-loop' is also extended with a new | ||
| 64 | ;; keyword, `iter-by', that iterates over an iterator. | ||
| 65 | ;; | ||
| 66 | |||
| 67 | ;;; Implementation: | ||
| 68 | |||
| 69 | ;; | ||
| 70 | ;; The internal cps transformation code uses the cps- namespace. | ||
| 71 | ;; Iteration functions use the `iter-' namespace. Generator functions | ||
| 72 | ;; are somewhat less efficient than conventional elisp routines, | ||
| 73 | ;; although we try to avoid CPS transformation on forms that do not | ||
| 74 | ;; invoke `iter-yield'. | ||
| 75 | ;; | ||
| 76 | |||
| 77 | ;;; Code: | ||
| 78 | |||
| 79 | (require 'cl-lib) | ||
| 80 | (require 'pcase) | ||
| 81 | |||
| 82 | (defvar *cps-bindings* nil) | ||
| 83 | (defvar *cps-states* nil) | ||
| 84 | (defvar *cps-value-symbol* nil) | ||
| 85 | (defvar *cps-state-symbol* nil) | ||
| 86 | (defvar *cps-cleanup-table-symbol* nil) | ||
| 87 | (defvar *cps-cleanup-function* nil) | ||
| 88 | |||
| 89 | (defvar *cps-dynamic-wrappers* '(identity) | ||
| 90 | "List of transformer functions to apply to atomic forms we | ||
| 91 | evaluate in CPS context.") | ||
| 92 | |||
| 93 | (defconst cps-standard-special-forms | ||
| 94 | '(setq setq-default throw interactive) | ||
| 95 | "List of special forms that we treat just like ordinary | ||
| 96 | function applications." ) | ||
| 97 | |||
| 98 | (defun cps--trace-funcall (func &rest args) | ||
| 99 | (message "%S: args=%S" func args) | ||
| 100 | (let ((result (apply func args))) | ||
| 101 | (message "%S: result=%S" func result) | ||
| 102 | result)) | ||
| 103 | |||
| 104 | (defun cps--trace (fmt &rest args) | ||
| 105 | (princ (apply #'format (concat fmt "\n") args))) | ||
| 106 | |||
| 107 | (defun cps--special-form-p (definition) | ||
| 108 | "Non-nil if and only if DEFINITION is a special form." | ||
| 109 | ;; Copied from ad-special-form-p | ||
| 110 | (if (and (symbolp definition) (fboundp definition)) | ||
| 111 | (setf definition (indirect-function definition))) | ||
| 112 | (and (subrp definition) (eq (cdr (subr-arity definition)) 'unevalled))) | ||
| 113 | |||
| 114 | (defmacro cps--define-unsupported (function) | ||
| 115 | `(defun ,(intern (format "cps--transform-%s" function)) | ||
| 116 | (error "%s not supported in generators" ,function))) | ||
| 117 | |||
| 118 | (defmacro cps--with-value-wrapper (wrapper &rest body) | ||
| 119 | "Continue generating CPS code with an atomic-form wrapper | ||
| 120 | to the current stack of such wrappers. WRAPPER is a function that | ||
| 121 | takes a form and returns a wrapped form. | ||
| 122 | |||
| 123 | Whenever we generate an atomic form (i.e., a form that can't | ||
| 124 | iter-yield), we first (before actually inserting that form in our | ||
| 125 | generated code) pass that form through all the transformer | ||
| 126 | functions. We use this facility to wrap forms that can transfer | ||
| 127 | control flow non-locally in goo that diverts this control flow to | ||
| 128 | the CPS state machinery. | ||
| 129 | " | ||
| 130 | (declare (indent 1)) | ||
| 131 | `(let ((*cps-dynamic-wrappers* | ||
| 132 | (cons | ||
| 133 | ,wrapper | ||
| 134 | *cps-dynamic-wrappers*))) | ||
| 135 | ,@body)) | ||
| 136 | |||
| 137 | (defun cps--make-dynamic-binding-wrapper (dynamic-var static-var) | ||
| 138 | (cl-assert lexical-binding) | ||
| 139 | (lambda (form) | ||
| 140 | `(let ((,dynamic-var ,static-var)) | ||
| 141 | (unwind-protect ; Update the static shadow after evaluation is done | ||
| 142 | ,form | ||
| 143 | (setf ,static-var ,dynamic-var)) | ||
| 144 | ,form))) | ||
| 145 | |||
| 146 | (defmacro cps--with-dynamic-binding (dynamic-var static-var &rest body) | ||
| 147 | "Evaluate BODY such that generated atomic evaluations run with | ||
| 148 | DYNAMIC-VAR bound to STATIC-VAR." | ||
| 149 | (declare (indent 2)) | ||
| 150 | `(cps--with-value-wrapper | ||
| 151 | (cps--make-dynamic-binding-wrapper ,dynamic-var ,static-var) | ||
| 152 | ,@body)) | ||
| 153 | |||
| 154 | (defun cps--add-state (kind body) | ||
| 155 | "Create a new CPS state with body BODY and return the state's name." | ||
| 156 | (declare (indent 1)) | ||
| 157 | (let* ((state (cl-gensym (format "cps-state-%s-" kind)))) | ||
| 158 | (push (list state body *cps-cleanup-function*) *cps-states*) | ||
| 159 | (push state *cps-bindings*) | ||
| 160 | state)) | ||
| 161 | |||
| 162 | (defun cps--add-binding (original-name) | ||
| 163 | (car (push (cl-gensym (format "cps-binding-%s-" original-name)) | ||
| 164 | *cps-bindings*))) | ||
| 165 | |||
| 166 | (defun cps--find-special-form-handler (form) | ||
| 167 | (let* ((handler-name (format "cps--transform-%s" (car-safe form))) | ||
| 168 | (handler (intern-soft handler-name))) | ||
| 169 | (and (fboundp handler) handler))) | ||
| 170 | |||
| 171 | (defvar cps-disable-atomic-optimization nil | ||
| 172 | "When t, always rewrite forms into cps even when they | ||
| 173 | don't yield.") | ||
| 174 | |||
| 175 | (defvar cps--yield-seen) | ||
| 176 | |||
| 177 | (defun cps--atomic-p (form) | ||
| 178 | "Return whether the given form never yields." | ||
| 179 | |||
| 180 | (and (not cps-disable-atomic-optimization) | ||
| 181 | (let* ((cps--yield-seen)) | ||
| 182 | (ignore (macroexpand-all | ||
| 183 | `(cl-macrolet ((cps-internal-yield | ||
| 184 | (_val) | ||
| 185 | (setf cps--yield-seen t))) | ||
| 186 | ,form))) | ||
| 187 | (not cps--yield-seen)))) | ||
| 188 | |||
| 189 | (defun cps--make-atomic-state (form next-state) | ||
| 190 | (let ((tform `(prog1 ,form (setf ,*cps-state-symbol* ,next-state)))) | ||
| 191 | (cl-loop for wrapper in *cps-dynamic-wrappers* | ||
| 192 | do (setf tform (funcall wrapper tform))) | ||
| 193 | ;; Bind *cps-cleanup-function* to nil here because the wrapper | ||
| 194 | ;; function mechanism is responsible for cleanup here, not the | ||
| 195 | ;; generic cleanup mechanism. If we didn't make this binding, | ||
| 196 | ;; we'd run cleanup handlers twice on anything that made it out | ||
| 197 | ;; to toplevel. | ||
| 198 | (let ((*cps-cleanup-function* nil)) | ||
| 199 | (cps--add-state "atom" | ||
| 200 | `(setf ,*cps-value-symbol* ,tform))))) | ||
| 201 | |||
| 202 | (defun cps--transform-1 (form next-state) | ||
| 203 | (pcase form | ||
| 204 | |||
| 205 | ;; If we're looking at an "atomic" form (i.e., one that does not | ||
| 206 | ;; iter-yield), just evaluate the form as a whole instead of rewriting | ||
| 207 | ;; it into CPS. | ||
| 208 | |||
| 209 | ((guard (cps--atomic-p form)) | ||
| 210 | (cps--make-atomic-state form next-state)) | ||
| 211 | |||
| 212 | ;; Process `and'. | ||
| 213 | |||
| 214 | (`(and) ; (and) -> t | ||
| 215 | (cps--transform-1 t next-state)) | ||
| 216 | (`(and ,condition) ; (and CONDITION) -> CONDITION | ||
| 217 | (cps--transform-1 condition next-state)) | ||
| 218 | (`(and ,condition . ,rest) | ||
| 219 | ;; Evaluate CONDITION; if it's true, go on to evaluate the rest | ||
| 220 | ;; of the `and'. | ||
| 221 | (cps--transform-1 | ||
| 222 | condition | ||
| 223 | (cps--add-state "and" | ||
| 224 | `(setf ,*cps-state-symbol* | ||
| 225 | (if ,*cps-value-symbol* | ||
| 226 | ,(cps--transform-1 `(and ,@rest) | ||
| 227 | next-state) | ||
| 228 | ,next-state))))) | ||
| 229 | |||
| 230 | ;; Process `catch'. | ||
| 231 | |||
| 232 | (`(catch ,tag . ,body) | ||
| 233 | (let ((tag-binding (cps--add-binding "catch-tag"))) | ||
| 234 | (cps--transform-1 tag | ||
| 235 | (cps--add-state "cps-update-tag" | ||
| 236 | `(setf ,tag-binding ,*cps-value-symbol* | ||
| 237 | ,*cps-state-symbol* | ||
| 238 | ,(cps--with-value-wrapper | ||
| 239 | (cps--make-catch-wrapper | ||
| 240 | tag-binding next-state) | ||
| 241 | (cps--transform-1 `(progn ,@body) | ||
| 242 | next-state))))))) | ||
| 243 | |||
| 244 | ;; Process `cond': transform into `if' or `or' depending on the | ||
| 245 | ;; precise kind of the condition we're looking at. | ||
| 246 | |||
| 247 | (`(cond) ; (cond) -> nil | ||
| 248 | (cps--transform-1 nil next-state)) | ||
| 249 | (`(cond (,condition) . ,rest) | ||
| 250 | (cps--transform-1 `(or ,condition (cond ,@rest)) | ||
| 251 | next-state)) | ||
| 252 | (`(cond (,condition . ,body) . ,rest) | ||
| 253 | (cps--transform-1 `(if ,condition | ||
| 254 | (progn ,@body) | ||
| 255 | (cond ,@rest)) | ||
| 256 | next-state)) | ||
| 257 | |||
| 258 | ;; Process `condition-case': do the heavy lifting in a helper | ||
| 259 | ;; function. | ||
| 260 | |||
| 261 | (`(condition-case ,var ,bodyform . ,handlers) | ||
| 262 | (cps--with-value-wrapper | ||
| 263 | (cps--make-condition-wrapper var next-state handlers) | ||
| 264 | (cps--transform-1 bodyform | ||
| 265 | next-state))) | ||
| 266 | |||
| 267 | ;; Process `if'. | ||
| 268 | |||
| 269 | (`(if ,cond ,then . ,else) | ||
| 270 | (cps--transform-1 cond | ||
| 271 | (cps--add-state "if" | ||
| 272 | `(setf ,*cps-state-symbol* | ||
| 273 | (if ,*cps-value-symbol* | ||
| 274 | ,(cps--transform-1 then | ||
| 275 | next-state) | ||
| 276 | ,(cps--transform-1 `(progn ,@else) | ||
| 277 | next-state)))))) | ||
| 278 | |||
| 279 | ;; Process `progn' and `inline': they are identical except for the | ||
| 280 | ;; name, which has some significance to the byte compiler. | ||
| 281 | |||
| 282 | (`(inline) (cps--transform-1 nil next-state)) | ||
| 283 | (`(inline ,form) (cps--transform-1 form next-state)) | ||
| 284 | (`(inline ,form . ,rest) | ||
| 285 | (cps--transform-1 form | ||
| 286 | (cps--transform-1 `(inline ,@rest) | ||
| 287 | next-state))) | ||
| 288 | |||
| 289 | (`(progn) (cps--transform-1 nil next-state)) | ||
| 290 | (`(progn ,form) (cps--transform-1 form next-state)) | ||
| 291 | (`(progn ,form . ,rest) | ||
| 292 | (cps--transform-1 form | ||
| 293 | (cps--transform-1 `(progn ,@rest) | ||
| 294 | next-state))) | ||
| 295 | |||
| 296 | ;; Process `let' in a helper function that transforms it into a | ||
| 297 | ;; let* with temporaries. | ||
| 298 | |||
| 299 | (`(let ,bindings . ,body) | ||
| 300 | (let* ((bindings (cl-loop for binding in bindings | ||
| 301 | collect (if (symbolp binding) | ||
| 302 | (list binding nil) | ||
| 303 | binding))) | ||
| 304 | (temps (cl-loop for (var value-form) in bindings | ||
| 305 | collect (cps--add-binding var)))) | ||
| 306 | (cps--transform-1 | ||
| 307 | `(let* ,(append | ||
| 308 | (cl-loop for (var value-form) in bindings | ||
| 309 | for temp in temps | ||
| 310 | collect (list temp value-form)) | ||
| 311 | (cl-loop for (var binding) in bindings | ||
| 312 | for temp in temps | ||
| 313 | collect (list var temp))) | ||
| 314 | ,@body) | ||
| 315 | next-state))) | ||
| 316 | |||
| 317 | ;; Process `let*' binding: process one binding at a time. Flatten | ||
| 318 | ;; lexical bindings. | ||
| 319 | |||
| 320 | (`(let* () . ,body) | ||
| 321 | (cps--transform-1 `(progn ,@body) next-state)) | ||
| 322 | |||
| 323 | (`(let* (,binding . ,more-bindings) . ,body) | ||
| 324 | (let* ((var (if (symbolp binding) binding (car binding))) | ||
| 325 | (value-form (car (cdr-safe binding))) | ||
| 326 | (new-var (cps--add-binding var))) | ||
| 327 | |||
| 328 | (cps--transform-1 | ||
| 329 | value-form | ||
| 330 | (cps--add-state "let*" | ||
| 331 | `(setf ,new-var ,*cps-value-symbol* | ||
| 332 | ,*cps-state-symbol* | ||
| 333 | ,(if (or (not lexical-binding) (special-variable-p var)) | ||
| 334 | (cps--with-dynamic-binding var new-var | ||
| 335 | (cps--transform-1 | ||
| 336 | `(let* ,more-bindings ,@body) | ||
| 337 | next-state)) | ||
| 338 | (cps--transform-1 | ||
| 339 | (cps--replace-variable-references | ||
| 340 | var new-var | ||
| 341 | `(let* ,more-bindings ,@body)) | ||
| 342 | next-state))))))) | ||
| 343 | |||
| 344 | ;; Process `or'. | ||
| 345 | |||
| 346 | (`(or) (cps--transform-1 nil next-state)) | ||
| 347 | (`(or ,condition) (cps--transform-1 condition next-state)) | ||
| 348 | (`(or ,condition . ,rest) | ||
| 349 | (cps--transform-1 | ||
| 350 | condition | ||
| 351 | (cps--add-state "or" | ||
| 352 | `(setf ,*cps-state-symbol* | ||
| 353 | (if ,*cps-value-symbol* | ||
| 354 | ,next-state | ||
| 355 | ,(cps--transform-1 | ||
| 356 | `(or ,@rest) next-state)))))) | ||
| 357 | |||
| 358 | ;; Process `prog1'. | ||
| 359 | |||
| 360 | (`(prog1 ,first) (cps--transform-1 first next-state)) | ||
| 361 | (`(prog1 ,first . ,body) | ||
| 362 | (cps--transform-1 | ||
| 363 | first | ||
| 364 | (let ((temp-var-symbol (cps--add-binding "prog1-temp"))) | ||
| 365 | (cps--add-state "prog1" | ||
| 366 | `(setf ,temp-var-symbol | ||
| 367 | ,*cps-value-symbol* | ||
| 368 | ,*cps-state-symbol* | ||
| 369 | ,(cps--transform-1 | ||
| 370 | `(progn ,@body) | ||
| 371 | (cps--add-state "prog1inner" | ||
| 372 | `(setf ,*cps-value-symbol* ,temp-var-symbol | ||
| 373 | ,*cps-state-symbol* ,next-state)))))))) | ||
| 374 | |||
| 375 | ;; Process `prog2'. | ||
| 376 | |||
| 377 | (`(prog2 ,form1 ,form2 . ,body) | ||
| 378 | (cps--transform-1 | ||
| 379 | `(progn ,form1 (prog1 ,form2 ,@body)) | ||
| 380 | next-state)) | ||
| 381 | |||
| 382 | ;; Process `unwind-protect': If we're inside an unwind-protect, we | ||
| 383 | ;; have a block of code UNWINDFORMS which we would like to run | ||
| 384 | ;; whenever control flows away from the main piece of code, | ||
| 385 | ;; BODYFORM. We deal with the local control flow case by | ||
| 386 | ;; generating BODYFORM such that it yields to a continuation that | ||
| 387 | ;; executes UNWINDFORMS, which then yields to NEXT-STATE. | ||
| 388 | ;; | ||
| 389 | ;; Non-local control flow is trickier: we need to ensure that we | ||
| 390 | ;; execute UNWINDFORMS even when control bypasses our normal | ||
| 391 | ;; continuation. To make this guarantee, we wrap every external | ||
| 392 | ;; application (i.e., every piece of elisp that can transfer | ||
| 393 | ;; control non-locally) in an unwind-protect that runs UNWINDFORMS | ||
| 394 | ;; before allowing the non-local control transfer to proceed. | ||
| 395 | ;; | ||
| 396 | ;; Unfortunately, because elisp lacks a mechanism for generically | ||
| 397 | ;; capturing the reason for an arbitrary non-local control | ||
| 398 | ;; transfer and restarting the transfer at a later point, we | ||
| 399 | ;; cannot reify non-local transfers and cannot allow | ||
| 400 | ;; continuation-passing code inside UNWINDFORMS. | ||
| 401 | |||
| 402 | (`(unwind-protect ,bodyform . ,unwindforms) | ||
| 403 | ;; Signal the evaluator-generator that it needs to generate code | ||
| 404 | ;; to handle cleanup forms. | ||
| 405 | (unless *cps-cleanup-table-symbol* | ||
| 406 | (setf *cps-cleanup-table-symbol* (cl-gensym "cps-cleanup-table-"))) | ||
| 407 | (let* ((unwind-state | ||
| 408 | (cps--add-state | ||
| 409 | "unwind" | ||
| 410 | ;; N.B. It's safe to just substitute unwindforms by | ||
| 411 | ;; sexp-splicing: we've already replaced all variable | ||
| 412 | ;; references inside it with lifted equivalents. | ||
| 413 | `(progn | ||
| 414 | ,@unwindforms | ||
| 415 | (setf ,*cps-state-symbol* ,next-state)))) | ||
| 416 | (old-cleanup *cps-cleanup-function*) | ||
| 417 | (*cps-cleanup-function* | ||
| 418 | (let ((*cps-cleanup-function* nil)) | ||
| 419 | (cps--add-state "cleanup" | ||
| 420 | `(progn | ||
| 421 | ,(when old-cleanup `(funcall ,old-cleanup)) | ||
| 422 | ,@unwindforms))))) | ||
| 423 | (cps--with-value-wrapper | ||
| 424 | (cps--make-unwind-wrapper unwindforms) | ||
| 425 | (cps--transform-1 bodyform unwind-state)))) | ||
| 426 | |||
| 427 | ;; Process `while'. | ||
| 428 | |||
| 429 | (`(while ,test . ,body) | ||
| 430 | ;; Open-code state addition instead of using cps--add-state: we | ||
| 431 | ;; need our states to be self-referential. (That's what makes the | ||
| 432 | ;; state a loop.) | ||
| 433 | (let* ((loop-state | ||
| 434 | (cl-gensym "cps-state-while-")) | ||
| 435 | (eval-loop-condition-state | ||
| 436 | (cps--transform-1 test loop-state)) | ||
| 437 | (loop-state-body | ||
| 438 | `(progn | ||
| 439 | (setf ,*cps-state-symbol* | ||
| 440 | (if ,*cps-value-symbol* | ||
| 441 | ,(cps--transform-1 | ||
| 442 | `(progn ,@body) | ||
| 443 | eval-loop-condition-state) | ||
| 444 | ,next-state))))) | ||
| 445 | (push (list loop-state loop-state-body *cps-cleanup-function*) | ||
| 446 | *cps-states*) | ||
| 447 | (push loop-state *cps-bindings*) | ||
| 448 | eval-loop-condition-state)) | ||
| 449 | |||
| 450 | ;; Process various kinds of `quote'. | ||
| 451 | |||
| 452 | (`(quote ,arg) (cps--add-state "quote" | ||
| 453 | `(setf ,*cps-value-symbol* (quote ,arg) | ||
| 454 | ,*cps-state-symbol* ,next-state))) | ||
| 455 | (`(function ,arg) (cps--add-state "function" | ||
| 456 | `(setf ,*cps-value-symbol* (function ,arg) | ||
| 457 | ,*cps-state-symbol* ,next-state))) | ||
| 458 | |||
| 459 | ;; Deal with `iter-yield'. | ||
| 460 | |||
| 461 | (`(cps-internal-yield ,value) | ||
| 462 | (cps--transform-1 | ||
| 463 | value | ||
| 464 | (cps--add-state "iter-yield" | ||
| 465 | `(progn | ||
| 466 | (setf ,*cps-state-symbol* | ||
| 467 | ,(if *cps-cleanup-function* | ||
| 468 | (cps--add-state "after-yield" | ||
| 469 | `(setf ,*cps-state-symbol* ,next-state)) | ||
| 470 | next-state)) | ||
| 471 | (throw 'cps--yield ,*cps-value-symbol*))))) | ||
| 472 | |||
| 473 | ;; Catch any unhandled special forms. | ||
| 474 | |||
| 475 | ((and `(,name . ,_) | ||
| 476 | (guard (cps--special-form-p name)) | ||
| 477 | (guard (not (memq name cps-standard-special-forms)))) | ||
| 478 | name ; Shut up byte compiler | ||
| 479 | (error "special form %S incorrect or not supported" form)) | ||
| 480 | |||
| 481 | ;; Process regular function applications with nontrivial | ||
| 482 | ;; parameters, converting them to applications of trivial | ||
| 483 | ;; let-bound parameters. | ||
| 484 | |||
| 485 | ((and `(,function . ,arguments) | ||
| 486 | (guard (not (cl-loop for argument in arguments | ||
| 487 | always (atom argument))))) | ||
| 488 | (let ((argument-symbols | ||
| 489 | (cl-loop for argument in arguments | ||
| 490 | collect (if (atom argument) | ||
| 491 | argument | ||
| 492 | (cl-gensym "cps-argument-"))))) | ||
| 493 | |||
| 494 | (cps--transform-1 | ||
| 495 | `(let* ,(cl-loop for argument in arguments | ||
| 496 | for argument-symbol in argument-symbols | ||
| 497 | unless (eq argument argument-symbol) | ||
| 498 | collect (list argument-symbol argument)) | ||
| 499 | ,(cons function argument-symbols)) | ||
| 500 | next-state))) | ||
| 501 | |||
| 502 | ;; Process everything else by just evaluating the form normally. | ||
| 503 | (t (cps--make-atomic-state form next-state)))) | ||
| 504 | |||
| 505 | (defun cps--make-catch-wrapper (tag-binding next-state) | ||
| 506 | (lambda (form) | ||
| 507 | (let ((normal-exit-symbol | ||
| 508 | (cl-gensym "cps-normal-exit-from-catch-"))) | ||
| 509 | `(let (,normal-exit-symbol) | ||
| 510 | (prog1 | ||
| 511 | (catch ,tag-binding | ||
| 512 | (prog1 | ||
| 513 | ,form | ||
| 514 | (setf ,normal-exit-symbol t))) | ||
| 515 | (unless ,normal-exit-symbol | ||
| 516 | (setf ,*cps-state-symbol* ,next-state))))))) | ||
| 517 | |||
| 518 | (defun cps--make-condition-wrapper (var next-state handlers) | ||
| 519 | ;; Each handler is both one of the transformers with which we wrap | ||
| 520 | ;; evaluated atomic forms and a state to which we jump when we | ||
| 521 | ;; encounter the given error. | ||
| 522 | |||
| 523 | (let* ((error-symbol (cps--add-binding "condition-case-error")) | ||
| 524 | (lexical-error-symbol (cl-gensym "cps-lexical-error-")) | ||
| 525 | (processed-handlers | ||
| 526 | (cl-loop for (condition . body) in handlers | ||
| 527 | collect (cons condition | ||
| 528 | (cps--transform-1 | ||
| 529 | (cps--replace-variable-references | ||
| 530 | var error-symbol | ||
| 531 | `(progn ,@body)) | ||
| 532 | next-state))))) | ||
| 533 | |||
| 534 | (lambda (form) | ||
| 535 | `(condition-case | ||
| 536 | ,lexical-error-symbol | ||
| 537 | ,form | ||
| 538 | ,@(cl-loop | ||
| 539 | for (condition . error-state) in processed-handlers | ||
| 540 | collect | ||
| 541 | `(,condition | ||
| 542 | (setf ,error-symbol | ||
| 543 | ,lexical-error-symbol | ||
| 544 | ,*cps-state-symbol* | ||
| 545 | ,error-state))))))) | ||
| 546 | |||
| 547 | (defun cps--replace-variable-references (var new-var form) | ||
| 548 | "Replace all non-shadowed references to VAR with NEW-VAR in FORM. | ||
| 549 | This routine does not modify FORM. Instead, it returns a | ||
| 550 | modified copy." | ||
| 551 | (macroexpand-all | ||
| 552 | `(cl-symbol-macrolet ((,var ,new-var)) ,form))) | ||
| 553 | |||
| 554 | (defun cps--make-unwind-wrapper (unwind-forms) | ||
| 555 | (cl-assert lexical-binding) | ||
| 556 | (lambda (form) | ||
| 557 | (let ((normal-exit-symbol | ||
| 558 | (cl-gensym "cps-normal-exit-from-unwind-"))) | ||
| 559 | `(let (,normal-exit-symbol) | ||
| 560 | (unwind-protect | ||
| 561 | (prog1 | ||
| 562 | ,form | ||
| 563 | (setf ,normal-exit-symbol t)) | ||
| 564 | (unless ,normal-exit-symbol | ||
| 565 | ,@unwind-forms)))))) | ||
| 566 | |||
| 567 | (put 'iter-end-of-sequence 'error-conditions '(iter-end-of-sequence)) | ||
| 568 | (put 'iter-end-of-sequence 'error-message "iteration terminated") | ||
| 569 | |||
| 570 | (defun cps--make-close-iterator-form (terminal-state) | ||
| 571 | (if *cps-cleanup-table-symbol* | ||
| 572 | `(let ((cleanup (cdr (assq ,*cps-state-symbol* ,*cps-cleanup-table-symbol*)))) | ||
| 573 | (setf ,*cps-state-symbol* ,terminal-state | ||
| 574 | ,*cps-value-symbol* nil) | ||
| 575 | (when cleanup (funcall cleanup))) | ||
| 576 | `(setf ,*cps-state-symbol* ,terminal-state | ||
| 577 | ,*cps-value-symbol* nil))) | ||
| 578 | |||
| 579 | (defun cps-generate-evaluator (form) | ||
| 580 | (let* (*cps-states* | ||
| 581 | *cps-bindings* | ||
| 582 | *cps-cleanup-function* | ||
| 583 | (*cps-value-symbol* (cl-gensym "cps-current-value-")) | ||
| 584 | (*cps-state-symbol* (cl-gensym "cps-current-state-")) | ||
| 585 | ;; We make *cps-cleanup-table-symbol** non-nil when we notice | ||
| 586 | ;; that we have cleanup processing to perform. | ||
| 587 | (*cps-cleanup-table-symbol* nil) | ||
| 588 | (terminal-state (cps--add-state "terminal" | ||
| 589 | `(signal 'iter-end-of-sequence | ||
| 590 | ,*cps-value-symbol*))) | ||
| 591 | (initial-state (cps--transform-1 | ||
| 592 | (macroexpand-all form) | ||
| 593 | terminal-state)) | ||
| 594 | (finalizer-symbol | ||
| 595 | (when *cps-cleanup-table-symbol* | ||
| 596 | (when *cps-cleanup-table-symbol* | ||
| 597 | (cl-gensym "cps-iterator-finalizer-"))))) | ||
| 598 | `(let ,(append (list *cps-state-symbol* *cps-value-symbol*) | ||
| 599 | (when *cps-cleanup-table-symbol* | ||
| 600 | (list *cps-cleanup-table-symbol*)) | ||
| 601 | (when finalizer-symbol | ||
| 602 | (list finalizer-symbol)) | ||
| 603 | (nreverse *cps-bindings*)) | ||
| 604 | ;; Order state list so that cleanup states are always defined | ||
| 605 | ;; before they're referenced. | ||
| 606 | ,@(cl-loop for (state body cleanup) in (nreverse *cps-states*) | ||
| 607 | collect `(setf ,state (lambda () ,body)) | ||
| 608 | when cleanup | ||
| 609 | do (cl-assert *cps-cleanup-table-symbol*) | ||
| 610 | and collect `(push (cons ,state ,cleanup) ,*cps-cleanup-table-symbol*)) | ||
| 611 | (setf ,*cps-state-symbol* ,initial-state) | ||
| 612 | |||
| 613 | (let ((iterator | ||
| 614 | (lambda (op value) | ||
| 615 | (cond | ||
| 616 | ,@(when finalizer-symbol | ||
| 617 | `(((eq op :stash-finalizer) | ||
| 618 | (setf ,finalizer-symbol value)) | ||
| 619 | ((eq op :get-finalizer) | ||
| 620 | ,finalizer-symbol))) | ||
| 621 | ((eq op :close) | ||
| 622 | ,(cps--make-close-iterator-form terminal-state)) | ||
| 623 | ((eq op :next) | ||
| 624 | (setf ,*cps-value-symbol* value) | ||
| 625 | (let ((yielded nil)) | ||
| 626 | (unwind-protect | ||
| 627 | (prog1 | ||
| 628 | (catch 'cps--yield | ||
| 629 | (while t | ||
| 630 | (funcall ,*cps-state-symbol*))) | ||
| 631 | (setf yielded t)) | ||
| 632 | (unless yielded | ||
| 633 | ;; If we're exiting non-locally (error, quit, | ||
| 634 | ;; etc.) close the iterator. | ||
| 635 | ,(cps--make-close-iterator-form terminal-state))))) | ||
| 636 | (t (error "unknown iterator operation %S" op)))))) | ||
| 637 | ,(when finalizer-symbol | ||
| 638 | `(funcall iterator | ||
| 639 | :stash-finalizer | ||
| 640 | (make-finalizer | ||
| 641 | (lambda () | ||
| 642 | (iter-close iterator))))) | ||
| 643 | iterator)))) | ||
| 644 | |||
| 645 | (defun iter-yield (value) | ||
| 646 | "When used inside a generator, yield control to caller. | ||
| 647 | The caller of `iter-next' receives VALUE, and the next call to | ||
| 648 | `iter-next' resumes execution at the previous | ||
| 649 | `iter-yield' point." | ||
| 650 | (identity value) | ||
| 651 | (error "`iter-yield' used outside a generator")) | ||
| 652 | |||
| 653 | (defmacro iter-yield-from (value) | ||
| 654 | "When used inside a generator function, delegate to a sub-iterator. | ||
| 655 | The values that the sub-iterator yields are passed directly to | ||
| 656 | the caller, and values supplied to `iter-next' are sent to the | ||
| 657 | sub-iterator. `iter-yield-from' evaluates to the value that the | ||
| 658 | sub-iterator function returns via `iter-end-of-sequence'." | ||
| 659 | (let ((errsym (cl-gensym "yield-from-result")) | ||
| 660 | (valsym (cl-gensym "yield-from-value"))) | ||
| 661 | `(let ((,valsym ,value)) | ||
| 662 | (unwind-protect | ||
| 663 | (condition-case ,errsym | ||
| 664 | (let ((vs nil)) | ||
| 665 | (while t | ||
| 666 | (setf vs (iter-yield (iter-next ,valsym vs))))) | ||
| 667 | (iter-end-of-sequence (cdr ,errsym))) | ||
| 668 | (iter-close ,valsym))))) | ||
| 669 | |||
| 670 | (defmacro iter-defun (name arglist &rest body) | ||
| 671 | "Creates a generator NAME. | ||
| 672 | When called as a function, NAME returns an iterator value that | ||
| 673 | encapsulates the state of a computation that produces a sequence | ||
| 674 | of values. Callers can retrieve each value using `iter-next'." | ||
| 675 | (declare (indent defun)) | ||
| 676 | (cl-assert lexical-binding) | ||
| 677 | `(defun ,name ,arglist | ||
| 678 | ,(cps-generate-evaluator | ||
| 679 | `(cl-macrolet ((iter-yield (value) `(cps-internal-yield ,value))) | ||
| 680 | ,@body)))) | ||
| 681 | |||
| 682 | (defmacro iter-lambda (arglist &rest body) | ||
| 683 | "Return a lambda generator. | ||
| 684 | `iter-lambda' is to `iter-defun' as `lambda' is to `defun'." | ||
| 685 | (declare (indent defun)) | ||
| 686 | (cl-assert lexical-binding) | ||
| 687 | `(lambda ,arglist | ||
| 688 | ,(cps-generate-evaluator | ||
| 689 | `(cl-macrolet ((iter-yield (value) `(cps-internal-yield ,value))) | ||
| 690 | ,@body)))) | ||
| 691 | |||
| 692 | (defun iter-next (iterator &optional yield-result) | ||
| 693 | "Extract a value from an iterator. | ||
| 694 | YIELD-RESULT becomes the return value of `iter-yield` in the | ||
| 695 | context of the generator. | ||
| 696 | |||
| 697 | This routine raises the `iter-end-of-sequence' condition if the | ||
| 698 | iterator cannot supply more values." | ||
| 699 | (funcall iterator :next yield-result)) | ||
| 700 | |||
| 701 | (defun iter-close (iterator) | ||
| 702 | "Terminate an iterator early. | ||
| 703 | Run any unwind-protect handlers in scope at the point ITERATOR | ||
| 704 | is blocked." | ||
| 705 | (funcall iterator :close nil)) | ||
| 706 | |||
| 707 | (cl-defmacro iter-do ((var iterator) &rest body) | ||
| 708 | "Loop over values from an iterator. | ||
| 709 | Evaluate BODY with VAR bound to each value from ITERATOR. | ||
| 710 | Return the value with which ITERATOR finished iteration." | ||
| 711 | (declare (indent 1)) | ||
| 712 | (let ((done-symbol (cl-gensym "iter-do-iterator-done")) | ||
| 713 | (condition-symbol (cl-gensym "iter-do-condition")) | ||
| 714 | (it-symbol (cl-gensym "iter-do-iterator")) | ||
| 715 | (result-symbol (cl-gensym "iter-do-result"))) | ||
| 716 | `(let (,var | ||
| 717 | ,result-symbol | ||
| 718 | (,done-symbol nil) | ||
| 719 | (,it-symbol ,iterator)) | ||
| 720 | (while (not ,done-symbol) | ||
| 721 | (condition-case ,condition-symbol | ||
| 722 | (setf ,var (iter-next ,it-symbol)) | ||
| 723 | (iter-end-of-sequence | ||
| 724 | (setf ,result-symbol (cdr ,condition-symbol)) | ||
| 725 | (setf ,done-symbol t))) | ||
| 726 | (unless ,done-symbol ,@body)) | ||
| 727 | ,result-symbol))) | ||
| 728 | |||
| 729 | (defvar cl--loop-args) | ||
| 730 | |||
| 731 | (defmacro cps--advance-for (conscell) | ||
| 732 | ;; See cps--handle-loop-for | ||
| 733 | `(condition-case nil | ||
| 734 | (progn | ||
| 735 | (setcar ,conscell (iter-next (cdr ,conscell))) | ||
| 736 | ,conscell) | ||
| 737 | (iter-end-of-sequence | ||
| 738 | nil))) | ||
| 739 | |||
| 740 | (defmacro cps--initialize-for (iterator) | ||
| 741 | ;; See cps--handle-loop-for | ||
| 742 | (let ((cs (cl-gensym "cps--loop-temp"))) | ||
| 743 | `(let ((,cs (cons nil ,iterator))) | ||
| 744 | (cps--advance-for ,cs)))) | ||
| 745 | |||
| 746 | (defun cps--handle-loop-for (var) | ||
| 747 | "Support `iter-by' in `loop'. " | ||
| 748 | ;; N.B. While the cl-loop-for-handler is a documented interface, | ||
| 749 | ;; there's no documented way for cl-loop-for-handler callbacks to do | ||
| 750 | ;; anything useful! Additionally, cl-loop currently lexbinds useful | ||
| 751 | ;; internal variables, so our only option is to modify | ||
| 752 | ;; cl--loop-args. If we substitute a general-purpose for-clause for | ||
| 753 | ;; our iterating clause, however, we can't preserve the | ||
| 754 | ;; parallel-versus-sequential `loop' semantics for for clauses --- | ||
| 755 | ;; we need a terminating condition as well, which requires us to use | ||
| 756 | ;; while, and inserting a while would break and-sequencing. | ||
| 757 | ;; | ||
| 758 | ;; To work around this problem, we actually use the "for var in LIST | ||
| 759 | ;; by FUNCTION" syntax, creating a new fake list each time through | ||
| 760 | ;; the loop, this "list" being a cons cell (val . it). | ||
| 761 | (let ((it-form (pop cl--loop-args))) | ||
| 762 | (setf cl--loop-args | ||
| 763 | (append | ||
| 764 | `(for ,var | ||
| 765 | in (cps--initialize-for ,it-form) | ||
| 766 | by 'cps--advance-for) | ||
| 767 | cl--loop-args)))) | ||
| 768 | |||
| 769 | (put 'iter-by 'cl-loop-for-handler 'cps--handle-loop-for) | ||
| 770 | |||
| 771 | (eval-after-load 'elisp-mode | ||
| 772 | (lambda () | ||
| 773 | (font-lock-add-keywords | ||
| 774 | 'emacs-lisp-mode | ||
| 775 | '(("(\\(iter-defun\\)\\_>\\s *\\(\\(?:\\sw\\|\\s_\\)+\\)?" | ||
| 776 | (1 font-lock-keyword-face nil t) | ||
| 777 | (2 font-lock-function-name-face nil t)) | ||
| 778 | ("(\\(iter-next\\)\\_>" | ||
| 779 | (1 font-lock-keyword-face nil t)) | ||
| 780 | ("(\\(iter-lambda\\)\\_>" | ||
| 781 | (1 font-lock-keyword-face nil t)) | ||
| 782 | ("(\\(iter-yield\\)\\_>" | ||
| 783 | (1 font-lock-keyword-face nil t)) | ||
| 784 | ("(\\(iter-yield-from\\)\\_>" | ||
| 785 | (1 font-lock-keyword-face nil t)))))) | ||
| 786 | |||
| 787 | (provide 'generator) | ||
| 788 | |||
| 789 | ;;; generator.el ends here | ||
diff --git a/test/ChangeLog b/test/ChangeLog index 684e98f880e..64ad85198af 100644 --- a/test/ChangeLog +++ b/test/ChangeLog | |||
| @@ -1,5 +1,7 @@ | |||
| 1 | 2015-03-02 Daniel Colascione <dancol@dancol.org> | 1 | 2015-03-02 Daniel Colascione <dancol@dancol.org> |
| 2 | 2 | ||
| 3 | * automated/generator-tests.el: New tests | ||
| 4 | |||
| 3 | * automated/finalizer-tests.el (finalizer-basic) | 5 | * automated/finalizer-tests.el (finalizer-basic) |
| 4 | (finalizer-circular-reference, finalizer-cross-reference) | 6 | (finalizer-circular-reference, finalizer-cross-reference) |
| 5 | (finalizer-error): New tests. | 7 | (finalizer-error): New tests. |
diff --git a/test/automated/generator-tests.el b/test/automated/generator-tests.el new file mode 100644 index 00000000000..2c5de31b40b --- /dev/null +++ b/test/automated/generator-tests.el | |||
| @@ -0,0 +1,288 @@ | |||
| 1 | ;;; generator-tests.el --- Testing generators -*- lexical-binding: t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2015 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Daniel Colascione <dancol@dancol.org> | ||
| 6 | ;; Keywords: | ||
| 7 | |||
| 8 | ;; This program is free software; you can redistribute it and/or modify | ||
| 9 | ;; it under the terms of the GNU General Public License as published by | ||
| 10 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 11 | ;; (at your option) any later version. | ||
| 12 | |||
| 13 | ;; This program is distributed in the hope that it will be useful, | ||
| 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | ;; GNU General Public License for more details. | ||
| 17 | |||
| 18 | ;; You should have received a copy of the GNU General Public License | ||
| 19 | ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 20 | |||
| 21 | ;;; Commentary: | ||
| 22 | |||
| 23 | (require 'generator) | ||
| 24 | (require 'ert) | ||
| 25 | (require 'cl-lib) | ||
| 26 | |||
| 27 | (defun generator-list-subrs () | ||
| 28 | (cl-loop for x being the symbols | ||
| 29 | when (and (fboundp x) | ||
| 30 | (cps--special-form-p (symbol-function x))) | ||
| 31 | collect x)) | ||
| 32 | |||
| 33 | (defmacro cps-testcase (name &rest body) | ||
| 34 | "Perform a simple test of the continuation-transforming code. | ||
| 35 | |||
| 36 | `cps-testcase' defines an ERT testcase called NAME that evaluates | ||
| 37 | BODY twice: once using ordinary `eval' and once using | ||
| 38 | lambda-generators. The test ensures that the two forms produce | ||
| 39 | identical output. | ||
| 40 | " | ||
| 41 | `(progn | ||
| 42 | (ert-deftest ,name () | ||
| 43 | (should | ||
| 44 | (equal | ||
| 45 | (funcall (lambda () ,@body)) | ||
| 46 | (iter-next | ||
| 47 | (funcall | ||
| 48 | (iter-lambda () (iter-yield (progn ,@body)))))))) | ||
| 49 | (ert-deftest ,(intern (format "%s-noopt" name)) () | ||
| 50 | (should | ||
| 51 | (equal | ||
| 52 | (funcall (lambda () ,@body)) | ||
| 53 | (iter-next | ||
| 54 | (funcall | ||
| 55 | (let ((cps-disable-atomic-optimization t)) | ||
| 56 | (iter-lambda () (iter-yield (progn ,@body))))))))))) | ||
| 57 | |||
| 58 | (put 'cps-testcase 'lisp-indent-function 1) | ||
| 59 | |||
| 60 | (defvar *cps-test-i* nil) | ||
| 61 | (defun cps-get-test-i () | ||
| 62 | *cps-test-i*) | ||
| 63 | |||
| 64 | (cps-testcase cps-simple-1 (progn 1 2 3)) | ||
| 65 | (cps-testcase cps-empty-progn (progn)) | ||
| 66 | (cps-testcase cps-inline-not-progn (inline 1 2 3)) | ||
| 67 | (cps-testcase cps-prog1-a (prog1 1 2 3)) | ||
| 68 | (cps-testcase cps-prog1-b (prog1 1)) | ||
| 69 | (cps-testcase cps-prog1-c (prog2 1 2 3)) | ||
| 70 | (cps-testcase cps-quote (progn 'hello)) | ||
| 71 | (cps-testcase cps-function (progn #'hello)) | ||
| 72 | |||
| 73 | (cps-testcase cps-and-fail (and 1 nil 2)) | ||
| 74 | (cps-testcase cps-and-succeed (and 1 2 3)) | ||
| 75 | (cps-testcase cps-and-empty (and)) | ||
| 76 | |||
| 77 | (cps-testcase cps-or-fallthrough (or nil 1 2)) | ||
| 78 | (cps-testcase cps-or-alltrue (or 1 2 3)) | ||
| 79 | (cps-testcase cps-or-empty (or)) | ||
| 80 | |||
| 81 | (cps-testcase cps-let* (let* ((i 10)) i)) | ||
| 82 | (cps-testcase cps-let*-shadow-empty (let* ((i 10)) (let (i) i))) | ||
| 83 | (cps-testcase cps-let (let ((i 10)) i)) | ||
| 84 | (cps-testcase cps-let-shadow-empty (let ((i 10)) (let (i) i))) | ||
| 85 | (cps-testcase cps-let-novars (let nil 42)) | ||
| 86 | (cps-testcase cps-let*-novars (let* nil 42)) | ||
| 87 | |||
| 88 | (cps-testcase cps-let-parallel | ||
| 89 | (let ((a 5) (b 6)) (let ((a b) (b a)) (list a b)))) | ||
| 90 | |||
| 91 | (cps-testcase cps-let*-parallel | ||
| 92 | (let* ((a 5) (b 6)) (let* ((a b) (b a)) (list a b)))) | ||
| 93 | |||
| 94 | (cps-testcase cps-while-dynamic | ||
| 95 | (setq *cps-test-i* 0) | ||
| 96 | (while (< *cps-test-i* 10) | ||
| 97 | (setf *cps-test-i* (+ *cps-test-i* 1))) | ||
| 98 | *cps-test-i*) | ||
| 99 | |||
| 100 | (cps-testcase cps-while-lexical | ||
| 101 | (let* ((i 0) (j 10)) | ||
| 102 | (while (< i 10) | ||
| 103 | (setf i (+ i 1)) | ||
| 104 | (setf j (+ j (* i 10)))) | ||
| 105 | j)) | ||
| 106 | |||
| 107 | (cps-testcase cps-while-incf | ||
| 108 | (let* ((i 0) (j 10)) | ||
| 109 | (while (< i 10) | ||
| 110 | (incf i) | ||
| 111 | (setf j (+ j (* i 10)))) | ||
| 112 | j)) | ||
| 113 | |||
| 114 | (cps-testcase cps-dynbind | ||
| 115 | (setf *cps-test-i* 0) | ||
| 116 | (let* ((*cps-test-i* 5)) | ||
| 117 | (cps-get-test-i))) | ||
| 118 | |||
| 119 | (cps-testcase cps-nested-application | ||
| 120 | (+ (+ 3 5) 1)) | ||
| 121 | |||
| 122 | (cps-testcase cps-unwind-protect | ||
| 123 | (setf *cps-test-i* 0) | ||
| 124 | (unwind-protect | ||
| 125 | (setf *cps-test-i* 1) | ||
| 126 | (setf *cps-test-i* 2)) | ||
| 127 | *cps-test-i*) | ||
| 128 | |||
| 129 | (cps-testcase cps-catch-unused | ||
| 130 | (catch 'mytag 42)) | ||
| 131 | |||
| 132 | (cps-testcase cps-catch-thrown | ||
| 133 | (1+ (catch 'mytag | ||
| 134 | (throw 'mytag (+ 2 2))))) | ||
| 135 | |||
| 136 | (cps-testcase cps-loop | ||
| 137 | (cl-loop for x from 1 to 10 collect x)) | ||
| 138 | |||
| 139 | (cps-testcase cps-loop-backquote | ||
| 140 | `(a b ,(cl-loop for x from 1 to 10 collect x) -1)) | ||
| 141 | |||
| 142 | (cps-testcase cps-if-branch-a | ||
| 143 | (if t 'abc)) | ||
| 144 | |||
| 145 | (cps-testcase cps-if-branch-b | ||
| 146 | (if t 'abc 'def)) | ||
| 147 | |||
| 148 | (cps-testcase cps-if-condition-fail | ||
| 149 | (if nil 'abc 'def)) | ||
| 150 | |||
| 151 | (cps-testcase cps-cond-empty | ||
| 152 | (cond)) | ||
| 153 | |||
| 154 | (cps-testcase cps-cond-atomi | ||
| 155 | (cond (42))) | ||
| 156 | |||
| 157 | (cps-testcase cps-cond-complex | ||
| 158 | (cond (nil 22) ((1+ 1) 42) (t 'bad))) | ||
| 159 | |||
| 160 | (put 'cps-test-error 'error-conditions '(cps-test-condition)) | ||
| 161 | |||
| 162 | (cps-testcase cps-condition-case | ||
| 163 | (condition-case | ||
| 164 | condvar | ||
| 165 | (signal 'cps-test-error 'test-data) | ||
| 166 | (cps-test-condition condvar))) | ||
| 167 | |||
| 168 | (cps-testcase cps-condition-case-no-error | ||
| 169 | (condition-case | ||
| 170 | condvar | ||
| 171 | 42 | ||
| 172 | (cps-test-condition condvar))) | ||
| 173 | |||
| 174 | (ert-deftest cps-generator-basic () | ||
| 175 | (let* ((gen (iter-lambda () | ||
| 176 | (iter-yield 1) | ||
| 177 | (iter-yield 2) | ||
| 178 | (iter-yield 3) | ||
| 179 | 4)) | ||
| 180 | (gen-inst (funcall gen))) | ||
| 181 | (should (eql (iter-next gen-inst) 1)) | ||
| 182 | (should (eql (iter-next gen-inst) 2)) | ||
| 183 | (should (eql (iter-next gen-inst) 3)) | ||
| 184 | |||
| 185 | ;; should-error doesn't catch the generator-end condition (which | ||
| 186 | ;; isn't an error), so we write our own. | ||
| 187 | (let (errored) | ||
| 188 | (condition-case x | ||
| 189 | (iter-next gen-inst) | ||
| 190 | (iter-end-of-sequence | ||
| 191 | (setf errored (cdr x)))) | ||
| 192 | (should (eql errored 4))))) | ||
| 193 | |||
| 194 | (iter-defun mygenerator (i) | ||
| 195 | (iter-yield 1) | ||
| 196 | (iter-yield i) | ||
| 197 | (iter-yield 2)) | ||
| 198 | |||
| 199 | (ert-deftest cps-test-iter-do () | ||
| 200 | (let (mylist) | ||
| 201 | (iter-do (x (mygenerator 4)) | ||
| 202 | (push x mylist)) | ||
| 203 | |||
| 204 | (assert (equal mylist '(2 4 1))))) | ||
| 205 | |||
| 206 | (iter-defun gen-using-yield-value () | ||
| 207 | (let (f) | ||
| 208 | (setf f (iter-yield 42)) | ||
| 209 | (iter-yield f) | ||
| 210 | -8)) | ||
| 211 | |||
| 212 | (ert-deftest cps-yield-value () | ||
| 213 | (let ((it (gen-using-yield-value))) | ||
| 214 | (should (eql (iter-next it -1) 42)) | ||
| 215 | (should (eql (iter-next it -1) -1)))) | ||
| 216 | |||
| 217 | (ert-deftest cps-loop () | ||
| 218 | (should | ||
| 219 | (equal (cl-loop for x iter-by (mygenerator 42) | ||
| 220 | collect x) | ||
| 221 | '(1 42 2)))) | ||
| 222 | |||
| 223 | (iter-defun gen-using-yield-from () | ||
| 224 | (let ((sub-iter (gen-using-yield-value))) | ||
| 225 | (iter-yield (1+ (iter-yield-from sub-iter))))) | ||
| 226 | |||
| 227 | (ert-deftest cps-test-yield-from-works () | ||
| 228 | (let ((it (gen-using-yield-from))) | ||
| 229 | (should (eql (iter-next it -1) 42)) | ||
| 230 | (should (eql (iter-next it -1) -1)) | ||
| 231 | (should (eql (iter-next it -1) -7)))) | ||
| 232 | |||
| 233 | (defvar cps-test-closed-flag nil) | ||
| 234 | |||
| 235 | (ert-deftest cps-test-iter-close () | ||
| 236 | (garbage-collect) | ||
| 237 | (let ((cps-test-closed-flag nil)) | ||
| 238 | (let ((iter (funcall | ||
| 239 | (iter-lambda () | ||
| 240 | (unwind-protect (iter-yield 1) | ||
| 241 | (setf cps-test-closed-flag t)))))) | ||
| 242 | (should (equal (iter-next iter) 1)) | ||
| 243 | (should (not cps-test-closed-flag)) | ||
| 244 | (iter-close iter) | ||
| 245 | (should cps-test-closed-flag)))) | ||
| 246 | |||
| 247 | (ert-deftest cps-test-iter-close-idempotent () | ||
| 248 | (garbage-collect) | ||
| 249 | (let ((cps-test-closed-flag nil)) | ||
| 250 | (let ((iter (funcall | ||
| 251 | (iter-lambda () | ||
| 252 | (unwind-protect (iter-yield 1) | ||
| 253 | (setf cps-test-closed-flag t)))))) | ||
| 254 | (should (equal (iter-next iter) 1)) | ||
| 255 | (should (not cps-test-closed-flag)) | ||
| 256 | (iter-close iter) | ||
| 257 | (should cps-test-closed-flag) | ||
| 258 | (setf cps-test-closed-flag nil) | ||
| 259 | (iter-close iter) | ||
| 260 | (should (not cps-test-closed-flag))))) | ||
| 261 | |||
| 262 | (ert-deftest cps-test-iter-close-finalizer () | ||
| 263 | (skip-unless gc-precise-p) | ||
| 264 | (garbage-collect) | ||
| 265 | (let ((cps-test-closed-flag nil)) | ||
| 266 | (let ((iter (funcall | ||
| 267 | (iter-lambda () | ||
| 268 | (unwind-protect (iter-yield 1) | ||
| 269 | (setf cps-test-closed-flag t)))))) | ||
| 270 | (should (equal (iter-next iter) 1)) | ||
| 271 | (should (not cps-test-closed-flag)) | ||
| 272 | (setf iter nil) | ||
| 273 | (garbage-collect) | ||
| 274 | (should cps-test-closed-flag)))) | ||
| 275 | |||
| 276 | (ert-deftest cps-test-iter-cleanup-once-only () | ||
| 277 | (let* ((nr-unwound 0) | ||
| 278 | (iter | ||
| 279 | (funcall (iter-lambda () | ||
| 280 | (unwind-protect | ||
| 281 | (progn | ||
| 282 | (iter-yield 1) | ||
| 283 | (error "test") | ||
| 284 | (iter-yield 2)) | ||
| 285 | (incf nr-unwound)))))) | ||
| 286 | (should (equal (iter-next iter) 1)) | ||
| 287 | (should-error (iter-next iter)) | ||
| 288 | (should (equal nr-unwound 1)))) | ||