refactor: Rework
This commit is contained in:
parent
ac55ed4c64
commit
6469dd6ced
34 changed files with 593 additions and 552 deletions
|
@ -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()
|
||||||
|
|
11
src/prolog/Substitution.kt
Normal file
11
src/prolog/Substitution.kt
Normal 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>
|
|
@ -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>
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
6
src/prolog/ast/arithmetic/Simplification.kt
Normal file
6
src/prolog/ast/arithmetic/Simplification.kt
Normal 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)
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
14
src/prolog/ast/logic/Satisfiable.kt
Normal file
14
src/prolog/ast/logic/Satisfiable.kt
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
5
src/prolog/ast/terms/VariableBinding.kt
Normal file
5
src/prolog/ast/terms/VariableBinding.kt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package prolog.ast.terms
|
||||||
|
|
||||||
|
import prolog.Substitution
|
||||||
|
|
||||||
|
class VariableBinding(from: Variable, to: Term) : Substitution(from, to)
|
|
@ -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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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(False, result, "1 should not be equal to 2")
|
assertEquals(0, result.size, "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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in a new issue