Checkpoint

This commit is contained in:
Tibo De Peuter 2025-04-06 12:14:18 +02:00
parent 438af6c053
commit 6eca9dfcb7
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
25 changed files with 309 additions and 113 deletions

View file

@ -1,11 +1,16 @@
package prolog.builtins
import prolog.components.expressions.Operand
import prolog.components.expressions.Operator
import prolog.components.terms.Atom
import prolog.components.terms.Body
/**
* Always fail.
*/
class Fail: Atom("fail")
class Fail: Atom("fail"), Body {
override fun prove(): Boolean = false
}
/**
* Same as fail, but the name has a more declarative connotation.
@ -15,6 +20,32 @@ typealias False = Fail
/**
* Always succeed.
*/
class True: Atom("true")
class True: Atom("true"), Body {
override fun prove(): Boolean = true
}
// TODO Repeat/0
// TODO !/0 (Cut)
/**
* Conjunction (and). True if both Goal1 and Goal2 are true.
*/
class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(","), leftOperand, rightOperand) {
override fun prove(): Boolean {
val left = leftOperand?.prove() ?: true
val right = rightOperand.prove()
return left && right
}
}
/**
* Disjunction (or). True if either Goal1 or Goal2 succeeds.
*/
class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) {
override fun prove(): Boolean {
val left = leftOperand?.prove() ?: false
val right = rightOperand.prove()
return left || right
}
}

View file

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

View file

@ -1,10 +0,0 @@
package prolog.builtins2.control
import prolog.components.terms.Atom
import prolog.components.Operand
import prolog.components.Operator
/**
* Conjunction (and). True if both Goal1 and Goal2 are true.
*/
class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(","), leftOperand, rightOperand)

View file

@ -1,10 +0,0 @@
package prolog.builtins2.control
import prolog.components.terms.Atom
import prolog.components.Operand
import prolog.components.Operator
/**
* Disjunction (or). True if either Goal1 or Goal2 succeeds.
*/
class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand)

View file

@ -10,16 +10,30 @@ import prolog.components.terms.Goal
/**
* Prolog Program or database.
*/
object Program {
object Program: Resolvent {
private var predicates: Map<Functor, Predicate> = emptyMap()
init {
setup()
}
private fun setup() {
// Initialize the program with built-in predicates
load(listOf(
Fact(True())
))
}
fun query(goal: Goal): Boolean {
val functor = goal.functor
// If the predicate does not exist, return false
val predicate = predicates[functor] ?: return false
// If the predicate exists, evaluate the goal against it
return predicate.solve(goal)
}
override fun solve(goal: Goal): Boolean = query(goal)
/**
* Loads a list of clauses into the program.
*/
@ -51,13 +65,9 @@ object Program {
}
}
fun query(goal: Goal): Boolean {
val functor = goal.functor
val predicate = predicates[functor]
?: // If the predicate does not exist, return false
return false
// If the predicate exists, evaluate the goal against it
return predicate.evaluate(goal)
fun clear() {
predicates = emptyMap()
setup()
}
}

View file

@ -0,0 +1,5 @@
package prolog.components
interface Provable {
fun prove(): Boolean
}

View file

@ -0,0 +1,10 @@
package prolog.components
import prolog.components.terms.Goal
/**
* Can be instructed to solve a goal.
*/
interface Resolvent {
fun solve(goal: Goal): Boolean
}

View file

@ -1,15 +1,9 @@
package prolog.components.expressions
import prolog.components.terms.Functor
import prolog.components.terms.Goal
import prolog.components.terms.Head
import prolog.components.terms.Term
import prolog.components.Resolvent
import prolog.components.terms.*
import prolog.unify
// TODO Change this to the right type, supporting operators and normal Clauses
// Probably needs a new interface or abstract class (?)
typealias Body = List<Term>
/**
* Sentence of a Prolog program.
*
@ -18,18 +12,29 @@ typealias Body = List<Term>
* @see [prolog.components.terms.Variable]
* @see [Predicate]
*/
abstract class Clause(val head: Head, val body: Body = emptyList()) : Expression {
abstract class Clause(private val head: Head, private val body: Body? = null) : Resolvent {
val functor: Functor = head.functor
override fun evaluate(goal: Goal): Boolean {
val result = unify(goal, head)
// TODO Evaluate the body
return result.isEmpty
override fun solve(goal: Goal): Boolean {
val unification = unify(goal, head)
if (unification.isEmpty) {
return false
}
val substitutions = unification.get()
val proven = body == null || body.prove()
substitutions.forEach { (variable, _) -> variable.unbind() }
return proven
}
override fun toString(): String {
return when {
body.isEmpty() -> head.toString()
else -> "$head :- ${body.joinToString(", ")}"
body == null -> head.toString()
else -> "$head :- $body"
}
}
}

View file

@ -1,7 +0,0 @@
package prolog.components.expressions
import prolog.components.terms.Goal
interface Expression {
fun evaluate(goal: Goal): Boolean
}

View file

@ -3,4 +3,4 @@ package prolog.components.expressions
import prolog.builtins.True
import prolog.components.terms.Head
class Fact(head: Head) : Clause(head, listOf(True()))
class Fact(head: Head) : Clause(head, True())

View file

@ -1,13 +1,19 @@
package prolog.components
package prolog.components.expressions
import prolog.components.terms.Argument
import prolog.components.Provable
import prolog.components.terms.Atom
import prolog.components.terms.CompoundTerm
import prolog.components.terms.Goal
open class Operator(
typealias Operand = Goal
abstract class Operator(
val symbol: Atom,
val leftOperand: Operand? = null,
val rightOperand: Operand
) {
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Provable {
abstract override fun prove(): Boolean
override fun toString(): String {
return when (leftOperand) {
null -> "${symbol.name} $rightOperand"
@ -15,5 +21,3 @@ open class Operator(
}
}
}
typealias Operand = Argument

View file

@ -1,5 +1,6 @@
package prolog.components.expressions
import prolog.components.Resolvent
import prolog.components.terms.Functor
import prolog.components.terms.Goal
@ -9,7 +10,7 @@ import prolog.components.terms.Goal
* If a goal is proved, the system looks for a predicate with the same functor, then uses indexing
* to select candidate clauses and then tries these clauses one-by-one.
*/
class Predicate : Expression {
class Predicate : Resolvent {
val functor: Functor
val clauses: MutableList<Clause>
@ -47,8 +48,8 @@ class Predicate : Expression {
this.clauses.addAll(clauses)
}
override fun evaluate(goal: Goal): Boolean {
override fun solve(goal: Goal): Boolean {
require(goal.functor == functor) { "Goal functor does not match predicate functor" }
return clauses.any { it.evaluate(goal) }
return clauses.any { it.solve(goal) }
}
}

View file

@ -1,5 +1,6 @@
package prolog.components.expressions
import prolog.components.terms.Body
import prolog.components.terms.Head
class Rule(head: Head, body: Body): Clause(head, body)

View file

@ -1,9 +1,27 @@
package prolog.components.terms
open class Atom(val name: String): Goal() {
import prolog.components.Resolvent
import prolog.unify
open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
override val functor: Functor = "$name/_"
override fun solve(goal: Goal): Boolean = unify(goal, this).isEmpty
override fun toString(): String {
return name
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Atom) return false
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}

View file

@ -0,0 +1,5 @@
package prolog.components.terms
import prolog.components.Provable
interface Body : Provable

View file

@ -1,16 +1,17 @@
package prolog.components.terms
import prolog.components.Program
import prolog.components.Provable
/**
* Question stated to the Prolog engine.
* Ask the Prolog engine.
*
* A goal is either an [atom][prolog.components.terms.Atom] or a [compound term][prolog.components.terms.CompoundTerm].
* A goal is either an [Atom] or a [CompoundTerm].
* 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.
*/
abstract class Goal : Head() {
fun prove(): Boolean {
return Program.query(this)
}
abstract class Goal : Term, Provable {
abstract val functor: Functor
override fun prove(): Boolean = Program.query(this)
}

View file

@ -2,12 +2,9 @@ package prolog.components.terms
/**
* Part of a [Clause][prolog.components.expressions.Clause] before the [neck][prolog.terms.Neck] operator.
*
* @see [Atom]
* @see [CompoundTerm]
*/
abstract class Head: Term {
abstract val functor: Functor
interface Head : Term {
val functor: Functor
}
typealias Functor = String

View file

@ -1,8 +1,17 @@
package prolog.components.terms
open class Structure(val name: Atom, val arguments: List<Argument>): Goal() {
import prolog.components.Resolvent
import prolog.unify
typealias Argument = Term
open class Structure(val name: Atom, val arguments: List<Argument>): Goal(), Head, Body, Resolvent {
override val functor: Functor = "${name.name}/${arguments.size}"
override fun solve(goal: Goal): Boolean {
return unify(goal, this).isEmpty
}
override fun toString(): String {
return when {
arguments.isEmpty() -> name.name

View file

@ -8,8 +8,6 @@ package prolog.components.terms
*/
interface Term
typealias Argument = Term
/*
<program> ::= <clause list> <query> | <query>
<clause list> ::= <clause> | <clause list> <clause>
@ -22,11 +20,4 @@ typealias Argument = Term
<query> ::= ?- <predicate list>.
<small atom> ::= <lowercase letter> | <small atom> <character>
<variable> ::= <uppercase letter> | <variable> <character>
<lowercase letter> ::= a | b | c | ... | x | y | z
<uppercase letter> ::= A | B | C | ... | X | Y | Z | _
<numeral> ::= <digit> | <numeral> <digit>
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<character> ::= <lowercase letter> | <uppercase letter> | <digit> | <special>
<special> ::= + | - | * | / | \ | ^ | ~ | : | . | ? | | # | $ | &
<string> ::= <character> | <string> <character>
*/

View file

@ -2,7 +2,7 @@ package prolog.components.terms
import java.util.Optional
data class Variable(val name: String): Term {
data class Variable(val name: String) : Term {
private var alias: Optional<Term> = Optional.empty()
fun alias(): Optional<Term> {
@ -27,4 +27,12 @@ data class Variable(val name: String): Term {
else -> name
}
}
override fun equals(other: Any?): Boolean {
return if (alias.isPresent) {
alias.get() == other
} else {
name == other.toString()
}
}
}

View file

@ -1,9 +0,0 @@
package prolog.terms
import prolog.components.Operand
import prolog.components.Operator
import prolog.components.terms.Atom
class Neck(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(":-"), leftOperand, rightOperand)
class Query(rightOperand: Operand): Operator(Atom("?-"), null, rightOperand)

View file

@ -74,9 +74,9 @@ fun unifyLazy(term1: Term, term2: Term, substitution: Substitution = emptyMap())
fun unify(term1: Term, term2: Term): Optional<Substitution> {
val substitutions = unifyLazy(term1, term2).toList()
return if (substitutions.isEmpty()) {
Optional.empty()
} else {
return if (substitutions.isNotEmpty()) {
Optional.of(substitutions.first())
} else {
Optional.empty()
}
}