Coverage Summary for Class: ChoicePointContextKt (it.unibo.tuprolog.argumentation.core.engine)

Class Class, % Method, % Branch, % Line, % Instruction, %
ChoicePointContextKt 100% (1/1) 100% (3/3) 100% (2/2) 60% (3/5) 87.1% (27/31)


 // Forked from 2p-kt's solve-prolog 3.0.2 (Apache License 2.0): see README.md in this package
 package it.unibo.tuprolog.argumentation.core.engine
 
 import it.unibo.tuprolog.core.Rule
 import it.unibo.tuprolog.core.Struct
 import it.unibo.tuprolog.solve.library.Runtime
 import it.unibo.tuprolog.solve.primitive.Solve
 import it.unibo.tuprolog.utils.Cursor
 
 /**
  * One node of the `:solve-prolog` choice-point queue: a saved point in the proof search that
  * [it.unibo.tuprolog.argumentation.core.engine.fsm.StateBacktracking] can resume from, chained to its [parent] to form the
  * full lineage of choice points recorded since the query started (see [pathToRoot]).
  *
  * Crucially, a choice point is *not* a single alternative goal to retry: [alternatives] is a lazy [Cursor] over
  * *all* remaining alternatives (primitive responses, for [Primitives]; candidate clauses, for [Rules]) at that
  * point, and only one is ever consumed per resolution step -- the rest stay in the cursor to be pulled later on
  * backtracking. Every [ChoicePointContext] also captures the [executionContext] active when it was recorded,
  * which is what makes it possible to resume an entirely different branch of the proof tree on [backtrack]: not
  * just "try the next alternative", but "restore the whole saved execution-context lineage, then try the next
  * alternative". [Primitives] and [Rules] are otherwise structurally identical, reflecting how both primitives
  * and rules are modelled uniformly as producers of lazy streams of alternatives.
  *
  * @see PrologExecutionContext.choicePoints
  * @see appendPrimitives
  * @see appendRules
  */
 sealed class ChoicePointContext(
     open val alternatives: Cursor<out Any>,
     open val executionContext: PrologExecutionContext?,
     open val parent: ChoicePointContext?,
     open val depth: Int = 0,
 ) {
     // This assertion fails on JS since depth is undefined
 //    init {
 //        require((depth == 0 && parent == null) || (depth > 0 && parent != null)) {
 //            """Violated initial constraint for claass ChoicePointContext: (depth == 0 && parent == null) || (depth > 0 && parent != null)
 //                |   depth=$depth
 //                |   parent=$parent
 //            """.trimMargin()
 //        }
 //    }
 
     /** Whether this is the first choice point of a resolution (i.e. it has no [parent]). */
     val isRoot: Boolean
         get() = depth == 0
 
     /** Whether this choice point, or any of its ancestors along [pathToRoot], still has an alternative to try. */
     val hasOpenAlternatives: Boolean
         get() = pathToRoot.any { it.alternatives.hasNext }
 
     /** This choice point and all its ancestors, from here up to (and including) the root, in that order. */
     val pathToRoot: Sequence<ChoicePointContext>
         get() =
             sequence {
                 var curr: ChoicePointContext? = this@ChoicePointContext
                 while (curr != null) {
                     @Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
                     yield(curr!!)
                     curr = curr.parent
                 }
             }
 
     /** Shorthand for [executionContext]'s [PrologExecutionContext.depth], or `null` if [executionContext] is `null`. */
     val executionContextDepth: Int?
         get() = executionContext?.depth
 
     /** Shorthand for [executionContext]'s [PrologExecutionContext.procedure], or `null` if [executionContext] is `null`. */
     val executionContextProcedure: Struct?
         get() = executionContext?.procedure
 
     override fun toString(): String =
         "$typeName(" +
             "alternatives=$alternatives, " +
             if (executionContext === null) {
                 "executionContext=$executionContext, "
             } else {
                 "executionContextDepth=$executionContextDepth, "
                 "executionContextProcedure=$executionContextProcedure, "
             } +
             "depth=$depth" +
             ")"
 
     protected abstract val typeName: String
 
     /**
      * Resumes this choice point: restores its saved [executionContext] (carrying over [context]'s current step
      * counter, flags and knowledge bases), advances [alternatives] to the next one, and returns the resulting
      * [PrologExecutionContext], ready to be handed to
      * [it.unibo.tuprolog.argumentation.core.engine.fsm.StatePrimitiveExecution] or
      * [it.unibo.tuprolog.argumentation.core.engine.fsm.StateRuleExecution] depending on the concrete subtype.
      */
     abstract fun backtrack(context: PrologExecutionContext): PrologExecutionContext
 
     // The libraries to go on with, backtracking from the given context: the current ones, as libraries apply to the
     // whole resolution, unless either context is within an inlined call, whose libraries only apply to its goal
     protected fun librariesFor(context: PrologExecutionContext): Runtime =
         executionContext!!.let { if (it.scoped || context.scoped) it.libraries else context.libraries }
 
     /** A choice point recording the remaining [it.unibo.tuprolog.solve.primitive.Solve.Response] alternatives of a primitive call. */
     data class Primitives(
         override val alternatives: Cursor<out Solve.Response>,
         override val executionContext: PrologExecutionContext?,
         override val parent: ChoicePointContext?,
         override val depth: Int,
     ) : ChoicePointContext(alternatives, executionContext, parent, depth) {
         override fun toString(): String = super.toString()
 
         override val typeName: String
             get() = "Primitives"
 
         override fun backtrack(context: PrologExecutionContext): PrologExecutionContext {
             val tempContext =
                 executionContext!!.copy(
                     primitives = alternatives,
                     step = context.step + 1,
                     startTime = context.startTime,
                     flags = context.flags,
                     dynamicKb = context.dynamicKb,
                     staticKb = context.staticKb,
                     operators = context.operators,
                     inputChannels = context.inputChannels,
                     outputChannels = context.outputChannels,
                     libraries = librariesFor(context),
                     customData = context.customData.discardEphemeral(),
                 )
 
             val nextChoicePointContext =
                 copy(
                     alternatives = alternatives.next,
                     executionContext = tempContext,
                 )
 
             return tempContext.copy(choicePoints = nextChoicePointContext)
         }
     }
 
     /** A choice point recording the remaining candidate [Rule]s of a clause resolution attempt. */
     data class Rules(
         override val alternatives: Cursor<out Rule>,
         override val executionContext: PrologExecutionContext?,
         override val parent: ChoicePointContext?,
         override val depth: Int,
     ) : ChoicePointContext(alternatives, executionContext, parent, depth) {
         override fun toString(): String = super.toString()
 
         override val typeName: String
             get() = "Rules"
 
         override fun backtrack(context: PrologExecutionContext): PrologExecutionContext {
             val tempContext =
                 executionContext!!.copy(
                     rules = alternatives,
                     step = context.step + 1,
                     startTime = context.startTime,
                     flags = context.flags,
                     dynamicKb = context.dynamicKb,
                     staticKb = context.staticKb,
                     operators = context.operators,
                     inputChannels = context.inputChannels,
                     outputChannels = context.outputChannels,
                     libraries = librariesFor(context),
                     customData = context.customData.discardEphemeral(),
                 )
 
             val nextChoicePointContext =
                 copy(
                     alternatives = alternatives.next,
                     executionContext = tempContext,
                 )
 
             return tempContext.copy(choicePoints = nextChoicePointContext)
         }
     }
 }
 
 /** The [ChoicePointContext.depth] a new choice point chained onto `this` one (or `null`, for the root) would have. */
 fun ChoicePointContext?.nextDepth(): Int = if (this == null) 0 else this.depth + 1
 
 /** Chains a new [ChoicePointContext.Primitives] recording [alternatives] onto `this` (`null` meaning "the root"). */
 fun ChoicePointContext?.appendPrimitives(
     alternatives: Cursor<out Solve.Response>,
     executionContext: PrologExecutionContext? = null,
 ): ChoicePointContext = ChoicePointContext.Primitives(alternatives, executionContext, this, nextDepth())
 
 /** Chains a new [ChoicePointContext.Rules] recording [alternatives] onto `this` (`null` meaning "the root"). */
 fun ChoicePointContext?.appendRules(
     alternatives: Cursor<out Rule>,
     executionContext: PrologExecutionContext? = null,
 ): ChoicePointContext = ChoicePointContext.Rules(alternatives, executionContext, this, nextDepth())