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

Class Class, % Method, % Branch, % Line, % Instruction, %
PrologExecutionContext 100% (1/1) 75% (12/16) 62.5% (25/40) 82.5% (85/103) 80.9% (448/554)


 // 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.argumentation.core.engine.fsm.TheoryIndexes
 import it.unibo.tuprolog.argumentation.core.engine.fsm.collectVariables
 import it.unibo.tuprolog.core.Rule
 import it.unibo.tuprolog.core.Struct
 import it.unibo.tuprolog.core.Substitution
 import it.unibo.tuprolog.core.Term
 import it.unibo.tuprolog.core.Truth
 import it.unibo.tuprolog.core.Var
 import it.unibo.tuprolog.core.operators.OperatorSet
 import it.unibo.tuprolog.solve.ExecutionContext
 import it.unibo.tuprolog.solve.MutableSolver
 import it.unibo.tuprolog.solve.Solver
 import it.unibo.tuprolog.solve.TimeDuration
 import it.unibo.tuprolog.solve.TimeInstant
 import it.unibo.tuprolog.solve.channel.InputStore
 import it.unibo.tuprolog.solve.channel.OutputStore
 import it.unibo.tuprolog.solve.data.CustomDataStore
 import it.unibo.tuprolog.solve.flags.FlagStore
 import it.unibo.tuprolog.solve.getAllOperators
 import it.unibo.tuprolog.solve.library.Runtime
 import it.unibo.tuprolog.solve.primitive.Solve
 import it.unibo.tuprolog.solve.sideffects.SideEffect
 import it.unibo.tuprolog.solve.toOperatorSet
 import it.unibo.tuprolog.theory.MutableTheory
 import it.unibo.tuprolog.theory.Theory
 import it.unibo.tuprolog.unify.Unificator
 import it.unibo.tuprolog.utils.Cursor
 import kotlin.collections.List as KtList
 import kotlin.collections.Set as KtSet
 
 /**
  * One frame of the `:solve-prolog` execution-context stack: a substitution, the streams of remaining goals,
  * candidate [Rule]s and candidate primitive responses, plus a [parent] link. Frames chain into their [parent]
  * rather than living in a separate stack container, so the [parent] chain (see [pathToRoot]) *is* the call
  * stack -- ordinary heap-allocated data rather than native JVM/JS stack frames, which is what lets Prolog
  * resolution of arbitrarily deep recursive programs run as an explicit, non-recursive [it.unibo.tuprolog.argumentation.core.engine.fsm.State]
  * loop instead of unbounded host-language recursion.
  *
  * Resolving a sub-goal (a primitive call or a rule body) pushes a new context whose [parent] is the current one
  * (see `it.unibo.tuprolog.argumentation.core.engine.fsm.createChild`); finishing a context's [goals] pops back to its
  * [parent] in [it.unibo.tuprolog.argumentation.core.engine.fsm.StateGoalSelection]. Every instance also threads through
  * [choicePoints], the sibling data structure (see [ChoicePointContext]) recording still-open backtracking
  * alternatives.
  *
  * Most fields mirror [it.unibo.tuprolog.solve.ExecutionContext]; the ones specific to the prolog engine are:
  * - [query]/[goals]: the original query, and the cursor over the goals still to be proven in this frame.
  * - [rules]/[primitives]: the still-untried candidate clauses/primitive responses for the current goal, i.e. the
  *   frame's own view of the [choicePoints] entry recorded for it (if any).
  * - [choicePoints]: the head of the choice-point queue as seen from this frame -- see [ChoicePointContext].
  * - [parent]/[depth]: the enclosing frame and this frame's distance from the root (`depth == 0`, [isRoot]).
  * - [step]: a monotonically increasing counter of state-machine transitions, used to keep [PrologSolver]'s
  *   `currentContext` in sync with resolution progress and to detect [it.unibo.tuprolog.solve.exception.TimeOutException]s.
  * - [relevantGoals]: the goals selected so far in this frame, whose variables are still of interest, used to trim
  *   [substitution]s of variables local to a finished sub-goal when popping back to [parent] (see
  *   [isVariableInteresting]); [trimmedSize] is the size of the substitution when it was last trimmed.
  * - [inlined]: whether this frame solves the goal of an [Inlining], against libraries of its own; [scoped]: whether
  *   this frame or one above does (so that its libraries are not the ones of the whole resolution).
  * - [theoryIndexes]: the indexes of the immutable theories, kept by the solver and shared with the solvers the
  *   resolution creates (see [TheoryIndexes]).
  *
  * @throws IllegalArgumentException if [depth] and [parent] are inconsistent (`depth == 0` iff `parent == null`),
  * or if [startTime] or [maxDuration] are negative.
  */
 data class PrologExecutionContext(
     override val procedure: Struct? = null,
     override val unificator: Unificator = Unificator.default,
     override val libraries: Runtime = Runtime.empty(),
     override val flags: FlagStore = FlagStore.empty(),
     override val staticKb: Theory = Theory.empty(unificator),
     override val dynamicKb: MutableTheory = MutableTheory.empty(unificator),
     override val operators: OperatorSet = getAllOperators(libraries, staticKb, dynamicKb).toOperatorSet(),
     override val inputChannels: InputStore = InputStore.fromStandard(),
     override val outputChannels: OutputStore = OutputStore.fromStandard(),
     override val customData: CustomDataStore = CustomDataStore.empty(),
     override val substitution: Substitution.Unifier = Substitution.empty(),
     val query: Struct = Truth.TRUE,
     val goals: Cursor<out Term> = Cursor.empty(),
     val rules: Cursor<out Rule> = Cursor.empty(),
     val primitives: Cursor<out Solve.Response> = Cursor.empty(),
     override val startTime: TimeInstant,
     override val maxDuration: TimeDuration = TimeDuration.MAX_VALUE,
     val choicePoints: ChoicePointContext? = null,
     val parent: PrologExecutionContext? = null,
     val depth: Int = 0,
     val step: Long = 0,
     val relevantGoals: RelevantGoals? = null,
     val trimmedSize: Int = 0,
     val inlined: Boolean = false,
     val scoped: Boolean = false,
     val theoryIndexes: TheoryIndexes = TheoryIndexes(),
 ) : ExecutionContext {
     init {
         require((depth == 0 && parent == null) || (depth > 0 && parent != null))
         require(startTime >= 0)
         require(maxDuration >= 0)
     }
 
     /** Whether this is the outermost frame of a resolution (i.e. it has no [parent]). */
     val isRoot: Boolean
         get() = depth == 0
 
     /** Whether [choicePoints] (or any of its ancestors) still has an alternative to backtrack into. */
     val hasOpenAlternatives: Boolean
         get() = choicePoints?.hasOpenAlternatives ?: false
 
     /**
      * This frame and all its ancestors, from here up to (and including) the root, in that order (arg2p: built when
      * needed, rather than for each of the contexts, one or more per resolution step).
      */
     val pathToRoot: Sequence<PrologExecutionContext>
         get() = generateSequence(this) { it.parent }
 
     // arg2p: the values computed when first needed are kept in fields rather than lazy delegates, which a copy of the
     // context (made at almost every step) allocated anew, with their initializers, for each of them
     private var currentGoalValue: Term? = null
     private var currentGoalComputed = false
     private var interestingVariablesValue: KtSet<Var>? = null
     private var logicStackTraceValue: KtList<Struct>? = null
 
     /** The current [goals] entry with [substitution] already applied, or `null` if there is none left to prove. */
     val currentGoal: Term?
         get() {
             if (!currentGoalComputed) {
                 currentGoalValue = if (goals.isOver) null else goals.current?.apply(substitution)
                 currentGoalComputed = true
             }
             return currentGoalValue
         }
 
     // A set rather than a sequence: membership is checked for every binding of the substitution whenever a frame is
     // left, and a sequence is scanned at each check (arg2p: the scans were quadratic in deep resolutions)
     private val interestingVariables: KtSet<Var>
         get() = interestingVariablesValue ?: collectInterestingVariables().also { interestingVariablesValue = it }
 
     private fun collectInterestingVariables(): KtSet<Var> =
         HashSet<Var>().also { variables ->
             query.collectVariables(variables)
             var context: PrologExecutionContext? = this
             while (context != null) {
                 var relevant = context.relevantGoals
                 while (relevant != null) {
                     relevant.goal.collectVariables(variables)
                     relevant = relevant.previous
                 }
                 // arg2p: the goals left to solve, not only the current one (as in tuProlog): a variable bound by a
                 // goal before and used by one after would otherwise lose its binding when a sub-goal ends, unless
                 // TrackVariables kept every goal selected
                 var left = context.goals
                 while (!left.isOver) {
                     left.current?.collectVariables(variables)
                     left = left.next
                 }
                 context = context.parent
             }
         }
 
     /**
      * Whether [variable] is still relevant to this frame or any of its ancestors (i.e. occurs in [relevantGoals],
      * in the goals left of this frame or of an ancestor, or in the [query]). Used when popping a finished
      * frame back to its [parent] (in [it.unibo.tuprolog.argumentation.core.engine.fsm.StateGoalSelection]) to filter the
      * [substitution] handed up, discarding bindings for variables local to the finished sub-goal.
      */
     fun isVariableInteresting(variable: Var) = variable in interestingVariables
 
     override val logicStackTrace: KtList<Struct>
         get() =
             logicStackTraceValue ?: pathToRoot
                 .map { it.procedure ?: Struct.of("?-", query) }
                 .toList()
                 .also { logicStackTraceValue = it }
 
     override fun createSolver(
         unificator: Unificator,
         libraries: Runtime,
         flags: FlagStore,
         staticKb: Theory,
         dynamicKb: Theory,
         inputChannels: InputStore,
         outputChannels: OutputStore,
     ): Solver =
         PrologSolver(
             unificator,
             libraries,
             flags,
             staticKb,
             dynamicKb,
             inputChannels,
             outputChannels,
             trustKb = true,
         ).also { it.shareTheoryIndexes(theoryIndexes) }
 
     override fun createMutableSolver(
         unificator: Unificator,
         libraries: Runtime,
         flags: FlagStore,
         staticKb: Theory,
         dynamicKb: Theory,
         inputChannels: InputStore,
         outputChannels: OutputStore,
     ): MutableSolver =
         MutablePrologSolver(
             unificator,
             libraries,
             flags,
             staticKb,
             dynamicKb,
             inputChannels,
             outputChannels,
             trustKb = true,
         ).also { it.shareTheoryIndexes(theoryIndexes) }
 
     override fun update(
         unificator: Unificator,
         libraries: Runtime,
         flags: FlagStore,
         staticKb: Theory,
         dynamicKb: Theory,
         operators: OperatorSet,
         inputChannels: InputStore,
         outputChannels: OutputStore,
         customData: CustomDataStore,
     ): PrologExecutionContext =
         copy(
             unificator = unificator,
             libraries = libraries,
             flags = flags,
             staticKb = staticKb,
             dynamicKb = dynamicKb.toMutableTheory(),
             operators = operators,
             inputChannels = inputChannels,
             outputChannels = outputChannels,
             customData = customData,
         )
 
     override fun apply(sideEffect: SideEffect): PrologExecutionContext = super.apply(sideEffect) as PrologExecutionContext
 
     override fun apply(sideEffects: Iterable<SideEffect>): PrologExecutionContext = super.apply(sideEffects) as PrologExecutionContext
 
     override fun apply(sideEffects: Sequence<SideEffect>): PrologExecutionContext = super.apply(sideEffects) as PrologExecutionContext
 
     override fun toString(): String =
         "PrologExecutionContext(" +
             "step=$step, " +
             "depth=$depth, " +
             "substitution=$substitution, " +
             "logicStackTrace=$logicStackTrace, " +
             "goals=$goals, " +
             "rules=$rules, " +
             "primitives=$primitives, " +
             "startTime=$startTime, " +
             "endTime=$endTime, " +
             "maxDuration=$maxDuration, " +
             "unificator=$unificator, " +
             "choicePoints=$choicePoints" +
             ")"
 }
 
 /**
  * The goals selected so far in a frame, the newest first: a chain extended at each step, rather than the set of their
  * variables (arg2p: copying the set at each step made long conjunctions quadratic). The variables are only collected
  * when a substitution is trimmed.
  */
 class RelevantGoals(
     val goal: Term,
     val previous: RelevantGoals?,
 )