package prolog.builtins import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import prolog.ast.Database.Program import prolog.ast.logic.Clause import prolog.ast.logic.Fact import prolog.ast.logic.Predicate import prolog.ast.logic.Rule import prolog.ast.terms.Atom import prolog.ast.terms.FunctorInfo import prolog.ast.terms.Structure import prolog.ast.terms.Variable class DatabaseOperatorsTests { @BeforeEach fun setup() { Program.reset() } abstract class AssertTestsBase { protected abstract fun createAssert(clause: Clause): Structure @BeforeEach fun setup() { Program.reset() } @ParameterizedTest @ValueSource(classes = [AssertA::class, AssertZ::class, Assert::class]) fun `assert(fact atom)`(assertKind: Class<*>) { val fact = Fact(Atom("a")) createAssert(fact).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") assertEquals(fact, Program.db.predicates[FunctorInfo.of("a/_")]!!.clauses[0]) } @Test fun `assert(fact structure)`() { val fact = Fact(Structure(Atom("a"), listOf(Atom("b")))) createAssert(fact).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") assertEquals(fact, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) } @Test fun `assert(rule)`() { val rule = Rule( Structure(Atom("a"), listOf(Atom("b"))), Atom("c") ) createAssert(rule).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") assertEquals(rule, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) } } @Nested class AssertTests : AssertTestsBase() { override fun createAssert(clause: Clause): Structure { return Assert(clause) } } @Nested class AssertATests : AssertTestsBase() { override fun createAssert(clause: Clause): Structure { return AssertA(clause) } @Test fun `asserta adds to the beginning`() { val rule1 = Rule( Structure(Atom("a"), listOf(Atom("b"))), Atom("c") ) val rule2 = Rule( Structure(Atom("a"), listOf(Atom("d"))), Atom("e") ) AssertA(rule1).satisfy(emptyMap()) AssertA(rule2).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1]) } } @Nested class AssertZTests : AssertTestsBase() { override fun createAssert(clause: Clause): Structure { return AssertZ(clause) } @Test fun `assertz adds to the end`() { val rule1 = Rule( Structure(Atom("a"), listOf(Atom("b"))), Atom("c") ) val rule2 = Rule( Structure(Atom("a"), listOf(Atom("d"))), Atom("e") ) AssertZ(rule1).satisfy(emptyMap()) AssertZ(rule2).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1]) } } @Test fun `retract fails silently for unknown predicates`() { val retract = Retract(Atom("unknown")) val result = retract.satisfy(emptyMap()) assertTrue(result.none(), "Expected no results") } @Test fun `simple retract`() { val predicate = Predicate(listOf(Fact(Atom("a"))), dynamic = true) Program.db.load(predicate) assertEquals(1, Program.query(Atom("a")).count()) val retract = Retract(Atom("a")) assertEquals(1, retract.satisfy(emptyMap()).toList().size, "Expected 1 result") assertEquals(0, predicate.clauses.size, "Expected 0 clauses") assertTrue(retract.satisfy(emptyMap()).none()) } @Test fun `retract atom`() { val predicate = Predicate( listOf( Fact(Atom("a")), Fact(Atom("a")), Fact(Atom("a")) ), dynamic = true ) Program.db.load(predicate) val control = Program.query(Atom("a")).toList() assertEquals(3, control.size, "Expected 3 results") val retract = Retract(Atom("a")) val result = retract.satisfy(emptyMap()).iterator() assertEquals(3, predicate.clauses.size, "Expected 3 clauses") assertTrue(result.hasNext(), "Expected more results") val answer = result.next() assertTrue(answer.isSuccess, "Expected success") assertTrue(answer.getOrNull()!!.isEmpty(), "Expected no substitutions") assertEquals(2, predicate.clauses.size, "Expected 2 clauses") assertTrue(result.next().isSuccess) assertTrue(result.next().isSuccess) assertFalse(result.hasNext(), "Expected more results") assertEquals(0, predicate.clauses.size, "Expected no remaining clauses") } @Test fun `retract compound with variable`() { val predicate = Predicate( listOf( Fact(Structure(Atom("a"), listOf(Atom("b")))), Fact(Structure(Atom("a"), listOf(Atom("c")))), Fact(Structure(Atom("a"), listOf(Atom("d")))) ), dynamic = true ) Program.db.load(predicate) val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList() assertEquals(3, control.size, "Expected 3 results") val retract = Retract(Structure(Atom("a"), listOf(Variable("X")))) val result = retract.satisfy(emptyMap()).iterator() assertEquals(3, predicate.clauses.size, "Expected 3 clauses") assertTrue(result.hasNext(), "Expected more results") var answer = result.next() assertTrue(answer.isSuccess, "Expected success") var subs = answer.getOrNull()!! assertTrue(subs.isNotEmpty(), "Expected substitutions") assertTrue(Variable("X") in subs, "Expected variable X") assertEquals(Atom("b"), subs[Variable("X")], "Expected b") assertEquals(2, predicate.clauses.size, "Expected 2 clauses") answer = result.next() assertTrue(answer.isSuccess, "Expected success") subs = answer.getOrNull()!! assertTrue(subs.isNotEmpty(), "Expected substitutions") assertTrue(Variable("X") in subs, "Expected variable X") assertEquals(Atom("c"), subs[Variable("X")], "Expected c") assertEquals(1, predicate.clauses.size, "Expected 1 clause") answer = result.next() assertTrue(answer.isSuccess, "Expected success") subs = answer.getOrNull()!! assertTrue(subs.isNotEmpty(), "Expected substitutions") assertTrue(Variable("X") in subs, "Expected variable X") assertEquals(Atom("d"), subs[Variable("X")], "Expected d") assertEquals(0, predicate.clauses.size, "Expected no clauses") assertFalse(result.hasNext(), "Expected no more results") } @Test fun `custom assert example`() { var query = Structure(Atom("likes"), listOf(Atom("alice"), Atom("pizza"))) var result = Program.query(query).toList() assertEquals(0, result.size, "Expected 0 results") var assert: Structure = Assert(Fact(query)) assert.satisfy(emptyMap()) result = Program.query(query).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].getOrNull()!!.isEmpty()) assert = AssertZ(Fact(Structure(Atom("likes"), listOf(Atom("bob"), Atom("sushi"))))) assert.satisfy(emptyMap()) query = Structure(Atom("likes"), listOf(Atom("bob"), Variable("X"))) result = Program.query(query).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") assertEquals(Atom("sushi"), result[0].getOrNull()!![Variable("X")], "Expected sushi") query = Structure(Atom("likes"), listOf(Variable("X"), Variable("Y"))) result = Program.query(query).toList() assertEquals(2, result.size, "Expected 2 results") assertTrue(result[0].isSuccess, "Expected success") var result0 = result[0].getOrNull()!! assertEquals(Atom("alice"), result0[Variable("X")], "Expected alice") assertEquals(Atom("pizza"), result0[Variable("Y")], "Expected pizza") assertTrue(result[1].isSuccess, "Expected success") var result1 = result[1].getOrNull()!! assertEquals(Atom("bob"), result1[Variable("X")], "Expected bob") assertEquals(Atom("sushi"), result1[Variable("Y")], "Expected sushi") assert = AssertA( Rule( Structure(Atom("likes"), listOf(Variable("X"), Atom("italian"))), Structure(Atom("likes"), listOf(Variable("X"), Atom("pizza"))) ) ) assert.satisfy(emptyMap()) result = Program.query(query).toList() assertEquals(3, result.size, "Expected 3 results") assertTrue(result[0].isSuccess, "Expected success") result0 = result[0].getOrNull()!! assertEquals(Atom("alice"), result0[Variable("X")], "Expected alice") assertEquals(Atom("italian"), result0[Variable("Y")], "Expected italian") assertTrue(result[1].isSuccess, "Expected success") result1 = result[1].getOrNull()!! assertEquals(Atom("alice"), result1[Variable("X")], "Expected alice") assertEquals(Atom("pizza"), result1[Variable("Y")], "Expected pizza") assertTrue(result[2].isSuccess, "Expected success") val result2 = result[2].getOrNull()!! assertEquals(Atom("bob"), result2[Variable("X")], "Expected bob") assertEquals(Atom("sushi"), result2[Variable("Y")], "Expected sushi") } @Test fun `retract all`() { val predicate = Predicate( listOf( Fact(Structure(Atom("a"), listOf(Atom("b")))), Fact(Structure(Atom("a"), listOf(Atom("c")))), Fact(Structure(Atom("a"), listOf(Atom("d")))) ), dynamic = true ) Program.db.load(predicate) val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList() assertEquals(3, control.size, "Expected 3 results") assertEquals(3, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses.size, "Expected 3 clauses") val retract = RetractAll(Structure(Atom("a"), listOf(Variable("X")))) val result = retract.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") assertEquals(0, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses.size, "Expected 0 clauses") } @Test fun `If Head refers to a predicate that is not defined, it is implicitly created as a dynamic predicate`() { val predicateName = "idonotyetexist" val predicateFunctor = FunctorInfo.of("$predicateName/1") assertFalse(predicateFunctor in Program.db.predicates, "Expected predicate to not exist before") val retractAll = RetractAll(Structure(Atom(predicateName), listOf(Variable("X")))) val result = retractAll.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") assertTrue(predicateFunctor in Program.db.predicates, "Expected predicate to exist after") assertTrue(Program.db.predicates[predicateFunctor]!!.dynamic, "Expected predicate to be dynamic") } }