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

Class Method, % Branch, % Line, % Instruction, %
AbstractState 100% (7/7) 80% (8/10) 100% (11/11) 82.4% (108/131)
AbstractState$Companion
Total 100% (7/7) 80% (8/10) 100% (11/11) 82.4% (108/131)


 // 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.fsm
 
 import it.unibo.tuprolog.argumentation.core.engine.PrologExecutionContext
 import it.unibo.tuprolog.solve.TimeDuration
 import it.unibo.tuprolog.solve.TimeInstant
 import it.unibo.tuprolog.solve.currentTimeInstant
 import it.unibo.tuprolog.solve.exception.ResolutionException
 import it.unibo.tuprolog.solve.exception.TimeOutException
 
 /**
  * Common base of every `:solve-prolog` [State]: wraps [computeNext] with a shared timeout check, so no
  * concrete state has to implement `SolveOptions.timeout` handling itself.
  *
  * [next] first compares [executionTime] against [PrologExecutionContext.startTime]/[PrologExecutionContext.maxDuration];
  * if the budget is exceeded it short-circuits straight to an [EndState] (`StateHalt`) carrying a [TimeOutException],
  * bypassing any pending `catch/3` (resource exhaustion is treated as unrecoverable), and so it does with a
  * [ResolutionException] telling so if the thread of the resolution was interrupted (arg2p: on the JVM, its caller
  * stopping it, see [resolutionInterrupted]);
  * otherwise it delegates to [computeNext], the actual per-state transition logic.
  */
 abstract class AbstractState(
     override val context: PrologExecutionContext,
 ) : State {
     /**
      * The (lazily captured) instant this state is being evaluated at, used for the timeout check in [next] (arg2p:
      * states are confined to the thread of their resolution, so the lazy value needs no synchronization).
      */
     protected val executionTime: TimeInstant
         get() = executionTimeValue ?: currentTime().also { executionTimeValue = it }
 
     // Kept in a field rather than a lazy delegate, which each state allocated with its initializer
     private var executionTimeValue: TimeInstant? = null
 
     /**
      * Whether [executionTime] is past [PrologExecutionContext.startTime] + [PrologExecutionContext.maxDuration]
      * (arg2p: without a time limit, the clock is not read at each step).
      */
     protected open val isTimeout: Boolean
         get() = context.maxDuration != TimeDuration.MAX_VALUE && executionTime - context.startTime > context.maxDuration
 
     override fun next(): State =
         when {
             isTimeout -> halt(TimeOutException(exceededDuration = context.maxDuration, context = context))
             resolutionInterrupted() -> halt(ResolutionException(INTERRUPTED, context = context))
             else -> computeNext()
         }
 
     // The resolution stopped by [exception], uncaught
     private fun halt(exception: ResolutionException): State = StateHalt(exception, context.copy(step = nextStep()))
 
     /** The actual, state-specific transition logic, invoked by [next] once the timeout check has passed. */
     protected abstract fun computeNext(): State
 
     protected fun currentTime(): TimeInstant = currentTimeInstant()
 
     protected fun nextStep(): Long = context.step + 1
 
     private companion object {
         // The message of the halt of a resolution its caller interrupted
         const val INTERRUPTED = "The resolution was interrupted by its caller"
     }
 }
 
 /**
  * Whether the resolution running on this thread was interrupted by its caller, which stops it: on the JVM, the
  * interruption of its thread (Thread.interrupt, Future.cancel(true)); never in JavaScript
  */
 internal expect fun resolutionInterrupted(): Boolean