Call & Ignore

This commit is contained in:
Tibo De Peuter 2025-05-09 08:36:11 +02:00
parent 9b454a9669
commit 5bfeb96176
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
7 changed files with 118 additions and 28 deletions

View file

@ -3,6 +3,7 @@ package interpreter
import io.Logger import io.Logger
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.lists.List as PList
import prolog.ast.logic.Clause import prolog.ast.logic.Clause
import prolog.ast.logic.Fact import prolog.ast.logic.Fact
import prolog.ast.logic.LogicOperand import prolog.ast.logic.LogicOperand
@ -50,9 +51,9 @@ open class Preprocessor {
protected open fun preprocess(term: Term, nested: Boolean = false): Term { protected open fun preprocess(term: Term, nested: Boolean = false): Term {
// TODO Remove hardcoding by storing the functors as constants in operators? // TODO Remove hardcoding by storing the functors as constants in operators?
val prepped = when { val prepped = when (term) {
term == Variable("_") -> AnonymousVariable.create() Variable("_") -> AnonymousVariable.create()
term is Atom || term is Structure -> { is Atom, is Structure -> {
// Preprocess the arguments first to recognize builtins // Preprocess the arguments first to recognize builtins
val args = if (term is Structure) { val args = if (term is Structure) {
term.arguments.map { preprocess(it, nested = true) } term.arguments.map { preprocess(it, nested = true) }
@ -149,6 +150,13 @@ open class Preprocessor {
Functor.of("nl/0") -> Nl Functor.of("nl/0") -> Nl
Functor.of("read/1") -> Read(args[0]) Functor.of("read/1") -> Read(args[0])
// Lists
Functor.of("member/2") -> Member(args[0], args[1] as PList)
// Meta
Functor.of("call/1") -> Call(args[0] as Goal)
Functor.of("ignore/1") -> Ignore(args[0] as Goal)
// Other // Other
Functor.of("initialization/1") -> Initialization(args[0] as Goal) Functor.of("initialization/1") -> Initialization(args[0] as Goal)
Functor.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal) Functor.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal)

View file

@ -114,7 +114,20 @@ class Conjunction(val left: LogicOperand, private val right: LogicOperand) :
open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) : open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) :
LogicOperator(Atom(";"), left, right) { LogicOperator(Atom(";"), left, right) {
override fun satisfy(subs: Substitutions): Answers = sequence { override fun satisfy(subs: Substitutions): Answers = sequence {
yieldAll(left.satisfy(subs)) left.satisfy(subs).forEach { left ->
left.fold(
onSuccess = { leftSubs ->
yield(Result.success(leftSubs))
},
onFailure = { failure ->
if (failure is AppliedCut) {
val leftSubs = failure.subs
yield(Result.failure(AppliedCut(leftSubs)))
return@sequence
}
}
)
}
yieldAll(right.satisfy(subs)) yieldAll(right.satisfy(subs))
} }

View file

@ -10,15 +10,12 @@ import prolog.ast.terms.Operator
import prolog.ast.terms.Term import prolog.ast.terms.Term
class Member(private val element: Term, private val list: List) : Operator(Atom("member"), element, list) { class Member(private val element: Term, private val list: List) : Operator(Atom("member"), element, list) {
private var solution: Operator = if (list is Empty) { private var solution: Operator = when (list) {
Unify(element, list) is Empty -> Disjunction(Fail, Fail)
} else if (list is Cons) { is Cons -> Disjunction(
Disjunction(
Unify(element, list.head), Unify(element, list.head),
Member(element, list.tail) Member(element, list.tail)
) )
} else {
throw IllegalArgumentException("Invalid list type: ${list::class.simpleName}")
} }
override fun satisfy(subs: Substitutions): Answers = solution.satisfy(subs) override fun satisfy(subs: Substitutions): Answers = solution.satisfy(subs)

View file

@ -0,0 +1,39 @@
package prolog.builtins
import prolog.Answers
import prolog.Substitutions
import prolog.ast.terms.Atom
import prolog.ast.terms.Goal
import prolog.ast.terms.Operator
import prolog.flags.AppliedCut
class Call(private val goal: Goal) : Operator(Atom("call"), null, goal) {
override fun satisfy(subs: Substitutions): Answers = goal.satisfy(subs)
}
/**
* Calls [Goal] once, but succeeds, regardless of whether Goal succeeded or not.
*/
class Ignore(goal: Goal) : Operator(Atom("ignore"), null, goal) {
private val disjunction = Disjunction(
Conjunction(Call(goal), Cut()),
True
)
override fun satisfy(subs: Substitutions): Answers = sequence {
disjunction.satisfy(subs).forEach { result ->
result.fold(
onSuccess = { newSubs ->
yield(Result.success(newSubs))
},
onFailure = { failure ->
if (failure is AppliedCut && failure.subs != null) {
yield(Result.success(failure.subs))
} else {
yield(Result.failure(failure))
}
}
)
}
}
}

View file

@ -7,12 +7,11 @@ import prolog.ast.arithmetic.Expression
import prolog.ast.arithmetic.Float import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.arithmetic.Number import prolog.ast.arithmetic.Number
import prolog.ast.lists.List
import prolog.ast.lists.List.Cons import prolog.ast.lists.List.Cons
import prolog.ast.lists.List.Empty import prolog.ast.lists.List.Empty
import prolog.ast.logic.Fact import prolog.ast.logic.Fact
import prolog.ast.logic.LogicOperator
import prolog.ast.terms.* import prolog.ast.terms.*
import prolog.ast.lists.List as PList
// 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 {
@ -29,16 +28,8 @@ fun applySubstitution(term: Term, subs: Substitutions): Term = when {
else -> term else -> term
} }
//TODO Combine with the other applySubstitution function fun applySubstitution(expr: Expression, subs: Substitutions): Expression {
fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when { return applySubstitution(expr as Term, subs) as Expression
variable(expr, emptyMap()) -> applySubstitution(expr as Term, subs) as Expression
atomic(expr, subs) -> expr
expr is LogicOperator -> {
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
expr
}
else -> expr
} }
// Check if a variable occurs in a term // Check if a variable occurs in a term
@ -90,7 +81,7 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
} }
} }
t1 is List && t2 is List -> { t1 is PList && t2 is PList -> {
if (equivalent(t1, t2, subs)) { if (equivalent(t1, t2, subs)) {
yield(Result.success(emptyMap())) yield(Result.success(emptyMap()))
} else if (t1.size == t2.size) { } else if (t1.size == t2.size) {
@ -120,8 +111,8 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
} }
private fun unifyArgs( private fun unifyArgs(
args1: kotlin.collections.List<Argument>, args1: List<Argument>,
args2: kotlin.collections.List<Argument>, args2: List<Argument>,
subs: Substitutions subs: Substitutions
): Answers = sequence { ): Answers = sequence {
// Using the current subs, unify the first argument of each list // Using the current subs, unify the first argument of each list
@ -171,7 +162,7 @@ fun equivalent(term1: Term, term2: Term, subs: Substitutions): Boolean {
term1 is Variable && term2 is Variable -> term1 == term2 term1 is Variable && term2 is Variable -> term1 == term2
term1 is Variable -> term1 in subs && equivalent(subs[term1]!!, term2, subs) term1 is Variable -> term1 in subs && equivalent(subs[term1]!!, term2, subs)
term2 is Variable -> term2 in subs && equivalent(subs[term2]!!, term1, subs) term2 is Variable -> term2 in subs && equivalent(subs[term2]!!, term1, subs)
term1 is List && term2 is List -> { term1 is PList && term2 is PList -> {
if (term1.isEmpty() && term2.isEmpty()) { if (term1.isEmpty() && term2.isEmpty()) {
true true
} else if (term1.isEmpty() || term2.isEmpty()) { } else if (term1.isEmpty() || term2.isEmpty()) {

View file

@ -6,6 +6,7 @@ import io.Terminal
import parser.ReplParser import parser.ReplParser
import prolog.Answer import prolog.Answer
import prolog.Answers import prolog.Answers
import prolog.flags.AppliedCut
class Repl { class Repl {
private val io = Terminal() private val io = Terminal()
@ -101,8 +102,15 @@ class Repl {
} }
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" } return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
}, },
onFailure = { onFailure = { failure ->
return "ERROR: ${it.message}" if (failure is AppliedCut) {
if (failure.subs != null) {
return prettyPrint(Result.success(failure.subs))
}
return "false."
}
return "ERROR: ${failure.message}"
} }
) )
} }

View file

@ -0,0 +1,34 @@
package prolog.builtins
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import prolog.ast.arithmetic.Integer
import prolog.ast.lists.List.Cons
import prolog.ast.lists.List.Empty
import prolog.ast.terms.Variable
class MetaOperatorsTests {
@Test
fun `ignore of failing goal succeeds`() {
val goal = Member(Integer(4), Cons(Integer(1), Cons(Integer(2), Cons(Integer(3), Empty))))
val result = Ignore(goal).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Should return one result")
assertTrue(result[0].isSuccess, "Result should be successful")
assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitutions")
}
@Test
fun `ignore of succeeding goal returns first solution`() {
val goal = Member(Variable("X"), Cons(Integer(1), Cons(Integer(2), Cons(Integer(3), Empty))))
val result = Ignore(goal).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Should return one result")
assertTrue(result[0].isSuccess, "Result should be successful")
val subs = result[0].getOrNull()!!
assertEquals(Integer(1), subs[Variable("X")], "Expected first solution to be 1")
}
}