diff options
-rw-r--r-- | share/txr/stdlib/yield.tl | 38 | ||||
-rw-r--r-- | txr.1 | 156 |
2 files changed, 122 insertions, 72 deletions
diff --git a/share/txr/stdlib/yield.tl b/share/txr/stdlib/yield.tl index 43fa57ee..d076267b 100644 --- a/share/txr/stdlib/yield.tl +++ b/share/txr/stdlib/yield.tl @@ -25,16 +25,23 @@ (defstruct (sys:yld-item val cont) nil val cont) +(defstruct (sys:rcv-item val cont) nil val cont) + (defun sys:obtain-impl (fun) (finalize - (lambda (: reply) - (let ((yi (call fun reply))) - (cond - ((eq (typeof yi) 'sys:yld-item) - (call fun 'sys:cont-free) - (set fun yi.cont) - yi.val) - (t yi)))) + (lambda (: resume-val) + (let ((yi (call fun resume-val))) + (while t + (cond + ((eq (typeof yi) 'sys:yld-item) + (call fun 'sys:cont-free) + (set fun yi.cont) + (return yi.val)) + ((eq (typeof yi) 'sys:rcv-item) + (call fun 'sys:cont-free) + (set fun yi.cont) + (set yi (call fun resume-val))) + (t (return yi)))))) (lambda (cont) (call cont 'sys:cont-poison)))) @@ -47,17 +54,22 @@ (defmacro obtain-block (name . body) ^(obtain (block ,name ,*body))) -(defmacro yield-from (:form ctx-form name form) +(defmacro yield-from (:form ctx-form name : (form nil have-form-p)) (let ((cont-sym (gensym))) ^(sys:capture-cont ',name (lambda (,cont-sym) (sys:abscond-from ,name - (new (sys:yld-item - ,form ,cont-sym)))) + ,(if have-form-p + ^(new (sys:yld-item + ,form ,cont-sym)) + ^(new (sys:rcv-item + nil ,cont-sym))))) ',ctx-form))) -(defmacro yield (form) - ^(yield-from nil ,form)) +(defmacro yield (: (form nil have-form-p)) + (if have-form-p + ^(yield-from nil ,form) + ^(yield-from nil))) (defmacro suspend (:form form name var . body) ^(sys:capture-cont ',name (lambda (,var) @@ -27727,7 +27727,7 @@ that control will return into a restarted copy of that context. .coNP Macros @ obtain and @ yield-from .synb .mets (obtain << forms *) -.mets (yield-from < name << form ) +.mets (yield-from < name <> [ form ]) .syne .desc The @@ -27737,59 +27737,66 @@ and macros closely inter-operate. The +.code obtain +macro treats zero or more +.metn form -s +as a suspendable execution context called the +.IR "obtain block" . +It is expected that +.metn form -s +establish a block named +.meta name +and return its result value to +.codn obtain . + +Without evaluating any of the forms in the obtain block, +.code obtain +returns a function, which takes one optional argument. +This argument, called the +.IR "resume value" , +defaults to +.code nil +if it is omitted. + +The function represents the suspended execution context. + +The context is resumed whenever the function is called, and executes +until the next .code yield-from -macro captures a continuation up to the closest enclosing block named +statement which references the block named .metn name . -Then it evaluates -.metn form . -Both the continuation and the value of +The function's reply argument is noted. + +If the +.code yield-from +specifies a .meta form -are encapsulated in a special -.IR "yield object" . -Finally, +argument, then the execution context suspends, and the resume function +terminates and returns the value of that form. When the function is called +again to resume the context, the .code yield-from -performs a non-local transfer to the same block, so that the yield object -appears as the result value of that block. The non-local transfer is -performed abruptly, by the -.code sys:abscond-from -operator. +returns the previously noted resume value (and the new resume +value just passed is noted in its place). -An -.code obtain -form returns a function of one optional argument, whose value defaults to -.codn nil . +If the +.code yield-from +specifies no +.meta form +argument, then it briefly suspends the execution context only +to retrieve the resume value, without producing an item. Since +no item is produced, the resume function does not return. +The execution context implicitly resumes. -When the function is invoked, its argument is ignored and -.meta forms -are evaluated. If -.meta forms -produce any object other than a yield object, the function returns -that object. +When execution reaches the last form in the obtain block, the +resume value is discarded. The execution context terminates, and +the most recent call to the resume function returns the value of +that last form. -If -.meta forms -produce a yield object, then the function returns the value that is -encapsulated in the yield object. Prior to returning this value, -the function updates its internal state such that the next time it is -called, instead of evaluating -.meta forms -it will invoke the continuation function stored in the yield object. Moreover, -instead of ignoring its argument, it will pass that argument to the -continuation function. In the continuation, the argument will emerge -out of the -.meta yield-from -form as its result value. - -The return value of the continuation is then -treated exactly like the result value of -.metn forms : -if it is an ordinary value, it is returned; otherwise, if it is -a yield object, its stored value is returned and the state is updated -with the new yield object's continuation. +.TP* Notes: The .code obtain -macro registers a finalizer against the returned function. +macro registers a finalizer against the returned resume function. The finalizer invokes the function, passing it the symbol .codn sys:cont-poison , thereby triggering unwinding in the most recently captured @@ -27797,22 +27804,15 @@ continuation. Thus, abandoned .code obtain blocks are subject to unwinding when they become garbage. -.TP* Notes: - -These macros provide a simple abstraction for the use of continuations. -A module of code can be written which uses +The .code yield-from -to suspend its execution, passing control back to specific top-level block, -along with a yielded item. The +macro works by capturing a continuation and performing a nonlocal +exit to the nearest block called +.metn name . +It passes a special yield object to that block. The .code obtain -macro converts that block to a function which can be iteratively called -to retrieve each successively yielded item, and resume the execution -of the suspended code so it can continue and yield the next one. -When the continuation completes, whatever value the block returns is -also produced as if it were a yielded item. Moreover, each -.code yield-from -call produces, as its return value, the argument of the function call which -resumes the continuation. Thus for each item which is yielded, +macro generates code which knows what to do with this special yield +object. .TP* Examples: @@ -27910,6 +27910,44 @@ of fruit names. (apple banana orange) .cble +The following example demonstrates an accumulator. Values passed to the +resume function are added to a counter which is initially zero. +Each call to the function returns the updated value of the accumulator. +Note the use of +.code (yield-from acc) +with no arguments to receive the value passed to the first +call to the resume function, without yielding an item. +The very first return value +.code 1 +is produced by the +.code (yield-from acc sum) +form, not by +.codn (yield-from acc) . +The latter only obtains the initial value +.code 1 +and uses it to establish the seed value of the accumulator. Without causing +the resume function to terminate and return, control passes into the loop, +which yields the first item, causing the resume function call +.code (call *1 1) +to return +.codn 1 : + +.cblk + 1> (obtain + (block acc + (let ((sum (yield-from acc))) + (while t (inc sum (yield-from acc sum)))))) + #<interpreted fun: lambda (: resume-val)> + 2> (call *1 1) + 1 + 3> (call *1 2) + 3 + 4> (call *1 3) + 6 + 5> (call *1 4) + 10 +.cble + .coNP Macro @ obtain-from .synb .mets (obtain-block < name << forms *) @@ -27935,7 +27973,7 @@ That is to say, the following equivalence holds: .coNP Macro @ yield .synb -.mets (yield << form ) +.mets (yield <> [ form ]) .syne .desc The |