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

8
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/compiler.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.10" />
</component>
</project>

8
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK" />
</project>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

0
documentatie/verslag.pdf Normal file
View file

17
documentatie/verslag.tex Normal file
View file

@ -0,0 +1,17 @@
%! Author = tdpeuter
%! Date = 27/03/2025
% Preamble
\documentclass[11pt]{article}
% Packages
\usepackage{amsmath}
% Document
\begin{document}
% Lexer op basis van https://craftinginterpreters.com/scanning.html
\end{document}

4
pl/scratchpad.pl Normal file
View file

@ -0,0 +1,4 @@
likes(mary, john).
likes(john, mary).
X :- likes(mary, X).

View file

@ -1,5 +0,0 @@
package prolog
import prolog.terms.Variable
data class Result(val success: Boolean, val substitutions: List<Variable>)

View file

@ -0,0 +1,20 @@
package prolog.builtins
import prolog.components.terms.Atom
/**
* Always fail.
*/
class Fail: Atom("fail")
/**
* Same as fail, but the name has a more declarative connotation.
*/
typealias False = Fail
/**
* Always succeed.
*/
class True: Atom("true")
// TODO Repeat/0

View file

@ -0,0 +1,22 @@
package prolog.builtins
import prolog.components.terms.Atom
import prolog.components.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
* arguments are all different variables (such a term is called a skeleton). If Term is atomic, Arity will be unified
* with the integer 0, and Name will be unified with Term. Raises instantiation_error() if Term is unbound and
* Name/Arity is insufficiently instantiated.
*
* SWI-Prolog also supports terms with arity 0, as in a() (see
* [section 5](https://www.swi-prolog.org/pldoc/man?section=extensions)). Such terms must be processed using functor/4
* or compound_name_arity/3. The predicate functor/3 and =../2 raise a domain_error when faced with these terms.
* Without this precaution a round trip of a term with arity 0 over functor/3 would create an atom.
*
* Source: [SWI-Prolog Predicate functor/3](https://www.swi-prolog.org/pldoc/doc_for?object=functor/3)
*/
fun functor(term: Term, name: Atom, arity: Int): Boolean {
// TODO Implement
return true
}

View file

@ -0,0 +1,41 @@
package prolog.builtins
import prolog.components.terms.CompoundTerm
import prolog.components.terms.Term
import prolog.components.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;
}

View file

@ -0,0 +1,10 @@
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

@ -0,0 +1,10 @@
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

@ -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
}
}
}

View file

@ -1,3 +0,0 @@
package prolog.terms
data class Atom(val name: String): Term()

View file

@ -1,3 +0,0 @@
package prolog.terms
sealed class Term

View file

@ -0,0 +1,9 @@
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

@ -1,7 +1,83 @@
package prolog
import prolog.terms.Term
import prolog.builtins.atomic
import prolog.builtins.compound
import prolog.builtins.variable
import prolog.components.Goal
import prolog.components.terms.Term
import prolog.components.terms.Variable
import prolog.components.terms.Structure
import java.util.*
fun unify(term1: Term, term2: Term): Result {
return Result(term1 == term2, emptyList())
typealias Substitution = Map<Variable, Term>
// Apply substitutions to a term
fun applySubstitution(term: Term, substitution: Substitution): Term = when {
variable(term) -> (term as Variable).alias().map { applySubstitution(it, substitution) }.orElse(term)
atomic(term) -> term
compound(term) -> {
val structure = term as Structure
Structure(structure.name, structure.arguments.map { applySubstitution(it, substitution) })
}
else -> term
}
// Check if a variable occurs in a term
fun occurs(variable: Variable, term: Term): Boolean = when {
variable(term) -> term == variable
atomic(term) -> false
compound(term) -> {
val structure = term as Structure
structure.arguments.any { occurs(variable, it) }
}
else -> false
}
// Generate possible substitutions
fun generateSubstitutions(term1: Term, term2: Term, substitution: Substitution): Sequence<Substitution> = sequence {
val t1 = applySubstitution(term1, substitution)
val t2 = applySubstitution(term2, substitution)
when {
t1 == t2 -> yield(substitution)
variable(t1) -> {
val variable = t1 as Variable
if (!occurs(variable, t2)) {
variable.bind(t2)
yield(substitution + (variable to t2))
}
}
variable(t2) -> {
val variable = t2 as Variable
if (!occurs(variable, t1)) {
variable.bind(t1)
yield(substitution + (variable to t1))
}
}
compound(t1) && compound(t2) -> {
val structure1 = t1 as Structure
val structure2 = t2 as Structure
if (structure1.functor == structure2.functor) {
val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(substitution) { acc, (arg1, arg2) ->
generateSubstitutions(arg1, arg2, acc).firstOrNull() ?: return@sequence
}
yield(newSubstitution)
}
}
else -> {}
}
}
// Unify two terms with backtracking and lazy evaluation
fun unifyLazy(term1: Term, term2: Term, substitution: Substitution = emptyMap()): Sequence<Substitution> {
return generateSubstitutions(term1, term2, substitution)
}
fun unify(term1: Term, term2: Term): Optional<Substitution> {
val substitutions = unifyLazy(term1, term2).toList()
return if (substitutions.isEmpty()) {
Optional.empty()
} else {
Optional.of(substitutions.first())
}
}

View file

@ -1,8 +1,14 @@
package lexer
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
/**
* Tests for the Prolog lexer.
*
* These tests are based on the Prolog syntax.
*/
class LexerScanPrologTest {
@Test
fun scan_simple_atom() {
@ -25,4 +31,31 @@ class LexerScanPrologTest {
assertEquals(TokenType.DOT, tokens[1].type, "Expected DOT token, got ${tokens[1].type}")
assertEquals(TokenType.EOF, tokens[2].type, "Expected EOF token, got ${tokens[2].type}")
}
@Test
fun scan_variable_with_number() {
val tokens = Lexer("X1.").scan()
assertEquals(3, tokens.size)
assertEquals(TokenType.ALPHANUMERIC, tokens[0].type, "Expected ALPHANUMERIC token, got ${tokens[0].type}")
assertEquals(TokenType.DOT, tokens[1].type, "Expected DOT token, got ${tokens[1].type}")
assertEquals(TokenType.EOF, tokens[2].type, "Expected EOF token, got ${tokens[2].type}")
}
@Test
fun scan_variable_with_underscore() {
val tokens = Lexer("X_1.").scan()
assertEquals(3, tokens.size)
assertEquals(TokenType.ALPHANUMERIC, tokens[0].type, "Expected ALPHANUMERIC token, got ${tokens[0].type}")
assertEquals(TokenType.DOT, tokens[1].type, "Expected DOT token, got ${tokens[1].type}")
assertEquals(TokenType.EOF, tokens[2].type, "Expected EOF token, got ${tokens[2].type}")
}
@Test
fun scan_variable_that_starts_with_a_number() {
assertThrows<Error> { Lexer("1X.").scan() }
}
}

View file

@ -1,8 +1,5 @@
package be.ugent.logprog.lexer
package lexer
import lexer.Error
import lexer.Lexer
import lexer.TokenType
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.Assertions.*

View file

@ -0,0 +1,26 @@
package prolog
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import prolog.components.Program
import prolog.components.expressions.Fact
import prolog.components.terms.Atom
class EvaluationTest {
@Test
fun successful_single_clause_program_query() {
val fact = Fact(Atom("a"))
Program.load(listOf(fact))
assertTrue(Program.query(Atom("a")))
}
@Test
fun failing_single_clause_program_query() {
val fact = Fact(Atom("a"))
Program.load(listOf(fact))
assertFalse(Program.query(Atom("b")))
}
}

246
tests/prolog/UnifyTest.kt Normal file
View file

@ -0,0 +1,246 @@
package prolog
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
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
*/
class UnifyTest {
@Test
fun identical_atoms_unify() {
val atom1 = Atom("a")
val atom2 = Atom("a")
val result = unify(atom1, atom2)
assertTrue(result.isPresent, "Identical atoms should unify")
assertEquals(0, result.get().size, "No substitutions should be made")
}
@Test
fun different_atoms_do_not_unify() {
val atom1 = Atom("a")
val atom2 = Atom("b")
val result = unify(atom1, atom2)
assertFalse(result.isPresent, "Different atoms should not unify")
}
@Test
fun identical_variables_unify() {
val variable1 = Variable("X")
val variable2 = Variable("X")
val result = unify(variable1, variable2)
assertTrue(result.isPresent, "Identical variables should unify")
assertEquals(0, result.get().size, "No substitutions should be made")
}
@Test
fun variable_unifies_with_atom() {
val variable = Variable("X")
val atom = Atom("a")
val result = unify(atom, variable)
assertTrue(result.isPresent, "Variable should unify with atom")
assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(atom, variable.alias().get(), "Variable should be substituted with atom")
}
@Test
fun variables_alias_when_unified() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val result = unify(variable1, variable2)
assertTrue(result.isPresent)
assertEquals(1, result.get().size)
assertEquals(variable2, variable1.alias().get(), "Variable 1 should alias to variable 2")
}
@Test
fun identical_compound_terms_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Identical compound terms should unify")
assertEquals(0, result.get().size, "No substitutions should be made")
}
@Test
fun compound_terms_with_different_arguments_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("c")))
val result = unify(structure1, structure2)
assertFalse(result.isPresent, "Different compound terms should not unify")
}
@Test
fun compound_terms_with_different_functors_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val structure2 = Structure(Atom("g"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertFalse(result.isPresent, "Compound terms with different functors should not unify")
}
@Test
fun variable_unifies_with_compound_term() {
val variable = Variable("X")
val structure = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(variable, structure)
assertTrue(result.isPresent, "Variable should unify with compound term")
assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(structure, variable.alias().get(), "Variable should be substituted with compound term")
}
@Test
fun compound_term_with_variable_unifies_with_part() {
val variable = Variable("X")
val structure1 = Structure(Atom("f"), listOf(Atom("a"), variable))
val structure2 = Structure(Atom("f"), listOf(Atom("a"), Atom("b")))
val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Compound term with variable should unify with part")
assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(Atom("b"), variable.alias().get(), "Variable should be substituted with atom")
}
@Test
fun compound_terms_with_variable_arguments_lists_alias_variables() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(variable1))
val structure2 = Structure(Atom("f"), listOf(variable2))
val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Compound terms with variable arguments should unify")
assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(variable2, variable1.alias().get(), "Variable 1 should alias to variable 2")
}
/**
* f(X) = f(Y, Z)
*/
@Test
fun compound_terms_with_different_arity_do_not_unify() {
val structure1 = Structure(Atom("f"), listOf(Variable("X")))
val structure2 = Structure(Atom("f"), listOf(Variable("Y"), Variable("Z")))
val result = unify(structure1, structure2)
assertFalse(result.isPresent, "Compound terms with different arity should not unify")
}
/**
* f(g(X)) = f(Y)
*/
@Test
fun nested_compound_terms_with_variables_unify() {
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(Structure(Atom("g"), listOf(Variable("X")))))
val structure2 = Structure(Atom("f"), listOf(variable2))
val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Nested compound terms with variables should unify")
assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(Structure(Atom("g"), listOf(Variable("X"))), variable2.alias().get(), "Variable should be substituted with compound term")
}
/**
* f(g(X), X) = f(Y, a)
*/
@Test
fun compound_terms_with_more_variables() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val structure1 = Structure(Atom("f"), listOf(Structure(Atom("g"), listOf(variable1)), variable1))
val structure2 = Structure(Atom("f"), listOf(variable2, Atom("a")))
val result = unify(structure1, structure2)
assertTrue(result.isPresent, "Compound terms with more variables should unify")
assertEquals(2, result.get().size, "There should be two substitutions")
assertEquals(Atom("a"), variable1.alias().get(), "Variable 1 should be substituted with atom")
assertEquals(Structure(Atom("g"), listOf(Atom("a"))), variable2.alias().get(), "Variable 2 should be substituted with compound term")
}
/**
* ?- X = f(X).
* X = f(f(X)).
*/
@Test
fun recursive_unification() {
val variable1 = Variable("X")
val structure2 = Structure(Atom("f"), listOf(Variable("X")))
val result = unify(variable1, structure2)
assertTrue(result.isPresent, "Recursive unification should succeed")
assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(structure2, variable1.alias().get(), "Variable should be substituted with compound term")
}
/**
* ?- X = bar, Y = bar, X = Y.
*/
@Test
fun multiple_unification() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val atom = Atom("bar")
variable1.bind(atom)
variable2.bind(atom)
val result = unify(variable1, variable2)
assertTrue(result.isPresent, "Multiple unification should succeed")
assertEquals(0, result.get().size, "No substitutions should be made")
}
/**
* ?- a = a().
* false.
*/
@Test
fun atom_with_different_arity() {
val atom1 = Atom("a")
val structure2 = Structure(Atom("a"), emptyList())
val result = unify(atom1, structure2)
assertFalse(result.isPresent, "Atom with different arity should not unify")
}
@Test
fun temp() {
val atom1 = Atom("a")
val atom2 = Atom("a")
val result = unify(atom1, atom2)
assertTrue(result.isPresent, "Identical atoms should unify")
}
}

View file

@ -0,0 +1,43 @@
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
/**
* 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)
*
* Notes by [David Tonhofer](https://github.com/dtonhofer)
*/
class TermAnalysisConstructionTest {
@Test
fun atomic_term_properties() {
val atom = Atom("foo")
assertTrue(atomic(atom))
assertFalse(compound(atom))
assertTrue(functor(atom, Atom("foo"), 0))
}
@Test
fun compound_arity_0_properties() {
val structure = Structure(Atom("foo"), emptyList())
assertFalse(atomic(structure))
assertTrue(compound(structure))
}
@Test
fun compound_arity_1_properties() {
val structure = Structure(Atom("foo"), listOf(Atom("bar")))
assertFalse(atomic(structure))
assertTrue(compound(structure))
assertTrue(functor(structure, Atom("foo"), 1))
}
}

View file

@ -0,0 +1,251 @@
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 kotlin.test.assertEquals
class BuiltinsTest {
@Test
fun unbound_variable_is_var() {
val variable = Variable("X")
assertTrue(variable(variable))
assertTrue(variable.alias().isEmpty)
}
@Test
fun variable_bound_to_atom_is_not_var() {
val variable = Variable("X")
assertTrue(variable(variable))
val atom = Atom("a")
variable.bind(atom)
assertFalse(variable(variable))
assertEquals(atom, variable.alias().get())
}
@Test
fun variable_bound_to_compound_term_is_not_var() {
val variable = Variable("X")
assertTrue(variable(variable))
val structure = Structure(Atom("a"), listOf(Atom("b")))
variable.bind(structure)
assertFalse(variable(variable))
assertEquals(structure, variable.alias().get())
}
/**
* ?- X = X, var(X).
* true.
*/
@Test
fun variable_bound_to_itself_is_var() {
val variable = Variable("X")
variable.bind(variable)
assertTrue(variable(variable))
}
/**
* ?- X = Y, var(X).
* X = Y.
*/
@Test
fun variable_bound_to_another_variable_is_var() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
variable1.bind(variable2)
assertTrue(variable(variable1))
assertEquals(variable2, variable1.alias().get())
}
/**
* ?- Y = a, X = Y, var(X).
* false.
*/
@Test
fun variable_bound_to_bound_variable_is_not_var() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
variable2.bind(Atom("a"))
variable1.bind(variable2)
assertFalse(variable(variable1))
}
@Test
fun atom_is_not_var() {
val atom = Atom("a")
assertFalse(variable(atom))
}
@Test
fun compound_term_is_not_var() {
val structure = Structure(Atom("a"), listOf(Atom("b")))
assertFalse(variable(structure))
}
@Test
fun compound_term_with_var_is_not_var() {
val structure = Structure(Atom("a"), listOf(Variable("X")))
assertFalse(variable(structure))
}
@Test
fun unbound_variable_is_not_nonvar() {
val variable = Variable("X")
assertFalse(nonvariable(variable))
}
/**
* ?- A = a, nonvar(A).
* A = a.
*/
@Test
fun variable_bound_to_atom_is_nonvar() {
val variable = Variable("X")
assertFalse(nonvariable(variable))
val atom = Atom("a")
variable.bind(atom)
assertTrue(nonvariable(variable))
assertEquals(atom, variable.alias().get())
}
/**
* ?- A = f(b), nonvar(A).
* A = f(b).
*/
@Test
fun variable_bound_to_compound_term_is_nonvar() {
val variable = Variable("X")
assertFalse(nonvariable(variable))
val structure = Structure(Atom("a"), listOf(Atom("b")))
variable.bind(structure)
assertTrue(nonvariable(variable))
assertEquals(structure, variable.alias().get())
}
/**
* ?- X = X, nonvar(X).
* false.
*/
@Test
fun variable_bound_to_itself_is_not_nonvar() {
val variable = Variable("X")
variable.bind(variable)
assertFalse(nonvariable(variable))
}
/**
* ?- X = Y, nonvar(X).
* false.
*/
@Test
fun variable_bound_to_another_variable_is_not_nonvar() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
variable1.bind(variable2)
assertFalse(nonvariable(variable1))
}
@Test
fun atom_is_nonvar() {
val atom = Atom("a")
assertTrue(nonvariable(atom))
}
@Test
fun compound_term_is_nonvar() {
val structure = Structure(Atom("a"), listOf(Atom("b")))
assertTrue(nonvariable(structure))
}
@Test
fun compound_term_with_var_is_nonvar() {
val structure = Structure(Atom("a"), listOf(Variable("X")))
assertTrue(nonvariable(structure))
}
@Test
fun unbound_variable_is_not_compound() {
val variable = Variable("X")
assertFalse(compound(variable))
}
/**
* ?- A = a, compound(A).
* false.
*/
@Test
fun bound_variable_to_atom_is_not_compound() {
val variable = Variable("X")
val atom = Atom("a")
variable.bind(atom)
assertFalse(compound(variable))
}
/**
* ?- A = f(b), compound(A).
* A = f(b).
*/
@Test
fun bound_variable_to_compound_term_is_compound() {
val variable = Variable("X")
val structure = Structure(Atom("a"), listOf(Atom("b")))
variable.bind(structure)
assertTrue(compound(variable))
}
/**
* ?- Y = f(b), X = Y, compound(X).
* Y = X, X = f(b).
*/
@Test
fun variable_bound_to_bound_variable_is_compound() {
val variable1 = Variable("X")
val variable2 = Variable("Y")
val structure = Structure(Atom("a"), listOf(Atom("b")))
variable2.bind(structure)
variable1.bind(variable2)
assertTrue(compound(variable1))
assertEquals(structure, variable2.alias().get())
assertEquals(variable2, variable1.alias().get())
}
@Test
fun atom_is_not_compound() {
val atom = Atom("a")
assertFalse(compound(atom))
}
@Test
fun compound_term_is_compound() {
val structure = Structure(Atom("a"), listOf(Atom("b")))
assertTrue(compound(structure))
}
/**
* ?- compound(a()).
* true.
*/
@Test
fun compound_term_with_no_arguments_is_compound() {
val structure = Structure(Atom("a"), emptyList())
assertTrue(compound(structure))
}
}

View file

@ -1,29 +0,0 @@
package prolog
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import prolog.terms.Atom
class unifyTest {
@Test
fun identical_atoms_unify() {
val atom1 = Atom("a")
val atom2 = Atom("a")
val result = unify(atom1, atom2)
assertTrue(result.success)
assertEquals(0, result.substitutions.size)
}
@Test
fun different_atoms_do_not_unify() {
val atom1 = Atom("a")
val atom2 = Atom("b")
val result = unify(atom1, atom2)
assertFalse(result.success)
assertEquals(0, result.substitutions.size)
}
}