Clause
This commit is contained in:
parent
752c278cb0
commit
8bda3c5af4
15 changed files with 361 additions and 114 deletions
|
@ -48,70 +48,93 @@ open class Preprocessor {
|
|||
}
|
||||
|
||||
protected open fun preprocess(term: Term, nested: Boolean = false): Term {
|
||||
val prepped = when (term) {
|
||||
Atom("true") -> True
|
||||
Structure(Atom("true"), emptyList()) -> True
|
||||
Atom("false") -> False
|
||||
Structure(Atom("false"), emptyList()) -> False
|
||||
Atom("fail") -> Fail
|
||||
Structure(Atom("fail"), emptyList()) -> Fail
|
||||
Atom("!") -> Cut()
|
||||
Structure(Atom("!"), emptyList()) -> Cut()
|
||||
Atom("inf") -> Integer(Int.MAX_VALUE)
|
||||
Atom("nl") -> Nl
|
||||
Variable("_") -> AnonymousVariable.create()
|
||||
is Structure -> {
|
||||
// TODO Remove hardcoding by storing the functors as constants in operators?
|
||||
|
||||
val prepped = when {
|
||||
term == Variable("_") -> AnonymousVariable.create()
|
||||
term is Atom || term is Structure -> {
|
||||
// Preprocess the arguments first to recognize builtins
|
||||
val args = term.arguments.map { preprocess(it, nested = true) }
|
||||
val args = if (term is Structure) {
|
||||
term.arguments.map { preprocess(it, nested = true) }
|
||||
} else emptyList()
|
||||
|
||||
when {
|
||||
// TODO Remove hardcoding by storing the functors as constants in operators?
|
||||
|
||||
term.functor == FunctorInfo.of(":-/2") -> Rule( args[0] as Head, args[1] as Body )
|
||||
|
||||
// Logic
|
||||
term.functor == FunctorInfo.of("=/2") -> Unify(args[0], args[1])
|
||||
term.functor == FunctorInfo.of("\\=/2") -> NotUnify(args[0], args[1])
|
||||
term.functor == FunctorInfo.of(",/2") -> Conjunction(args[0] as LogicOperand, args[1] as LogicOperand)
|
||||
term.functor == FunctorInfo.of(";/2") -> Disjunction(args[0] as LogicOperand, args[1] as LogicOperand)
|
||||
term.functor == FunctorInfo.of("\\+/1") -> Not(args[0] as Goal)
|
||||
term.functor == FunctorInfo.of("\\==/2") -> NotEquivalent(args[0], args[1])
|
||||
term.functor == FunctorInfo.of("==/2") -> Equivalent(args[0], args[1])
|
||||
|
||||
term.functor == FunctorInfo.of("=\\=/2") && args.all { it is Expression } -> EvaluatesToDifferent(args[0] as Expression, args[1] as Expression)
|
||||
term.functor == FunctorInfo.of("=:=/2") && args.all { it is Expression } -> EvaluatesTo(args[0] as Expression, args[1] as Expression)
|
||||
term.functor == FunctorInfo.of("is/2") && args.all { it is Expression } -> Is(args[0] as Expression, args[1] as Expression)
|
||||
when (term.functor) {
|
||||
// Analysis
|
||||
Functor.of("functor/3") -> FunctorOp(args[0], args[1], args[2])
|
||||
Functor.of("arg/3") -> Arg(args[0], args[1], args[2])
|
||||
Functor.of("clause/2") -> ClauseOp(args[0] as Head, args[1] as Body)
|
||||
|
||||
// Arithmetic
|
||||
Functor.of("inf/0") -> Integer(Int.MAX_VALUE)
|
||||
Functor.of("=\\=/2") -> if (args.all { it is Expression }) {
|
||||
EvaluatesToDifferent(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
term.functor == FunctorInfo.of("-/1") && args.all { it is Expression } -> Negate(args[0] as Expression)
|
||||
term.functor == FunctorInfo.of("-/2") && args.all { it is Expression } -> Subtract(args[0] as Expression, args[1] as Expression)
|
||||
term.functor == FunctorInfo.of("+/1") && args.all { it is Expression } -> Positive(args[0] as Expression)
|
||||
term.functor == FunctorInfo.of("+/2") && args.all { it is Expression } -> Add(args[0] as Expression, args[1] as Expression)
|
||||
term.functor == FunctorInfo.of("*/2") && args.all { it is Expression } -> Multiply(args[0] as Expression, args[1] as Expression)
|
||||
term.functor == FunctorInfo.of("//2") && args.all { it is Expression } -> Divide(args[0] as Expression, args[1] as Expression)
|
||||
term.functor == FunctorInfo.of("between/3") && args.all { it is Expression } -> Between(args[0] as Expression, args[1] as Expression, args[2] as Expression)
|
||||
term.functor == FunctorInfo.of("succ/2") && args.all { it is Expression } -> Successor(args[0] as Expression, args[1] as Expression)
|
||||
Functor.of("=:=/2") -> if (args.all { it is Expression }) {
|
||||
EvaluatesTo(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
Functor.of("is/2") -> if (args.all { it is Expression }) {
|
||||
Is(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
Functor.of("-/1") -> if (args.all { it is Expression }) Negate(args[0] as Expression) else term
|
||||
Functor.of("+/1") -> if (args.all { it is Expression }) Positive(args[0] as Expression) else term
|
||||
Functor.of("+/2") -> if (args.all { it is Expression }) {
|
||||
Add(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
Functor.of("-/2") -> if (args.all { it is Expression }) {
|
||||
Subtract(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
Functor.of("*/2") -> if (args.all { it is Expression }) {
|
||||
Multiply(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
Functor.of("//2") -> if (args.all { it is Expression }) {
|
||||
Divide(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
Functor.of("between/3") -> if (args.all { it is Expression }) {
|
||||
Between(args[0] as Expression, args[1] as Expression, args[2] as Expression)
|
||||
} else term
|
||||
|
||||
Functor.of("succ/2") -> if (args.all { it is Expression }) {
|
||||
Successor(args[0] as Expression, args[1] as Expression)
|
||||
} else term
|
||||
|
||||
// Control
|
||||
Functor.of("fail/0") -> Fail
|
||||
Functor.of("false/0") -> False
|
||||
Functor.of("true/0") -> True
|
||||
Functor.of("!/0") -> Cut()
|
||||
Functor.of(",/2") -> Conjunction(args[0] as LogicOperand, args[1] as LogicOperand)
|
||||
Functor.of(";/2") -> Disjunction(args[0] as LogicOperand, args[1] as LogicOperand)
|
||||
Functor.of("|/2") -> Bar(args[0] as LogicOperand, args[1] as LogicOperand)
|
||||
Functor.of("\\+/1") -> Not(args[0] as Goal)
|
||||
|
||||
// Database
|
||||
term.functor == FunctorInfo.of("dynamic/1") -> Dynamic(FunctorInfo.of((args[0] as Atom).name))
|
||||
term.functor == FunctorInfo.of("retract/1") -> Retract(args[0])
|
||||
term.functor == FunctorInfo.of("retractall/1") -> RetractAll(args[0])
|
||||
term.functor == FunctorInfo.of("assert/1") -> {
|
||||
Functor.of("dynamic/1") -> Dynamic(Functor.of((args[0] as Atom).name))
|
||||
Functor.of("retract/1") -> Retract(args[0])
|
||||
Functor.of("retractall/1") -> RetractAll(args[0])
|
||||
Functor.of("assert/1") -> {
|
||||
if (args[0] is Rule) {
|
||||
Assert(args[0] as Rule)
|
||||
} else {
|
||||
Assert(Fact(args[0] as Head))
|
||||
}
|
||||
}
|
||||
term.functor == FunctorInfo.of("asserta/1") -> {
|
||||
|
||||
Functor.of("asserta/1") -> {
|
||||
if (args[0] is Rule) {
|
||||
AssertA(args[0] as Rule)
|
||||
} else {
|
||||
AssertA(Fact(args[0] as Head))
|
||||
}
|
||||
}
|
||||
term.functor == FunctorInfo.of("assertz/1") -> {
|
||||
|
||||
Functor.of("assertz/1") -> {
|
||||
if (args[0] is Rule) {
|
||||
AssertZ(args[0] as Rule)
|
||||
} else {
|
||||
|
@ -119,14 +142,25 @@ open class Preprocessor {
|
|||
}
|
||||
}
|
||||
|
||||
// IO
|
||||
Functor.of("write/1") -> Write(args[0])
|
||||
Functor.of("nl/0") -> Nl
|
||||
Functor.of("read/1") -> Read(args[0])
|
||||
|
||||
// Other
|
||||
term.functor == FunctorInfo.of("write/1") -> Write(args[0])
|
||||
term.functor == FunctorInfo.of("read/1") -> Read(args[0])
|
||||
term.functor == FunctorInfo.of("initialization/1") -> Initialization(args[0] as Goal)
|
||||
term.functor == FunctorInfo.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal)
|
||||
Functor.of("initialization/1") -> Initialization(args[0] as Goal)
|
||||
Functor.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal)
|
||||
|
||||
// Unification
|
||||
Functor.of("=/2") -> Unify(args[0], args[1])
|
||||
Functor.of("\\=/2") -> NotUnify(args[0], args[1])
|
||||
Functor.of("==/2") -> Equivalent(args[0], args[1])
|
||||
Functor.of("\\==/2") -> NotEquivalent(args[0], args[1])
|
||||
|
||||
Functor.of(":-/2") -> Rule(args[0] as Head, args[1] as Body)
|
||||
|
||||
else -> {
|
||||
term.arguments = args
|
||||
if (term is Structure) term.arguments = args
|
||||
term
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,14 @@ import prolog.Substitutions
|
|||
import prolog.ast.logic.Clause
|
||||
import prolog.ast.logic.Predicate
|
||||
import prolog.ast.logic.Resolvent
|
||||
import prolog.ast.terms.FunctorInfo
|
||||
import prolog.ast.terms.Functor
|
||||
import prolog.ast.terms.Goal
|
||||
|
||||
/**
|
||||
* Prolog Program or Database
|
||||
*/
|
||||
open class Database(val sourceFile: String) {
|
||||
var predicates: Map<FunctorInfo, Predicate> = emptyMap()
|
||||
var predicates: Map<Functor, Predicate> = emptyMap()
|
||||
|
||||
/**
|
||||
* Initializes the database by running the initialization clauses of that database.
|
||||
|
@ -23,14 +23,14 @@ open class Database(val sourceFile: String) {
|
|||
|
||||
if (sourceFile !== "") {
|
||||
Logger.debug("Moving clauses from $sourceFile to main database")
|
||||
predicates.filter { it.key != FunctorInfo.of("/_") }
|
||||
predicates.filter { it.key != Functor.of("/_") }
|
||||
.forEach { (_, predicate) -> db.load(predicate, force = true) }
|
||||
}
|
||||
|
||||
Logger.info("Initializing database from $sourceFile")
|
||||
if (predicates.contains(FunctorInfo.of("/_"))) {
|
||||
if (predicates.contains(Functor.of("/_"))) {
|
||||
Logger.debug("Loading clauses from /_ predicate")
|
||||
predicates[FunctorInfo.of("/_")]?.clauses?.forEach {
|
||||
predicates[Functor.of("/_")]?.clauses?.forEach {
|
||||
Logger.debug("Loading clause $it")
|
||||
val goal = it.body as Goal
|
||||
goal.satisfy(emptyMap()).toList()
|
||||
|
|
|
@ -20,7 +20,7 @@ import prolog.logic.unifyLazy
|
|||
* @see [Predicate]
|
||||
*/
|
||||
abstract class Clause(var head: Head, var body: Body) : Term, Resolvent {
|
||||
val functor: FunctorInfo = head.functor
|
||||
val functor: Functor = head.functor
|
||||
|
||||
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.
|
||||
|
|
|
@ -2,25 +2,25 @@ package prolog.ast.logic
|
|||
|
||||
import prolog.Answers
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.terms.FunctorInfo
|
||||
import prolog.ast.terms.Functor
|
||||
import prolog.ast.terms.Goal
|
||||
import prolog.flags.AppliedCut
|
||||
|
||||
/**
|
||||
* Collection of [Clause]s with the same [FunctorInfo].
|
||||
* Collection of [Clause]s with the same [Functor].
|
||||
*
|
||||
* If a goal is proved, the system looks for a predicate with the same functor, then uses indexing
|
||||
* to select candidate clauses and then tries these clauses one-by-one.
|
||||
*/
|
||||
class Predicate : Resolvent {
|
||||
val functor: FunctorInfo
|
||||
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: FunctorInfo, dynamic: Boolean = false) {
|
||||
constructor(functor: Functor, dynamic: Boolean = false) {
|
||||
this.functor = functor
|
||||
this.clauses = mutableListOf()
|
||||
this.dynamic = dynamic
|
||||
|
|
|
@ -7,7 +7,7 @@ import prolog.logic.unifyLazy
|
|||
import prolog.ast.arithmetic.Integer
|
||||
|
||||
open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
|
||||
override val functor: FunctorInfo = FunctorInfo(this, Integer(0))
|
||||
override val functor: Functor = Functor(this, Integer(0))
|
||||
|
||||
override fun solve(goal: Goal, subs: Substitutions): Answers = unifyLazy(goal, this, subs)
|
||||
|
||||
|
|
|
@ -3,27 +3,33 @@ package prolog.ast.terms
|
|||
import prolog.Substitutions
|
||||
import prolog.ast.arithmetic.Integer
|
||||
|
||||
data class FunctorInfo(val name: Atom, val arity: Integer) : Term {
|
||||
data class Functor(val name: Atom, val arity: Integer) : Term {
|
||||
companion object {
|
||||
fun of(functor: String): FunctorInfo {
|
||||
fun of(functor: String): Functor {
|
||||
// Split the functor string into name and arity, by splitting the last "/"
|
||||
val lastSlash = functor.lastIndexOf('/')
|
||||
val name = Atom(functor.substring(0, lastSlash))
|
||||
val arity = Integer(functor.substring(lastSlash + 1).toIntOrNull() ?: 0)
|
||||
return FunctorInfo(name, arity)
|
||||
return Functor(name, arity)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "${name.name}/$arity"
|
||||
override fun applySubstitution(subs: Substitutions) : FunctorInfo = this
|
||||
override fun applySubstitution(subs: Substitutions) : Functor = this
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null) return false
|
||||
if (other !is FunctorInfo && other !is String) return false
|
||||
if (other is FunctorInfo) {
|
||||
if (other !is Functor && other !is String) return false
|
||||
if (other is Functor) {
|
||||
return this.name.toString() == other.name.toString() && this.arity == other.arity
|
||||
}
|
||||
return this.toString() == other
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + arity.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ import prolog.ast.logic.LogicOperand
|
|||
* or it fails if Prolog fails to prove it.
|
||||
*/
|
||||
abstract class Goal : LogicOperand(), Term {
|
||||
abstract val functor: FunctorInfo
|
||||
abstract val functor: Functor
|
||||
|
||||
override fun satisfy(subs: Substitutions): Answers = Program.solve(this, subs)
|
||||
}
|
|
@ -4,5 +4,5 @@ package prolog.ast.terms
|
|||
* Part of a [Clause][prolog.ast.logic.Clause] before the neck operator.
|
||||
*/
|
||||
interface Head : Term {
|
||||
val functor: FunctorInfo
|
||||
val functor: Functor
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ typealias Argument = Term
|
|||
typealias CompoundTerm = Structure
|
||||
|
||||
open class Structure(val name: Atom, var arguments: List<Argument>) : Goal(), Head, Body, Resolvent {
|
||||
override val functor: FunctorInfo = FunctorInfo(name, Integer(arguments.size))
|
||||
override val functor: Functor = Functor(name, Integer(arguments.size))
|
||||
|
||||
override fun solve(goal: Goal, subs: Substitutions): Answers {
|
||||
return unifyLazy(goal, this, subs)
|
||||
|
|
|
@ -2,19 +2,19 @@ package prolog.builtins
|
|||
|
||||
import prolog.Answers
|
||||
import prolog.Substitutions
|
||||
import prolog.ast.Database.Program
|
||||
import prolog.ast.arithmetic.Integer
|
||||
import prolog.ast.terms.*
|
||||
import prolog.ast.logic.Clause
|
||||
import prolog.logic.*
|
||||
import java.util.Locale
|
||||
import java.util.Locale.getDefault
|
||||
|
||||
/**
|
||||
* [True] when [Term] is a term with [FunctorInfo] Name/Arity.
|
||||
* [True] when [Term] is a term with [Functor] Name/Arity.
|
||||
*
|
||||
* If Term is a [Variable] it is unified with a new term whose arguments are all different variables.
|
||||
* If Term is [atomic], Arity will be unified with the integer 0, and Name will be unified with Term.
|
||||
*/
|
||||
class Functor(private val term: Term, private val functorName: Term, private val functorArity: Term) :
|
||||
class FunctorOp(private val term: Term, private val functorName: Term, private val functorArity: Term) :
|
||||
Structure(Atom("functor"), listOf(term, functorName, functorArity)) {
|
||||
override fun satisfy(subs: Substitutions): Answers {
|
||||
return when {
|
||||
|
@ -42,7 +42,7 @@ class Functor(private val term: Term, private val functorName: Term, private val
|
|||
}
|
||||
}
|
||||
|
||||
override fun applySubstitution(subs: Substitutions): Functor = Functor(
|
||||
override fun applySubstitution(subs: Substitutions): FunctorOp = FunctorOp(
|
||||
term.applySubstitution(subs),
|
||||
functorName.applySubstitution(subs),
|
||||
functorArity.applySubstitution(subs)
|
||||
|
@ -95,3 +95,45 @@ class Arg(private val arg: Term, private val term: Term, private val value: Term
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [True] if [Head] can be unified with a [Clause] and [Body] with the corresponding Clause Body.
|
||||
*
|
||||
* Gives alternative clauses on backtracking. For facts, Body is unified with the atom [True].
|
||||
*
|
||||
* When accessing builtins (static predicates, private procedures), the program will act as if the builtins do not
|
||||
* exist. Head can only match with dynamic or imported predicates.
|
||||
*
|
||||
* [SWI-Prolog Operator clause/2](https://www.swi-prolog.org/pldoc/doc_for?object=clause/2)
|
||||
*/
|
||||
class ClauseOp(private val head: Head, private val body: Body) :
|
||||
Structure(Atom("clause"), listOf(head, body)) {
|
||||
override fun satisfy(subs: Substitutions): Answers = sequence {
|
||||
require(nonvariable(head, subs)) { "Arguments are not sufficiently instantiated" }
|
||||
|
||||
val predicate = Program.db.predicates[head.functor]
|
||||
|
||||
if (predicate != null) {
|
||||
for (clause in predicate.clauses) {
|
||||
val clauseHead = clause.head
|
||||
val clauseBody = clause.body
|
||||
|
||||
// Unify the head of the clause with the head of the goal
|
||||
unifyLazy(clauseHead, head, subs).forEach { result ->
|
||||
result.map { headSubs ->
|
||||
// Unify the body of the clause with the body of the goal
|
||||
unifyLazy(clauseBody, body, headSubs).forEach { bodyResult ->
|
||||
bodyResult.map { bodySubs ->
|
||||
// Combine the substitutions from the head and body
|
||||
val combinedSubs = headSubs + bodySubs
|
||||
yield(Result.success(combinedSubs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield(Result.success(emptyMap()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,10 +14,10 @@ import prolog.logic.applySubstitution
|
|||
import prolog.logic.unifyLazy
|
||||
|
||||
/**
|
||||
* (Make) the [Predicate] with the corresponding [FunctorInfo] dynamic.
|
||||
* (Make) the [Predicate] with the corresponding [Functor] dynamic.
|
||||
*/
|
||||
class Dynamic(private val dynamicFunctor: FunctorInfo) : Goal(), Body {
|
||||
override val functor = FunctorInfo(Atom("dynamic"), Integer(1))
|
||||
class Dynamic(private val dynamicFunctor: Functor) : Goal(), Body {
|
||||
override val functor = Functor(Atom("dynamic"), Integer(1))
|
||||
|
||||
override fun satisfy(subs: Substitutions): Answers {
|
||||
val predicate = Program.db.predicates[dynamicFunctor]
|
||||
|
@ -43,7 +43,7 @@ class Dynamic(private val dynamicFunctor: FunctorInfo) : Goal(), Body {
|
|||
}
|
||||
|
||||
class Assert(clause: Clause) : AssertZ(clause) {
|
||||
override val functor = FunctorInfo(Atom("assert"), Integer(1))
|
||||
override val functor = Functor(Atom("assert"), Integer(1))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Reference in a new issue