refactor: Rework
This commit is contained in:
parent
ac55ed4c64
commit
6469dd6ced
34 changed files with 593 additions and 552 deletions
|
@ -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 -> {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue