All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.cloning.kt Maven / Gradle / Ivy

Go to download

Provides fully-fledged multishot delimitied continuations in Kotlin with Coroutines

The newest version!
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")