Coverage Summary for Class: Arg2pUnificator (it.unibo.tuprolog.argumentation.core.runtime)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| Arg2pUnificator |
83.3%
(5/6)
|
60%
(18/30)
|
86.7%
(13/15)
|
76.8%
(106/138)
|
| Arg2pUnificator$Unification |
100%
(5/5)
|
92.5%
(37/40)
|
95.7%
(22/23)
|
97.5%
(195/200)
|
| Total |
90.9%
(10/11)
|
78.6%
(55/70)
|
92.1%
(35/38)
|
89.1%
(301/338)
|
package it.unibo.tuprolog.argumentation.core.runtime
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
import it.unibo.tuprolog.unify.Unificator
/**
* The unificator of the argumentation solvers: the default one, without the occurs check unless explicitly
* requested (as in unify_with_occurs_check/2), as in standard Prolog, computed in a single pass (see [Unification]).
*
* The occurs check visits the whole term bound to a variable, and arguments (nested lists of rules,
* conclusions and premises) are bound to variables at almost every resolution step.
*/
object Arg2pUnificator : Unificator by Unificator.default {
override fun mgu(
term1: Term,
term2: Term,
): Substitution = Unification().takeIf { it.unify(term1, term2) }?.result ?: Substitution.failed()
/**
* The most general unifier of two terms, as the default unificator computes it (strict equality of constants, a
* variable bound to what it meets, the left one when two variables meet, no occurs check), but in a single
* recursive pass: the default one rewrites a queue of equations, applying each new binding to all of them.
* The bindings are kept applied to each other, as in the resulting substitution.
*/
private class Unification {
private val bindings = LinkedHashMap<Var, Term>()
val result: Substitution
get() = Substitution.unifier(bindings)
private fun dereference(term: Term): Term = if (term is Var) bindings[term] ?: term else term
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
}
}
/**
* Theories look clauses up by matching `Head :- _` against each stored clause: the body is a fresh variable,
* which matches any body, so only the heads are compared. Matching the bodies too would cost, with the
* default unificator, a pass over the stored body for every variable of the head.
*/
override fun match(
term1: Term,
term2: Term,
): Boolean {
if (term1 is Rule && term2 is Clause) {
val body = term1.body
if (body is Var && term1.head.variables.none { it == body }) {
return term2.head?.let { Unification().unify(term1.head, it) } ?: false
}
}
return Unification().unify(term1, term2)
}
override fun unify(
term1: Term,
term2: Term,
): Term? = mgu(term1, term2).takeIf { it.isSuccess }?.let { term1.apply(it) }
// Merging with an empty substitution (as the replies of most primitives, which only test) copies nothing
override fun merge(
substitution1: Substitution,
substitution2: Substitution,
): Substitution =
when {
substitution2.isSuccess && substitution2.isEmpty() -> substitution1
substitution1.isSuccess && substitution1.isEmpty() -> substitution2
else -> Unificator.default.merge(substitution1, substitution2, false)
}
override fun merge(
substitution1: Substitution,
substitution2: Substitution,
occurCheckEnabled: Boolean,
): Substitution =
if (occurCheckEnabled) {
Unificator.default.merge(substitution1, substitution2, true)
} else {
merge(substitution1, substitution2)
}
}