Retract
This commit is contained in:
parent
80fb3d1e60
commit
5bfa1691dd
10 changed files with 276 additions and 17 deletions
8
.idea/2025LogProg-PrologInterpreter.iml
generated
Normal file
8
.idea/2025LogProg-PrologInterpreter.iml
generated
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module version="4">
|
||||||
|
<component name="AdditionalModuleElements">
|
||||||
|
<content url="file://$MODULE_DIR$" dumb="true">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/examples" type="java-resource" />
|
||||||
|
</content>
|
||||||
|
</component>
|
||||||
|
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/2025LogProg-PrologInterpreter.iml" filepath="$PROJECT_DIR$/.idea/2025LogProg-PrologInterpreter.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -1,5 +1,4 @@
|
||||||
import com.xenomachina.argparser.ArgParser
|
import com.xenomachina.argparser.ArgParser
|
||||||
import com.xenomachina.argparser.mainBody
|
|
||||||
import interpreter.FileLoader
|
import interpreter.FileLoader
|
||||||
import io.GhentPrologArgParser
|
import io.GhentPrologArgParser
|
||||||
import io.Logger
|
import io.Logger
|
||||||
|
|
|
@ -92,6 +92,7 @@ open class Preprocessor {
|
||||||
term.functor == "between/3" && args.all { it is Expression } -> Between(args[0] as Expression, args[1] as Expression, args[2] as Expression)
|
term.functor == "between/3" && args.all { it is Expression } -> Between(args[0] as Expression, args[1] as Expression, args[2] as Expression)
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
|
term.functor == "retract/1" -> Retract(args[0])
|
||||||
term.functor == "assert/1" -> {
|
term.functor == "assert/1" -> {
|
||||||
if (args[0] is Rule) {
|
if (args[0] is Rule) {
|
||||||
Assert(args[0] as Rule)
|
Assert(args[0] as Rule)
|
||||||
|
@ -99,7 +100,20 @@ open class Preprocessor {
|
||||||
Assert(Fact(args[0] as Head))
|
Assert(Fact(args[0] as Head))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
term.functor == "asserta/1" -> AssertA(args[0] as Clause)
|
term.functor == "asserta/1" -> {
|
||||||
|
if (args[0] is Rule) {
|
||||||
|
AssertA(args[0] as Rule)
|
||||||
|
} else {
|
||||||
|
AssertA(Fact(args[0] as Head))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
term.functor == "assertz/1" -> {
|
||||||
|
if (args[0] is Rule) {
|
||||||
|
AssertZ(args[0] as Rule)
|
||||||
|
} else {
|
||||||
|
AssertZ(Fact(args[0] as Head))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
term.functor == "write/1" -> Write(args[0])
|
term.functor == "write/1" -> Write(args[0])
|
||||||
|
|
|
@ -13,7 +13,7 @@ import prolog.ast.terms.Goal
|
||||||
*/
|
*/
|
||||||
object Program : Resolvent {
|
object Program : Resolvent {
|
||||||
val internalDb = Database("")
|
val internalDb = Database("")
|
||||||
private val databases: MutableList<Database> = mutableListOf(internalDb)
|
val databases: MutableList<Database> = mutableListOf(internalDb)
|
||||||
|
|
||||||
var storeNewLine: Boolean = false
|
var storeNewLine: Boolean = false
|
||||||
var variableRenamingStart: Int = 0
|
var variableRenamingStart: Int = 0
|
||||||
|
|
|
@ -8,6 +8,11 @@ import prolog.ast.terms.Structure
|
||||||
import prolog.ast.logic.Predicate
|
import prolog.ast.logic.Predicate
|
||||||
import prolog.Program
|
import prolog.Program
|
||||||
import prolog.ast.terms.Functor
|
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) {
|
class Assert(clause: Clause) : AssertZ(clause) {
|
||||||
override val functor: Functor = "assert/1"
|
override val functor: Functor = "assert/1"
|
||||||
|
@ -16,7 +21,7 @@ class Assert(clause: Clause) : AssertZ(clause) {
|
||||||
/**
|
/**
|
||||||
* Assert a [Clause] as a first clause of the [Predicate] into the [Program].
|
* Assert a [Clause] as a first clause of the [Predicate] into the [Program].
|
||||||
*/
|
*/
|
||||||
class AssertA(val clause: Clause) : Structure(Atom("asserta"), listOf(clause)) {
|
class AssertA(val clause: Clause) : Operator(Atom("asserta"), null, clause) {
|
||||||
override fun satisfy(subs: Substitutions): Answers {
|
override fun satisfy(subs: Substitutions): Answers {
|
||||||
// Add clause to the program
|
// Add clause to the program
|
||||||
Program.load(listOf(clause), 0)
|
Program.load(listOf(clause), 0)
|
||||||
|
@ -28,7 +33,7 @@ class AssertA(val clause: Clause) : Structure(Atom("asserta"), listOf(clause)) {
|
||||||
/**
|
/**
|
||||||
* Assert a [Clause] as a last clause of the [Predicate] into the [Program].
|
* Assert a [Clause] as a last clause of the [Predicate] into the [Program].
|
||||||
*/
|
*/
|
||||||
open class AssertZ(val clause: Clause) : Structure(Atom("assertz"), listOf(clause)) {
|
open class AssertZ(val clause: Clause) : Operator(Atom("assertz"), null, clause) {
|
||||||
override fun satisfy(subs: Substitutions): Answers {
|
override fun satisfy(subs: Substitutions): Answers {
|
||||||
// Add clause to the program
|
// Add clause to the program
|
||||||
Program.load(listOf(clause))
|
Program.load(listOf(clause))
|
||||||
|
@ -36,3 +41,43 @@ open class AssertZ(val clause: Clause) : Structure(Atom("assertz"), listOf(claus
|
||||||
return sequenceOf(Result.success(emptyMap()))
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,10 +42,9 @@ class Repl {
|
||||||
val iterator = answers.iterator()
|
val iterator = answers.iterator()
|
||||||
|
|
||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
io.say("false.")
|
io.say("false.\n")
|
||||||
} else {
|
} else {
|
||||||
var previous = iterator.next()
|
io.say(prettyPrint(iterator.next()))
|
||||||
io.say(prettyPrint(previous))
|
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
var command = io.prompt("")
|
var command = io.prompt("")
|
||||||
|
@ -57,8 +56,7 @@ class Repl {
|
||||||
|
|
||||||
when (command) {
|
when (command) {
|
||||||
";" -> {
|
";" -> {
|
||||||
previous = iterator.next()
|
io.say(prettyPrint(iterator.next()))
|
||||||
io.say(prettyPrint(previous))
|
|
||||||
}
|
}
|
||||||
"a" -> return
|
"a" -> return
|
||||||
"." -> return
|
"." -> return
|
||||||
|
@ -88,7 +86,7 @@ class Repl {
|
||||||
val subs = result.getOrNull()!!
|
val subs = result.getOrNull()!!
|
||||||
if (subs.isEmpty()) {
|
if (subs.isEmpty()) {
|
||||||
io.checkNewLine()
|
io.checkNewLine()
|
||||||
return "true.\n"
|
return "true."
|
||||||
}
|
}
|
||||||
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
|
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
|
||||||
},
|
},
|
||||||
|
|
|
@ -529,5 +529,80 @@ class PreprocessorTests {
|
||||||
assertEquals(expected, result)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.junit.jupiter.params.provider.ValueSource
|
||||||
import prolog.Program
|
import prolog.Program
|
||||||
import prolog.ast.logic.Clause
|
import prolog.ast.logic.Clause
|
||||||
import prolog.ast.logic.Fact
|
import prolog.ast.logic.Fact
|
||||||
|
import prolog.ast.logic.Predicate
|
||||||
import prolog.ast.logic.Rule
|
import prolog.ast.logic.Rule
|
||||||
import prolog.ast.terms.Atom
|
import prolog.ast.terms.Atom
|
||||||
import prolog.ast.terms.Structure
|
import prolog.ast.terms.Structure
|
||||||
|
@ -16,6 +17,11 @@ import prolog.ast.terms.Variable
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class DatabaseOperatorsTests {
|
class DatabaseOperatorsTests {
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
Program.clear()
|
||||||
|
}
|
||||||
|
|
||||||
abstract class AssertTestsBase<T : Structure> {
|
abstract class AssertTestsBase<T : Structure> {
|
||||||
protected abstract fun createAssert(clause: Clause): Structure
|
protected abstract fun createAssert(clause: Clause): Structure
|
||||||
|
|
||||||
|
@ -114,7 +120,111 @@ class DatabaseOperatorsTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `custom example`() {
|
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 query = Structure(Atom("likes"), listOf(Atom("alice"), Atom("pizza")))
|
||||||
|
|
||||||
var result = Program.query(query).toList()
|
var result = Program.query(query).toList()
|
||||||
|
@ -150,10 +260,12 @@ class DatabaseOperatorsTests {
|
||||||
assertEquals(Atom("bob"), result1[Variable("X")], "Expected bob")
|
assertEquals(Atom("bob"), result1[Variable("X")], "Expected bob")
|
||||||
assertEquals(Atom("sushi"), result1[Variable("Y")], "Expected sushi")
|
assertEquals(Atom("sushi"), result1[Variable("Y")], "Expected sushi")
|
||||||
|
|
||||||
assert = AssertA(Rule(
|
assert = AssertA(
|
||||||
|
Rule(
|
||||||
Structure(Atom("likes"), listOf(Variable("X"), Atom("italian"))),
|
Structure(Atom("likes"), listOf(Variable("X"), Atom("italian"))),
|
||||||
Structure(Atom("likes"), listOf(Variable("X"), Atom("pizza")))
|
Structure(Atom("likes"), listOf(Variable("X"), Atom("pizza")))
|
||||||
))
|
)
|
||||||
|
)
|
||||||
assert.satisfy(emptyMap())
|
assert.satisfy(emptyMap())
|
||||||
|
|
||||||
result = Program.query(query).toList()
|
result = Program.query(query).toList()
|
||||||
|
|
Reference in a new issue