package prolog.builtins import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import prolog.ast.Database.Program import prolog.ast.arithmetic.Integer import prolog.ast.lists.List import prolog.ast.lists.List.Cons import prolog.ast.lists.List.Empty import prolog.ast.logic.Fact import prolog.ast.logic.Rule import prolog.ast.terms.* import prolog.logic.equivalent class AnalysisOperatorsTests { @Test fun `functor(foo, foo, 0)`() { val functor = FunctorOp(Atom("foo"), Atom("foo"), Integer(0)) val result = functor.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitutions") } @Test fun `functor(foo(X), foo, Y)`() { val functor = FunctorOp( Structure(Atom("foo"), listOf(Variable("X"))), Atom("foo"), Variable("Y") ) val result = functor.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(Integer(1), subs[Variable("Y")]) } @Test fun `functor(foo, X, Y)`() { val atom = Atom("foo") val functor = FunctorOp(atom, Variable("X"), Variable("Y")) val result = functor.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(2, subs.size, "Expected 2 substitutions") assertEquals(atom.functor.name, subs[Variable("X")]) assertEquals(atom.functor.arity, subs[Variable("Y")]) } @Test fun `functor(X, foo, 1)`() { val functor = FunctorOp(Variable("X"), Atom("foo"), Integer(1)) val result = functor.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertInstanceOf(Structure::class.java, subs[Variable("X")]) val structure = subs[Variable("X")] as Structure assertEquals(Atom("foo"), structure.name) assertEquals(1, structure.arguments.size) assertInstanceOf(AnonymousVariable::class.java, structure.arguments[0]) } @Test fun `functor(foo(a), foo, 0)`() { val functor = FunctorOp(Structure(Atom("foo"), listOf(Atom("a"))), Atom("foo"), Integer(0)) val result = functor.satisfy(emptyMap()).toList() assertTrue(result.isEmpty(), "Expected no results") } @Test fun `functor(foo(X), foo, 0)`() { val functor = FunctorOp(Structure(Atom("foo"), listOf(Variable("X"))), Atom("foo"), Integer(0)) val result = functor.satisfy(emptyMap()).toList() assertTrue(result.isEmpty(), "Expected no results") } @Test fun `functor(X, Y, 1)`() { val functor = FunctorOp(Variable("X"), Variable("Y"), Integer(1)) val exception = assertThrows { functor.satisfy(emptyMap()).toList() } assertEquals("Arguments are not sufficiently instantiated", exception.message) } @Test fun `arg without variables`() { val arg = Arg( Integer(1), Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))), Atom("a") ) val result = arg.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertTrue(subs.isEmpty(), "Expected no substitutions") } @Test fun `arg with variable value`() { val arg = Arg( Integer(1), Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))), Variable("Term") ) val result = arg.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(Atom("a"), subs[Variable("Term")]) } @Test fun `arg with variable arg`() { val arguments = listOf(Atom("a"), Atom("b"), Atom("c")) for (i in arguments.indices) { val arg = Arg( Variable("Arg"), Structure(Atom("f"), arguments), arguments[i] ) val result = arg.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result for arg $i") assertTrue(result[0].isSuccess, "Expected success for arg $i") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution for arg $i") assertEquals(Integer(i + 1), subs[Variable("Arg")], "Expected arg to be $i + 1") } } @Test fun `arg with backtracking`() { val arg = Arg( Variable("Position"), Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))), Variable("Term") ) val result = arg.satisfy(emptyMap()).toList() assertEquals(3, result.size, "Expected 3 results") assertTrue(result[0].isSuccess, "Expected success") val subs1 = result[0].getOrNull()!! assertEquals(2, subs1.size, "Expected 2 substitutions") assertEquals(Integer(1), subs1[Variable("Position")]) assertEquals(Atom("a"), subs1[Variable("Term")]) assertTrue(result[1].isSuccess, "Expected success") val subs2 = result[1].getOrNull()!! assertEquals(2, subs2.size, "Expected 2 substitutions") assertEquals(Integer(2), subs2[Variable("Position")]) assertEquals(Atom("b"), subs2[Variable("Term")]) assertTrue(result[2].isSuccess, "Expected success") val subs3 = result[2].getOrNull()!! assertEquals(2, subs3.size, "Expected 2 substitutions") assertEquals(Integer(3), subs3[Variable("Position")]) assertEquals(Atom("c"), subs3[Variable("Term")]) } @Test fun `arg raises error if Arg is not compound`() { val arg = Arg(Integer(1), Atom("foo"), Variable("X")) val exception = assertThrows { arg.satisfy(emptyMap()).toList() } assertEquals("Type error: `structure' expected, found `foo' (atom)", exception.message) } @Test fun `arg fails silently if arg = 0`() { val arg = Arg( Integer(0), Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))), Variable("Term") ) val result = arg.satisfy(emptyMap()).toList() assertTrue(result.isEmpty(), "Expected no results") } @Test fun `arg fails silently if arg gt arity`() { val arg = Arg( Integer(4), Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))), Variable("Term") ) val result = arg.satisfy(emptyMap()).toList() assertTrue(result.isEmpty(), "Expected no results") } @Test fun `arg raises error if arg lt 0`() { val arg = Arg( Integer(-1), Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))), Variable("Term") ) val exception = assertThrows { arg.satisfy(emptyMap()).toList() } assertEquals("Domain error: not_less_than_zero", exception.message) } @Nested class `Clause operator` { @BeforeEach fun setup() { Program.reset() } @Test fun `clause fact atom without variables`() { Program.load(listOf(Fact(Atom("foo")))) val result = ClauseOp( Atom("foo"), True ).satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertTrue(subs.isEmpty(), "Expected empty substitutions") } @Test fun `clause fact compound without variables`() { Program.load( listOf( Fact(CompoundTerm(Atom("foo"), listOf(Atom("a"), Atom("b")))) ) ) val result = ClauseOp( CompoundTerm(Atom("foo"), listOf(Atom("a"), Atom("b"))), True ).satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertTrue(subs.isEmpty(), "Expected empty substitutions") } @Test fun `clause rule without variables`() { Program.load(listOf(Rule(Atom("foo"), Atom("bar")))) val result = ClauseOp( Atom("foo"), Atom("bar") ).satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertTrue(subs.isEmpty(), "Expected empty substitutions") } @Test fun `clause fact variable body`() { Program.load(listOf(Fact(Atom("foo")))) val variable = Variable("Term") val result = ClauseOp( Atom("foo"), variable ).satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(True, subs[variable]) } @Test fun `clause fact with variable head`() { Program.load( listOf( Fact(CompoundTerm(Atom("foo"), listOf(Atom("a")))) ) ) val result = ClauseOp( CompoundTerm(Atom("foo"), listOf(Variable("X"))), True ).satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(Atom("a"), subs[Variable("X")]) } @Test fun `clause rule with variable body`() { Program.load(listOf(Rule(Atom("foo"), Atom("bar")))) val result = ClauseOp( Atom("foo"), Variable("Term") ).satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(Atom("bar"), subs[Variable("Term")]) } @Test fun `clause fact variable value with backtracking`() { Program.load( listOf( Fact(CompoundTerm(Atom("bar"), listOf(Atom("d")))), Fact(CompoundTerm(Atom("bar"), listOf(Atom("e")))), Fact(CompoundTerm(Atom("foo"), listOf(Atom("a")))), Fact(CompoundTerm(Atom("foo"), listOf(Atom("b")))), Fact(CompoundTerm(Atom("foo"), listOf(Atom("c")))), Rule( CompoundTerm(Atom("foo"), listOf(Variable("X"))), CompoundTerm(Atom("bar"), listOf(Variable("X"))) ) ) ) val x = Variable("X") val term = Variable("Term") val result = ClauseOp( CompoundTerm(Atom("foo"), listOf(x)), term ).satisfy(emptyMap()).toList() assertEquals(4, result.size, "Expected 4 results") assertTrue(result[0].isSuccess, "Expected success") val subs1 = result[0].getOrNull()!! assertEquals(2, subs1.size, "Expected 2 substitutions") assertEquals(Atom("a"), subs1[x], "Expected a") assertEquals(True, subs1[term], "Expected True") assertTrue(result[1].isSuccess, "Expected success") val subs2 = result[1].getOrNull()!! assertEquals(2, subs2.size, "Expected 2 substitutions") assertEquals(Atom("b"), subs2[x], "Expected b") assertEquals(True, subs2[term], "Expected True") assertTrue(result[2].isSuccess, "Expected success") val subs3 = result[2].getOrNull()!! assertEquals(2, subs3.size, "Expected 2 substitutions") assertEquals(Atom("c"), subs3[x], "Expected c") assertEquals(True, subs3[term], "Expected True") assertTrue(result[3].isSuccess, "Expected success") val subs4 = result[3].getOrNull()!! assertEquals(1, subs4.size, "Expected 2 substitutions") assertEquals( CompoundTerm(Atom("bar"), listOf(Variable("X"))), subs4[term], "Expected bar(X)" ) } } @Nested class `Univ operator` { @Test fun `univ without variables`() { val univ = Univ(Atom("foo"), Cons(Atom("foo"), Empty)) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertTrue(subs.isEmpty(), "Expected empty substitutions") } @Test fun `univ with variable list`() { val list = Variable("List") val univ = Univ( CompoundTerm(Atom("foo"), listOf(Atom("a"), Atom("b"))), list ) val expected = Cons(Atom("foo"), Cons(Atom("a"), Cons(Atom("b"), Empty))) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(expected, subs[list], "Expected List to be $expected") } @Test fun `univ with variable in compound`() { val list = Variable("List") val univ = Univ( CompoundTerm(Atom("foo"), listOf(Variable("X"))), list ) val expected = Cons(Atom("foo"), Cons(Variable("X"), Empty)) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(expected, subs[list], "Expected List to be $expected") } @Test fun `univ with var foo`() { val univ = Univ( Variable("Term"), Cons(Atom("foo"), Empty) ) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(Atom("foo"), subs[Variable("Term")], "Expected Term to be foo") } @Test fun `univ with var foo(a)`() { val univ = Univ( Variable("Term"), Cons(Atom("foo"), Cons(Atom("a"), Empty)) ) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals( Structure(Atom("foo"), listOf(Atom("a"))), subs[Variable("Term")], "Expected Term to be foo(a)" ) } @Test fun `univ with var foo(X)`() { val univ = Univ( Variable("Term"), Cons(Atom("foo"), Cons(Variable("X"), Empty)) ) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals( Structure(Atom("foo"), listOf(Variable("X"))), subs[Variable("Term")], "Expected Term to be foo(X)" ) } @Test fun `univ with var foo(a, b(X), c)`() { val univ = Univ( Variable("Term"), Cons( Atom("foo"), Cons(Atom("a"), Cons(CompoundTerm(Atom("b"), listOf(Variable("X"))), Cons(Atom("c"), Empty))) ) ) val expected = CompoundTerm( Atom("foo"), listOf(Atom("a"), CompoundTerm(Atom("b"), listOf(Variable("X"))), Atom("c")) ) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(expected, subs[Variable("Term")], "Expected Term to be $expected") } @Test fun `univ with unified var should return substitution`() { val univ = Univ( CompoundTerm( Atom("foo"), listOf( Atom("a"), CompoundTerm(Atom("bar"), listOf(Variable("X"))), Atom("c") ) ), Cons( Atom("foo"), Cons(Atom("a"), Cons(CompoundTerm(Atom("bar"), listOf(Atom("b"))), Cons(Atom("c"), Empty))) ) ) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(1, subs.size, "Expected 1 substitution") assertEquals(Atom("b"), subs[Variable("X")], "Expected X to be b") } @Test fun `univ of incompatible structures should fail`() { val univ = Univ( CompoundTerm(Atom("foo"), listOf(Atom("a"))), Cons(Atom("bar"), Empty) ) val result = univ.satisfy(emptyMap()).toList() assertEquals(0, result.size, "Expected 0 results") } @Test fun `univ extra debugging`() { val univ = Univ( Structure("baz", Variable("Foo")), Cons(Variable("Baz"), Cons(Structure("foo", Integer(1)), Empty)) ) val result = univ.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") val subs = result[0].getOrNull()!! assertEquals(2, subs.size, "Expected 2 substitutions") assertEquals(Atom("baz"), subs[Variable("Baz")], "Expected Baz to be baz") assertEquals(Structure("foo", Integer(1)), subs[Variable("Foo")], "Expected Foo to be foo(1)") } @Test fun `univ with two vars should throw`() { val univ = Univ(Variable("X"), Variable("Y")) val exception = assertThrows { univ.satisfy(emptyMap()).toList() } assertEquals("Arguments are not sufficiently instantiated", exception.message) } class OpenUniv(term: Term, list: Term) : Univ(term, list) { public override fun listToTerm(list: List): Term = super.listToTerm(list) public override fun termToList(term: Term): List = super.termToList(term) } @Test fun `a to term`() { val univ = OpenUniv(Atom(""), Empty) val originalList = Cons(Atom("a"), Empty) val originalTerm = Atom("a") val term = univ.listToTerm(originalList) assertEquals(originalTerm, term, "Expected term to be a") val list = univ.termToList(term) assertEquals(originalList, list, "Expected list to be [a]") } @Test fun `foo, bar to compound`() { val univ = OpenUniv(Atom(""), Empty) val originalList = Cons(Atom("foo"), Cons(Atom("bar"), Empty)) val originalTerm = CompoundTerm(Atom("foo"), listOf(Atom("bar"))) val term = univ.listToTerm(originalList) assertEquals(originalTerm, term) val list = univ.termToList(term) assertEquals(originalList, list) } @Test fun `foo, bar, baz to compound`() { val univ = OpenUniv(Atom(""), Empty) val originalList = Cons(Atom("foo"), Cons(Atom("bar"), Cons(Atom("baz"), Empty))) val originalTerm = CompoundTerm(Atom("foo"), listOf(Atom("bar"), Atom("baz"))) val term = univ.listToTerm(originalList) assertEquals(originalTerm, term) val list = univ.termToList(term) assertEquals(originalList, list) } @Test fun `foo, bar, (baz, bar) to compound with list`() { val univ = OpenUniv(Atom(""), Empty) val originalList = Cons(Atom("foo"), Cons(Atom("bar"), Cons(Cons(Atom("baz"), Cons(Atom("bar"), Empty)), Empty))) val originalTerm = CompoundTerm( Atom("foo"), listOf(Atom("bar"), Cons(Atom("baz"), Cons(Atom("bar"), Empty))) ) val term = univ.listToTerm(originalList) assertTrue(equivalent(originalTerm, term, emptyMap()), "Expected term to be foo(bar, [baz, bar]))") val list = univ.termToList(term) assertTrue(equivalent(originalList, list, emptyMap()), "Expected list to be [foo, bar, [baz, bar]]") } } }