Coverage Summary for Class: StoredClauseResolver (it.unibo.tuprolog.argumentation.core.engine.fsm)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| StoredClauseResolver |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(6/6)
|
| StoredClauseResolver$Unification |
100%
(11/11)
|
92.7%
(89/96)
|
95.2%
(60/63)
|
98.9%
(644/651)
|
| Total |
100%
(12/12)
|
92.7%
(89/96)
|
95.3%
(61/64)
|
98.9%
(650/657)
|
package it.unibo.tuprolog.argumentation.core.engine.fsm
import it.unibo.tuprolog.core.Clause
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.Var
/**
* Resolution of a goal with a clause as stored, for the unificator of arg2p (strict equality of constants, no occurs
* check): the head of the clause is unified with the goal and the body built from the result, the variables of the
* clause being renamed on the way. [RenamingClauseResolver] copies the clause as a whole, unifies its head, then
* prepares its body for execution and builds it with the unifier: here the clause is visited once, and only its
* parts that are not ground are copied.
*
* A variable of the clause meeting a term of the goal stands for that term, and a variable of the goal meeting a
* term of the clause is bound to its copy: the bindings only bind variables of the goal, kept applied to each other.
*/
internal object StoredClauseResolver : ClauseResolver {
override fun resolve(
rule: Rule,
goal: Term,
): Resolution? = Unification().resolve(rule, goal)
// The state of a single resolution
private class Unification {
// The variables of the clause, as the terms of the goal (or the fresh variables) they stand for
private val renamed = HashMap<Var, Term>()
// The fresh variables, standing for the variables of the clause not met by a term of the goal
private val fresh = HashSet<Var>()
// The bindings of the variables of the goal (and of the fresh ones), applied to each other
private val bindings = LinkedHashMap<Var, Term>()
private val unifier: Substitution.Unifier by lazy(LazyThreadSafetyMode.NONE) {
Substitution.unifier(bindings)
}
// The variables of the clause in the body, with the bindings applied
private val instantiated = HashMap<Var, Term>()
// The bindings of the variables of the goal
private val goalBindings: Substitution.Unifier
get() =
if (fresh.none { it in bindings }) {
unifier
} else {
Substitution.unifier(bindings.filterKeys { it !in fresh })
}
fun resolve(
rule: Rule,
goal: Term,
): Resolution? =
if (unifyWithClause(rule.head, goal)) {
Resolution(goalBindings, prepare(instantiate(rule.body)))
} else {
null
}
private fun dereference(term: Term): Term = if (term is Var) bindings[term] ?: term else term
// Unifies a term of the clause with one of the goal
private fun unifyWithClause(
clauseTerm: Term,
goalTerm: Term,
): Boolean {
if (clauseTerm.isGround) return unify(clauseTerm, goalTerm)
val goal = dereference(goalTerm)
return when {
clauseTerm is Var -> renamed[clauseTerm]?.let { unify(it, goal) } ?: true.also { renamed[clauseTerm] = goal }
goal is Var -> bind(goal, copy(clauseTerm))
clauseTerm is Struct && goal is Struct ->
clauseTerm.arity == goal.arity &&
clauseTerm.functor == goal.functor &&
(0 until clauseTerm.arity).all { unifyWithClause(clauseTerm[it], goal[it]) }
else -> false
}
}
// Unifies two terms of the goal (as Arg2pUnificator)
private fun unify(
term1: Term,
term2: Term,
): Boolean {
val first = dereference(term1)
val second = dereference(term2)
return when {
first is Var && second is Var -> first == second || bind(first, second)
first is Var -> bind(first, second)
second is Var -> bind(second, first)
first.isConstant || second.isConstant -> first == second
first is Struct && second is Struct ->
first.arity == second.arity &&
first.functor == second.functor &&
(0 until first.arity).all { unify(first[it], second[it]) }
else -> first == second
}
}
private fun bind(
variable: Var,
term: Term,
): Boolean {
val value = if (bindings.isEmpty() || term.isGround) term else term.apply(Substitution.unifier(bindings))
if (bindings.isNotEmpty()) {
val binding = Substitution.of(variable, value)
for (entry in bindings.entries) {
if (!entry.value.isGround) entry.setValue(entry.value.apply(binding))
}
}
bindings[variable] = value
return true
}
// A term of the clause as a term of the goal: its variables renamed as they stand
private fun copy(term: Term): Term =
when {
term.isGround -> term
term is Var -> renamed.getOrPut(term) { Var.of(term.name).also { fresh.add(it) } }
term is Struct -> Struct.of(term.functor, List(term.arity) { copy(term[it]) })
else -> term
}
// A term of the clause as a term of the goal, with the bindings applied
private fun instantiate(term: Term): Term =
when {
term.isGround -> term
term is Var ->
instantiated.getOrPut(term) {
renamed[term]?.let { if (bindings.isEmpty()) it else it.apply(unifier) }
?: Var.of(term.name).also { renamed[term] = it }
}
term is Struct -> Struct.of(term.functor, List(term.arity) { instantiate(term[it]) })
else -> term
}
// As Clause.prepareForExecution: the variables in the place of a goal are called through call/1
private fun prepare(goal: Term): Term =
when {
goal is Var -> Struct.of("call", goal)
goal is Struct && goal.arity == 2 && goal.functor in Clause.notableFunctors -> {
val left = prepare(goal[0])
val right = prepare(goal[1])
if (left === goal[0] && right === goal[1]) goal else Struct.of(goal.functor, left, right)
}
else -> goal
}
}
}