refactor: Herstructurering

This commit is contained in:
Tibo De Peuter 2025-04-06 19:16:50 +02:00
parent 1acd1cfb67
commit e3c84e1761
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
33 changed files with 290 additions and 178 deletions

View file

@ -1,12 +1,15 @@
package prolog.components
package prolog
import prolog.Substituted
import prolog.logic.Substituted
import prolog.ast.logic.Clause
import prolog.ast.logic.Fact
import prolog.ast.logic.Predicate
import prolog.ast.logic.Resolvent
import prolog.ast.terms.Functor
import prolog.ast.terms.Goal
import prolog.builtins.True
import prolog.components.expressions.Clause
import prolog.components.expressions.Fact
import prolog.components.expressions.Predicate
import prolog.components.terms.Functor
import prolog.components.terms.Goal
typealias Database = Program
/**
* Prolog Program or database.
@ -29,7 +32,7 @@ object Program: Resolvent {
* Queries the program with a goal.
* @return true if the goal can be proven, false otherwise.
*/
fun query(goal: Goal): Boolean = solve(goal, emptyMap()).toList().isNotEmpty()
fun query(goal: Goal): Sequence<Substituted> = solve(goal, emptyMap())
override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> {
val functor = goal.functor
@ -75,5 +78,3 @@ object Program: Resolvent {
setup()
}
}
typealias Database = Program

View file

@ -1,18 +1,19 @@
package prolog.components.expressions
package prolog.ast.logic
import prolog.Substituted
import prolog.logic.Substituted
import prolog.ast.terms.Body
import prolog.ast.terms.Functor
import prolog.ast.terms.Goal
import prolog.ast.terms.Head
import prolog.builtins.True
import prolog.builtins.equivalent
import prolog.components.Resolvent
import prolog.components.terms.*
import prolog.unifyLazy
import prolog.logic.unifyLazy
/**
* 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].
* A clause consists of a [Head] and body separated by the neck operator, or it is a [Fact].
*
* @see [prolog.components.terms.Variable]
* @see [prolog.ast.terms.Variable]
* @see [Predicate]
*/
abstract class Clause(private val head: Head, private val body: Body) : Resolvent {

View file

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

View file

@ -1,9 +1,8 @@
package prolog.components.expressions
package prolog.ast.logic
import prolog.Substituted
import prolog.components.Resolvent
import prolog.components.terms.Functor
import prolog.components.terms.Goal
import prolog.logic.Substituted
import prolog.ast.terms.Functor
import prolog.ast.terms.Goal
/**
* Collection of [Clause]s with the same [Functor].
@ -37,7 +36,7 @@ class Predicate : Resolvent {
* Adds a clause to the predicate.
*/
fun add(clause: Clause) {
require (clause.functor == functor) { "Clause functor does not match predicate functor" }
require(clause.functor == functor) { "Clause functor does not match predicate functor" }
clauses.add(clause)
}

View file

@ -1,6 +1,6 @@
package prolog.components
package prolog.ast.logic
import prolog.Substituted
import prolog.logic.Substituted
interface Provable {
/**

View file

@ -1,7 +1,7 @@
package prolog.components
package prolog.ast.logic
import prolog.Substituted
import prolog.components.terms.Goal
import prolog.logic.Substituted
import prolog.ast.terms.Goal
/**
* Can be instructed to solve a goal.

View file

@ -0,0 +1,6 @@
package prolog.ast.logic
import prolog.ast.terms.Body
import prolog.ast.terms.Head
class Rule(head: Head, body: Body) : Clause(head, body)

View file

@ -1,8 +1,8 @@
package prolog.components.terms
package prolog.ast.terms
import prolog.Substituted
import prolog.components.Resolvent
import prolog.unifyLazy
import prolog.ast.logic.Resolvent
import prolog.logic.Substituted
import prolog.logic.unifyLazy
open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
override val functor: Functor = "$name/_"

View file

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

View file

@ -1,8 +1,8 @@
package prolog.components.terms
package prolog.ast.terms
import prolog.Substituted
import prolog.components.Program
import prolog.components.Provable
import prolog.Program
import prolog.ast.logic.Provable
import prolog.logic.Substituted
/**
* Ask the Prolog engine.

View file

@ -0,0 +1,10 @@
package prolog.ast.terms
/**
* Part of a [Clause][prolog.ast.logic.Clause] before the neck operator.
*/
interface Head : Term {
val functor: Functor
}
typealias Functor = String

View file

@ -1,12 +1,7 @@
package prolog.components.expressions
package prolog.ast.terms
import prolog.Substituted
import prolog.components.Provable
import prolog.components.terms.Atom
import prolog.components.terms.CompoundTerm
import prolog.components.terms.Goal
typealias Operand = Goal
import prolog.ast.logic.Provable
import prolog.logic.Substituted
abstract class Operator(
private val symbol: Atom,
@ -22,3 +17,5 @@ abstract class Operator(
}
}
}
typealias Operand = Goal

View file

@ -1,13 +1,15 @@
package prolog.components.terms
package prolog.ast.terms
import prolog.Substituted
import prolog.ast.logic.Resolvent
import prolog.builtins.equivalent
import prolog.components.Resolvent
import prolog.unifyLazy
import prolog.logic.Substituted
import prolog.logic.unifyLazy
typealias Argument = Term
open class Structure(val name: Atom, val arguments: List<Argument>): Goal(), Head, Body, Resolvent {
typealias CompoundTerm = Structure
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, subs: Substituted): Sequence<Substituted> {
@ -39,5 +41,3 @@ open class Structure(val name: Atom, val arguments: List<Argument>): Goal(), Hea
}
}
}
typealias CompoundTerm = Structure

View file

@ -0,0 +1,9 @@
package prolog.ast.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 : Comparable<Term>

View file

@ -1,6 +1,6 @@
package prolog.components.terms
package prolog.ast.terms
import java.util.Optional
import java.util.*
data class Variable(val name: String) : Term {
private var alias: Optional<Term> = Optional.empty()

View file

@ -0,0 +1 @@
package prolog.builtins

View file

@ -1,15 +1,12 @@
package prolog.builtins
import prolog.Substituted
import prolog.components.expressions.Operand
import prolog.components.expressions.Operator
import prolog.components.terms.Atom
import prolog.components.terms.Body
import prolog.ast.terms.*
import prolog.logic.Substituted
/**
* Always fail.
*/
class Fail: Atom("fail"), Body {
class Fail : Atom("fail"), Body {
override fun prove(subs: Substituted): Sequence<Substituted> = emptySequence()
}
@ -21,7 +18,7 @@ typealias False = Fail
/**
* Always succeed.
*/
class True: Atom("true"), Body {
class True : Atom("true"), Body {
override fun prove(subs: Substituted): Sequence<Substituted> = sequenceOf(emptyMap())
}
@ -49,7 +46,7 @@ class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom("
/**
* Disjunction (or). True if either Goal1 or Goal2 succeeds.
*/
class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) {
open class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) {
override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
if (leftOperand != null) {
yieldAll(leftOperand.prove(subs))
@ -57,3 +54,24 @@ class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom("
yieldAll(rightOperand.prove(subs))
}
}
@Deprecated("Use Disjunction instead")
class Bar(leftOperand: Operand, rightOperand: Operand) : Disjunction(leftOperand, rightOperand)
// TODO ->
// TODO *->
/**
* True if 'Goal' cannot be proven.
*/
class Not(goal: Goal) : Operator(Atom("\\+"), rightOperand = goal) {
override fun prove(subs: Substituted): Sequence<Substituted> {
// If the goal can be proven, return an empty sequence
if (rightOperand.prove(subs).toList().isNotEmpty()) {
return emptySequence()
}
// If the goal cannot be proven, return a sequence with an empty map
return sequenceOf(emptyMap())
}
}

View file

@ -0,0 +1 @@
package prolog.builtins

View file

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

View file

@ -1,7 +1,7 @@
package prolog.builtins
import prolog.components.terms.Atom
import prolog.components.terms.Term
import prolog.ast.terms.Atom
import prolog.ast.terms.Term
/**
* True when Term is a term with functor Name/Arity. If Term is a variable it is unified with a new term whose

View file

@ -1,9 +1,9 @@
package prolog.builtins
import prolog.components.terms.Atom
import prolog.components.terms.Structure
import prolog.components.terms.Term
import prolog.components.terms.Variable
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
/**
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
@ -18,3 +18,7 @@ fun equivalent(term1: Term, term2: Term): Boolean {
else -> false
}
}
/**
*
*/

View file

@ -1,8 +1,8 @@
package prolog.builtins
import prolog.components.terms.CompoundTerm
import prolog.components.terms.Term
import prolog.components.terms.Variable
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.

View file

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

View file

@ -1,6 +0,0 @@
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,5 +0,0 @@
package prolog.components.terms
import prolog.components.Provable
interface Body : Provable

View file

@ -1,10 +0,0 @@
package prolog.components.terms
/**
* Part of a [Clause][prolog.components.expressions.Clause] before the [neck][prolog.terms.Neck] operator.
*/
interface Head : Term {
val functor: Functor
}
typealias Functor = String

View file

@ -1,23 +0,0 @@
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 : Comparable<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>
*/

View file

@ -1,12 +1,12 @@
package prolog
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.components.terms.Term
import prolog.components.terms.Variable
import prolog.components.terms.Structure
import java.util.*
typealias Substituted = Map<Variable, Term>

View file

@ -3,14 +3,15 @@ package prolog
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import prolog.ast.logic.Fact
import prolog.ast.logic.Rule
import prolog.builtins.Conjunction
import prolog.builtins.Disjunction
import prolog.components.Program
import prolog.components.expressions.Fact
import prolog.components.expressions.Rule
import prolog.components.terms.Atom
import prolog.components.terms.Structure
import prolog.components.terms.Variable
import prolog.builtins.Query
import prolog.builtins.equivalent
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
class EvaluationTest {
@BeforeEach
@ -25,7 +26,7 @@ class EvaluationTest {
val result = Program.query(Atom("a"))
assertTrue(result)
assertTrue(result.any())
}
@Test
@ -35,7 +36,7 @@ class EvaluationTest {
val result = Program.query(Atom("b"))
assertFalse(result)
assertFalse(result.any())
}
@Test
@ -44,9 +45,9 @@ class EvaluationTest {
val fact2 = Fact(Atom("b"))
Program.load(listOf(fact1, fact2))
assertTrue(Program.query(Atom("a")))
assertTrue(Program.query(Atom("b")))
assertFalse(Program.query(Atom("c")))
assertTrue(Program.query(Atom("a")).any())
assertTrue(Program.query(Atom("b")).any())
assertFalse(Program.query(Atom("c")).any())
}
@Test
@ -54,7 +55,7 @@ class EvaluationTest {
val structure = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
Program.load(listOf(Fact(structure)))
assertTrue(Program.query(Structure(Atom("f"), listOf(Atom("a"), Atom("b")))))
assertTrue(Program.query(Structure(Atom("f"), listOf(Atom("a"), Atom("b")))).any())
}
@Test
@ -63,9 +64,9 @@ class EvaluationTest {
val structure2 = Structure(Atom("g"), listOf(Atom("c"), Atom("d")))
Program.load(listOf(Fact(structure1), Fact(structure2)))
assertTrue(Program.query(Structure(Atom("f"), listOf(Atom("a"), Atom("b")))))
assertTrue(Program.query(Structure(Atom("g"), listOf(Atom("c"), Atom("d")))))
assertFalse(Program.query(Structure(Atom("h"), listOf(Atom("e")))))
assertTrue(Program.query(Structure(Atom("f"), listOf(Atom("a"), Atom("b")))).any())
assertTrue(Program.query(Structure(Atom("g"), listOf(Atom("c"), Atom("d")))).any())
assertFalse(Program.query(Structure(Atom("h"), listOf(Atom("e")))).any())
}
/**
@ -89,8 +90,8 @@ class EvaluationTest {
Program.load(listOf(father, mother, parent1, parent2))
assertTrue(Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jimmy")))))
assertTrue(Program.query(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy")))))
assertTrue(Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jimmy")))).any())
assertTrue(Program.query(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy")))).any())
}
/**
@ -110,19 +111,20 @@ class EvaluationTest {
Structure(Atom("father"), listOf(variable1, variable2)),
/* ; */
Structure(Atom("mother"), listOf(variable1, variable2))
))
)
)
Program.load(listOf(father, mother, parent))
val result1 = Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jimmy"))))
assertTrue(result1)
assertTrue(result1.toList().isNotEmpty())
val result2 = Program.query(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy"))))
assertTrue(result2)
assertTrue(result2.toList().isNotEmpty())
val result3 = Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jane"))))
assertFalse(result3)
assertFalse(result3.any())
val result4 = Program.query(Structure(Atom("father"), listOf(Atom("john"), Atom("jane"))))
assertFalse(result4)
assertFalse(result4.any())
}
/**
@ -162,18 +164,55 @@ class EvaluationTest {
Program.load(listOf(male, female, parent1, parent2, isFather, isMother))
val result1 = Program.query(Structure(Atom("isFather"), listOf(Atom("john"), Atom("jimmy"))))
assertTrue(result1)
assertTrue(result1.any())
val result2 = Program.query(Structure(Atom("isMother"), listOf(Atom("jane"), Atom("jimmy"))))
assertTrue(result2)
assertTrue(result2.any())
val result3 = Program.query(Structure(Atom("isFather"), listOf(Atom("jane"), Atom("jimmy"))))
assertFalse(result3)
assertFalse(result3.any())
val result4 = Program.query(Structure(Atom("isMother"), listOf(Atom("john"), Atom("jimmy"))))
assertFalse(result4)
assertFalse(result4.any())
val result5 = Program.query(Structure(Atom("parent"), listOf(Atom("trudy"), Atom("jimmy"))))
assertFalse(result5)
assertFalse(result5.any())
}
@Test
fun requires_backtracking() {
val fact1 = Fact(Structure(Atom("a"), listOf(Atom("b"))))
val fact2 = Fact(Structure(Atom("a"), listOf(Atom("c"))))
val fact3 = Fact(Structure(Atom("a"), listOf(Atom("d"))))
Program.load(listOf(fact1, fact2, fact3))
assertTrue(Program.query(Structure(Atom("a"), listOf(Atom("b")))).any())
assertTrue(Program.query(Structure(Atom("a"), listOf(Atom("c")))).any())
assertTrue(Program.query(Structure(Atom("a"), listOf(Atom("d")))).any())
}
@Test
fun multiple_choices() {
val fact1 = Fact(Structure(Atom("a"), listOf(Atom("b"))))
val fact2 = Fact(Structure(Atom("a"), listOf(Atom("c"))))
val fact3 = Fact(Structure(Atom("a"), listOf(Atom("d"))))
Program.load(listOf(fact1, fact2, fact3))
val results = Query(Structure(Atom("a"), listOf(Variable("X")))).prove(emptyMap())
val expectedResults = listOf(
mapOf(Variable("X") to Atom("b")),
mapOf(Variable("X") to Atom("c")),
mapOf(Variable("X") to Atom("d"))
)
val actualResults = results.toList()
assertEquals(expectedResults.size, actualResults.size, "Number of results should match")
for (i in expectedResults.indices) {
assertEquals(expectedResults[i].size, actualResults[i].size, "Substitution size should match")
assertTrue(expectedResults[i].all { actualResults[i][it.key]?.let { it1 -> equivalent(it.value, it1) } ?: false }, "Substitution values should match")
}
}
}

View file

@ -0,0 +1,66 @@
package prolog.builtins
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import prolog.Program
import prolog.ast.logic.Fact
import prolog.ast.terms.Atom
class ControlBuiltinsTest {
@BeforeEach
fun setUp() {
Program.clear()
}
@Test
fun not_atom() {
val success = Fact(Atom("a"))
Program.load(listOf(success))
val goal = Atom("a")
val notGoal = Not(goal)
val result1 = Program.query(goal)
val result2 = Program.query(notGoal)
assertTrue(result1.any(), "Expected query to succeed")
assertFalse(result2.any(), "Expected query to fail")
}
@Test
fun not_compound() {
val success = Fact(Atom("f"))
val failure = Fact(Atom("g"))
Program.load(listOf(success, failure))
val goal = Atom("f")
val notGoal = Not(Atom("g"))
val result1 = Program.query(goal)
val result2 = Program.query(notGoal)
assertTrue(result1.any(), "Expected query to succeed")
assertFalse(result2.any(), "Expected query to fail")
}
@Test
fun fail_should_cause_fails() {
val success = Fact(Atom("a"))
val failure = Fact(Atom("b"))
Program.load(listOf(success, failure))
val goal = Atom("a")
val failGoal = Fail()
val result1 = Program.query(goal)
val result2 = Program.query(failGoal)
assertTrue(result1.any(), "Expected query to succeed")
assertFalse(result2.any(), "Expected query to fail")
}
}

View file

@ -3,9 +3,8 @@ package prolog.builtins
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import prolog.components.terms.Atom
import prolog.components.terms.Structure
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
/**
* 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)

View file

@ -3,12 +3,12 @@ package prolog.builtins
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import prolog.components.terms.Atom
import prolog.components.terms.Structure
import prolog.components.terms.Variable
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
import kotlin.test.assertEquals
class BuiltinsTest {
class VerificationBuiltinsTest {
@Test
fun unbound_variable_is_var() {
val variable = Variable("X")

View file

@ -1,12 +1,12 @@
package prolog
package prolog.logic
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
import prolog.builtins.equivalent
import prolog.components.terms.Atom
import prolog.components.terms.Structure
import prolog.components.terms.Variable
/*
* Based on: https://en.wikipedia.org/wiki/Unification_%28computer_science%29#Examples_of_syntactic_unification_of_first-order_terms