Skip to content

Instantly share code, notes, and snippets.

@smitsgit
Created January 9, 2019 16:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smitsgit/d67e0ec74a9aa444f43d4b70ad3b4609 to your computer and use it in GitHub Desktop.
Save smitsgit/d67e0ec74a9aa444f43d4b70ad3b4609 to your computer and use it in GitHub Desktop.
package generator
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
/*
ES6-style generator that can send values between coroutine and outer code in both ways.
Note that in ES6-generators the first invocation of `next()` goes not accept a parameter, but
just starts a coroutine until a subsequent `yield`, so to adopt it for the type-safe interface
we must declare `next` to be always invoked with a parameter and make our coroutine receive the first
parameter to `next` when it starts (so it is not lost). We also have to introduce an additional parameter to
`yieldAll` to start a delegated generator.
*/
interface Generator<out T, in R> {
fun next(param: R): T? // returns `null` when generator is over
}
@RestrictsSuspension
interface GeneratorBuilder<in T, R> {
suspend fun yield(value: T): R
suspend fun yieldAll(generator: Generator<T, R>, param: R)
}
fun <T, R> generate(block: suspend GeneratorBuilder<T, R>.(R) -> Unit): Generator<T, R> {
val coroutine = GeneratorCoroutine<T, R>()
val initial: suspend (R) -> Unit = { result -> block(coroutine, result) }
coroutine.nextStep = { param -> initial.startCoroutine(param, coroutine) }
return coroutine
}
// Generator coroutine implementation class
internal class GeneratorCoroutine<T, R>: Generator<T, R>, GeneratorBuilder<T, R>, Continuation<Unit> {
lateinit var nextStep: (R) -> Unit
private var lastValue: T? = null
private var lastException: Throwable? = null
// Generator<T, R> implementation
override fun next(param: R): T? {
nextStep(param)
lastException?.let { throw it }
return lastValue
}
// GeneratorBuilder<T, R> implementation
override suspend fun yield(value: T): R = suspendCoroutineUninterceptedOrReturn { cont ->
lastValue = value
nextStep = { param -> cont.resume(param) }
COROUTINE_SUSPENDED
}
override suspend fun yieldAll(generator: Generator<T, R>, param: R): Unit = suspendCoroutineUninterceptedOrReturn sc@ { cont ->
lastValue = generator.next(param)
if (lastValue == null) return@sc Unit // delegated coroutine does not generate anything -- resume
nextStep = { param ->
lastValue = generator.next(param)
if (lastValue == null) cont.resume(Unit) // resume when delegate is over
}
COROUTINE_SUSPENDED
}
// Continuation<Unit> implementation
override val context: CoroutineContext get() = EmptyCoroutineContext
override fun resumeWith(result: Result<Unit>) {
result
.onSuccess { lastValue = null }
.onFailure { lastException = it }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment