81 lines
3 KiB
Kotlin
81 lines
3 KiB
Kotlin
package prolog.ast.logic
|
||
|
||
import prolog.Answers
|
||
import prolog.ast.Database.Program
|
||
import prolog.Substitutions
|
||
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.
|
||
|
||
val (headEnd, headRenaming) = numbervars(head, Program.variableRenamingStart, subs)
|
||
Program.variableRenamingStart = headEnd
|
||
|
||
val renamedHead = applySubstitution(head, headRenaming)
|
||
unifyLazy(goal, renamedHead, subs).forEach { headAnswer ->
|
||
headAnswer.map { headSubs ->
|
||
// If the body can be proven, yield the (combined) substitutions
|
||
val preBody = applySubstitution(body, headRenaming + headSubs) as Body
|
||
preBody.satisfy(subs).forEach { bodyAnswer ->
|
||
bodyAnswer.fold(
|
||
onSuccess = { bodySubs ->
|
||
val newSubs = headSubs + bodySubs
|
||
val result = newSubs
|
||
.mapValues { applySubstitution(it.value, newSubs) }
|
||
.filter { it.key !is AnonymousVariable && occurs(it.key as Variable, goal, emptyMap()) }
|
||
yield(Result.success(result))
|
||
},
|
||
onFailure = { error ->
|
||
if (error is AppliedCut) {
|
||
// Find single solution and return immediately
|
||
if (error.subs != null) {
|
||
yield(Result.failure(AppliedCut(headSubs + error.subs)))
|
||
} 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 {
|
||
return super.hashCode()
|
||
}
|
||
}
|