Checkpoint
This commit is contained in:
parent
e3c84e1761
commit
e73e5cbfc8
32 changed files with 1354 additions and 92 deletions
|
@ -2,12 +2,10 @@ package prolog
|
||||||
|
|
||||||
import prolog.logic.Substituted
|
import prolog.logic.Substituted
|
||||||
import prolog.ast.logic.Clause
|
import prolog.ast.logic.Clause
|
||||||
import prolog.ast.logic.Fact
|
|
||||||
import prolog.ast.logic.Predicate
|
import prolog.ast.logic.Predicate
|
||||||
import prolog.ast.logic.Resolvent
|
import prolog.ast.logic.Resolvent
|
||||||
import prolog.ast.terms.Functor
|
import prolog.ast.terms.Functor
|
||||||
import prolog.ast.terms.Goal
|
import prolog.ast.terms.Goal
|
||||||
import prolog.builtins.True
|
|
||||||
|
|
||||||
typealias Database = Program
|
typealias Database = Program
|
||||||
|
|
||||||
|
@ -24,7 +22,6 @@ object Program: Resolvent {
|
||||||
private fun setup() {
|
private fun setup() {
|
||||||
// Initialize the program with built-in predicates
|
// Initialize the program with built-in predicates
|
||||||
load(listOf(
|
load(listOf(
|
||||||
Fact(True())
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
src/prolog/ast/arithmetic/ArithmeticOperator.kt
Normal file
12
src/prolog/ast/arithmetic/ArithmeticOperator.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package prolog.ast.arithmetic
|
||||||
|
|
||||||
|
import prolog.ast.terms.Atom
|
||||||
|
import prolog.ast.terms.Operator
|
||||||
|
import prolog.ast.terms.Term
|
||||||
|
import prolog.logic.Substituted
|
||||||
|
|
||||||
|
abstract class ArithmeticOperator(symbol: Atom, leftOperand: Expression, rightOperand: Expression) :
|
||||||
|
Operator(symbol, leftOperand, rightOperand), Expression {
|
||||||
|
// Operators should overload the evaluate method to perform the operation
|
||||||
|
abstract override fun evaluate(subs: Substituted): Pair<Term, Substituted>
|
||||||
|
}
|
5
src/prolog/ast/arithmetic/Expression.kt
Normal file
5
src/prolog/ast/arithmetic/Expression.kt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package prolog.ast.arithmetic
|
||||||
|
|
||||||
|
import prolog.ast.terms.Term
|
||||||
|
|
||||||
|
interface Expression : Term
|
|
@ -37,7 +37,7 @@ abstract class Clause(private val head: Head, private val body: Body) : Resolven
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return when {
|
return when {
|
||||||
body == True() -> head.toString()
|
body is True -> head.toString()
|
||||||
else -> "$head :- $body"
|
else -> "$head :- $body"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,4 @@ package prolog.ast.logic
|
||||||
import prolog.ast.terms.Head
|
import prolog.ast.terms.Head
|
||||||
import prolog.builtins.True
|
import prolog.builtins.True
|
||||||
|
|
||||||
class Fact(head: Head) : Clause(head, True())
|
class Fact(head: Head) : Clause(head, True)
|
5
src/prolog/ast/logic/LogicOperand.kt
Normal file
5
src/prolog/ast/logic/LogicOperand.kt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package prolog.ast.logic
|
||||||
|
|
||||||
|
import prolog.ast.terms.Operand
|
||||||
|
|
||||||
|
abstract class LogicOperand : Operand, Provable
|
13
src/prolog/ast/logic/LogicOperator.kt
Normal file
13
src/prolog/ast/logic/LogicOperator.kt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package prolog.ast.logic
|
||||||
|
|
||||||
|
import prolog.ast.terms.Atom
|
||||||
|
import prolog.ast.terms.Operator
|
||||||
|
import prolog.logic.Substituted
|
||||||
|
|
||||||
|
abstract class LogicOperator(
|
||||||
|
symbol: Atom,
|
||||||
|
leftOperand: LogicOperand? = null,
|
||||||
|
rightOperand: LogicOperand
|
||||||
|
) : Operator(symbol, leftOperand, rightOperand), Provable {
|
||||||
|
abstract override fun prove(subs: Substituted): Sequence<Substituted>
|
||||||
|
}
|
|
@ -9,6 +9,11 @@ open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
|
||||||
|
|
||||||
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = unifyLazy(goal, this, subs)
|
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = unifyLazy(goal, this, subs)
|
||||||
|
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> = Pair(this, emptyMap())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See also [SWI Prolog Standard Order of Terms](https://www.swi-prolog.org/pldoc/man?section=standardorder)
|
||||||
|
*/
|
||||||
override fun compareTo(other: Term): Int {
|
override fun compareTo(other: Term): Int {
|
||||||
return when (other) {
|
return when (other) {
|
||||||
is Variable -> 1
|
is Variable -> 1
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package prolog.ast.terms
|
package prolog.ast.terms
|
||||||
|
|
||||||
import prolog.Program
|
import prolog.Program
|
||||||
import prolog.ast.logic.Provable
|
import prolog.ast.logic.LogicOperand
|
||||||
import prolog.logic.Substituted
|
import prolog.logic.Substituted
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,7 @@ import prolog.logic.Substituted
|
||||||
* A goal either [succeeds][prolog.builtins.True], in which case the variables in the compound terms have a binding,
|
* A goal either [succeeds][prolog.builtins.True], in which case the variables in the compound terms have a binding,
|
||||||
* or it fails if Prolog fails to prove it.
|
* or it fails if Prolog fails to prove it.
|
||||||
*/
|
*/
|
||||||
abstract class Goal : Term, Provable {
|
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 prove(subs: Substituted): Sequence<Substituted> = Program.solve(this, subs)
|
||||||
|
|
32
src/prolog/ast/terms/Integer.kt
Normal file
32
src/prolog/ast/terms/Integer.kt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package prolog.ast.terms
|
||||||
|
|
||||||
|
import prolog.ast.arithmetic.Expression
|
||||||
|
import prolog.logic.Substituted
|
||||||
|
|
||||||
|
data class Integer(val value: Int): Term, Expression {
|
||||||
|
/**
|
||||||
|
* See also [SWI Prolog Standard Order of Terms](https://www.swi-prolog.org/pldoc/man?section=standardorder)
|
||||||
|
*/
|
||||||
|
override fun compareTo(other: Term): Int {
|
||||||
|
return when (other) {
|
||||||
|
is Variable -> 1
|
||||||
|
is Integer -> value.compareTo(other.value)
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integers are already evaluated
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> = Pair(this, emptyMap())
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plus(other: Integer): Integer {
|
||||||
|
return Integer(value + other.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun minus(other: Integer): Integer {
|
||||||
|
return Integer(value - other.value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,16 @@
|
||||||
package prolog.ast.terms
|
package prolog.ast.terms
|
||||||
|
|
||||||
import prolog.ast.logic.Provable
|
typealias Operand = Term
|
||||||
import prolog.logic.Substituted
|
|
||||||
|
|
||||||
abstract class Operator(
|
abstract class Operator(
|
||||||
private val symbol: Atom,
|
private val symbol: Atom,
|
||||||
val leftOperand: Operand? = null,
|
private val leftOperand: Operand? = null,
|
||||||
val rightOperand: Operand
|
private val rightOperand: Operand
|
||||||
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Provable {
|
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)) {
|
||||||
abstract override fun prove(subs: Substituted): Sequence<Substituted>
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return when (leftOperand) {
|
return when (leftOperand) {
|
||||||
null -> "${symbol.name} $rightOperand"
|
null -> "${symbol.name} $rightOperand"
|
||||||
else -> "$leftOperand ${symbol.name} $rightOperand"
|
else -> "($leftOperand ${symbol.name} $rightOperand)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias Operand = Goal
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package prolog.ast.terms
|
package prolog.ast.terms
|
||||||
|
|
||||||
import prolog.ast.logic.Resolvent
|
import prolog.ast.logic.Resolvent
|
||||||
import prolog.builtins.equivalent
|
import prolog.logic.equivalent
|
||||||
import prolog.logic.Substituted
|
import prolog.logic.Substituted
|
||||||
import prolog.logic.unifyLazy
|
import prolog.logic.unifyLazy
|
||||||
|
|
||||||
|
@ -9,13 +9,19 @@ typealias Argument = Term
|
||||||
|
|
||||||
typealias CompoundTerm = Structure
|
typealias CompoundTerm = Structure
|
||||||
|
|
||||||
open class Structure(val name: Atom, val arguments: List<Argument>) : Goal(), Head, Body, Resolvent {
|
open class Structure(val name: Atom, var arguments: List<Argument>) : Goal(), Head, Body, Resolvent {
|
||||||
override val functor: Functor = "${name.name}/${arguments.size}"
|
override val functor: Functor = "${name.name}/${arguments.size}"
|
||||||
|
|
||||||
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> {
|
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> {
|
||||||
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 {
|
override fun compareTo(other: Term): Int {
|
||||||
when (other) {
|
when (other) {
|
||||||
is Structure -> {
|
is Structure -> {
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
package prolog.ast.terms
|
package prolog.ast.terms
|
||||||
|
|
||||||
|
import prolog.logic.Substituted
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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> {
|
||||||
|
/**
|
||||||
|
* Returns the term that this expression evaluates to. (All the way down.)
|
||||||
|
*/
|
||||||
|
fun evaluate(subs: Substituted): Pair<Term, Substituted>
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package prolog.ast.terms
|
package prolog.ast.terms
|
||||||
|
|
||||||
|
import prolog.ast.arithmetic.Expression
|
||||||
|
import prolog.logic.Substituted
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class Variable(val name: String) : Term {
|
data class Variable(val name: String) : Term, Expression {
|
||||||
private var alias: Optional<Term> = Optional.empty()
|
private var alias: Optional<Term> = Optional.empty()
|
||||||
|
|
||||||
fun alias(): Optional<Term> {
|
fun alias(): Optional<Term> {
|
||||||
|
@ -21,6 +23,16 @@ data class Variable(val name: String) : Term {
|
||||||
alias = Optional.empty()
|
alias = Optional.empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
|
||||||
|
// If the variable is bound, return the value of the binding
|
||||||
|
// If the variable is not bound, return the variable itself
|
||||||
|
return if (alias.isPresent) {
|
||||||
|
alias.get().evaluate(subs)
|
||||||
|
} else {
|
||||||
|
Pair(this, emptyMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun compareTo(other: Term): Int {
|
override fun compareTo(other: Term): Int {
|
||||||
return when (other) {
|
return when (other) {
|
||||||
is Variable -> name.compareTo(other.name)
|
is Variable -> name.compareTo(other.name)
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package prolog.builtins
|
|
152
src/prolog/builtins/arithmeticOperators.kt
Normal file
152
src/prolog/builtins/arithmeticOperators.kt
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package prolog.builtins
|
||||||
|
|
||||||
|
import prolog.ast.arithmetic.ArithmeticOperator
|
||||||
|
import prolog.ast.arithmetic.Expression
|
||||||
|
import prolog.ast.logic.LogicOperand
|
||||||
|
import prolog.ast.logic.LogicOperator
|
||||||
|
import prolog.ast.logic.Provable
|
||||||
|
import prolog.ast.terms.*
|
||||||
|
import prolog.logic.*
|
||||||
|
|
||||||
|
// TODO >
|
||||||
|
|
||||||
|
// TODO <
|
||||||
|
|
||||||
|
// TODO =<
|
||||||
|
|
||||||
|
// TODO >=
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if expression Expr1 evaluates to a number non-equal to Expr2.
|
||||||
|
*/
|
||||||
|
class EvaluatesToDifferent(private val left: Expression, private val right: Expression) :
|
||||||
|
ArithmeticOperator(Atom("=\\="), left, right) {
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
|
||||||
|
val t1 = left.evaluate(subs)
|
||||||
|
val t2 = right.evaluate(subs)
|
||||||
|
|
||||||
|
// Should both be instantiated
|
||||||
|
if (!atomic(t1.first) || !atomic(t2.first)) {
|
||||||
|
throw IllegalArgumentException("Both operands must be instantiated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (equivalent(t1.first, t2.first)) {
|
||||||
|
Pair(False, emptyMap())
|
||||||
|
} else {
|
||||||
|
Pair(True, t1.second + t2.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if Expr1 evaluates to a number equal to Expr2.
|
||||||
|
*/
|
||||||
|
class EvaluatesTo(private val left: Expression, private val right: Expression) :
|
||||||
|
ArithmeticOperator(Atom("=:="), left, right) {
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
|
||||||
|
val t1 = left.evaluate(subs)
|
||||||
|
val t2 = right.evaluate(subs)
|
||||||
|
|
||||||
|
// Should both be instantiated
|
||||||
|
if (!atomic(t1.first) || !atomic(t2.first)) {
|
||||||
|
throw IllegalArgumentException("Both operands must be instantiated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (equivalent(t1.first, t2.first)) {
|
||||||
|
Pair(True, t1.second + t2.second)
|
||||||
|
} else {
|
||||||
|
Pair(False, emptyMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when Number is the value to which Expr evaluates.
|
||||||
|
*/
|
||||||
|
class Is(private val left: Expression, private val right: Expression) :
|
||||||
|
Operator(Atom("is"), left, right), Provable {
|
||||||
|
override fun prove(subs: Substituted): Sequence<Substituted> {
|
||||||
|
val t1 = left.evaluate(subs)
|
||||||
|
val t2 = right.evaluate(subs)
|
||||||
|
|
||||||
|
if (!atomic(t2.first)) {
|
||||||
|
throw IllegalArgumentException("Arguments are not sufficiently instantiated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return unifyLazy(t1.first, t2.first, subs).map{ it + t1.second + t2.second }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result = -Expr
|
||||||
|
*/
|
||||||
|
class Negate(operand: Expression) : Subtract(Integer(0), operand)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result = Expr
|
||||||
|
*/
|
||||||
|
class Positive(operand: Expression) : Add(Integer(0), operand)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result = Expr1 + Expr2
|
||||||
|
*/
|
||||||
|
open class Add(private val expr1: Expression, private val expr2: Expression) :
|
||||||
|
ArithmeticOperator(Atom("+"), expr1, expr2) {
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
|
||||||
|
val result = Variable("Result")
|
||||||
|
val map = plus(expr1, expr2, result, subs)
|
||||||
|
return result.evaluate(map.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result = Expr1 - Expr2
|
||||||
|
*/
|
||||||
|
open class Subtract(private val expr1: Expression, private val expr2: Expression) :
|
||||||
|
ArithmeticOperator(Atom("-"), expr1, expr2) {
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
|
||||||
|
val result = Variable("Result")
|
||||||
|
val map = plus(expr2, result, expr1, subs)
|
||||||
|
return result.evaluate(map.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Expr * Expr
|
||||||
|
/**
|
||||||
|
* Result = Expr1 * Expr2
|
||||||
|
*/
|
||||||
|
class Multiply(private val expr1: Expression, private val expr2: Expression) :
|
||||||
|
ArithmeticOperator(Atom("*"), expr1, expr2) {
|
||||||
|
override fun evaluate(subs: Substituted): Pair<Term, Substituted> {
|
||||||
|
val result = Variable("Result")
|
||||||
|
val map = mul(expr1, expr2, result, subs)
|
||||||
|
return result.evaluate(map.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Expr / Expr
|
||||||
|
|
||||||
|
// TODO Expr mod Expr
|
||||||
|
|
||||||
|
// TODO Expr rem Expr
|
||||||
|
|
||||||
|
class Between(private val expr1: Expression, private val expr2: Expression, private val expr3: Expression) :
|
||||||
|
Operator(Atom("between"), expr1, expr2) {
|
||||||
|
override fun prove(subs: Substituted): Sequence<Substituted> {
|
||||||
|
val e1 = expr1.evaluate(subs)
|
||||||
|
val e2 = expr2.evaluate(subs)
|
||||||
|
val e3 = expr3.evaluate(subs)
|
||||||
|
|
||||||
|
require(e1.first is Integer && e2.first is Integer) { "Arguments must be integers" }
|
||||||
|
|
||||||
|
val v1 = e1.first as Integer
|
||||||
|
val v2 = e2.first as Integer
|
||||||
|
|
||||||
|
return if (variable(e3.first)) {
|
||||||
|
between(v1, v2, e3.first as Variable).map { it + e1.second + e2.second }
|
||||||
|
} else {
|
||||||
|
between(v1, v2, e3.first as Integer).map { it + e1.second + e2.second }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,16 @@
|
||||||
package prolog.builtins
|
package prolog.builtins
|
||||||
|
|
||||||
import prolog.ast.terms.*
|
import prolog.ast.logic.LogicOperand
|
||||||
|
import prolog.ast.terms.Atom
|
||||||
|
import prolog.ast.terms.Body
|
||||||
|
import prolog.ast.terms.Goal
|
||||||
|
import prolog.ast.logic.LogicOperator
|
||||||
import prolog.logic.Substituted
|
import prolog.logic.Substituted
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Always fail.
|
* Always fail.
|
||||||
*/
|
*/
|
||||||
class Fail : Atom("fail"), Body {
|
object Fail : Atom("fail"), Body {
|
||||||
override fun prove(subs: Substituted): Sequence<Substituted> = emptySequence()
|
override fun prove(subs: Substituted): Sequence<Substituted> = emptySequence()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +22,7 @@ typealias False = Fail
|
||||||
/**
|
/**
|
||||||
* Always succeed.
|
* Always succeed.
|
||||||
*/
|
*/
|
||||||
class True : Atom("true"), Body {
|
object True : Atom("true"), Body {
|
||||||
override fun prove(subs: Substituted): Sequence<Substituted> = sequenceOf(emptyMap())
|
override fun prove(subs: Substituted): Sequence<Substituted> = sequenceOf(emptyMap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,34 +33,29 @@ class 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(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(","), leftOperand, rightOperand) {
|
class Conjunction(private val left: LogicOperand, private val right: LogicOperand) : LogicOperator(Atom(","), left, right) {
|
||||||
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
|
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
|
||||||
if (leftOperand != null) {
|
left.prove(subs).forEach { left ->
|
||||||
leftOperand.prove(subs).forEach { left ->
|
right.prove(subs + left).forEach { right ->
|
||||||
rightOperand.prove(subs + left).forEach { right ->
|
|
||||||
yield(left + right)
|
yield(left + right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
yieldAll(rightOperand.prove(subs))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disjunction (or). True if either Goal1 or Goal2 succeeds.
|
* Disjunction (or). True if either Goal1 or Goal2 succeeds.
|
||||||
*/
|
*/
|
||||||
open class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) {
|
open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) :
|
||||||
|
LogicOperator(Atom(";"), left, right) {
|
||||||
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
|
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
|
||||||
if (leftOperand != null) {
|
yieldAll(left.prove(subs))
|
||||||
yieldAll(leftOperand.prove(subs))
|
yieldAll(right.prove(subs))
|
||||||
}
|
|
||||||
yieldAll(rightOperand.prove(subs))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Use Disjunction instead")
|
@Deprecated("Use Disjunction instead")
|
||||||
class Bar(leftOperand: Operand, rightOperand: Operand) : Disjunction(leftOperand, rightOperand)
|
class Bar(leftOperand: LogicOperand, rightOperand: LogicOperand) : Disjunction(leftOperand, rightOperand)
|
||||||
|
|
||||||
// TODO ->
|
// TODO ->
|
||||||
|
|
||||||
|
@ -65,10 +64,10 @@ class Bar(leftOperand: Operand, rightOperand: Operand) : Disjunction(leftOperand
|
||||||
/**
|
/**
|
||||||
* True if 'Goal' cannot be proven.
|
* True if 'Goal' cannot be proven.
|
||||||
*/
|
*/
|
||||||
class Not(goal: Goal) : Operator(Atom("\\+"), rightOperand = goal) {
|
class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) {
|
||||||
override fun prove(subs: Substituted): Sequence<Substituted> {
|
override fun prove(subs: Substituted): Sequence<Substituted> {
|
||||||
// If the goal can be proven, return an empty sequence
|
// If the goal can be proven, return an empty sequence
|
||||||
if (rightOperand.prove(subs).toList().isNotEmpty()) {
|
if (goal.prove(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
|
|
@ -1 +0,0 @@
|
||||||
package prolog.builtins
|
|
|
@ -1,10 +1,10 @@
|
||||||
package prolog.builtins
|
package prolog.builtins
|
||||||
|
|
||||||
|
import prolog.ast.logic.LogicOperand
|
||||||
import prolog.ast.terms.Atom
|
import prolog.ast.terms.Atom
|
||||||
import prolog.ast.terms.Operand
|
import prolog.ast.logic.LogicOperator
|
||||||
import prolog.ast.terms.Operator
|
|
||||||
import prolog.logic.Substituted
|
import prolog.logic.Substituted
|
||||||
|
|
||||||
class Query(rightOperand: Operand) : Operator(Atom("?-"), null, rightOperand) {
|
class Query(private val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {
|
||||||
override fun prove(subs: Substituted): Sequence<Substituted> = rightOperand.prove(subs)
|
override fun prove(subs: Substituted): Sequence<Substituted> = query.prove(subs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Comparison and Unification of Terms
|
||||||
|
*
|
||||||
|
* [SWI Prolog Documentation](https://www.swi-prolog.org/pldoc/man?section=compare)
|
||||||
|
*/
|
||||||
package prolog.builtins
|
package prolog.builtins
|
||||||
|
|
||||||
import prolog.ast.terms.Atom
|
import prolog.ast.terms.Atom
|
||||||
import prolog.ast.terms.Structure
|
import prolog.ast.terms.Operator
|
||||||
import prolog.ast.terms.Term
|
import prolog.ast.terms.Term
|
||||||
import prolog.ast.terms.Variable
|
import prolog.logic.Substituted
|
||||||
|
import prolog.logic.applySubstitution
|
||||||
|
import prolog.logic.equivalent
|
||||||
|
|
||||||
/**
|
class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) {
|
||||||
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
|
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
|
||||||
*/
|
val t1 = applySubstitution(term1, subs)
|
||||||
fun equivalent(term1: Term, term2: Term): Boolean {
|
val t2 = applySubstitution(term2, subs)
|
||||||
return when {
|
|
||||||
term1 is Variable && term2 is Variable -> term1 == term2
|
if (equivalent(t1, t2)) {
|
||||||
term1 is Variable -> term1.alias().isPresent && equivalent(term1.alias().get(), term2)
|
yield(emptyMap())
|
||||||
term2 is Variable -> term2.alias().isPresent && equivalent(term2.alias().get(), term1)
|
}
|
||||||
term1 is Atom && term2 is Atom -> term1.compareTo(term2) == 0
|
|
||||||
term1 is Structure && term2 is Structure -> term1.compareTo(term2) == 0
|
|
||||||
else -> false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
150
src/prolog/logic/arithmetic.kt
Normal file
150
src/prolog/logic/arithmetic.kt
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package prolog.logic
|
||||||
|
|
||||||
|
import prolog.ast.arithmetic.Expression
|
||||||
|
import prolog.ast.terms.Integer
|
||||||
|
import prolog.ast.terms.Variable
|
||||||
|
import prolog.builtins.Is
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Low and High are integers, High ≥Low.
|
||||||
|
*
|
||||||
|
* 1. If Value is an integer, Low ≤ Value ≤ High.
|
||||||
|
* 2. When Value is a variable it is successively bound to all integers between Low and High.
|
||||||
|
* If High is inf or infinite between/3 is true iff Value ≥ Low, a feature that is particularly interesting for
|
||||||
|
* generating integers from a certain value.
|
||||||
|
*/
|
||||||
|
fun between(
|
||||||
|
low: Integer,
|
||||||
|
high: Integer,
|
||||||
|
value: Integer
|
||||||
|
): Sequence<Substituted> {
|
||||||
|
return if (value.value in low.value..high.value) {
|
||||||
|
sequenceOf(emptyMap())
|
||||||
|
} else {
|
||||||
|
emptySequence()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun between(
|
||||||
|
low: Integer,
|
||||||
|
high: Integer,
|
||||||
|
variable: Variable
|
||||||
|
): Sequence<Substituted> {
|
||||||
|
return sequence {
|
||||||
|
for (i in low.value..high.value) {
|
||||||
|
yield(mapOf(variable to Integer(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if Int2 = Int1 + 1 and Int1 ≥ 0.
|
||||||
|
*
|
||||||
|
* At least one of the arguments must be instantiated to a natural number.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException the domain error not_less_than_zero if called with a negative integer.
|
||||||
|
* E.g. succ(X, 0) fails silently and succ(X, -1) raises a domain error.125
|
||||||
|
*/
|
||||||
|
fun succ(term1: Expression, term2: Expression, subs: Substituted): Sequence<Substituted> {
|
||||||
|
if (term2 is Integer) {
|
||||||
|
require(term2.value >= 0) { "Domain error: not_less_than_zero" }
|
||||||
|
}
|
||||||
|
val result = plus(term1, Integer(1), term2, subs)
|
||||||
|
// If term1 is a variable, we need to check if it is bound to a negative integer
|
||||||
|
return sequence { result.forEach { newSubs ->
|
||||||
|
val t1 = applySubstitution(term1, newSubs)
|
||||||
|
if (t1 is Variable && t1.alias().isPresent) {
|
||||||
|
val e1 = t1.evaluate(subs)
|
||||||
|
if(e1.first is Integer && (e1.first as Integer).value < 0) {
|
||||||
|
return@sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield(newSubs)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if Int3 = Int1 + Int2.
|
||||||
|
*
|
||||||
|
* At least two of the three arguments must be instantiated to integers.
|
||||||
|
*/
|
||||||
|
fun plus(term1: Expression, term2: Expression, term3: Expression, subs: Substituted): Sequence<Substituted> = sequence {
|
||||||
|
val t1 = applySubstitution(term1, subs)
|
||||||
|
val t2 = applySubstitution(term2, subs)
|
||||||
|
val t3 = applySubstitution(term3, subs)
|
||||||
|
|
||||||
|
// At least two arguments must be Integers
|
||||||
|
when {
|
||||||
|
nonvariable(t1) && nonvariable(t2) && nonvariable(t3) -> {
|
||||||
|
val e1 = t1.evaluate(subs)
|
||||||
|
val e2 = t2.evaluate(subs)
|
||||||
|
val e3 = t3.evaluate(subs)
|
||||||
|
|
||||||
|
val int3Value = e1.first as Integer + e2.first as Integer
|
||||||
|
if (int3Value == e3.first as Integer) {
|
||||||
|
yield(e1.second + e2.second + e3.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nonvariable(t1) && nonvariable(t2) && variable(t3) -> {
|
||||||
|
val e1 = t1.evaluate(subs)
|
||||||
|
val e2 = t2.evaluate(subs)
|
||||||
|
|
||||||
|
val int3Value = e1.first as Integer + e2.first as Integer
|
||||||
|
val int3 = t3 as Variable
|
||||||
|
int3.bind(int3Value)
|
||||||
|
yield(mapOf(int3 to int3Value) + e1.second + e2.second)
|
||||||
|
}
|
||||||
|
nonvariable(t1) && variable(t2) && nonvariable(t3) -> {
|
||||||
|
val e1 = t1.evaluate(subs)
|
||||||
|
val e3 = t3.evaluate(subs)
|
||||||
|
|
||||||
|
val int2Value = e3.first as Integer - e1.first as Integer
|
||||||
|
val int2 = t2 as Variable
|
||||||
|
int2.bind(int2Value)
|
||||||
|
yield(mapOf(int2 to int2Value) + e1.second + e3.second)
|
||||||
|
}
|
||||||
|
variable(t1) && nonvariable(t2) && nonvariable(t3) -> {
|
||||||
|
val e2 = t2.evaluate(subs)
|
||||||
|
val e3 = t3.evaluate(subs)
|
||||||
|
|
||||||
|
val int1Value = e3.first as Integer - e2.first as Integer
|
||||||
|
val int1 = t1 as Variable
|
||||||
|
int1.bind(int1Value)
|
||||||
|
yield(mapOf(int1 to int1Value) + e2.second + e3.second)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw IllegalArgumentException("At least two arguments must be instantiated to integers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive implementation of the multiply operator, logical programming-wise.
|
||||||
|
*/
|
||||||
|
fun mul(term1: Expression, term2: Expression, term3: Expression, subs: Substituted): Sequence<Substituted> = sequence {
|
||||||
|
val t1 = applySubstitution(term1, subs)
|
||||||
|
val t2 = applySubstitution(term2, subs)
|
||||||
|
val t3 = applySubstitution(term3, subs)
|
||||||
|
|
||||||
|
// Base case
|
||||||
|
if (equivalent(t2, Integer(0))) {
|
||||||
|
yieldAll(Is(t3, Integer(0)).prove(subs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive case
|
||||||
|
try {
|
||||||
|
val decremented = Variable("Decremented")
|
||||||
|
succ(decremented, t2, subs).forEach { decrementMap ->
|
||||||
|
val multiplied = Variable("Multiplied")
|
||||||
|
mul(t1, decremented, multiplied, subs + decrementMap).forEach { multipliedMap ->
|
||||||
|
yieldAll(plus(t1, multiplied, t3, subs + decrementMap + multipliedMap))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(_: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO divmod
|
||||||
|
|
||||||
|
// TODO nth_integer_root_and_remainder
|
|
@ -1,27 +1,33 @@
|
||||||
package prolog.logic
|
package prolog.logic
|
||||||
|
|
||||||
import prolog.ast.terms.Structure
|
import prolog.ast.arithmetic.Expression
|
||||||
import prolog.ast.terms.Term
|
import prolog.ast.logic.LogicOperator
|
||||||
import prolog.ast.terms.Variable
|
import prolog.ast.terms.*
|
||||||
import prolog.builtins.atomic
|
|
||||||
import prolog.builtins.compound
|
|
||||||
import prolog.builtins.equivalent
|
|
||||||
import prolog.builtins.variable
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
typealias Substituted = Map<Variable, Term>
|
typealias Substituted = Map<Variable, Term>
|
||||||
|
|
||||||
// Apply substitutions to a term
|
// Apply substitutions to a term
|
||||||
private fun applySubstitution(term: Term, substitution: Substituted): Term = when {
|
fun applySubstitution(term: Term, subs: Substituted): Term = when {
|
||||||
variable(term) -> substitution[(term as Variable)] ?: term
|
variable(term) -> subs[(term as Variable)] ?: term
|
||||||
atomic(term) -> term
|
atomic(term) -> term
|
||||||
compound(term) -> {
|
compound(term) -> {
|
||||||
val structure = term as Structure
|
val structure = term as Structure
|
||||||
Structure(structure.name, structure.arguments.map { applySubstitution(it, substitution) })
|
Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) })
|
||||||
}
|
}
|
||||||
else -> term
|
else -> term
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun applySubstitution(expr: Expression, subs: Substituted): Expression = when {
|
||||||
|
variable(expr) -> applySubstitution(expr as Term, subs) as Expression
|
||||||
|
atomic(expr) -> expr
|
||||||
|
expr is LogicOperator -> {
|
||||||
|
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
else -> expr
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a variable occurs in a term
|
// Check if a variable occurs in a term
|
||||||
private fun occurs(variable: Variable, term: Term): Boolean = when {
|
private fun occurs(variable: Variable, term: Term): Boolean = when {
|
||||||
variable(term) -> term == variable
|
variable(term) -> term == variable
|
||||||
|
@ -33,8 +39,8 @@ private fun occurs(variable: Variable, term: Term): Boolean = when {
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate possible substitutions
|
// Unify two terms with backtracking and lazy evaluation
|
||||||
private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
|
fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
|
||||||
val t1 = applySubstitution(term1, subs)
|
val t1 = applySubstitution(term1, subs)
|
||||||
val t2 = applySubstitution(term2, subs)
|
val t2 = applySubstitution(term2, subs)
|
||||||
|
|
||||||
|
@ -59,7 +65,7 @@ private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted):
|
||||||
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) ->
|
val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(subs) { acc, (arg1, arg2) ->
|
||||||
generateSubstitutions(arg1, arg2, acc).firstOrNull() ?: return@sequence
|
unifyLazy(arg1, arg2, acc).firstOrNull() ?: return@sequence
|
||||||
}
|
}
|
||||||
yield(newSubstitution)
|
yield(newSubstitution)
|
||||||
}
|
}
|
||||||
|
@ -68,14 +74,6 @@ private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unify two terms with backtracking and lazy evaluation
|
|
||||||
fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
|
|
||||||
generateSubstitutions(term1, term2, subs).forEach { newSubs ->
|
|
||||||
// Return the new substitution
|
|
||||||
yield(newSubs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unify(term1: Term, term2: Term): Optional<Substituted> {
|
fun unify(term1: Term, term2: Term): Optional<Substituted> {
|
||||||
val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
|
val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
|
||||||
return if (substitutions.isNotEmpty()) {
|
return if (substitutions.isNotEmpty()) {
|
||||||
|
@ -84,3 +82,18 @@ fun unify(term1: Term, term2: Term): Optional<Substituted> {
|
||||||
Optional.empty()
|
Optional.empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
|
||||||
|
*/
|
||||||
|
fun equivalent(term1: Term, term2: Term): Boolean {
|
||||||
|
return when {
|
||||||
|
term1 is Atom && term2 is Atom -> term1.compareTo(term2) == 0
|
||||||
|
term1 is Structure && term2 is Structure -> term1.compareTo(term2) == 0
|
||||||
|
term1 is Integer && term2 is Integer -> term1.compareTo(term2) == 0
|
||||||
|
term1 is Variable && term2 is Variable -> term1 == term2
|
||||||
|
term1 is Variable -> term1.alias().isPresent && equivalent(term1.alias().get(), term2)
|
||||||
|
term2 is Variable -> term2.alias().isPresent && equivalent(term2.alias().get(), term1)
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package prolog.builtins
|
package prolog.logic
|
||||||
|
|
||||||
import prolog.ast.terms.CompoundTerm
|
import prolog.ast.terms.CompoundTerm
|
||||||
import prolog.ast.terms.Term
|
import prolog.ast.terms.Term
|
||||||
|
@ -37,5 +37,5 @@ fun variable(term: Term): Boolean {
|
||||||
return term.alias().isEmpty || term.alias().get() === term || variable(term.alias().get())
|
return term.alias().isEmpty || term.alias().get() === term || variable(term.alias().get())
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ import prolog.ast.logic.Rule
|
||||||
import prolog.builtins.Conjunction
|
import prolog.builtins.Conjunction
|
||||||
import prolog.builtins.Disjunction
|
import prolog.builtins.Disjunction
|
||||||
import prolog.builtins.Query
|
import prolog.builtins.Query
|
||||||
import prolog.builtins.equivalent
|
import prolog.logic.equivalent
|
||||||
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
|
||||||
|
|
426
tests/prolog/builtins/ArithmeticOperatorsTests.kt
Normal file
426
tests/prolog/builtins/ArithmeticOperatorsTests.kt
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
package prolog.builtins
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import prolog.ast.terms.Integer
|
||||||
|
import prolog.ast.terms.Variable
|
||||||
|
import prolog.logic.between
|
||||||
|
import prolog.logic.equivalent
|
||||||
|
|
||||||
|
class ArithmeticOperatorsTests {
|
||||||
|
@Test
|
||||||
|
fun `1 evaluates to 1`() {
|
||||||
|
val op1 = EvaluatesTo(
|
||||||
|
Integer(1),
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
var result = op1.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(True, result, "1 should be equal to 1")
|
||||||
|
|
||||||
|
val op2 = EvaluatesToDifferent(
|
||||||
|
Integer(1),
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = op2.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(False, result, "1 should not be different from 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 does not evaluate to 2`() {
|
||||||
|
val op1 = EvaluatesTo(
|
||||||
|
Integer(1),
|
||||||
|
Integer(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
var result = op1.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(False, result, "1 should not be equal to 2")
|
||||||
|
|
||||||
|
val op2 = EvaluatesToDifferent(
|
||||||
|
Integer(1),
|
||||||
|
Integer(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = op2.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(True, result, "1 should be different from 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `2 + 3 evaluates to 5`() {
|
||||||
|
val op = EvaluatesTo(
|
||||||
|
Add(Integer(2), Integer(3)),
|
||||||
|
Integer(5)
|
||||||
|
)
|
||||||
|
|
||||||
|
var result = op.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(True, result, "2 + 3 should be equal to 5")
|
||||||
|
|
||||||
|
val op2 = EvaluatesToDifferent(
|
||||||
|
Add(Integer(2), Integer(3)),
|
||||||
|
Integer(5)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = op2.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(False, result, "2 + 3 should not be different from 5")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `2 + 3 evaluates to 4 + 1`() {
|
||||||
|
val op = EvaluatesTo(
|
||||||
|
Add(Integer(2), Integer(3)),
|
||||||
|
Add(Integer(4), Integer(1))
|
||||||
|
)
|
||||||
|
|
||||||
|
var result = op.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(True, result, "2 + 3 should be equal to 4 + 1")
|
||||||
|
|
||||||
|
val op2 = EvaluatesToDifferent(
|
||||||
|
Add(Integer(2), Integer(3)),
|
||||||
|
Add(Integer(4), Integer(1))
|
||||||
|
)
|
||||||
|
|
||||||
|
result = op2.evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(False, result, "2 + 3 should not be different from 4 + 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 evaluates to variable not sufficiently instantiated`() {
|
||||||
|
val op = EvaluatesTo(
|
||||||
|
Integer(1),
|
||||||
|
Variable("X")
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> { op.evaluate(emptyMap()) }
|
||||||
|
|
||||||
|
val op2 = EvaluatesToDifferent(
|
||||||
|
Integer(1),
|
||||||
|
Variable("X")
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> { op2.evaluate(emptyMap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `(1 + var) evaluates to 1 not sufficiently instantiated`() {
|
||||||
|
val op = EvaluatesTo(
|
||||||
|
Add(Integer(1), Variable("X")),
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> { op.evaluate(emptyMap()) }
|
||||||
|
|
||||||
|
val op2 = EvaluatesToDifferent(
|
||||||
|
Add(Integer(1), Variable("X")),
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> { op2.evaluate(emptyMap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 is 1`() {
|
||||||
|
val op = Is(
|
||||||
|
Integer(1),
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = op.prove(emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "1 should be equal to 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 is not 2`() {
|
||||||
|
val op = Is(
|
||||||
|
Integer(1),
|
||||||
|
Integer(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = op.prove(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isEmpty(), "1 should not be equal to 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bound-to-1-var is 1`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val op = Is(
|
||||||
|
t1,
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
t1.bind(Integer(1))
|
||||||
|
|
||||||
|
val result = op.prove(emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "X should be equal to 1")
|
||||||
|
assertTrue(result.first().isEmpty(), "X should not be rebound")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bound-to-2-var is not 1`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val op = Is(
|
||||||
|
t1,
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
t1.bind(Integer(2))
|
||||||
|
|
||||||
|
val result = op.prove(emptyMap())
|
||||||
|
|
||||||
|
assertFalse(result.any(), "X should not be equal to 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `variable is 1`() {
|
||||||
|
val op = Is(
|
||||||
|
Variable("X"),
|
||||||
|
Integer(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = op.prove(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertFalse(result.isEmpty(), "X should be equal to 1")
|
||||||
|
assertEquals(1, result[0].size, "X should be rebound")
|
||||||
|
assertTrue(equivalent(Integer(1), result[0][Variable("X")]!!), "X should be equal to 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `variable is 1 + 2`() {
|
||||||
|
val op = Is(
|
||||||
|
Variable("X"),
|
||||||
|
Add(Integer(1), Integer(2))
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = op.prove(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertFalse(result.isEmpty(), "X should be equal to 3")
|
||||||
|
assertEquals(1, result[0].size, "X should be rebound")
|
||||||
|
assertTrue(equivalent(Integer(3), result[0][Variable("X")]!!), "X should be equal to 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `var is var should throw`() {
|
||||||
|
val op = Is(
|
||||||
|
Variable("X"),
|
||||||
|
Variable("Y")
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> { op.prove(emptyMap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 is var should throw`() {
|
||||||
|
val op = Is(
|
||||||
|
Integer(1),
|
||||||
|
Variable("X")
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> { op.prove(emptyMap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `var is bound-to-sum-var`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Variable("Y")
|
||||||
|
|
||||||
|
t2.bind(Add(Integer(1), Integer(2)))
|
||||||
|
|
||||||
|
val op = Is(t1, t2)
|
||||||
|
|
||||||
|
val result = op.prove(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "X should be equal to 3")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ?- between(1, 2, X), Y is 1 + X.
|
||||||
|
* X = 1, Y = 2 ;
|
||||||
|
* X = 2, Y = 3 .
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun `between 1 and 2 plus 1 to get 2 and 3`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Variable("X")
|
||||||
|
val t4 = Variable("Y")
|
||||||
|
|
||||||
|
val op = Conjunction(
|
||||||
|
Between(t1, t2, t3),
|
||||||
|
Is(t4, Add(Integer(1), t3))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `negate 1 to get -1`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
|
||||||
|
val result = Negate(t1).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(-1), result, "negate(1) should be equal to -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `negate 0 to get 0`() {
|
||||||
|
val t1 = Integer(0)
|
||||||
|
|
||||||
|
val result = Negate(t1).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(0), result, "negate(0) should be equal to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `negate -1 to get 1`() {
|
||||||
|
val t1 = Integer(-1)
|
||||||
|
|
||||||
|
val result = Negate(t1).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(1), result, "negate(-1) should be equal to 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `negate bound-to-1-var to get -1`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
|
||||||
|
t1.bind(Integer(1))
|
||||||
|
|
||||||
|
val result = Negate(t1).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(-1), result, "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
|
||||||
|
fun `negate 1 + 3 to get -4`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(3)
|
||||||
|
|
||||||
|
val result = Negate(Add(t1, t2)).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(-4), result, "negate(1 + 3) should be equal to -4")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Add 1 and 2 to get 3`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
|
||||||
|
val result = Add(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(3), result, "1 + 2 should be equal to 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Add 1 and bound-to-2-var to get 3`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Variable("X")
|
||||||
|
|
||||||
|
t2.bind(Integer(2))
|
||||||
|
|
||||||
|
val result = Add(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
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
|
||||||
|
fun `Subtract 1 and 2 to get -1`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
|
||||||
|
val result = Subtract(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(-1), result, "1 - 2 should be equal to -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Subtract 3 and 1 to get 2`() {
|
||||||
|
val t1 = Integer(3)
|
||||||
|
val t2 = Integer(1)
|
||||||
|
|
||||||
|
val result = Subtract(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(2), result, "3 - 1 should be equal to 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Multiply 0 and 0 to get 0`() {
|
||||||
|
val t1 = Integer(0)
|
||||||
|
val t2 = Integer(0)
|
||||||
|
|
||||||
|
val result = Multiply(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(0), result, "0 * 0 should be equal to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Multiply 1 and 0 to get 0`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(0)
|
||||||
|
|
||||||
|
val result = Multiply(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(0), result, "1 * 0 should be equal to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Multiply 0 and 1 to get 0`() {
|
||||||
|
val t1 = Integer(0)
|
||||||
|
val t2 = Integer(1)
|
||||||
|
|
||||||
|
val result = Multiply(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(0), result, "0 * 1 should be equal to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Multiply 3 and 1 to get 3`() {
|
||||||
|
val t1 = Integer(3)
|
||||||
|
val t2 = Integer(1)
|
||||||
|
|
||||||
|
val result = Multiply(t1, t2).evaluate(emptyMap()).first
|
||||||
|
|
||||||
|
assertEquals(Integer(3), result, "3 * 1 should be equal to 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Multiply 2 and 3 to get 6`() {
|
||||||
|
val t1 = Integer(2)
|
||||||
|
val t2 = Integer(3)
|
||||||
|
|
||||||
|
val result = Multiply(t1, t2).evaluate(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "There should only be one solution")
|
||||||
|
assertEquals(Integer(6), result, "2 * 3 should be equal to 6")
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import prolog.Program
|
||||||
import prolog.ast.logic.Fact
|
import prolog.ast.logic.Fact
|
||||||
import prolog.ast.terms.Atom
|
import prolog.ast.terms.Atom
|
||||||
|
|
||||||
class ControlBuiltinsTest {
|
class ControlTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
Program.clear()
|
Program.clear()
|
||||||
|
@ -55,7 +55,7 @@ class ControlBuiltinsTest {
|
||||||
Program.load(listOf(success, failure))
|
Program.load(listOf(success, failure))
|
||||||
|
|
||||||
val goal = Atom("a")
|
val goal = Atom("a")
|
||||||
val failGoal = Fail()
|
val failGoal = Fail
|
||||||
|
|
||||||
val result1 = Program.query(goal)
|
val result1 = Program.query(goal)
|
||||||
val result2 = Program.query(failGoal)
|
val result2 = Program.query(failGoal)
|
|
@ -5,6 +5,8 @@ import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import prolog.ast.terms.Atom
|
import prolog.ast.terms.Atom
|
||||||
import prolog.ast.terms.Structure
|
import prolog.ast.terms.Structure
|
||||||
|
import prolog.logic.atomic
|
||||||
|
import prolog.logic.compound
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on [Predicates for analyzing/constructing terms](https://github.com/dtonhofer/prolog_notes/blob/master/swipl_notes/about_term_analysis_and_construction/term_analysis_construction.png)
|
* Based on [Predicates for analyzing/constructing terms](https://github.com/dtonhofer/prolog_notes/blob/master/swipl_notes/about_term_analysis_and_construction/term_analysis_construction.png)
|
||||||
|
|
52
tests/prolog/builtins/UnificationTest.kt
Normal file
52
tests/prolog/builtins/UnificationTest.kt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package prolog.builtins
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import prolog.ast.terms.Atom
|
||||||
|
import prolog.ast.terms.Integer
|
||||||
|
import prolog.ast.terms.Variable
|
||||||
|
|
||||||
|
class UnificationTest {
|
||||||
|
/**
|
||||||
|
* ?- X == a.
|
||||||
|
* false.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun equivalent_variable_and_atom() {
|
||||||
|
val variable = Variable("X")
|
||||||
|
val atom = Atom("a")
|
||||||
|
|
||||||
|
val result = Equivalent(variable, atom).prove(emptyMap())
|
||||||
|
|
||||||
|
assertFalse(result.any(), "Variable and atom should not be equivalent")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ?- a == a.
|
||||||
|
* true.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun equivalent_atom_and_atom() {
|
||||||
|
val atom1 = Atom("a")
|
||||||
|
val atom2 = Atom("a")
|
||||||
|
|
||||||
|
val result = Equivalent(atom1, atom2).prove(emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "Identical atoms should be equivalent")
|
||||||
|
assertEquals(0, result.first().size, "No substitutions should be made")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ?- 1 + 2 == 3.
|
||||||
|
* false.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun simple_addition_equivalence() {
|
||||||
|
val addition = Add(Integer(1), Integer(2))
|
||||||
|
val solution = Integer(3)
|
||||||
|
|
||||||
|
val result = Equivalent(addition, solution).prove(emptyMap())
|
||||||
|
|
||||||
|
assertFalse(result.any(), "Addition should be equivalent")
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,12 @@ import org.junit.jupiter.api.Test
|
||||||
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.nonvariable
|
||||||
|
import prolog.logic.variable
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class VerificationBuiltinsTest {
|
class VerificationTest {
|
||||||
@Test
|
@Test
|
||||||
fun unbound_variable_is_var() {
|
fun unbound_variable_is_var() {
|
||||||
val variable = Variable("X")
|
val variable = Variable("X")
|
357
tests/prolog/logic/ArithmeticTests.kt
Normal file
357
tests/prolog/logic/ArithmeticTests.kt
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
package prolog.logic
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import prolog.ast.terms.Integer
|
||||||
|
import prolog.ast.terms.Term
|
||||||
|
import prolog.ast.terms.Variable
|
||||||
|
|
||||||
|
class ArithmeticTests {
|
||||||
|
@Test
|
||||||
|
fun `1_between_0_and_2`() {
|
||||||
|
val result = between(Integer(0), Integer(2), Integer(1))
|
||||||
|
|
||||||
|
assertTrue(result.any(), "Expected 1 to be between 0 and 2")
|
||||||
|
assertEquals(emptyMap<Variable, Term>(), result.first(), "Expected no substitutions")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `3_not_between_0_and_2`() {
|
||||||
|
val result = between(Integer(0), Integer(2), Integer(3))
|
||||||
|
|
||||||
|
assertTrue(result.none(), "Expected 3 not to be between 0 and 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun numbers_between_0_and_2() {
|
||||||
|
val result = between(Integer(0), Integer(2), Variable("X"))
|
||||||
|
|
||||||
|
val expectedResults = listOf(
|
||||||
|
mapOf(Variable("X") to Integer(0)),
|
||||||
|
mapOf(Variable("X") to Integer(1)),
|
||||||
|
mapOf(Variable("X") to Integer(2))
|
||||||
|
)
|
||||||
|
|
||||||
|
val actualResults = result.toList()
|
||||||
|
assertEquals(expectedResults.size, actualResults.size, "Expected 3 results")
|
||||||
|
for ((expected, actual) in expectedResults.zip(actualResults)) {
|
||||||
|
for ((key, value) in expected) {
|
||||||
|
assertTrue(actual.containsKey(key), "Expected key $key to be present")
|
||||||
|
assertTrue(
|
||||||
|
equivalent(value, actual[key]!!),
|
||||||
|
"Expected value $value for key $key, but got ${actual[key]}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `succ(0) is 1`() {
|
||||||
|
val t1 = Integer(0)
|
||||||
|
|
||||||
|
val expected = Integer(1)
|
||||||
|
|
||||||
|
val result = succ(t1, expected, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "Expected 0 + 1 to be equal to 1")
|
||||||
|
assertTrue(result.first().isEmpty(), "Expected no substitutions")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `succ(1) is 2`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
|
||||||
|
val expected = Integer(2)
|
||||||
|
|
||||||
|
val result = succ(t1, expected, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "Expected 1 + 1 to be equal to 2")
|
||||||
|
assertTrue(result.first().isEmpty(), "Expected no substitutions")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `succ(1) is var`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Variable("X")
|
||||||
|
|
||||||
|
val expected = Integer(2)
|
||||||
|
|
||||||
|
val result = succ(t1, t2, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "Expected 1 + 1 to be equal to X")
|
||||||
|
assertTrue(result[0][t2] == expected, "Expected X to be equal to 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `succ(bound-to-1-var) is 2`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Integer(2)
|
||||||
|
|
||||||
|
t1.bind(Integer(1))
|
||||||
|
|
||||||
|
val result = succ(t1, t2, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "Expected X + 1 to be equal to 2")
|
||||||
|
assertTrue(result.first().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)))
|
||||||
|
|
||||||
|
assertTrue(result.any(), "Expected X + 1 to be equal to 2")
|
||||||
|
assertTrue(result.first().isEmpty(), "Expected no substitutions")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `succ(bound-to-1-var) is var`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Variable("Y")
|
||||||
|
|
||||||
|
t1.bind(Integer(1))
|
||||||
|
|
||||||
|
val result = succ(t1, t2, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "Expected X + 1 to be equal to Y")
|
||||||
|
assertTrue(result[0][t2] == Integer(2), "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()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "Expected X + 1 to be equal to Y")
|
||||||
|
assertTrue(result[0][t2] == Integer(2), "Expected Y to be equal to 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `succ(var, 0) fails silently`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Integer(0)
|
||||||
|
|
||||||
|
val result = succ(t1, t2, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isEmpty(), "Expected X + 1 to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1_plus_2_is_3`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Integer(3)
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "1 + 2 should be equal to 3")
|
||||||
|
assertTrue(result.first().isEmpty(), "1 + 2 should be equal to 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1_plus_2_is_not_4`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Integer(4)
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.none(), "1 + 2 should not be equal to 4")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1_plus_2_is_variable`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Variable("X")
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "1 + 2 should be equal to X")
|
||||||
|
assertTrue(equivalent(result[0][t3]!!, Integer(3)), "X should be equal to 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1_plus_variable_is_3`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Variable("X")
|
||||||
|
val t3 = Integer(3)
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "1 + X should be equal to 3")
|
||||||
|
assertTrue(equivalent(result[0][t2]!!, Integer(2)), "X should be equal to 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun variable_plus_2_is_3() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Integer(3)
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "X + 2 should be equal to 3")
|
||||||
|
assertTrue(equivalent(result[0][t1]!!, Integer(1)), "X should be equal to 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 plus 2 is bound-to-3-var`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Variable("X")
|
||||||
|
|
||||||
|
t3.bind(Integer(3))
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "1 + 2 should be equal to X")
|
||||||
|
assertTrue(result.first().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)))
|
||||||
|
|
||||||
|
assertTrue(result.any(), "1 + 2 should be equal to X")
|
||||||
|
assertTrue(result.first().isEmpty(), "t3 should not be rebound")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 plus 2 is bound-to-4-var`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Variable("X")
|
||||||
|
|
||||||
|
t3.bind(Integer(4))
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.none(), "1 + 2 should not be equal to X")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 plus bound-to-2-var is 3`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Variable("X")
|
||||||
|
val t3 = Integer(3)
|
||||||
|
|
||||||
|
t2.bind(Integer(2))
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "1 + X should be equal to 3")
|
||||||
|
assertTrue(result.first().isEmpty(), "t2 should not be rebound")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 plus bound-to-2-var is not 4`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Variable("X")
|
||||||
|
val t3 = Integer(4)
|
||||||
|
|
||||||
|
t2.bind(Integer(2))
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.none(), "1 + X should not be equal to 4")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bound-to-1-var plus 2 is 3`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Integer(3)
|
||||||
|
|
||||||
|
t1.bind(Integer(1))
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.any(), "X + 2 should be equal to 3")
|
||||||
|
assertTrue(result.first().none(), "t1 should not be rebound")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bound-to-1-var plus 2 is not 4`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Integer(2)
|
||||||
|
val t3 = Integer(4)
|
||||||
|
|
||||||
|
t1.bind(Integer(1))
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.none(), "X + 2 should not be equal to 4")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `two unbound vars plus should throw`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Variable("Y")
|
||||||
|
val t3 = Integer(3)
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
plus(t1, t2, t3, emptyMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bound-to-1-var plus bound-to-2-var is variable`() {
|
||||||
|
val t1 = Variable("X")
|
||||||
|
val t2 = Variable("Y")
|
||||||
|
val t3 = Variable("Z")
|
||||||
|
|
||||||
|
t1.bind(Integer(1))
|
||||||
|
t2.bind(Integer(2))
|
||||||
|
|
||||||
|
val result = plus(t1, t2, t3, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertTrue(result.isNotEmpty(), "X + Y should be equal to Z")
|
||||||
|
assertTrue(equivalent(result[0][t3]!!, Integer(3)), "Z should be equal to 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `1 times 1 is 1`() {
|
||||||
|
val t1 = Integer(1)
|
||||||
|
val t2 = Integer(1)
|
||||||
|
val t3 = Integer(1)
|
||||||
|
|
||||||
|
val result = mul(t1, t2, t3, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "There should be one solution")
|
||||||
|
assertTrue(result[0].isEmpty(), "1 * 1 should already be equal to 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `2 times 3 is 6`() {
|
||||||
|
val t1 = Integer(2)
|
||||||
|
val t2 = Integer(3)
|
||||||
|
val t3 = Integer(6)
|
||||||
|
|
||||||
|
val result = mul(t1, t2, t3, emptyMap()).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "There should be one solution")
|
||||||
|
assertTrue(result[0].isEmpty(), "2 * 3 should already be equal to 6")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `2 times 3 is not 4`() {
|
||||||
|
val t1 = Integer(2)
|
||||||
|
val t2 = Integer(3)
|
||||||
|
val t3 = Integer(4)
|
||||||
|
|
||||||
|
val result = mul(t1, t2, t3, emptyMap())
|
||||||
|
|
||||||
|
assertTrue(result.none(), "2 * 3 should not be equal to 4")
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,10 @@ 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.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.equivalent
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
|
@ -260,4 +260,25 @@ class UnifyTest {
|
||||||
|
|
||||||
assertFalse(result.isPresent, "Atom with different arity should not unify")
|
assertFalse(result.isPresent, "Atom with different arity should not unify")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun identical_integers_unify() {
|
||||||
|
val int1 = Integer(1)
|
||||||
|
val int2 = Integer(1)
|
||||||
|
|
||||||
|
val result = unify(int1, int2)
|
||||||
|
|
||||||
|
assertTrue(result.isPresent, "Identical integers should unify")
|
||||||
|
assertEquals(0, result.get().size, "No substitutions should be made")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun different_integers_do_not_unify() {
|
||||||
|
val int1 = Integer(1)
|
||||||
|
val int2 = Integer(2)
|
||||||
|
|
||||||
|
val result = unify(int1, int2)
|
||||||
|
|
||||||
|
assertFalse(result.isPresent, "Different integers should not unify")
|
||||||
|
}
|
||||||
}
|
}
|
Reference in a new issue