Checkpoint

This commit is contained in:
Tibo De Peuter 2025-04-06 14:42:57 +02:00
parent ef8b82457c
commit d702b9b081
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
13 changed files with 114 additions and 63 deletions

View file

@ -1,15 +1,18 @@
package prolog.builtins package prolog.builtins
import prolog.Substitution
import prolog.components.expressions.Operand import prolog.components.expressions.Operand
import prolog.components.expressions.Operator import prolog.components.expressions.Operator
import prolog.components.terms.Atom import prolog.components.terms.Atom
import prolog.components.terms.Body import prolog.components.terms.Body
import prolog.components.terms.Term
import prolog.components.terms.Variable
/** /**
* Always fail. * Always fail.
*/ */
class Fail: Atom("fail"), Body { class Fail: Atom("fail"), Body {
override fun prove(): Boolean = false override fun prove(): Sequence<Substitution> = emptySequence()
} }
/** /**
@ -21,7 +24,7 @@ typealias False = Fail
* Always succeed. * Always succeed.
*/ */
class True: Atom("true"), Body { class True: Atom("true"), Body {
override fun prove(): Boolean = true override fun prove(): Sequence<Substitution> = sequenceOf(emptyMap())
} }
// TODO Repeat/0 // TODO Repeat/0
@ -32,10 +35,16 @@ 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(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(","), leftOperand, rightOperand) {
override fun prove(): Boolean { override fun prove(): Sequence<Substitution> = sequence {
val left = leftOperand?.prove() ?: true if (leftOperand != null) {
val right = rightOperand.prove() leftOperand.prove().forEach { left ->
return left && right rightOperand.prove().forEach { right ->
yield(left + right)
}
}
} else {
yieldAll(rightOperand.prove())
}
} }
} }
@ -43,9 +52,10 @@ class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom("
* Disjunction (or). True if either Goal1 or Goal2 succeeds. * Disjunction (or). True if either Goal1 or Goal2 succeeds.
*/ */
class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) { class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) {
override fun prove(): Boolean { override fun prove(): Sequence<Substitution> = sequence {
val left = leftOperand?.prove() ?: false if (leftOperand != null) {
val right = rightOperand.prove() yieldAll(leftOperand.prove())
return left || right }
yieldAll(rightOperand.prove())
} }
} }

View file

@ -1,9 +1,10 @@
package prolog.builtins package prolog.builtins
import prolog.Substitution
import prolog.components.expressions.Operand import prolog.components.expressions.Operand
import prolog.components.expressions.Operator import prolog.components.expressions.Operator
import prolog.components.terms.Atom import prolog.components.terms.Atom
class Query(rightOperand: Operand) : Operator(Atom("?-"), null, rightOperand) { class Query(rightOperand: Operand) : Operator(Atom("?-"), null, rightOperand) {
override fun prove(): Boolean = rightOperand.prove() override fun prove(): Sequence<Substitution> = rightOperand.prove()
} }

View file

@ -1,5 +1,6 @@
package prolog.components package prolog.components
import prolog.Substitution
import prolog.builtins.True import prolog.builtins.True
import prolog.components.expressions.Clause import prolog.components.expressions.Clause
import prolog.components.expressions.Fact import prolog.components.expressions.Fact
@ -24,16 +25,20 @@ object Program: Resolvent {
)) ))
} }
fun query(goal: Goal): Boolean { /**
* Queries the program with a goal.
* @return true if the goal can be proven, false otherwise.
*/
fun query(goal: Goal): Boolean = solve(goal).any()
override fun solve(goal: Goal): Sequence<Substitution> {
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 false val predicate = predicates[functor] ?: return emptySequence()
// If the predicate exists, evaluate the goal against it // If the predicate exists, evaluate the goal against it
return predicate.solve(goal) return predicate.solve(goal)
} }
override fun solve(goal: Goal): Boolean = query(goal)
/** /**
* Loads a list of clauses into the program. * Loads a list of clauses into the program.
*/ */

View file

@ -1,5 +1,13 @@
package prolog.components package prolog.components
import prolog.Substitution
interface Provable { interface Provable {
fun prove(): Boolean /**
* Proves the current [Provable] instance.
*
* @return a sequence of [Substitution] instances representing the results of the proof.
* If the proof fails, an empty sequence is returned.
*/
fun prove(): Sequence<Substitution>
} }

View file

@ -1,10 +1,17 @@
package prolog.components package prolog.components
import prolog.Substitution
import prolog.components.terms.Goal import prolog.components.terms.Goal
/** /**
* Can be instructed to solve a goal. * Can be instructed to solve a goal.
*/ */
interface Resolvent { interface Resolvent {
fun solve(goal: Goal): Boolean /**
* Attempts to solve the given goal.
*
* @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.
*/
fun solve(goal: Goal): Sequence<Substitution>
} }

View file

@ -1,8 +1,9 @@
package prolog.components.expressions package prolog.components.expressions
import prolog.Substitution
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.components.terms.* import prolog.components.terms.*
import prolog.unify import prolog.unifyLazy
/** /**
* Sentence of a Prolog program. * Sentence of a Prolog program.
@ -15,20 +16,22 @@ import prolog.unify
abstract class Clause(private val head: Head, private val body: Body? = null) : Resolvent { abstract class Clause(private val head: Head, private val body: Body? = null) : Resolvent {
val functor: Functor = head.functor val functor: Functor = head.functor
override fun solve(goal: Goal): Boolean { override fun solve(goal: Goal): Sequence<Substitution> = sequence {
val unification = unify(goal, head) if (body == null) {
// If the clause is a fact, unify the goal with the head, and return the substitutions.
if (unification.isEmpty) { // Do this in a lazy way.
return false yieldAll(unifyLazy(goal, head))
} else {
// 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.
// Do this in a lazy way.
unifyLazy(goal, head).forEach { subs ->
// If the body can be proven, yield the (combined) substitutions
body.prove().forEach { bodySubs ->
yield(subs + bodySubs)
}
}
} }
val substitutions = unification.get()
val proven = body == null || body.prove()
substitutions.forEach { (variable, _) -> variable.unbind() }
return proven
} }
override fun toString(): String { override fun toString(): String {

View file

@ -1,5 +1,6 @@
package prolog.components.expressions package prolog.components.expressions
import prolog.Substitution
import prolog.components.Provable import prolog.components.Provable
import prolog.components.terms.Atom import prolog.components.terms.Atom
import prolog.components.terms.CompoundTerm import prolog.components.terms.CompoundTerm
@ -8,11 +9,11 @@ import prolog.components.terms.Goal
typealias Operand = Goal typealias Operand = Goal
abstract class Operator( abstract class Operator(
val symbol: Atom, private val symbol: Atom,
val leftOperand: Operand? = null, val leftOperand: Operand? = null,
val rightOperand: Operand val rightOperand: Operand
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Provable { ) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Provable {
abstract override fun prove(): Boolean abstract override fun prove(): Sequence<Substitution>
override fun toString(): String { override fun toString(): String {
return when (leftOperand) { return when (leftOperand) {

View file

@ -1,5 +1,6 @@
package prolog.components.expressions package prolog.components.expressions
import prolog.Substitution
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.components.terms.Functor import prolog.components.terms.Functor
import prolog.components.terms.Goal import prolog.components.terms.Goal
@ -48,8 +49,12 @@ class Predicate : Resolvent {
this.clauses.addAll(clauses) this.clauses.addAll(clauses)
} }
override fun solve(goal: Goal): Boolean { override fun solve(goal: Goal): Sequence<Substitution> = sequence {
require(goal.functor == functor) { "Goal functor does not match predicate functor" } require(goal.functor == functor) { "Goal functor does not match predicate functor" }
return clauses.any { it.solve(goal) } for (clause in clauses) {
// Try to unify the goal with the clause
// If the unification is successful, yield the substitutions
yieldAll(clause.solve(goal))
}
} }
} }

View file

@ -1,12 +1,14 @@
package prolog.components.terms package prolog.components.terms
import prolog.Substitution
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.unify import prolog.unify
import prolog.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): Boolean = unify(goal, this).isEmpty override fun solve(goal: Goal): Sequence<Substitution> = unifyLazy(goal, this)
override fun toString(): String { override fun toString(): String {
return name return name

View file

@ -1,5 +1,6 @@
package prolog.components.terms package prolog.components.terms
import prolog.Substitution
import prolog.components.Program import prolog.components.Program
import prolog.components.Provable import prolog.components.Provable
@ -13,5 +14,5 @@ import prolog.components.Provable
abstract class Goal : Term, Provable { abstract class Goal : Term, Provable {
abstract val functor: Functor abstract val functor: Functor
override fun prove(): Boolean = Program.query(this) override fun prove(): Sequence<Substitution> = Program.solve(this)
} }

View file

@ -1,15 +1,16 @@
package prolog.components.terms package prolog.components.terms
import prolog.Substitution
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.unify import prolog.unifyLazy
typealias Argument = Term typealias Argument = Term
open class Structure(val name: Atom, val arguments: List<Argument>): Goal(), Head, Body, Resolvent { open class Structure(val name: Atom, val 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): Boolean { override fun solve(goal: Goal): Sequence<Substitution> {
return unify(goal, this).isEmpty return unifyLazy(goal, this)
} }
override fun toString(): String { override fun toString(): String {

View file

@ -11,7 +11,7 @@ import java.util.*
typealias Substitution = Map<Variable, Term> typealias Substitution = Map<Variable, Term>
// Apply substitutions to a term // Apply substitutions to a term
fun applySubstitution(term: Term, substitution: Substitution): Term = when { private fun applySubstitution(term: Term, substitution: Substitution): Term = when {
variable(term) -> (term as Variable).alias().map { applySubstitution(it, substitution) }.orElse(term) variable(term) -> (term as Variable).alias().map { applySubstitution(it, substitution) }.orElse(term)
atomic(term) -> term atomic(term) -> term
compound(term) -> { compound(term) -> {
@ -22,7 +22,7 @@ fun applySubstitution(term: Term, substitution: Substitution): Term = when {
} }
// Check if a variable occurs in a term // Check if a variable occurs in a term
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
atomic(term) -> false atomic(term) -> false
compound(term) -> { compound(term) -> {
@ -33,7 +33,7 @@ fun occurs(variable: Variable, term: Term): Boolean = when {
} }
// Generate possible substitutions // Generate possible substitutions
fun generateSubstitutions(term1: Term, term2: Term, substitution: Substitution): Sequence<Substitution> = sequence { private fun generateSubstitutions(term1: Term, term2: Term, substitution: Substitution): Sequence<Substitution> = sequence {
val t1 = applySubstitution(term1, substitution) val t1 = applySubstitution(term1, substitution)
val t2 = applySubstitution(term2, substitution) val t2 = applySubstitution(term2, substitution)
@ -42,15 +42,17 @@ fun generateSubstitutions(term1: Term, term2: Term, substitution: Substitution):
variable(t1) -> { variable(t1) -> {
val variable = t1 as Variable val variable = t1 as Variable
if (!occurs(variable, t2)) { if (!occurs(variable, t2)) {
variable.bind(t2) // variable.bind(t2)
yield(substitution + (variable to t2)) yield(substitution + (variable to t2))
// variable.unbind()
} }
} }
variable(t2) -> { variable(t2) -> {
val variable = t2 as Variable val variable = t2 as Variable
if (!occurs(variable, t1)) { if (!occurs(variable, t1)) {
variable.bind(t1) // variable.bind(t1)
yield(substitution + (variable to t1)) yield(substitution + (variable to t1))
// variable.unbind()
} }
} }
compound(t1) && compound(t2) -> { compound(t1) && compound(t2) -> {
@ -68,8 +70,11 @@ fun generateSubstitutions(term1: Term, term2: Term, substitution: Substitution):
} }
// Unify two terms with backtracking and lazy evaluation // Unify two terms with backtracking and lazy evaluation
fun unifyLazy(term1: Term, term2: Term, substitution: Substitution = emptyMap()): Sequence<Substitution> { fun unifyLazy(term1: Term, term2: Term, substitution: Substitution = emptyMap()): Sequence<Substitution> = sequence {
return generateSubstitutions(term1, term2, substitution) generateSubstitutions(term1, term2, substitution).forEach { newSubstitution ->
// Return the new substitution
yield(newSubstitution)
}
} }
fun unify(term1: Term, term2: Term): Optional<Substitution> { fun unify(term1: Term, term2: Term): Optional<Substitution> {

View file

@ -1,7 +1,6 @@
package prolog package prolog
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import prolog.builtins.Conjunction import prolog.builtins.Conjunction
@ -11,6 +10,7 @@ import prolog.components.expressions.Fact
import prolog.components.expressions.Rule import prolog.components.expressions.Rule
import prolog.components.terms.Atom import prolog.components.terms.Atom
import prolog.components.terms.Structure import prolog.components.terms.Structure
import prolog.components.terms.Variable
class EvaluationTest { class EvaluationTest {
@BeforeEach @BeforeEach
@ -23,7 +23,9 @@ class EvaluationTest {
val fact = Fact(Atom("a")) val fact = Fact(Atom("a"))
Program.load(listOf(fact)) Program.load(listOf(fact))
assertTrue(Program.query(Atom("a"))) val result = Program.query(Atom("a"))
assertTrue(result)
} }
@Test @Test
@ -75,14 +77,14 @@ class EvaluationTest {
val mother = Fact(Structure(Atom("mother"), listOf(Atom("jane"), Atom("jimmy")))) val mother = Fact(Structure(Atom("mother"), listOf(Atom("jane"), Atom("jimmy"))))
val parent1 = Rule( val parent1 = Rule(
Structure(Atom("parent"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))),
/* :- */ /* :- */
Structure(Atom("father"), listOf(Atom("X"), Atom("Y"))) Structure(Atom("father"), listOf(Variable("X"), Variable("Y")))
) )
val parent2 = Rule( val parent2 = Rule(
Structure(Atom("parent"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))),
/* :- */ /* :- */
Structure(Atom("mother"), listOf(Atom("X"), Atom("Y"))) Structure(Atom("mother"), listOf(Variable("X"), Variable("Y")))
) )
Program.load(listOf(father, mother, parent1, parent2)) Program.load(listOf(father, mother, parent1, parent2))
@ -100,11 +102,11 @@ class EvaluationTest {
val mother = Fact(Structure(Atom("mother"), listOf(Atom("jane"), Atom("jimmy")))) val mother = Fact(Structure(Atom("mother"), listOf(Atom("jane"), Atom("jimmy"))))
val parent = Rule( val parent = Rule(
Structure(Atom("parent"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))),
/* :- */ Disjunction( /* :- */ Disjunction(
Structure(Atom("father"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("father"), listOf(Variable("X"), Variable("Y"))),
/* ; */ /* ; */
Structure(Atom("mother"), listOf(Atom("X"), Atom("Y"))) Structure(Atom("mother"), listOf(Variable("X"), Variable("Y")))
)) ))
Program.load(listOf(father, mother, parent)) Program.load(listOf(father, mother, parent))
@ -129,17 +131,17 @@ class EvaluationTest {
val parent2 = Fact(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy")))) val parent2 = Fact(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy"))))
val isFather = Rule( val isFather = Rule(
Structure(Atom("isFather"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("isFather"), listOf(Variable("X"), Variable("Y"))),
Conjunction( Conjunction(
Structure(Atom("parent"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))),
Structure(Atom("male"), listOf(Atom("X"))) Structure(Atom("male"), listOf(Variable("X")))
) )
) )
val isMother = Rule( val isMother = Rule(
Structure(Atom("isMother"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("isMother"), listOf(Variable("X"), Variable("Y"))),
Conjunction( Conjunction(
Structure(Atom("parent"), listOf(Atom("X"), Atom("Y"))), Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))),
Structure(Atom("female"), listOf(Atom("X"))) Structure(Atom("female"), listOf(Variable("X")))
) )
) )