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())