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

Class Method, % Branch, % Line, % Instruction, %
Utils 100% (16/16) 95.5% (21/22) 93.9% (46/49) 90.4% (357/395)
Utils$raisingErrorsInTurn$1 100% (1/1) 100% (6/6) 100% (8/8) 100% (60/60)
Total 100% (17/17) 96.4% (27/28) 94.7% (54/57) 91.6% (417/455)


 // Forked from 2p-kt's solve-prolog 3.0.2 (Apache License 2.0): see README.md in this package
 @file:JvmName("Utils")
 
 package it.unibo.tuprolog.argumentation.core.engine.fsm
 
 import it.unibo.tuprolog.argumentation.core.engine.PrologExecutionContext
 import it.unibo.tuprolog.argumentation.core.engine.appendPrimitives
 import it.unibo.tuprolog.argumentation.core.engine.appendRules
 import it.unibo.tuprolog.argumentation.core.engine.stdlib.primitive.Throw
 import it.unibo.tuprolog.core.Rule
 import it.unibo.tuprolog.core.Struct
 import it.unibo.tuprolog.core.Term
 import it.unibo.tuprolog.core.Var
 import it.unibo.tuprolog.solve.ExecutionContext
 import it.unibo.tuprolog.solve.Signature
 import it.unibo.tuprolog.solve.TimeInstant
 import it.unibo.tuprolog.solve.exception.ResolutionException
 import it.unibo.tuprolog.solve.exception.error.SystemError
 import it.unibo.tuprolog.solve.primitive.Solve
 import it.unibo.tuprolog.utils.Cursor
 import it.unibo.tuprolog.utils.cursor
 import kotlin.jvm.JvmName
 
 /** Flattens a (possibly nested) `, `/2 tuple of goals into the sequence of its leaves, depth-first. */
 fun Term.unfoldGoals(): Sequence<Term> =
     when {
         this.isTuple -> castToTuple().toSequence().flatMap { it.unfoldGoals() }
         else -> sequenceOf(this)
     }
 
 /**
  * Turns this term into the [Cursor] of goals `StateGoalSelection` iterates over: [unfoldGoals] flattens
  * conjunctions, and every variable goal is wrapped into `call(X)` so it goes through the ISO callability check
  * before execution.
  */
 fun Term.toGoals(): Cursor<out Term> =
     unfoldGoals()
         .map {
             when {
                 it.isVar -> Struct.of("call", it)
                 else -> it
             }
         }.cursor()
 
 /** The goals of the body of a clause, as [toGoals]: none for `true`, the body of a fact (arg2p) */
 fun Term.toBodyGoals(): Cursor<out Term> = if (isTrue) Cursor.empty() else toGoals()
 
 /**
  * Pushes a new child frame onto the execution-context stack for the current goal, becoming its own
  * [PrologExecutionContext.goals] (arg2p: [currentGoal] is the current goal with the substitution applied, when the
  * caller already has it)
  */
 fun PrologExecutionContext.createChild(currentGoal: Struct = this.currentGoal!!.castToStruct()): PrologExecutionContext =
     copy(
         goals = currentGoal.toGoals(),
         procedure = currentGoal,
         parent = this,
         depth = depth + 1,
         step = step + 1,
         relevantGoals = null,
         inlined = false,
     )
 
 /**
  * Last-call-optimization counterpart of [createChild]: reuses the current frame for the current goal (incrementing
  * [PrologExecutionContext.depth] without growing the [PrologExecutionContext.parent] chain), used by
  * `StateRuleSelection` for tail calls to avoid an unbounded execution-context stack.
  */
 fun PrologExecutionContext.replaceWithChild(currentGoal: Struct = this.currentGoal!!.castToStruct()): PrologExecutionContext =
     copy(
         goals = currentGoal.toGoals(),
         procedure = currentGoal,
         depth = depth + 1,
         step = step + 1,
         // arg2p: the goals selected in the frame are done, the tail call being the last: the variables of interest
         // are those of the goals the frame selects next, and of the goals above it (a tail-recursive loop would keep
         // every goal it selected)
         relevantGoals = null,
     )
 
 /**
  * Attaches [rules] as this context's [PrologExecutionContext.rules] cursor and records a matching
  * [it.unibo.tuprolog.argumentation.core.engine.ChoicePointContext.Rules] onto [PrologExecutionContext.choicePoints], so the
  * remaining candidates (if any) can be retried later on backtracking.
  */
 fun PrologExecutionContext.appendRulesAndChoicePoints(rules: Cursor<out Rule>): PrologExecutionContext {
     val newChoicePointContext =
         if (rules.hasNext) {
             choicePoints.appendRules(rules.next, this)
         } else {
             choicePoints.appendRules(Cursor.empty(), this)
         }
 
     return copy(rules = rules, choicePoints = newChoicePointContext)
 }
 
 /**
  * Records a [it.unibo.tuprolog.argumentation.core.engine.ChoicePointContext.Rules] onto
  * [PrologExecutionContext.choicePoints] whose [alternatives] resolve the current goal of this context on backtracking
  * (arg2p): for the frames whose first alternative is not a clause (see [ControlConstruct])
  */
 fun PrologExecutionContext.appendChoicePoint(alternatives: Cursor<out Rule>): PrologExecutionContext =
     copy(choicePoints = choicePoints.appendRules(alternatives, this))
 
 /**
  * This exception, raised by the code of a primitive, as a resolution exception (arg2p): a Kotlin exception becomes a
  * system_error, which catch/3 can handle, rather than crashing the resolution
  */
 fun Exception.asResolutionException(context: ExecutionContext): ResolutionException =
     this as? ResolutionException ?: SystemError.forUncaughtException(context, this)
 
 /**
  * The answers of a primitive, an error raised computing one of them becoming an answer raising it (arg2p): the
  * engine computes an answer ahead (to know whether the call leaves alternatives), but the error is only raised if the
  * resolution backtracks into it, as an error of that answer
  */
 fun Sequence<Solve.Response>.raisingErrorsInTurn(request: Solve.Request<ExecutionContext>): Sequence<Solve.Response> =
     sequence {
         val answers = iterator()
         while (true) {
             val answer =
                 try {
                     if (answers.hasNext()) answers.next() else null
                 } catch (exception: Exception) {
                     request.replyException(exception.asResolutionException(request.context))
                 } ?: break
             yield(answer)
             if (answer.solution.isHalt) break
         }
     }
 
 /** The context an error raised in this one is reported in: the nearest one that is not throw/1 */
 fun PrologExecutionContext.skipThrow(): ExecutionContext = pathToRoot.first { it.procedure?.functor != Throw.functor }
 
 /** [Solve.Response] analogue of [appendRulesAndChoicePoints], recording a [it.unibo.tuprolog.argumentation.core.engine.ChoicePointContext.Primitives] instead. */
 fun PrologExecutionContext.appendPrimitivesAndChoicePoints(primitiveExecutions: Cursor<out Solve.Response>): PrologExecutionContext {
     val newChoicePointContext =
         if (primitiveExecutions.hasNext) {
             choicePoints.appendPrimitives(primitiveExecutions.next, this)
         } else {
             choicePoints.appendPrimitives(Cursor.empty(), this)
         }
 
     return copy(primitives = primitiveExecutions, choicePoints = newChoicePointContext)
 }
 
 /** Combines [createChild] and [appendRulesAndChoicePoints]: the usual way `StateRuleSelection` moves into a non-tail rule call. */
 fun PrologExecutionContext.createChildAppendingRulesAndChoicePoints(
     rules: Cursor<out Rule>,
     currentGoal: Struct = this.currentGoal!!.castToStruct(),
 ): PrologExecutionContext = createChild(currentGoal).appendRulesAndChoicePoints(rules)
 
 /** Combines [replaceWithChild] and [appendRulesAndChoicePoints]: how `StateRuleSelection` moves into a last-call-optimized (tail) rule call. */
 fun PrologExecutionContext.replaceWithChildAppendingRulesAndChoicePoints(
     rules: Cursor<out Rule>,
     currentGoal: Struct = this.currentGoal!!.castToStruct(),
 ): PrologExecutionContext = replaceWithChild(currentGoal).appendRulesAndChoicePoints(rules)
 
 /** Builds the [Solve.Request] passed to a `Primitive`'s `solve` from this context, [goal] and [signature], at [startTime]. */
 fun PrologExecutionContext.toRequest(
     goal: Struct,
     signature: Signature,
     startTime: TimeInstant,
 ) = Solve.Request(signature, goal.args, this, startTime)
 
 // The variables of a term, collected by a plain recursion (Term.variables nests a lazy sequence for each argument),
 // skipping its ground parts (whose groundness is known: lists build it, other structures compute it once)
 internal fun Term.collectVariables(into: MutableSet<Var>) {
     when (this) {
         is Var -> into.add(this)
         is Struct -> if (!isGround) for (i in 0 until arity) this[i].collectVariables(into)
         else -> Unit
     }
 }