Checkpoint
This commit is contained in:
parent
e3c84e1761
commit
e73e5cbfc8
32 changed files with 1354 additions and 92 deletions
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
|
||||
|
||||
import prolog.ast.terms.Structure
|
||||
import prolog.ast.terms.Term
|
||||
import prolog.ast.terms.Variable
|
||||
import prolog.builtins.atomic
|
||||
import prolog.builtins.compound
|
||||
import prolog.builtins.equivalent
|
||||
import prolog.builtins.variable
|
||||
import prolog.ast.arithmetic.Expression
|
||||
import prolog.ast.logic.LogicOperator
|
||||
import prolog.ast.terms.*
|
||||
import java.util.*
|
||||
|
||||
typealias Substituted = Map<Variable, Term>
|
||||
|
||||
// Apply substitutions to a term
|
||||
private fun applySubstitution(term: Term, substitution: Substituted): Term = when {
|
||||
variable(term) -> substitution[(term as Variable)] ?: term
|
||||
fun applySubstitution(term: Term, subs: Substituted): Term = when {
|
||||
variable(term) -> subs[(term as Variable)] ?: term
|
||||
atomic(term) -> term
|
||||
compound(term) -> {
|
||||
val structure = term as Structure
|
||||
Structure(structure.name, structure.arguments.map { applySubstitution(it, substitution) })
|
||||
Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) })
|
||||
}
|
||||
else -> term
|
||||
}
|
||||
|
||||
fun applySubstitution(expr: Expression, subs: Substituted): Expression = when {
|
||||
variable(expr) -> applySubstitution(expr as Term, subs) as Expression
|
||||
atomic(expr) -> expr
|
||||
expr is LogicOperator -> {
|
||||
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
|
||||
expr
|
||||
}
|
||||
else -> expr
|
||||
}
|
||||
|
||||
// Check if a variable occurs in a term
|
||||
private fun occurs(variable: Variable, term: Term): Boolean = when {
|
||||
variable(term) -> term == variable
|
||||
|
@ -33,8 +39,8 @@ private fun occurs(variable: Variable, term: Term): Boolean = when {
|
|||
else -> false
|
||||
}
|
||||
|
||||
// Generate possible substitutions
|
||||
private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
|
||||
// Unify two terms with backtracking and lazy evaluation
|
||||
fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
|
||||
val t1 = applySubstitution(term1, subs)
|
||||
val t2 = applySubstitution(term2, subs)
|
||||
|
||||
|
@ -59,7 +65,7 @@ private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted):
|
|||
val structure2 = t2 as Structure
|
||||
if (structure1.functor == structure2.functor) {
|
||||
val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(subs) { acc, (arg1, arg2) ->
|
||||
generateSubstitutions(arg1, arg2, acc).firstOrNull() ?: return@sequence
|
||||
unifyLazy(arg1, arg2, acc).firstOrNull() ?: return@sequence
|
||||
}
|
||||
yield(newSubstitution)
|
||||
}
|
||||
|
@ -68,14 +74,6 @@ private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted):
|
|||
}
|
||||
}
|
||||
|
||||
// Unify two terms with backtracking and lazy evaluation
|
||||
fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
|
||||
generateSubstitutions(term1, term2, subs).forEach { newSubs ->
|
||||
// Return the new substitution
|
||||
yield(newSubs)
|
||||
}
|
||||
}
|
||||
|
||||
fun unify(term1: Term, term2: Term): Optional<Substituted> {
|
||||
val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
|
||||
return if (substitutions.isNotEmpty()) {
|
||||
|
@ -84,3 +82,18 @@ fun unify(term1: Term, term2: Term): Optional<Substituted> {
|
|||
Optional.empty()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
|
||||
*/
|
||||
fun equivalent(term1: Term, term2: Term): Boolean {
|
||||
return when {
|
||||
term1 is Atom && term2 is Atom -> term1.compareTo(term2) == 0
|
||||
term1 is Structure && term2 is Structure -> term1.compareTo(term2) == 0
|
||||
term1 is Integer && term2 is Integer -> term1.compareTo(term2) == 0
|
||||
term1 is Variable && term2 is Variable -> term1 == term2
|
||||
term1 is Variable -> term1.alias().isPresent && equivalent(term1.alias().get(), term2)
|
||||
term2 is Variable -> term2.alias().isPresent && equivalent(term2.alias().get(), term1)
|
||||
else -> false
|
||||
}
|
||||
}
|
41
src/prolog/logic/verification.kt
Normal file
41
src/prolog/logic/verification.kt
Normal file
|
@ -0,0 +1,41 @@
|
|||
package prolog.logic
|
||||
|
||||
import prolog.ast.terms.CompoundTerm
|
||||
import prolog.ast.terms.Term
|
||||
import prolog.ast.terms.Variable
|
||||
|
||||
/**
|
||||
* True if [Term] is bound (i.e., not a variable) and is not compound.
|
||||
* Thus, atomic acts as if defined by:
|
||||
*
|
||||
* atomic(Term) :-
|
||||
* nonvar(Term),
|
||||
* \+ compound(Term).
|
||||
*/
|
||||
fun atomic(term: Term): Boolean = nonvariable(term) && !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.
|
||||
*/
|
||||
fun compound(term: Term): Boolean {
|
||||
val isCompound = term is CompoundTerm
|
||||
val isVariableCompound = term is Variable && term.alias().isPresent && compound(term.alias().get())
|
||||
return isCompound || isVariableCompound
|
||||
}
|
||||
|
||||
/**
|
||||
* True if [Term] currently is not a free variable.
|
||||
*/
|
||||
fun nonvariable(term: Term): Boolean = !variable(term)
|
||||
|
||||
/**
|
||||
* True if [Term] currently is a free variable.
|
||||
*/
|
||||
fun variable(term: Term): Boolean {
|
||||
if (term is Variable) {
|
||||
return term.alias().isEmpty || term.alias().get() === term || variable(term.alias().get())
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Reference in a new issue