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 { protected open fun preprocess(term: Term, nested: Boolean = false): Term {
val prepped = when (term) { // TODO Remove hardcoding by storing the functors as constants in operators?
Atom("true") -> True
Structure(Atom("true"), emptyList()) -> True val prepped = when {
Atom("false") -> False term == Variable("_") -> AnonymousVariable.create()
Structure(Atom("false"), emptyList()) -> False term is Atom || term is Structure -> {
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 -> {
// Preprocess the arguments first to recognize builtins // 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 { when (term.functor) {
// TODO Remove hardcoding by storing the functors as constants in operators? // Analysis
Functor.of("functor/3") -> FunctorOp(args[0], args[1], args[2])
term.functor == FunctorInfo.of(":-/2") -> Rule( args[0] as Head, args[1] as Body ) Functor.of("arg/3") -> Arg(args[0], args[1], args[2])
Functor.of("clause/2") -> ClauseOp(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)
// Arithmetic // 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) Functor.of("=:=/2") -> if (args.all { it is Expression }) {
term.functor == FunctorInfo.of("-/2") && args.all { it is Expression } -> Subtract(args[0] as Expression, args[1] as Expression) EvaluatesTo(args[0] as Expression, args[1] as Expression)
term.functor == FunctorInfo.of("+/1") && args.all { it is Expression } -> Positive(args[0] as Expression) } else term
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) Functor.of("is/2") -> if (args.all { it is Expression }) {
term.functor == FunctorInfo.of("//2") && args.all { it is Expression } -> Divide(args[0] as Expression, args[1] as Expression) Is(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) } else term
term.functor == FunctorInfo.of("succ/2") && args.all { it is Expression } -> Successor(args[0] as Expression, args[1] as Expression)
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 // Database
term.functor == FunctorInfo.of("dynamic/1") -> Dynamic(FunctorInfo.of((args[0] as Atom).name)) Functor.of("dynamic/1") -> Dynamic(Functor.of((args[0] as Atom).name))
term.functor == FunctorInfo.of("retract/1") -> Retract(args[0]) Functor.of("retract/1") -> Retract(args[0])
term.functor == FunctorInfo.of("retractall/1") -> RetractAll(args[0]) Functor.of("retractall/1") -> RetractAll(args[0])
term.functor == FunctorInfo.of("assert/1") -> { Functor.of("assert/1") -> {
if (args[0] is Rule) { if (args[0] is Rule) {
Assert(args[0] as Rule) Assert(args[0] as Rule)
} else { } else {
Assert(Fact(args[0] as Head)) Assert(Fact(args[0] as Head))
} }
} }
term.functor == FunctorInfo.of("asserta/1") -> {
Functor.of("asserta/1") -> {
if (args[0] is Rule) { if (args[0] is Rule) {
AssertA(args[0] as Rule) AssertA(args[0] as Rule)
} else { } else {
AssertA(Fact(args[0] as Head)) AssertA(Fact(args[0] as Head))
} }
} }
term.functor == FunctorInfo.of("assertz/1") -> {
Functor.of("assertz/1") -> {
if (args[0] is Rule) { if (args[0] is Rule) {
AssertZ(args[0] as Rule) AssertZ(args[0] as Rule)
} else { } 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 // Other
term.functor == FunctorInfo.of("write/1") -> Write(args[0]) Functor.of("initialization/1") -> Initialization(args[0] as Goal)
term.functor == FunctorInfo.of("read/1") -> Read(args[0]) Functor.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal)
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) // 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 -> { else -> {
term.arguments = args if (term is Structure) term.arguments = args
term term
} }
} }

View file

@ -6,14 +6,14 @@ import prolog.Substitutions
import prolog.ast.logic.Clause import prolog.ast.logic.Clause
import prolog.ast.logic.Predicate import prolog.ast.logic.Predicate
import prolog.ast.logic.Resolvent import prolog.ast.logic.Resolvent
import prolog.ast.terms.FunctorInfo import prolog.ast.terms.Functor
import prolog.ast.terms.Goal import prolog.ast.terms.Goal
/** /**
* Prolog Program or Database * Prolog Program or Database
*/ */
open class Database(val sourceFile: String) { 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. * Initializes the database by running the initialization clauses of that database.
@ -23,14 +23,14 @@ open class Database(val sourceFile: String) {
if (sourceFile !== "") { if (sourceFile !== "") {
Logger.debug("Moving clauses from $sourceFile to main database") 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) } .forEach { (_, predicate) -> db.load(predicate, force = true) }
} }
Logger.info("Initializing database from $sourceFile") Logger.info("Initializing database from $sourceFile")
if (predicates.contains(FunctorInfo.of("/_"))) { if (predicates.contains(Functor.of("/_"))) {
Logger.debug("Loading clauses from /_ predicate") Logger.debug("Loading clauses from /_ predicate")
predicates[FunctorInfo.of("/_")]?.clauses?.forEach { predicates[Functor.of("/_")]?.clauses?.forEach {
Logger.debug("Loading clause $it") Logger.debug("Loading clause $it")
val goal = it.body as Goal val goal = it.body as Goal
goal.satisfy(emptyMap()).toList() goal.satisfy(emptyMap()).toList()

View file

@ -20,7 +20,7 @@ import prolog.logic.unifyLazy
* @see [Predicate] * @see [Predicate]
*/ */
abstract class Clause(var head: Head, var body: Body) : Term, Resolvent { 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 { 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. // 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.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.terms.FunctorInfo import prolog.ast.terms.Functor
import prolog.ast.terms.Goal import prolog.ast.terms.Goal
import prolog.flags.AppliedCut 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 * 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. * to select candidate clauses and then tries these clauses one-by-one.
*/ */
class Predicate : Resolvent { class Predicate : Resolvent {
val functor: FunctorInfo val functor: Functor
val clauses: MutableList<Clause> val clauses: MutableList<Clause>
var dynamic = false var dynamic = false
/** /**
* Creates a predicate with the given functor and an empty list of clauses. * Creates a predicate with the given functor and an empty list of clauses.
*/ */
constructor(functor: FunctorInfo, dynamic: Boolean = false) { constructor(functor: Functor, dynamic: Boolean = false) {
this.functor = functor this.functor = functor
this.clauses = mutableListOf() this.clauses = mutableListOf()
this.dynamic = dynamic this.dynamic = dynamic

View file

@ -7,7 +7,7 @@ import prolog.logic.unifyLazy
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
open class Atom(val name: String) : Goal(), Head, Body, Resolvent { 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) 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.Substitutions
import prolog.ast.arithmetic.Integer 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 { companion object {
fun of(functor: String): FunctorInfo { fun of(functor: String): Functor {
// Split the functor string into name and arity, by splitting the last "/" // Split the functor string into name and arity, by splitting the last "/"
val lastSlash = functor.lastIndexOf('/') val lastSlash = functor.lastIndexOf('/')
val name = Atom(functor.substring(0, lastSlash)) val name = Atom(functor.substring(0, lastSlash))
val arity = Integer(functor.substring(lastSlash + 1).toIntOrNull() ?: 0) 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 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 { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null) return false if (other == null) return false
if (other !is FunctorInfo && other !is String) return false if (other !is Functor && other !is String) return false
if (other is FunctorInfo) { if (other is Functor) {
return this.name.toString() == other.name.toString() && this.arity == other.arity return this.name.toString() == other.name.toString() && this.arity == other.arity
} }
return this.toString() == other 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. * or it fails if Prolog fails to prove it.
*/ */
abstract class Goal : LogicOperand(), Term { abstract class Goal : LogicOperand(), Term {
abstract val functor: FunctorInfo abstract val functor: Functor
override fun satisfy(subs: Substitutions): Answers = Program.solve(this, subs) 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. * Part of a [Clause][prolog.ast.logic.Clause] before the neck operator.
*/ */
interface Head : Term { interface Head : Term {
val functor: FunctorInfo val functor: Functor
} }

View file

@ -12,7 +12,7 @@ typealias Argument = Term
typealias CompoundTerm = Structure typealias CompoundTerm = Structure
open class Structure(val name: Atom, var arguments: List<Argument>) : Goal(), Head, Body, Resolvent { 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 { override fun solve(goal: Goal, subs: Substitutions): Answers {
return unifyLazy(goal, this, subs) return unifyLazy(goal, this, subs)

View file

@ -2,19 +2,19 @@ package prolog.builtins
import prolog.Answers import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.Database.Program
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.terms.* import prolog.ast.terms.*
import prolog.ast.logic.Clause
import prolog.logic.* 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 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. * 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)) { Structure(Atom("functor"), listOf(term, functorName, functorArity)) {
override fun satisfy(subs: Substitutions): Answers { override fun satisfy(subs: Substitutions): Answers {
return when { 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), term.applySubstitution(subs),
functorName.applySubstitution(subs), functorName.applySubstitution(subs),
functorArity.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 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 { class Dynamic(private val dynamicFunctor: Functor) : Goal(), Body {
override val functor = FunctorInfo(Atom("dynamic"), Integer(1)) override val functor = Functor(Atom("dynamic"), Integer(1))
override fun satisfy(subs: Substitutions): Answers { override fun satisfy(subs: Substitutions): Answers {
val predicate = Program.db.predicates[dynamicFunctor] val predicate = Program.db.predicates[dynamicFunctor]
@ -43,7 +43,7 @@ class Dynamic(private val dynamicFunctor: FunctorInfo) : Goal(), Body {
} }
class Assert(clause: Clause) : AssertZ(clause) { 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 org.junit.jupiter.api.Test
import parser.grammars.TermsGrammar import parser.grammars.TermsGrammar
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.logic.Fact
import prolog.ast.logic.Rule import prolog.ast.logic.Rule
import prolog.ast.terms.* import prolog.ast.terms.*
import prolog.builtins.* import prolog.builtins.*
@ -612,7 +611,7 @@ class PreprocessorTests {
Atom("declaration/1") Atom("declaration/1")
) )
) )
val expected = Dynamic(FunctorInfo.of("declaration/1")) val expected = Dynamic(Functor.of("declaration/1"))
val result = preprocessor.preprocess(input) 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.Grammar
import com.github.h0tk3y.betterParse.grammar.parseToEnd import com.github.h0tk3y.betterParse.grammar.parseToEnd
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -14,8 +13,7 @@ import prolog.ast.logic.Rule
import prolog.ast.terms.CompoundTerm import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.builtins.Conjunction import prolog.ast.terms.Functor
import prolog.ast.terms.FunctorInfo
class LogicGrammarTests { class LogicGrammarTests {
private lateinit var parser: Grammar<List<Clause>> private lateinit var parser: Grammar<List<Clause>>
@ -95,13 +93,13 @@ class LogicGrammarTests {
assertTrue(rule.head is Structure, "Expected head to be a structure") assertTrue(rule.head is Structure, "Expected head to be a structure")
val head = rule.head as 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("X"), head.arguments[0], "Expected first argument 'X'")
assertEquals(Variable("Y"), head.arguments[1], "Expected second argument 'Y'") assertEquals(Variable("Y"), head.arguments[1], "Expected second argument 'Y'")
assertTrue(rule.body is Structure, "Expected body to be a structure") assertTrue(rule.body is Structure, "Expected body to be a structure")
val body = rule.body as 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("X"), body.arguments[0], "Expected first argument 'X'")
assertEquals(Variable("Y"), body.arguments[1], "Expected second argument 'Y'") assertEquals(Variable("Y"), body.arguments[1], "Expected second argument 'Y'")
} }
@ -126,12 +124,12 @@ class LogicGrammarTests {
assertEquals(1, result.size, "Expected 1 rule") assertEquals(1, result.size, "Expected 1 rule")
val rule = result[0] as Rule val rule = result[0] as Rule
assertEquals(FunctorInfo.of("guest/2"), rule.head.functor, "Expected functor 'guest/2'") assertEquals(Functor.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(",/2"), (rule.body as CompoundTerm).functor, "Expected functor ',/2'")
val l1 = (rule.body as CompoundTerm).arguments[0] as CompoundTerm 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 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 @Test
@ -158,6 +156,6 @@ class LogicGrammarTests {
assertEquals(1, result.size, "Expected 1 rule") assertEquals(1, result.size, "Expected 1 rule")
assertTrue(result[0] is Rule, "Expected a rule") assertTrue(result[0] is Rule, "Expected a rule")
val rule = result[0] as 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 package prolog.builtins
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import prolog.ast.Database.Program
import prolog.ast.arithmetic.Integer 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.AnonymousVariable
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
class AnalysingAndConstructionOperatorsTests { class AnalysisOperatorsTests {
@Test @Test
fun `functor(foo, foo, 0)`() { 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() val result = functor.satisfy(emptyMap()).toList()
@ -25,7 +29,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test @Test
fun `functor(foo(X), foo, Y)`() { fun `functor(foo(X), foo, Y)`() {
val functor = Functor( val functor = FunctorOp(
Structure(Atom("foo"), listOf(Variable("X"))), Structure(Atom("foo"), listOf(Variable("X"))),
Atom("foo"), Atom("foo"),
Variable("Y") Variable("Y")
@ -43,7 +47,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test @Test
fun `functor(foo, X, Y)`() { fun `functor(foo, X, Y)`() {
val atom = Atom("foo") 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() val result = functor.satisfy(emptyMap()).toList()
@ -57,7 +61,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test @Test
fun `functor(X, foo, 1)`() { 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() val result = functor.satisfy(emptyMap()).toList()
@ -74,7 +78,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test @Test
fun `functor(foo(a), foo, 0)`() { 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() val result = functor.satisfy(emptyMap()).toList()
@ -83,7 +87,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test @Test
fun `functor(foo(X), foo, 0)`() { 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() val result = functor.satisfy(emptyMap()).toList()
@ -92,7 +96,7 @@ class AnalysingAndConstructionOperatorsTests {
@Test @Test
fun `functor(X, Y, 1)`() { 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> { val exception = assertThrows<Exception> {
functor.satisfy(emptyMap()).toList() functor.satisfy(emptyMap()).toList()
@ -233,4 +237,168 @@ class AnalysingAndConstructionOperatorsTests {
} }
assertEquals("Domain error: not_less_than_zero", exception.message) 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.Predicate
import prolog.ast.logic.Rule import prolog.ast.logic.Rule
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.FunctorInfo import prolog.ast.terms.Functor
import prolog.ast.terms.Structure import prolog.ast.terms.Structure
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
@ -39,7 +39,7 @@ class DatabaseOperatorsTests {
createAssert(fact).satisfy(emptyMap()) createAssert(fact).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") 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 @Test
@ -48,7 +48,7 @@ class DatabaseOperatorsTests {
createAssert(fact).satisfy(emptyMap()) createAssert(fact).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") 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 @Test
@ -60,7 +60,7 @@ class DatabaseOperatorsTests {
createAssert(rule).satisfy(emptyMap()) createAssert(rule).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") 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()) AssertA(rule2).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) assertEquals(rule2, Program.db.predicates[Functor.of("a/1")]!!.clauses[0])
assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1]) assertEquals(rule1, Program.db.predicates[Functor.of("a/1")]!!.clauses[1])
} }
} }
@ -116,8 +116,8 @@ class DatabaseOperatorsTests {
AssertZ(rule2).satisfy(emptyMap()) AssertZ(rule2).satisfy(emptyMap())
assertEquals(1, Program.db.predicates.size, "Expected 1 predicate") assertEquals(1, Program.db.predicates.size, "Expected 1 predicate")
assertEquals(rule1, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[0]) assertEquals(rule1, Program.db.predicates[Functor.of("a/1")]!!.clauses[0])
assertEquals(rule2, Program.db.predicates[FunctorInfo.of("a/1")]!!.clauses[1]) 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() val control = Program.query(Structure(Atom("a"), listOf(Variable("X")))).toList()
assertEquals(3, control.size, "Expected 3 results") 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 retract = RetractAll(Structure(Atom("a"), listOf(Variable("X"))))
val result = retract.satisfy(emptyMap()).toList() val result = retract.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result") assertEquals(1, result.size, "Expected 1 result")
assertTrue(result[0].isSuccess, "Expected success") 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 @Test
fun `If Head refers to a predicate that is not defined, it is implicitly created as a dynamic predicate`() { fun `If Head refers to a predicate that is not defined, it is implicitly created as a dynamic predicate`() {
val predicateName = "idonotyetexist" 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") assertFalse(predicateFunctor in Program.db.predicates, "Expected predicate to not exist before")