RetractAll

This commit is contained in:
Tibo De Peuter 2025-05-05 22:06:26 +02:00
parent 4d334c1600
commit 1179e6a29b
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
4 changed files with 94 additions and 16 deletions

View file

@ -94,6 +94,7 @@ open class Preprocessor {
// Database // Database
term.functor == "dynamic/1" -> Dynamic((args[0] as Atom).name) term.functor == "dynamic/1" -> Dynamic((args[0] as Atom).name)
term.functor == "retract/1" -> Retract(args[0]) term.functor == "retract/1" -> Retract(args[0])
term.functor == "retractall/1" -> RetractAll(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)

View file

@ -1,5 +1,6 @@
package prolog.builtins package prolog.builtins
import io.Logger
import prolog.Answers import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.logic.Clause import prolog.ast.logic.Clause
@ -86,7 +87,7 @@ open class AssertZ(val clause: Clause) : Operator(Atom("assertz"), null, clause)
* *
* @see [SWI-Prolog Predicate retract/1](https://www.swi-prolog.org/pldoc/doc_for?object=retract/1) * @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) { open class Retract(val term: Term) : Operator(Atom("retract"), null, term) {
override fun satisfy(subs: Substitutions): Answers = sequence { override fun satisfy(subs: Substitutions): Answers = sequence {
// Check that term is a structure or atom // Check that term is a structure or atom
if (term !is Structure && term !is Atom) { if (term !is Structure && term !is Atom) {
@ -101,6 +102,12 @@ class Retract(val term: Term) : Operator(Atom("retract"), null, term) {
return@sequence return@sequence
} }
// Check if the predicate is dynamic
if (!predicate.dynamic) {
yield(Result.failure(Exception("No permission to modify static procedure '$functorName'")))
return@sequence
}
predicate.clauses.toList().forEach { clause -> predicate.clauses.toList().forEach { clause ->
unifyLazy(term, clause.head, subs).forEach { unifyResult -> unifyLazy(term, clause.head, subs).forEach { unifyResult ->
unifyResult.fold( unifyResult.fold(
@ -119,3 +126,29 @@ class Retract(val term: Term) : Operator(Atom("retract"), null, term) {
override fun applySubstitution(subs: Substitutions): Retract = Retract(applySubstitution(term, subs)) override fun applySubstitution(subs: Substitutions): Retract = Retract(applySubstitution(term, subs))
} }
class RetractAll(term: Term) : Retract(term) {
override fun satisfy(subs: Substitutions): Answers {
// Check that term is a structure or atom
if (term !is Structure && term !is Atom) {
return sequenceOf(Result.failure(Exception("Cannot retract a non-structure or non-atom")))
}
// If the predicate does not exist, implicitly create it
val functor = term.functor
val predicate = Program.db.predicates[functor]
if (predicate == null) {
Logger.debug("Predicate $functor not found, creating it")
Program.db.predicates += functor to Predicate(functor, dynamic = true)
}
// Propagate errors from the super class
super.satisfy(subs).forEach {
if (it.isFailure) {
return sequenceOf(it)
}
}
return sequenceOf(Result.success(emptyMap()))
}
}

View file

@ -95,9 +95,7 @@ class Repl {
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" } return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
}, },
onFailure = { onFailure = {
val text = "Failure: ${it.message}" return "ERROR: ${it.message}"
Logger.warn(text)
return text
} }
) )
} }

View file

@ -15,6 +15,7 @@ import prolog.ast.logic.Fact
import prolog.ast.logic.Predicate 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.Functor
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
@ -131,7 +132,7 @@ class DatabaseOperatorsTests {
@Test @Test
fun `simple retract`() { fun `simple retract`() {
val predicate = Predicate(listOf(Fact(Atom("a")))) val predicate = Predicate(listOf(Fact(Atom("a"))), dynamic = true)
Program.db.load(predicate) Program.db.load(predicate)
assertEquals(1, Program.query(Atom("a")).count()) assertEquals(1, Program.query(Atom("a")).count())
@ -146,11 +147,13 @@ class DatabaseOperatorsTests {
@Test @Test
fun `retract atom`() { fun `retract atom`() {
val predicate = Predicate(listOf( val predicate = Predicate(
Fact(Atom("a")), listOf(
Fact(Atom("a")), Fact(Atom("a")),
Fact(Atom("a")) Fact(Atom("a")),
)) Fact(Atom("a"))
), dynamic = true
)
Program.db.load(predicate) Program.db.load(predicate)
val control = Program.query(Atom("a")).toList() val control = Program.query(Atom("a")).toList()
@ -181,11 +184,13 @@ class DatabaseOperatorsTests {
@Test @Test
fun `retract compound with variable`() { fun `retract compound with variable`() {
val predicate = Predicate(listOf( val predicate = Predicate(
Fact(Structure(Atom("a"), listOf(Atom("b")))), listOf(
Fact(Structure(Atom("a"), listOf(Atom("c")))), Fact(Structure(Atom("a"), listOf(Atom("b")))),
Fact(Structure(Atom("a"), listOf(Atom("d")))) Fact(Structure(Atom("a"), listOf(Atom("c")))),
)) Fact(Structure(Atom("a"), listOf(Atom("d"))))
), dynamic = true
)
Program.db.load(predicate) Program.db.load(predicate)
val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList() val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList()
@ -289,4 +294,45 @@ class DatabaseOperatorsTests {
assertEquals(Atom("bob"), result2[Variable("X")], "Expected bob") assertEquals(Atom("bob"), result2[Variable("X")], "Expected bob")
assertEquals(Atom("sushi"), result2[Variable("Y")], "Expected sushi") assertEquals(Atom("sushi"), result2[Variable("Y")], "Expected sushi")
} }
@Test
fun `retract all`() {
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"))))
), dynamic = true
)
Program.db.load(predicate)
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")
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")
}
@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"
assertFalse(predicateFunctor in Program.db.predicates, "Expected predicate to not exist before")
val retractAll = RetractAll(Structure(Atom(predicateName), listOf(Variable("X"))))
val result = retractAll.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
assertTrue(predicateFunctor in Program.db.predicates, "Expected predicate to exist after")
assertTrue(Program.db.predicates[predicateFunctor]!!.dynamic, "Expected predicate to be dynamic")
}
} }