(define-library
  (sx coroutines)
  (export
    make-coroutine
    coroutine?
    coroutine-alive?
    coroutine-yield
    coroutine-handle-result
    coroutine-resume)
  (begin
    (define make-coroutine (fn (thunk) {:suspension nil :thunk thunk :type "coroutine" :state "ready"}))
    (define
      coroutine?
      (fn (v) (and (dict? v) (= (get v "type") "coroutine"))))
    (define
      coroutine-alive?
      (fn (c) (and (coroutine? c) (not (= (get c "state") "dead")))))
    (define coroutine-yield (fn (val) (perform {:value val :op "coroutine-yield"})))
    (define
      coroutine-handle-result
      (fn
        (c result)
        (if
          (cek-terminal? result)
          (do (dict-set! c "state" "dead") {:done true :value (cek-value result)})
          (let
            ((request (cek-io-request result)))
            (if
              (and (dict? request) (= (get request "op") "coroutine-yield"))
              (do
                (dict-set! c "state" "suspended")
                (dict-set! c "suspension" result)
                {:done false :value (get request "value")})
              (perform request))))))
    (define
      coroutine-resume
      (fn
        (c val)
        (cond
          (not (coroutine? c))
          (error "coroutine-resume: not a coroutine")
          (= (get c "state") "dead")
          (error "coroutine-resume: coroutine is dead")
          (= (get c "state") "ready")
          (do
            (dict-set! c "state" "running")
            (coroutine-handle-result
              c
              (cek-step-loop
                (make-cek-state (list (get c "thunk")) (make-env) (list)))))
          (= (get c "state") "suspended")
          (do
            (dict-set! c "state" "running")
            (coroutine-handle-result c (cek-resume (get c "suspension") val)))
          :else (error
            (str "coroutine-resume: unexpected state: " (get c "state"))))))))
