Coverage Summary for Class: ContextBranches (it.unibo.tuprolog.argumentation.core.runtime.context)

Class Class, % Method, % Branch, % Line, % Instruction, %
ContextBranches 100% (1/1) 100% (15/15) 81.2% (13/16) 100% (37/37) 98.4% (246/250)


 package it.unibo.tuprolog.argumentation.core.runtime.context
 
 import it.unibo.tuprolog.core.Substitution
 import it.unibo.tuprolog.core.Term
 import it.unibo.tuprolog.core.Var
 
 /**
  * The branches of a context: each a store of clauses (see [ClauseStore]), one of them active, a new one forked from
  * another; and the identifiers of the terms (context_id/2), shared by all the branches. Branch 0 is the root.
  */
 internal class ContextBranches {
     private var next: Int = 1
     private val stores: MutableMap<Int, ClauseStore> = mutableMapOf(0 to ClauseStore())
     private val identifiers: MutableMap<Term, Int> = mutableMapOf()
     private val terms: MutableList<Term> = mutableListOf()
 
     /** The active branch */
     var active: Int = 0
         private set
 
     /** The store of the active branch */
     val selected: ClauseStore
         get() = stores[active]!!
 
     /** The branch the next one created will be */
     val nextBranch: Int
         get() = next
 
     /** The most recently created branch */
     val last: Int
         get() = next - 1
 
     /** The store of [branch]; null if there is no such branch (never created, or dropped) */
     fun storeOf(branch: Int): ClauseStore? = stores[branch]
 
     /** Back to a single empty branch, the root, and no identifiers */
     fun reset() {
         active = 0
         next = 1
         stores.clear()
         stores[0] = ClauseStore()
         identifiers.clear()
         terms.clear()
     }
 
     /** [branch] made active; false if there is no such branch */
     fun checkout(branch: Int): Boolean {
         if (branch !in stores) return false
         active = branch
         return true
     }
 
     /** A new branch forked from [from], made active; null if there is no branch [from] */
     fun branch(from: Int): Int? {
         val source = stores[from] ?: return null
         stores[next] = source.fork()
         active = next++
         return active
     }
 
     /** [branch] dropped; false for the active one */
     fun drop(branch: Int): Boolean {
         if (branch == active) return false
         stores.remove(branch)
         return true
     }
 
     /** The branches created after [after] dropped, but those in [keep] and the active one */
     fun dropAfter(
         after: Int,
         keep: Set<Int>,
     ) {
         stores.keys.removeAll { it > after && it !in keep && it != active }
     }
 
     /**
      * The identifier of [term], shared by all the copies of the term which only differ in variable names (variables
      * get renamed whenever a term is read back from the context)
      */
     fun identifier(term: Term): Int =
         identifiers.getOrPut(canonical(term)) {
             terms += term
             terms.size - 1
         }
 
     /** The identifier of [term] if it has one already (see [identifier]) */
     fun knownIdentifier(term: Term): Int? = identifiers[canonical(term)]
 
     /** The term with identifier [identifier], as first given an identifier; null if none has it */
     fun term(identifier: Int): Term? = terms.getOrNull(identifier)
 
     // The variables of the canonical terms, the i-th variable of a term replaced by the i-th of them: variables of their
     // own, which no term given has (an atom in their place would make a term with that atom the same as one with a
     // variable)
     private val canonicalVariables: MutableList<Var> = mutableListOf()
 
     private fun canonical(term: Term): Term {
         val variables = term.variables.distinct().toList()
         if (variables.isEmpty()) return term
         while (canonicalVariables.size < variables.size) canonicalVariables += Var.of("V${canonicalVariables.size}")
         return term.apply(Substitution.of(variables.mapIndexed { i, v -> v to canonicalVariables[i] }.toMap()))
     }
 }