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,6 +1,5 @@
package prolog package prolog
import prolog.logic.Substituted
import prolog.ast.logic.Clause import prolog.ast.logic.Clause
import prolog.ast.logic.Predicate import prolog.ast.logic.Predicate
import prolog.ast.logic.Resolvent import prolog.ast.logic.Resolvent
@ -29,9 +28,9 @@ object Program: Resolvent {
* Queries the program with a goal. * Queries the program with a goal.
* @return true if the goal can be proven, false otherwise. * @return true if the goal can be proven, false otherwise.
*/ */
fun query(goal: Goal): Sequence<Substituted> = solve(goal, emptyMap()) fun query(goal: Goal): Answers = solve(goal, emptyMap())
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> { override fun solve(goal: Goal, subs: Substitutions): Answers {
val functor = goal.functor val functor = goal.functor
// If the predicate does not exist, return false // If the predicate does not exist, return false
val predicate = predicates[functor] ?: return emptySequence() val predicate = predicates[functor] ?: return emptySequence()

View file

@ -0,0 +1,11 @@
package prolog
import prolog.ast.terms.Term
abstract class Substitution(val from: Term, val to: Term) {
val mapped: Pair<Term, Term>? = if (from != to) from to to else null
override fun toString(): String = "$from -> $to"
}
typealias Substitutions = Map<Term, Term>
typealias Answer = Result<Substitutions>
typealias Answers = Sequence<Answer>

View file

@ -2,11 +2,6 @@ package prolog.ast.arithmetic
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Operator import prolog.ast.terms.Operator
import prolog.ast.terms.Term
import prolog.logic.Substituted
abstract class ArithmeticOperator(symbol: Atom, leftOperand: Expression, rightOperand: Expression) : abstract class ArithmeticOperator(symbol: Atom, leftOperand: Expression, rightOperand: Expression) :
Operator(symbol, leftOperand, 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

@ -1,5 +1,11 @@
package prolog.ast.arithmetic package prolog.ast.arithmetic
import prolog.Substitutions
import prolog.ast.terms.Term import prolog.ast.terms.Term
interface Expression : Term interface Expression : Term {
/**
* Returns the term that this expression evaluates to. (All the way down.)
*/
fun simplify(subs: Substitutions): Simplification
}

View file

@ -0,0 +1,6 @@
package prolog.ast.arithmetic
import prolog.Substitution
import prolog.ast.terms.Term
class Simplification(from: Expression, to: Term) : Substitution(from, to)

View file

@ -1,6 +1,7 @@
package prolog.ast.logic package prolog.ast.logic
import prolog.logic.Substituted import prolog.Answers
import prolog.Substitutions
import prolog.ast.terms.Body import prolog.ast.terms.Body
import prolog.ast.terms.Functor import prolog.ast.terms.Functor
import prolog.ast.terms.Goal import prolog.ast.terms.Goal
@ -19,26 +20,26 @@ import prolog.logic.unifyLazy
abstract class Clause(private val head: Head, private val body: Body) : Resolvent { abstract class Clause(private val head: Head, private val body: Body) : Resolvent {
val functor: Functor = head.functor val functor: Functor = head.functor
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = sequence { override fun solve (goal: Goal, subs: Substitutions): Answers = sequence {
// If the clause is a rule, unify the goal with the head and then try to prove the body. // If the clause is a rule, unify the goal with the head and then try to prove the body.
// Only if the body can be proven, the substitutions should be returned. // Only if the body can be proven, the substitutions should be returned.
// Do this in a lazy way. // Do this in a lazy way.
unifyLazy(goal, head, subs).forEach { newHeadSubs -> unifyLazy(goal, head, subs).forEach { headAnswer ->
// If the body can be proven, yield the (combined) substitutions headAnswer.map { newHeadSubs ->
body.prove(subs + newHeadSubs).forEach { newBodySubs -> // If the body can be proven, yield the (combined) substitutions
yield(newHeadSubs + newBodySubs) body.satisfy(subs + newHeadSubs).forEach { bodyAnswer ->
// Unbind the newly bound variables, so they can be reused. bodyAnswer.map { newBodySubs ->
newBodySubs.keys.forEach { it.unbind() } yield(Result.success(newHeadSubs + newBodySubs))
}
}
} }
// Unbind the newly bound variables, so they can be reused.
newHeadSubs.keys.forEach { it.unbind() }
} }
} }
override fun toString(): String { override fun toString(): String {
return when { return when {
body is True -> head.toString() body is True -> head.toString()
else -> "$head :- $body" else -> "$head :- $body"
} }
} }
} }

View file

@ -2,4 +2,4 @@ package prolog.ast.logic
import prolog.ast.terms.Operand import prolog.ast.terms.Operand
abstract class LogicOperand : Operand, Provable abstract class LogicOperand : Operand, Satisfiable

View file

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

View file

@ -1,6 +1,7 @@
package prolog.ast.logic package prolog.ast.logic
import prolog.logic.Substituted import prolog.Answers
import prolog.Substitutions
import prolog.ast.terms.Functor import prolog.ast.terms.Functor
import prolog.ast.terms.Goal import prolog.ast.terms.Goal
@ -48,7 +49,7 @@ class Predicate : Resolvent {
this.clauses.addAll(clauses) this.clauses.addAll(clauses)
} }
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = sequence { override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
require(goal.functor == functor) { "Goal functor does not match predicate functor" } require(goal.functor == functor) { "Goal functor does not match predicate functor" }
// Try to unify the goal with the clause // Try to unify the goal with the clause
// If the unification is successful, yield the substitutions // If the unification is successful, yield the substitutions

View file

@ -1,14 +0,0 @@
package prolog.ast.logic
import prolog.logic.Substituted
interface Provable {
/**
* Proves the current [Provable] instance.
*
* @return a sequence of [Substituted] instances representing the results of the proof.
* If the proof fails, an empty sequence is returned.
*/
fun prove(subs: Substituted): Sequence<Substituted>
}

View file

@ -1,6 +1,7 @@
package prolog.ast.logic package prolog.ast.logic
import prolog.logic.Substituted import prolog.Answers
import prolog.Substitutions
import prolog.ast.terms.Goal import prolog.ast.terms.Goal
/** /**
@ -13,5 +14,5 @@ interface Resolvent {
* @return A sequence of substitutions that can be applied to the goal to unify it with this resolvent. * @return A sequence of substitutions that can be applied to the goal to unify it with this resolvent.
* If the goal cannot be unified with this resolvent, an empty sequence is returned. * If the goal cannot be unified with this resolvent, an empty sequence is returned.
*/ */
fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> fun solve(goal: Goal, subs: Substitutions): Answers
} }

View file

@ -0,0 +1,14 @@
package prolog.ast.logic
import prolog.Answers
import prolog.Substitutions
interface Satisfiable {
/**
* Proves the current [Satisfiable] instance.
*
* @return a sequence of [Substitutions] instances representing the results of the proof.
* If the proof fails, an empty sequence is returned.
*/
fun satisfy(subs: Substitutions): Answers
}

View file

@ -1,27 +1,14 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.Answers
import prolog.Substitutions
import prolog.ast.logic.Resolvent import prolog.ast.logic.Resolvent
import prolog.logic.Substituted
import prolog.logic.unifyLazy import prolog.logic.unifyLazy
open class Atom(val name: String) : Goal(), Head, Body, Resolvent { open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
override val functor: Functor = "$name/_" override val functor: Functor = "$name/_"
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = unifyLazy(goal, this, subs) override fun solve(goal: Goal, subs: Substitutions): Answers = 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
is Atom -> name.compareTo(other.name)
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $this with $other")
}
}
override fun toString(): String { override fun toString(): String {
return name return name

View file

@ -1,5 +1,5 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.ast.logic.Provable import prolog.ast.logic.Satisfiable
interface Body : Provable interface Body : Satisfiable

View file

@ -1,8 +1,9 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.Answers
import prolog.Program import prolog.Program
import prolog.Substitutions
import prolog.ast.logic.LogicOperand import prolog.ast.logic.LogicOperand
import prolog.logic.Substituted
/** /**
* Ask the Prolog engine. * Ask the Prolog engine.
@ -14,5 +15,5 @@ import prolog.logic.Substituted
abstract class Goal : LogicOperand(), Term { abstract class Goal : LogicOperand(), Term {
abstract val functor: Functor abstract val functor: Functor
override fun prove(subs: Substituted): Sequence<Substituted> = Program.solve(this, subs) override fun satisfy(subs: Substitutions): Answers = Program.solve(this, subs)
} }

View file

@ -1,22 +1,12 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.Substitutions
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.logic.Substituted import prolog.ast.arithmetic.Simplification
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
}
}
data class Integer(val value: Int): Expression {
// Integers are already evaluated // Integers are already evaluated
override fun evaluate(subs: Substituted): Pair<Term, Substituted> = Pair(this, emptyMap()) override fun simplify(subs: Substitutions): Simplification = Simplification(this, this)
override fun toString(): String { override fun toString(): String {
return value.toString() return value.toString()

View file

@ -1,5 +1,7 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.ast.arithmetic.Expression
typealias Operand = Term typealias Operand = Term
abstract class Operator( abstract class Operator(

View file

@ -1,8 +1,8 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.Answers
import prolog.Substitutions
import prolog.ast.logic.Resolvent import prolog.ast.logic.Resolvent
import prolog.logic.equivalent
import prolog.logic.Substituted
import prolog.logic.unifyLazy import prolog.logic.unifyLazy
typealias Argument = Term typealias Argument = Term
@ -12,34 +12,10 @@ typealias CompoundTerm = Structure
open class Structure(val name: Atom, var 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 val functor: Functor = "${name.name}/${arguments.size}"
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> { override fun solve(goal: Goal, subs: Substitutions): Answers {
return unifyLazy(goal, this, subs) 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 -> {
val arityComparison = arguments.size.compareTo(other.arguments.size)
if (arityComparison != 0) return arityComparison
val nameComparison = name.compareTo(other.name)
if (nameComparison != 0) return nameComparison
arguments.zip(other.arguments).forEach { (arg1, arg2) ->
val argsComparison = equivalent(arg1, arg2)
if (!argsComparison) return arg1.compareTo(arg2)
}
return 0
}
// Structure is always greater than other terms
else -> return 1
}
}
override fun toString(): String { override fun toString(): String {
return when { return when {
arguments.isEmpty() -> name.name arguments.isEmpty() -> name.name

View file

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

View file

@ -1,50 +1,20 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.Substitutions
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.logic.Substituted import prolog.ast.arithmetic.Simplification
import java.util.*
data class Variable(val name: String) : Term, Expression { data class Variable(val name: String) : Term, Expression {
private var alias: Optional<Term> = Optional.empty() override fun simplify(subs: Substitutions): Simplification {
fun alias(): Optional<Term> {
return alias
}
fun bind(term: Term): Optional<Term> {
if (alias.isEmpty) {
alias = Optional.of(term)
}
return alias
}
fun unbind() {
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 bound, return the value of the binding
// If the variable is not bound, return the variable itself // If the variable is not bound, return the variable itself
return if (alias.isPresent) { var result = this as Term
alias.get().evaluate(subs) if (this in subs) {
} else { val boundTerm = subs[this]!!
Pair(this, emptyMap()) result = if (boundTerm is Expression) boundTerm.simplify(subs).to else boundTerm
} }
return Simplification(this, result)
} }
override fun compareTo(other: Term): Int { override fun toString(): String = name
return when (other) {
is Variable -> name.compareTo(other.name)
// Variables are always less than atoms
else -> -1
}
}
override fun toString(): String {
return when {
alias.isPresent -> "$name: ${alias.get()}"
else -> name
}
}
} }

View file

@ -0,0 +1,5 @@
package prolog.ast.terms
import prolog.Substitution
class VariableBinding(from: Variable, to: Term) : Substitution(from, to)

View file

@ -1,10 +1,11 @@
package prolog.builtins package prolog.builtins
import prolog.Answers
import prolog.Substitutions
import prolog.ast.arithmetic.ArithmeticOperator import prolog.ast.arithmetic.ArithmeticOperator
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.ast.logic.LogicOperand import prolog.ast.arithmetic.Simplification
import prolog.ast.logic.LogicOperator import prolog.ast.logic.Satisfiable
import prolog.ast.logic.Provable
import prolog.ast.terms.* import prolog.ast.terms.*
import prolog.logic.* import prolog.logic.*
@ -20,20 +21,20 @@ import prolog.logic.*
* True if expression Expr1 evaluates to a number non-equal to Expr2. * True if expression Expr1 evaluates to a number non-equal to Expr2.
*/ */
class EvaluatesToDifferent(private val left: Expression, private val right: Expression) : class EvaluatesToDifferent(private val left: Expression, private val right: Expression) :
ArithmeticOperator(Atom("=\\="), left, right) { Operator(Atom("=\\="), left, right), Satisfiable {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> { override fun satisfy(subs: Substitutions): Answers {
val t1 = left.evaluate(subs) val t1 = left.simplify(subs)
val t2 = right.evaluate(subs) val t2 = right.simplify(subs)
// Should both be instantiated // Should both be instantiated
if (!atomic(t1.first) || !atomic(t2.first)) { if (!atomic(t1.to, subs) || !atomic(t2.to, subs)) {
throw IllegalArgumentException("Both operands must be instantiated") return sequenceOf(Result.failure(IllegalArgumentException("Both operands must be instantiated")))
} }
return if (equivalent(t1.first, t2.first)) { return if (equivalent(t1.to, t2.to, subs)) {
Pair(False, emptyMap()) emptySequence()
} else { } else {
Pair(True, t1.second + t2.second) sequenceOf(Result.success(emptyMap()))
} }
} }
@ -43,21 +44,17 @@ class EvaluatesToDifferent(private val left: Expression, private val right: Expr
* True if Expr1 evaluates to a number equal to Expr2. * True if Expr1 evaluates to a number equal to Expr2.
*/ */
class EvaluatesTo(private val left: Expression, private val right: Expression) : class EvaluatesTo(private val left: Expression, private val right: Expression) :
ArithmeticOperator(Atom("=:="), left, right) { Operator(Atom("=:="), left, right) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> { override fun satisfy(subs: Substitutions): Answers {
val t1 = left.evaluate(subs) val t1 = left.simplify(subs)
val t2 = right.evaluate(subs) val t2 = right.simplify(subs)
// Should both be instantiated // Should both be instantiated
if (!atomic(t1.first) || !atomic(t2.first)) { if (!atomic(t1.to, subs) || !atomic(t2.to, subs)) {
throw IllegalArgumentException("Both operands must be instantiated") return sequenceOf(Result.failure(IllegalArgumentException("Both operands must be instantiated")))
} }
return if (equivalent(t1.first, t2.first)) { return if (equivalent(t1.to, t2.to, subs)) sequenceOf(Result.success(emptyMap())) else emptySequence()
Pair(True, t1.second + t2.second)
} else {
Pair(False, emptyMap())
}
} }
} }
@ -65,16 +62,20 @@ class EvaluatesTo(private val left: Expression, private val right: Expression) :
* True when Number is the value to which Expr evaluates. * True when Number is the value to which Expr evaluates.
*/ */
class Is(private val left: Expression, private val right: Expression) : class Is(private val left: Expression, private val right: Expression) :
Operator(Atom("is"), left, right), Provable { Operator(Atom("is"), left, right), Satisfiable {
override fun prove(subs: Substituted): Sequence<Substituted> { override fun satisfy(subs: Substitutions): Answers {
val t1 = left.evaluate(subs) val t1 = left.simplify(subs)
val t2 = right.evaluate(subs) val t2 = right.simplify(subs)
if (!atomic(t2.first)) { if (!atomic(t2.to, subs)) {
throw IllegalArgumentException("Arguments are not sufficiently instantiated") return sequenceOf(Result.failure(IllegalArgumentException("Right operand must be instantiated")))
} }
return unifyLazy(t1.first, t2.first, subs).map{ it + t1.second + t2.second } if (!variable(t1.to, subs) && equivalent(t1.to, t2.to, subs + listOfNotNull(t1.mapped, t2.mapped))) {
return sequenceOf(Result.success(emptyMap()))
}
return unifyLazy(t1.to, t2.to, subs)
} }
} }
@ -93,10 +94,11 @@ class Positive(operand: Expression) : Add(Integer(0), operand)
*/ */
open class Add(private val expr1: Expression, private val expr2: Expression) : open class Add(private val expr1: Expression, private val expr2: Expression) :
ArithmeticOperator(Atom("+"), expr1, expr2) { ArithmeticOperator(Atom("+"), expr1, expr2) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> { override fun simplify(subs: Substitutions): Simplification {
val result = Variable("Result") val result = Variable("Result")
val map = plus(expr1, expr2, result, subs) val map = plus(expr1, expr2, result, subs)
return result.evaluate(map.first().getOrThrow()) val simplification = result.simplify(map.first().getOrThrow())
return Simplification(this, simplification.to)
} }
} }
@ -105,10 +107,11 @@ open class Add(private val expr1: Expression, private val expr2: Expression) :
*/ */
open class Subtract(private val expr1: Expression, private val expr2: Expression) : open class Subtract(private val expr1: Expression, private val expr2: Expression) :
ArithmeticOperator(Atom("-"), expr1, expr2) { ArithmeticOperator(Atom("-"), expr1, expr2) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> { override fun simplify(subs: Substitutions): Simplification {
val result = Variable("Result") val result = Variable("Result")
val map = plus(expr2, result, expr1, subs) val map = plus(expr2, result, expr1, subs)
return result.evaluate(map.first().getOrThrow()) val simplification = result.simplify(map.first().getOrThrow())
return Simplification(this, simplification.to)
} }
} }
@ -118,10 +121,11 @@ open class Subtract(private val expr1: Expression, private val expr2: Expression
*/ */
class Multiply(private val expr1: Expression, private val expr2: Expression) : class Multiply(private val expr1: Expression, private val expr2: Expression) :
ArithmeticOperator(Atom("*"), expr1, expr2) { ArithmeticOperator(Atom("*"), expr1, expr2) {
override fun evaluate(subs: Substituted): Pair<Term, Substituted> { override fun simplify(subs: Substitutions): Simplification {
val result = Variable("Result") val result = Variable("Result")
val map = mul(expr1, expr2, result, subs) val map = mul(expr1, expr2, result, subs)
return result.evaluate(map.first().getOrThrow()) val simplification = result.simplify(map.first().getOrThrow())
return Simplification(this, simplification.to)
} }
} }
@ -133,20 +137,24 @@ class Multiply(private val expr1: Expression, private val expr2: Expression) :
class Between(private val expr1: Expression, private val expr2: Expression, private val expr3: Expression) : class Between(private val expr1: Expression, private val expr2: Expression, private val expr3: Expression) :
Operator(Atom("between"), expr1, expr2) { Operator(Atom("between"), expr1, expr2) {
override fun prove(subs: Substituted): Sequence<Substituted> { override fun satisfy(subs: Substitutions): Answers {
val e1 = expr1.evaluate(subs) val e1 = expr1.simplify(subs)
val e2 = expr2.evaluate(subs) val e2 = expr2.simplify(subs)
val e3 = expr3.evaluate(subs) val e3 = expr3.simplify(subs)
require(e1.first is Integer && e2.first is Integer) { "Arguments must be integers" } require(e1.to is Integer && e2.to is Integer) { "Arguments must be integers" }
val v1 = e1.first as Integer val v1 = e1.to as Integer
val v2 = e2.first as Integer val v2 = e2.to as Integer
return if (variable(e3.first)) { return if (variable(e3.to, subs)) {
between(v1, v2, e3.first as Variable).map { it + e1.second + e2.second } between(v1, v2, e3.to as Variable).map { answer ->
answer.mapCatching { it + listOfNotNull(e1.mapped, e2.mapped) }
}
} else { } else {
between(v1, v2, e3.first as Integer).map { it + e1.second + e2.second } between(v1, v2, e3.to as Integer).map { answer ->
answer.mapCatching { it + listOfNotNull(e1.mapped, e2.mapped) }
}
} }
} }
} }

View file

@ -1,17 +1,18 @@
package prolog.builtins package prolog.builtins
import prolog.Answers
import prolog.Substitutions
import prolog.ast.logic.LogicOperand import prolog.ast.logic.LogicOperand
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Body import prolog.ast.terms.Body
import prolog.ast.terms.Goal import prolog.ast.terms.Goal
import prolog.ast.logic.LogicOperator import prolog.ast.logic.LogicOperator
import prolog.logic.Substituted
/** /**
* Always fail. * Always fail.
*/ */
object Fail : Atom("fail"), Body { object Fail : Atom("fail"), Body {
override fun prove(subs: Substituted): Sequence<Substituted> = emptySequence() override fun satisfy(subs: Substitutions): Answers = emptySequence()
} }
/** /**
@ -23,7 +24,7 @@ typealias False = Fail
* Always succeed. * Always succeed.
*/ */
object True : Atom("true"), Body { object True : Atom("true"), Body {
override fun prove(subs: Substituted): Sequence<Substituted> = sequenceOf(emptyMap()) override fun satisfy(subs: Substitutions): Answers = sequenceOf(Result.success(emptyMap()))
} }
// TODO Repeat/0 // TODO Repeat/0
@ -34,10 +35,14 @@ object True : Atom("true"), Body {
* Conjunction (and). True if both Goal1 and Goal2 are true. * Conjunction (and). True if both Goal1 and Goal2 are true.
*/ */
class Conjunction(private val left: LogicOperand, private val right: LogicOperand) : LogicOperator(Atom(","), left, right) { class Conjunction(private val left: LogicOperand, private val right: LogicOperand) : LogicOperator(Atom(","), left, right) {
override fun prove(subs: Substituted): Sequence<Substituted> = sequence { override fun satisfy(subs: Substitutions): Answers = sequence {
left.prove(subs).forEach { left -> left.satisfy(subs).forEach { leftResult ->
right.prove(subs + left).forEach { right -> leftResult.mapCatching { left ->
yield(left + right) right.satisfy(subs + left).forEach { rightResult ->
rightResult.map { right ->
yield(Result.success(left + right))
}
}
} }
} }
} }
@ -48,9 +53,9 @@ class Conjunction(private val left: LogicOperand, private val right: LogicOperan
*/ */
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 prove(subs: Substituted): Sequence<Substituted> = sequence { override fun satisfy(subs: Substitutions): Answers = sequence {
yieldAll(left.prove(subs)) yieldAll(left.satisfy(subs))
yieldAll(right.prove(subs)) yieldAll(right.satisfy(subs))
} }
} }
@ -65,12 +70,12 @@ class Bar(leftOperand: LogicOperand, rightOperand: LogicOperand) : Disjunction(l
* True if 'Goal' cannot be proven. * True if 'Goal' cannot be proven.
*/ */
class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) { class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) {
override fun prove(subs: Substituted): Sequence<Substituted> { override fun satisfy(subs: Substitutions): Answers {
// If the goal can be proven, return an empty sequence // If the goal can be proven, return an empty sequence
if (goal.prove(subs).toList().isNotEmpty()) { if (goal.satisfy(subs).toList().isNotEmpty()) {
return emptySequence() return emptySequence()
} }
// If the goal cannot be proven, return a sequence with an empty map // If the goal cannot be proven, return a sequence with an empty map
return sequenceOf(emptyMap()) return sequenceOf(Result.success(emptyMap()))
} }
} }

View file

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

View file

@ -5,20 +5,21 @@
*/ */
package prolog.builtins package prolog.builtins
import prolog.Answers
import prolog.Substitutions
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Operator import prolog.ast.terms.Operator
import prolog.ast.terms.Term import prolog.ast.terms.Term
import prolog.logic.Substituted
import prolog.logic.applySubstitution import prolog.logic.applySubstitution
import prolog.logic.equivalent import prolog.logic.equivalent
class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) { class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) {
override fun prove(subs: Substituted): Sequence<Substituted> = sequence { override fun satisfy(subs: Substitutions): Answers = sequence {
val t1 = applySubstitution(term1, subs) val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs) val t2 = applySubstitution(term2, subs)
if (equivalent(t1, t2)) { if (equivalent(t1, t2, subs)) {
yield(emptyMap()) yield(Result.success(emptyMap()))
} }
} }
} }

View file

@ -1,10 +1,12 @@
package prolog.logic package prolog.logic
import prolog.Answers
import prolog.Substitution
import prolog.Substitutions
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.ast.terms.Integer import prolog.ast.terms.Integer
import prolog.ast.terms.Term
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.builtins.Is
import java.util.*
/** /**
* Low and High are integers, High Low. * Low and High are integers, High Low.
@ -18,9 +20,9 @@ fun between(
low: Integer, low: Integer,
high: Integer, high: Integer,
value: Integer value: Integer
): Sequence<Substituted> { ): Answers {
return if (value.value in low.value..high.value) { return if (value.value in low.value..high.value) {
sequenceOf(emptyMap()) sequenceOf(Result.success(emptyMap()))
} else { } else {
emptySequence() emptySequence()
} }
@ -30,10 +32,10 @@ fun between(
low: Integer, low: Integer,
high: Integer, high: Integer,
variable: Variable variable: Variable
): Sequence<Substituted> { ): Answers {
return sequence { return sequence {
for (i in low.value..high.value) { 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. * @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 * 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) { if (term2 is Integer) {
require(term2.value >= 0) { "Domain error: not_less_than_zero" } 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 // If term1 is a variable, we need to check if it is bound to a negative integer
return sequence { return sequence {
result.forEach { newSubs -> plus(term1, Integer(1), term2, subs).forEach {
if (newSubs.isSuccess) { it.fold(
val t1 = applySubstitution(term1, newSubs.getOrNull()!!) onSuccess = { result ->
if (t1 is Variable && t1.alias().isPresent) { val t1 = applySubstitution(term1, result)
val e1 = t1.evaluate(subs) if (t1 in result) {
if (e1.first is Integer && (e1.first as Integer).value < 0) { val e1 = t1.simplify(result)
return@sequence if (e1.to is Integer && e1.to.value < 0) {
return@sequence
}
} }
} yield(Result.success(result))
} },
yield(newSubs) 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. * 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) 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) operate(term1, term2, term3, subs, Integer::times, Integer::div)
fun operate( fun operate(
term1: Expression, term1: Expression,
term2: Expression, term2: Expression,
term3: Expression, term3: Expression,
subs: Substituted, subs: Substitutions,
op: (Integer, Integer) -> Integer, op: (Integer, Integer) -> Integer,
inverseOp: (Integer, Integer) -> Integer inverseOp: (Integer, Integer) -> Integer
): Sequence<Result<Substituted>> = sequence { ): Answers = sequence {
val t1 = applySubstitution(term1, subs) val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs) val t2 = applySubstitution(term2, subs)
val t3 = applySubstitution(term3, subs) val t3 = applySubstitution(term3, subs)
when { when {
nonvariable(t1) && nonvariable(t2) && nonvariable(t3) -> { nonvariable(t1, subs) && nonvariable(t2, subs) && nonvariable(t3, subs) -> {
val e1 = t1.evaluate(subs) val e1 = t1.simplify(subs)
val e2 = t2.evaluate(subs) val e2 = t2.simplify(subs)
val e3 = t3.evaluate(subs) val e3 = t3.simplify(subs)
val int3Value = op(e1.first as Integer, e2.first as Integer) val int3Value = op(e1.to as Integer, e2.to as Integer)
if (int3Value == e3.first as Integer) { if (int3Value == e3.to as Integer) {
yield(Result.success(e1.second + e2.second + e3.second)) 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) -> { nonvariable(t1, subs) && nonvariable(t2, subs) && variable(t3, subs) -> {
val e1 = t1.evaluate(subs) val e1 = t1.simplify(subs)
val e2 = t2.evaluate(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 val int3 = t3 as Variable
int3.bind(int3Value) yield(Result.success(mapOf(int3 to int3Value) + listOfNotNull(e1.mapped, e2.mapped)))
yield(Result.success(mapOf(int3 to int3Value) + e1.second + e2.second))
} }
((nonvariable(t1) && variable(t2)) || (variable(t1) && nonvariable(t2))) && nonvariable(t3) -> { ((nonvariable(t1, subs) && variable(t2, subs)) || (variable(t1, subs) && nonvariable(t2, subs))) && nonvariable(t3, subs) -> {
val t = if (nonvariable(t1)) t2 else t1 val t = if (nonvariable(t1, subs)) t2 else t1
val e = if (nonvariable(t1)) t1.evaluate(subs) else t2.evaluate(subs) val e = if (nonvariable(t1, subs)) t1.simplify(subs) else t2.simplify(subs)
val e3 = t3.evaluate(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 val int = t as Variable
int.bind(value) yield(Result.success(mapOf(int to value) + listOfNotNull(e.mapped, e3.mapped)))
yield(Result.success(mapOf(int to value) + e.second + e3.second))
} }
else -> { else -> {

View file

@ -1,26 +1,27 @@
package prolog.logic package prolog.logic
import prolog.Answer
import prolog.Answers
import prolog.Substitutions
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.ast.logic.LogicOperator import prolog.ast.logic.LogicOperator
import prolog.ast.terms.* import prolog.ast.terms.*
import java.util.* import kotlin.NoSuchElementException
typealias Substituted = Map<Variable, Term>
// Apply substitutions to a term // Apply substitutions to a term
fun applySubstitution(term: Term, subs: Substituted): Term = when { fun applySubstitution(term: Term, subs: Substitutions): Term = when {
variable(term) -> subs[(term as Variable)] ?: term variable(term, emptyMap()) -> subs[(term as Variable)] ?: term
atomic(term) -> term atomic(term, subs) -> term
compound(term) -> { compound(term, subs) -> {
val structure = term as Structure val structure = term as Structure
Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) }) Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) })
} }
else -> term else -> term
} }
//TODO Combine with the other applySubstitution function
fun applySubstitution(expr: Expression, subs: Substituted): Expression = when { fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when {
variable(expr) -> applySubstitution(expr as Term, subs) as Expression variable(expr, subs) -> applySubstitution(expr as Term, subs) as Expression
atomic(expr) -> expr atomic(expr, subs) -> expr
expr is LogicOperator -> { expr is LogicOperator -> {
expr.arguments = expr.arguments.map { applySubstitution(it, subs) } expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
expr expr
@ -29,71 +30,138 @@ fun applySubstitution(expr: Expression, subs: Substituted): Expression = when {
} }
// Check if a variable occurs in a term // Check if a variable occurs in a term
private fun occurs(variable: Variable, term: Term): Boolean = when { private fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean = when {
variable(term) -> term == variable variable(term, subs) -> term == variable
atomic(term) -> false atomic(term, subs) -> false
compound(term) -> { compound(term, subs) -> {
val structure = term as Structure val structure = term as Structure
structure.arguments.any { occurs(variable, it) } structure.arguments.any { occurs(variable, it, subs) }
} }
else -> false else -> false
} }
// Unify two terms with backtracking and lazy evaluation // 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 t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, subs) val t2 = applySubstitution(term2, subs)
when { when {
equivalent(t1, t2) -> yield(subs) equivalent(t1, t2, subs) -> yield(Result.success(subs))
variable(t1) -> { variable(t1, subs) -> {
val variable = t1 as Variable val variable = t1 as Variable
if (!occurs(variable, t2)) { if (!occurs(variable, t2, subs)) {
variable.bind(t2) yield(Result.success(subs + (variable to t2)))
yield(subs + (variable to t2))
} }
} }
variable(t2) -> { variable(t2, subs) -> {
val variable = t2 as Variable val variable = t2 as Variable
if (!occurs(variable, t1)) { if (!occurs(variable, t1, subs)) {
variable.bind(t1) yield(Result.success(subs + (variable to t1)))
yield(subs + (variable to t1))
} }
} }
compound(t1) && compound(t2) -> { compound(t1, subs) && compound(t2, subs) -> {
val structure1 = t1 as Structure val structure1 = t1 as Structure
val structure2 = t2 as Structure val structure2 = t2 as Structure
if (structure1.functor == structure2.functor) { if (structure1.functor == structure2.functor) {
val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(subs) { acc, (arg1, arg2) -> // Unify each argument at the same time, and yield the result
unifyLazy(arg1, arg2, acc).firstOrNull() ?: return@sequence 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 -> {} else -> {}
} }
} }
fun unify(term1: Term, term2: Term): Optional<Substituted> { fun unify(term1: Term, term2: Term): Answer {
val substitutions = unifyLazy(term1, term2, emptyMap()).toList() val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
return if (substitutions.isNotEmpty()) { return if (substitutions.isNotEmpty()) {
Optional.of(substitutions.first()) substitutions.first()
} else { } else {
Optional.empty() Result.failure(NoSuchElementException())
} }
} }
/** /**
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable. * 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 { return when {
term1 is Atom && term2 is Atom -> term1.compareTo(term2) == 0 term1 is Atom && term2 is Atom -> compare(term1, term2, subs) == 0
term1 is Structure && term2 is Structure -> term1.compareTo(term2) == 0 term1 is Structure && term2 is Structure -> compare(term1, term2, subs) == 0
term1 is Integer && term2 is Integer -> term1.compareTo(term2) == 0 term1 is Integer && term2 is Integer -> compare(term1, term2, subs) == 0
term1 is Variable && term2 is Variable -> term1 == term2 term1 is Variable && term2 is Variable -> term1 == term2
term1 is Variable -> term1.alias().isPresent && equivalent(term1.alias().get(), term2) term1 is Variable -> term1 in subs && equivalent(subs[term1]!!, term2, subs)
term2 is Variable -> term2.alias().isPresent && equivalent(term2.alias().get(), term1) term2 is Variable -> term2 in subs && equivalent(subs[term2]!!, term1, subs)
else -> false 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 package prolog.logic
import prolog.Substitutions
import prolog.ast.terms.CompoundTerm import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.Term import prolog.ast.terms.Term
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
@ -12,29 +13,29 @@ import prolog.ast.terms.Variable
* nonvar(Term), * nonvar(Term),
* \+ compound(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. * True if [Term] is bound to a compound term.
* See also functor/3 =../2, compound_name_arity/3 and compound_name_arguments/3. * 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 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 return isCompound || isVariableCompound
} }
/** /**
* True if [Term] currently is not a free variable. * 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. * 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) { 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 return false

View file

@ -200,7 +200,7 @@ class EvaluationTest {
Program.load(listOf(fact1, fact2, fact3)) Program.load(listOf(fact1, fact2, fact3))
val results = Query(Structure(Atom("a"), listOf(Variable("X")))).prove(emptyMap()) val results = Query(Structure(Atom("a"), listOf(Variable("X")))).satisfy(emptyMap())
val expectedResults = listOf( val expectedResults = listOf(
mapOf(Variable("X") to Atom("b")), mapOf(Variable("X") to Atom("b")),
@ -211,8 +211,8 @@ class EvaluationTest {
assertEquals(expectedResults.size, actualResults.size, "Number of results should match") assertEquals(expectedResults.size, actualResults.size, "Number of results should match")
for (i in expectedResults.indices) { for (i in expectedResults.indices) {
assertEquals(expectedResults[i].size, actualResults[i].size, "Substitution size should match") assertEquals(expectedResults[i].size, actualResults[i].getOrNull()!!.size, "Substitution size should match")
assertTrue(expectedResults[i].all { actualResults[i][it.key]?.let { it1 -> equivalent(it.value, it1) } ?: false }, "Substitution values should match") assertTrue(expectedResults[i].all { actualResults[i].getOrNull()!![it.key]?.let { it1 -> equivalent(it.value, it1, emptyMap()) } ?: false }, "Substitution values should match")
} }
} }
} }

View file

@ -3,9 +3,10 @@ package prolog.builtins
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import prolog.Substitutions
import prolog.ast.terms.Integer import prolog.ast.terms.Integer
import prolog.ast.terms.Term
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.logic.between
import prolog.logic.equivalent import prolog.logic.equivalent
class ArithmeticOperatorsTests { class ArithmeticOperatorsTests {
@ -16,18 +17,21 @@ class ArithmeticOperatorsTests {
Integer(1) Integer(1)
) )
var result = op1.evaluate(emptyMap()).first var result = op1.satisfy(emptyMap()).toList()
assertEquals(True, result, "1 should be equal to 1") assertEquals(1, result.size, "1 should be equal to 1")
assertTrue(result[0].isSuccess, "1 should be equal to 1")
val subs = result[0].getOrNull()!!
assertTrue(subs.isEmpty(), "1 should not be rebound")
val op2 = EvaluatesToDifferent( val op2 = EvaluatesToDifferent(
Integer(1), Integer(1),
Integer(1) Integer(1)
) )
result = op2.evaluate(emptyMap()).first result = op2.satisfy(emptyMap()).toList()
assertEquals(False, result, "1 should not be different from 1") assertEquals(0, result.size, "1 should not be different from 1")
} }
@Test @Test
@ -37,18 +41,21 @@ class ArithmeticOperatorsTests {
Integer(2) Integer(2)
) )
var result = op1.evaluate(emptyMap()).first var result = op1.satisfy(emptyMap()).toList()
assertEquals(0, result.size, "1 should not be equal to 2")
assertEquals(False, result, "1 should not be equal to 2")
val op2 = EvaluatesToDifferent( val op2 = EvaluatesToDifferent(
Integer(1), Integer(1),
Integer(2) Integer(2)
) )
result = op2.evaluate(emptyMap()).first result = op2.satisfy(emptyMap()).toList()
assertEquals(True, result, "1 should be different from 2") assertEquals(1, result.size, "1 should be different from 2")
assertTrue(result[0].isSuccess, "1 should be different from 2")
val subs = result[0].getOrNull()!!
assertTrue(subs.isEmpty(), "1 should not be rebound")
} }
@Test @Test
@ -58,18 +65,21 @@ class ArithmeticOperatorsTests {
Integer(5) Integer(5)
) )
var result = op.evaluate(emptyMap()).first var result = op.satisfy(emptyMap()).toList()
assertEquals(True, result, "2 + 3 should be equal to 5") assertEquals(1, result.size, "2 + 3 should be equal to 5")
assertTrue(result[0].isSuccess, "2 + 3 should be equal to 5")
val subs = result[0].getOrNull()!!
assertTrue(subs.isEmpty(), "2 + 3 should not be rebound")
val op2 = EvaluatesToDifferent( val op2 = EvaluatesToDifferent(
Add(Integer(2), Integer(3)), Add(Integer(2), Integer(3)),
Integer(5) Integer(5)
) )
result = op2.evaluate(emptyMap()).first result = op2.satisfy(emptyMap()).toList()
assertEquals(False, result, "2 + 3 should not be different from 5") assertEquals(0, result.size, "2 + 3 should not be different from 5")
} }
@Test @Test
@ -79,18 +89,21 @@ class ArithmeticOperatorsTests {
Add(Integer(4), Integer(1)) Add(Integer(4), Integer(1))
) )
var result = op.evaluate(emptyMap()).first var result = op.satisfy(emptyMap()).toList()
assertEquals(True, result, "2 + 3 should be equal to 4 + 1") assertEquals(1, result.size, "2 + 3 should be equal to 4 + 1")
assertTrue(result[0].isSuccess, "2 + 3 should be equal to 4 + 1")
val subs = result[0].getOrNull()!!
assertTrue(subs.isEmpty(), "2 + 3 should not be rebound")
val op2 = EvaluatesToDifferent( val op2 = EvaluatesToDifferent(
Add(Integer(2), Integer(3)), Add(Integer(2), Integer(3)),
Add(Integer(4), Integer(1)) Add(Integer(4), Integer(1))
) )
result = op2.evaluate(emptyMap()).first result = op2.satisfy(emptyMap()).toList()
assertEquals(False, result, "2 + 3 should not be different from 4 + 1") assertEquals(0, result.size, "2 + 3 should not be different from 4 + 1")
} }
@Test @Test
@ -100,14 +113,24 @@ class ArithmeticOperatorsTests {
Variable("X") Variable("X")
) )
assertThrows<IllegalArgumentException> { op.evaluate(emptyMap()) } var result = op.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "One exception should be thrown")
assertTrue(result[0].isFailure, "One exception should be thrown")
var exceptions = result[0].exceptionOrNull()
assertTrue(exceptions is IllegalArgumentException, "One exception should be thrown")
val op2 = EvaluatesToDifferent( val op2 = EvaluatesToDifferent(
Integer(1), Integer(1),
Variable("X") Variable("X")
) )
assertThrows<IllegalArgumentException> { op2.evaluate(emptyMap()) } result = op2.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "One exception should be thrown")
assertTrue(result[0].isFailure, "One exception should be thrown")
exceptions = result[0].exceptionOrNull()
assertTrue(exceptions is IllegalArgumentException, "One exception should be thrown")
} }
@Test @Test
@ -117,14 +140,14 @@ class ArithmeticOperatorsTests {
Integer(1) Integer(1)
) )
assertThrows<IllegalArgumentException> { op.evaluate(emptyMap()) } assertThrows<IllegalArgumentException> { op.satisfy(emptyMap()) }
val op2 = EvaluatesToDifferent( val op2 = EvaluatesToDifferent(
Add(Integer(1), Variable("X")), Add(Integer(1), Variable("X")),
Integer(1) Integer(1)
) )
assertThrows<IllegalArgumentException> { op2.evaluate(emptyMap()) } assertThrows<IllegalArgumentException> { op2.satisfy(emptyMap()) }
} }
@Test @Test
@ -134,7 +157,7 @@ class ArithmeticOperatorsTests {
Integer(1) Integer(1)
) )
val result = op.prove(emptyMap()) val result = op.satisfy(emptyMap())
assertTrue(result.any(), "1 should be equal to 1") assertTrue(result.any(), "1 should be equal to 1")
} }
@ -146,7 +169,7 @@ class ArithmeticOperatorsTests {
Integer(2) Integer(2)
) )
val result = op.prove(emptyMap()).toList() val result = op.satisfy(emptyMap()).toList()
assertTrue(result.isEmpty(), "1 should not be equal to 2") assertTrue(result.isEmpty(), "1 should not be equal to 2")
} }
@ -159,12 +182,11 @@ class ArithmeticOperatorsTests {
Integer(1) Integer(1)
) )
t1.bind(Integer(1)) val result = op.satisfy(mapOf(t1 to Integer(1))).toList()
val result = op.prove(emptyMap()) assertEquals(1, result.size, "X should be equal to 1")
assertTrue(result.first().isSuccess, "X should be equal to 1")
assertTrue(result.any(), "X should be equal to 1") assertTrue(result.first().getOrNull()!!.isEmpty(), "X should not be rebound")
assertTrue(result.first().isEmpty(), "X should not be rebound")
} }
@Test @Test
@ -175,9 +197,7 @@ class ArithmeticOperatorsTests {
Integer(1) Integer(1)
) )
t1.bind(Integer(2)) val result = op.satisfy(mapOf(t1 to Integer(2)))
val result = op.prove(emptyMap())
assertFalse(result.any(), "X should not be equal to 1") assertFalse(result.any(), "X should not be equal to 1")
} }
@ -189,11 +209,12 @@ class ArithmeticOperatorsTests {
Integer(1) Integer(1)
) )
val result = op.prove(emptyMap()).toList() val result = op.satisfy(emptyMap()).toList()
assertFalse(result.isEmpty(), "X should be equal to 1") assertFalse(result.isEmpty(), "X should be equal to 1")
assertEquals(1, result[0].size, "X should be rebound") assertTrue(result.first().isSuccess, "X should be equal to 1")
assertTrue(equivalent(Integer(1), result[0][Variable("X")]!!), "X should be equal to 1") assertEquals(1, result[0].getOrNull()!!.size, "X should be rebound")
assertTrue(equivalent(Integer(1), result[0].getOrNull()!![Variable("X")]!!, emptyMap()), "X should be equal to 1")
} }
@Test @Test
@ -203,11 +224,18 @@ class ArithmeticOperatorsTests {
Add(Integer(1), Integer(2)) Add(Integer(1), Integer(2))
) )
val result = op.prove(emptyMap()).toList() val result = op.satisfy(emptyMap()).toList()
assertFalse(result.isEmpty(), "X should be equal to 3") assertFalse(result.isEmpty(), "X should be equal to 3")
assertEquals(1, result[0].size, "X should be rebound") assertTrue(result.first().isSuccess, "X should be equal to 3")
assertTrue(equivalent(Integer(3), result[0][Variable("X")]!!), "X should be equal to 3")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "X should be rebound")
assertTrue(
equivalent(Integer(3), subs[Variable("X")]!!, emptyMap()),
"X should be equal to 3"
)
} }
@Test @Test
@ -217,7 +245,12 @@ class ArithmeticOperatorsTests {
Variable("Y") Variable("Y")
) )
assertThrows<IllegalArgumentException> { op.prove(emptyMap()) } val result = op.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "One exception should be thrown")
assertTrue(result[0].isFailure, "One exception should be thrown")
val exceptions = result[0].exceptionOrNull()
assertTrue(exceptions is IllegalArgumentException, "One exception should be thrown")
} }
@Test @Test
@ -227,23 +260,29 @@ class ArithmeticOperatorsTests {
Variable("X") Variable("X")
) )
assertThrows<IllegalArgumentException> { op.prove(emptyMap()) } val result = op.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "One exception should be thrown")
assertTrue(result[0].isFailure, "One exception should be thrown")
val exceptions = result[0].exceptionOrNull()
assertTrue(exceptions is IllegalArgumentException, "One exception should be thrown")
} }
/**
* ?- X = 1, Y = 1 + 2, X is Y.
* false.
*/
@Test @Test
fun `var is bound-to-sum-var`() { fun `var is bound-to-sum-var`() {
val t1 = Variable("X") val t1 = Variable("X")
val t2 = Variable("Y") val t2 = Variable("Y")
t2.bind(Add(Integer(1), Integer(2)))
val op = Is(t1, t2) val op = Is(t1, t2)
val map: Substitutions = mapOf(t1 to Integer(1), t2 to Add(Integer(1), Integer(2)))
val result = op.prove(emptyMap()).toList() val result = op.satisfy(map).toList()
assertTrue(result.isNotEmpty(), "X should be equal to 3") assertEquals(0, result.size, "X should not be equal to Y")
assertEquals(1, result[0].size, "X should be rebound, Y should not")
assertTrue(equivalent(Integer(3), result[0][t1]!!), "X should be equal to 3")
} }
/** /**
@ -268,47 +307,38 @@ class ArithmeticOperatorsTests {
fun `negate 1 to get -1`() { fun `negate 1 to get -1`() {
val t1 = Integer(1) val t1 = Integer(1)
val result = Negate(t1).evaluate(emptyMap()).first val result = Negate(t1).simplify(emptyMap())
assertEquals(Integer(-1), result, "negate(1) should be equal to -1") assertEquals(Integer(-1), result.to, "negate(1) should be equal to -1")
} }
@Test @Test
fun `negate 0 to get 0`() { fun `negate 0 to get 0`() {
val t1 = Integer(0) val t1 = Integer(0)
val result = Negate(t1).evaluate(emptyMap()).first val result = Negate(t1).simplify(emptyMap())
assertEquals(Integer(0), result, "negate(0) should be equal to 0") assertEquals(Integer(0), result.to, "negate(0) should be equal to 0")
} }
@Test @Test
fun `negate -1 to get 1`() { fun `negate -1 to get 1`() {
val t1 = Integer(-1) val t1 = Integer(-1)
val result = Negate(t1).evaluate(emptyMap()).first val result = Negate(t1).simplify(emptyMap())
assertEquals(Integer(1), result, "negate(-1) should be equal to 1") assertEquals(Integer(1), result.to, "negate(-1) should be equal to 1")
} }
@Test @Test
fun `negate bound-to-1-var to get -1`() { fun `negate bound-to-1-var to get -1`() {
val t1 = Variable("X") val t1 = Variable("X")
t1.bind(Integer(1)) val map: Substitutions = mapOf(t1 to Integer(1))
val result = Negate(t1).evaluate(emptyMap()).first val result = Negate(t1).simplify(map)
assertEquals(Integer(-1), result, "negate(1) should be equal to -1") assertEquals(Integer(-1), result.to, "negate(1) should be equal to -1")
}
@Test
fun `negate bound-to-1-var to get -1 by map`() {
val t1 = Variable("X")
val result = Negate(t1).evaluate(mapOf(t1 to Integer(1))).first
assertEquals(Integer(-1), result, "negate(1) should be equal to -1")
} }
@Test @Test
@ -316,9 +346,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(1) val t1 = Integer(1)
val t2 = Integer(3) val t2 = Integer(3)
val result = Negate(Add(t1, t2)).evaluate(emptyMap()).first val result = Negate(Add(t1, t2)).simplify(emptyMap())
assertEquals(Integer(-4), result, "negate(1 + 3) should be equal to -4") assertEquals(Integer(-4), result.to, "negate(1 + 3) should be equal to -4")
} }
@Test @Test
@ -326,9 +356,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(1) val t1 = Integer(1)
val t2 = Integer(2) val t2 = Integer(2)
val result = Add(t1, t2).evaluate(emptyMap()).first val result = Add(t1, t2).simplify(emptyMap())
assertEquals(Integer(3), result, "1 + 2 should be equal to 3") assertEquals(Integer(3), result.to, "1 + 2 should be equal to 3")
} }
@Test @Test
@ -336,21 +366,10 @@ class ArithmeticOperatorsTests {
val t1 = Integer(1) val t1 = Integer(1)
val t2 = Variable("X") val t2 = Variable("X")
t2.bind(Integer(2)) val subs: Substitutions = mapOf(t2 to Integer(2))
val result = Add(t1, t2).simplify(subs)
val result = Add(t1, t2).evaluate(emptyMap()).first assertEquals(Integer(3), result.to, "1 + 2 should be equal to 3")
assertEquals(Integer(3), result, "1 + 2 should be equal to 3")
}
@Test
fun `Add 1 and bound-to-2-var to get 3 by map`() {
val t1 = Integer(1)
val t2 = Variable("X")
val result = Add(t1, t2).evaluate(mapOf(t2 to Integer(2))).first
assertEquals(Integer(3), result, "1 + 2 should be equal to 3")
} }
@Test @Test
@ -358,9 +377,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(1) val t1 = Integer(1)
val t2 = Integer(2) val t2 = Integer(2)
val result = Subtract(t1, t2).evaluate(emptyMap()).first val result = Subtract(t1, t2).simplify(emptyMap())
assertEquals(Integer(-1), result, "1 - 2 should be equal to -1") assertEquals(Integer(-1), result.to, "1 - 2 should be equal to -1")
} }
@Test @Test
@ -368,9 +387,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(3) val t1 = Integer(3)
val t2 = Integer(1) val t2 = Integer(1)
val result = Subtract(t1, t2).evaluate(emptyMap()).first val result = Subtract(t1, t2).simplify(emptyMap())
assertEquals(Integer(2), result, "3 - 1 should be equal to 2") assertEquals(Integer(2), result.to, "3 - 1 should be equal to 2")
} }
@Test @Test
@ -378,9 +397,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(0) val t1 = Integer(0)
val t2 = Integer(0) val t2 = Integer(0)
val result = Multiply(t1, t2).evaluate(emptyMap()).first val result = Multiply(t1, t2).simplify(emptyMap())
assertEquals(Integer(0), result, "0 * 0 should be equal to 0") assertEquals(Integer(0), result.to, "0 * 0 should be equal to 0")
} }
@Test @Test
@ -388,9 +407,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(1) val t1 = Integer(1)
val t2 = Integer(0) val t2 = Integer(0)
val result = Multiply(t1, t2).evaluate(emptyMap()).first val result = Multiply(t1, t2).simplify(emptyMap())
assertEquals(Integer(0), result, "1 * 0 should be equal to 0") assertEquals(Integer(0), result.to, "1 * 0 should be equal to 0")
} }
@Test @Test
@ -398,9 +417,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(0) val t1 = Integer(0)
val t2 = Integer(1) val t2 = Integer(1)
val result = Multiply(t1, t2).evaluate(emptyMap()).first val result = Multiply(t1, t2).simplify(emptyMap())
assertEquals(Integer(0), result, "0 * 1 should be equal to 0") assertEquals(Integer(0), result.to, "0 * 1 should be equal to 0")
} }
@Test @Test
@ -408,9 +427,9 @@ class ArithmeticOperatorsTests {
val t1 = Integer(3) val t1 = Integer(3)
val t2 = Integer(1) val t2 = Integer(1)
val result = Multiply(t1, t2).evaluate(emptyMap()).first val result = Multiply(t1, t2).simplify(emptyMap())
assertEquals(Integer(3), result, "3 * 1 should be equal to 3") assertEquals(Integer(3), result.to, "3 * 1 should be equal to 3")
} }
@Test @Test
@ -418,8 +437,8 @@ class ArithmeticOperatorsTests {
val t1 = Integer(2) val t1 = Integer(2)
val t2 = Integer(3) val t2 = Integer(3)
val result = Multiply(t1, t2).evaluate(emptyMap()) val result = Multiply(t1, t2).simplify(emptyMap())
assertEquals(Integer(6), result.first, "2 * 3 should be equal to 6") assertEquals(Integer(6), result.to, "2 * 3 should be equal to 6")
} }
} }

View file

@ -16,7 +16,7 @@ class UnificationTest {
val variable = Variable("X") val variable = Variable("X")
val atom = Atom("a") val atom = Atom("a")
val result = Equivalent(variable, atom).prove(emptyMap()) val result = Equivalent(variable, atom).satisfy(emptyMap())
assertFalse(result.any(), "Variable and atom should not be equivalent") assertFalse(result.any(), "Variable and atom should not be equivalent")
} }
@ -30,10 +30,11 @@ class UnificationTest {
val atom1 = Atom("a") val atom1 = Atom("a")
val atom2 = Atom("a") val atom2 = Atom("a")
val result = Equivalent(atom1, atom2).prove(emptyMap()) val result = Equivalent(atom1, atom2).satisfy(emptyMap())
assertTrue(result.any(), "Identical atoms should be equivalent") assertTrue(result.any(), "Identical atoms should be equivalent")
assertEquals(0, result.first().size, "No substitutions should be made") assertTrue(result.first().isSuccess, "Result should be successful")
assertEquals(0, result.first().getOrNull()!!.size, "No substitutions should be made")
} }
/** /**
@ -45,7 +46,7 @@ class UnificationTest {
val addition = Add(Integer(1), Integer(2)) val addition = Add(Integer(1), Integer(2))
val solution = Integer(3) val solution = Integer(3)
val result = Equivalent(addition, solution).prove(emptyMap()) val result = Equivalent(addition, solution).satisfy(emptyMap())
assertFalse(result.any(), "Addition should be equivalent") assertFalse(result.any(), "Addition should be equivalent")
} }

View file

@ -3,20 +3,19 @@ package prolog.builtins
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import prolog.Substitutions
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.logic.compound import prolog.logic.compound
import prolog.logic.nonvariable import prolog.logic.nonvariable
import prolog.logic.variable import prolog.logic.variable
import kotlin.test.assertEquals
class VerificationTest { class VerificationTest {
@Test @Test
fun unbound_variable_is_var() { fun unbound_variable_is_var() {
val variable = Variable("X") val variable = Variable("X")
assertTrue(variable(variable)) assertTrue(variable(variable))
assertTrue(variable.alias().isEmpty)
} }
@Test @Test
@ -26,10 +25,8 @@ class VerificationTest {
assertTrue(variable(variable)) assertTrue(variable(variable))
val atom = Atom("a") val atom = Atom("a")
variable.bind(atom)
assertFalse(variable(variable)) assertFalse(variable(variable, mapOf(variable to atom)))
assertEquals(atom, variable.alias().get())
} }
@Test @Test
@ -39,10 +36,8 @@ class VerificationTest {
assertTrue(variable(variable)) assertTrue(variable(variable))
val structure = Structure(Atom("a"), listOf(Atom("b"))) val structure = Structure(Atom("a"), listOf(Atom("b")))
variable.bind(structure)
assertFalse(variable(variable)) assertFalse(variable(variable, mapOf(variable to structure)))
assertEquals(structure, variable.alias().get())
} }
/** /**
@ -52,8 +47,7 @@ class VerificationTest {
@Test @Test
fun variable_bound_to_itself_is_var() { fun variable_bound_to_itself_is_var() {
val variable = Variable("X") val variable = Variable("X")
variable.bind(variable) assertTrue(variable(variable, mapOf(variable to variable)))
assertTrue(variable(variable))
} }
/** /**
@ -64,9 +58,7 @@ class VerificationTest {
fun variable_bound_to_another_variable_is_var() { fun variable_bound_to_another_variable_is_var() {
val variable1 = Variable("X") val variable1 = Variable("X")
val variable2 = Variable("Y") val variable2 = Variable("Y")
variable1.bind(variable2) assertTrue(variable(variable1, mapOf(variable1 to variable2)))
assertTrue(variable(variable1))
assertEquals(variable2, variable1.alias().get())
} }
/** /**
@ -77,9 +69,11 @@ class VerificationTest {
fun variable_bound_to_bound_variable_is_not_var() { fun variable_bound_to_bound_variable_is_not_var() {
val variable1 = Variable("X") val variable1 = Variable("X")
val variable2 = Variable("Y") val variable2 = Variable("Y")
variable2.bind(Atom("a")) val map: Substitutions = mapOf(
variable1.bind(variable2) variable1 to variable2,
assertFalse(variable(variable1)) variable2 to Atom("a")
)
assertFalse(variable(variable1, map))
} }
@Test @Test
@ -117,10 +111,8 @@ class VerificationTest {
assertFalse(nonvariable(variable)) assertFalse(nonvariable(variable))
val atom = Atom("a") val atom = Atom("a")
variable.bind(atom)
assertTrue(nonvariable(variable)) assertTrue(nonvariable(variable, mapOf(variable to atom)))
assertEquals(atom, variable.alias().get())
} }
/** /**
@ -134,10 +126,8 @@ class VerificationTest {
assertFalse(nonvariable(variable)) assertFalse(nonvariable(variable))
val structure = Structure(Atom("a"), listOf(Atom("b"))) val structure = Structure(Atom("a"), listOf(Atom("b")))
variable.bind(structure)
assertTrue(nonvariable(variable)) assertTrue(nonvariable(variable, mapOf(variable to structure)))
assertEquals(structure, variable.alias().get())
} }
/** /**
@ -147,8 +137,7 @@ class VerificationTest {
@Test @Test
fun variable_bound_to_itself_is_not_nonvar() { fun variable_bound_to_itself_is_not_nonvar() {
val variable = Variable("X") val variable = Variable("X")
variable.bind(variable) assertFalse(nonvariable(variable, mapOf(variable to variable)))
assertFalse(nonvariable(variable))
} }
/** /**
@ -159,8 +148,7 @@ class VerificationTest {
fun variable_bound_to_another_variable_is_not_nonvar() { fun variable_bound_to_another_variable_is_not_nonvar() {
val variable1 = Variable("X") val variable1 = Variable("X")
val variable2 = Variable("Y") val variable2 = Variable("Y")
variable1.bind(variable2) assertFalse(nonvariable(variable1, mapOf(variable1 to variable2)))
assertFalse(nonvariable(variable1))
} }
@Test @Test
@ -195,8 +183,7 @@ class VerificationTest {
fun bound_variable_to_atom_is_not_compound() { fun bound_variable_to_atom_is_not_compound() {
val variable = Variable("X") val variable = Variable("X")
val atom = Atom("a") val atom = Atom("a")
variable.bind(atom) assertFalse(compound(variable, mapOf(variable to atom)))
assertFalse(compound(variable))
} }
/** /**
@ -207,8 +194,7 @@ class VerificationTest {
fun bound_variable_to_compound_term_is_compound() { fun bound_variable_to_compound_term_is_compound() {
val variable = Variable("X") val variable = Variable("X")
val structure = Structure(Atom("a"), listOf(Atom("b"))) val structure = Structure(Atom("a"), listOf(Atom("b")))
variable.bind(structure) assertTrue(compound(variable, mapOf(variable to structure)))
assertTrue(compound(variable))
} }
/** /**
@ -221,13 +207,12 @@ class VerificationTest {
val variable2 = Variable("Y") val variable2 = Variable("Y")
val structure = Structure(Atom("a"), listOf(Atom("b"))) val structure = Structure(Atom("a"), listOf(Atom("b")))
variable2.bind(structure) val subs: Substitutions = mapOf(
variable1.bind(variable2) variable1 to variable2,
variable2 to structure
)
assertTrue(compound(variable1)) assertTrue(compound(variable1, subs))
assertEquals(structure, variable2.alias().get())
assertEquals(variable2, variable1.alias().get())
} }
@Test @Test

View file

@ -4,7 +4,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.RepeatedTest
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import prolog.Substitutions
import prolog.ast.terms.Integer import prolog.ast.terms.Integer
import prolog.ast.terms.Term import prolog.ast.terms.Term
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
@ -15,7 +15,11 @@ class ArithmeticTests {
val result = between(Integer(0), Integer(2), Integer(1)) val result = between(Integer(0), Integer(2), Integer(1))
assertTrue(result.any(), "Expected 1 to be between 0 and 2") assertTrue(result.any(), "Expected 1 to be between 0 and 2")
assertEquals(emptyMap<Variable, Term>(), result.first(), "Expected no substitutions") assertTrue(result.first().isSuccess, "Expected success")
val subs = result.first().getOrNull()!!
assertEquals(emptyMap<Variable, Term>(), subs, "Expected no substitutions")
} }
@Test @Test
@ -39,9 +43,11 @@ class ArithmeticTests {
assertEquals(expectedResults.size, actualResults.size, "Expected 3 results") assertEquals(expectedResults.size, actualResults.size, "Expected 3 results")
for ((expected, actual) in expectedResults.zip(actualResults)) { for ((expected, actual) in expectedResults.zip(actualResults)) {
for ((key, value) in expected) { for ((key, value) in expected) {
assertTrue(actual.isSuccess, "Expected success")
val actual = actual.getOrNull()!!
assertTrue(actual.containsKey(key), "Expected key $key to be present") assertTrue(actual.containsKey(key), "Expected key $key to be present")
assertTrue( assertTrue(
equivalent(value, actual[key]!!), equivalent(value, actual[key]!!, emptyMap()),
"Expected value $value for key $key, but got ${actual[key]}" "Expected value $value for key $key, but got ${actual[key]}"
) )
} }
@ -93,20 +99,6 @@ class ArithmeticTests {
val t1 = Variable("X") val t1 = Variable("X")
val t2 = Integer(2) val t2 = Integer(2)
t1.bind(Integer(1))
val result = succ(t1, t2, emptyMap()).toList()
assertEquals(1, result.size, "Expected X + 1 to be equal to 2")
assertTrue(result[0].isSuccess, "Expected success")
assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected no substitutions")
}
@Test
fun `succ(bound-to-1-var) is 2 by map`() {
val t1 = Variable("X")
val t2 = Integer(2)
val result = succ(t1, t2, mapOf(t1 to Integer(1))).toList() val result = succ(t1, t2, mapOf(t1 to Integer(1))).toList()
assertEquals(1, result.size, "Expected X + 1 to be equal to 2") assertEquals(1, result.size, "Expected X + 1 to be equal to 2")
@ -119,20 +111,6 @@ class ArithmeticTests {
val t1 = Variable("X") val t1 = Variable("X")
val t2 = Variable("Y") val t2 = Variable("Y")
t1.bind(Integer(1))
val result = succ(t1, t2, emptyMap()).toList()
assertEquals(1, result.size, "Expected X + 1 to be equal to Y")
assertTrue(result[0].isSuccess, "Expected success")
assertEquals(Integer(2), result[0].getOrNull()!![t2], "Expected Y to be equal to 2")
}
@Test
fun `succ(bound-to-1-var) is var by map`() {
val t1 = Variable("X")
val t2 = Variable("Y")
val result = succ(t1, t2, mapOf(t1 to Integer(1))).toList() val result = succ(t1, t2, mapOf(t1 to Integer(1))).toList()
assertEquals(1, result.size, "Expected X + 1 to be equal to Y") assertEquals(1, result.size, "Expected X + 1 to be equal to Y")
@ -219,21 +197,6 @@ class ArithmeticTests {
val t2 = Integer(2) val t2 = Integer(2)
val t3 = Variable("X") val t3 = Variable("X")
t3.bind(Integer(3))
val result = plus(t1, t2, t3, emptyMap()).toList()
assertEquals(1, result.size, "1 + 2 should be equal to X")
assertTrue(result[0].isSuccess, "Expected success")
assertTrue(result[0].getOrNull()!!.isEmpty(), "t3 should not be rebound")
}
@Test
fun `1 plus 2 is bound-to-3-var by map`() {
val t1 = Integer(1)
val t2 = Integer(2)
val t3 = Variable("X")
val result = plus(t1, t2, t3, mapOf(Variable("X") to Integer(3))).toList() val result = plus(t1, t2, t3, mapOf(Variable("X") to Integer(3))).toList()
assertEquals(1, result.size, "1 + 2 should be equal to X") assertEquals(1, result.size, "1 + 2 should be equal to X")
@ -247,9 +210,7 @@ class ArithmeticTests {
val t2 = Integer(2) val t2 = Integer(2)
val t3 = Variable("X") val t3 = Variable("X")
t3.bind(Integer(4)) val result = plus(t1, t2, t3, mapOf(t3 to Integer(4)))
val result = plus(t1, t2, t3, emptyMap())
assertTrue(result.none(), "1 + 2 should not be equal to X") assertTrue(result.none(), "1 + 2 should not be equal to X")
} }
@ -260,9 +221,7 @@ class ArithmeticTests {
val t2 = Variable("X") val t2 = Variable("X")
val t3 = Integer(3) val t3 = Integer(3)
t2.bind(Integer(2)) val result = plus(t1, t2, t3, mapOf(t2 to Integer(2))).toList()
val result = plus(t1, t2, t3, emptyMap()).toList()
assertEquals(1, result.size, "1 + X should be equal to 3") assertEquals(1, result.size, "1 + X should be equal to 3")
assertTrue(result[0].isSuccess, "Expected success") assertTrue(result[0].isSuccess, "Expected success")
@ -275,9 +234,7 @@ class ArithmeticTests {
val t2 = Variable("X") val t2 = Variable("X")
val t3 = Integer(4) val t3 = Integer(4)
t2.bind(Integer(2)) val result = plus(t1, t2, t3, mapOf(t2 to Integer(2)))
val result = plus(t1, t2, t3, emptyMap())
assertTrue(result.none(), "1 + X should not be equal to 4") assertTrue(result.none(), "1 + X should not be equal to 4")
} }
@ -288,9 +245,7 @@ class ArithmeticTests {
val t2 = Integer(2) val t2 = Integer(2)
val t3 = Integer(3) val t3 = Integer(3)
t1.bind(Integer(1)) val result = plus(t1, t2, t3, mapOf(t1 to Integer(1))).toList()
val result = plus(t1, t2, t3, emptyMap()).toList()
assertEquals(1, result.size, "X + 2 should be equal to 3") assertEquals(1, result.size, "X + 2 should be equal to 3")
assertTrue(result[0].isSuccess, "Expected success") assertTrue(result[0].isSuccess, "Expected success")
@ -303,9 +258,7 @@ class ArithmeticTests {
val t2 = Integer(2) val t2 = Integer(2)
val t3 = Integer(4) val t3 = Integer(4)
t1.bind(Integer(1)) val result = plus(t1, t2, t3, mapOf(t1 to Integer(1)))
val result = plus(t1, t2, t3, emptyMap())
assertTrue(result.none(), "X + 2 should not be equal to 4") assertTrue(result.none(), "X + 2 should not be equal to 4")
} }
@ -329,14 +282,16 @@ class ArithmeticTests {
val t2 = Variable("Y") val t2 = Variable("Y")
val t3 = Variable("Z") val t3 = Variable("Z")
t1.bind(Integer(1)) val map: Substitutions = mapOf(
t2.bind(Integer(2)) t1 to Integer(1),
t2 to Integer(2),
)
val result = plus(t1, t2, t3, emptyMap()).toList() val result = plus(t1, t2, t3, map).toList()
assertTrue(result.isNotEmpty(), "X + Y should be equal to Z") assertTrue(result.isNotEmpty(), "X + Y should be equal to Z")
assertTrue(result[0].isSuccess, "Expected success") assertTrue(result[0].isSuccess, "Expected success")
assertTrue(equivalent(result[0].getOrThrow()[t3]!!, Integer(3)), "Z should be equal to 3") assertTrue(equivalent(result[0].getOrThrow()[t3]!!, Integer(3), result[0].getOrNull()!!), "Z should be equal to 3")
} }
@Test @Test

View file

@ -3,10 +3,12 @@ package prolog.logic
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import prolog.Substitutions
import prolog.ast.terms.Integer import prolog.ast.terms.Integer
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.builtins.Add
/* /*
* Based on: https://en.wikipedia.org/wiki/Unification_%28computer_science%29#Examples_of_syntactic_unification_of_first-order_terms * Based on: https://en.wikipedia.org/wiki/Unification_%28computer_science%29#Examples_of_syntactic_unification_of_first-order_terms
@ -19,8 +21,8 @@ class UnifyTest {
val result = unify(atom1, atom2) val result = unify(atom1, atom2)
assertTrue(result.isPresent, "Identical atoms should unify") assertTrue(result.isSuccess, "Identical atoms should unify")
assertEquals(0, result.get().size, "No substitutions should be made") assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
} }
@Test @Test
@ -30,7 +32,7 @@ class UnifyTest {
val result = unify(atom1, atom2) val result = unify(atom1, atom2)
assertFalse(result.isPresent, "Different atoms should not unify") assertFalse(result.isSuccess, "Different atoms should not unify")
} }
/** /**
@ -44,8 +46,8 @@ class UnifyTest {
val result = unify(variable1, variable2) val result = unify(variable1, variable2)
assertTrue(result.isPresent, "Identical variables should unify") assertTrue(result.isSuccess, "Identical variables should unify")
assertEquals(0, result.get().size, "No substitutions should be made") assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
} }
@Test @Test
@ -55,9 +57,9 @@ class UnifyTest {
val result = unify(atom, variable) val result = unify(atom, variable)
assertTrue(result.isPresent, "Variable should unify with atom") assertTrue(result.isSuccess, "Variable should unify with atom")
assertEquals(1, result.get().size, "There should be one substitution") assertEquals(1, result.getOrNull()!!.size, "There should be one substitution")
assertEquals(atom, variable.alias().get(), "Variable should be substituted with atom") assertEquals(atom, result.getOrNull()!![variable], "Variable should be substituted with atom")
} }
@Test @Test
@ -67,9 +69,9 @@ class UnifyTest {
val result = unify(variable1, variable2) val result = unify(variable1, variable2)
assertTrue(result.isPresent) assertTrue(result.isSuccess)
assertEquals(1, result.get().size) assertEquals(1, result.getOrNull()!!.size)
assertEquals(variable2, variable1.alias().get(), "Variable 1 should alias to variable 2") assertEquals(variable2, result.getOrNull()!![variable1], "Variable 1 should alias to variable 2")
} }
@Test @Test
@ -79,8 +81,8 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Identical compound terms should unify") assertTrue(result.isSuccess, "Identical compound terms should unify")
assertEquals(0, result.get().size, "No substitutions should be made") assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
} }
@Test @Test
@ -90,7 +92,7 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertFalse(result.isPresent, "Different compound terms should not unify") assertFalse(result.isSuccess, "Different compound terms should not unify")
} }
@Test @Test
@ -100,7 +102,7 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertFalse(result.isPresent, "Compound terms with different functors should not unify") assertFalse(result.isSuccess, "Compound terms with different functors should not unify")
} }
/** /**
@ -114,10 +116,14 @@ class UnifyTest {
val result = unify(variable, structure) val result = unify(variable, structure)
assertTrue(result.isPresent, "Variable should unify with compound term") assertTrue(result.isSuccess, "Variable should unify with compound term")
assertEquals(1, result.get().size, "There should be one substitution")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable), "Variable should be in the substitution map")
assertTrue( assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Atom("b"))), variable.alias().get()), equivalent(Structure(Atom("f"), listOf(Atom("a"), Atom("b"))), subs[variable]!!, subs),
"Variable should be substituted with compound term" "Variable should be substituted with compound term"
) )
} }
@ -130,9 +136,13 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Compound term with variable should unify with part") assertTrue(result.isSuccess, "Compound term with variable should unify with part")
assertEquals(1, result.get().size, "There should be one substitution")
val equivalence = equivalent(Atom("b"), variable.alias().get()) val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable), "Variable should be in the substitution map")
val equivalence = equivalent(Atom("b"), subs[variable]!!, subs)
assertTrue(equivalence, "Variable should be substituted with atom") assertTrue(equivalence, "Variable should be substituted with atom")
} }
@ -146,9 +156,13 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Compound terms with variable arguments should unify") assertTrue(result.isSuccess, "Compound terms with variable arguments should unify")
assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(variable2, variable1.alias().get(), "Variable 1 should alias to variable 2") val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable1), "Variable 1 should be in the substitution map")
assertEquals(variable2, subs[variable1], "Variable 1 should alias to variable 2")
} }
/** /**
@ -161,7 +175,7 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertFalse(result.isPresent, "Compound terms with different arity should not unify") assertFalse(result.isSuccess, "Compound terms with different arity should not unify")
} }
/** /**
@ -177,10 +191,14 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Nested compound terms with variables should unify") assertTrue(result.isSuccess, "Nested compound terms with variables should unify")
assertEquals(1, result.get().size, "There should be one substitution")
val subs = result.getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable2), "Variable 2 should be in the substitution map")
assertTrue( assertTrue(
equivalent(Structure(Atom("g"), listOf(Variable("X"))), variable2.alias().get()), equivalent(Structure(Atom("g"), listOf(Variable("X"))), subs[variable2]!!, subs),
"Variable should be substituted with compound term" "Variable should be substituted with compound term"
) )
} }
@ -200,15 +218,22 @@ class UnifyTest {
val result = unify(structure1, structure2) val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Compound terms with more variables should unify") assertTrue(result.isSuccess, "Compound terms with more variables should unify")
assertEquals(2, result.get().size, "There should be two substitutions")
val subs = result.getOrNull()!!
assertEquals(2, subs.size, "There should be two substitutions")
assertTrue(subs.containsKey(variable1), "Variable 1 should be in the substitution map")
assertTrue( assertTrue(
equivalent(Atom("a"), variable1.alias().get()), equivalent(Atom("a"), subs[variable1]!!, subs),
"Variable 1 should be substituted with atom" "Variable 1 should be substituted with atom"
) )
val equivalence = equivalent(Structure(Atom("g"), listOf(Atom("a"))), variable2.alias().get()) assertTrue(subs.containsKey(variable2), "Variable 2 should be in the substitution map")
assertTrue(equivalence, "Variable 2 should be substituted with compound term") assertTrue(
equivalent(Structure(Atom("g"), listOf(Atom("a"))), subs[variable2]!!, subs),
"Variable 2 should be substituted with compound term"
)
} }
/** /**
@ -220,11 +245,16 @@ class UnifyTest {
val variable1 = Variable("X") val variable1 = Variable("X")
val structure2 = Structure(Atom("f"), listOf(Variable("X"))) val structure2 = Structure(Atom("f"), listOf(Variable("X")))
val result = unify(variable1, structure2) val result = unifyLazy(variable1, structure2, emptyMap()).toList()
assertTrue(result.isPresent, "Recursive unification should succeed") assertEquals(1, result.size, "There should be one result")
assertEquals(1, result.get().size, "There should be one substitution") assertTrue(result[0].isSuccess, "Recursive unification should succeed")
assertEquals(structure2, variable1.alias().get(), "Variable should be substituted with compound term")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "There should be one substitution")
assertTrue(subs.containsKey(variable1), "Variable should be in the substitution map")
assertEquals(structure2, subs[variable1], "Variable should be substituted with compound term")
} }
/** /**
@ -238,13 +268,15 @@ class UnifyTest {
val variable2 = Variable("Y") val variable2 = Variable("Y")
val atom = Atom("bar") val atom = Atom("bar")
variable1.bind(atom) val map: Substitutions = mapOf(
variable2.bind(atom) variable1 to atom,
variable2 to atom
)
val result = unifyLazy(variable1, variable2, map).toList()
val result = unify(variable1, variable2) assertEquals(1, result.size, "There should be one substitution")
assertTrue(result[0].isSuccess, "Multiple unification should succeed")
assertTrue(result.isPresent, "Multiple unification should succeed") assertEquals(0, result[0].getOrNull()!!.size, "No (additional) substitutions should be made")
assertEquals(0, result.get().size, "No substitutions should be made")
} }
/** /**
@ -258,7 +290,7 @@ class UnifyTest {
val result = unify(atom1, structure2) val result = unify(atom1, structure2)
assertFalse(result.isPresent, "Atom with different arity should not unify") assertFalse(result.isSuccess, "Atom with different arity should not unify")
} }
@Test @Test
@ -268,8 +300,8 @@ class UnifyTest {
val result = unify(int1, int2) val result = unify(int1, int2)
assertTrue(result.isPresent, "Identical integers should unify") assertTrue(result.isSuccess, "Identical integers should unify")
assertEquals(0, result.get().size, "No substitutions should be made") assertEquals(0, result.getOrNull()!!.size, "No substitutions should be made")
} }
@Test @Test
@ -279,6 +311,17 @@ class UnifyTest {
val result = unify(int1, int2) val result = unify(int1, int2)
assertFalse(result.isPresent, "Different integers should not unify") assertFalse(result.isSuccess, "Different integers should not unify")
}
@Test
fun `1 + 2 does not unify with 3`() {
val expr1 = Add(Integer(1), Integer(2))
val expr2 = Integer(3)
val result = unify(expr1, expr2)
assertFalse(result.isSuccess, "1 + 2 should not unify with 3")
} }
} }