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. // 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 (end, renamed: Substitutions) = numbervars(head, Program.variableRenamingStart, subs) val reverse = renamed.entries.associate { (a, b) -> b to a } Program.variableRenamingStart = end var newSubs: Substitutions = subs + renamed unifyLazy(applySubstitution(goal, subs), head, newSubs).forEach { headAnswer -> headAnswer.map { headSubs -> // If the body can be proven, yield the (combined) substitutions newSubs = subs + renamed + headSubs body.satisfy(newSubs).forEach { bodyAnswer -> bodyAnswer.fold( onSuccess = { bodySubs -> var result = (headSubs + bodySubs) .mapKeys { applySubstitution(it.key, reverse)} .mapValues { applySubstitution(it.value, reverse) } result = result.map { it.key to applySubstitution(it.value, result) } .toMap() .filterNot { it.key in renamed.keys && !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() } }