diff --git a/src/interpreter/Preprocessor.kt b/src/interpreter/Preprocessor.kt index 4b36614..5bd2ed7 100644 --- a/src/interpreter/Preprocessor.kt +++ b/src/interpreter/Preprocessor.kt @@ -67,51 +67,51 @@ open class Preprocessor { when { // TODO Remove hardcoding by storing the functors as constants in operators? - term.functor == ":-/2" -> Rule( args[0] as Head, args[1] as Body ) + term.functor == FunctorInfo.of(":-/2") -> Rule( args[0] as Head, args[1] as Body ) // Logic - term.functor == "=/2" -> Unify(args[0], args[1]) - term.functor == "\\=/2" -> NotUnify(args[0], args[1]) - term.functor == ",/2" -> Conjunction(args[0] as LogicOperand, args[1] as LogicOperand) - term.functor == ";/2" -> Disjunction(args[0] as LogicOperand, args[1] as LogicOperand) - term.functor == "\\+/1" -> Not(args[0] as Goal) - term.functor == "\\==/2" -> NotEquivalent(args[0], args[1]) - term.functor == "==/2" -> Equivalent(args[0], args[1]) + term.functor == FunctorInfo.of("=/2") -> Unify(args[0], args[1]) + term.functor == FunctorInfo.of("\\=/2") -> NotUnify(args[0], args[1]) + term.functor == FunctorInfo.of(",/2") -> Conjunction(args[0] as LogicOperand, args[1] as LogicOperand) + term.functor == FunctorInfo.of(";/2") -> Disjunction(args[0] as LogicOperand, args[1] as LogicOperand) + term.functor == FunctorInfo.of("\\+/1") -> Not(args[0] as Goal) + term.functor == FunctorInfo.of("\\==/2") -> NotEquivalent(args[0], args[1]) + term.functor == FunctorInfo.of("==/2") -> Equivalent(args[0], args[1]) - term.functor == "=\\=/2" && args.all { it is Expression } -> EvaluatesToDifferent(args[0] as Expression, args[1] as Expression) - term.functor == "=:=/2" && args.all { it is Expression } -> EvaluatesTo(args[0] as Expression, args[1] as Expression) - term.functor == "is/2" && args.all { it is Expression } -> Is(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("=\\=/2") && args.all { it is Expression } -> EvaluatesToDifferent(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("=:=/2") && args.all { it is Expression } -> EvaluatesTo(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("is/2") && args.all { it is Expression } -> Is(args[0] as Expression, args[1] as Expression) // Arithmetic - term.functor == "-/1" && args.all { it is Expression } -> Negate(args[0] as Expression) - term.functor == "-/2" && args.all { it is Expression } -> Subtract(args[0] as Expression, args[1] as Expression) - term.functor == "+/1" && args.all { it is Expression } -> Positive(args[0] as Expression) - term.functor == "+/2" && args.all { it is Expression } -> Add(args[0] as Expression, args[1] as Expression) - term.functor == "*/2" && args.all { it is Expression } -> Multiply(args[0] as Expression, args[1] as Expression) - term.functor == "//2" && args.all { it is Expression } -> Divide(args[0] as Expression, args[1] as Expression) - term.functor == "between/3" && args.all { it is Expression } -> Between(args[0] as Expression, args[1] as Expression, args[2] as Expression) - term.functor == "succ/2" && args.all { it is Expression } -> Successor(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("-/1") && args.all { it is Expression } -> Negate(args[0] as Expression) + term.functor == FunctorInfo.of("-/2") && args.all { it is Expression } -> Subtract(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("+/1") && args.all { it is Expression } -> Positive(args[0] as Expression) + term.functor == FunctorInfo.of("+/2") && args.all { it is Expression } -> Add(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("*/2") && args.all { it is Expression } -> Multiply(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("//2") && args.all { it is Expression } -> Divide(args[0] as Expression, args[1] as Expression) + term.functor == FunctorInfo.of("between/3") && args.all { it is Expression } -> Between(args[0] as Expression, args[1] as Expression, args[2] as Expression) + term.functor == FunctorInfo.of("succ/2") && args.all { it is Expression } -> Successor(args[0] as Expression, args[1] as Expression) // Database - term.functor == "dynamic/1" -> Dynamic((args[0] as Atom).name) - term.functor == "retract/1" -> Retract(args[0]) - term.functor == "retractall/1" -> RetractAll(args[0]) - term.functor == "assert/1" -> { + term.functor == FunctorInfo.of("dynamic/1") -> Dynamic(FunctorInfo.of((args[0] as Atom).name)) + term.functor == FunctorInfo.of("retract/1") -> Retract(args[0]) + term.functor == FunctorInfo.of("retractall/1") -> RetractAll(args[0]) + term.functor == FunctorInfo.of("assert/1") -> { if (args[0] is Rule) { Assert(args[0] as Rule) } else { Assert(Fact(args[0] as Head)) } } - term.functor == "asserta/1" -> { + term.functor == FunctorInfo.of("asserta/1") -> { if (args[0] is Rule) { AssertA(args[0] as Rule) } else { AssertA(Fact(args[0] as Head)) } } - term.functor == "assertz/1" -> { + term.functor == FunctorInfo.of("assertz/1") -> { if (args[0] is Rule) { AssertZ(args[0] as Rule) } else { @@ -120,10 +120,10 @@ open class Preprocessor { } // Other - term.functor == "write/1" -> Write(args[0]) - term.functor == "read/1" -> Read(args[0]) - term.functor == "initialization/1" -> Initialization(args[0] as Goal) - term.functor == "forall/2" -> ForAll(args[0] as LogicOperand, args[1] as Goal) + term.functor == FunctorInfo.of("write/1") -> Write(args[0]) + term.functor == FunctorInfo.of("read/1") -> Read(args[0]) + term.functor == FunctorInfo.of("initialization/1") -> Initialization(args[0] as Goal) + term.functor == FunctorInfo.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal) else -> { term.arguments = args diff --git a/src/prolog/ast/Database.kt b/src/prolog/ast/Database.kt index be2da7a..28589ef 100644 --- a/src/prolog/ast/Database.kt +++ b/src/prolog/ast/Database.kt @@ -6,14 +6,14 @@ import prolog.Substitutions import prolog.ast.logic.Clause import prolog.ast.logic.Predicate import prolog.ast.logic.Resolvent -import prolog.ast.terms.Functor +import prolog.ast.terms.FunctorInfo import prolog.ast.terms.Goal /** * Prolog Program or Database */ open class Database(val sourceFile: String) { - var predicates: Map = emptyMap() + var predicates: Map = emptyMap() /** * Initializes the database by running the initialization clauses of that database. @@ -23,14 +23,14 @@ open class Database(val sourceFile: String) { if (sourceFile !== "") { Logger.debug("Moving clauses from $sourceFile to main database") - predicates.filter { it.key != "/_" } + predicates.filter { it.key != FunctorInfo.of("/_") } .forEach { (_, predicate) -> db.load(predicate, force = true) } } Logger.info("Initializing database from $sourceFile") - if (predicates.contains("/_")) { + if (predicates.contains(FunctorInfo.of("/_"))) { Logger.debug("Loading clauses from /_ predicate") - predicates["/_"]?.clauses?.forEach { + predicates[FunctorInfo.of("/_")]?.clauses?.forEach { Logger.debug("Loading clause $it") val goal = it.body as Goal goal.satisfy(emptyMap()).toList() diff --git a/src/prolog/ast/logic/Clause.kt b/src/prolog/ast/logic/Clause.kt index 1080a9f..be3cc62 100644 --- a/src/prolog/ast/logic/Clause.kt +++ b/src/prolog/ast/logic/Clause.kt @@ -1,8 +1,8 @@ package prolog.ast.logic import prolog.Answers -import prolog.ast.Database.Program import prolog.Substitutions +import prolog.ast.Database.Program import prolog.ast.terms.* import prolog.builtins.True import prolog.flags.AppliedCut @@ -20,7 +20,7 @@ import prolog.logic.unifyLazy * @see [Predicate] */ abstract class Clause(var head: Head, var body: Body) : Term, Resolvent { - val functor: Functor = head.functor + val functor: FunctorInfo = head.functor override fun solve(goal: Goal, subs: Substitutions): Answers = sequence { // If the clause is a rule, unify the goal with the head and then try to prove the body. @@ -45,7 +45,14 @@ abstract class Clause(var head: Head, var body: Body) : Term, Resolvent { onSuccess = { bodySubs -> // If the body can be proven, yield the (combined) substitutions val goalToHeadResult = goalToHeadSubs.mapValues { applySubstitution(it.value, bodySubs) } - val headResult = headToGoalSubs.filterKeys { key -> goalToHeadSubs.any { occurs(key as Variable, it.value) } } + val headResult = headToGoalSubs.filterKeys { key -> + goalToHeadSubs.any { + occurs( + key as Variable, + it.value + ) + } + } yield(Result.success(goalToHeadResult + headResult)) }, onFailure = { error -> diff --git a/src/prolog/ast/logic/Predicate.kt b/src/prolog/ast/logic/Predicate.kt index 25fb737..e935468 100644 --- a/src/prolog/ast/logic/Predicate.kt +++ b/src/prolog/ast/logic/Predicate.kt @@ -2,25 +2,25 @@ package prolog.ast.logic import prolog.Answers import prolog.Substitutions -import prolog.ast.terms.Functor +import prolog.ast.terms.FunctorInfo import prolog.ast.terms.Goal import prolog.flags.AppliedCut /** - * Collection of [Clause]s with the same [Functor]. + * Collection of [Clause]s with the same [FunctorInfo]. * * If a goal is proved, the system looks for a predicate with the same functor, then uses indexing * to select candidate clauses and then tries these clauses one-by-one. */ class Predicate : Resolvent { - val functor: Functor + val functor: FunctorInfo val clauses: MutableList var dynamic = false /** * Creates a predicate with the given functor and an empty list of clauses. */ - constructor(functor: Functor, dynamic: Boolean = false) { + constructor(functor: FunctorInfo, dynamic: Boolean = false) { this.functor = functor this.clauses = mutableListOf() this.dynamic = dynamic diff --git a/src/prolog/ast/terms/Atom.kt b/src/prolog/ast/terms/Atom.kt index 5bc2004..2dae78d 100644 --- a/src/prolog/ast/terms/Atom.kt +++ b/src/prolog/ast/terms/Atom.kt @@ -4,9 +4,10 @@ import prolog.Answers import prolog.Substitutions import prolog.ast.logic.Resolvent import prolog.logic.unifyLazy +import prolog.ast.arithmetic.Integer open class Atom(val name: String) : Goal(), Head, Body, Resolvent { - override val functor: Functor = "$name/_" + override val functor: FunctorInfo = FunctorInfo(this, Integer(0)) override fun solve(goal: Goal, subs: Substitutions): Answers = unifyLazy(goal, this, subs) diff --git a/src/prolog/ast/terms/FunctorInfo.kt b/src/prolog/ast/terms/FunctorInfo.kt new file mode 100644 index 0000000..eb61ef7 --- /dev/null +++ b/src/prolog/ast/terms/FunctorInfo.kt @@ -0,0 +1,29 @@ +package prolog.ast.terms + +import prolog.Substitutions +import prolog.ast.arithmetic.Integer + +data class FunctorInfo(val name: Atom, val arity: Integer) : Term { + companion object { + fun of(functor: String): FunctorInfo { + // Split the functor string into name and arity, by splitting the last "/" + val lastSlash = functor.lastIndexOf('/') + val name = Atom(functor.substring(0, lastSlash)) + val arity = Integer(functor.substring(lastSlash + 1).toIntOrNull() ?: 0) + return FunctorInfo(name, arity) + } + } + + override fun toString(): String = "${name.name}/$arity" + override fun applySubstitution(subs: Substitutions) : FunctorInfo = this + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null) return false + if (other !is FunctorInfo && other !is String) return false + if (other is FunctorInfo) { + return this.name.toString() == other.name.toString() && this.arity == other.arity + } + return this.toString() == other + } +} diff --git a/src/prolog/ast/terms/Goal.kt b/src/prolog/ast/terms/Goal.kt index 95f9016..0ba62f0 100644 --- a/src/prolog/ast/terms/Goal.kt +++ b/src/prolog/ast/terms/Goal.kt @@ -1,8 +1,8 @@ package prolog.ast.terms import prolog.Answers -import prolog.ast.Database.Program import prolog.Substitutions +import prolog.ast.Database.Program import prolog.ast.logic.LogicOperand /** @@ -13,7 +13,7 @@ import prolog.ast.logic.LogicOperand * or it fails if Prolog fails to prove it. */ abstract class Goal : LogicOperand(), Term { - abstract val functor: Functor + abstract val functor: FunctorInfo override fun satisfy(subs: Substitutions): Answers = Program.solve(this, subs) } \ No newline at end of file diff --git a/src/prolog/ast/terms/Head.kt b/src/prolog/ast/terms/Head.kt index 71b4e8a..fa5bd8d 100644 --- a/src/prolog/ast/terms/Head.kt +++ b/src/prolog/ast/terms/Head.kt @@ -4,7 +4,5 @@ package prolog.ast.terms * Part of a [Clause][prolog.ast.logic.Clause] before the neck operator. */ interface Head : Term { - val functor: Functor + val functor: FunctorInfo } - -typealias Functor = String diff --git a/src/prolog/ast/terms/Structure.kt b/src/prolog/ast/terms/Structure.kt index afed37c..4aadf01 100644 --- a/src/prolog/ast/terms/Structure.kt +++ b/src/prolog/ast/terms/Structure.kt @@ -5,13 +5,14 @@ import prolog.Substitutions import prolog.ast.logic.Resolvent import prolog.logic.applySubstitution import prolog.logic.unifyLazy +import prolog.ast.arithmetic.Integer typealias Argument = Term typealias CompoundTerm = Structure open class Structure(val name: Atom, var arguments: List) : Goal(), Head, Body, Resolvent { - override val functor: Functor = "${name.name}/${arguments.size}" + override val functor: FunctorInfo = FunctorInfo(name, Integer(arguments.size)) override fun solve(goal: Goal, subs: Substitutions): Answers { return unifyLazy(goal, this, subs) diff --git a/src/prolog/builtins/analysingAndConstructionOperators.kt b/src/prolog/builtins/analysingAndConstructionOperators.kt new file mode 100644 index 0000000..ba589ef --- /dev/null +++ b/src/prolog/builtins/analysingAndConstructionOperators.kt @@ -0,0 +1,65 @@ +package prolog.builtins + +import prolog.Answers +import prolog.Substitutions +import prolog.ast.arithmetic.Integer +import prolog.ast.terms.AnonymousVariable +import prolog.ast.terms.Atom +import prolog.ast.terms.Head +import prolog.ast.terms.Structure +import prolog.ast.terms.Term +import prolog.ast.terms.Variable +import prolog.logic.applySubstitution +import prolog.logic.atomic +import prolog.logic.nonvariable +import prolog.logic.unifyLazy +import prolog.logic.variable + +class Functor(private val term: Term, private val functorName: Term, private val functorArity: Term) : + Structure(Atom("functor"), listOf(term, functorName, functorArity)) { + override fun satisfy(subs: Substitutions): Answers { + if (nonvariable(term, subs)) { + val t = applySubstitution(term, subs) as Head + + return Conjunction( + Unify(t.functor.arity, functorArity), + Unify(t.functor.name, functorName) + ).satisfy(subs) + } + + if (variable(term, subs)) { + require(atomic(functorName, subs) && atomic(functorArity, subs)) { + "Arguments are not sufficiently instantiated" + } + + val name = applySubstitution(functorName, subs) as Atom + val arity = applySubstitution(functorArity, subs) as Integer + + val result = if (arity.value == 0) { + functorName + } else { + val argList = mutableListOf() + for (i in 1..arity.value) { + argList.add(AnonymousVariable.create()) + } + Structure(name, argList) + } + + if (nonvariable(term, subs)) { + return unifyLazy(term, result, subs) + } + + return sequenceOf(Result.success(mapOf(term to result))) + } + + throw IllegalStateException() + } + + override fun applySubstitution(subs: Substitutions): Functor = Functor( + term.applySubstitution(subs), + functorName.applySubstitution(subs), + functorArity.applySubstitution(subs) + ) + + override fun toString(): String = "functor($term, $functorName, $functorArity)" +} diff --git a/src/prolog/builtins/databaseOperators.kt b/src/prolog/builtins/databaseOperators.kt index ea36376..05f228c 100644 --- a/src/prolog/builtins/databaseOperators.kt +++ b/src/prolog/builtins/databaseOperators.kt @@ -3,26 +3,21 @@ package prolog.builtins import io.Logger import prolog.Answers import prolog.Substitutions -import prolog.ast.logic.Clause -import prolog.ast.terms.Atom -import prolog.ast.terms.Structure -import prolog.ast.logic.Predicate -import prolog.ast.Database.Program -import prolog.ast.terms.Functor -import prolog.ast.terms.Term -import prolog.ast.logic.Fact import prolog.ast.Database -import prolog.ast.terms.Body -import prolog.ast.terms.Goal -import prolog.ast.terms.Operator +import prolog.ast.Database.Program +import prolog.ast.arithmetic.Integer +import prolog.ast.logic.Clause +import prolog.ast.logic.Fact +import prolog.ast.logic.Predicate +import prolog.ast.terms.* import prolog.logic.applySubstitution import prolog.logic.unifyLazy /** - * (Make) the [Predicate] with the corresponding [Functor] dynamic. + * (Make) the [Predicate] with the corresponding [FunctorInfo] dynamic. */ -class Dynamic(private val dynamicFunctor: Functor): Goal(), Body { - override val functor: Functor = "dynamic/1" +class Dynamic(private val dynamicFunctor: FunctorInfo) : Goal(), Body { + override val functor = FunctorInfo(Atom("dynamic"), Integer(1)) override fun satisfy(subs: Substitutions): Answers { val predicate = Program.db.predicates[dynamicFunctor] @@ -48,7 +43,7 @@ class Dynamic(private val dynamicFunctor: Functor): Goal(), Body { } class Assert(clause: Clause) : AssertZ(clause) { - override val functor: Functor = "assert/1" + override val functor = FunctorInfo(Atom("assert"), Integer(1)) } /** diff --git a/tests/interpreter/PreprocessorTests.kt b/tests/interpreter/PreprocessorTests.kt index bf54869..4d9ed7f 100644 --- a/tests/interpreter/PreprocessorTests.kt +++ b/tests/interpreter/PreprocessorTests.kt @@ -612,7 +612,7 @@ class PreprocessorTests { Atom("declaration/1") ) ) - val expected = Dynamic("declaration/1") + val expected = Dynamic(FunctorInfo.of("declaration/1")) val result = preprocessor.preprocess(input) diff --git a/tests/parser/grammars/LogicGrammarTests.kt b/tests/parser/grammars/LogicGrammarTests.kt index 57a1f99..7ee3fcc 100644 --- a/tests/parser/grammars/LogicGrammarTests.kt +++ b/tests/parser/grammars/LogicGrammarTests.kt @@ -15,6 +15,7 @@ import prolog.ast.terms.CompoundTerm import prolog.ast.terms.Structure import prolog.ast.terms.Variable import prolog.builtins.Conjunction +import prolog.ast.terms.FunctorInfo class LogicGrammarTests { private lateinit var parser: Grammar> @@ -94,13 +95,13 @@ class LogicGrammarTests { assertTrue(rule.head is Structure, "Expected head to be a structure") val head = rule.head as Structure - assertEquals("parent/2", head.functor, "Expected functor 'parent/2'") + assertEquals(FunctorInfo.of("parent/2"), head.functor, "Expected functor 'parent/2'") assertEquals(Variable("X"), head.arguments[0], "Expected first argument 'X'") assertEquals(Variable("Y"), head.arguments[1], "Expected second argument 'Y'") assertTrue(rule.body is Structure, "Expected body to be a structure") val body = rule.body as Structure - assertEquals("father/2", body.functor, "Expected functor 'father/2'") + assertEquals(FunctorInfo.of("father/2"), body.functor, "Expected functor 'father/2'") assertEquals(Variable("X"), body.arguments[0], "Expected first argument 'X'") assertEquals(Variable("Y"), body.arguments[1], "Expected second argument 'Y'") } @@ -125,12 +126,12 @@ class LogicGrammarTests { assertEquals(1, result.size, "Expected 1 rule") val rule = result[0] as Rule - assertEquals("guest/2", rule.head.functor, "Expected functor 'guest/2'") - assertEquals(",/2", (rule.body as CompoundTerm).functor, "Expected functor ',/2'") + assertEquals(FunctorInfo.of("guest/2"), rule.head.functor, "Expected functor 'guest/2'") + assertEquals(FunctorInfo.of(",/2"), (rule.body as CompoundTerm).functor, "Expected functor ',/2'") val l1 = (rule.body as CompoundTerm).arguments[0] as CompoundTerm - assertEquals(",/2", l1.functor, "Expected functor ',/2'") + assertEquals(FunctorInfo.of(",/2"), l1.functor, "Expected functor ',/2'") val l2 = l1.arguments[0] as CompoundTerm - assertEquals("invited/2", l2.functor, "Expected functor 'invited/2'") + assertEquals(FunctorInfo.of("invited/2"), l2.functor, "Expected functor 'invited/2'") } @Test @@ -157,6 +158,6 @@ class LogicGrammarTests { assertEquals(1, result.size, "Expected 1 rule") assertTrue(result[0] is Rule, "Expected a rule") val rule = result[0] as Rule - assertEquals("/_", rule.head.functor, "Expected a constraint") + assertEquals(FunctorInfo.of("/0"), rule.head.functor, "Expected a constraint") } } \ No newline at end of file diff --git a/tests/prolog/builtins/AnalysingAndConstructionOperatorsTests.kt b/tests/prolog/builtins/AnalysingAndConstructionOperatorsTests.kt new file mode 100644 index 0000000..6e91bfc --- /dev/null +++ b/tests/prolog/builtins/AnalysingAndConstructionOperatorsTests.kt @@ -0,0 +1,91 @@ +package prolog.builtins + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import prolog.ast.arithmetic.Integer +import prolog.ast.terms.AnonymousVariable +import prolog.ast.terms.Atom +import prolog.ast.terms.Structure +import prolog.ast.terms.Variable + +class AnalysingAndConstructionOperatorsTests { + @Test + fun `functor(foo, foo, 0)`() { + val functor = Functor(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 = Functor( + 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 = Functor(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 = Functor(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 = Functor(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 = Functor(Structure(Atom("foo"), listOf(Variable("X"))), Atom("foo"), Integer(0)) + + val result = functor.satisfy(emptyMap()).toList() + + assertTrue(result.isEmpty(), "Expected no results") + } +} \ No newline at end of file diff --git a/tests/prolog/builtins/DatabaseOperatorsTests.kt b/tests/prolog/builtins/DatabaseOperatorsTests.kt index 73cfef0..cbb5bb6 100644 --- a/tests/prolog/builtins/DatabaseOperatorsTests.kt +++ b/tests/prolog/builtins/DatabaseOperatorsTests.kt @@ -8,14 +8,13 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource -import prolog.ast.Database import prolog.ast.Database.Program import prolog.ast.logic.Clause import prolog.ast.logic.Fact import prolog.ast.logic.Predicate import prolog.ast.logic.Rule import prolog.ast.terms.Atom -import prolog.ast.terms.Functor +import prolog.ast.terms.FunctorInfo import prolog.ast.terms.Structure import prolog.ast.terms.Variable @@ -40,7 +39,7 @@ class DatabaseOperatorsTests { createAssert(fact).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") - assertEquals(fact, Program.db.predicates["a/_"]!!.clauses[0]) + assertEquals(fact, Program.db.predicates[FunctorInfo.of("a/_")]!!.clauses[0]) } @Test @@ -49,7 +48,7 @@ class DatabaseOperatorsTests { createAssert(fact).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") - assertEquals(fact, Program.db.predicates["a/1"]!!.clauses[0]) + assertEquals(fact, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) } @Test @@ -61,7 +60,7 @@ class DatabaseOperatorsTests { createAssert(rule).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") - assertEquals(rule, Program.db.predicates["a/1"]!!.clauses[0]) + assertEquals(rule, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) } } @@ -92,8 +91,8 @@ class DatabaseOperatorsTests { AssertA(rule2).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") - assertEquals(rule2, Program.db.predicates["a/1"]!!.clauses[0]) - assertEquals(rule1, Program.db.predicates["a/1"]!!.clauses[1]) + assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) + assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1]) } } @@ -117,8 +116,8 @@ class DatabaseOperatorsTests { AssertZ(rule2).satisfy(emptyMap()) assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") - assertEquals(rule1, Program.db.predicates["a/1"]!!.clauses[0]) - assertEquals(rule2, Program.db.predicates["a/1"]!!.clauses[1]) + assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) + assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1]) } } @@ -309,20 +308,20 @@ class DatabaseOperatorsTests { val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList() assertEquals(3, control.size, "Expected 3 results") - assertEquals(3, Program.db.predicates["a/1"]!!.clauses.size, "Expected 3 clauses") + assertEquals(3, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses.size, "Expected 3 clauses") val retract = RetractAll(Structure(Atom("a"), listOf(Variable("X")))) val result = retract.satisfy(emptyMap()).toList() assertEquals(1, result.size, "Expected 1 result") assertTrue(result[0].isSuccess, "Expected success") - assertEquals(0, Program.db.predicates["a/1"]!!.clauses.size, "Expected 0 clauses") + assertEquals(0, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses.size, "Expected 0 clauses") } @Test fun `If Head refers to a predicate that is not defined, it is implicitly created as a dynamic predicate`() { val predicateName = "idonotyetexist" - val predicateFunctor = "$predicateName/1" + val predicateFunctor = FunctorInfo.of("$predicateName/1") assertFalse(predicateFunctor in Program.db.predicates, "Expected predicate to not exist before")