Checkpoint
This commit is contained in:
parent
5bfa1691dd
commit
a85169dced
27 changed files with 377 additions and 250 deletions
|
@ -1,27 +1,32 @@
|
|||
% choice(X) :- X = 1, !; X = 2.
|
||||
grade(alice, a).
|
||||
grade(bob, b).
|
||||
grade(carol, a).
|
||||
grade(dave, c).
|
||||
:- dynamic declaration/1.
|
||||
|
||||
got_an_a(Student) :-
|
||||
grade(Student, Grade),
|
||||
Grade = a.
|
||||
add_declaration_first(NewDecl) :-
|
||||
asserta(declaration(NewDecl)).
|
||||
|
||||
did_not_get_an_a(Student) :-
|
||||
grade(Student, Grade),
|
||||
Grade \= a.
|
||||
add_declaration_last(NewDecl) :-
|
||||
assertz(declaration(NewDecl)).
|
||||
|
||||
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).
|
||||
|
||||
main :-
|
||||
write("While "),
|
||||
got_an_a(X),
|
||||
write(X), write(" got an A, "), fail;
|
||||
write("but "),
|
||||
did_not_get_an_a(Y),
|
||||
write(Y), write(" did not get an A, "), fail; write("unfortunately."), nl.
|
||||
database,
|
||||
show_declarations,
|
||||
retractall(declaration(_)),
|
||||
show_declarations.
|
||||
|
||||
:- initialization(main).
|
||||
main :- write('gpl zegt: '), groet(wereld), nl.
|
||||
groet(X) :- write(dag(X)).
|
||||
|
|
|
@ -3,8 +3,9 @@ package interpreter
|
|||
import io.Logger
|
||||
import parser.ScriptParser
|
||||
import prolog.ast.Database
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import prolog.ast.logic.Clause
|
||||
import prolog.ast.logic.Predicate
|
||||
|
||||
class FileLoader {
|
||||
private val parser = ScriptParser()
|
||||
|
@ -17,10 +18,8 @@ class FileLoader {
|
|||
Logger.debug("Parsing content of $filePath")
|
||||
val clauses: List<Clause> = parser.parse(input)
|
||||
|
||||
val db = Database(filePath)
|
||||
db.load(clauses)
|
||||
Program.add(db)
|
||||
db.initialize()
|
||||
Logger.debug("Adding clauses to program")
|
||||
addToProgram(clauses, filePath)
|
||||
|
||||
Logger.debug("Finished loading file: $filePath")
|
||||
}
|
||||
|
@ -41,4 +40,16 @@ class FileLoader {
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
// Database
|
||||
term.functor == "dynamic/1" -> Dynamic((args[0] as Atom).name)
|
||||
term.functor == "retract/1" -> Retract(args[0])
|
||||
term.functor == "assert/1" -> {
|
||||
if (args[0] is Rule) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io
|
||||
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import java.io.BufferedReader
|
||||
import java.io.BufferedWriter
|
||||
import java.io.InputStream
|
||||
|
|
|
@ -6,30 +6,32 @@ import com.github.h0tk3y.betterParse.parser.Parser
|
|||
import prolog.ast.arithmetic.Float
|
||||
import prolog.ast.arithmetic.Integer
|
||||
import prolog.ast.terms.*
|
||||
import prolog.builtins.Dynamic
|
||||
|
||||
/**
|
||||
* Precedence is based on the following table:
|
||||
*
|
||||
* | Precedence | Type | Operators |
|
||||
* |------------|------|-----------------------------------------------------------------------------------------------|
|
||||
* | 1200 | xfx | --\>, :-, =\>, ==\> |
|
||||
* | 1200 | fx | :-, ?- |
|
||||
* | 1105 | xfy | \| |
|
||||
* | 1100 | xfy | ; |
|
||||
* | 1050 | xfy | -\>, \*-\> |
|
||||
* | 1000 | xfy | , |
|
||||
* | 990 | xfx | := |
|
||||
* | 900 | fy | \\+ |
|
||||
* | 700 | xfx | \<, =, =.., =:=, =\<, ==, =\\=, \>, \>=, \\=, \\==, as, is, \>:\<, :\< |
|
||||
* | 600 | xfy | : |
|
||||
* | 500 | yfx | +, -, /\\, \\/, xor |
|
||||
* | 500 | fx | ? |
|
||||
* | 400 | yfx | \*, /, //, div, rdiv, \<\<, \>\>, mod, rem |
|
||||
* | 200 | xfx | \*\* |
|
||||
* | 200 | xfy | ^ |
|
||||
* | 200 | fy | +, -, \\ |
|
||||
* | 100 | yfx | . |
|
||||
* | 1 | fx | $ |
|
||||
* | 1200 | xfx | --\>, :-, =\>, ==\> |
|
||||
* | 1200 | fx | :-, ?- |
|
||||
* | 1150 | fx | dynamic |
|
||||
* | 1105 | xfy | | |
|
||||
* | 1100 | xfy | ; |
|
||||
* | 1050 | xfy | ->, *-> |
|
||||
* | 1000 | xfy | , |
|
||||
* | 990 | xfx | := |
|
||||
* | 900 | fy | \+ |
|
||||
* | 700 | xfx | <, =, =.., =:=, =<, ==, =\=, >, >=, \=, \==, as, is, >:<, :< |
|
||||
* | 600 | xfy | : |
|
||||
* | 500 | yfx | +, -, /\, \/, xor |
|
||||
* | 500 | fx | ? |
|
||||
* | 400 | yfx | *, /, //, div, rdiv, <<, >>, mod, rem |
|
||||
* | 200 | xfx | ** |
|
||||
* | 200 | xfy | ^ |
|
||||
* | 200 | fy | +, -, \ |
|
||||
* | 100 | yfx | . |
|
||||
* | 1 | fx | $ |
|
||||
*
|
||||
* It is very easy to extend this grammar to support more operators. Just add them at the appropriate rule or create a
|
||||
* new rule and chain it to the existing ones.
|
||||
|
@ -58,6 +60,8 @@ open class TermsGrammar : Tokens() {
|
|||
protected val int: Parser<Integer> by integerToken use { Integer(text.toInt()) }
|
||||
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)
|
||||
protected val baseTerm: Parser<Term> by (dummy
|
||||
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)) }
|
||||
}
|
||||
|
||||
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 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)) }
|
||||
}
|
||||
|
||||
|
|
|
@ -8,21 +8,29 @@ import com.github.h0tk3y.betterParse.lexer.regexToken
|
|||
import com.github.h0tk3y.betterParse.lexer.token
|
||||
|
||||
abstract class Tokens : Grammar<Any>() {
|
||||
// Special tokens
|
||||
protected val neck by literalToken(":-")
|
||||
protected val leftParenthesis: 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(";")
|
||||
// 1000
|
||||
protected val comma: Token by literalToken(",")
|
||||
// 700
|
||||
protected val equivalent: 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 minus: Token by literalToken("-")
|
||||
protected val notEquals: Token by literalToken("\\=")
|
||||
// 400
|
||||
protected val multiply: Token by literalToken("*")
|
||||
protected val divide: Token by literalToken("/")
|
||||
protected val exclamation: Token by literalToken("!")
|
||||
protected val isOp: Token by literalToken("is")
|
||||
// 100
|
||||
protected val dot by literalToken(".")
|
||||
|
||||
// Prolog tokens
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package prolog.ast
|
||||
|
||||
import io.Logger
|
||||
import prolog.Program
|
||||
import prolog.Answers
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.logic.Clause
|
||||
|
@ -13,10 +12,21 @@ import prolog.ast.terms.Goal
|
|||
/**
|
||||
* Prolog Program or Database
|
||||
*/
|
||||
class Database(val sourceFile: String): Resolvent {
|
||||
open class Database(val sourceFile: String) {
|
||||
var predicates: Map<Functor, Predicate> = emptyMap()
|
||||
|
||||
/**
|
||||
* Initializes the database by running the initialization clauses of that database.
|
||||
*/
|
||||
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")
|
||||
if (predicates.contains("/_")) {
|
||||
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.
|
||||
*
|
||||
* @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) {
|
||||
val functor = clause.functor
|
||||
val predicate = predicates[functor]
|
||||
|
||||
if (predicate != null) {
|
||||
// If the predicate already exists, add the clause to it
|
||||
predicate.add(clause, index)
|
||||
predicate.add(clause, index, force)
|
||||
} else {
|
||||
// If the predicate does not exist, create a new one
|
||||
predicates += Pair(functor, Predicate(listOf(clause)))
|
||||
// If the predicate does not exist, create a new one, usually during Program execution, so dynamic.
|
||||
predicates += Pair(functor, Predicate(listOf(clause), dynamic = true))
|
||||
}
|
||||
|
||||
Logger.debug("Loaded clause $clause into predicate $functor")
|
||||
}
|
||||
}
|
||||
|
||||
fun load(predicate: Predicate) {
|
||||
fun load(predicate: Predicate, force: Boolean = false) {
|
||||
val functor = predicate.functor
|
||||
val existingPredicate = predicates[functor]
|
||||
|
||||
|
@ -70,7 +76,55 @@ class Database(val sourceFile: String): Resolvent {
|
|||
}
|
||||
|
||||
fun clear() {
|
||||
Logger.debug("Clearing ${this::class.java.simpleName}")
|
||||
predicates = emptyMap()
|
||||
if (sourceFile == "") {
|
||||
Logger.debug("Clearing main database")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package prolog.ast.logic
|
||||
|
||||
import prolog.Answers
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.terms.*
|
||||
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].
|
||||
*
|
||||
* @see [prolog.ast.terms.Variable]
|
||||
* @see [Variable]
|
||||
* @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
|
||||
|
||||
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
|
||||
|
||||
var newSubs: Substitutions = subs + renamed
|
||||
unifyLazy(goal, head, newSubs).forEach { headAnswer ->
|
||||
unifyLazy(applySubstitution(goal, subs), head, newSubs).forEach { headAnswer ->
|
||||
headAnswer.map { headSubs ->
|
||||
// If the body can be proven, yield the (combined) substitutions
|
||||
newSubs = subs + renamed + headSubs
|
||||
|
@ -43,8 +43,8 @@ abstract class Clause(val head: Head, val body: Body) : Term, Resolvent {
|
|||
bodyAnswer.fold(
|
||||
onSuccess = { bodySubs ->
|
||||
var result = (headSubs + bodySubs)
|
||||
.mapKeys { reverse[it.key] ?: it.key }
|
||||
.mapValues { reverse[it.value] ?: it.value }
|
||||
.mapKeys { applySubstitution(it.key, reverse)}
|
||||
.mapValues { applySubstitution(it.value, reverse) }
|
||||
result = result.map { it.key to applySubstitution(it.value, result) }
|
||||
.toMap()
|
||||
.filterNot { it.key in renamed.keys && !occurs(it.key as Variable, goal, emptyMap())}
|
||||
|
|
|
@ -15,37 +15,48 @@ import prolog.flags.AppliedCut
|
|||
class Predicate : Resolvent {
|
||||
val functor: Functor
|
||||
val clauses: MutableList<Clause>
|
||||
var dynamic = false
|
||||
|
||||
/**
|
||||
* 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.clauses = mutableListOf()
|
||||
this.dynamic = dynamic
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a predicate with the given clauses.
|
||||
*/
|
||||
constructor(clauses: List<Clause>) {
|
||||
constructor(clauses: List<Clause>, dynamic: Boolean = false) {
|
||||
this.functor = clauses.first().functor
|
||||
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
|
||||
this.clauses = clauses.toMutableList()
|
||||
this.dynamic = dynamic
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(dynamic || force) { "No permission to modify static procedure '$functor'" }
|
||||
|
||||
if (index != null) clauses.add(index, clause) else clauses.add(clause)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(dynamic || force) { "No permission to modify static procedure '$functor'" }
|
||||
|
||||
this.clauses.addAll(clauses)
|
||||
}
|
||||
|
||||
|
@ -75,4 +86,4 @@ class Predicate : Resolvent {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package prolog.ast.terms
|
||||
|
||||
import prolog.Answers
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.logic.LogicOperand
|
||||
|
||||
|
|
|
@ -120,7 +120,8 @@ class Bar(leftOperand: LogicOperand, rightOperand: LogicOperand) : Disjunction(l
|
|||
class Not(private val goal: Goal) : LogicOperator(Atom("\\+"), rightOperand = goal) {
|
||||
override fun satisfy(subs: Substitutions): Answers {
|
||||
// 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()
|
||||
}
|
||||
// If the goal cannot be proven, return a sequence with an empty map
|
||||
|
|
|
@ -6,14 +6,44 @@ import prolog.ast.logic.Clause
|
|||
import prolog.ast.terms.Atom
|
||||
import prolog.ast.terms.Structure
|
||||
import prolog.ast.logic.Predicate
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import prolog.ast.terms.Functor
|
||||
import prolog.ast.terms.Term
|
||||
import prolog.ast.logic.Fact
|
||||
import prolog.ast.Database
|
||||
import prolog.ast.terms.Body
|
||||
import prolog.ast.terms.Goal
|
||||
import prolog.ast.terms.Operator
|
||||
import prolog.logic.applySubstitution
|
||||
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) {
|
||||
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) {
|
||||
override fun satisfy(subs: Substitutions): Answers {
|
||||
// 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()))
|
||||
}
|
||||
|
@ -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) {
|
||||
override fun satisfy(subs: Substitutions): Answers {
|
||||
// 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()))
|
||||
}
|
||||
|
@ -58,26 +90,24 @@ class Retract(val term: Term) : Operator(Atom("retract"), null, term) {
|
|||
|
||||
val functorName = term.functor
|
||||
|
||||
Program.databases
|
||||
.filter { it.predicates.containsKey(functorName) }
|
||||
.mapNotNull { it.predicates[functorName] }
|
||||
.map { predicate ->
|
||||
val clausesIterator = predicate.clauses.iterator()
|
||||
while (clausesIterator.hasNext()) {
|
||||
val clause = clausesIterator.next()
|
||||
unifyLazy(term, clause.head, subs).forEach { unifyResult ->
|
||||
unifyResult.fold(
|
||||
onSuccess = { substitutions ->
|
||||
// If unification is successful, remove the clause
|
||||
yield(Result.success(substitutions))
|
||||
clausesIterator.remove()
|
||||
},
|
||||
onFailure = {
|
||||
// If unification fails, do nothing
|
||||
}
|
||||
)
|
||||
val predicate = Program.db.predicates[functorName]
|
||||
if (predicate == null) {
|
||||
return@sequence
|
||||
}
|
||||
|
||||
predicate.clauses.toList().forEach { clause ->
|
||||
unifyLazy(term, clause.head, subs).forEach { unifyResult ->
|
||||
unifyResult.fold(
|
||||
onSuccess = { substitutions ->
|
||||
// If unification is successful, remove the clause
|
||||
predicate.clauses.remove(clause)
|
||||
yield(Result.success(substitutions))
|
||||
},
|
||||
onFailure = {
|
||||
// If unification fails, do nothing
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.Logger
|
|||
import io.Terminal
|
||||
import parser.ReplParser
|
||||
import prolog.Answers
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.logic.Satisfiable
|
||||
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()))
|
||||
}
|
||||
|
||||
override fun toString(): String = "write($term)"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ import prolog.ast.logic.LogicOperator
|
|||
|
||||
class Initialization(val goal: LogicOperand) : LogicOperator(Atom(":-"), null, goal) {
|
||||
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) {
|
||||
|
|
|
@ -47,17 +47,17 @@ fun numbervars(
|
|||
}
|
||||
|
||||
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
|
||||
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()
|
||||
suggestedName = "${from.name}_${randomInfix}_($start)"
|
||||
suggestedName = "${from.name}@${randomInfix}_($start)"
|
||||
}
|
||||
return Pair(start + 1, mapOf(from to Variable(suggestedName)))
|
||||
}
|
||||
|
||||
compound(term, subs) -> {
|
||||
val from = term as Structure
|
||||
val from = applySubstitution(term, subs) as Structure
|
||||
var n = start
|
||||
val s: MutableMap<Term, Term> = sessionSubs.toMutableMap()
|
||||
from.arguments.forEach { arg ->
|
||||
|
|
|
@ -4,23 +4,24 @@ import prolog.Answer
|
|||
import prolog.Answers
|
||||
import prolog.Substitutions
|
||||
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.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
|
||||
fun applySubstitution(term: Term, subs: Substitutions): Term = when {
|
||||
term is Fact -> {
|
||||
Fact(applySubstitution(term.head, subs) as Head)
|
||||
}
|
||||
|
||||
variable(term, emptyMap()) -> {
|
||||
var result = subs[(term as Variable)]
|
||||
|
||||
while (result != null && result is Variable && result in subs) {
|
||||
result = subs[result]
|
||||
}
|
||||
|
||||
result ?: term
|
||||
val variable = term as Variable
|
||||
subs[variable]?.let { applySubstitution(term = it, subs = subs) } ?: term
|
||||
}
|
||||
atomic(term, subs) -> term
|
||||
compound(term, subs) -> {
|
||||
|
@ -105,9 +106,9 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
|
|||
}
|
||||
|
||||
fun unify(term1: Term, term2: Term): Answer {
|
||||
val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
|
||||
return if (substitutions.isNotEmpty()) {
|
||||
substitutions.first()
|
||||
val substitutions = unifyLazy(term1, term2, emptyMap()).iterator()
|
||||
return if (substitutions.hasNext()) {
|
||||
substitutions.next()
|
||||
} else {
|
||||
Result.failure(NoSuchElementException())
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import io.Terminal
|
|||
import parser.ReplParser
|
||||
import prolog.Answer
|
||||
import prolog.Answers
|
||||
import prolog.Program
|
||||
|
||||
class Repl {
|
||||
private val io = Terminal()
|
||||
|
@ -97,4 +96,4 @@ class Repl {
|
|||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
package e2e
|
||||
|
||||
import interpreter.FileLoader
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import org.junit.jupiter.params.provider.ValueSource
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
|
||||
|
@ -26,21 +25,39 @@ class Examples {
|
|||
System.setOut(PrintStream(outStream))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun debugHelper() {
|
||||
loader.load("examples/basics/backtracking.pl")
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("expectations")
|
||||
fun test(inputFile: String, expected: String) {
|
||||
loader.load(inputFile)
|
||||
@MethodSource("basics")
|
||||
fun `Identical output for basics`(inputFile: String, expected: String) {
|
||||
loader.load("examples/basics/$inputFile")
|
||||
assertEquals(expected, outStream.toString())
|
||||
}
|
||||
|
||||
fun expectations() = listOf(
|
||||
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"),
|
||||
Arguments.of("examples/basics/backtracking.pl", "0\ns(0)\ns(s(0))\ns(s(s(0)))\n"),
|
||||
Arguments.of("examples/basics/cut.pl", "0\n"),
|
||||
Arguments.of("examples/basics/disjunction.pl", "Alice likes Italian food.\nBob likes Italian food.\n"),
|
||||
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"),
|
||||
Arguments.of("examples/basics/write.pl", "gpl zegt: dag(wereld)\n"),
|
||||
@ParameterizedTest
|
||||
@MethodSource("other")
|
||||
fun `Identical output for other`(inputFile: String, expected: String) {
|
||||
loader.load("examples/$inputFile")
|
||||
assertEquals(expected, outStream.toString())
|
||||
}
|
||||
|
||||
fun basics() = listOf(
|
||||
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)")
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import org.junit.jupiter.api.Nested
|
|||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.ValueSource
|
||||
import parser.grammars.TermsGrammar
|
||||
import prolog.Program
|
||||
import prolog.ast.arithmetic.Float
|
||||
import prolog.ast.arithmetic.Integer
|
||||
import prolog.ast.terms.Atom
|
||||
|
|
|
@ -604,5 +604,19 @@ class PreprocessorTests {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@ package interpreter
|
|||
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
|
||||
class SourceFileReaderTests {
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -2,13 +2,13 @@ package parser.builtins
|
|||
|
||||
import com.github.h0tk3y.betterParse.grammar.Grammar
|
||||
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.Test
|
||||
import parser.grammars.TermsGrammar
|
||||
import prolog.ast.terms.Atom
|
||||
import prolog.ast.terms.Structure
|
||||
import prolog.ast.terms.Term
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DatabaseOperatorsParserTests {
|
||||
private lateinit var parser: Grammar<Term>
|
||||
|
@ -62,4 +62,14 @@ class DatabaseOperatorsParserTests {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,12 @@ import prolog.logic.equivalent
|
|||
import prolog.ast.terms.Atom
|
||||
import prolog.ast.terms.Structure
|
||||
import prolog.ast.terms.Variable
|
||||
import prolog.ast.Database.Program
|
||||
|
||||
class EvaluationTests {
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -350,7 +351,7 @@ class EvaluationTests {
|
|||
)
|
||||
)
|
||||
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
Program.load(listOf(fact1, fact2, fact3, rule1))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package prolog.builtins
|
|||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import prolog.Program
|
||||
import prolog.ast.Database.Program
|
||||
import prolog.ast.logic.Fact
|
||||
import prolog.ast.logic.Rule
|
||||
import prolog.ast.terms.Atom
|
||||
|
@ -14,7 +14,7 @@ import prolog.ast.terms.Variable
|
|||
class ControlOperatorsTests {
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -55,7 +55,7 @@ class ControlOperatorsTests {
|
|||
|
||||
// Now with cut
|
||||
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
|
||||
Program.load(
|
||||
listOf(
|
||||
|
@ -104,7 +104,7 @@ class ControlOperatorsTests {
|
|||
|
||||
// Now with cut in the middle
|
||||
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
|
||||
Program.load(
|
||||
listOf(
|
||||
|
@ -138,7 +138,7 @@ class ControlOperatorsTests {
|
|||
|
||||
// Now with cut at the end
|
||||
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
|
||||
Program.load(
|
||||
listOf(
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package prolog.builtins
|
||||
|
||||
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.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
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.Fact
|
||||
import prolog.ast.logic.Predicate
|
||||
|
@ -14,12 +17,11 @@ import prolog.ast.logic.Rule
|
|||
import prolog.ast.terms.Atom
|
||||
import prolog.ast.terms.Structure
|
||||
import prolog.ast.terms.Variable
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class DatabaseOperatorsTests {
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
}
|
||||
|
||||
abstract class AssertTestsBase<T : Structure> {
|
||||
|
@ -27,7 +29,7 @@ class DatabaseOperatorsTests {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
Program.clear()
|
||||
Program.reset()
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -36,8 +38,8 @@ class DatabaseOperatorsTests {
|
|||
val fact = Fact(Atom("a"))
|
||||
createAssert(fact).satisfy(emptyMap())
|
||||
|
||||
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(fact, Program.internalDb.predicates["a/_"]!!.clauses[0])
|
||||
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(fact, Program.db.predicates["a/_"]!!.clauses[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -45,8 +47,8 @@ class DatabaseOperatorsTests {
|
|||
val fact = Fact(Structure(Atom("a"), listOf(Atom("b"))))
|
||||
createAssert(fact).satisfy(emptyMap())
|
||||
|
||||
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(fact, Program.internalDb.predicates["a/1"]!!.clauses[0])
|
||||
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(fact, Program.db.predicates["a/1"]!!.clauses[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -57,8 +59,8 @@ class DatabaseOperatorsTests {
|
|||
)
|
||||
createAssert(rule).satisfy(emptyMap())
|
||||
|
||||
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(rule, Program.internalDb.predicates["a/1"]!!.clauses[0])
|
||||
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(rule, Program.db.predicates["a/1"]!!.clauses[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,9 +90,9 @@ class DatabaseOperatorsTests {
|
|||
AssertA(rule1).satisfy(emptyMap())
|
||||
AssertA(rule2).satisfy(emptyMap())
|
||||
|
||||
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(rule2, Program.internalDb.predicates["a/1"]!!.clauses[0])
|
||||
assertEquals(rule1, Program.internalDb.predicates["a/1"]!!.clauses[1])
|
||||
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(rule2, Program.db.predicates["a/1"]!!.clauses[0])
|
||||
assertEquals(rule1, Program.db.predicates["a/1"]!!.clauses[1])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,9 +115,9 @@ class DatabaseOperatorsTests {
|
|||
AssertZ(rule1).satisfy(emptyMap())
|
||||
AssertZ(rule2).satisfy(emptyMap())
|
||||
|
||||
assertEquals(1, Program.internalDb.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(rule1, Program.internalDb.predicates["a/1"]!!.clauses[0])
|
||||
assertEquals(rule2, Program.internalDb.predicates["a/1"]!!.clauses[1])
|
||||
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
|
||||
assertEquals(rule1, Program.db.predicates["a/1"]!!.clauses[0])
|
||||
assertEquals(rule2, Program.db.predicates["a/1"]!!.clauses[1])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,13 +132,13 @@ class DatabaseOperatorsTests {
|
|||
@Test
|
||||
fun `simple retract`() {
|
||||
val predicate = Predicate(listOf(Fact(Atom("a"))))
|
||||
Program.internalDb.load(predicate)
|
||||
Program.db.load(predicate)
|
||||
|
||||
assertEquals(1, Program.query(Atom("a")).count())
|
||||
|
||||
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")
|
||||
|
||||
assertTrue(retract.satisfy(emptyMap()).none())
|
||||
|
@ -149,7 +151,7 @@ class DatabaseOperatorsTests {
|
|||
Fact(Atom("a")),
|
||||
Fact(Atom("a"))
|
||||
))
|
||||
Program.internalDb.load(predicate)
|
||||
Program.db.load(predicate)
|
||||
|
||||
val control = Program.query(Atom("a")).toList()
|
||||
|
||||
|
@ -157,20 +159,25 @@ class DatabaseOperatorsTests {
|
|||
|
||||
val retract = Retract(Atom("a"))
|
||||
|
||||
val result = retract.satisfy(emptyMap())
|
||||
val result = retract.satisfy(emptyMap()).iterator()
|
||||
|
||||
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")
|
||||
var subs = answer.getOrNull()!!
|
||||
assertTrue(subs.isEmpty(), "Expected no substitutions")
|
||||
assertTrue(answer.getOrNull()!!.isEmpty(), "Expected no substitutions")
|
||||
|
||||
assertTrue(result.hasNext(), "Expected more results")
|
||||
assertEquals(2, predicate.clauses.size, "Expected 2 clauses")
|
||||
|
||||
assertTrue(result.first().isSuccess)
|
||||
assertTrue(result.first().isSuccess)
|
||||
assertTrue(result.next().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")
|
||||
}
|
||||
|
||||
|
@ -181,7 +188,7 @@ class DatabaseOperatorsTests {
|
|||
Fact(Structure(Atom("a"), listOf(Atom("c")))),
|
||||
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()
|
||||
|
||||
|
@ -189,38 +196,40 @@ class DatabaseOperatorsTests {
|
|||
|
||||
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")
|
||||
|
||||
var answer = result.first()
|
||||
assertTrue(result.hasNext(), "Expected more results")
|
||||
var answer = result.next()
|
||||
|
||||
assertTrue(answer.isSuccess, "Expected success")
|
||||
var subs = answer.getOrNull()!!
|
||||
assertTrue(subs.isNotEmpty(), "Expected substitutions")
|
||||
assertTrue(Variable("X") in subs, "Expected variable X")
|
||||
assertEquals(Atom("b"), subs[Variable("X")], "Expected b")
|
||||
assertTrue(result.hasNext(), "Expected more results")
|
||||
assertEquals(2, predicate.clauses.size, "Expected 2 clauses")
|
||||
|
||||
answer = result.first()
|
||||
answer = result.next()
|
||||
|
||||
assertTrue(answer.isSuccess, "Expected success")
|
||||
subs = answer.getOrNull()!!
|
||||
assertTrue(subs.isNotEmpty(), "Expected substitutions")
|
||||
assertTrue(Variable("X") in subs, "Expected variable X")
|
||||
assertEquals(Atom("c"), subs[Variable("X")], "Expected c")
|
||||
assertTrue(result.hasNext(), "Expected more results")
|
||||
assertEquals(1, predicate.clauses.size, "Expected 1 clause")
|
||||
|
||||
answer = result.first()
|
||||
answer = result.next()
|
||||
|
||||
assertTrue(answer.isSuccess, "Expected success")
|
||||
subs = answer.getOrNull()!!
|
||||
assertTrue(subs.isNotEmpty(), "Expected substitutions")
|
||||
assertTrue(Variable("X") in subs, "Expected variable X")
|
||||
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, result.count(), "Expected no remaining results")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -29,7 +29,7 @@ class TermsTests {
|
|||
assertEquals(start + 1, end, "Expected end to be incremented by 1")
|
||||
assertEquals(1, subs.size, "Expected one substitution")
|
||||
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
|
||||
|
@ -53,9 +53,9 @@ class TermsTests {
|
|||
assertEquals(start + 2, end, "Expected end to be incremented by 2")
|
||||
assertEquals(2, subs.size, "Expected two substitutions")
|
||||
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")
|
||||
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
|
||||
|
@ -68,9 +68,9 @@ class TermsTests {
|
|||
assertEquals(start + 1, end, "Expected end to be incremented by 1")
|
||||
assertEquals(1, subs.size, "Expected one substitution")
|
||||
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")
|
||||
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
|
||||
|
@ -83,7 +83,7 @@ class TermsTests {
|
|||
assertEquals(start + 1, end, "Expected end to be incremented by 1")
|
||||
assertEquals(1, subs.size, "Expected one substitution")
|
||||
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
|
||||
|
@ -97,13 +97,14 @@ class TermsTests {
|
|||
assertEquals(start + 1, end1, "Expected end to be incremented by 1")
|
||||
assertEquals(1, subs1.size, "Expected one substitution")
|
||||
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)
|
||||
|
||||
assertEquals(start + 2, end2, "Expected end to be incremented by 2")
|
||||
assertEquals(1, subs2.size, "Expected one substitution")
|
||||
assertTrue(subs2.containsKey(variable), "Expected subs to contain the variable")
|
||||
assertEquals(Variable("X($end1)"), subs2[variable], "Expected subs to contain the new term")
|
||||
assertTrue(subs2.containsKey(variable1), "Expected subs to contain the variable")
|
||||
assertEquals(Variable("X@$start@$end1"), subs2[variable1], "Expected subs to contain the new term")
|
||||
}
|
||||
}
|
Reference in a new issue