Checkpoint

This commit is contained in:
Tibo De Peuter 2025-04-11 19:27:01 +02:00
parent e3c84e1761
commit e73e5cbfc8
32 changed files with 1354 additions and 92 deletions

View file

@ -2,12 +2,10 @@ package prolog
import prolog.logic.Substituted
import prolog.ast.logic.Clause
import prolog.ast.logic.Fact
import prolog.ast.logic.Predicate
import prolog.ast.logic.Resolvent
import prolog.ast.terms.Functor
import prolog.ast.terms.Goal
import prolog.builtins.True
typealias Database = Program
@ -24,7 +22,6 @@ object Program: Resolvent {
private fun setup() {
// Initialize the program with built-in predicates
load(listOf(
Fact(True())
))
}

View file

@ -0,0 +1,12 @@
package prolog.ast.arithmetic
import prolog.ast.terms.Atom
import prolog.ast.terms.Operator
import prolog.ast.terms.Term
import prolog.logic.Substituted
abstract class ArithmeticOperator(symbol: Atom, leftOperand: Expression, rightOperand: Expression) :
Operator(symbol, leftOperand, rightOperand), Expression {
// Operators should overload the evaluate method to perform the operation
abstract override fun evaluate(subs: Substituted): Pair<Term, Substituted>
}

View file

@ -0,0 +1,5 @@
package prolog.ast.arithmetic
import prolog.ast.terms.Term
interface Expression : Term

View file

@ -37,8 +37,8 @@ abstract class Clause(private val head: Head, private val body: Body) : Resolven
override fun toString(): String {
return when {
body == True() -> head.toString()
else -> "$head :- $body"
body is True -> head.toString()
else -> "$head :- $body"
}
}
}

View file

@ -3,4 +3,4 @@ package prolog.ast.logic
import prolog.ast.terms.Head
import prolog.builtins.True
class Fact(head: Head) : Clause(head, True())
class Fact(head: Head) : Clause(head, True)

View file

@ -0,0 +1,5 @@
package prolog.ast.logic
import prolog.ast.terms.Operand
abstract class LogicOperand : Operand, Provable

View file

@ -0,0 +1,13 @@
package prolog.ast.logic
import prolog.ast.terms.Atom
import prolog.ast.terms.Operator
import prolog.logic.Substituted
abstract class LogicOperator(
symbol: Atom,
leftOperand: LogicOperand? = null,
rightOperand: LogicOperand
) : Operator(symbol, leftOperand, rightOperand), Provable {
abstract override fun prove(subs: Substituted): Sequence<Substituted>
}

View file

@ -9,6 +9,11 @@ open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = unifyLazy(goal, this, subs)
override fun evaluate(subs: Substituted): Pair<Term, Substituted> = Pair(this, emptyMap())
/**
* See also [SWI Prolog Standard Order of Terms](https://www.swi-prolog.org/pldoc/man?section=standardorder)
*/
override fun compareTo(other: Term): Int {
return when (other) {
is Variable -> 1

View file

@ -1,7 +1,7 @@
package prolog.ast.terms
import prolog.Program
import prolog.ast.logic.Provable
import prolog.ast.logic.LogicOperand
import prolog.logic.Substituted
/**
@ -11,7 +11,7 @@ import prolog.logic.Substituted
* A goal either [succeeds][prolog.builtins.True], in which case the variables in the compound terms have a binding,
* or it fails if Prolog fails to prove it.
*/
abstract class Goal : Term, Provable {
abstract class Goal : LogicOperand(), Term {
abstract val functor: Functor
override fun prove(subs: Substituted): Sequence<Substituted> = Program.solve(this, subs)

View file

@ -0,0 +1,32 @@
package prolog.ast.terms
import prolog.ast.arithmetic.Expression
import prolog.logic.Substituted
data class Integer(val value: Int): Term, Expression {
/**
* See also [SWI Prolog Standard Order of Terms](https://www.swi-prolog.org/pldoc/man?section=standardorder)
*/
override fun compareTo(other: Term): Int {
return when (other) {
is Variable -> 1
is Integer -> value.compareTo(other.value)
else -> -1
}
}
// Integers are already evaluated
override fun evaluate(subs: Substituted): Pair<Term, Substituted> = Pair(this, emptyMap())
override fun toString(): String {
return value.toString()
}
operator fun plus(other: Integer): Integer {
return Integer(value + other.value)
}
operator fun minus(other: Integer): Integer {
return Integer(value - other.value)
}
}

View file

@ -1,21 +1,16 @@
package prolog.ast.terms
import prolog.ast.logic.Provable
import prolog.logic.Substituted
typealias Operand = Term
abstract class Operator(
private val symbol: Atom,
val leftOperand: Operand? = null,
val rightOperand: Operand
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Provable {
abstract override fun prove(subs: Substituted): Sequence<Substituted>
private val leftOperand: Operand? = null,
private val rightOperand: Operand
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)) {
override fun toString(): String {
return when (leftOperand) {
null -> "${symbol.name} $rightOperand"
else -> "$leftOperand ${symbol.name} $rightOperand"
else -> "($leftOperand ${symbol.name} $rightOperand)"
}
}
}
typealias Operand = Goal

View file

@ -1,7 +1,7 @@
package prolog.ast.terms
import prolog.ast.logic.Resolvent
import prolog.builtins.equivalent
import prolog.logic.equivalent
import prolog.logic.Substituted
import prolog.logic.unifyLazy
@ -9,13 +9,19 @@ typealias Argument = Term
typealias CompoundTerm = Structure
open class Structure(val name: Atom, val arguments: List<Argument>) : Goal(), Head, Body, Resolvent {
open class Structure(val name: Atom, var arguments: List<Argument>) : Goal(), Head, Body, Resolvent {
override val functor: Functor = "${name.name}/${arguments.size}"
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> {
return unifyLazy(goal, this, subs)
}
// A structure does not need to be evaluated, so return an empty sequence.
override fun evaluate(subs: Substituted): Pair<Term, Substituted> = Pair(this, emptyMap())
/**
* See also [SWI Prolog Standard Order of Terms](https://www.swi-prolog.org/pldoc/man?section=standardorder)
*/
override fun compareTo(other: Term): Int {
when (other) {
is Structure -> {

View file

@ -1,9 +1,16 @@
package prolog.ast.terms
import prolog.logic.Substituted
/**
* Value in Prolog.
*
* A [Term] is either a [Variable], [Atom], integer, float or [CompoundTerm].
* In addition, SWI-Prolog also defines the type string.
*/
interface Term : Comparable<Term>
interface Term : Comparable<Term> {
/**
* Returns the term that this expression evaluates to. (All the way down.)
*/
fun evaluate(subs: Substituted): Pair<Term, Substituted>
}

View file

@ -1,8 +1,10 @@
package prolog.ast.terms
import prolog.ast.arithmetic.Expression
import prolog.logic.Substituted
import java.util.*
data class Variable(val name: String) : Term {
data class Variable(val name: String) : Term, Expression {
private var alias: Optional<Term> = Optional.empty()
fun alias(): Optional<Term> {
@ -21,6 +23,16 @@ data class Variable(val name: String) : Term {
alias = Optional.empty()
}
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
// If the variable is bound, return the value of the binding
// If the variable is not bound, return the variable itself
return if (alias.isPresent) {
alias.get().evaluate(subs)
} else {
Pair(this, emptyMap())
}
}
override fun compareTo(other: Term): Int {
return when (other) {
is Variable -> name.compareTo(other.name)

View file

@ -1 +0,0 @@
package prolog.builtins

View file

@ -0,0 +1,152 @@
package prolog.builtins
import prolog.ast.arithmetic.ArithmeticOperator
import prolog.ast.arithmetic.Expression
import prolog.ast.logic.LogicOperand
import prolog.ast.logic.LogicOperator
import prolog.ast.logic.Provable
import prolog.ast.terms.*
import prolog.logic.*
// TODO >
// TODO <
// TODO =<
// TODO >=
/**
* True if expression Expr1 evaluates to a number non-equal to Expr2.
*/
class EvaluatesToDifferent(private val left: Expression, private val right: Expression) :
ArithmeticOperator(Atom("=\\="), left, right) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
val t1 = left.evaluate(subs)
val t2 = right.evaluate(subs)
// Should both be instantiated
if (!atomic(t1.first) || !atomic(t2.first)) {
throw IllegalArgumentException("Both operands must be instantiated")
}
return if (equivalent(t1.first, t2.first)) {
Pair(False, emptyMap())
} else {
Pair(True, t1.second + t2.second)
}
}
}
/**
* True if Expr1 evaluates to a number equal to Expr2.
*/
class EvaluatesTo(private val left: Expression, private val right: Expression) :
ArithmeticOperator(Atom("=:="), left, right) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
val t1 = left.evaluate(subs)
val t2 = right.evaluate(subs)
// Should both be instantiated
if (!atomic(t1.first) || !atomic(t2.first)) {
throw IllegalArgumentException("Both operands must be instantiated")
}
return if (equivalent(t1.first, t2.first)) {
Pair(True, t1.second + t2.second)
} else {
Pair(False, emptyMap())
}
}
}
/**
* True when Number is the value to which Expr evaluates.
*/
class Is(private val left: Expression, private val right: Expression) :
Operator(Atom("is"), left, right), Provable {
override fun prove(subs: Substituted): Sequence<Substituted> {
val t1 = left.evaluate(subs)
val t2 = right.evaluate(subs)
if (!atomic(t2.first)) {
throw IllegalArgumentException("Arguments are not sufficiently instantiated")
}
return unifyLazy(t1.first, t2.first, subs).map{ it + t1.second + t2.second }
}
}
/**
* Result = -Expr
*/
class Negate(operand: Expression) : Subtract(Integer(0), operand)
/**
* Result = Expr
*/
class Positive(operand: Expression) : Add(Integer(0), operand)
/**
* Result = Expr1 + Expr2
*/
open class Add(private val expr1: Expression, private val expr2: Expression) :
ArithmeticOperator(Atom("+"), expr1, expr2) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
val result = Variable("Result")
val map = plus(expr1, expr2, result, subs)
return result.evaluate(map.first())
}
}
/**
* Result = Expr1 - Expr2
*/
open class Subtract(private val expr1: Expression, private val expr2: Expression) :
ArithmeticOperator(Atom("-"), expr1, expr2) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
val result = Variable("Result")
val map = plus(expr2, result, expr1, subs)
return result.evaluate(map.first())
}
}
// TODO Expr * Expr
/**
* Result = Expr1 * Expr2
*/
class Multiply(private val expr1: Expression, private val expr2: Expression) :
ArithmeticOperator(Atom("*"), expr1, expr2) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
val result = Variable("Result")
val map = mul(expr1, expr2, result, subs)
return result.evaluate(map.first())
}
}
// TODO Expr / Expr
// TODO Expr mod Expr
// TODO Expr rem Expr
class Between(private val expr1: Expression, private val expr2: Expression, private val expr3: Expression) :
Operator(Atom("between"), expr1, expr2) {
override fun prove(subs: Substituted): Sequence<Substituted> {
val e1 = expr1.evaluate(subs)
val e2 = expr2.evaluate(subs)
val e3 = expr3.evaluate(subs)
require(e1.first is Integer && e2.first is Integer) { "Arguments must be integers" }
val v1 = e1.first as Integer
val v2 = e2.first as Integer
return if (variable(e3.first)) {
between(v1, v2, e3.first as Variable).map { it + e1.second + e2.second }
} else {
between(v1, v2, e3.first as Integer).map { it + e1.second + e2.second }
}
}
}

View file

@ -1,12 +1,16 @@
package prolog.builtins
import prolog.ast.terms.*
import prolog.ast.logic.LogicOperand
import prolog.ast.terms.Atom
import prolog.ast.terms.Body
import prolog.ast.terms.Goal
import prolog.ast.logic.LogicOperator
import prolog.logic.Substituted
/**
* Always fail.
*/
class Fail : Atom("fail"), Body {
object Fail : Atom("fail"), Body {
override fun prove(subs: Substituted): Sequence<Substituted> = emptySequence()
}
@ -18,7 +22,7 @@ typealias False = Fail
/**
* Always succeed.
*/
class True : Atom("true"), Body {
object True : Atom("true"), Body {
override fun prove(subs: Substituted): Sequence<Substituted> = sequenceOf(emptyMap())
}
@ -29,16 +33,12 @@ class True : Atom("true"), Body {
/**
* Conjunction (and). True if both Goal1 and Goal2 are true.
*/
class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(","), leftOperand, rightOperand) {
class Conjunction(private val left: LogicOperand, private val right: LogicOperand) : LogicOperator(Atom(","), left, right) {
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
if (leftOperand != null) {
leftOperand.prove(subs).forEach { left ->
rightOperand.prove(subs + left).forEach { right ->
yield(left + right)
}
left.prove(subs).forEach { left ->
right.prove(subs + left).forEach { right ->
yield(left + right)
}
} else {
yieldAll(rightOperand.prove(subs))
}
}
}
@ -46,17 +46,16 @@ class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom("
/**
* Disjunction (or). True if either Goal1 or Goal2 succeeds.
*/
open class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) {
open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) :
LogicOperator(Atom(";"), left, right) {
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
if (leftOperand != null) {
yieldAll(leftOperand.prove(subs))
}
yieldAll(rightOperand.prove(subs))
yieldAll(left.prove(subs))
yieldAll(right.prove(subs))
}
}
@Deprecated("Use Disjunction instead")
class Bar(leftOperand: Operand, rightOperand: Operand) : Disjunction(leftOperand, rightOperand)
class Bar(leftOperand: LogicOperand, rightOperand: LogicOperand) : Disjunction(leftOperand, rightOperand)
// TODO ->
@ -65,10 +64,10 @@ class Bar(leftOperand: Operand, rightOperand: Operand) : Disjunction(leftOperand
/**
* True if 'Goal' cannot be proven.
*/
class Not(goal: Goal) : Operator(Atom("\\+"), rightOperand = goal) {
class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) {
override fun prove(subs: Substituted): Sequence<Substituted> {
// If the goal can be proven, return an empty sequence
if (rightOperand.prove(subs).toList().isNotEmpty()) {
if (goal.prove(subs).toList().isNotEmpty()) {
return emptySequence()
}
// If the goal cannot be proven, return a sequence with an empty map

View file

@ -1 +0,0 @@
package prolog.builtins

View file

@ -1,10 +1,10 @@
package prolog.builtins
import prolog.ast.logic.LogicOperand
import prolog.ast.terms.Atom
import prolog.ast.terms.Operand
import prolog.ast.terms.Operator
import prolog.ast.logic.LogicOperator
import prolog.logic.Substituted
class Query(rightOperand: Operand) : Operator(Atom("?-"), null, rightOperand) {
override fun prove(subs: Substituted): Sequence<Substituted> = rightOperand.prove(subs)
class Query(private val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {
override fun prove(subs: Substituted): Sequence<Substituted> = query.prove(subs)
}

View file

@ -1,24 +1,24 @@
/**
* Comparison and Unification of Terms
*
* [SWI Prolog Documentation](https://www.swi-prolog.org/pldoc/man?section=compare)
*/
package prolog.builtins
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Operator
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.logic.Substituted
import prolog.logic.applySubstitution
import prolog.logic.equivalent
/**
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
*/
fun equivalent(term1: Term, term2: Term): Boolean {
return when {
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 Atom && term2 is Atom -> term1.compareTo(term2) == 0
term1 is Structure && term2 is Structure -> term1.compareTo(term2) == 0
else -> false
class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) {
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs)
if (equivalent(t1, t2)) {
yield(emptyMap())
}
}
}
/**
*
*/

View file

@ -0,0 +1,150 @@
package prolog.logic
import prolog.ast.arithmetic.Expression
import prolog.ast.terms.Integer
import prolog.ast.terms.Variable
import prolog.builtins.Is
import java.util.*
/**
* Low and High are integers, High Low.
*
* 1. If Value is an integer, Low Value High.
* 2. When Value is a variable it is successively bound to all integers between Low and High.
* If High is inf or infinite between/3 is true iff Value Low, a feature that is particularly interesting for
* generating integers from a certain value.
*/
fun between(
low: Integer,
high: Integer,
value: Integer
): Sequence<Substituted> {
return if (value.value in low.value..high.value) {
sequenceOf(emptyMap())
} else {
emptySequence()
}
}
fun between(
low: Integer,
high: Integer,
variable: Variable
): Sequence<Substituted> {
return sequence {
for (i in low.value..high.value) {
yield(mapOf(variable to Integer(i)))
}
}
}
/**
* True if Int2 = Int1 + 1 and Int1 0.
*
* At least one of the arguments must be instantiated to a natural number.
*
* @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<Substituted> {
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 ->
val t1 = applySubstitution(term1, newSubs)
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
}
}
yield(newSubs)
}}
}
/**
* True if Int3 = Int1 + Int2.
*
* At least two of the three arguments must be instantiated to integers.
*/
fun plus(term1: Expression, term2: Expression, term3: Expression, subs: Substituted): Sequence<Substituted> = sequence {
val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs)
val t3 = applySubstitution(term3, subs)
// At least two arguments must be Integers
when {
nonvariable(t1) && nonvariable(t2) && nonvariable(t3) -> {
val e1 = t1.evaluate(subs)
val e2 = t2.evaluate(subs)
val e3 = t3.evaluate(subs)
val int3Value = e1.first as Integer + e2.first as Integer
if (int3Value == e3.first as Integer) {
yield(e1.second + e2.second + e3.second)
}
}
nonvariable(t1) && nonvariable(t2) && variable(t3) -> {
val e1 = t1.evaluate(subs)
val e2 = t2.evaluate(subs)
val int3Value = e1.first as Integer + e2.first as Integer
val int3 = t3 as Variable
int3.bind(int3Value)
yield(mapOf(int3 to int3Value) + e1.second + e2.second)
}
nonvariable(t1) && variable(t2) && nonvariable(t3) -> {
val e1 = t1.evaluate(subs)
val e3 = t3.evaluate(subs)
val int2Value = e3.first as Integer - e1.first as Integer
val int2 = t2 as Variable
int2.bind(int2Value)
yield(mapOf(int2 to int2Value) + e1.second + e3.second)
}
variable(t1) && nonvariable(t2) && nonvariable(t3) -> {
val e2 = t2.evaluate(subs)
val e3 = t3.evaluate(subs)
val int1Value = e3.first as Integer - e2.first as Integer
val int1 = t1 as Variable
int1.bind(int1Value)
yield(mapOf(int1 to int1Value) + e2.second + e3.second)
}
else -> {
throw IllegalArgumentException("At least two arguments must be instantiated to integers")
}
}
}
/**
* Recursive implementation of the multiply operator, logical programming-wise.
*/
fun mul(term1: Expression, term2: Expression, term3: Expression, subs: Substituted): Sequence<Substituted> = sequence {
val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs)
val t3 = applySubstitution(term3, subs)
// Base case
if (equivalent(t2, Integer(0))) {
yieldAll(Is(t3, Integer(0)).prove(subs))
}
// Recursive case
try {
val decremented = Variable("Decremented")
succ(decremented, t2, subs).forEach { decrementMap ->
val multiplied = Variable("Multiplied")
mul(t1, decremented, multiplied, subs + decrementMap).forEach { multipliedMap ->
yieldAll(plus(t1, multiplied, t3, subs + decrementMap + multipliedMap))
}
}
} catch(_: Exception) {
}
}
// TODO divmod
// TODO nth_integer_root_and_remainder

View file

@ -1,27 +1,33 @@
package prolog.logic
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.builtins.atomic
import prolog.builtins.compound
import prolog.builtins.equivalent
import prolog.builtins.variable
import prolog.ast.arithmetic.Expression
import prolog.ast.logic.LogicOperator
import prolog.ast.terms.*
import java.util.*
typealias Substituted = Map<Variable, Term>
// Apply substitutions to a term
private fun applySubstitution(term: Term, substitution: Substituted): Term = when {
variable(term) -> substitution[(term as Variable)] ?: term
fun applySubstitution(term: Term, subs: Substituted): Term = when {
variable(term) -> subs[(term as Variable)] ?: term
atomic(term) -> term
compound(term) -> {
val structure = term as Structure
Structure(structure.name, structure.arguments.map { applySubstitution(it, substitution) })
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
expr is LogicOperator -> {
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
expr
}
else -> expr
}
// Check if a variable occurs in a term
private fun occurs(variable: Variable, term: Term): Boolean = when {
variable(term) -> term == variable
@ -33,8 +39,8 @@ private fun occurs(variable: Variable, term: Term): Boolean = when {
else -> false
}
// Generate possible substitutions
private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
// Unify two terms with backtracking and lazy evaluation
fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs)
@ -59,7 +65,7 @@ private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted):
val structure2 = t2 as Structure
if (structure1.functor == structure2.functor) {
val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(subs) { acc, (arg1, arg2) ->
generateSubstitutions(arg1, arg2, acc).firstOrNull() ?: return@sequence
unifyLazy(arg1, arg2, acc).firstOrNull() ?: return@sequence
}
yield(newSubstitution)
}
@ -68,14 +74,6 @@ private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted):
}
}
// Unify two terms with backtracking and lazy evaluation
fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
generateSubstitutions(term1, term2, subs).forEach { newSubs ->
// Return the new substitution
yield(newSubs)
}
}
fun unify(term1: Term, term2: Term): Optional<Substituted> {
val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
return if (substitutions.isNotEmpty()) {
@ -84,3 +82,18 @@ fun unify(term1: Term, term2: Term): Optional<Substituted> {
Optional.empty()
}
}
/**
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
*/
fun equivalent(term1: Term, term2: Term): 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 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)
else -> false
}
}

View file

@ -1,4 +1,4 @@
package prolog.builtins
package prolog.logic
import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.Term
@ -37,5 +37,5 @@ fun variable(term: Term): Boolean {
return term.alias().isEmpty || term.alias().get() === term || variable(term.alias().get())
}
return false;
return false
}