This repository has been archived on 2025-09-23. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
2025LogProg-project-GhentPr.../src/prolog/builtins/controlOperators.kt
2025-05-09 14:43:20 +02:00

173 lines
6.5 KiB
Kotlin

package prolog.builtins
import prolog.Answers
import prolog.Substitutions
import prolog.ast.Database.Program
import prolog.ast.logic.LogicOperand
import prolog.ast.logic.LogicOperator
import prolog.ast.terms.Atom
import prolog.ast.terms.Body
import prolog.ast.terms.Goal
import prolog.flags.AppliedCut
import prolog.flags.AppliedShift
import prolog.logic.applySubstitution
import prolog.logic.numbervars
/**
* Always fail.
*/
object Fail : Atom("fail"), Body {
override fun satisfy(subs: Substitutions): Answers = emptySequence()
override fun applySubstitution(subs: Substitutions): Fail = this
}
/**
* Same as fail, but the name has a more declarative connotation.
*/
typealias False = Fail
/**
* Always succeed.
*/
object True : Atom("true"), Body {
override fun satisfy(subs: Substitutions): Answers = sequenceOf(Result.success(emptyMap()))
override fun applySubstitution(subs: Substitutions): True = this
}
// TODO Repeat/0
class Cut() : Atom("!") {
override fun satisfy(subs: Substitutions): Answers {
return sequenceOf(Result.failure(AppliedCut(emptyMap())))
}
override fun applySubstitution(subs: Substitutions): Cut = this
}
/**
* Conjunction (and). True if both Goal1 and Goal2 are true.
*/
open class Conjunction(val left: LogicOperand, private val right: LogicOperand) :
LogicOperator(Atom(","), left, right) {
override fun satisfy(subs: Substitutions): Answers = sequence {
fun satisfyRight(leftSubs: Substitutions): Answers = sequence {
right.satisfy(subs + leftSubs).forEach { right ->
right.fold(
// If the right part succeeds, yield the result with the left substitutions
onSuccess = { rightSubs ->
yield(Result.success(leftSubs + rightSubs))
},
onFailure = { exception ->
// If the right part fails, check if it's a cut
if (exception is AppliedCut) {
// If it's a cut, yield the result with the left substitutions
val newSubs = if (exception.subs != null) leftSubs + exception.subs else null
yield(Result.failure(AppliedCut(newSubs)))
return@sequence
}
// If it's not a cut, yield the failure
yield(Result.failure(exception))
}
)
}
}
fun findNextCutSolution(appliedCut: AppliedCut): Answers = sequence {
val leftSubs = appliedCut.subs
right.satisfy(subs + (appliedCut.subs!!)).firstOrNull()?.map { rightSubs ->
// If the right part succeeds, yield the result with the left substitutions
yield(Result.success(leftSubs + rightSubs))
return@sequence
} ?: yield(Result.failure(AppliedCut()))
}
// Satisfy the left part first, which either succeeds or fails
left.satisfy(subs).forEach { left ->
left.fold(
// If it succeeds, satisfy the right part with the updated substitutions and return all results
onSuccess = { leftSubs -> yieldAll(satisfyRight(leftSubs)) },
// If it fails, check these conditions:
onFailure = { exception ->
when (exception) {
// 1. If the left part is a cut, satisfy the right part ONCE, and stop searching for more solutions
is AppliedCut -> yieldAll(findNextCutSolution(exception))
// 2. If the left part is a shift, we need to check if the right part can be satisfied
is AppliedShift -> {
if (exception.cont == null) {
// Pass the right of our disjunction as the continuation
val newShift = AppliedShift(exception.subs, exception.ball, right as Goal)
yield(Result.failure(newShift))
} else {
// Satisfy the right part with the updated substitutions
yieldAll(satisfyRight(exception.subs))
}
}
// 3. Any other failure should be returned as is
else -> yield(Result.failure(exception))
}
}
)
}
}
override fun applySubstitution(subs: Substitutions): Conjunction = Conjunction(
applySubstitution(left, subs) as LogicOperand,
applySubstitution(right, subs) as LogicOperand
)
}
/**
* Disjunction (or). True if either Goal1 or Goal2 succeeds.
*/
open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) :
LogicOperator(Atom(";"), left, right) {
override fun satisfy(subs: Substitutions): Answers = sequence {
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))
}
override fun applySubstitution(subs: Substitutions): Disjunction = Disjunction(
applySubstitution(left, subs) as LogicOperand,
applySubstitution(right, subs) as LogicOperand
)
}
@Deprecated("Use Disjunction instead")
class Bar(leftOperand: LogicOperand, rightOperand: LogicOperand) : Disjunction(leftOperand, rightOperand)
// TODO ->
// TODO *->
/**
* True if 'Goal' cannot be proven.
*/
class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) {
override fun satisfy(subs: Substitutions): Answers {
// If the goal can be proven, return an empty sequence
val goalResults = goal.satisfy(subs).iterator()
if (goalResults.hasNext()) {
return emptySequence()
}
// If the goal cannot be proven, return a sequence with an empty map
return sequenceOf(Result.success(emptyMap()))
}
override fun applySubstitution(subs: Substitutions): Not = Not(applySubstitution(goal, subs) as Goal)
}