test: Cut not_equal
This commit is contained in:
parent
229a8bbc3c
commit
2fcab52f65
7 changed files with 75 additions and 18 deletions
|
@ -7,7 +7,7 @@ import prolog.ast.terms.Functor
|
||||||
import prolog.ast.terms.Goal
|
import prolog.ast.terms.Goal
|
||||||
import prolog.ast.terms.Head
|
import prolog.ast.terms.Head
|
||||||
import prolog.builtins.True
|
import prolog.builtins.True
|
||||||
import prolog.exceptions.AppliedCut
|
import prolog.flags.AppliedCut
|
||||||
import prolog.logic.unifyLazy
|
import prolog.logic.unifyLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +36,11 @@ abstract class Clause(private val head: Head, private val body: Body) : Resolven
|
||||||
onFailure = { error ->
|
onFailure = { error ->
|
||||||
if (error is AppliedCut) {
|
if (error is AppliedCut) {
|
||||||
// Find single solution and return immediately
|
// Find single solution and return immediately
|
||||||
yield(Result.failure(AppliedCut(newHeadSubs + error.subs)))
|
if (error.subs != null) {
|
||||||
|
yield(Result.failure(AppliedCut(newHeadSubs + error.subs)))
|
||||||
|
} else {
|
||||||
|
yield(Result.failure(AppliedCut()))
|
||||||
|
}
|
||||||
return@sequence
|
return@sequence
|
||||||
} else {
|
} else {
|
||||||
yield(Result.failure(error))
|
yield(Result.failure(error))
|
||||||
|
|
|
@ -4,7 +4,7 @@ import prolog.Answers
|
||||||
import prolog.Substitutions
|
import prolog.Substitutions
|
||||||
import prolog.ast.terms.Functor
|
import prolog.ast.terms.Functor
|
||||||
import prolog.ast.terms.Goal
|
import prolog.ast.terms.Goal
|
||||||
import prolog.exceptions.AppliedCut
|
import prolog.flags.AppliedCut
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of [Clause]s with the same [Functor].
|
* Collection of [Clause]s with the same [Functor].
|
||||||
|
@ -62,8 +62,10 @@ class Predicate : Resolvent {
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
if (it is AppliedCut) {
|
if (it is AppliedCut) {
|
||||||
// If it's a cut, yield the result with the left substitutions
|
if (it.subs != null) {
|
||||||
yield(Result.failure(AppliedCut(it.subs)))
|
// If it's a cut, yield the result with the left substitutions
|
||||||
|
yield(Result.success(it.subs))
|
||||||
|
}
|
||||||
return@sequence
|
return@sequence
|
||||||
} else {
|
} else {
|
||||||
yield(Result.failure(it))
|
yield(Result.failure(it))
|
||||||
|
|
|
@ -7,7 +7,7 @@ import prolog.ast.terms.Atom
|
||||||
import prolog.ast.terms.Body
|
import prolog.ast.terms.Body
|
||||||
import prolog.ast.terms.Goal
|
import prolog.ast.terms.Goal
|
||||||
import prolog.ast.logic.LogicOperator
|
import prolog.ast.logic.LogicOperator
|
||||||
import prolog.exceptions.AppliedCut
|
import prolog.flags.AppliedCut
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Always fail.
|
* Always fail.
|
||||||
|
@ -56,13 +56,17 @@ class Conjunction(private val left: LogicOperand, private val right: LogicOperan
|
||||||
// If the right part fails, check if it's a cut
|
// If the right part fails, check if it's a cut
|
||||||
onFailure = { exception ->
|
onFailure = { exception ->
|
||||||
if (exception is AppliedCut) {
|
if (exception is AppliedCut) {
|
||||||
// If it's a cut, yield the result with the left substitutions
|
if (exception.subs != null) {
|
||||||
yield(Result.failure(AppliedCut(leftSubs + exception.subs)))
|
// If it's a cut, yield the result with the left substitutions
|
||||||
|
yield(Result.failure(AppliedCut(leftSubs + exception.subs)))
|
||||||
|
} else {
|
||||||
|
yield(Result.failure(AppliedCut()))
|
||||||
|
}
|
||||||
return@sequence
|
return@sequence
|
||||||
} else {
|
|
||||||
// If it's not a cut, yield the failure
|
|
||||||
yield(Result.failure(exception))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's not a cut, yield the failure
|
||||||
|
yield(Result.failure(exception))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +75,7 @@ class Conjunction(private val left: LogicOperand, private val right: LogicOperan
|
||||||
onFailure = { exception ->
|
onFailure = { exception ->
|
||||||
// 1. If the left part is a cut, satisfy the right part ONCE, and stop searching for more solutions
|
// 1. If the left part is a cut, satisfy the right part ONCE, and stop searching for more solutions
|
||||||
if (exception is AppliedCut) {
|
if (exception is AppliedCut) {
|
||||||
right.satisfy(subs + exception.subs).first().fold(
|
right.satisfy(subs + (exception.subs!!)).firstOrNull()?.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
// If the right part succeeds, yield the result with the left substitutions
|
// If the right part succeeds, yield the result with the left substitutions
|
||||||
yield(Result.success(exception.subs + it))
|
yield(Result.success(exception.subs + it))
|
||||||
|
@ -81,7 +85,7 @@ class Conjunction(private val left: LogicOperand, private val right: LogicOperan
|
||||||
// If the right part fails, yield the failure
|
// If the right part fails, yield the failure
|
||||||
yield(Result.failure(it))
|
yield(Result.failure(it))
|
||||||
}
|
}
|
||||||
)
|
) ?: yield(Result.failure(AppliedCut()))
|
||||||
} else {
|
} else {
|
||||||
// 2. Any other failure should be returned as is
|
// 2. Any other failure should be returned as is
|
||||||
yield(Result.failure(exception))
|
yield(Result.failure(exception))
|
||||||
|
|
|
@ -12,6 +12,19 @@ import prolog.ast.terms.Operator
|
||||||
import prolog.ast.terms.Term
|
import prolog.ast.terms.Term
|
||||||
import prolog.logic.applySubstitution
|
import prolog.logic.applySubstitution
|
||||||
import prolog.logic.equivalent
|
import prolog.logic.equivalent
|
||||||
|
import prolog.logic.unifyLazy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unify Term1 with Term2. True if the unification succeeds.
|
||||||
|
*/
|
||||||
|
class Unify(private val term1: Term, private val term2: Term): Operator(Atom("="), term1, term2) {
|
||||||
|
override fun satisfy(subs: Substitutions): Answers = sequence {
|
||||||
|
val t1 = applySubstitution(term1, subs)
|
||||||
|
val t2 = applySubstitution(term2, subs)
|
||||||
|
|
||||||
|
yieldAll(unifyLazy(t1, t2, subs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) {
|
class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) {
|
||||||
override fun satisfy(subs: Substitutions): Answers = sequence {
|
override fun satisfy(subs: Substitutions): Answers = sequence {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package prolog.exceptions
|
|
||||||
|
|
||||||
import prolog.Substitutions
|
|
||||||
|
|
||||||
class AppliedCut(val subs: Substitutions): Exception()
|
|
11
src/prolog/flags/AppliedCut.kt
Normal file
11
src/prolog/flags/AppliedCut.kt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package prolog.flags
|
||||||
|
|
||||||
|
import prolog.Substitutions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception that indicates that a cut has been applied in the Prolog engine.
|
||||||
|
*
|
||||||
|
* @param subs The substitutions that were in effect when the cut was applied.
|
||||||
|
* If null, it means that the cut was applied on a failed branch.
|
||||||
|
*/
|
||||||
|
class AppliedCut(val subs: Substitutions? = null): Throwable()
|
|
@ -17,6 +17,8 @@ class ControlOperatorsTests {
|
||||||
Program.clear()
|
Program.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See also: https://stackoverflow.com/a/23292126
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `simple cut program`() {
|
fun `simple cut program`() {
|
||||||
// First an example without cut
|
// First an example without cut
|
||||||
|
@ -149,6 +151,32 @@ class ControlOperatorsTests {
|
||||||
assertEquals(1, result.size, "Expected 1 result")
|
assertEquals(1, result.size, "Expected 1 result")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `not_equal cut test`() {
|
||||||
|
Program.load(
|
||||||
|
listOf(
|
||||||
|
Rule(
|
||||||
|
CompoundTerm(Atom("not_equal"), listOf(Variable("X"), Variable("Y"))),
|
||||||
|
Conjunction(
|
||||||
|
Unify(Variable("X"), Variable("Y")),
|
||||||
|
Conjunction(Cut(), Fail)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Fact(CompoundTerm(Atom("not_equal"), listOf(Variable("_1"), Variable("_2"))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var goal = CompoundTerm(Atom("not_equal"), listOf(Integer(1), Integer(1)))
|
||||||
|
var result = Program.query(goal).toList()
|
||||||
|
|
||||||
|
assertTrue(result.none(), "Expected no results")
|
||||||
|
|
||||||
|
goal = CompoundTerm(Atom("not_equal"), listOf(Integer(1), Integer(2)))
|
||||||
|
result = Program.query(goal).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Expected 1 result")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun not_atom() {
|
fun not_atom() {
|
||||||
val success = Fact(Atom("a"))
|
val success = Fact(Atom("a"))
|
||||||
|
|
Reference in a new issue