refactor: Rework

This commit is contained in:
Tibo De Peuter 2025-04-15 12:32:59 +02:00
parent ac55ed4c64
commit 6469dd6ced
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
34 changed files with 593 additions and 552 deletions

View file

@ -1,10 +1,12 @@
package prolog.logic
import prolog.Answers
import prolog.Substitution
import prolog.Substitutions
import prolog.ast.arithmetic.Expression
import prolog.ast.terms.Integer
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.builtins.Is
import java.util.*
/**
* Low and High are integers, High Low.
@ -18,9 +20,9 @@ fun between(
low: Integer,
high: Integer,
value: Integer
): Sequence<Substituted> {
): Answers {
return if (value.value in low.value..high.value) {
sequenceOf(emptyMap())
sequenceOf(Result.success(emptyMap()))
} else {
emptySequence()
}
@ -30,10 +32,10 @@ fun between(
low: Integer,
high: Integer,
variable: Variable
): Sequence<Substituted> {
): Answers {
return sequence {
for (i in low.value..high.value) {
yield(mapOf(variable to Integer(i)))
yield(Result.success(mapOf(variable to Integer(i))))
}
}
}
@ -46,24 +48,26 @@ fun between(
* @throws IllegalArgumentException the domain error not_less_than_zero if called with a negative integer.
* E.g. succ(X, 0) fails silently and succ(X, -1) raises a domain error.125
*/
fun succ(term1: Expression, term2: Expression, subs: Substituted): Sequence<Result<Substituted>> {
fun succ(term1: Expression, term2: Expression, subs: Substitutions): Answers {
if (term2 is Integer) {
require(term2.value >= 0) { "Domain error: not_less_than_zero" }
}
val result = plus(term1, Integer(1), term2, subs)
// If term1 is a variable, we need to check if it is bound to a negative integer
return sequence {
result.forEach { newSubs ->
if (newSubs.isSuccess) {
val t1 = applySubstitution(term1, newSubs.getOrNull()!!)
if (t1 is Variable && t1.alias().isPresent) {
val e1 = t1.evaluate(subs)
if (e1.first is Integer && (e1.first as Integer).value < 0) {
return@sequence
plus(term1, Integer(1), term2, subs).forEach {
it.fold(
onSuccess = { result ->
val t1 = applySubstitution(term1, result)
if (t1 in result) {
val e1 = t1.simplify(result)
if (e1.to is Integer && e1.to.value < 0) {
return@sequence
}
}
}
}
yield(newSubs)
yield(Result.success(result))
},
onFailure = { yield(Result.success(emptyMap())) }
)
}
}
}
@ -73,55 +77,56 @@ fun succ(term1: Expression, term2: Expression, subs: Substituted): Sequence<Resu
*
* At least two of the three arguments must be instantiated to integers.
*/
fun plus(term1: Expression, term2: Expression, term3: Expression, subs: Substituted): Sequence<Result<Substituted>> =
fun plus(term1: Expression, term2: Expression, term3: Expression, subs: Substitutions): Answers =
operate(term1, term2, term3, subs, Integer::plus, Integer::minus)
fun mul(term1: Expression, term2: Expression, term3: Expression, subs: Substituted): Sequence<Result<Substituted>> =
fun mul(term1: Expression, term2: Expression, term3: Expression, subs: Substitutions): Answers =
operate(term1, term2, term3, subs, Integer::times, Integer::div)
fun operate(
term1: Expression,
term2: Expression,
term3: Expression,
subs: Substituted,
subs: Substitutions,
op: (Integer, Integer) -> Integer,
inverseOp: (Integer, Integer) -> Integer
): Sequence<Result<Substituted>> = sequence {
): Answers = sequence {
val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs)
val t3 = applySubstitution(term3, subs)
when {
nonvariable(t1) && nonvariable(t2) && nonvariable(t3) -> {
val e1 = t1.evaluate(subs)
val e2 = t2.evaluate(subs)
val e3 = t3.evaluate(subs)
nonvariable(t1, subs) && nonvariable(t2, subs) && nonvariable(t3, subs) -> {
val e1 = t1.simplify(subs)
val e2 = t2.simplify(subs)
val e3 = t3.simplify(subs)
val int3Value = op(e1.first as Integer, e2.first as Integer)
if (int3Value == e3.first as Integer) {
yield(Result.success(e1.second + e2.second + e3.second))
val int3Value = op(e1.to as Integer, e2.to as Integer)
if (int3Value == e3.to as Integer) {
val opSubs: Substitutions = listOfNotNull(e1.mapped, e2.mapped, e3.mapped)
.filter{ pair: Pair<Term, Term>? -> pair != null && !subs.contains(pair.first) }
.toMap()
yield(Result.success(opSubs))
}
}
nonvariable(t1) && nonvariable(t2) && variable(t3) -> {
val e1 = t1.evaluate(subs)
val e2 = t2.evaluate(subs)
nonvariable(t1, subs) && nonvariable(t2, subs) && variable(t3, subs) -> {
val e1 = t1.simplify(subs)
val e2 = t2.simplify(subs)
val int3Value = op(e1.first as Integer, e2.first as Integer)
val int3Value = op(e1.to as Integer, e2.to as Integer)
val int3 = t3 as Variable
int3.bind(int3Value)
yield(Result.success(mapOf(int3 to int3Value) + e1.second + e2.second))
yield(Result.success(mapOf(int3 to int3Value) + listOfNotNull(e1.mapped, e2.mapped)))
}
((nonvariable(t1) && variable(t2)) || (variable(t1) && nonvariable(t2))) && nonvariable(t3) -> {
val t = if (nonvariable(t1)) t2 else t1
val e = if (nonvariable(t1)) t1.evaluate(subs) else t2.evaluate(subs)
val e3 = t3.evaluate(subs)
((nonvariable(t1, subs) && variable(t2, subs)) || (variable(t1, subs) && nonvariable(t2, subs))) && nonvariable(t3, subs) -> {
val t = if (nonvariable(t1, subs)) t2 else t1
val e = if (nonvariable(t1, subs)) t1.simplify(subs) else t2.simplify(subs)
val e3 = t3.simplify(subs)
val value = inverseOp(e3.first as Integer, e.first as Integer)
val value = inverseOp(e3.to as Integer, e.to as Integer)
val int = t as Variable
int.bind(value)
yield(Result.success(mapOf(int to value) + e.second + e3.second))
yield(Result.success(mapOf(int to value) + listOfNotNull(e.mapped, e3.mapped)))
}
else -> {

View file

@ -1,26 +1,27 @@
package prolog.logic
import prolog.Answer
import prolog.Answers
import prolog.Substitutions
import prolog.ast.arithmetic.Expression
import prolog.ast.logic.LogicOperator
import prolog.ast.terms.*
import java.util.*
typealias Substituted = Map<Variable, Term>
import kotlin.NoSuchElementException
// Apply substitutions to a term
fun applySubstitution(term: Term, subs: Substituted): Term = when {
variable(term) -> subs[(term as Variable)] ?: term
atomic(term) -> term
compound(term) -> {
fun applySubstitution(term: Term, subs: Substitutions): Term = when {
variable(term, emptyMap()) -> subs[(term as Variable)] ?: term
atomic(term, subs) -> term
compound(term, subs) -> {
val structure = term as Structure
Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) })
}
else -> term
}
fun applySubstitution(expr: Expression, subs: Substituted): Expression = when {
variable(expr) -> applySubstitution(expr as Term, subs) as Expression
atomic(expr) -> expr
//TODO Combine with the other applySubstitution function
fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when {
variable(expr, subs) -> applySubstitution(expr as Term, subs) as Expression
atomic(expr, subs) -> expr
expr is LogicOperator -> {
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
expr
@ -29,71 +30,138 @@ fun applySubstitution(expr: Expression, subs: Substituted): Expression = when {
}
// Check if a variable occurs in a term
private fun occurs(variable: Variable, term: Term): Boolean = when {
variable(term) -> term == variable
atomic(term) -> false
compound(term) -> {
private fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean = when {
variable(term, subs) -> term == variable
atomic(term, subs) -> false
compound(term, subs) -> {
val structure = term as Structure
structure.arguments.any { occurs(variable, it) }
structure.arguments.any { occurs(variable, it, subs) }
}
else -> false
}
// Unify two terms with backtracking and lazy evaluation
fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence {
val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs)
when {
equivalent(t1, t2) -> yield(subs)
variable(t1) -> {
equivalent(t1, t2, subs) -> yield(Result.success(subs))
variable(t1, subs) -> {
val variable = t1 as Variable
if (!occurs(variable, t2)) {
variable.bind(t2)
yield(subs + (variable to t2))
if (!occurs(variable, t2, subs)) {
yield(Result.success(subs + (variable to t2)))
}
}
variable(t2) -> {
variable(t2, subs) -> {
val variable = t2 as Variable
if (!occurs(variable, t1)) {
variable.bind(t1)
yield(subs + (variable to t1))
if (!occurs(variable, t1, subs)) {
yield(Result.success(subs + (variable to t1)))
}
}
compound(t1) && compound(t2) -> {
compound(t1, subs) && compound(t2, subs) -> {
val structure1 = t1 as Structure
val structure2 = t2 as Structure
if (structure1.functor == structure2.functor) {
val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(subs) { acc, (arg1, arg2) ->
unifyLazy(arg1, arg2, acc).firstOrNull() ?: return@sequence
// Unify each argument at the same time, and yield the result
val args1 = structure1.arguments
val args2 = structure2.arguments
if (args1.size == args2.size) {
val results = args1.zip(args2).map { (arg1, arg2) ->
unifyLazy(arg1, arg2, subs)
}
// Combine the results of all unifications
val combinedResults = results.reduce { acc, result ->
acc.flatMap { a -> result.map { b ->
if (a.isSuccess && b.isSuccess) a.getOrThrow() + b.getOrThrow() else emptyMap()
} }.map { Result.success(it) }
}
yieldAll(combinedResults)
}
yield(newSubstitution)
}
}
else -> {}
}
}
fun unify(term1: Term, term2: Term): Optional<Substituted> {
fun unify(term1: Term, term2: Term): Answer {
val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
return if (substitutions.isNotEmpty()) {
Optional.of(substitutions.first())
substitutions.first()
} else {
Optional.empty()
Result.failure(NoSuchElementException())
}
}
/**
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
*/
fun equivalent(term1: Term, term2: Term): Boolean {
fun equivalent(term1: Term, term2: Term, subs: Substitutions): Boolean {
return when {
term1 is Atom && term2 is Atom -> term1.compareTo(term2) == 0
term1 is Structure && term2 is Structure -> term1.compareTo(term2) == 0
term1 is Integer && term2 is Integer -> term1.compareTo(term2) == 0
term1 is Atom && term2 is Atom -> compare(term1, term2, subs) == 0
term1 is Structure && term2 is Structure -> compare(term1, term2, subs) == 0
term1 is Integer && term2 is Integer -> compare(term1, term2, subs) == 0
term1 is Variable && term2 is Variable -> term1 == term2
term1 is Variable -> term1.alias().isPresent && equivalent(term1.alias().get(), term2)
term2 is Variable -> term2.alias().isPresent && equivalent(term2.alias().get(), term1)
term1 is Variable -> term1 in subs && equivalent(subs[term1]!!, term2, subs)
term2 is Variable -> term2 in subs && equivalent(subs[term2]!!, term1, subs)
else -> false
}
}
/**
* See also [SWI Prolog Standard Order of Terms](https://www.swi-prolog.org/pldoc/man?section=standardorder)
*/
fun compare(term1: Term, term2: Term, subs: Substitutions): Int {
val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs)
return when (t1) {
is Variable -> {
when (t2) {
is Variable -> t1.name.compareTo(t2.name)
is Integer -> -1
is Atom -> -1
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
is Integer -> {
when (t2) {
is Variable -> 1
is Integer -> t1.value.compareTo(t2.value)
is Atom -> -1
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
is Atom -> {
when (t2) {
is Variable -> 1
is Integer -> 1
is Atom -> t1.name.compareTo(t2.name)
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
is Structure -> {
when (t2) {
is Variable -> 1
is Integer -> 1
is Atom -> 1
is Structure -> {
val arityComparison = t1.arguments.size.compareTo(t2.arguments.size)
if (arityComparison != 0) return arityComparison
val nameComparison = t1.name.compareTo(t2.name)
if (nameComparison != 0) return nameComparison
t1.arguments.zip(t2.arguments).forEach { (arg1, arg2) ->
val argsComparison = equivalent(arg1, arg2, emptyMap())
if (!argsComparison) return compare(arg1, arg2, subs)
}
return 0
}
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}

View file

@ -1,5 +1,6 @@
package prolog.logic
import prolog.Substitutions
import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
@ -12,29 +13,29 @@ import prolog.ast.terms.Variable
* nonvar(Term),
* \+ compound(Term).
*/
fun atomic(term: Term): Boolean = nonvariable(term) && !compound(term)
fun atomic(term: Term, subs: Substitutions = emptyMap()): Boolean = nonvariable(term, subs) && !compound(term, subs)
/**
* True if [Term] is bound to a compound term.
* See also functor/3 =../2, compound_name_arity/3 and compound_name_arguments/3.
*/
fun compound(term: Term): Boolean {
fun compound(term: Term, subs: Substitutions = emptyMap()): Boolean {
val isCompound = term is CompoundTerm
val isVariableCompound = term is Variable && term.alias().isPresent && compound(term.alias().get())
val isVariableCompound = term is Variable && term in subs && compound(subs[term]!!, subs)
return isCompound || isVariableCompound
}
/**
* True if [Term] currently is not a free variable.
*/
fun nonvariable(term: Term): Boolean = !variable(term)
fun nonvariable(term: Term, subs: Substitutions = emptyMap()): Boolean = !variable(term, subs)
/**
* True if [Term] currently is a free variable.
*/
fun variable(term: Term): Boolean {
fun variable(term: Term, subs: Substitutions = emptyMap()): Boolean {
if (term is Variable) {
return term.alias().isEmpty || term.alias().get() === term || variable(term.alias().get())
return term !in subs || subs[term] === term || variable(subs[term]!!, subs)
}
return false