91 lines
3.9 KiB
Kotlin
91 lines
3.9 KiB
Kotlin
package prolog.ast.logic
|
||
|
||
import prolog.Answers
|
||
import prolog.Substitutions
|
||
import prolog.ast.Database.Program
|
||
import prolog.ast.terms.*
|
||
import prolog.builtins.True
|
||
import prolog.flags.AppliedCut
|
||
import prolog.logic.applySubstitution
|
||
import prolog.logic.numbervars
|
||
import prolog.logic.occurs
|
||
import prolog.logic.unifyLazy
|
||
|
||
/**
|
||
* ‘Sentence’ of a Prolog program.
|
||
*
|
||
* A clause consists of a [Head] and body separated by the neck operator, or it is a [Fact].
|
||
*
|
||
* @see [Variable]
|
||
* @see [Predicate]
|
||
*/
|
||
abstract class Clause(var head: Head, var body: Body) : Term, Resolvent {
|
||
val functor: Functor = head.functor
|
||
|
||
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
|
||
// If the clause is a rule, unify the goal with the head and then try to prove the body.
|
||
// Only if the body can be proven, the substitutions should be returned.
|
||
// Do this in a lazy way.
|
||
|
||
// Since we are only interested in substitutions in the goal (as opposed to the head of this clause),
|
||
// we can use variable renaming and filter out the substitutions that are not in the goal.
|
||
val (headEnd, headRenaming) = numbervars(head, Program.variableRenamingStart, emptyMap())
|
||
Program.variableRenamingStart = headEnd
|
||
|
||
val renamedHead = applySubstitution(head, headRenaming)
|
||
val simplifiedGoal = applySubstitution(goal, subs)
|
||
unifyLazy(simplifiedGoal, renamedHead, emptyMap()).forEach { headAnswer ->
|
||
headAnswer.map { headAndGoalSubs ->
|
||
// Filter the substitutions to only include those that map from head to goal
|
||
val headToGoalSubs = headAndGoalSubs.filterKeys { it in headRenaming.values }
|
||
val goalToHeadSubs = headAndGoalSubs.filterKeys { occurs(it as Variable, simplifiedGoal) }
|
||
val newSubs = headRenaming + headToGoalSubs
|
||
body.satisfy(newSubs).forEach { bodyAnswer ->
|
||
bodyAnswer.fold(
|
||
onSuccess = { bodySubs ->
|
||
// If the body can be proven, yield the (combined) substitutions
|
||
val goalToHeadResult = goalToHeadSubs.mapValues { applySubstitution(it.value, bodySubs) }
|
||
val headResult = headToGoalSubs.filterKeys { key ->
|
||
goalToHeadSubs.any {
|
||
occurs(
|
||
key as Variable,
|
||
it.value
|
||
)
|
||
}
|
||
}
|
||
yield(Result.success(goalToHeadResult + headResult))
|
||
},
|
||
onFailure = { error ->
|
||
if (error is AppliedCut) {
|
||
// Find single solution and return immediately
|
||
if (error.subs != null) {
|
||
var result = goalToHeadSubs.mapValues { applySubstitution(it.value, error.subs) }
|
||
yield(Result.failure(AppliedCut(result)))
|
||
} else {
|
||
yield(Result.failure(AppliedCut()))
|
||
}
|
||
return@sequence
|
||
} else {
|
||
yield(Result.failure(error))
|
||
}
|
||
}
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
override fun toString(): String = if (body is True) head.toString() else "$head :- $body"
|
||
|
||
override fun equals(other: Any?): Boolean {
|
||
if (this === other) return true
|
||
if (other !is Clause) return false
|
||
|
||
if (head != other.head) return false
|
||
if (body != other.body) return false
|
||
|
||
return true
|
||
}
|
||
|
||
override fun hashCode(): Int = super.hashCode()
|
||
}
|