Checkpoint

This commit is contained in:
Tibo De Peuter 2025-05-08 15:07:24 +02:00
parent 973365e2ec
commit 3724ac72f9
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
13 changed files with 659 additions and 29 deletions

View file

@ -19,6 +19,8 @@ import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.builtins.Is
import prolog.logic.equivalent
import prolog.ast.lists.List.Empty
import prolog.ast.lists.List.Cons
class TermsGrammarTests {
private lateinit var parser: Grammar<Term>
@ -352,4 +354,49 @@ class TermsGrammarTests {
)
}
}
@Nested
class Lists {
private lateinit var parser: Grammar<Term>
@BeforeEach
fun setup() {
parser = TermsGrammar() as Grammar<Term>
}
@Test
fun `parse empty list`() {
val input = "[]"
val result = parser.parseToEnd(input)
assertEquals(Empty, result, "Expected empty list")
}
@Test
fun `parse non-empty list`() {
val input = "[a, b, c]"
val result = parser.parseToEnd(input)
assertEquals(
Cons(Atom("a"), Cons(Atom("b"), Cons(Atom("c"), Empty))),
result,
"Expected non-empty list"
)
}
@Test
fun `parse nested lists`() {
val input = "[a, [b, c], d]"
val result = parser.parseToEnd(input)
assertEquals(
Cons(Atom("a"), Cons(Cons(Atom("b"), Cons(Atom("c"), Empty)), Cons(Atom("d"), Empty))),
result,
"Expected nested lists"
)
}
}
}

View file

@ -7,13 +7,13 @@ 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.CompoundTerm
import prolog.ast.terms.AnonymousVariable
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
import prolog.ast.terms.*
import prolog.logic.equivalent
class AnalysisOperatorsTests {
@Test
@ -401,4 +401,246 @@ class AnalysisOperatorsTests {
)
}
}
@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]]")
}
}
}

View file

@ -0,0 +1,60 @@
package prolog.builtins
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import prolog.ast.lists.List.Cons
import prolog.ast.lists.List.Empty
import prolog.ast.terms.Atom
class ListOperatorsTests {
@Test
fun `size empty list is 0`() {
assertEquals(0, Empty.size.value, "Expected size of empty list to be 0")
}
@Test
fun `size of singleton is 1`() {
val list = Cons(Atom("a"), Empty)
assertEquals(1, list.size.value, "Expected size of singleton list to be 1")
}
@Test
fun `size of list with five elements is 5`() {
val list = Cons(Atom("a"), Cons(Atom("b"), Cons(Atom("c"), Cons(Atom("d"), Cons(Atom("e"), Empty)))))
assertEquals(5, list.size.value, "Expected size of list with five elements to be 5")
}
@Test
fun `member(a, list of a)`() {
val atom = Atom("a")
val list = Cons(atom, Empty)
val member = Member(atom, list)
val result = member.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected one solution")
assertTrue(result[0].isSuccess, "Expected success")
assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitution map")
}
@Disabled("Not required functionality")
@Test
fun `member with variable in list`() {
val atom = Atom("a")
val variable = Atom("X")
val list = Cons(variable, Empty)
val member = Member(atom, list)
val result = member.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected one solution")
assertTrue(result[0].isSuccess, "Expected success")
assertEquals(atom, result[0].getOrNull()!![variable], "Expected variable to be unified with atom")
}
}

View file

@ -10,13 +10,16 @@ import prolog.ast.terms.Variable
import prolog.builtins.Add
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Nested
import prolog.ast.lists.List
import prolog.ast.lists.List.Empty
import prolog.ast.lists.List.Cons
/*
* Based on: https://en.wikipedia.org/wiki/Unification_%28computer_science%29#Examples_of_syntactic_unification_of_first-order_terms
*/
class UnificationTests {
@Nested
class `unify` {
class `unify logic` {
@Test
fun identical_atoms_unify() {
val atom1 = Atom("a")
@ -330,7 +333,7 @@ class UnificationTests {
}
@Nested
class `applySubstitution` {
class `applySubstitution logic` {
@Test
fun `apply substitution without sub`() {
val term = Variable("X")
@ -367,4 +370,67 @@ class UnificationTests {
assertEquals(Integer(35), result)
}
}
@Nested
class `equivalent logic` {
@Test
fun `empty lists are equivalent`() {
val eq = equivalent(Empty, Empty, emptyMap())
assertTrue(eq, "Empty lists should be equivalent")
}
@Test
fun `singletons are equivalent`() {
val atom = Atom("a")
val list1 = Cons(atom, Empty)
val list2 = Cons(atom, Empty)
val eq = equivalent(list1, list2, emptyMap())
assertTrue(eq, "Singleton lists should be equivalent")
}
@Test
fun `singleton and empty list are not equivalent`() {
val atom = Atom("a")
val list1 = Cons(atom, Empty)
val list2 = Empty
var eq = equivalent(list1, list2, emptyMap())
assertFalse(eq, "Singleton and empty lists should not be equivalent")
eq = equivalent(list2, list1, emptyMap())
assertFalse(eq, "Empty and singleton lists should not be equivalent")
}
@Test
fun `identical lists are equivalent`() {
val list1 = Cons(Atom("a"), Cons(Atom("b"), Empty))
val list2 = Cons(Atom("a"), Cons(Atom("b"), Empty))
var eq = equivalent(list1, list2, emptyMap())
assertTrue(eq, "Identical lists should be equivalent")
eq = equivalent(list2, list1, emptyMap())
assertTrue(eq, "Identical lists should be equivalent")
}
@Test
fun `identical nested lists are equivalent`() {
val list1 = Cons(Atom("foo"), Cons(Atom("bar"), Cons(Cons(Atom("baz"), Cons(Atom("bar"), Empty)), Empty)))
val list2 = Cons(Atom("foo"), Cons(Atom("bar"), Cons(Cons(Atom("baz"), Cons(Atom("bar"), Empty)), Empty)))
var eq = equivalent(list1, list2, emptyMap())
assertTrue(eq, "Identical nested lists should be equivalent")
}
@Test
fun `lists with different nests are not equivalent`() {
val list1 = Cons(Atom("foo"), Cons(Atom("bar"), Cons(Cons(Atom("bar"), Cons(Atom("baz"), Empty)), Empty)))
val list2 = Cons(Atom("foo"), Cons(Atom("bar"), Cons(Cons(Atom("baz"), Cons(Atom("bar"), Empty)), Empty)))
var eq = equivalent(list1, list2, emptyMap())
assertFalse(eq, "Lists with different nests should not be equivalent")
}
}
}