diff --git a/.idea/2025LogProg-PrologInterpreter.iml b/.idea/2025LogProg-PrologInterpreter.iml
deleted file mode 100644
index 42d53f5..0000000
--- a/.idea/2025LogProg-PrologInterpreter.iml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 5b434ac..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Main.kt b/src/Main.kt
index 2416fdb..55a12bf 100644
--- a/src/Main.kt
+++ b/src/Main.kt
@@ -1,4 +1,5 @@
import com.xenomachina.argparser.ArgParser
+import com.xenomachina.argparser.mainBody
import interpreter.FileLoader
import io.GhentPrologArgParser
import io.Logger
diff --git a/src/interpreter/Preprocessor.kt b/src/interpreter/Preprocessor.kt
index 7477702..559a7b2 100644
--- a/src/interpreter/Preprocessor.kt
+++ b/src/interpreter/Preprocessor.kt
@@ -66,53 +66,71 @@ 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 )
-
// 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" -> Equivalent(args[0], args[1])
+ term.functor == "=/2" -> {
+ Unify(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 == "\\=/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" -> {
+ 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)
+ }
// 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 == "-/1" && args.all { it is Expression } -> {
+ Negate(args[0] as Expression)
+ }
- // Database
- term.functor == "retract/1" -> Retract(args[0])
- term.functor == "assert/1" -> {
- if (args[0] is Rule) {
- Assert(args[0] as Rule)
- } else {
- Assert(Fact(args[0] as Head))
- }
+ term.functor == "-/2" && args.all { it is Expression } -> {
+ Subtract(args[0] as Expression, args[1] as Expression)
}
- term.functor == "asserta/1" -> {
- if (args[0] is Rule) {
- AssertA(args[0] as Rule)
- } else {
- AssertA(Fact(args[0] as Head))
- }
+
+ term.functor == "+/1" && args.all { it is Expression } -> {
+ Positive(args[0] as Expression)
}
- term.functor == "assertz/1" -> {
- if (args[0] is Rule) {
- AssertZ(args[0] as Rule)
- } else {
- AssertZ(Fact(args[0] as Head))
- }
+
+ 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)
}
// Other
diff --git a/src/parser/grammars/TermsGrammar.kt b/src/parser/grammars/TermsGrammar.kt
index f62e54d..487f9f7 100644
--- a/src/parser/grammars/TermsGrammar.kt
+++ b/src/parser/grammars/TermsGrammar.kt
@@ -31,12 +31,10 @@ import prolog.ast.terms.*
* | 100 | yfx | . |
* | 1 | fx | $ |
*
- * It is very easy to extend this grammar to support more operators. Just add them at the appropriate rule or create a
- * new rule and chain it to the existing ones.
- *
* @see [SWI-Prolog Predicate op/3](https://www.swi-prolog.org/pldoc/man?predicate=op/3)
*/
open class TermsGrammar : Tokens() {
+
// Basic named terms
protected val variable: Parser by (variableToken or anonymousVariableToken) use { Variable(text) }
protected val simpleAtom: Parser by (nameToken or exclamation) use { Atom(text) }
@@ -68,43 +66,42 @@ open class TermsGrammar : Tokens() {
or int
)
+ // Level 200 - prefix operators (+, -, \)
protected val op200: Parser by ((plus or minus) * parser(::term200)) use {
CompoundTerm(Atom(t1.text), listOf(t2))
}
protected val term200: Parser by (op200 or baseTerm)
+ // Level 400 - multiplication, division
protected val op400: Parser by (multiply or divide) use { text }
protected val term400: Parser by (term200 * zeroOrMore(op400 * term200)) use {
t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
}
+ // Level 500 - addition, subtraction
protected val op500: Parser by (plus or minus) use { text }
protected val term500: Parser by (term400 * zeroOrMore(op500 * term400)) use {
t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
}
+ // Level 700 - comparison operators
protected val op700: Parser by (equivalent or equals or notEquals or isOp) use { text }
protected val term700: Parser by (term500 * optional(op700 * term500)) use {
if (t2 == null) t1 else CompoundTerm(Atom(t2!!.t1), listOf(t1, t2!!.t2))
}
- protected val op1000: Parser by (comma) use { text }
- protected val term1000: Parser by (term700 * zeroOrMore(op1000 * term700)) use {
- t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
+ // Level 1000 - conjunction (,)
+ protected val term1000: Parser by (term700 * zeroOrMore(comma * term700)) use {
+ t2.fold(t1) { acc, (_, term) -> CompoundTerm(Atom(","), listOf(acc, term)) }
}
- protected val op1100: Parser by (semicolon) use { text }
- protected val term1100: Parser by (term1000 * zeroOrMore(op1100 * term1000)) use {
- t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
- }
-
- protected val op1200: Parser by (neck) use { text }
- protected val term1200: Parser by (term1100 * zeroOrMore(op1200 * term1100)) use {
- t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
+ // Level 1100 - disjunction (;)
+ protected val term1100: Parser by (term1000 * zeroOrMore(semicolon * term1000)) use {
+ t2.fold(t1) { acc, (_, term) -> CompoundTerm(Atom(";"), listOf(acc, term)) }
}
// Term - highest level expression
- protected val term: Parser by term1200
+ protected val term: Parser by term1100
protected val termNoConjunction: Parser by term700
// Parts for clauses
diff --git a/src/prolog/Program.kt b/src/prolog/Program.kt
index e82b0bf..d129f0d 100644
--- a/src/prolog/Program.kt
+++ b/src/prolog/Program.kt
@@ -12,8 +12,8 @@ import prolog.ast.terms.Goal
* This object is a singleton that manages a list of databases.
*/
object Program : Resolvent {
- val internalDb = Database("")
- val databases: MutableList = mutableListOf(internalDb)
+ private val internalDb = Database("")
+ private val databases: MutableList = mutableListOf(internalDb)
var storeNewLine: Boolean = false
var variableRenamingStart: Int = 0
@@ -35,7 +35,7 @@ object Program : Resolvent {
}
}
- fun load(clauses: List, index: Int? = null) = internalDb.load(clauses, index)
+ fun load(clauses: List) = internalDb.load(clauses)
fun clear() {
databases.forEach { it.clear() }
diff --git a/src/prolog/Substitution.kt b/src/prolog/Substitution.kt
index 9058f8c..062d63e 100644
--- a/src/prolog/Substitution.kt
+++ b/src/prolog/Substitution.kt
@@ -8,4 +8,4 @@ abstract class Substitution(val from: Term, val to: Term) {
}
typealias Substitutions = Map
typealias Answer = Result
-typealias Answers = Sequence
+typealias Answers = Sequence
\ No newline at end of file
diff --git a/src/prolog/ast/Database.kt b/src/prolog/ast/Database.kt
index d10695d..e295e39 100644
--- a/src/prolog/ast/Database.kt
+++ b/src/prolog/ast/Database.kt
@@ -14,7 +14,7 @@ import prolog.ast.terms.Goal
* Prolog Program or Database
*/
class Database(val sourceFile: String): Resolvent {
- var predicates: Map = emptyMap()
+ private var predicates: Map = emptyMap()
fun initialize() {
Logger.info("Initializing database from $sourceFile")
@@ -39,14 +39,14 @@ class Database(val sourceFile: String): Resolvent {
/**
* Loads a list of clauses into the program.
*/
- fun load(clauses: List, index: Int? = null) {
+ fun load(clauses: List) {
for (clause in clauses) {
val functor = clause.functor
val predicate = predicates[functor]
if (predicate != null) {
// If the predicate already exists, add the clause to it
- predicate.add(clause, index)
+ predicate.add(clause)
} else {
// If the predicate does not exist, create a new one
predicates += Pair(functor, Predicate(listOf(clause)))
diff --git a/src/prolog/ast/logic/Clause.kt b/src/prolog/ast/logic/Clause.kt
index a48fb1f..f402951 100644
--- a/src/prolog/ast/logic/Clause.kt
+++ b/src/prolog/ast/logic/Clause.kt
@@ -19,7 +19,7 @@ import prolog.logic.unifyLazy
* @see [prolog.ast.terms.Variable]
* @see [Predicate]
*/
-abstract class Clause(val head: Head, val body: Body) : Term, Resolvent {
+abstract class Clause(val head: Head, val body: Body) : Resolvent {
val functor: Functor = head.functor
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
@@ -70,18 +70,4 @@ abstract class Clause(val head: Head, val body: Body) : Term, Resolvent {
}
override fun toString(): String = if (body is True) head.toString() else "$head :- $body"
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Clause) return false
-
- if (head != other.head) return false
- if (body != other.body) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- return super.hashCode()
- }
}
diff --git a/src/prolog/ast/logic/Predicate.kt b/src/prolog/ast/logic/Predicate.kt
index 5640f57..236f286 100644
--- a/src/prolog/ast/logic/Predicate.kt
+++ b/src/prolog/ast/logic/Predicate.kt
@@ -36,9 +36,9 @@ class Predicate : Resolvent {
/**
* Adds a clause to the predicate.
*/
- fun add(clause: Clause, index: Int? = null) {
+ fun add(clause: Clause) {
require(clause.functor == functor) { "Clause functor does not match predicate functor" }
- if (index != null) clauses.add(index, clause) else clauses.add(clause)
+ clauses.add(clause)
}
/**
diff --git a/src/prolog/builtins/databaseOperators.kt b/src/prolog/builtins/databaseOperators.kt
deleted file mode 100644
index a002bfa..0000000
--- a/src/prolog/builtins/databaseOperators.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package prolog.builtins
-
-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.Program
-import prolog.ast.terms.Functor
-import prolog.ast.terms.Term
-import prolog.ast.logic.Fact
-import prolog.ast.Database
-import prolog.ast.terms.Operator
-import prolog.logic.unifyLazy
-
-class Assert(clause: Clause) : AssertZ(clause) {
- override val functor: Functor = "assert/1"
-}
-
-/**
- * Assert a [Clause] as a first clause of the [Predicate] into the [Program].
- */
-class AssertA(val clause: Clause) : Operator(Atom("asserta"), null, clause) {
- override fun satisfy(subs: Substitutions): Answers {
- // Add clause to the program
- Program.load(listOf(clause), 0)
-
- return sequenceOf(Result.success(emptyMap()))
- }
-}
-
-/**
- * Assert a [Clause] as a last clause of the [Predicate] into the [Program].
- */
-open class AssertZ(val clause: Clause) : Operator(Atom("assertz"), null, clause) {
- override fun satisfy(subs: Substitutions): Answers {
- // Add clause to the program
- Program.load(listOf(clause))
-
- return sequenceOf(Result.success(emptyMap()))
- }
-}
-
-/**
- * When [Term] is an [Atom] or a term, it is unified with the first unifying [Clause] in the [Database].
- * The [Fact] or Clause is removed from the Database. It respects the logical update view.
- *
- * @see [SWI-Prolog Predicate retract/1](https://www.swi-prolog.org/pldoc/doc_for?object=retract/1)
- */
-class Retract(val term: Term) : Operator(Atom("retract"), null, term) {
- override fun satisfy(subs: Substitutions): Answers = sequence {
- // Check that term is a structure or atom
- if (term !is Structure && term !is Atom) {
- yield(Result.failure(Exception("Cannot retract a non-structure or non-atom")))
- return@sequence
- }
-
- val functorName = term.functor
-
- Program.databases
- .filter { it.predicates.containsKey(functorName) }
- .mapNotNull { it.predicates[functorName] }
- .map { predicate ->
- val clausesIterator = predicate.clauses.iterator()
- while (clausesIterator.hasNext()) {
- val clause = clausesIterator.next()
- unifyLazy(term, clause.head, subs).forEach { unifyResult ->
- unifyResult.fold(
- onSuccess = { substitutions ->
- // If unification is successful, remove the clause
- yield(Result.success(substitutions))
- clausesIterator.remove()
- },
- onFailure = {
- // If unification fails, do nothing
- }
- )
- }
- }
- }
- }
-}
diff --git a/src/prolog/builtins/ioOperators.kt b/src/prolog/builtins/io.kt
similarity index 100%
rename from src/prolog/builtins/ioOperators.kt
rename to src/prolog/builtins/io.kt
diff --git a/src/repl/Repl.kt b/src/repl/Repl.kt
index aae1390..a7c684a 100644
--- a/src/repl/Repl.kt
+++ b/src/repl/Repl.kt
@@ -42,9 +42,10 @@ class Repl {
val iterator = answers.iterator()
if (!iterator.hasNext()) {
- io.say("false.\n")
+ io.say("false.")
} else {
- io.say(prettyPrint(iterator.next()))
+ var previous = iterator.next()
+ io.say(prettyPrint(previous))
while (iterator.hasNext()) {
var command = io.prompt("")
@@ -56,7 +57,8 @@ class Repl {
when (command) {
";" -> {
- io.say(prettyPrint(iterator.next()))
+ previous = iterator.next()
+ io.say(prettyPrint(previous))
}
"a" -> return
"." -> return
@@ -86,7 +88,7 @@ class Repl {
val subs = result.getOrNull()!!
if (subs.isEmpty()) {
io.checkNewLine()
- return "true."
+ return "true.\n"
}
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
},
diff --git a/tests/interpreter/PreprocessorTests.kt b/tests/interpreter/PreprocessorTests.kt
index 0b98ff4..d7d6c19 100644
--- a/tests/interpreter/PreprocessorTests.kt
+++ b/tests/interpreter/PreprocessorTests.kt
@@ -6,8 +6,6 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import parser.grammars.TermsGrammar
import prolog.ast.arithmetic.Integer
-import prolog.ast.logic.Fact
-import prolog.ast.logic.Rule
import prolog.ast.terms.*
import prolog.builtins.*
@@ -500,109 +498,4 @@ class PreprocessorTests {
)
}
}
-
- @Nested
- class `Database operators` {
- private val preprocessor = OpenPreprocessor()
-
- @Test
- fun `assert(fact)`() {
- val input = Structure(
- Atom("assert"), listOf(
- Structure(
- Atom(":-"), listOf(
- Atom("a"),
- Atom("b")
- )
- )
- )
- )
- val expected = Assert(
- Rule(
- Atom("a"),
- Atom("b")
- )
- )
-
- val result = preprocessor.preprocess(input)
-
- assertEquals(expected, result)
- }
-
- @Test
- fun `asserta(fact)`() {
- val input = Structure(
- Atom("asserta"), listOf(
- Structure(
- Atom(":-"), listOf(
- Atom("a"),
- Atom("b")
- )
- )
- )
- )
- val expected = AssertA(
- Rule(
- Atom("a"),
- Atom("b")
- )
- )
-
- val result = preprocessor.preprocess(input)
-
- assertEquals(expected, result)
- }
-
- @Test
- fun `assertz(fact)`() {
- val input = Structure(
- Atom("assertz"), listOf(
- Structure(
- Atom(":-"), listOf(
- Atom("a"),
- Atom("b")
- )
- )
- )
- )
- val expected = AssertZ(
- Rule(
- Atom("a"),
- Atom("b")
- )
- )
-
- val result = preprocessor.preprocess(input)
-
- assertEquals(expected, result)
- }
-
- @Test
- fun `retract(atom)`() {
- val input = Structure(
- Atom("retract"), listOf(
- Atom("a")
- )
- )
- val expected = Retract(Atom("a"))
-
- val result = preprocessor.preprocess(input)
-
- assertEquals(expected, result)
- }
-
- @Test
- fun `retract(compund with variable)`() {
- val input = Structure(
- Atom("retract"), listOf(
- CompoundTerm(Atom("a"), listOf(Variable("X")))
- )
- )
- val expected = Retract(CompoundTerm(Atom("a"), listOf(Variable("X"))))
-
- val result = preprocessor.preprocess(input)
-
- assertEquals(expected, result)
- }
- }
}
diff --git a/tests/parser/builtins/DatabaseOperatorsParserTests.kt b/tests/parser/builtins/DatabaseOperatorsParserTests.kt
deleted file mode 100644
index 9f13e57..0000000
--- a/tests/parser/builtins/DatabaseOperatorsParserTests.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package parser.builtins
-
-import com.github.h0tk3y.betterParse.grammar.Grammar
-import com.github.h0tk3y.betterParse.grammar.parseToEnd
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import parser.grammars.TermsGrammar
-import prolog.ast.terms.Atom
-import prolog.ast.terms.Structure
-import prolog.ast.terms.Term
-import kotlin.test.assertEquals
-
-class DatabaseOperatorsParserTests {
- private lateinit var parser: Grammar
-
- @BeforeEach
- fun setup() {
- parser = TermsGrammar() as Grammar
- }
-
- @Test
- fun `parse assert(rule)`() {
- val input = "assert((a :- b))"
- val expected = Structure(Atom("assert"), listOf(
- Structure(Atom(":-"), listOf(
- Atom("a"),
- Atom("b")
- ))
- ))
-
- val result = parser.parseToEnd(input)
-
- assertEquals(expected, result)
- }
-
- @Test
- fun `parse assertA(rule)`() {
- val input = "assertA((a :- b))"
- val expected = Structure(Atom("assertA"), listOf(
- Structure(Atom(":-"), listOf(
- Atom("a"),
- Atom("b")
- ))
- ))
-
- val result = parser.parseToEnd(input)
-
- assertEquals(expected, result)
- }
-
- @Test
- fun `parse assertZ(rule)`() {
- val input = "assertZ((a :- b))"
- val expected = Structure(Atom("assertZ"), listOf(
- Structure(Atom(":-"), listOf(
- Atom("a"),
- Atom("b")
- ))
- ))
-
- val result = parser.parseToEnd(input)
-
- assertEquals(expected, result)
- }
-}
diff --git a/tests/prolog/builtins/DatabaseOperatorsTests.kt b/tests/prolog/builtins/DatabaseOperatorsTests.kt
deleted file mode 100644
index a14453d..0000000
--- a/tests/prolog/builtins/DatabaseOperatorsTests.kt
+++ /dev/null
@@ -1,286 +0,0 @@
-package prolog.builtins
-
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.BeforeEach
-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.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.Structure
-import prolog.ast.terms.Variable
-import kotlin.test.assertTrue
-
-class DatabaseOperatorsTests {
- @BeforeEach
- fun setup() {
- Program.clear()
- }
-
- abstract class AssertTestsBase {
- protected abstract fun createAssert(clause: Clause): Structure
-
- @BeforeEach
- fun setup() {
- Program.clear()
- }
-
- @ParameterizedTest
- @ValueSource(classes = [AssertA::class, AssertZ::class, Assert::class])
- fun `assert(fact atom)`(assertKind: Class<*>) {
- val fact = Fact(Atom("a"))
- createAssert(fact).satisfy(emptyMap())
-
- assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
- assertEquals(fact, Program.internalDb.predicates["a/_"]!!.clauses[0])
- }
-
- @Test
- fun `assert(fact structure)`() {
- val fact = Fact(Structure(Atom("a"), listOf(Atom("b"))))
- createAssert(fact).satisfy(emptyMap())
-
- assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
- assertEquals(fact, Program.internalDb.predicates["a/1"]!!.clauses[0])
- }
-
- @Test
- fun `assert(rule)`() {
- val rule = Rule(
- Structure(Atom("a"), listOf(Atom("b"))),
- Atom("c")
- )
- createAssert(rule).satisfy(emptyMap())
-
- assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
- assertEquals(rule, Program.internalDb.predicates["a/1"]!!.clauses[0])
- }
- }
-
- @Nested
- class AssertTests : AssertTestsBase() {
- override fun createAssert(clause: Clause): Structure {
- return Assert(clause)
- }
- }
-
- @Nested
- class AssertATests : AssertTestsBase() {
- override fun createAssert(clause: Clause): Structure {
- return AssertA(clause)
- }
-
- @Test
- fun `asserta adds to the beginning`() {
- val rule1 = Rule(
- Structure(Atom("a"), listOf(Atom("b"))),
- Atom("c")
- )
- val rule2 = Rule(
- Structure(Atom("a"), listOf(Atom("d"))),
- Atom("e")
- )
- AssertA(rule1).satisfy(emptyMap())
- AssertA(rule2).satisfy(emptyMap())
-
- assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
- assertEquals(rule2, Program.internalDb.predicates["a/1"]!!.clauses[0])
- assertEquals(rule1, Program.internalDb.predicates["a/1"]!!.clauses[1])
- }
- }
-
- @Nested
- class AssertZTests : AssertTestsBase() {
- override fun createAssert(clause: Clause): Structure {
- return AssertZ(clause)
- }
-
- @Test
- fun `assertz adds to the end`() {
- val rule1 = Rule(
- Structure(Atom("a"), listOf(Atom("b"))),
- Atom("c")
- )
- val rule2 = Rule(
- Structure(Atom("a"), listOf(Atom("d"))),
- Atom("e")
- )
- AssertZ(rule1).satisfy(emptyMap())
- AssertZ(rule2).satisfy(emptyMap())
-
- assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
- assertEquals(rule1, Program.internalDb.predicates["a/1"]!!.clauses[0])
- assertEquals(rule2, Program.internalDb.predicates["a/1"]!!.clauses[1])
- }
- }
-
- @Test
- fun `retract fails silently for unknown predicates`() {
- val retract = Retract(Atom("unknown"))
- val result = retract.satisfy(emptyMap())
-
- assertTrue(result.none(), "Expected no results")
- }
-
- @Test
- fun `simple retract`() {
- val predicate = Predicate(listOf(Fact(Atom("a"))))
- Program.internalDb.load(predicate)
-
- assertEquals(1, Program.query(Atom("a")).count())
-
- val retract = Retract(Atom("a"))
-
- assertTrue(retract.satisfy(emptyMap()).any(), "Expected 1 result")
- assertEquals(0, predicate.clauses.size, "Expected 0 clauses")
-
- assertTrue(retract.satisfy(emptyMap()).none())
- }
-
- @Test
- fun `retract atom`() {
- val predicate = Predicate(listOf(
- Fact(Atom("a")),
- Fact(Atom("a")),
- Fact(Atom("a"))
- ))
- Program.internalDb.load(predicate)
-
- val control = Program.query(Atom("a")).toList()
-
- assertEquals(3, control.size, "Expected 3 results")
-
- val retract = Retract(Atom("a"))
-
- val result = retract.satisfy(emptyMap())
-
- assertEquals(3, predicate.clauses.size, "Expected 3 clauses")
-
- var answer = result.first()
-
- assertTrue(answer.isSuccess, "Expected success")
- var subs = answer.getOrNull()!!
- assertTrue(subs.isEmpty(), "Expected no substitutions")
- assertEquals(2, predicate.clauses.size, "Expected 2 clauses")
-
- assertTrue(result.first().isSuccess)
- assertTrue(result.first().isSuccess)
-
- assertEquals(0, predicate.clauses.size, "Expected no remaining clauses")
- }
-
- @Test
- fun `retract compound with variable`() {
- val predicate = Predicate(listOf(
- Fact(Structure(Atom("a"), listOf(Atom("b")))),
- Fact(Structure(Atom("a"), listOf(Atom("c")))),
- Fact(Structure(Atom("a"), listOf(Atom("d"))))
- ))
- Program.internalDb.load(predicate)
-
- val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList()
-
- assertEquals(3, control.size, "Expected 3 results")
-
- val retract = Retract(Structure(Atom("a"), listOf(Variable("X"))))
-
- val result = retract.satisfy(emptyMap())
-
- assertEquals(3, predicate.clauses.size, "Expected 3 clauses")
-
- var answer = result.first()
-
- assertTrue(answer.isSuccess, "Expected success")
- var subs = answer.getOrNull()!!
- assertTrue(subs.isNotEmpty(), "Expected substitutions")
- assertTrue(Variable("X") in subs, "Expected variable X")
- assertEquals(Atom("b"), subs[Variable("X")], "Expected b")
- assertEquals(2, predicate.clauses.size, "Expected 2 clauses")
-
- answer = result.first()
-
- assertTrue(answer.isSuccess, "Expected success")
- subs = answer.getOrNull()!!
- assertTrue(subs.isNotEmpty(), "Expected substitutions")
- assertTrue(Variable("X") in subs, "Expected variable X")
- assertEquals(Atom("c"), subs[Variable("X")], "Expected c")
- assertEquals(1, predicate.clauses.size, "Expected 1 clause")
-
- answer = result.first()
-
- assertTrue(answer.isSuccess, "Expected success")
- subs = answer.getOrNull()!!
- assertTrue(subs.isNotEmpty(), "Expected substitutions")
- assertTrue(Variable("X") in subs, "Expected variable X")
- assertEquals(Atom("d"), subs[Variable("X")], "Expected d")
- assertEquals(0, predicate.clauses.size, "Expected no clauses")
-
- assertEquals(0, result.count(), "Expected no remaining results")
- }
-
- @Test
- fun `custom assert example`() {
- var query = Structure(Atom("likes"), listOf(Atom("alice"), Atom("pizza")))
-
- var result = Program.query(query).toList()
- assertEquals(0, result.size, "Expected 0 results")
-
- var assert: Structure = Assert(Fact(query))
- assert.satisfy(emptyMap())
-
- result = Program.query(query).toList()
- assertEquals(1, result.size, "Expected 1 result")
- assertTrue(result[0].getOrNull()!!.isEmpty())
-
- assert = AssertZ(Fact(Structure(Atom("likes"), listOf(Atom("bob"), Atom("sushi")))))
- assert.satisfy(emptyMap())
-
- query = Structure(Atom("likes"), listOf(Atom("bob"), Variable("X")))
-
- result = Program.query(query).toList()
- assertEquals(1, result.size, "Expected 1 result")
- assertTrue(result[0].isSuccess, "Expected success")
- assertEquals(Atom("sushi"), result[0].getOrNull()!![Variable("X")], "Expected sushi")
-
- query = Structure(Atom("likes"), listOf(Variable("X"), Variable("Y")))
-
- result = Program.query(query).toList()
- assertEquals(2, result.size, "Expected 2 results")
- assertTrue(result[0].isSuccess, "Expected success")
- var result0 = result[0].getOrNull()!!
- assertEquals(Atom("alice"), result0[Variable("X")], "Expected alice")
- assertEquals(Atom("pizza"), result0[Variable("Y")], "Expected pizza")
- assertTrue(result[1].isSuccess, "Expected success")
- var result1 = result[1].getOrNull()!!
- assertEquals(Atom("bob"), result1[Variable("X")], "Expected bob")
- assertEquals(Atom("sushi"), result1[Variable("Y")], "Expected sushi")
-
- assert = AssertA(
- Rule(
- Structure(Atom("likes"), listOf(Variable("X"), Atom("italian"))),
- Structure(Atom("likes"), listOf(Variable("X"), Atom("pizza")))
- )
- )
- assert.satisfy(emptyMap())
-
- result = Program.query(query).toList()
- assertEquals(3, result.size, "Expected 3 results")
- assertTrue(result[0].isSuccess, "Expected success")
- result0 = result[0].getOrNull()!!
- assertEquals(Atom("alice"), result0[Variable("X")], "Expected alice")
- assertEquals(Atom("italian"), result0[Variable("Y")], "Expected italian")
- assertTrue(result[1].isSuccess, "Expected success")
- result1 = result[1].getOrNull()!!
- assertEquals(Atom("alice"), result1[Variable("X")], "Expected alice")
- assertEquals(Atom("pizza"), result1[Variable("Y")], "Expected pizza")
- assertTrue(result[2].isSuccess, "Expected success")
- val result2 = result[2].getOrNull()!!
- assertEquals(Atom("bob"), result2[Variable("X")], "Expected bob")
- assertEquals(Atom("sushi"), result2[Variable("Y")], "Expected sushi")
- }
-}
\ No newline at end of file