Coverage Summary for Class: AbstractPrologSolver (it.unibo.tuprolog.argumentation.core.engine)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| AbstractPrologSolver |
100%
(1/1)
|
100%
(8/8)
|
90%
(9/10)
|
100%
(53/53)
|
97%
(259/267)
|
// 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.EndState
import it.unibo.tuprolog.argumentation.core.engine.fsm.State
import it.unibo.tuprolog.argumentation.core.engine.fsm.StateInit
import it.unibo.tuprolog.argumentation.core.engine.fsm.TheoryIndexes
import it.unibo.tuprolog.core.Struct
import it.unibo.tuprolog.core.operators.OperatorSet
import it.unibo.tuprolog.solve.Solution
import it.unibo.tuprolog.solve.SolveOptions
import it.unibo.tuprolog.solve.channel.InputChannel
import it.unibo.tuprolog.solve.channel.InputStore
import it.unibo.tuprolog.solve.channel.OutputChannel
import it.unibo.tuprolog.solve.channel.OutputStore
import it.unibo.tuprolog.solve.currentTimeInstant
import it.unibo.tuprolog.solve.exception.Warning
import it.unibo.tuprolog.solve.flags.FlagStore
import it.unibo.tuprolog.solve.impl.AbstractSolver
import it.unibo.tuprolog.solve.library.Runtime
import it.unibo.tuprolog.theory.MutableTheory
import it.unibo.tuprolog.theory.Theory
import it.unibo.tuprolog.unify.Unificator
/**
* Base implementation of every `:solve-prolog` [it.unibo.tuprolog.solve.Solver]/[it.unibo.tuprolog.solve.MutableSolver]
* ([PrologSolver] and [MutablePrologSolver], both `internal`, are the only two concrete subclasses; instances
* are normally obtained through [Arg2pPrologSolverFactory], which is what the `Solver.prolog` property resolves to).
*
* What this class actually contributes over [AbstractSolver] is [solveImpl]: turning a goal into a
* [Sequence] of [Solution]s by driving the [it.unibo.tuprolog.argumentation.core.engine.fsm.State] finite-state machine
* described by the `:solve-prolog` state-machine explanation page. Concretely, it builds the root
* [PrologExecutionContext] for the query, wraps a fresh [it.unibo.tuprolog.argumentation.core.engine.fsm.StateInit] into a
* [SolutionIterator] (via the abstract [solutionIterator] hook, which lets subclasses choose a plain or
* hijackable iterator), and exposes that iterator as a lazy [Sequence] -- so pulling the next [Solution] from the
* sequence is exactly what drives the state machine one step further, rather than eagerly computing every
* solution up front. [currentContext] is kept in sync with the state machine's progress via
* [updateCurrentContextAfterStateTransition], which every [SolutionIterator] created here is wired to call after
* each transition.
*
* Running resolution as an explicit loop over [it.unibo.tuprolog.argumentation.core.engine.fsm.State] values, instead of
* host-language recursion, is what lets a resolution step be paused, resumed, or inspected without unwinding a
* JVM/JS call stack, and sidesteps host stack-depth limits for deeply recursive Prolog programs (the "call
* stack" here is the [PrologExecutionContext] parent chain -- ordinary heap data).
*
* @see Arg2pPrologSolverFactory
* @see PrologExecutionContext
* @see SolutionIterator
*/
abstract class AbstractPrologSolver(
unificator: Unificator = Unificator.default,
libraries: Runtime = Runtime.empty(),
flags: FlagStore = FlagStore.empty(),
initialStaticKb: Theory = Theory.empty(unificator),
initialDynamicKb: Theory = MutableTheory.empty(unificator),
inputChannels: InputStore = InputStore.fromStandard(),
outputChannels: OutputStore = OutputStore.fromStandard(),
trustKb: Boolean = false,
) : AbstractSolver<PrologExecutionContext>(
unificator,
libraries,
flags,
initialStaticKb,
initialDynamicKb,
inputChannels,
outputChannels,
trustKb,
) {
override lateinit var currentContext: PrologExecutionContext
constructor(
unificator: Unificator = Unificator.default,
libraries: Runtime = Runtime.empty(),
flags: FlagStore = FlagStore.empty(),
staticKb: Theory = Theory.empty(unificator),
dynamicKb: Theory = MutableTheory.empty(unificator),
stdIn: InputChannel<String> = InputChannel.stdIn(),
stdOut: OutputChannel<String> = OutputChannel.stdOut(),
stdErr: OutputChannel<String> = OutputChannel.stdErr(),
warnings: OutputChannel<Warning> = OutputChannel.warn(),
trustKb: Boolean = false,
) : this(
unificator,
libraries,
flags,
staticKb,
dynamicKb,
InputStore.fromStandard(stdIn),
OutputStore.fromStandard(stdOut, stdErr, warnings),
trustKb,
)
final override fun initializeContext(
unificator: Unificator,
libraries: Runtime,
flags: FlagStore,
staticKb: Theory,
dynamicKb: Theory,
operators: OperatorSet,
inputChannels: InputStore,
outputChannels: OutputStore,
trustKb: Boolean,
) = PrologExecutionContext(
unificator = unificator,
libraries = libraries,
flags = flags,
staticKb = if (trustKb) staticKb.toImmutableTheory() else Theory.empty(unificator),
dynamicKb = if (trustKb) dynamicKb.toMutableTheory() else MutableTheory.empty(unificator),
// arg2p: the operators the solver has just collected from the libraries, rather than collecting them again
operators = operators,
inputChannels = inputChannels,
outputChannels = outputChannels,
startTime = 0,
)
/**
* Builds the root [PrologExecutionContext] for [goal] (carrying over this solver's current [Theory] knowledge
* bases, [Runtime] libraries, [FlagStore], channels and [SolveOptions.timeout]) and returns the lazy
* [Sequence] of [Solution]s obtained by driving a fresh [it.unibo.tuprolog.argumentation.core.engine.fsm.StateInit]
* through the state machine via [solutionIterator].
*/
final override fun solveImpl(
goal: Struct,
options: SolveOptions,
): Sequence<Solution> {
currentContext =
PrologExecutionContext(
unificator = unificator,
query = goal,
libraries = libraries,
flags = flags,
staticKb = staticKb.toImmutableTheory(),
dynamicKb = dynamicKb.toMutableTheory(),
operators = operators,
inputChannels = inputChannels,
outputChannels = outputChannels,
customData = currentContext.customData,
theoryIndexes = currentContext.theoryIndexes,
startTime = currentTimeInstant(),
maxDuration = options.timeout,
)
return solutionIterator(StateInit(currentContext), this::updateCurrentContextAfterStateTransition).asSequence()
}
/**
* Hook through which subclasses choose which [SolutionIterator] flavour drives resolution from [initialState]
* onwards, invoking [onStateTransition] after every step ([PrologSolver] always returns a plain
* [SolutionIterator]; nothing in this module currently returns another flavour here, as the
* `MutableSolutionIterator` of tuProlog's classic solver, though the hook is shaped to allow it).
*/
protected abstract fun solutionIterator(
initialState: State,
onStateTransition: (State, State, Long) -> Unit,
): SolutionIterator
@Suppress("UNUSED_PARAMETER")
private fun updateCurrentContextAfterStateTransition(
source: State,
destination: State,
index: Long,
) {
require(destination.context.step == index)
val context = destination.context
// arg2p: a resolution ending within an inlined call (failing in it, or halting) ends with the libraries of the
// call, which only apply to its goal: the solver keeps those of the whole resolution, the root's
currentContext =
if (destination is EndState && context.scoped) {
context.copy(libraries = context.pathToRoot.last().libraries)
} else {
context
}
}
/** The indexes of the immutable theories this solver resolves with (arg2p, see [TheoryIndexes]) */
internal val theoryIndexes: TheoryIndexes
get() = currentContext.theoryIndexes
/**
* Resolves with [indexes] (arg2p): those of the resolution creating this solver, which runs in the same thread
* (see [TheoryIndexes])
*/
internal fun shareTheoryIndexes(indexes: TheoryIndexes) {
currentContext = currentContext.copy(theoryIndexes = indexes)
}
abstract override fun copy(
unificator: Unificator,
libraries: Runtime,
flags: FlagStore,
staticKb: Theory,
dynamicKb: Theory,
stdIn: InputChannel<String>,
stdOut: OutputChannel<String>,
stdErr: OutputChannel<String>,
warnings: OutputChannel<Warning>,
): AbstractPrologSolver
abstract override fun clone(): AbstractPrologSolver
}