Checkpoint

This commit is contained in:
Tibo De Peuter 2025-05-04 21:50:58 +02:00
parent 5bfa1691dd
commit a85169dced
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
27 changed files with 377 additions and 250 deletions

View file

@ -1,27 +1,32 @@
% choice(X) :- X = 1, !; X = 2. % choice(X) :- X = 1, !; X = 2.
grade(alice, a). :- dynamic declaration/1.
grade(bob, b).
grade(carol, a).
grade(dave, c).
got_an_a(Student) :- add_declaration_first(NewDecl) :-
grade(Student, Grade), asserta(declaration(NewDecl)).
Grade = a.
did_not_get_an_a(Student) :- add_declaration_last(NewDecl) :-
grade(Student, Grade), assertz(declaration(NewDecl)).
Grade \= a.
database :-
add_declaration_first('Man is born free, and everywhere he is in chains.'),
retract(declaration(_)),
add_declaration_last('The revolution devours its own children.'),
add_declaration_first('I disapprove of what you say, but I will defend to the death your right to say it.'),
add_declaration_first('Give me Liberty, or give me Death!'),
add_declaration_last('So this is how liberty dies, with thunderous applause.').
show_declarations :-
declaration(Decl),
write(Decl), nl,
fail.
show_declarations.
:- initialization(main). :- initialization(main).
main :- main :-
write("While "), database,
got_an_a(X), show_declarations,
write(X), write(" got an A, "), fail; retractall(declaration(_)),
write("but "), show_declarations.
did_not_get_an_a(Y),
write(Y), write(" did not get an A, "), fail; write("unfortunately."), nl.
:- initialization(main).
main :- write('gpl zegt: '), groet(wereld), nl.
groet(X) :- write(dag(X)).

View file

@ -3,8 +3,9 @@ package interpreter
import io.Logger import io.Logger
import parser.ScriptParser import parser.ScriptParser
import prolog.ast.Database import prolog.ast.Database
import prolog.Program import prolog.ast.Database.Program
import prolog.ast.logic.Clause import prolog.ast.logic.Clause
import prolog.ast.logic.Predicate
class FileLoader { class FileLoader {
private val parser = ScriptParser() private val parser = ScriptParser()
@ -17,10 +18,8 @@ class FileLoader {
Logger.debug("Parsing content of $filePath") Logger.debug("Parsing content of $filePath")
val clauses: List<Clause> = parser.parse(input) val clauses: List<Clause> = parser.parse(input)
val db = Database(filePath) Logger.debug("Adding clauses to program")
db.load(clauses) addToProgram(clauses, filePath)
Program.add(db)
db.initialize()
Logger.debug("Finished loading file: $filePath") Logger.debug("Finished loading file: $filePath")
} }
@ -41,4 +40,16 @@ class FileLoader {
throw RuntimeException("Error reading file: $filePath", e) throw RuntimeException("Error reading file: $filePath", e)
} }
} }
fun addToProgram(clauses: List<Clause>, filePath: String) {
Logger.debug("Grouping clauses by functor")
val groupedClauses = clauses.groupBy { it.functor }
val predicates: List<Predicate> = groupedClauses.map { (_, clauses) ->
Predicate(clauses)
}
val database = Database(filePath)
predicates.forEach { database.load(it) }
Program.consult(database)
}
} }

View file

@ -92,6 +92,7 @@ open class Preprocessor {
term.functor == "between/3" && args.all { it is Expression } -> Between(args[0] as Expression, args[1] as Expression, args[2] as Expression) term.functor == "between/3" && args.all { it is Expression } -> Between(args[0] as Expression, args[1] as Expression, args[2] as Expression)
// Database // Database
term.functor == "dynamic/1" -> Dynamic((args[0] as Atom).name)
term.functor == "retract/1" -> Retract(args[0]) term.functor == "retract/1" -> Retract(args[0])
term.functor == "assert/1" -> { term.functor == "assert/1" -> {
if (args[0] is Rule) { if (args[0] is Rule) {

View file

@ -1,6 +1,6 @@
package io package io
import prolog.Program import prolog.ast.Database.Program
import java.io.BufferedReader import java.io.BufferedReader
import java.io.BufferedWriter import java.io.BufferedWriter
import java.io.InputStream import java.io.InputStream

View file

@ -6,6 +6,7 @@ import com.github.h0tk3y.betterParse.parser.Parser
import prolog.ast.arithmetic.Float import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.terms.* import prolog.ast.terms.*
import prolog.builtins.Dynamic
/** /**
* Precedence is based on the following table: * Precedence is based on the following table:
@ -14,20 +15,21 @@ import prolog.ast.terms.*
* |------------|------|-----------------------------------------------------------------------------------------------| * |------------|------|-----------------------------------------------------------------------------------------------|
* | 1200 | xfx | --\>, :-, =\>, ==\> | * | 1200 | xfx | --\>, :-, =\>, ==\> |
* | 1200 | fx | :-, ?- | * | 1200 | fx | :-, ?- |
* | 1105 | xfy | \| | * | 1150 | fx | dynamic |
* | 1105 | xfy | | |
* | 1100 | xfy | ; | * | 1100 | xfy | ; |
* | 1050 | xfy | -\>, \*-\> | * | 1050 | xfy | ->, *-> |
* | 1000 | xfy | , | * | 1000 | xfy | , |
* | 990 | xfx | := | * | 990 | xfx | := |
* | 900 | fy | \\+ | * | 900 | fy | \+ |
* | 700 | xfx | \<, =, =.., =:=, =\<, ==, =\\=, \>, \>=, \\=, \\==, as, is, \>:\<, :\< | * | 700 | xfx | <, =, =.., =:=, =<, ==, =\=, >, >=, \=, \==, as, is, >:<, :< |
* | 600 | xfy | : | * | 600 | xfy | : |
* | 500 | yfx | +, -, /\\, \\/, xor | * | 500 | yfx | +, -, /\, \/, xor |
* | 500 | fx | ? | * | 500 | fx | ? |
* | 400 | yfx | \*, /, //, div, rdiv, \<\<, \>\>, mod, rem | * | 400 | yfx | *, /, //, div, rdiv, <<, >>, mod, rem |
* | 200 | xfx | \*\* | * | 200 | xfx | ** |
* | 200 | xfy | ^ | * | 200 | xfy | ^ |
* | 200 | fy | +, -, \\ | * | 200 | fy | +, -, \ |
* | 100 | yfx | . | * | 100 | yfx | . |
* | 1 | fx | $ | * | 1 | fx | $ |
* *
@ -58,6 +60,8 @@ open class TermsGrammar : Tokens() {
protected val int: Parser<Integer> by integerToken use { Integer(text.toInt()) } protected val int: Parser<Integer> by integerToken use { Integer(text.toInt()) }
protected val float: Parser<Float> by floatToken use { Float(text.toFloat()) } protected val float: Parser<Float> by floatToken use { Float(text.toFloat()) }
protected val functor: Parser<String> by (nameToken * divide * int) use { "${t1.text}${t2.text}$t3" }
// Base terms (atoms, compounds, variables, numbers) // Base terms (atoms, compounds, variables, numbers)
protected val baseTerm: Parser<Term> by (dummy protected val baseTerm: Parser<Term> by (dummy
or (-leftParenthesis * parser(::term) * -rightParenthesis) or (-leftParenthesis * parser(::term) * -rightParenthesis)
@ -98,8 +102,13 @@ open class TermsGrammar : Tokens() {
t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) } t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
} }
protected val dynamic: Parser<Term> by (dynamicOp * functor) use {
CompoundTerm( Atom(t1.text), listOf(Atom(t2)) )
}
protected val term1150: Parser<Term> by (dynamic or term1100) use { this }
protected val op1200: Parser<String> by (neck) use { text } protected val op1200: Parser<String> by (neck) use { text }
protected val term1200: Parser<Term> by (term1100 * zeroOrMore(op1200 * term1100)) use { protected val term1200: Parser<Term> by (term1150 * zeroOrMore(op1200 * term1100)) use {
t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) } t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
} }

View file

@ -8,21 +8,29 @@ import com.github.h0tk3y.betterParse.lexer.regexToken
import com.github.h0tk3y.betterParse.lexer.token import com.github.h0tk3y.betterParse.lexer.token
abstract class Tokens : Grammar<Any>() { abstract class Tokens : Grammar<Any>() {
// Special tokens
protected val neck by literalToken(":-")
protected val leftParenthesis: Token by literalToken("(") protected val leftParenthesis: Token by literalToken("(")
protected val rightParenthesis: Token by literalToken(")") protected val rightParenthesis: Token by literalToken(")")
protected val comma: Token by literalToken(",") protected val exclamation: Token by literalToken("!")
// 1200
protected val neck by literalToken(":-")
// 1150
protected val dynamicOp by literalToken("dynamic")
// 1100
protected val semicolon: Token by literalToken(";") protected val semicolon: Token by literalToken(";")
// 1000
protected val comma: Token by literalToken(",")
// 700
protected val equivalent: Token by literalToken("==") protected val equivalent: Token by literalToken("==")
protected val equals: Token by literalToken("=") protected val equals: Token by literalToken("=")
protected val notEquals: Token by literalToken("\\=") protected val isOp: Token by literalToken("is")
// 500
protected val plus: Token by literalToken("+") protected val plus: Token by literalToken("+")
protected val minus: Token by literalToken("-") protected val minus: Token by literalToken("-")
protected val notEquals: Token by literalToken("\\=")
// 400
protected val multiply: Token by literalToken("*") protected val multiply: Token by literalToken("*")
protected val divide: Token by literalToken("/") protected val divide: Token by literalToken("/")
protected val exclamation: Token by literalToken("!") // 100
protected val isOp: Token by literalToken("is")
protected val dot by literalToken(".") protected val dot by literalToken(".")
// Prolog tokens // Prolog tokens

View file

@ -1,57 +0,0 @@
package prolog
import io.Logger
import prolog.ast.Database
import prolog.ast.logic.Clause
import prolog.ast.logic.Resolvent
import prolog.ast.terms.Goal
/**
* Object to handle execution
*
* This object is a singleton that manages a list of databases.
*/
object Program : Resolvent {
val internalDb = Database("")
val databases: MutableList<Database> = mutableListOf(internalDb)
var storeNewLine: Boolean = false
var variableRenamingStart: Int = 0
fun add(database: Database) {
databases.add(database)
}
/**
* Queries the program with a goal.
* @return true if the goal can be proven, false otherwise.
*/
fun query(goal: Goal): Answers = solve(goal, emptyMap())
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
Logger.debug("Solving goal $goal")
for (database in databases) {
yieldAll(database.solve(goal, subs))
}
}
fun load(clauses: List<Clause>, index: Int? = null) = internalDb.load(clauses, index)
fun clear() {
databases.forEach { it.clear() }
}
fun clear(filePath: String) {
val correspondingDBs = databases.filter { it.sourceFile == filePath }
require(correspondingDBs.isNotEmpty()) { "No database found for file: $filePath" }
correspondingDBs.forEach { it.clear() }
}
fun reset() {
clear()
variableRenamingStart = 0
storeNewLine = false
}
}

View file

@ -1,7 +1,6 @@
package prolog.ast package prolog.ast
import io.Logger import io.Logger
import prolog.Program
import prolog.Answers import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.logic.Clause import prolog.ast.logic.Clause
@ -13,10 +12,21 @@ import prolog.ast.terms.Goal
/** /**
* Prolog Program or Database * Prolog Program or Database
*/ */
class Database(val sourceFile: String): Resolvent { open class Database(val sourceFile: String) {
var predicates: Map<Functor, Predicate> = emptyMap() var predicates: Map<Functor, Predicate> = emptyMap()
/**
* Initializes the database by running the initialization clauses of that database.
*/
fun initialize() { fun initialize() {
databases.add(this)
if (sourceFile !== "") {
Logger.debug("Moving clauses from $sourceFile to main database")
predicates.filter { it.key != "/_" }
.forEach { (_, predicate) -> db.load(predicate, force = true) }
}
Logger.info("Initializing database from $sourceFile") Logger.info("Initializing database from $sourceFile")
if (predicates.contains("/_")) { if (predicates.contains("/_")) {
Logger.debug("Loading clauses from /_ predicate") Logger.debug("Loading clauses from /_ predicate")
@ -28,35 +38,31 @@ class Database(val sourceFile: String): Resolvent {
} }
} }
override fun solve(goal: Goal, subs: Substitutions): Answers {
val functor = goal.functor
// If the predicate does not exist, return false
val predicate = predicates[functor] ?: return emptySequence()
// If the predicate exists, evaluate the goal against it
return predicate.solve(goal, subs)
}
/** /**
* Loads a list of clauses into the program. * Loads a list of clauses into the program.
*
* @param clauses The list of clauses to load.
* @param index The index at which to insert the clause. If null, the clause is added to the end of the list.
* @param force If true, the clause is added even if the predicate is static.
*/ */
fun load(clauses: List<Clause>, index: Int? = null) { fun load(clauses: List<Clause>, index: Int? = null, force: Boolean = false) {
for (clause in clauses) { for (clause in clauses) {
val functor = clause.functor val functor = clause.functor
val predicate = predicates[functor] val predicate = predicates[functor]
if (predicate != null) { if (predicate != null) {
// If the predicate already exists, add the clause to it // If the predicate already exists, add the clause to it
predicate.add(clause, index) predicate.add(clause, index, force)
} else { } else {
// If the predicate does not exist, create a new one // If the predicate does not exist, create a new one, usually during Program execution, so dynamic.
predicates += Pair(functor, Predicate(listOf(clause))) predicates += Pair(functor, Predicate(listOf(clause), dynamic = true))
} }
Logger.debug("Loaded clause $clause into predicate $functor") Logger.debug("Loaded clause $clause into predicate $functor")
} }
} }
fun load(predicate: Predicate) { fun load(predicate: Predicate, force: Boolean = false) {
val functor = predicate.functor val functor = predicate.functor
val existingPredicate = predicates[functor] val existingPredicate = predicates[functor]
@ -70,7 +76,55 @@ class Database(val sourceFile: String): Resolvent {
} }
fun clear() { fun clear() {
Logger.debug("Clearing ${this::class.java.simpleName}") if (sourceFile == "") {
Logger.debug("Clearing main database")
predicates = emptyMap() predicates = emptyMap()
return
}
Logger.debug("Clearing database $sourceFile")
// Remove our clauses from the database
predicates.forEach { (_, predicate) ->
val dbPredicate = db.predicates[predicate.functor]
predicate.clauses.forEach { clause -> dbPredicate?.clauses?.remove(clause) }
}
databases.remove(this)
}
/**
* Object to handle execution
*
* This object is a singleton that manages a list of databases.
*/
companion object Program : Resolvent {
var db = Database("")
var databases: MutableList<Database> = mutableListOf(db)
var storeNewLine: Boolean = false
var variableRenamingStart: Int = 0
/**
* Queries the program with a goal.
* @return true if the goal can be proven, false otherwise.
*/
fun query(goal: Goal): Answers = solve(goal, emptyMap())
override fun solve(goal: Goal, subs: Substitutions): Answers {
val functor = goal.functor
// If the predicate does not exist, return false
val predicate = db.predicates[functor] ?: return emptySequence()
// If the predicate exists, evaluate the goal against it
return predicate.solve(goal, subs)
}
fun consult(database: Database) = database.initialize()
fun disregard(database: Database) = database.clear()
fun load(clauses: List<Clause>, index: Int? = null, force: Boolean = false) = db.load(clauses, index, force)
fun reset() {
databases.toList().map { it.clear() }
variableRenamingStart = 0
storeNewLine = false
}
} }
} }

View file

@ -1,7 +1,7 @@
package prolog.ast.logic package prolog.ast.logic
import prolog.Answers import prolog.Answers
import prolog.Program import prolog.ast.Database.Program
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.terms.* import prolog.ast.terms.*
import prolog.builtins.True import prolog.builtins.True
@ -16,10 +16,10 @@ import prolog.logic.unifyLazy
* *
* A clause consists of a [Head] and body separated by the 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.ast.terms.Variable] * @see [Variable]
* @see [Predicate] * @see [Predicate]
*/ */
abstract class Clause(val head: Head, val body: Body) : Term, Resolvent { abstract class Clause(var head: Head, var body: Body) : Term, Resolvent {
val functor: Functor = head.functor val functor: Functor = head.functor
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence { override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
@ -35,7 +35,7 @@ abstract class Clause(val head: Head, val body: Body) : Term, Resolvent {
Program.variableRenamingStart = end Program.variableRenamingStart = end
var newSubs: Substitutions = subs + renamed var newSubs: Substitutions = subs + renamed
unifyLazy(goal, head, newSubs).forEach { headAnswer -> unifyLazy(applySubstitution(goal, subs), head, newSubs).forEach { headAnswer ->
headAnswer.map { headSubs -> headAnswer.map { headSubs ->
// If the body can be proven, yield the (combined) substitutions // If the body can be proven, yield the (combined) substitutions
newSubs = subs + renamed + headSubs newSubs = subs + renamed + headSubs
@ -43,8 +43,8 @@ abstract class Clause(val head: Head, val body: Body) : Term, Resolvent {
bodyAnswer.fold( bodyAnswer.fold(
onSuccess = { bodySubs -> onSuccess = { bodySubs ->
var result = (headSubs + bodySubs) var result = (headSubs + bodySubs)
.mapKeys { reverse[it.key] ?: it.key } .mapKeys { applySubstitution(it.key, reverse)}
.mapValues { reverse[it.value] ?: it.value } .mapValues { applySubstitution(it.value, reverse) }
result = result.map { it.key to applySubstitution(it.value, result) } result = result.map { it.key to applySubstitution(it.value, result) }
.toMap() .toMap()
.filterNot { it.key in renamed.keys && !occurs(it.key as Variable, goal, emptyMap())} .filterNot { it.key in renamed.keys && !occurs(it.key as Variable, goal, emptyMap())}

View file

@ -15,37 +15,48 @@ import prolog.flags.AppliedCut
class Predicate : Resolvent { class Predicate : Resolvent {
val functor: Functor val functor: Functor
val clauses: MutableList<Clause> val clauses: MutableList<Clause>
var dynamic = false
/** /**
* Creates a predicate with the given functor and an empty list of clauses. * Creates a predicate with the given functor and an empty list of clauses.
*/ */
constructor(functor: Functor) { constructor(functor: Functor, dynamic: Boolean = false) {
this.functor = functor this.functor = functor
this.clauses = mutableListOf() this.clauses = mutableListOf()
this.dynamic = dynamic
} }
/** /**
* Creates a predicate with the given clauses. * Creates a predicate with the given clauses.
*/ */
constructor(clauses: List<Clause>) { constructor(clauses: List<Clause>, dynamic: Boolean = false) {
this.functor = clauses.first().functor this.functor = clauses.first().functor
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" } require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
this.clauses = clauses.toMutableList() this.clauses = clauses.toMutableList()
this.dynamic = dynamic
} }
/** /**
* Adds a clause to the predicate. * Adds a clause to the predicate.
*
* @param clause The clause to add.
* @param index The index at which to insert the clause. If null, the clause is added to the end of the list.
* @param force If true, the clause is added even if the predicate is static.
*/ */
fun add(clause: Clause, index: Int? = null) { fun add(clause: Clause, index: Int? = null, force: Boolean = false) {
require(clause.functor == functor) { "Clause functor does not match predicate functor" } require(clause.functor == functor) { "Clause functor does not match predicate functor" }
require(dynamic || force) { "No permission to modify static procedure '$functor'" }
if (index != null) clauses.add(index, clause) else clauses.add(clause) if (index != null) clauses.add(index, clause) else clauses.add(clause)
} }
/** /**
* Adds a list of clauses to the predicate. * Adds a list of clauses to the predicate.
*/ */
fun addAll(clauses: List<Clause>) { fun addAll(clauses: List<Clause>, force: Boolean = false) {
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" } require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
require(dynamic || force) { "No permission to modify static procedure '$functor'" }
this.clauses.addAll(clauses) this.clauses.addAll(clauses)
} }

View file

@ -1,7 +1,7 @@
package prolog.ast.terms package prolog.ast.terms
import prolog.Answers import prolog.Answers
import prolog.Program import prolog.ast.Database.Program
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.logic.LogicOperand import prolog.ast.logic.LogicOperand

View file

@ -120,7 +120,8 @@ class Bar(leftOperand: LogicOperand, rightOperand: LogicOperand) : Disjunction(l
class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) { class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) {
override fun satisfy(subs: Substitutions): Answers { override fun satisfy(subs: Substitutions): Answers {
// If the goal can be proven, return an empty sequence // If the goal can be proven, return an empty sequence
if (goal.satisfy(subs).toList().isNotEmpty()) { val goalResults = goal.satisfy(subs).iterator()
if (goalResults.hasNext()) {
return emptySequence() return emptySequence()
} }
// If the goal cannot be proven, return a sequence with an empty map // If the goal cannot be proven, return a sequence with an empty map

View file

@ -6,14 +6,44 @@ import prolog.ast.logic.Clause
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.logic.Predicate import prolog.ast.logic.Predicate
import prolog.Program import prolog.ast.Database.Program
import prolog.ast.terms.Functor import prolog.ast.terms.Functor
import prolog.ast.terms.Term import prolog.ast.terms.Term
import prolog.ast.logic.Fact import prolog.ast.logic.Fact
import prolog.ast.Database import prolog.ast.Database
import prolog.ast.terms.Body
import prolog.ast.terms.Goal
import prolog.ast.terms.Operator import prolog.ast.terms.Operator
import prolog.logic.applySubstitution
import prolog.logic.unifyLazy import prolog.logic.unifyLazy
/**
* (Make) the [Predicate] with the corresponding [Functor] dynamic.
*/
class Dynamic(private val dynamicFunctor: Functor): Goal(), Body {
override val functor: Functor = "dynamic/1"
override fun satisfy(subs: Substitutions): Answers {
val predicate = Program.db.predicates[dynamicFunctor]
if (predicate == null) {
return sequenceOf(Result.failure(Exception("Predicate $dynamicFunctor not found")))
}
predicate.dynamic = true
return sequenceOf(Result.success(emptyMap()))
}
override fun toString(): String = "dynamic $dynamicFunctor"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Dynamic) return false
return dynamicFunctor == other.dynamicFunctor
}
override fun hashCode(): Int = super.hashCode()
}
class Assert(clause: Clause) : AssertZ(clause) { class Assert(clause: Clause) : AssertZ(clause) {
override val functor: Functor = "assert/1" override val functor: Functor = "assert/1"
} }
@ -24,7 +54,8 @@ class Assert(clause: Clause) : AssertZ(clause) {
class AssertA(val clause: Clause) : Operator(Atom("asserta"), null, clause) { class AssertA(val clause: Clause) : Operator(Atom("asserta"), null, clause) {
override fun satisfy(subs: Substitutions): Answers { override fun satisfy(subs: Substitutions): Answers {
// Add clause to the program // Add clause to the program
Program.load(listOf(clause), 0) val evaluatedClause = applySubstitution(clause, subs) as Clause
Program.load(listOf(evaluatedClause), 0)
return sequenceOf(Result.success(emptyMap())) return sequenceOf(Result.success(emptyMap()))
} }
@ -36,7 +67,8 @@ class AssertA(val clause: Clause) : Operator(Atom("asserta"), null, clause) {
open class AssertZ(val clause: Clause) : Operator(Atom("assertz"), null, clause) { open class AssertZ(val clause: Clause) : Operator(Atom("assertz"), null, clause) {
override fun satisfy(subs: Substitutions): Answers { override fun satisfy(subs: Substitutions): Answers {
// Add clause to the program // Add clause to the program
Program.load(listOf(clause)) val evaluatedClause = applySubstitution(clause, subs) as Clause
Program.load(listOf(evaluatedClause))
return sequenceOf(Result.success(emptyMap())) return sequenceOf(Result.success(emptyMap()))
} }
@ -58,19 +90,18 @@ class Retract(val term: Term) : Operator(Atom("retract"), null, term) {
val functorName = term.functor val functorName = term.functor
Program.databases val predicate = Program.db.predicates[functorName]
.filter { it.predicates.containsKey(functorName) } if (predicate == null) {
.mapNotNull { it.predicates[functorName] } return@sequence
.map { predicate -> }
val clausesIterator = predicate.clauses.iterator()
while (clausesIterator.hasNext()) { predicate.clauses.toList().forEach { clause ->
val clause = clausesIterator.next()
unifyLazy(term, clause.head, subs).forEach { unifyResult -> unifyLazy(term, clause.head, subs).forEach { unifyResult ->
unifyResult.fold( unifyResult.fold(
onSuccess = { substitutions -> onSuccess = { substitutions ->
// If unification is successful, remove the clause // If unification is successful, remove the clause
predicate.clauses.remove(clause)
yield(Result.success(substitutions)) yield(Result.success(substitutions))
clausesIterator.remove()
}, },
onFailure = { onFailure = {
// If unification fails, do nothing // If unification fails, do nothing
@ -79,5 +110,4 @@ class Retract(val term: Term) : Operator(Atom("retract"), null, term) {
} }
} }
} }
}
} }

View file

@ -4,7 +4,7 @@ import io.Logger
import io.Terminal import io.Terminal
import parser.ReplParser import parser.ReplParser
import prolog.Answers import prolog.Answers
import prolog.Program import prolog.ast.Database.Program
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.logic.Satisfiable import prolog.ast.logic.Satisfiable
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
@ -26,6 +26,8 @@ class Write(private val term: Term) : Operator(Atom("write"), null, term), Satis
return sequenceOf(Result.success(emptyMap())) return sequenceOf(Result.success(emptyMap()))
} }
override fun toString(): String = "write($term)"
} }
/** /**

View file

@ -8,6 +8,7 @@ import prolog.ast.logic.LogicOperator
class Initialization(val goal: LogicOperand) : LogicOperator(Atom(":-"), null, goal) { class Initialization(val goal: LogicOperand) : LogicOperator(Atom(":-"), null, goal) {
override fun satisfy(subs: Substitutions): Answers = goal.satisfy(subs).take(1) override fun satisfy(subs: Substitutions): Answers = goal.satisfy(subs).take(1)
override fun toString(): String = goal.toString()
} }
class Query(val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) { class Query(val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {

View file

@ -47,17 +47,17 @@ fun numbervars(
} }
val from = term as Variable val from = term as Variable
var suggestedName = "${from.name}($start)" var suggestedName = "${from.name}@$start"
// If the suggested name is already in use, find a new one // If the suggested name is already in use, find a new one
while ((subs + sessionSubs).filter { (it.key as Variable).name == suggestedName }.isNotEmpty()) { while ((subs + sessionSubs).any { (it.key as Variable).name == suggestedName }) {
val randomInfix = ((0..9) + ('a'..'z') + ('A'..'Z')).random() val randomInfix = ((0..9) + ('a'..'z') + ('A'..'Z')).random()
suggestedName = "${from.name}_${randomInfix}_($start)" suggestedName = "${from.name}@${randomInfix}_($start)"
} }
return Pair(start + 1, mapOf(from to Variable(suggestedName))) return Pair(start + 1, mapOf(from to Variable(suggestedName)))
} }
compound(term, subs) -> { compound(term, subs) -> {
val from = term as Structure val from = applySubstitution(term, subs) as Structure
var n = start var n = start
val s: MutableMap<Term, Term> = sessionSubs.toMutableMap() val s: MutableMap<Term, Term> = sessionSubs.toMutableMap()
from.arguments.forEach { arg -> from.arguments.forEach { arg ->

View file

@ -4,23 +4,24 @@ import prolog.Answer
import prolog.Answers import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.ast.logic.LogicOperator
import prolog.ast.terms.*
import kotlin.NoSuchElementException
import prolog.ast.arithmetic.Number
import prolog.ast.arithmetic.Integer
import prolog.ast.arithmetic.Float import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.arithmetic.Number
import prolog.ast.logic.Clause
import prolog.ast.logic.Fact
import prolog.ast.logic.LogicOperator
import prolog.ast.logic.Rule
import prolog.ast.terms.*
// Apply substitutions to a term // Apply substitutions to a term
fun applySubstitution(term: Term, subs: Substitutions): Term = when { fun applySubstitution(term: Term, subs: Substitutions): Term = when {
variable(term, emptyMap()) -> { term is Fact -> {
var result = subs[(term as Variable)] Fact(applySubstitution(term.head, subs) as Head)
while (result != null && result is Variable && result in subs) {
result = subs[result]
} }
result ?: term variable(term, emptyMap()) -> {
val variable = term as Variable
subs[variable]?.let { applySubstitution(term = it, subs = subs) } ?: term
} }
atomic(term, subs) -> term atomic(term, subs) -> term
compound(term, subs) -> { compound(term, subs) -> {
@ -105,9 +106,9 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
} }
fun unify(term1: Term, term2: Term): Answer { fun unify(term1: Term, term2: Term): Answer {
val substitutions = unifyLazy(term1, term2, emptyMap()).toList() val substitutions = unifyLazy(term1, term2, emptyMap()).iterator()
return if (substitutions.isNotEmpty()) { return if (substitutions.hasNext()) {
substitutions.first() substitutions.next()
} else { } else {
Result.failure(NoSuchElementException()) Result.failure(NoSuchElementException())
} }

View file

@ -6,7 +6,6 @@ import io.Terminal
import parser.ReplParser import parser.ReplParser
import prolog.Answer import prolog.Answer
import prolog.Answers import prolog.Answers
import prolog.Program
class Repl { class Repl {
private val io = Terminal() private val io = Terminal()

View file

@ -1,15 +1,14 @@
package e2e package e2e
import interpreter.FileLoader import interpreter.FileLoader
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.junit.jupiter.params.provider.ValueSource import prolog.ast.Database.Program
import prolog.Program
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.PrintStream import java.io.PrintStream
@ -26,21 +25,39 @@ class Examples {
System.setOut(PrintStream(outStream)) System.setOut(PrintStream(outStream))
} }
@Test
fun debugHelper() {
loader.load("examples/basics/backtracking.pl")
}
@ParameterizedTest @ParameterizedTest
@MethodSource("expectations") @MethodSource("basics")
fun test(inputFile: String, expected: String) { fun `Identical output for basics`(inputFile: String, expected: String) {
loader.load(inputFile) loader.load("examples/basics/$inputFile")
assertEquals(expected, outStream.toString()) assertEquals(expected, outStream.toString())
} }
fun expectations() = listOf( @ParameterizedTest
Arguments.of("examples/basics/arithmetics.pl", "gimli is a level 4 fighter with 35 hitpoints.\nlegolas is a level 5 ranger with 30 hitpoints.\ngandalf is a level 10 wizard with 25 hitpoints.\nfrodo is a level 2 rogue with 20 hitpoints.\nlegolas threw gimli, and gimli took 5 damage.\ngimli is a level 4 fighter with 30 hitpoints.\ngandalf casts aid.\ngimli is a level 4 fighter with 35 hitpoints.\nlegolas leveled up.\nlegolas is a level 6 ranger with 30 hitpoints"), @MethodSource("other")
Arguments.of("examples/basics/backtracking.pl", "0\ns(0)\ns(s(0))\ns(s(s(0)))\n"), fun `Identical output for other`(inputFile: String, expected: String) {
Arguments.of("examples/basics/cut.pl", "0\n"), loader.load("examples/$inputFile")
Arguments.of("examples/basics/disjunction.pl", "Alice likes Italian food.\nBob likes Italian food.\n"), assertEquals(expected, outStream.toString())
Arguments.of("examples/basics/equality.pl", "X == Y failed\nX = Y succeeded\nX == Y succeeded\nX = Y succeeded\nX == Y succeeded\n"), }
Arguments.of("examples/basics/fraternity.pl", "Citizen robespierre is eligible for the event.\nCitizen danton is eligible for the event.\nCitizen camus is eligible for the event.\n"),
Arguments.of("examples/basics/unification.pl", "While alice got an A, carol got an A, but bob did not get an A, dave did not get an A, unfortunately.\n"), fun basics() = listOf(
Arguments.of("examples/basics/write.pl", "gpl zegt: dag(wereld)\n"), Arguments.of("arithmetics.pl", "gimli is a level 4 fighter with 35 hitpoints.\nlegolas is a level 5 ranger with 30 hitpoints.\ngandalf is a level 10 wizard with 25 hitpoints.\nfrodo is a level 2 rogue with 20 hitpoints.\nlegolas threw gimli, and gimli took 5 damage.\ngimli is a level 4 fighter with 30 hitpoints.\ngandalf casts aid.\ngimli is a level 4 fighter with 35 hitpoints.\nlegolas leveled up.\nlegolas is a level 6 ranger with 30 hitpoints"),
Arguments.of("backtracking.pl", "0\ns(0)\ns(s(0))\ns(s(s(0)))\n"),
Arguments.of("cut.pl", "0\n"),
Arguments.of("disjunction.pl", "Alice likes Italian food.\nBob likes Italian food.\n"),
Arguments.of("equality.pl", "X == Y failed\nX = Y succeeded\nX == Y succeeded\nX = Y succeeded\nX == Y succeeded\n"),
Arguments.of("forall.pl", "Only alice likes pizza.\n"),
Arguments.of("fraternity.pl", "Citizen robespierre is eligible for the event.\nCitizen danton is eligible for the event.\nCitizen camus is eligible for the event.\n"),
Arguments.of("liberty.pl", "Give me Liberty, or give me Death!\nI disapprove of what you say, but I will defend to the death your right to say it.\nThe revolution devours its own children.\nSo this is how liberty dies, with thunderous applause.\n"),
Arguments.of("unification.pl", "While alice got an A, carol got an A, but bob did not get an A, dave did not get an A, unfortunately.\n"),
Arguments.of("write.pl", "gpl zegt: dag(wereld)\n"),
)
fun other() = listOf(
Arguments.of("program.pl", "10\nhello(world)")
) )
} }

View file

@ -6,7 +6,6 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource import org.junit.jupiter.params.provider.ValueSource
import parser.grammars.TermsGrammar import parser.grammars.TermsGrammar
import prolog.Program
import prolog.ast.arithmetic.Float import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom import prolog.ast.terms.Atom

View file

@ -604,5 +604,19 @@ class PreprocessorTests {
assertEquals(expected, result) assertEquals(expected, result)
} }
@Test
fun `dynamic declaration`() {
val input = Structure(
Atom("dynamic"), listOf(
Atom("declaration/1")
)
)
val expected = Dynamic("declaration/1")
val result = preprocessor.preprocess(input)
assertEquals(expected, result)
}
} }
} }

View file

@ -2,12 +2,12 @@ package interpreter
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.Program import prolog.ast.Database.Program
class SourceFileReaderTests { class SourceFileReaderTests {
@BeforeEach @BeforeEach
fun setup() { fun setup() {
Program.clear() Program.reset()
} }
@Test @Test

View file

@ -2,13 +2,13 @@ package parser.builtins
import com.github.h0tk3y.betterParse.grammar.Grammar import com.github.h0tk3y.betterParse.grammar.Grammar
import com.github.h0tk3y.betterParse.grammar.parseToEnd import com.github.h0tk3y.betterParse.grammar.parseToEnd
import org.junit.jupiter.api.Assertions.assertEquals
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 parser.grammars.TermsGrammar import parser.grammars.TermsGrammar
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Term import prolog.ast.terms.Term
import kotlin.test.assertEquals
class DatabaseOperatorsParserTests { class DatabaseOperatorsParserTests {
private lateinit var parser: Grammar<Term> private lateinit var parser: Grammar<Term>
@ -62,4 +62,14 @@ class DatabaseOperatorsParserTests {
assertEquals(expected, result) assertEquals(expected, result)
} }
@Test
fun `parse dynamic declaration`() {
val input = "dynamic declaration/1"
val expected = Structure(Atom("dynamic"), listOf(Atom("declaration/1")))
val result = parser.parseToEnd(input)
assertEquals(expected, result)
}
} }

View file

@ -13,11 +13,12 @@ import prolog.logic.equivalent
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.ast.Database.Program
class EvaluationTests { class EvaluationTests {
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
Program.clear() Program.reset()
} }
@Test @Test
@ -350,7 +351,7 @@ class EvaluationTests {
) )
) )
Program.clear() Program.reset()
Program.load(listOf(fact1, fact2, fact3, rule1)) Program.load(listOf(fact1, fact2, fact3, rule1))
} }

View file

@ -3,7 +3,7 @@ package prolog.builtins
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
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.Program import prolog.ast.Database.Program
import prolog.ast.logic.Fact import prolog.ast.logic.Fact
import prolog.ast.logic.Rule import prolog.ast.logic.Rule
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
@ -14,7 +14,7 @@ import prolog.ast.terms.Variable
class ControlOperatorsTests { class ControlOperatorsTests {
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
Program.clear() Program.reset()
} }
@Test @Test
@ -55,7 +55,7 @@ class ControlOperatorsTests {
// Now with cut // Now with cut
Program.clear() Program.reset()
Program.load( Program.load(
listOf( listOf(
@ -104,7 +104,7 @@ class ControlOperatorsTests {
// Now with cut in the middle // Now with cut in the middle
Program.clear() Program.reset()
Program.load( Program.load(
listOf( listOf(
@ -138,7 +138,7 @@ class ControlOperatorsTests {
// Now with cut at the end // Now with cut at the end
Program.clear() Program.reset()
Program.load( Program.load(
listOf( listOf(

View file

@ -1,12 +1,15 @@
package prolog.builtins package prolog.builtins
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
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.BeforeEach
import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource import org.junit.jupiter.params.provider.ValueSource
import prolog.Program import prolog.ast.Database
import prolog.ast.Database.Program
import prolog.ast.logic.Clause import prolog.ast.logic.Clause
import prolog.ast.logic.Fact import prolog.ast.logic.Fact
import prolog.ast.logic.Predicate import prolog.ast.logic.Predicate
@ -14,12 +17,11 @@ import prolog.ast.logic.Rule
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import kotlin.test.assertTrue
class DatabaseOperatorsTests { class DatabaseOperatorsTests {
@BeforeEach @BeforeEach
fun setup() { fun setup() {
Program.clear() Program.reset()
} }
abstract class AssertTestsBase<T : Structure> { abstract class AssertTestsBase<T : Structure> {
@ -27,7 +29,7 @@ class DatabaseOperatorsTests {
@BeforeEach @BeforeEach
fun setup() { fun setup() {
Program.clear() Program.reset()
} }
@ParameterizedTest @ParameterizedTest
@ -36,8 +38,8 @@ class DatabaseOperatorsTests {
val fact = Fact(Atom("a")) val fact = Fact(Atom("a"))
createAssert(fact).satisfy(emptyMap()) createAssert(fact).satisfy(emptyMap())
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate") assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(fact, Program.internalDb.predicates["a/_"]!!.clauses[0]) assertEquals(fact, Program.db.predicates["a/_"]!!.clauses[0])
} }
@Test @Test
@ -45,8 +47,8 @@ class DatabaseOperatorsTests {
val fact = Fact(Structure(Atom("a"), listOf(Atom("b")))) val fact = Fact(Structure(Atom("a"), listOf(Atom("b"))))
createAssert(fact).satisfy(emptyMap()) createAssert(fact).satisfy(emptyMap())
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate") assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(fact, Program.internalDb.predicates["a/1"]!!.clauses[0]) assertEquals(fact, Program.db.predicates["a/1"]!!.clauses[0])
} }
@Test @Test
@ -57,8 +59,8 @@ class DatabaseOperatorsTests {
) )
createAssert(rule).satisfy(emptyMap()) createAssert(rule).satisfy(emptyMap())
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate") assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule, Program.internalDb.predicates["a/1"]!!.clauses[0]) assertEquals(rule, Program.db.predicates["a/1"]!!.clauses[0])
} }
} }
@ -88,9 +90,9 @@ class DatabaseOperatorsTests {
AssertA(rule1).satisfy(emptyMap()) AssertA(rule1).satisfy(emptyMap())
AssertA(rule2).satisfy(emptyMap()) AssertA(rule2).satisfy(emptyMap())
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate") assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule2, Program.internalDb.predicates["a/1"]!!.clauses[0]) assertEquals(rule2, Program.db.predicates["a/1"]!!.clauses[0])
assertEquals(rule1, Program.internalDb.predicates["a/1"]!!.clauses[1]) assertEquals(rule1, Program.db.predicates["a/1"]!!.clauses[1])
} }
} }
@ -113,9 +115,9 @@ class DatabaseOperatorsTests {
AssertZ(rule1).satisfy(emptyMap()) AssertZ(rule1).satisfy(emptyMap())
AssertZ(rule2).satisfy(emptyMap()) AssertZ(rule2).satisfy(emptyMap())
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate") assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule1, Program.internalDb.predicates["a/1"]!!.clauses[0]) assertEquals(rule1, Program.db.predicates["a/1"]!!.clauses[0])
assertEquals(rule2, Program.internalDb.predicates["a/1"]!!.clauses[1]) assertEquals(rule2, Program.db.predicates["a/1"]!!.clauses[1])
} }
} }
@ -130,13 +132,13 @@ class DatabaseOperatorsTests {
@Test @Test
fun `simple retract`() { fun `simple retract`() {
val predicate = Predicate(listOf(Fact(Atom("a")))) val predicate = Predicate(listOf(Fact(Atom("a"))))
Program.internalDb.load(predicate) Program.db.load(predicate)
assertEquals(1, Program.query(Atom("a")).count()) assertEquals(1, Program.query(Atom("a")).count())
val retract = Retract(Atom("a")) val retract = Retract(Atom("a"))
assertTrue(retract.satisfy(emptyMap()).any(), "Expected 1 result") assertEquals(1, retract.satisfy(emptyMap()).toList().size, "Expected 1 result")
assertEquals(0, predicate.clauses.size, "Expected 0 clauses") assertEquals(0, predicate.clauses.size, "Expected 0 clauses")
assertTrue(retract.satisfy(emptyMap()).none()) assertTrue(retract.satisfy(emptyMap()).none())
@ -149,7 +151,7 @@ class DatabaseOperatorsTests {
Fact(Atom("a")), Fact(Atom("a")),
Fact(Atom("a")) Fact(Atom("a"))
)) ))
Program.internalDb.load(predicate) Program.db.load(predicate)
val control = Program.query(Atom("a")).toList() val control = Program.query(Atom("a")).toList()
@ -157,20 +159,25 @@ class DatabaseOperatorsTests {
val retract = Retract(Atom("a")) val retract = Retract(Atom("a"))
val result = retract.satisfy(emptyMap()) val result = retract.satisfy(emptyMap()).iterator()
assertEquals(3, predicate.clauses.size, "Expected 3 clauses") assertEquals(3, predicate.clauses.size, "Expected 3 clauses")
var answer = result.first() assertTrue(result.hasNext(), "Expected more results")
val answer = result.next()
assertTrue(answer.isSuccess, "Expected success") assertTrue(answer.isSuccess, "Expected success")
var subs = answer.getOrNull()!! assertTrue(answer.getOrNull()!!.isEmpty(), "Expected no substitutions")
assertTrue(subs.isEmpty(), "Expected no substitutions")
assertTrue(result.hasNext(), "Expected more results")
assertEquals(2, predicate.clauses.size, "Expected 2 clauses") assertEquals(2, predicate.clauses.size, "Expected 2 clauses")
assertTrue(result.first().isSuccess) assertTrue(result.next().isSuccess)
assertTrue(result.first().isSuccess) assertTrue(result.hasNext(), "Expected more results")
assertTrue(result.next().isSuccess)
assertFalse(result.hasNext(), "Expected more results")
assertEquals(0, predicate.clauses.size, "Expected no remaining clauses") assertEquals(0, predicate.clauses.size, "Expected no remaining clauses")
} }
@ -181,7 +188,7 @@ class DatabaseOperatorsTests {
Fact(Structure(Atom("a"), listOf(Atom("c")))), Fact(Structure(Atom("a"), listOf(Atom("c")))),
Fact(Structure(Atom("a"), listOf(Atom("d")))) Fact(Structure(Atom("a"), listOf(Atom("d"))))
)) ))
Program.internalDb.load(predicate) Program.db.load(predicate)
val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList() val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList()
@ -189,38 +196,40 @@ class DatabaseOperatorsTests {
val retract = Retract(Structure(Atom("a"), listOf(Variable("X")))) val retract = Retract(Structure(Atom("a"), listOf(Variable("X"))))
val result = retract.satisfy(emptyMap()) val result = retract.satisfy(emptyMap()).iterator()
assertEquals(3, predicate.clauses.size, "Expected 3 clauses") assertEquals(3, predicate.clauses.size, "Expected 3 clauses")
var answer = result.first() assertTrue(result.hasNext(), "Expected more results")
var answer = result.next()
assertTrue(answer.isSuccess, "Expected success") assertTrue(answer.isSuccess, "Expected success")
var subs = answer.getOrNull()!! var subs = answer.getOrNull()!!
assertTrue(subs.isNotEmpty(), "Expected substitutions") assertTrue(subs.isNotEmpty(), "Expected substitutions")
assertTrue(Variable("X") in subs, "Expected variable X") assertTrue(Variable("X") in subs, "Expected variable X")
assertEquals(Atom("b"), subs[Variable("X")], "Expected b") assertEquals(Atom("b"), subs[Variable("X")], "Expected b")
assertTrue(result.hasNext(), "Expected more results")
assertEquals(2, predicate.clauses.size, "Expected 2 clauses") assertEquals(2, predicate.clauses.size, "Expected 2 clauses")
answer = result.first() answer = result.next()
assertTrue(answer.isSuccess, "Expected success") assertTrue(answer.isSuccess, "Expected success")
subs = answer.getOrNull()!! subs = answer.getOrNull()!!
assertTrue(subs.isNotEmpty(), "Expected substitutions") assertTrue(subs.isNotEmpty(), "Expected substitutions")
assertTrue(Variable("X") in subs, "Expected variable X") assertTrue(Variable("X") in subs, "Expected variable X")
assertEquals(Atom("c"), subs[Variable("X")], "Expected c") assertEquals(Atom("c"), subs[Variable("X")], "Expected c")
assertTrue(result.hasNext(), "Expected more results")
assertEquals(1, predicate.clauses.size, "Expected 1 clause") assertEquals(1, predicate.clauses.size, "Expected 1 clause")
answer = result.first() answer = result.next()
assertTrue(answer.isSuccess, "Expected success") assertTrue(answer.isSuccess, "Expected success")
subs = answer.getOrNull()!! subs = answer.getOrNull()!!
assertTrue(subs.isNotEmpty(), "Expected substitutions") assertTrue(subs.isNotEmpty(), "Expected substitutions")
assertTrue(Variable("X") in subs, "Expected variable X") assertTrue(Variable("X") in subs, "Expected variable X")
assertEquals(Atom("d"), subs[Variable("X")], "Expected d") assertEquals(Atom("d"), subs[Variable("X")], "Expected d")
assertFalse(result.hasNext(), "Expected no more results")
assertEquals(0, predicate.clauses.size, "Expected no clauses") assertEquals(0, predicate.clauses.size, "Expected no clauses")
assertEquals(0, result.count(), "Expected no remaining results")
} }
@Test @Test

View file

@ -29,7 +29,7 @@ class TermsTests {
assertEquals(start + 1, end, "Expected end to be incremented by 1") assertEquals(start + 1, end, "Expected end to be incremented by 1")
assertEquals(1, subs.size, "Expected one substitution") assertEquals(1, subs.size, "Expected one substitution")
assertTrue(subs.containsKey(term), "Expected subs to contain the original term") assertTrue(subs.containsKey(term), "Expected subs to contain the original term")
assertEquals(Variable("X($start)"), subs[term], "Expected subs to contain the new term") assertEquals(Variable("X@$start"), subs[term], "Expected subs to contain the new term")
} }
@Test @Test
@ -53,9 +53,9 @@ class TermsTests {
assertEquals(start + 2, end, "Expected end to be incremented by 2") assertEquals(start + 2, end, "Expected end to be incremented by 2")
assertEquals(2, subs.size, "Expected two substitutions") assertEquals(2, subs.size, "Expected two substitutions")
assertTrue(subs.containsKey(term.arguments[0]), "Expected subs to contain the first original term") assertTrue(subs.containsKey(term.arguments[0]), "Expected subs to contain the first original term")
assertEquals(Variable("X($start)"), subs[term.arguments[0]], "Expected subs to contain the new term") assertEquals(Variable("X@$start"), subs[term.arguments[0]], "Expected subs to contain the new term")
assertTrue(subs.containsKey(term.arguments[1]), "Expected subs to contain the second original term") assertTrue(subs.containsKey(term.arguments[1]), "Expected subs to contain the second original term")
assertEquals(Variable("Y(${start + 1})"), subs[term.arguments[1]], "Expected subs to contain the new term") assertEquals(Variable("Y@${start + 1}"), subs[term.arguments[1]], "Expected subs to contain the new term")
} }
@Test @Test
@ -68,9 +68,9 @@ class TermsTests {
assertEquals(start + 1, end, "Expected end to be incremented by 1") assertEquals(start + 1, end, "Expected end to be incremented by 1")
assertEquals(1, subs.size, "Expected one substitution") assertEquals(1, subs.size, "Expected one substitution")
assertTrue(subs.containsKey(term.arguments[0]), "Expected subs to contain the first original term") assertTrue(subs.containsKey(term.arguments[0]), "Expected subs to contain the first original term")
assertEquals(Variable("X($start)"), subs[term.arguments[0]], "Expected subs to contain the new term") assertEquals(Variable("X@$start"), subs[term.arguments[0]], "Expected subs to contain the new term")
assertTrue(subs.containsKey(term.arguments[1]), "Expected subs to contain the second original term") assertTrue(subs.containsKey(term.arguments[1]), "Expected subs to contain the second original term")
assertEquals(Variable("X($start)"), subs[term.arguments[1]], "Expected subs to contain the new term") assertEquals(Variable("X@$start"), subs[term.arguments[1]], "Expected subs to contain the new term")
} }
@Test @Test
@ -83,7 +83,7 @@ class TermsTests {
assertEquals(start + 1, end, "Expected end to be incremented by 1") assertEquals(start + 1, end, "Expected end to be incremented by 1")
assertEquals(1, subs.size, "Expected one substitution") assertEquals(1, subs.size, "Expected one substitution")
assertTrue(subs.containsKey(Variable("X")), "Expected subs to contain the variable") assertTrue(subs.containsKey(Variable("X")), "Expected subs to contain the variable")
assertEquals(Variable("X($start)"), subs[term.arguments[0]], "Expected subs to contain the new term") assertEquals(Variable("X@$start"), subs[term.arguments[0]], "Expected subs to contain the new term")
} }
@Test @Test
@ -97,13 +97,14 @@ class TermsTests {
assertEquals(start + 1, end1, "Expected end to be incremented by 1") assertEquals(start + 1, end1, "Expected end to be incremented by 1")
assertEquals(1, subs1.size, "Expected one substitution") assertEquals(1, subs1.size, "Expected one substitution")
assertTrue(subs1.containsKey(variable), "Expected subs to contain the variable") assertTrue(subs1.containsKey(variable), "Expected subs to contain the variable")
assertEquals(Variable("X($start)"), subs1[variable], "Expected subs to contain the new term") assertEquals(Variable("X@$start"), subs1[variable], "Expected subs to contain the new term")
val variable1 = subs1[variable] as Variable
val (end2, subs2) = numbervars(term, end1, subs1) val (end2, subs2) = numbervars(term, end1, subs1)
assertEquals(start + 2, end2, "Expected end to be incremented by 2") assertEquals(start + 2, end2, "Expected end to be incremented by 2")
assertEquals(1, subs2.size, "Expected one substitution") assertEquals(1, subs2.size, "Expected one substitution")
assertTrue(subs2.containsKey(variable), "Expected subs to contain the variable") assertTrue(subs2.containsKey(variable1), "Expected subs to contain the variable")
assertEquals(Variable("X($end1)"), subs2[variable], "Expected subs to contain the new term") assertEquals(Variable("X@$start@$end1"), subs2[variable1], "Expected subs to contain the new term")
} }
} }