Checkpoint
This commit is contained in:
parent
43b364044e
commit
9db1c66781
34 changed files with 746 additions and 194 deletions
|
@ -1,32 +1,25 @@
|
|||
package prolog
|
||||
|
||||
import io.Logger
|
||||
import prolog.ast.Database
|
||||
import prolog.ast.logic.Clause
|
||||
import prolog.ast.logic.Predicate
|
||||
import prolog.ast.logic.Resolvent
|
||||
import prolog.ast.terms.Functor
|
||||
import prolog.ast.terms.Goal
|
||||
|
||||
typealias Database = Program
|
||||
|
||||
/**
|
||||
* Prolog Program or database.
|
||||
* Object to handle execution
|
||||
*
|
||||
* This object is a singleton that manages a list of databases.
|
||||
*/
|
||||
object Program: Resolvent {
|
||||
var predicates: Map<Functor, Predicate> = emptyMap()
|
||||
object Program : Resolvent {
|
||||
private val internalDb = Database("")
|
||||
private val databases: MutableList<Database> = mutableListOf(internalDb)
|
||||
|
||||
init {
|
||||
Logger.debug("Initializing ${this::class.java.simpleName}")
|
||||
setup()
|
||||
Logger.debug("Initialization of ${this::class.java.simpleName} complete")
|
||||
}
|
||||
var storeNewLine: Boolean = false
|
||||
var variableRenamingStart: Int = 0
|
||||
|
||||
private fun setup() {
|
||||
Logger.debug("Setting up ${this::class.java.simpleName}")
|
||||
|
||||
// Initialize the program with built-in predicates
|
||||
load(listOf(
|
||||
))
|
||||
fun add(database: Database) {
|
||||
databases.add(database)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,51 +28,24 @@ object Program: Resolvent {
|
|||
*/
|
||||
fun query(goal: Goal): Answers = solve(goal, emptyMap())
|
||||
|
||||
override fun solve(goal: Goal, subs: Substitutions): Answers {
|
||||
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
|
||||
Logger.debug("Solving goal $goal")
|
||||
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.
|
||||
*/
|
||||
fun load(clauses: List<Clause>) {
|
||||
for (clause in clauses) {
|
||||
val functor = clause.functor
|
||||
val predicate = predicates[functor]
|
||||
|
||||
if (predicate != null) {
|
||||
// If the predicate already exists, add the clause to it
|
||||
predicate.add(clause)
|
||||
} else {
|
||||
// If the predicate does not exist, create a new one
|
||||
predicates += Pair(functor, Predicate(listOf(clause)))
|
||||
}
|
||||
|
||||
Logger.debug("Loaded clause $clause into predicate $functor")
|
||||
for (database in databases) {
|
||||
yieldAll(database.solve(goal, subs))
|
||||
}
|
||||
}
|
||||
|
||||
fun load(predicate: Predicate) {
|
||||
val functor = predicate.functor
|
||||
val existingPredicate = predicates[functor]
|
||||
|
||||
if (existingPredicate != null) {
|
||||
// If the predicate already exists, add the clauses to it
|
||||
existingPredicate.addAll(predicate.clauses)
|
||||
} else {
|
||||
// If the predicate does not exist, create a new one
|
||||
predicates += Pair(functor, predicate)
|
||||
}
|
||||
}
|
||||
fun load(clauses: List<Clause>) = internalDb.load(clauses)
|
||||
|
||||
fun clear() {
|
||||
Logger.debug("Clearing ${this::class.java.simpleName}")
|
||||
predicates = emptyMap()
|
||||
setup()
|
||||
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() }
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import prolog.ast.terms.Term
|
|||
|
||||
abstract class Substitution(val from: Term, val to: Term) {
|
||||
val mapped: Pair<Term, Term>? = if (from != to) from to to else null
|
||||
override fun toString(): String = "$from -> $to"
|
||||
override fun toString(): String = "$from |-> $to"
|
||||
}
|
||||
typealias Substitutions = Map<Term, Term>
|
||||
typealias Answer = Result<Substitutions>
|
||||
|
|
76
src/prolog/ast/Database.kt
Normal file
76
src/prolog/ast/Database.kt
Normal file
|
@ -0,0 +1,76 @@
|
|||
package prolog.ast
|
||||
|
||||
import io.Logger
|
||||
import prolog.Program
|
||||
import prolog.Answers
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.logic.Clause
|
||||
import prolog.ast.logic.Predicate
|
||||
import prolog.ast.logic.Resolvent
|
||||
import prolog.ast.terms.Functor
|
||||
import prolog.ast.terms.Goal
|
||||
|
||||
/**
|
||||
* Prolog Program or Database
|
||||
*/
|
||||
class Database(val sourceFile: String): Resolvent {
|
||||
private var predicates: Map<Functor, Predicate> = emptyMap()
|
||||
|
||||
fun initialize() {
|
||||
Logger.info("Initializing database from $sourceFile")
|
||||
if (predicates.contains("/_")) {
|
||||
Logger.debug("Loading clauses from /_ predicate")
|
||||
predicates["/_"]?.clauses?.forEach {
|
||||
Logger.debug("Loading clause $it")
|
||||
val goal = it.body as Goal
|
||||
goal.satisfy(emptyMap()).toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
fun load(clauses: List<Clause>) {
|
||||
for (clause in clauses) {
|
||||
val functor = clause.functor
|
||||
val predicate = predicates[functor]
|
||||
|
||||
if (predicate != null) {
|
||||
// If the predicate already exists, add the clause to it
|
||||
predicate.add(clause)
|
||||
} else {
|
||||
// If the predicate does not exist, create a new one
|
||||
predicates += Pair(functor, Predicate(listOf(clause)))
|
||||
}
|
||||
|
||||
Logger.debug("Loaded clause $clause into predicate $functor")
|
||||
}
|
||||
}
|
||||
|
||||
fun load(predicate: Predicate) {
|
||||
val functor = predicate.functor
|
||||
val existingPredicate = predicates[functor]
|
||||
|
||||
if (existingPredicate != null) {
|
||||
// If the predicate already exists, add the clauses to it
|
||||
existingPredicate.addAll(predicate.clauses)
|
||||
} else {
|
||||
// If the predicate does not exist, create a new one
|
||||
predicates += Pair(functor, predicate)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
Logger.debug("Clearing ${this::class.java.simpleName}")
|
||||
predicates = emptyMap()
|
||||
}
|
||||
}
|
|
@ -31,4 +31,15 @@ class Float(override val value: kotlin.Float): Number {
|
|||
is Integer -> Float(value * other.value.toFloat())
|
||||
else -> throw IllegalArgumentException("Cannot multiply $this and $other")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Float) return false
|
||||
if (value != other.value) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return super.hashCode()
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
package prolog.ast.logic
|
||||
|
||||
import prolog.Answers
|
||||
import prolog.Program
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.terms.Body
|
||||
import prolog.ast.terms.Functor
|
||||
import prolog.ast.terms.Goal
|
||||
import prolog.ast.terms.Head
|
||||
import prolog.ast.terms.*
|
||||
import prolog.builtins.True
|
||||
import prolog.flags.AppliedCut
|
||||
import prolog.logic.applySubstitution
|
||||
import prolog.logic.numbervars
|
||||
import prolog.logic.unifyLazy
|
||||
|
||||
/**
|
||||
|
@ -21,23 +21,39 @@ import prolog.logic.unifyLazy
|
|||
abstract class Clause(val head: Head, val body: Body) : Resolvent {
|
||||
val functor: Functor = head.functor
|
||||
|
||||
override fun solve (goal: Goal, subs: Substitutions): Answers = sequence {
|
||||
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
|
||||
// If the clause is a rule, unify the goal with the head and then try to prove the body.
|
||||
// Only if the body can be proven, the substitutions should be returned.
|
||||
// Do this in a lazy way.
|
||||
unifyLazy(goal, head, subs).forEach { headAnswer ->
|
||||
headAnswer.map { newHeadSubs ->
|
||||
|
||||
// Since we are only interested in substitutions in the goal (as opposed to the head of this clause),
|
||||
// we can use variable renaming and filter out the substitutions that are not in the goal.
|
||||
val (end, renamed: Substitutions) = numbervars(head, Program.variableRenamingStart, subs)
|
||||
|
||||
val reverse = renamed.entries.associate { (a, b) -> b to a }
|
||||
Program.variableRenamingStart = end
|
||||
|
||||
var newSubs: Substitutions = subs + renamed
|
||||
unifyLazy(goal, head, newSubs).forEach { headAnswer ->
|
||||
headAnswer.map { headSubs ->
|
||||
// If the body can be proven, yield the (combined) substitutions
|
||||
body.satisfy(subs + newHeadSubs).forEach { bodyAnswer ->
|
||||
newSubs = subs + renamed + headSubs
|
||||
body.satisfy(newSubs).forEach { bodyAnswer ->
|
||||
bodyAnswer.fold(
|
||||
onSuccess = { newBodySubs ->
|
||||
yield(Result.success(newHeadSubs + newBodySubs))
|
||||
onSuccess = { bodySubs ->
|
||||
var result = (headSubs + bodySubs)
|
||||
.mapKeys { reverse[it.key] ?: it.key }
|
||||
.mapValues { reverse[it.value] ?: it.value }
|
||||
result = result.map { it.key to applySubstitution(it.value, result) }
|
||||
.toMap()
|
||||
.filterNot { it.key in renamed.keys }
|
||||
yield(Result.success(result))
|
||||
},
|
||||
onFailure = { error ->
|
||||
if (error is AppliedCut) {
|
||||
// Find single solution and return immediately
|
||||
if (error.subs != null) {
|
||||
yield(Result.failure(AppliedCut(newHeadSubs + error.subs)))
|
||||
yield(Result.failure(AppliedCut(headSubs + error.subs)))
|
||||
} else {
|
||||
yield(Result.failure(AppliedCut()))
|
||||
}
|
||||
|
@ -52,10 +68,5 @@ abstract class Clause(val head: Head, val body: Body) : Resolvent {
|
|||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return when {
|
||||
body is True -> head.toString()
|
||||
else -> "$head :- $body"
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun toString(): String = if (body is True) head.toString() else "$head :- $body"
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ class Predicate : Resolvent {
|
|||
|
||||
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
|
||||
require(goal.functor == functor) { "Goal functor does not match predicate functor" }
|
||||
|
||||
// Try to unify the goal with the clause
|
||||
// If the unification is successful, yield the substitutions
|
||||
clauses.forEach { clause ->
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package prolog.ast.terms
|
||||
|
||||
import prolog.Answers
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.arithmetic.Expression
|
||||
import prolog.ast.arithmetic.Simplification
|
||||
|
||||
data class Variable(val name: String) : Term, Expression {
|
||||
data class Variable(val name: String) : Term, Body, Expression {
|
||||
override fun simplify(subs: Substitutions): Simplification {
|
||||
// If the variable is bound, return the value of the binding
|
||||
// If the variable is not bound, return the variable itself
|
||||
|
@ -16,5 +17,15 @@ data class Variable(val name: String) : Term, Expression {
|
|||
return Simplification(this, result)
|
||||
}
|
||||
|
||||
override fun satisfy(subs: Substitutions): Answers {
|
||||
// If the variable is bound, satisfy the bound term
|
||||
if (this in subs) {
|
||||
val boundTerm = subs[this]!! as Body
|
||||
return boundTerm.satisfy(subs)
|
||||
}
|
||||
|
||||
return sequenceOf(Result.failure(IllegalArgumentException("Unbound variable: $this")))
|
||||
}
|
||||
|
||||
override fun toString(): String = name
|
||||
}
|
|
@ -4,6 +4,7 @@ import io.Logger
|
|||
import io.Terminal
|
||||
import parser.ReplParser
|
||||
import prolog.Answers
|
||||
import prolog.Program
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.logic.Satisfiable
|
||||
import prolog.ast.terms.Atom
|
||||
|
@ -21,6 +22,8 @@ class Write(private val term: Term) : Operator(Atom("write"), null, term), Satis
|
|||
|
||||
Terminal().say(t.toString())
|
||||
|
||||
Program.storeNewLine = true
|
||||
|
||||
return sequenceOf(Result.success(emptyMap()))
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +34,7 @@ class Write(private val term: Term) : Operator(Atom("write"), null, term), Satis
|
|||
object Nl : Atom("nl"), Satisfiable {
|
||||
override fun satisfy(subs: Substitutions): Answers {
|
||||
Terminal().say("\n")
|
||||
Program.storeNewLine = false
|
||||
return sequenceOf(Result.success(emptyMap()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ import prolog.ast.logic.LogicOperand
|
|||
import prolog.ast.terms.Atom
|
||||
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)
|
||||
}
|
||||
|
||||
class Query(val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {
|
||||
override fun satisfy(subs: Substitutions): Answers = query.satisfy(subs)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,11 @@ class Unify(private val term1: Term, private val term2: Term): Operator(Atom("="
|
|||
}
|
||||
}
|
||||
|
||||
class NotUnify(term1: Term, term2: Term) : Operator(Atom("\\="), term1, term2) {
|
||||
private val not = Not(Unify(term1, term2))
|
||||
override fun satisfy(subs: Substitutions): Answers = not.satisfy(subs)
|
||||
}
|
||||
|
||||
class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) {
|
||||
override fun satisfy(subs: Substitutions): Answers = sequence {
|
||||
val t1 = applySubstitution(term1, subs)
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package prolog.logic
|
||||
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.terms.Atom
|
||||
import prolog.ast.terms.Structure
|
||||
import prolog.ast.terms.Term
|
||||
import prolog.ast.terms.Variable
|
||||
|
||||
/**
|
||||
* True when Term is a term with functor Name/Arity. If Term is a variable it is unified with a new term whose
|
||||
|
@ -20,3 +23,53 @@ fun functor(term: Term, name: Atom, arity: Int): Boolean {
|
|||
// TODO Implement
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Unify the free variables in Term with a term $VAR(N), where N is the number of the variable.
|
||||
* Counting starts at Start.
|
||||
* End is unified with the number that should be given to the next variable.
|
||||
*
|
||||
* Source: [SWI-Prolog Predicate numbervars/3](https://www.swi-prolog.org/pldoc/man?predicate=numbervars/3)
|
||||
*
|
||||
* @return Pair of the next number and only the new substitutions of variables to $VAR(N)
|
||||
*/
|
||||
fun numbervars(
|
||||
term: Term,
|
||||
start: Int = 0,
|
||||
subs: Substitutions = emptyMap(),
|
||||
sessionSubs: Substitutions = emptyMap()
|
||||
): Pair<Int, Substitutions> {
|
||||
when {
|
||||
variable(term, subs) -> {
|
||||
// All instances of the same variable are unified with the same term
|
||||
if (term in sessionSubs) {
|
||||
return Pair(start, emptyMap())
|
||||
}
|
||||
|
||||
val from = term as Variable
|
||||
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()) {
|
||||
val randomInfix = ((0..9) + ('a'..'z') + ('A'..'Z')).random()
|
||||
suggestedName = "${from.name}_${randomInfix}_($start)"
|
||||
}
|
||||
return Pair(start + 1, mapOf(from to Variable(suggestedName)))
|
||||
}
|
||||
|
||||
compound(term, subs) -> {
|
||||
val from = term as Structure
|
||||
var n = start
|
||||
val s: MutableMap<Term, Term> = sessionSubs.toMutableMap()
|
||||
from.arguments.forEach { arg ->
|
||||
val (newN, newSubs) = numbervars(arg, n, subs, s)
|
||||
n = newN
|
||||
s += newSubs
|
||||
}
|
||||
return Pair(n, s)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return Pair(start, emptyMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when
|
|||
}
|
||||
|
||||
// Check if a variable occurs in a term
|
||||
private fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean = when {
|
||||
fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean = when {
|
||||
variable(term, subs) -> term == variable
|
||||
atomic(term, subs) -> false
|
||||
compound(term, subs) -> {
|
||||
|
@ -53,18 +53,18 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
|
|||
val t2 = applySubstitution(term2, subs)
|
||||
|
||||
when {
|
||||
equivalent(t1, t2, subs) -> yield(Result.success(subs))
|
||||
equivalent(t1, t2, subs) -> yield(Result.success(emptyMap()))
|
||||
variable(t1, subs) -> {
|
||||
val variable = t1 as Variable
|
||||
if (!occurs(variable, t2, subs)) {
|
||||
yield(Result.success(subs + (variable to t2)))
|
||||
yield(Result.success(mapOf(term1 to t2)))
|
||||
}
|
||||
}
|
||||
|
||||
variable(t2, subs) -> {
|
||||
val variable = t2 as Variable
|
||||
if (!occurs(variable, t1, subs)) {
|
||||
yield(Result.success(subs + (variable to t1)))
|
||||
yield(Result.success(mapOf(term2 to t1)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Reference in a new issue