This commit is contained in:
Tibo De Peuter 2025-05-07 22:26:02 +02:00
parent 752c278cb0
commit 8bda3c5af4
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
15 changed files with 361 additions and 114 deletions

View file

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

View file

@ -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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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))
}
/**

View file

@ -6,7 +6,6 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import parser.grammars.TermsGrammar
import prolog.ast.arithmetic.Integer
import prolog.ast.logic.Fact
import prolog.ast.logic.Rule
import prolog.ast.terms.*
import prolog.builtins.*
@ -612,7 +611,7 @@ class PreprocessorTests {
Atom("declaration/1")
)
)
val expected = Dynamic(FunctorInfo.of("declaration/1"))
val expected = Dynamic(Functor.of("declaration/1"))
val result = preprocessor.preprocess(input)

View file

@ -2,7 +2,6 @@ package parser.grammars
import com.github.h0tk3y.betterParse.grammar.Grammar
import com.github.h0tk3y.betterParse.grammar.parseToEnd
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ -14,8 +13,7 @@ import prolog.ast.logic.Rule
import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
import prolog.builtins.Conjunction
import prolog.ast.terms.FunctorInfo
import prolog.ast.terms.Functor
class LogicGrammarTests {
private lateinit var parser: Grammar<List<Clause>>
@ -95,13 +93,13 @@ class LogicGrammarTests {
assertTrue(rule.head is Structure, "Expected head to be a structure")
val head = rule.head as Structure
assertEquals(FunctorInfo.of("parent/2"), head.functor, "Expected functor 'parent/2'")
assertEquals(Functor.of("parent/2"), head.functor, "Expected functor 'parent/2'")
assertEquals(Variable("X"), head.arguments[0], "Expected first argument 'X'")
assertEquals(Variable("Y"), head.arguments[1], "Expected second argument 'Y'")
assertTrue(rule.body is Structure, "Expected body to be a structure")
val body = rule.body as Structure
assertEquals(FunctorInfo.of("father/2"), body.functor, "Expected functor 'father/2'")
assertEquals(Functor.of("father/2"), body.functor, "Expected functor 'father/2'")
assertEquals(Variable("X"), body.arguments[0], "Expected first argument 'X'")
assertEquals(Variable("Y"), body.arguments[1], "Expected second argument 'Y'")
}
@ -126,12 +124,12 @@ class LogicGrammarTests {
assertEquals(1, result.size, "Expected 1 rule")
val rule = result[0] as Rule
assertEquals(FunctorInfo.of("guest/2"), rule.head.functor, "Expected functor 'guest/2'")
assertEquals(FunctorInfo.of(",/2"), (rule.body as CompoundTerm).functor, "Expected functor ',/2'")
assertEquals(Functor.of("guest/2"), rule.head.functor, "Expected functor 'guest/2'")
assertEquals(Functor.of(",/2"), (rule.body as CompoundTerm).functor, "Expected functor ',/2'")
val l1 = (rule.body as CompoundTerm).arguments[0] as CompoundTerm
assertEquals(FunctorInfo.of(",/2"), l1.functor, "Expected functor ',/2'")
assertEquals(Functor.of(",/2"), l1.functor, "Expected functor ',/2'")
val l2 = l1.arguments[0] as CompoundTerm
assertEquals(FunctorInfo.of("invited/2"), l2.functor, "Expected functor 'invited/2'")
assertEquals(Functor.of("invited/2"), l2.functor, "Expected functor 'invited/2'")
}
@Test
@ -158,6 +156,6 @@ class LogicGrammarTests {
assertEquals(1, result.size, "Expected 1 rule")
assertTrue(result[0] is Rule, "Expected a rule")
val rule = result[0] as Rule
assertEquals(FunctorInfo.of("/0"), rule.head.functor, "Expected a constraint")
assertEquals(Functor.of("/0"), rule.head.functor, "Expected a constraint")
}
}

View file

@ -1,20 +1,24 @@
package prolog.builtins
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import prolog.ast.Database.Program
import prolog.ast.arithmetic.Integer
import prolog.ast.logic.Fact
import prolog.ast.logic.Rule
import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.AnonymousVariable
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
class AnalysingAndConstructionOperatorsTests {
class AnalysisOperatorsTests {
@Test
fun `functor(foo, foo, 0)`() {
val functor = Functor(Atom("foo"), Atom("foo"), Integer(0))
val functor = FunctorOp(Atom("foo"), Atom("foo"), Integer(0))
val result = functor.satisfy(emptyMap()).toList()
@ -25,7 +29,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test
fun `functor(foo(X), foo, Y)`() {
val functor = Functor(
val functor = FunctorOp(
Structure(Atom("foo"), listOf(Variable("X"))),
Atom("foo"),
Variable("Y")
@ -43,7 +47,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test
fun `functor(foo, X, Y)`() {
val atom = Atom("foo")
val functor = Functor(atom, Variable("X"), Variable("Y"))
val functor = FunctorOp(atom, Variable("X"), Variable("Y"))
val result = functor.satisfy(emptyMap()).toList()
@ -57,7 +61,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test
fun `functor(X, foo, 1)`() {
val functor = Functor(Variable("X"), Atom("foo"), Integer(1))
val functor = FunctorOp(Variable("X"), Atom("foo"), Integer(1))
val result = functor.satisfy(emptyMap()).toList()
@ -74,7 +78,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test
fun `functor(foo(a), foo, 0)`() {
val functor = Functor(Structure(Atom("foo"), listOf(Atom("a"))), Atom("foo"), Integer(0))
val functor = FunctorOp(Structure(Atom("foo"), listOf(Atom("a"))), Atom("foo"), Integer(0))
val result = functor.satisfy(emptyMap()).toList()
@ -83,7 +87,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test
fun `functor(foo(X), foo, 0)`() {
val functor = Functor(Structure(Atom("foo"), listOf(Variable("X"))), Atom("foo"), Integer(0))
val functor = FunctorOp(Structure(Atom("foo"), listOf(Variable("X"))), Atom("foo"), Integer(0))
val result = functor.satisfy(emptyMap()).toList()
@ -92,7 +96,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test
fun `functor(X, Y, 1)`() {
val functor = Functor(Variable("X"), Variable("Y"), Integer(1))
val functor = FunctorOp(Variable("X"), Variable("Y"), Integer(1))
val exception = assertThrows<Exception> {
functor.satisfy(emptyMap()).toList()
@ -233,4 +237,168 @@ class AnalysingAndConstructionOperatorsTests {
}
assertEquals("Domain error: not_less_than_zero", exception.message)
}
@Nested
class `Clause operator` {
@BeforeEach
fun setup() {
Program.reset()
}
@Test
fun `clause fact atom without variables`() {
Program.load(listOf(Fact(Atom("foo"))))
val result = ClauseOp(
Atom("foo"),
True
).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
val subs = result[0].getOrNull()!!
assertTrue(subs.isEmpty(), "Expected empty substitutions")
}
@Test
fun `clause fact compound without variables`() {
Program.load(
listOf(
Fact(CompoundTerm(Atom("foo"), listOf(Atom("a"), Atom("b"))))
)
)
val result = ClauseOp(
CompoundTerm(Atom("foo"), listOf(Atom("a"), Atom("b"))),
True
).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
val subs = result[0].getOrNull()!!
assertTrue(subs.isEmpty(), "Expected empty substitutions")
}
@Test
fun `clause rule without variables`() {
Program.load(listOf(Rule(Atom("foo"), Atom("bar"))))
val result = ClauseOp(
Atom("foo"),
Atom("bar")
).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
val subs = result[0].getOrNull()!!
assertTrue(subs.isEmpty(), "Expected empty substitutions")
}
@Test
fun `clause fact variable body`() {
Program.load(listOf(Fact(Atom("foo"))))
val variable = Variable("Term")
val result = ClauseOp(
Atom("foo"),
variable
).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "Expected 1 substitution")
assertEquals(True, subs[variable])
}
@Test
fun `clause fact with variable head`() {
Program.load(
listOf(
Fact(CompoundTerm(Atom("foo"), listOf(Atom("a"))))
)
)
val result = ClauseOp(
CompoundTerm(Atom("foo"), listOf(Variable("X"))),
True
).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "Expected 1 substitution")
assertEquals(Atom("a"), subs[Variable("X")])
}
@Test
fun `clause rule with variable body`() {
Program.load(listOf(Rule(Atom("foo"), Atom("bar"))))
val result = ClauseOp(
Atom("foo"),
Variable("Term")
).satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "Expected 1 substitution")
assertEquals(Atom("bar"), subs[Variable("Term")])
}
@Test
fun `clause fact variable value with backtracking`() {
Program.load(
listOf(
Fact(CompoundTerm(Atom("bar"), listOf(Atom("d")))),
Fact(CompoundTerm(Atom("bar"), listOf(Atom("e")))),
Fact(CompoundTerm(Atom("foo"), listOf(Atom("a")))),
Fact(CompoundTerm(Atom("foo"), listOf(Atom("b")))),
Fact(CompoundTerm(Atom("foo"), listOf(Atom("c")))),
Rule(
CompoundTerm(Atom("foo"), listOf(Variable("X"))),
CompoundTerm(Atom("bar"), listOf(Variable("X")))
)
)
)
val x = Variable("X")
val term = Variable("Term")
val result = ClauseOp(
CompoundTerm(Atom("foo"), listOf(x)),
term
).satisfy(emptyMap()).toList()
assertEquals(4, result.size, "Expected 4 results")
assertTrue(result[0].isSuccess, "Expected success")
val subs1 = result[0].getOrNull()!!
assertEquals(2, subs1.size, "Expected 2 substitutions")
assertEquals(Atom("a"), subs1[x], "Expected a")
assertEquals(True, subs1[term], "Expected True")
assertTrue(result[1].isSuccess, "Expected success")
val subs2 = result[1].getOrNull()!!
assertEquals(2, subs2.size, "Expected 2 substitutions")
assertEquals(Atom("b"), subs2[x], "Expected b")
assertEquals(True, subs2[term], "Expected True")
assertTrue(result[2].isSuccess, "Expected success")
val subs3 = result[2].getOrNull()!!
assertEquals(2, subs3.size, "Expected 2 substitutions")
assertEquals(Atom("c"), subs3[x], "Expected c")
assertEquals(True, subs3[term], "Expected True")
assertTrue(result[3].isSuccess, "Expected success")
val subs4 = result[3].getOrNull()!!
assertEquals(1, subs4.size, "Expected 2 substitutions")
assertEquals(
CompoundTerm(Atom("bar"), listOf(Variable("X"))),
subs4[term],
"Expected bar(X)"
)
}
}
}

View file

@ -14,7 +14,7 @@ import prolog.ast.logic.Fact
import prolog.ast.logic.Predicate
import prolog.ast.logic.Rule
import prolog.ast.terms.Atom
import prolog.ast.terms.FunctorInfo
import prolog.ast.terms.Functor
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
@ -39,7 +39,7 @@ class DatabaseOperatorsTests {
createAssert(fact).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(fact, Program.db.predicates[FunctorInfo.of("a/_")]!!.clauses[0])
assertEquals(fact, Program.db.predicates[Functor.of("a/_")]!!.clauses[0])
}
@Test
@ -48,7 +48,7 @@ class DatabaseOperatorsTests {
createAssert(fact).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(fact, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0])
assertEquals(fact, Program.db.predicates[Functor.of("a/1")]!!.clauses[0])
}
@Test
@ -60,7 +60,7 @@ class DatabaseOperatorsTests {
createAssert(rule).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0])
assertEquals(rule, Program.db.predicates[Functor.of("a/1")]!!.clauses[0])
}
}
@ -91,8 +91,8 @@ class DatabaseOperatorsTests {
AssertA(rule2).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0])
assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1])
assertEquals(rule2, Program.db.predicates[Functor.of("a/1")]!!.clauses[0])
assertEquals(rule1, Program.db.predicates[Functor.of("a/1")]!!.clauses[1])
}
}
@ -116,8 +116,8 @@ class DatabaseOperatorsTests {
AssertZ(rule2).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0])
assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1])
assertEquals(rule1, Program.db.predicates[Functor.of("a/1")]!!.clauses[0])
assertEquals(rule2, Program.db.predicates[Functor.of("a/1")]!!.clauses[1])
}
}
@ -308,20 +308,20 @@ class DatabaseOperatorsTests {
val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList()
assertEquals(3, control.size, "Expected 3 results")
assertEquals(3, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses.size, "Expected 3 clauses")
assertEquals(3, Program.db.predicates[Functor.of("a/1")]!!.clauses.size, "Expected 3 clauses")
val retract = RetractAll(Structure(Atom("a"), listOf(Variable("X"))))
val result = retract.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success")
assertEquals(0, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses.size, "Expected 0 clauses")
assertEquals(0, Program.db.predicates[Functor.of("a/1")]!!.clauses.size, "Expected 0 clauses")
}
@Test
fun `If Head refers to a predicate that is not defined, it is implicitly created as a dynamic predicate`() {
val predicateName = "idonotyetexist"
val predicateFunctor = FunctorInfo.of("$predicateName/1")
val predicateFunctor = Functor.of("$predicateName/1")
assertFalse(predicateFunctor in Program.db.predicates, "Expected predicate to not exist before")