This repository has been archived on 2025-09-23. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
2025LogProg-project-GhentPr.../tests/prolog/builtins/AnalysisOperatorsTests.kt
2025-05-08 15:07:24 +02:00

646 lines
22 KiB
Kotlin

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<Exception> {
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<IllegalArgumentException> {
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<IllegalArgumentException> {
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 with two vars should throw`() {
val univ = Univ(Variable("X"), Variable("Y"))
val exception = assertThrows<Exception> {
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]]")
}
}
}