123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- "### Continuation objects
- The value given to the handler in [[call-with-prompt]], much like
- the value captured by [[shift]], is not a coroutine: it is a
- continuation object. Continuation objects (or just \"continuations\")
- can be applied, much like functions, to continue their execution.
- They may also be given to [[call-with-prompt]]."
- (import lua/coroutine c)
- (import data/struct ())
- (defstruct (continuation (hide reify-continuation) continuation?)
- (fields
- (immutable thread (hide cont-thread)))
- (constructor new
- (lambda (k)
- (setmetatable (new k)
- { :__call (lambda (k &args)
- (apply continue (cont-thread k) args)) }))))
- (defmethod (pretty continuation) (_) "«continuation»")
- (defun call-with-prompt (prompt-tag body handler)
- "Call the thunk BODY with a prompt PROMPT-TAG in scope. If BODY
- aborts to PROMPT-TAG, then HANDLER is invoked with the coroutine
- representing the rest of BODY along with any extra arguments to
- [[abort-to-prompt]].
- **NOTE**: The given HANDLER is not executed in the scope of the
- prompt, so subsequent calls to [[abort-to-prompt]] in the
- continuation will not be handled.
- ### Example
- ```cl
- > (call-with-prompt 'tag
- . (lambda ()
- . (+ 1 (abort-to-prompt 'tag)))
- . (lambda (k)
- . (k 1)))
- out = 2
- ```"
- (let* [(k (cond
- [(= (type body) "thread") body]
- ; we reify the continuation before handing it off to the
- ; handler anyway
- [(= (type body) "continuation") (cont-thread body)]
- [(= (type body) "function") (c/create body)]
- [else (error! (.. "expected a coroutine or a function, got " (type body)))]))]
- (loop [(k k)
- (res nil)]
- [(= (c/status k) :dead) res]
- (let* [((ok err) (c/resume k))]
- (cond
- [(and ok
- (list? err)
- (>= (n err) 2)
- (eq? (car err) :abort))
- (if (eq? (cadr err) prompt-tag)
- (handler (reify-continuation k) (cddr err))
- (apply abort-to-prompt (cadr err) (cddr err)))]
- [(not ok)
- (error! err)]
- [ok
- (recur k err)])))))
- (define call/p call-with-prompt)
- (defmacro let-prompt (tg e h)
- "Evaluate E in a prompt with the tag TG and handler H."
- `(call-with-prompt ,tg (lambda () ,e) ,h))
- (define-macro let/p let-prompt)
- (defun call-with-escape-continuation (body)
- "Invoke the thunk BODY with an escape continuation.
- ### Example
- ```cl
- > (call-with-escape-continuation
- . (lambda (return)
- . (print! \"this is printed\")
- . (return 1)
- . (print! \"this is not\")))
- this is printed
- out = 1
- ```"
- (call-with-prompt 'escape-continuation
- (lambda ()
- (body (lambda (&rest)
- (apply abort-to-prompt
- 'escape-continuation rest))))
- (lambda (_ &rest)
- (splice (car rest)))))
- (defmacro let-escape-continuation (k &body)
- "Bind K within BODY to an escape continuation.
- ### Example
- ```cl
- > (let-escape-continuation return
- . (print! 1)
- . (return 2)
- . (print! 3))
- 1
- out = 2
- ```"
- `(call-with-escape-continuation (lambda (,k) ,@body)))
- (define call/ec call-with-escape-continuation)
- (define-macro let/ec let-escape-continuation)
- (defun continue (k &args) :hidden
- (let* [(last-res nil)]
- (while (/= (c/status k) :dead)
- (let* [((ok err) (apply c/resume k args))]
- (if (not ok)
- (error! err)
- (progn
- (set! args '())
- (when err
- (set! last-res err))))))
- last-res))
- (defun abort-to-prompt (tag &rest)
- "Abort to the prompt TAG, giving REST as arguments to the handler."
- (c/yield (cons :abort tag rest)))
- (defun abort/p (tag &rest)
- "Abort to the prompt TAG, giving REST as arguments to the handler."
- (c/yield (cons :abort tag rest)))
- (defmacro reset (&body)
- "Establish a prompt, and evaluate BODY within that prompt.
- ### Example
- ```
- > (* 2 (reset (+ 1 (shift k (k 5)))))
- out = 12
- ```"
- (let* [(cont (gensym))
- (f (gensym))]
- `(call-with-prompt ','reset-tag
- (lambda () ,@body)
- (lambda (,cont ,f)
- ((car ,f) ,cont)))))
- (defmacro shift (k &body)
- "Abort to the nearest [[reset]], and evaluate BODY in a scope where
- the captured continuation is bound to K.
- ### Example
- ```
- > (* 2 (reset (+ 1 (shift k (k 5)))))
- out = 12
- ```"
- `(abort-to-prompt ','reset-tag
- (lambda (,k)
- ,@body)))
- (defun alive? (k)
- "Check that the continuation K may be executed further.
- ### Example:
- ```
- > (alive? (reset (shift k k)))
- out = true
- ```"
- (cond
- [(continuation? k)
- (/= (c/status (cont-thread k)) "dead")]
- [(= (type k) :thread)
- (/= (c/status k) "dead")]
- [(function? k)
- true]
- [else false]))
- (defmacro block (&body)
- "Estabilish an escape continuation called `break` and evaluate BODY.
- ### Example:
- ```cl
- > (block
- . (print! 1)
- . (break)
- . (print! 2))
- 1
- out = nil
- ```"
- `(let-escape-continuation ,'break ,@body))
|