Checkpoint

This commit is contained in:
Tibo De Peuter 2025-04-05 17:36:37 +02:00
parent 39c3af4ba5
commit da21d890fb
39 changed files with 1166 additions and 48 deletions

View file

@ -0,0 +1,9 @@
package prolog.components
import prolog.components.terms.Atom
data class Functor(val name: Atom, val arity: Int) {
override fun toString(): String {
return "${name.name}/${arity}"
}
}

View file

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

View file

@ -0,0 +1,19 @@
package prolog.components
import prolog.components.terms.Argument
import prolog.components.terms.Atom
open class Operator(
val symbol: Atom,
val leftOperand: Operand? = null,
val rightOperand: Operand
) {
override fun toString(): String {
return when (leftOperand) {
null -> "${symbol.name} $rightOperand"
else -> "$leftOperand ${symbol.name} $rightOperand"
}
}
}
typealias Operand = Argument

View file

@ -0,0 +1,62 @@
package prolog.components
import prolog.builtins.True
import prolog.components.expressions.Clause
import prolog.components.expressions.Fact
import prolog.components.expressions.Predicate
/**
* Prolog Program or database.
*/
object Program {
private var predicates: Map<Functor, Predicate> = emptyMap()
init {
// Initialize the program with built-in predicates
load(listOf(
Fact(True())
))
}
/**
* Loads a list of clauses into the program.
*/
fun load(clauses: List<Clause>) {
for (clause in clauses) {
val functor = clause.functor
val predicate = predicates[functor]
if (predicate != null) {
// If the predicate already exists, add the clause to it
predicate.add(clause)
} else {
// If the predicate does not exist, create a new one
predicates += Pair(functor, Predicate(listOf(clause)))
}
}
}
fun load(predicate: Predicate) {
val functor = predicate.functor
val existingPredicate = predicates[functor]
if (existingPredicate != null) {
// If the predicate already exists, add the clauses to it
existingPredicate.addAll(predicate.clauses)
} else {
// If the predicate does not exist, create a new one
predicates += Pair(functor, predicate)
}
}
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)
}
}
typealias Database = Program

View file

@ -0,0 +1,35 @@
package prolog.components.expressions
import prolog.components.Functor
import prolog.components.Goal
import prolog.components.terms.Head
import prolog.components.terms.Term
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.
*
* A clause consists of a [Head] and body separated by the [neck][prolog.terms.Neck] operator, or it is a [Fact].
*
* @see [prolog.components.terms.Variable]
* @see [Predicate]
*/
abstract class Clause(val head: Head, val body: Body = emptyList()) : Expression {
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 toString(): String {
return when {
body.isEmpty() -> head.toString()
else -> "$head :- ${body.joinToString(", ")}"
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,54 @@
package prolog.components.expressions
import prolog.components.Functor
import prolog.components.Goal
/**
* Collection of [Clause]s with the same [Functor].
*
* 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 {
val functor: Functor
val clauses: MutableList<Clause>
/**
* Creates a predicate with the given functor and an empty list of clauses.
*/
constructor(functor: Functor) {
this.functor = functor
this.clauses = mutableListOf()
}
/**
* Creates a predicate with the given clauses.
*/
constructor(clauses: List<Clause>) {
this.functor = clauses.first().functor
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
this.clauses = clauses.toMutableList()
}
/**
* Adds a clause to the predicate.
*/
fun add(clause: Clause) {
require (clause.functor == functor) { "Clause functor does not match predicate functor" }
clauses.add(clause)
}
/**
* Adds a list of clauses to the predicate.
*/
fun addAll(clauses: List<Clause>) {
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
this.clauses.addAll(clauses)
}
override fun evaluate(goal: Goal): Boolean {
require(goal.functor == functor) { "Goal functor does not match predicate functor" }
return clauses.any { it.evaluate(goal) }
}
}

View file

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

View file

@ -0,0 +1,12 @@
package prolog.components.terms
import prolog.components.Functor
import prolog.components.Goal
open class Atom(val name: String): Head(), Term, Goal {
override val functor: Functor = Functor(this, 0)
override fun toString(): String {
return name
}
}

View file

@ -0,0 +1,13 @@
package prolog.components.terms
import prolog.components.Functor
/**
* 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
}

View file

@ -0,0 +1,17 @@
package prolog.components.terms
import prolog.components.Functor
import prolog.components.Goal
open class Structure(val name: Atom, val arguments: List<Argument>): Head(), Term, Goal {
override val functor: Functor = Functor(name, arguments.size)
override fun toString(): String {
return when {
arguments.isEmpty() -> name.name
else -> "${name.name}(${arguments.joinToString(", ")})"
}
}
}
typealias CompoundTerm = Structure

View file

@ -0,0 +1,32 @@
package prolog.components.terms
/**
* Value in Prolog.
*
* A [Term] is either a [Variable], [Atom], integer, float or [CompoundTerm].
* In addition, SWI-Prolog also defines the type string.
*/
interface Term
typealias Argument = Term
/*
<program> ::= <clause list> <query> | <query>
<clause list> ::= <clause> | <clause list> <clause>
<clause> ::= <predicate> . | <predicate> :- <predicate list>.
<predicate list> ::= <predicate> | <predicate list> , <predicate>
<predicate> ::= <atom> | <atom> ( <term list> )
<term list> ::= <term> | <term list> , <term>
<term> ::= <numeral> | <atom> | <variable> | <structure>
<structure> ::= <atom> ( <term list> )
<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

@ -0,0 +1,30 @@
package prolog.components.terms
import java.util.Optional
data class Variable(val name: String): Term {
private var alias: Optional<Term> = Optional.empty()
fun alias(): Optional<Term> {
return alias
}
fun bind(term: Term): Optional<Term> {
if (alias.isEmpty) {
alias = Optional.of(term)
}
return alias
}
fun unbind() {
alias = Optional.empty()
}
override fun toString(): String {
return when {
alias.isPresent -> "$name = ${alias.get()}"
else -> name
}
}
}