commonMain.cloning.kt Maven / Gradle / Ivy
Show all versions of kontinuity-jvm Show documentation
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.intercepted
import kotlin.jvm.JvmInline
internal expect val Continuation<*>.isCompilerGenerated: Boolean
internal expect val Continuation<*>.completion: Continuation<*>
internal expect fun Continuation.copy(completion: Continuation<*>): Continuation
@JvmInline
internal value class Frame(val cont: Continuation) {
fun resumeWith(value: Result, into: Continuation) = cont.copy(into).resumeWith(value)
fun resumeWithIntercepted(value: Result, into: Continuation) = cont.copy(into).intercepted().resumeWith(value)
}
@JvmInline
internal value class FrameList(val frame: Frame) {
@Suppress("UNCHECKED_CAST")
private val completion: Continuation get() = frame.cont.completion as Continuation
fun head() = frame
fun tail(): FrameList? =
if (this.completion is WrapperCont) null else FrameList(Frame(this.completion))
}
internal sealed interface SplitSeq {
val context: CoroutineContext
}
internal fun SplitSeq.resumeWith(result: Result, isIntercepted: Boolean) {
val exception = result.exceptionOrNull()
if (exception is SeekingStackException) exception.use(this)
else resumeWithImpl(result, isIntercepted)
}
private tailrec fun SplitSeq.resumeWithImpl(
result: Result, isIntercepted: Boolean
) {
when (this) {
is EmptyCont -> (if (isIntercepted) underlying.intercepted() else underlying).resumeWith(
result
)
is FramesCont -> if (wrapperCont == null) {
if (isIntercepted) {
head.resumeWithIntercepted(result, WrapperCont(tail))
} else {
val tail = tail
val wrapper = WrapperCont(tail, isWaitingForValue = true)
head.resumeWith(result, wrapper)
val res = wrapper.result
if (res != waitingForValue && res != hasBeenIntercepted) {
val exception = res.exceptionOrNull()
if (exception is SeekingStackException) exception.use(tail)
else tail.resumeWithImpl(res, false)
} else {
wrapper.result = hasBeenIntercepted
}
}
} else {
val wrapper = wrapperCont
val rest = rest!! as SplitSeq
wrapper.seq = rest
wrapper.realContext = rest.context
if (isIntercepted) {
wrapper.result = hasBeenIntercepted
frames.head().cont.intercepted().resumeWith(result)
} else {
wrapper.result = waitingForValue
frames.head().cont.resumeWith(result)
val res = wrapper.result
if (res != waitingForValue && res != hasBeenIntercepted) {
val exception = res.exceptionOrNull()
if (exception is SeekingStackException) exception.use(rest)
else rest.resumeWithImpl(res, false)
} else {
wrapper.result = hasBeenIntercepted
}
}
}
is PromptCont -> rest!!.resumeWithImpl(result, isIntercepted = isIntercepted)
is ReaderCont<*, Start, First, End> -> rest!!.resumeWithImpl(result, isIntercepted = isIntercepted)
}
}
internal tailrec fun SplitSeq.splitAtAux(
p: Prompt, seg: Segment
): Pair, SplitSeq> = when (this) {
is EmptyCont -> error("Prompt not found $p in $seg")
is FramesCont -> (rest!! as SplitSeq).splitAtAux(p, FramesSegment(frames, seg))
is PromptCont -> if (p === this.p) {
// Start and P are now unified, but the compiler doesn't get it
val pair: Pair, SplitSeq> = seg to rest!!
@Suppress("UNCHECKED_CAST")
pair as Pair, SplitSeq>
} else {
rest!!.splitAtAux(p, PromptSegment(this.p, seg))
}
is ReaderCont<*, Start, First, End> -> rest!!.splitAtAux(p, toSegment(seg))
}
private fun ReaderCont.toSegment(seg: Segment): ReaderSegment =
ReaderSegment(p, _state, fork, seg)
internal tailrec fun SplitSeq.deleteReader(
p: Reader<*>, previous: ExpectsSequenceStartingWith<*>
): Unit = when (this) {
is EmptyCont -> error("Reader not found $p")
is FramesCont -> rest!!.deleteReader(p, this)
is PromptCont -> rest!!.deleteReader(p, this)
is ReaderCont<*, Start, *, End> -> if (p === this.p) {
previous as ExpectsSequenceStartingWith
previous.sequence = rest!!
} else rest!!.deleteReader(p, this)
}
internal tailrec fun SplitSeq.find(p: Prompt): SplitSeq
= when (this) {
is EmptyCont -> error("Prompt not found $p")
is FramesCont -> rest!!.find(p)
is PromptCont -> if (p === this.p) {
// Start and P are now unified, but the compiler doesn't get it
@Suppress("UNCHECKED_CAST")
this.rest!! as SplitSeq
} else rest!!.find(p)
is ReaderCont<*, Start, *, End> -> rest!!.find(p)
}
internal tailrec fun SplitSeq.findGuyBefore(
p: Prompt,
previous: ExpectsSequenceStartingWith?
): ExpectsSequenceStartingWith? = when (this) {
is EmptyCont -> error("Prompt not found $p")
is FramesCont -> (rest!! as SplitSeq).findGuyBefore(p, this)
is PromptCont -> if (p === this.p) {
// Start and P are now unified, but the compiler doesn't get it
@Suppress("UNCHECKED_CAST")
previous as ExpectsSequenceStartingWith?
} else rest!!.findGuyBefore(p, this)
is ReaderCont<*, Start, *, End> -> rest!!.findGuyBefore(p, this)
}
internal tailrec fun SplitSeq.find(p: Reader): S = when (this) {
is EmptyCont -> error("Reader not found $p")
is FramesCont -> rest!!.find(p)
is PromptCont -> rest!!.find(p)
is ReaderCont<*, Start, *, End> -> if (p === this.p) {
@Suppress("UNCHECKED_CAST")
this.state as S
} else rest!!.find(p)
}
internal tailrec fun SplitSeq.findOrNull(p: Reader): S? = when (this) {
is EmptyCont -> null
is FramesCont -> rest!!.findOrNull(p)
is PromptCont -> rest!!.findOrNull(p)
is ReaderCont<*, Start, *, End> -> if (p === this.p) {
@Suppress("UNCHECKED_CAST")
this.state as S
} else rest!!.findOrNull(p)
}
internal fun SplitSeq.splitAt(p: Prompt): Pair, SplitSeq> =
splitAtAux(p, EmptySegment)
internal fun SplitSeq.splitAtOnce(p: Prompt): Pair, SplitSeq> {
val box = findGuyBefore(p, null)
return if (box != null) {
SingleUseSegment(box, this) to box.sequence!!.also { box.sequence = null } as SplitSeq
} else {
EmptySegment to this as SplitSeq
}
}
internal fun
SplitSeq
.pushPrompt(p: Prompt
): PromptCont
=
PromptCont(p, this)
internal fun SplitSeq
.pushReader(
p: Reader, value: S, fork: S.() -> S
): ReaderCont = ReaderCont(p, value, fork, this)
internal data class EmptyCont(val underlying: Continuation) : SplitSeq {
override val context: CoroutineContext = underlying.context
}
// frame :: frames ::: rest
internal data class FramesCont(
val frames: FrameList, var rest: SplitSeq?,
val wrapperCont: WrapperCont?,
) : SplitSeq, ExpectsSequenceStartingWith {
val head get() = frames.head()
@Suppress("UNCHECKED_CAST")
val tail: SplitSeq
get() = frames.tail()?.let { FramesCont(it, rest, wrapperCont) }
?: rest as SplitSeq // First == Last, but the compiler doesn't get it
override val context: CoroutineContext = rest!!.context
override var sequence: SplitSeq?
get() = rest
set(value) {
rest = value as SplitSeq?
}
}
internal fun CoroutineContext.unwrap(): CoroutineContext =
if (this is WrapperCont<*>) this.realContext else this
internal class WrapperCont(seq: SplitSeq, isWaitingForValue: Boolean = false) : Continuation,
CoroutineContext {
var seq: SplitSeq? = seq
var result: Result = if (isWaitingForValue) waitingForValue else hasBeenIntercepted
var realContext = seq.context.unwrap()
set(value) {
field = value.unwrap()
}
override val context: CoroutineContext get() = this
override fun resumeWith(result: Result) {
if (this.result == waitingForValue) {
this.result = result
} else {
seq!!.resumeWith(result, isIntercepted = false)
}
}
// TODO improve these implementations
override fun fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R =
realContext.fold(initial, operation)
override fun plus(context: CoroutineContext): CoroutineContext = realContext.plus(context)
override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext =
realContext.minusKey(key)
override fun get(key: CoroutineContext.Key): E? =
realContext.get(key)
}
private data object WaitingForValue : Throwable()
private val waitingForValue = Result.failure(WaitingForValue)
private data object HasBeenIntercepted : Throwable()
private val hasBeenIntercepted = Result.failure(HasBeenIntercepted)
internal data class PromptCont(
val p: Prompt, var rest: SplitSeq?
) : SplitSeq, ExpectsSequenceStartingWith {
override val context = rest!!.context
override var sequence: SplitSeq?
get() = rest
set(value) {
rest = value as SplitSeq?
}
}
internal data class ReaderCont(
val p: Reader, var _state: State, val fork: State.() -> State, var rest: SplitSeq?,
private var forkOnFirstRead: Boolean = false
) : SplitSeq, ExpectsSequenceStartingWith {
override val context = rest!!.context
val state: State
get() {
if (forkOnFirstRead) {
forkOnFirstRead = false
_state = fork(_state)
}
return _state
}
override var sequence: SplitSeq?
get() = rest
set(value) {
rest = value as SplitSeq?
}
}
// sub continuations / stack segments
// mirrors the stack, and so is in reverse order. allows easy access to the state
// stored in the current prompt
internal sealed interface Segment
internal tailrec infix fun Segment.prependTo(stack: SplitSeq): SplitSeq =
when (this) {
is EmptySegment -> {
// Start == End
stack as SplitSeq
}
is FramesSegment -> init prependTo FramesCont(frames, stack, null)
is PromptSegment -> init prependTo PromptCont(prompt, stack)
is ReaderSegment<*, Start, First, End> -> init prependTo ReaderCont(prompt, state, fork, stack, forkOnFirstRead = true)
is SingleUseSegment -> {
box.sequence = stack
cont as SplitSeq
}
}
internal data object EmptySegment : Segment
internal data class FramesSegment(
val frames: FrameList, val init: Segment
) : Segment
internal data class PromptSegment(
val prompt: Prompt, val init: Segment
) : Segment
internal data class ReaderSegment(
val prompt: Reader, val state: State, val fork: (State.() -> State), val init: Segment
) : Segment
// Expects that cont eventually refers to box
internal data class SingleUseSegment(
val box: ExpectsSequenceStartingWith, val cont: SplitSeq
) : Segment
internal sealed interface ExpectsSequenceStartingWith {
var sequence: SplitSeq?
}
internal fun collectStack(continuation: Continuation): SplitSeq =
findNearestWrapperCont(continuation).toFramesCont(continuation)
private fun WrapperCont.toFramesCont(
continuation: Continuation
): FramesCont =
FramesCont(FrameList(Frame(continuation)), seq!!.also { seq = null }, this)
internal fun findNearestSplitSeq(continuation: Continuation<*>): SplitSeq<*, *, *> =
findNearestWrapperCont(continuation).seq!!
private fun findNearestWrapperCont(continuation: Continuation<*>): WrapperCont<*> =
continuation.context as? WrapperCont<*> ?: error("No WrapperCont found in stack")