Checkpoint

This commit is contained in:
Tibo De Peuter 2025-05-02 13:28:00 +02:00
parent 23b2ce9362
commit f9017da734
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
18 changed files with 814 additions and 412 deletions

View file

@ -59,6 +59,7 @@ open class Preprocessor {
Structure(Atom("!"), emptyList()) -> Cut() Structure(Atom("!"), emptyList()) -> Cut()
Atom("inf") -> Integer(Int.MAX_VALUE) Atom("inf") -> Integer(Int.MAX_VALUE)
Atom("nl") -> Nl Atom("nl") -> Nl
Variable("_") -> AnonymousVariable.create()
is Structure -> { is Structure -> {
// Preprocess the arguments first to recognize builtins // Preprocess the arguments first to recognize builtins
val args = term.arguments.map { preprocess(it, nested = true) } val args = term.arguments.map { preprocess(it, nested = true) }
@ -86,6 +87,10 @@ open class Preprocessor {
Not(args[0] as Goal) Not(args[0] as Goal)
} }
term.functor == "==/2" -> {
Equivalent(args[0], args[1])
}
term.functor == "=\\=/2" && args.all { it is Expression } -> { term.functor == "=\\=/2" && args.all { it is Expression } -> {
EvaluatesToDifferent(args[0] as Expression, args[1] as Expression) EvaluatesToDifferent(args[0] as Expression, args[1] as Expression)
} }
@ -133,7 +138,10 @@ open class Preprocessor {
term.functor == "read/1" -> Read(args[0]) term.functor == "read/1" -> Read(args[0])
term.functor == "initialization/1" -> Initialization(args[0] as Goal) term.functor == "initialization/1" -> Initialization(args[0] as Goal)
else -> term else -> {
term.arguments = args
term
}
} }
} }

View file

@ -36,8 +36,8 @@ import prolog.ast.terms.*
open class TermsGrammar : Tokens() { open class TermsGrammar : Tokens() {
// Basic named terms // Basic named terms
protected val variable: Parser<Variable> by variableToken use { Variable(text) } protected val variable: Parser<Variable> by (variableToken or anonymousVariableToken) use { Variable(text) }
protected val simpleAtom: Parser<Atom> by nameToken use { Atom(text) } protected val simpleAtom: Parser<Atom> by (nameToken or exclamation) use { Atom(text) }
protected val quotedAtom: Parser<Atom> by (dummy protected val quotedAtom: Parser<Atom> by (dummy
or ticked or ticked
or doubleTicked or doubleTicked
@ -85,7 +85,7 @@ open class TermsGrammar : Tokens() {
} }
// Level 700 - comparison operators // Level 700 - comparison operators
protected val op700: Parser<String> by (equals or notEquals) use { text } protected val op700: Parser<String> by (equivalent or equals or notEquals or isOp) use { text }
protected val term700: Parser<Term> by (term500 * optional(op700 * term500)) use { protected val term700: Parser<Term> by (term500 * optional(op700 * term500)) use {
if (t2 == null) t1 else CompoundTerm(Atom(t2!!.t1), listOf(t1, t2!!.t2)) if (t2 == null) t1 else CompoundTerm(Atom(t2!!.t1), listOf(t1, t2!!.t2))
} }

View file

@ -8,28 +8,32 @@ import com.github.h0tk3y.betterParse.lexer.regexToken
import com.github.h0tk3y.betterParse.lexer.token import com.github.h0tk3y.betterParse.lexer.token
abstract class Tokens : Grammar<Any>() { abstract class Tokens : Grammar<Any>() {
// Prolog tokens
protected val nameToken: Token by regexToken("[a-z][a-zA-Z0-9_]*")
protected val variableToken: Token by regexToken("[A-Z][a-zA-Z0-9_]*")
// Arithmetic tokens
protected val floatToken: Token by regexToken("-?[1-9][0-9]*\\.[0-9]+")
protected val integerToken: Token by regexToken("-?([1-9][0-9]*|0)")
// Special tokens // Special tokens
protected val neck by literalToken(":-") protected val neck by literalToken(":-")
protected val leftParenthesis: Token by literalToken("(") protected val leftParenthesis: Token by literalToken("(")
protected val rightParenthesis: Token by literalToken(")") protected val rightParenthesis: Token by literalToken(")")
protected val comma: Token by literalToken(",") protected val comma: Token by literalToken(",")
protected val semicolon: Token by literalToken(";") protected val semicolon: Token by literalToken(";")
protected val equivalent: Token by literalToken("==")
protected val equals: Token by literalToken("=") protected val equals: Token by literalToken("=")
protected val notEquals: Token by literalToken("\\=") protected val notEquals: Token by literalToken("\\=")
protected val plus: Token by literalToken("+") protected val plus: Token by literalToken("+")
protected val minus: Token by literalToken("-") protected val minus: Token by literalToken("-")
protected val multiply: Token by literalToken("*") protected val multiply: Token by literalToken("*")
protected val divide: Token by literalToken("/") protected val divide: Token by literalToken("/")
protected val exclamation: Token by literalToken("!")
protected val isOp: Token by literalToken("is")
protected val dot by literalToken(".") protected val dot by literalToken(".")
// Prolog tokens
protected val nameToken: Token by regexToken("[a-z][a-zA-Z0-9_]*")
protected val variableToken: Token by regexToken("[A-Z][a-zA-Z0-9_]*")
protected val anonymousVariableToken: Token by literalToken("_")
// Arithmetic tokens
protected val floatToken: Token by regexToken("-?[1-9][0-9]*\\.[0-9]+")
protected val integerToken: Token by regexToken("-?([1-9][0-9]*|0)")
// Ignored tokens // Ignored tokens
protected val whitespace: Token by regexToken("\\s+", ignore = true) protected val whitespace: Token by regexToken("\\s+", ignore = true)
protected val singleLineComment: Token by regexToken("%[^\\n]*", ignore = true) protected val singleLineComment: Token by regexToken("%[^\\n]*", ignore = true)
@ -41,4 +45,4 @@ abstract class Tokens : Grammar<Any>() {
// Helper // Helper
protected val dummy by token { _, _ -> -1 } use { throw IllegalStateException("This parser should not be used") } protected val dummy by token { _, _ -> -1 } use { throw IllegalStateException("This parser should not be used") }
} }

View file

@ -0,0 +1,17 @@
package prolog.ast.terms
import io.Logger
class AnonymousVariable(id: Int) : Variable("_$id") {
companion object {
private var counter = 0
fun create(): AnonymousVariable {
val id = counter
counter++
Logger.debug("Creating anonymous variable: _${id}")
return AnonymousVariable(id)
}
}
override fun toString(): String = "_"
}

View file

@ -6,7 +6,7 @@ import prolog.ast.arithmetic.Expression
import prolog.ast.arithmetic.Simplification import prolog.ast.arithmetic.Simplification
import prolog.ast.logic.LogicOperand import prolog.ast.logic.LogicOperand
data class Variable(val name: String) : Term, Body, Expression, LogicOperand() { open class Variable(val name: String) : Term, Body, Expression, LogicOperand() {
override fun simplify(subs: Substitutions): Simplification { override fun simplify(subs: Substitutions): Simplification {
// If the variable is bound, return the value of the binding // If the variable is bound, return the value of the binding
// If the variable is not bound, return the variable itself // If the variable is not bound, return the variable itself
@ -28,5 +28,15 @@ data class Variable(val name: String) : Term, Body, Expression, LogicOperand() {
return sequenceOf(Result.failure(IllegalArgumentException("Unbound variable: $this"))) return sequenceOf(Result.failure(IllegalArgumentException("Unbound variable: $this")))
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || other !is Variable) return false
return name == other.name
}
override fun hashCode(): Int {
return name.hashCode()
}
override fun toString(): String = name override fun toString(): String = name
} }

View file

@ -3,10 +3,10 @@ package prolog.builtins
import prolog.Answers import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.logic.LogicOperand import prolog.ast.logic.LogicOperand
import prolog.ast.logic.LogicOperator
import prolog.ast.terms.Atom 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.flags.AppliedCut import prolog.flags.AppliedCut
/** /**

View file

@ -18,12 +18,7 @@ import prolog.logic.unifyLazy
*/ */
class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable { class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable {
override fun satisfy(subs: Substitutions): Answers { override fun satisfy(subs: Substitutions): Answers {
var t = term val t = applySubstitution(term, subs)
var temp = applySubstitution(t, subs)
while (t != temp) {
t = temp
temp = applySubstitution(t, subs)
}
Terminal().say(t.toString()) Terminal().say(t.toString())

View file

@ -58,7 +58,7 @@ fun succ(term1: Expression, term2: Expression, subs: Substitutions): Answers {
it.fold( it.fold(
onSuccess = { result -> onSuccess = { result ->
val t1 = applySubstitution(term1, result) val t1 = applySubstitution(term1, result)
if (t1 in result) { if (t1 in result || t1 in result.values) {
val e1 = t1.simplify(result) val e1 = t1.simplify(result)
if (e1.to is Integer && e1.to.value < 0) { if (e1.to is Integer && e1.to.value < 0) {
return@sequence return@sequence

View file

@ -13,7 +13,15 @@ import prolog.ast.arithmetic.Float
// Apply substitutions to a term // Apply substitutions to a term
fun applySubstitution(term: Term, subs: Substitutions): Term = when { fun applySubstitution(term: Term, subs: Substitutions): Term = when {
variable(term, emptyMap()) -> subs[(term as Variable)] ?: term variable(term, emptyMap()) -> {
var result = subs[(term as Variable)]
while (result != null && result is Variable && result in subs) {
result = subs[result]
}
result ?: term
}
atomic(term, subs) -> term atomic(term, subs) -> term
compound(term, subs) -> { compound(term, subs) -> {
val structure = term as Structure val structure = term as Structure
@ -25,7 +33,7 @@ fun applySubstitution(term: Term, subs: Substitutions): Term = when {
//TODO Combine with the other applySubstitution function //TODO Combine with the other applySubstitution function
fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when { fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when {
variable(expr, subs) -> applySubstitution(expr as Term, subs) as Expression variable(expr, emptyMap()) -> applySubstitution(expr as Term, subs) as Expression
atomic(expr, subs) -> expr atomic(expr, subs) -> expr
expr is LogicOperator -> { expr is LogicOperator -> {
expr.arguments = expr.arguments.map { applySubstitution(it, subs) } expr.arguments = expr.arguments.map { applySubstitution(it, subs) }

View file

@ -4,43 +4,43 @@ import interpreter.FileLoader
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import org.junit.jupiter.params.provider.ValueSource
import prolog.Program import prolog.Program
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.PrintStream import java.io.PrintStream
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class Examples { class Examples {
private val loader = FileLoader() private val loader = FileLoader()
private lateinit var outStream: ByteArrayOutputStream
@BeforeEach @BeforeEach
fun setup() { fun setup() {
Program.reset() Program.reset()
}
@Test outStream = ByteArrayOutputStream()
fun fraternity() {
val inputFile = "examples/basics/fraternity.pl"
loader.load(inputFile)
}
@Test
fun unification() {
val inputFile = "examples/basics/unification.pl"
loader.load(inputFile)
}
@Test
fun write() {
val expected = "gpl zegt: dag(wereld)\n"
val outStream = ByteArrayOutputStream()
System.setOut(PrintStream(outStream)) System.setOut(PrintStream(outStream))
}
val inputFile = "examples/basics/write.pl" @ParameterizedTest
@MethodSource("expectations")
// Compare the stdio output with the expected output fun test(inputFile: String, expected: String) {
loader.load(inputFile) loader.load(inputFile)
assertEquals(expected, outStream.toString()) assertEquals(expected, outStream.toString())
} }
fun expectations() = listOf(
Arguments.of("examples/basics/arithmetics.pl", "gimli is a level 4 fighter with 35 hitpoints.\nlegolas is a level 5 ranger with 30 hitpoints.\ngandalf is a level 10 wizard with 25 hitpoints.\nfrodo is a level 2 rogue with 20 hitpoints.\nlegolas threw gimli, and gimli took 5 damage.\ngimli is a level 4 fighter with 30 hitpoints.\ngandalf casts aid.\ngimli is a level 4 fighter with 35 hitpoints.\nlegolas leveled up.\nlegolas is a level 6 ranger with 30 hitpoints"),
Arguments.of("examples/basics/backtracking.pl", "0\ns(0)\ns(s(0))\ns(s(s(0)))\n"),
Arguments.of("examples/basics/cut.pl", "0\n"),
Arguments.of("examples/basics/disjunction.pl", "Alice likes Italian food.\nBob likes Italian food.\n"),
Arguments.of("examples/basics/equality.pl", "X == Y failed\nX = Y succeeded\nX == Y succeeded\nX = Y succeeded\nX == Y succeeded\n"),
Arguments.of("examples/basics/fraternity.pl", "Citizen robespierre is eligible for the event.\nCitizen danton is eligible for the event.\nCitizen camus is eligible for the event.\n"),
Arguments.of("examples/basics/unification.pl", "While alice got an A, carol got an A, but bob did not get an A, dave did not get an A, unfortunately.\n"),
Arguments.of("examples/basics/write.pl", "gpl zegt: dag(wereld)\n"),
)
} }

View file

@ -0,0 +1,9 @@
package interpreter
import prolog.ast.terms.Term
class OpenPreprocessor : Preprocessor() {
public override fun preprocess(term: Term, nested: Boolean): Term {
return super.preprocess(term, nested)
}
}

View file

@ -0,0 +1,93 @@
package interpreter
import com.github.h0tk3y.betterParse.grammar.parseToEnd
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Nested
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import parser.grammars.TermsGrammar
import prolog.Program
import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom
import prolog.ast.terms.Goal
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.builtins.Is
import prolog.builtins.Subtract
class ParserPreprocessorIntegrationTests {
@Nested
class `Arithmetic`() {
val parser = TermsGrammar()
val preprocessor = OpenPreprocessor()
@ParameterizedTest
@ValueSource(strings = ["-1", "-1.0", "-1.5"])
fun `can parse negative numbers`(input: String) {
val number = if (input.contains('.')) {
Float(input.substring(1).toFloat())
} else {
Integer(input.substring(1).toInt())
}
val negativeNumber = if (input.contains('.')) {
Float(input.toFloat())
} else {
Integer(input.toInt())
}
// Check if parser returns the same result
val parsed = parser.parseToEnd("X is $input") as Term
assertEquals(
Structure(Atom("is"), listOf(
Variable("X"),
Structure(Atom("-"), listOf(number)),
)),
parsed
)
// Check if preprocessor returns the same result
val prepped = preprocessor.preprocess(parsed)
val expected = Is(
Variable("X"),
Subtract(Integer(0), number)
)
assertEquals(expected, prepped)
assertEquals(expected.toString(), prepped.toString())
// Check if evaluation is correct
val solutions = (prepped as Is).satisfy(emptyMap()).toList()
assertEquals(1, solutions.size)
assertEquals(negativeNumber, solutions[0].getOrNull()!![Variable("X")])
}
@ParameterizedTest
@ValueSource(strings = ["X is 1 - 2", "X is 1-2"])
fun `can add negative numbers`(input: String) {
val result = parser.parseToEnd(input) as Term
assertEquals(
Structure(Atom("is"), listOf(Variable("X"), Structure(Atom("-"), listOf(Integer(1), Integer(2))))),
result
)
val prepped = preprocessor.preprocess(result)
val expected = Is(
Variable("X"),
Subtract(Integer(1), Integer(2))
)
assertEquals(expected, prepped)
assertEquals(expected.toString(), prepped.toString())
}
}
}

View file

@ -1,32 +1,80 @@
package interpreter package interpreter
import org.junit.jupiter.api.Assertions.assertEquals import com.github.h0tk3y.betterParse.grammar.parseToEnd
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import parser.grammars.TermsGrammar
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom import prolog.ast.terms.*
import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.builtins.* import prolog.builtins.*
class PreprocessorTests { class PreprocessorTests {
class OpenPreprocessor : Preprocessor() { val preprocessor = OpenPreprocessor()
public override fun preprocess(term: Term, nested: Boolean): Term {
return super.preprocess(term, nested)
}
}
companion object { companion object {
val preprocessor = OpenPreprocessor()
fun test(tests: Map<Term, Term>) { fun test(tests: Map<Term, Term>) {
for ((input, expected) in tests) { for ((input, expected) in tests) {
val result = OpenPreprocessor().preprocess(input) val result = preprocessor.preprocess(input)
assertEquals(expected, result, "Expected preprocessed") assertEquals(expected, result, "Expected preprocessed")
assertEquals(expected::class, result::class, "Expected same class") assertEquals(expected::class, result::class, "Expected same class")
} }
} }
} }
@Test
fun `can preprocess anonymous variable`() {
val input = Variable("_")
val result = preprocessor.preprocess(input)
assertInstanceOf(AnonymousVariable::class.java, result, "Expected anonymous variable")
assertTrue((result as Variable).name.matches("_\\d+".toRegex()), "Expected anonymous variable name")
}
@Test
fun `multiple anonymous variables should be unique`() {
val input = CompoundTerm(Atom("foo"), listOf(Variable("_"), Variable("_")))
val result = preprocessor.preprocess(input)
assertInstanceOf(CompoundTerm::class.java, result, "Expected compound term")
assertEquals(2, (result as CompoundTerm).arguments.size, "Expected two terms")
for (argument in result.arguments) {
assertTrue(
(argument as Variable).name.matches("_\\d+".toRegex()),
"Expected anonymous variable name, but got ${argument.name}"
)
}
val first = result.arguments[0] as Variable
val second = result.arguments[1] as Variable
assertNotEquals(first.name, second.name, "Expected different anonymous variable names")
}
@Test
fun `can preprocess nested anonymous variables`() {
val input = TermsGrammar().parseToEnd("name(character(Name, _, _, _))") as Term
val result = preprocessor.preprocess(input)
assertInstanceOf(CompoundTerm::class.java, result, "Expected compound term")
assertEquals(1, (result as CompoundTerm).arguments.size, "Expected one term")
assertInstanceOf(CompoundTerm::class.java, result.arguments[0], "Expected compound term")
val inner = result.arguments[0] as CompoundTerm
assertEquals(4, inner.arguments.size, "Expected four terms")
for (argument in inner.arguments) {
if ((argument as Variable).name != "Name") {
assertTrue(
(argument as Variable).name.matches("_\\d+".toRegex()),
"Expected anonymous variable name, but got ${argument.name}"
)
}
}
}
@Nested @Nested
class `Arithmetic operators` { class `Arithmetic operators` {
@Test @Test
@ -432,5 +480,22 @@ class PreprocessorTests {
) )
) )
} }
@Test
fun `is`() {
test(
mapOf(
CompoundTerm(Atom("is"), listOf(Variable("T"), Integer(1))) to Is(Variable("T"), Integer(1)),
CompoundTerm(Atom("is"), listOf(Variable("T"), Add(Variable("HP"), Integer(5)))) to Is(
Variable("T"),
Add(Variable("HP"), Integer(5))
),
CompoundTerm(Atom("is"), listOf(Variable("T"), Subtract(Variable("HP"), Integer(5)))) to Is(
Variable("T"),
Subtract(Variable("HP"), Integer(5))
),
)
)
}
} }
} }

View file

@ -133,6 +133,21 @@ class LogicGrammarTests {
assertEquals("invited/2", l2.functor, "Expected functor 'invited/2'") assertEquals("invited/2", l2.functor, "Expected functor 'invited/2'")
} }
@Test
fun `parse check_identical(X, Y)`() {
var input = "check_identical(X, Y) :- X == Y."
assertDoesNotThrow {
val result = parser.parseToEnd(input)
}
input = "check_identical(X, Y) :- X = Y, !, write('X == Y succeeded'), nl."
assertDoesNotThrow {
val result = parser.parseToEnd(input)
}
}
@Test @Test
fun `parse constraints`() { fun `parse constraints`() {
val input = ":- a." val input = ":- a."

View file

@ -17,6 +17,7 @@ import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Term import prolog.ast.terms.Term
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.builtins.Is
import prolog.logic.equivalent import prolog.logic.equivalent
class TermsGrammarTests { class TermsGrammarTests {
@ -60,7 +61,16 @@ class TermsGrammarTests {
fun `parse variable`(name: String) { fun `parse variable`(name: String) {
val result = parser.parseToEnd(name) val result = parser.parseToEnd(name)
assertEquals(Variable(name), result, "Expected atom '$name'") assertEquals(Variable(name), result)
}
@Test
fun `parse anonymous variable`() {
val input = "_"
val result = parser.parseToEnd(input)
assertEquals(Variable("_"), result, "Expected anonymous variable")
} }
@Test @Test
@ -100,24 +110,33 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
assertTrue( assertEquals(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Variable("X"))), result, emptyMap()), Structure(Atom("f"), listOf(Atom("a"), Variable("X"))),
result,
"Expected atom 'f(a, X)'" "Expected atom 'f(a, X)'"
) )
} }
@Test
fun `parse compound term with var and int`() {
val input = "check_identical(A, 13)"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom("check_identical"), listOf(Variable("A"), Integer(13))),
result,
"Expected atom 'check_identical(A, 13)'"
)
}
@Test @Test
fun `parse nested compound term f(a, g(b))`() { fun `parse nested compound term f(a, g(b))`() {
val input = "f(a, g(b))" val input = "f(a, g(b))"
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertTrue( assertEquals(
equivalent( Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Atom("b"))))),
Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Atom("b"))))), result,
result,
emptyMap()
),
"Expected atom 'f(a, g(b))'" "Expected atom 'f(a, g(b))'"
) )
} }
@ -128,24 +147,63 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertTrue( assertEquals(
equivalent( Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Variable("X"))))),
Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Variable("X"))))), result,
result,
emptyMap()
),
"Expected atom 'f(a, g(X))'" "Expected atom 'f(a, g(X))'"
) )
} }
@Test
fun `parse nested compound term with variables`() {
val input = "hit(character(Name, Class, Level, HP), character(Name, Class, Level, T))"
val result = parser.parseToEnd(input)
assertEquals(
Structure(
Atom("hit"),
listOf(
Structure(Atom("character"), listOf(Variable("Name"), Variable("Class"), Variable("Level"), Variable("HP"))),
Structure(Atom("character"), listOf(Variable("Name"), Variable("Class"), Variable("Level"), Variable("T")))
)
),
result,
"Expected atom 'hit(character(Name, Class, Level, HP), character(Name, Class, Level, T))'"
)
}
@Test
fun `parse compound term with anonymous variables`() {
val input = "f(a, _, g(X))"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom("f"), listOf(Atom("a"), Variable("_"), Structure(Atom("g"), listOf(Variable("X"))))),
result,
"Expected atom 'f(a, _, g(X))'"
)
}
@ParameterizedTest @ParameterizedTest
@ValueSource(ints = [-987654321, -543, -21, -1, 0, 1, 5, 12, 345, 123456789]) @ValueSource(ints = [0, 1, 5, 12, 345, 123456789])
fun `parse integer`(number: Int) { fun `parse positive integer`(number: Int) {
val input = number.toString() val input = number.toString()
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertEquals(Integer(number), result, "Expected integer '$number'") assertEquals(Integer(number), result, "Expected integer '$number'")
}
@ParameterizedTest
@ValueSource(ints = [-987654321, -543, -21, -1])
fun `parse negative integer`(number: Int) {
val input = number.toString()
val result = parser.parseToEnd(input)
assertEquals(Structure(Atom("-"), listOf(Integer(0 - number))), result, "Expected integer '$number'")
} }
@Test @Test
@ -154,10 +212,7 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertTrue( assertEquals(Float(42.0f), result, "Expected float '42.0'")
equivalent(Float(42.0f), result, emptyMap()),
"Expected float '42.0'"
)
} }
@Test @Test
@ -166,7 +221,7 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
assertEquals(Float(-42.0f), result, "Expected float '-42.0'") assertEquals(Structure(Atom("-"), listOf(Float(42.0f))), result, "Expected float '-42.0'")
} }
@ParameterizedTest @ParameterizedTest
@ -176,7 +231,7 @@ class TermsGrammarTests {
} }
@Nested @Nested
class `Operator precedence` { class `Operators and precedence` {
private lateinit var parser: Grammar<Term> private lateinit var parser: Grammar<Term>
@BeforeEach @BeforeEach
@ -184,6 +239,74 @@ class TermsGrammarTests {
parser = TermsGrammar() as Grammar<Term> parser = TermsGrammar() as Grammar<Term>
} }
@Test
fun `can parse equivalent`() {
val input = "X == Y"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom("=="), listOf(Variable("X"), Variable("Y"))),
result,
"Expected equivalent operator"
)
}
@Test
fun `can parse cut`() {
val input = "!"
val result = parser.parseToEnd(input)
assertEquals(Atom("!"), result, "Expected cut operator")
}
@Test
fun `can parse 'is'`() {
val input = "T is 1"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom("is"), listOf(Variable("T"), Integer(1))),
result
)
}
@Test
fun `can parse 'is' with addition`() {
val input = "T is 1 + 2"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom("is"), listOf(Variable("T"), Structure(Atom("+"), listOf(Integer(1), Integer(2))))),
result
)
}
@ParameterizedTest
@ValueSource(strings = ["+", "-", "*", "/"])
fun `can parse with spaces`(operator: String) {
val input = "1 $operator 2"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom(operator), listOf(Integer(1), Integer(2))),
result,
"Expected operator '$operator'"
)
}
@ParameterizedTest
@ValueSource(strings = ["+", "-", "*", "/"])
fun `can parse without spaces`(operator: String) {
val input = "1${operator}2"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom(operator), listOf(Integer(1), Integer(2))),
result,
"Expected operator '$operator' without spaces"
)
}
@Test @Test
fun `parse addition and multiplication`() { fun `parse addition and multiplication`() {
val input = "1 + 2 * 3" val input = "1 + 2 * 3"

View file

@ -583,10 +583,23 @@ class ArithmeticTests {
assertTrue(equivalent(result[0].getOrThrow()[t3]!!, Float(6.0f), result[0].getOrNull()!!), "X should be equal to 6.0") assertTrue(equivalent(result[0].getOrThrow()[t3]!!, Float(6.0f), result[0].getOrNull()!!), "X should be equal to 6.0")
} }
@Test
fun `addition with negative`() {
val t1 = Integer(1)
val t2 = Integer(-1)
val t3 = Integer(0)
val result = plus(t1, t2, t3, emptyMap()).toList()
assertEquals(1, result.size, "There should be one solution")
assertTrue(result[0].isSuccess, "Expected success")
assertTrue(result[0].getOrNull()!!.isEmpty(), "1 + -1 should already be equal to 0")
}
@RepeatedTest(100) @RepeatedTest(100)
fun `random test for mul`() { fun `random test for mul`() {
val t1 = Integer((0..1000).random()) val t1 = Integer((-1000..1000).random())
val t2 = Integer((0..1000).random()) val t2 = Integer((-1000..1000).random())
val t3 = Variable("X") val t3 = Variable("X")
val result = mul(t1, t2, t3, emptyMap()).toList() val result = mul(t1, t2, t3, emptyMap()).toList()

View file

@ -0,0 +1,370 @@
package prolog.logic
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import prolog.Substitutions
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
import prolog.builtins.Add
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Nested
/*
* Based on: https://en.wikipedia.org/wiki/Unification_%28computer_science%29#Examples_of_syntactic_unification_of_first-order_terms
*/
class UnificationTests {
@Nested
class `unify` {
@Test
fun identical_atoms_unify() {
val atom1 = Atom("a")
val atom2 = Atom("a")
val result = unify(atom1, atom2)
assertTrue(result.isSuccess, "Identical atoms should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun different_atoms_do_not_unify() {
val atom1 = Atom("a")
val atom2 = Atom("b")
val result = unify(atom1, atom2)
assertFalse(result.isSuccess, "Different atoms should not unify")
}
/**
* ?- X = X.
* true.
*/
@Test
fun identical_variables_unify() {
val variable1 = Variable("X")
val variable2 = Variable("X")
val result = unify(variable1, variable2)
assertTrue(result.isSuccess, "Identical variables should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun variable_unifies_with_atom() {
val variable = Variable("X")
val atom = Atom("a")
val result = unify(atom, variable)
assertTrue(result.isSuccess, "Variable should unify with atom")
assertEquals(1, result.getOrNull()!!.size, "There should be one substitution")
assertEquals(atom, result.getOrNull()!![variable], "Variable should be substituted with atom")
}
@Test
fun variables_alias_when_unified() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val result = unify(variable1, variable2)
assertTrue(result.isSuccess)
assertEquals(1, result.getOrNull()!!.size)
assertEquals(variable2, result.getOrNull()!![variable1], "Variable 1 should alias to variable 2")
}
@Test
fun identical_compound_terms_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Identical compound terms should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun compound_terms_with_different_arguments_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("c")))
val result = unify(structure1, structure2)
assertFalse(result.isSuccess, "Different compound terms should not unify")
}
@Test
fun compound_terms_with_different_functors_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("g"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertFalse(result.isSuccess, "Compound terms with different functors should not unify")
}
/**
* ?- X = f(a, b).
* X = f(a, b).
*/
@Test
fun variable_unifies_with_compound_term() {
val variable = Variable("X")
val structure = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(variable, structure)
assertTrue(result.isSuccess, "Variable should unify with compound term")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable), "Variable should be in the substitution map")
assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Atom("b"))), subs[variable]!!, subs),
"Variable should be substituted with compound term"
)
}
@Test
fun compound_term_with_variable_unifies_with_part() {
val variable = Variable("X")
val structure1 = Structure(Atom("f"), listOf(Atom("a"), variable))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Compound term with variable should unify with part")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable), "Variable should be in the substitution map")
val equivalence = equivalent(Atom("b"), subs[variable]!!, subs)
assertTrue(equivalence, "Variable should be substituted with atom")
}
@Test
fun compound_terms_with_variable_arguments_lists_alias_variables() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(variable1))
val structure2 = Structure(Atom("f"), listOf(variable2))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Compound terms with variable arguments should unify")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable1), "Variable 1 should be in the substitution map")
assertEquals(variable2, subs[variable1], "Variable 1 should alias to variable 2")
}
/**
* f(X) = f(Y, Z)
*/
@Test
fun compound_terms_with_different_arity_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Variable("X")))
val structure2 = Structure(Atom("f"), listOf(Variable("Y"), Variable("Z")))
val result = unify(structure1, structure2)
assertFalse(result.isSuccess, "Compound terms with different arity should not unify")
}
/**
* ?- f(g(X)) = f(Y).
* Y = g(X).
*/
@Test
fun nested_compound_terms_with_variables_unify() {
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(Structure(Atom("g"), listOf(Variable("X")))))
val structure2 = Structure(Atom("f"), listOf(variable2))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Nested compound terms with variables should unify")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable2), "Variable 2 should be in the substitution map")
assertTrue(
equivalent(Structure(Atom("g"), listOf(Variable("X"))), subs[variable2]!!, subs),
"Variable should be substituted with compound term"
)
}
/**
* ?- f(g(X), X) = f(Y, a).
* X = a,
* Y = g(a).
*/
@Test
fun compound_terms_with_more_variables() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(Structure(Atom("g"), listOf(variable1)), variable1))
val structure2 = Structure(Atom("f"), listOf(variable2, Atom("a")))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Compound terms with more variables should unify")
val subs = result.getOrNull()!!
assertEquals(2, subs.size, "There should be two substitutions")
assertTrue(subs.containsKey(variable1), "Variable 1 should be in the substitution map")
assertTrue(
equivalent(Atom("a"), subs[variable1]!!, subs),
"Variable 1 should be substituted with atom"
)
assertTrue(subs.containsKey(variable2), "Variable 2 should be in the substitution map")
assertTrue(
equivalent(Structure(Atom("g"), listOf(Atom("a"))), subs[variable2]!!, subs),
"Variable 2 should be substituted with compound term"
)
}
/**
* ?- X = f(X).
* X = f(f(X)).
*/
@Test
@Disabled("If the occurs check is applied, this should fail")
fun recursive_unification() {
val variable1 = Variable("X")
val structure2 = Structure(Atom("f"), listOf(Variable("X")))
val result = unifyLazy(variable1, structure2, emptyMap()).toList()
assertEquals(1, result.size, "There should be one result")
assertTrue(result[0].isSuccess, "Recursive unification should succeed")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable1), "Variable should be in the substitution map")
assertEquals(structure2, subs[variable1], "Variable should be substituted with compound term")
}
/**
* ?- X = bar, Y = bar, X = Y.
* X = Y, Y = bar.
*/
@Test
fun multiple_unification() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val atom = Atom("bar")
val map: Substitutions = mapOf(
variable1 to atom,
variable2 to atom
)
val result = unifyLazy(variable1, variable2, map).toList()
assertEquals(1, result.size, "There should be one substitution")
assertTrue(result[0].isSuccess, "Multiple unification should succeed")
assertEquals(0, result[0].getOrNull()!!.size, "No (additional) substitutions should be made")
}
/**
* ?- a = a().
* false.
*/
@Test
fun atom_with_different_arity() {
val atom1 = Atom("a")
val structure2 = Structure(Atom("a"), emptyList())
val result = unify(atom1, structure2)
assertFalse(result.isSuccess, "Atom with different arity should not unify")
}
@Test
fun identical_integers_unify() {
val int1 = Integer(1)
val int2 = Integer(1)
val result = unify(int1, int2)
assertTrue(result.isSuccess, "Identical integers should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun different_integers_do_not_unify() {
val int1 = Integer(1)
val int2 = Integer(2)
val result = unify(int1, int2)
assertFalse(result.isSuccess, "Different integers should not unify")
}
@Test
fun `1 + 2 does not unify with 3`() {
val expr1 = Add(Integer(1), Integer(2))
val expr2 = Integer(3)
val result = unify(expr1, expr2)
assertFalse(result.isSuccess, "1 + 2 should not unify with 3")
}
}
@Nested
class `applySubstitution` {
@Test
fun `apply substitution without sub`() {
val term = Variable("X")
val subs: Substitutions = emptyMap()
val result = applySubstitution(term, subs)
assertEquals(term, result)
}
@Test
fun `apply single substitution`() {
val sub = Variable("X") to Integer(5)
val subs: Substitutions = mapOf(sub)
val term = Variable("X")
val result = applySubstitution(term, subs)
assertEquals(Integer(5), result)
}
@Test
fun `apply chained substitution`() {
val sub1 = Variable("HP") to Variable("HP(19)")
val sub2 = Variable("HP(19)") to Integer(35)
val subs: Substitutions = mapOf(sub1, sub2)
val term = Variable("HP")
val result = applySubstitution(term, subs)
assertEquals(Integer(35), result)
}
}
}

View file

@ -1,328 +0,0 @@
package prolog.logic
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import prolog.Substitutions
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
import prolog.builtins.Add
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.fail
/*
* Based on: https://en.wikipedia.org/wiki/Unification_%28computer_science%29#Examples_of_syntactic_unification_of_first-order_terms
*/
class UnifyTests {
@Test
fun identical_atoms_unify() {
val atom1 = Atom("a")
val atom2 = Atom("a")
val result = unify(atom1, atom2)
assertTrue(result.isSuccess, "Identical atoms should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun different_atoms_do_not_unify() {
val atom1 = Atom("a")
val atom2 = Atom("b")
val result = unify(atom1, atom2)
assertFalse(result.isSuccess, "Different atoms should not unify")
}
/**
* ?- X = X.
* true.
*/
@Test
fun identical_variables_unify() {
val variable1 = Variable("X")
val variable2 = Variable("X")
val result = unify(variable1, variable2)
assertTrue(result.isSuccess, "Identical variables should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun variable_unifies_with_atom() {
val variable = Variable("X")
val atom = Atom("a")
val result = unify(atom, variable)
assertTrue(result.isSuccess, "Variable should unify with atom")
assertEquals(1, result.getOrNull()!!.size, "There should be one substitution")
assertEquals(atom, result.getOrNull()!![variable], "Variable should be substituted with atom")
}
@Test
fun variables_alias_when_unified() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val result = unify(variable1, variable2)
assertTrue(result.isSuccess)
assertEquals(1, result.getOrNull()!!.size)
assertEquals(variable2, result.getOrNull()!![variable1], "Variable 1 should alias to variable 2")
}
@Test
fun identical_compound_terms_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Identical compound terms should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun compound_terms_with_different_arguments_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("c")))
val result = unify(structure1, structure2)
assertFalse(result.isSuccess, "Different compound terms should not unify")
}
@Test
fun compound_terms_with_different_functors_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("g"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertFalse(result.isSuccess, "Compound terms with different functors should not unify")
}
/**
* ?- X = f(a, b).
* X = f(a, b).
*/
@Test
fun variable_unifies_with_compound_term() {
val variable = Variable("X")
val structure = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(variable, structure)
assertTrue(result.isSuccess, "Variable should unify with compound term")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable), "Variable should be in the substitution map")
assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Atom("b"))), subs[variable]!!, subs),
"Variable should be substituted with compound term"
)
}
@Test
fun compound_term_with_variable_unifies_with_part() {
val variable = Variable("X")
val structure1 = Structure(Atom("f"), listOf(Atom("a"), variable))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Compound term with variable should unify with part")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable), "Variable should be in the substitution map")
val equivalence = equivalent(Atom("b"), subs[variable]!!, subs)
assertTrue(equivalence, "Variable should be substituted with atom")
}
@Test
fun compound_terms_with_variable_arguments_lists_alias_variables() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(variable1))
val structure2 = Structure(Atom("f"), listOf(variable2))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Compound terms with variable arguments should unify")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable1), "Variable 1 should be in the substitution map")
assertEquals(variable2, subs[variable1], "Variable 1 should alias to variable 2")
}
/**
* f(X) = f(Y, Z)
*/
@Test
fun compound_terms_with_different_arity_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Variable("X")))
val structure2 = Structure(Atom("f"), listOf(Variable("Y"), Variable("Z")))
val result = unify(structure1, structure2)
assertFalse(result.isSuccess, "Compound terms with different arity should not unify")
}
/**
* ?- f(g(X)) = f(Y).
* Y = g(X).
*/
@Test
fun nested_compound_terms_with_variables_unify() {
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(Structure(Atom("g"), listOf(Variable("X")))))
val structure2 = Structure(Atom("f"), listOf(variable2))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Nested compound terms with variables should unify")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable2), "Variable 2 should be in the substitution map")
assertTrue(
equivalent(Structure(Atom("g"), listOf(Variable("X"))), subs[variable2]!!, subs),
"Variable should be substituted with compound term"
)
}
/**
* ?- f(g(X), X) = f(Y, a).
* X = a,
* Y = g(a).
*/
@Test
fun compound_terms_with_more_variables() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(Structure(Atom("g"), listOf(variable1)), variable1))
val structure2 = Structure(Atom("f"), listOf(variable2, Atom("a")))
val result = unify(structure1, structure2)
assertTrue(result.isSuccess, "Compound terms with more variables should unify")
val subs = result.getOrNull()!!
assertEquals(2, subs.size, "There should be two substitutions")
assertTrue(subs.containsKey(variable1), "Variable 1 should be in the substitution map")
assertTrue(
equivalent(Atom("a"), subs[variable1]!!, subs),
"Variable 1 should be substituted with atom"
)
assertTrue(subs.containsKey(variable2), "Variable 2 should be in the substitution map")
assertTrue(
equivalent(Structure(Atom("g"), listOf(Atom("a"))), subs[variable2]!!, subs),
"Variable 2 should be substituted with compound term"
)
}
/**
* ?- X = f(X).
* X = f(f(X)).
*/
@Test
@Disabled("If the occurs check is applied, this should fail")
fun recursive_unification() {
val variable1 = Variable("X")
val structure2 = Structure(Atom("f"), listOf(Variable("X")))
val result = unifyLazy(variable1, structure2, emptyMap()).toList()
assertEquals(1, result.size, "There should be one result")
assertTrue(result[0].isSuccess, "Recursive unification should succeed")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable1), "Variable should be in the substitution map")
assertEquals(structure2, subs[variable1], "Variable should be substituted with compound term")
}
/**
* ?- X = bar, Y = bar, X = Y.
* X = Y, Y = bar.
*/
@Test
fun multiple_unification() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val atom = Atom("bar")
val map: Substitutions = mapOf(
variable1 to atom,
variable2 to atom
)
val result = unifyLazy(variable1, variable2, map).toList()
assertEquals(1, result.size, "There should be one substitution")
assertTrue(result[0].isSuccess, "Multiple unification should succeed")
assertEquals(0, result[0].getOrNull()!!.size, "No (additional) substitutions should be made")
}
/**
* ?- a = a().
* false.
*/
@Test
fun atom_with_different_arity() {
val atom1 = Atom("a")
val structure2 = Structure(Atom("a"), emptyList())
val result = unify(atom1, structure2)
assertFalse(result.isSuccess, "Atom with different arity should not unify")
}
@Test
fun identical_integers_unify() {
val int1 = Integer(1)
val int2 = Integer(1)
val result = unify(int1, int2)
assertTrue(result.isSuccess, "Identical integers should unify")
assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
}
@Test
fun different_integers_do_not_unify() {
val int1 = Integer(1)
val int2 = Integer(2)
val result = unify(int1, int2)
assertFalse(result.isSuccess, "Different integers should not unify")
}
@Test
fun `1 + 2 does not unify with 3`() {
val expr1 = Add(Integer(1), Integer(2))
val expr2 = Integer(3)
val result = unify(expr1, expr2)
assertFalse(result.isSuccess, "1 + 2 should not unify with 3")
}
}