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() }