diff --git a/examples/meta/continuations.pl b/examples/meta/continuations.pl deleted file mode 100644 index 8661361..0000000 --- a/examples/meta/continuations.pl +++ /dev/null @@ -1,22 +0,0 @@ -% Based on -% https://occasionallycogent.com/continuations_in_prolog/ - -test(Cont, Term) :- - writeln("Inside test"), - reset(test_, Term, Cont), - writeln("After reset"). - -test_ :- - writeln("Entering reset"), - shift(Y), - X is 1 + (2 * Y), - write("In test X = "), write(X), writeln("; done"). - -main :- - test(Cont, Term), - \+ \+ ( writeln("Calling Cont(2)"), - Term = 2, call(Cont)), - \+ \+ ( writeln("Calling Cont(4)"), - Term = 4, call(Cont)). - -:- initialization(main). diff --git a/src/interpreter/Preprocessor.kt b/src/interpreter/Preprocessor.kt index 7076dc8..226af60 100644 --- a/src/interpreter/Preprocessor.kt +++ b/src/interpreter/Preprocessor.kt @@ -3,7 +3,6 @@ package interpreter import io.Logger import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Integer -import prolog.ast.lists.List as PList import prolog.ast.logic.Clause import prolog.ast.logic.Fact import prolog.ast.logic.LogicOperand @@ -51,9 +50,9 @@ open class Preprocessor { protected open fun preprocess(term: Term, nested: Boolean = false): Term { // TODO Remove hardcoding by storing the functors as constants in operators? - val prepped = when (term) { - Variable("_") -> AnonymousVariable.create() - is Atom, is Structure -> { + val prepped = when { + term == Variable("_") -> AnonymousVariable.create() + term is Atom || term is Structure -> { // Preprocess the arguments first to recognize builtins val args = if (term is Structure) { term.arguments.map { preprocess(it, nested = true) } @@ -145,23 +144,11 @@ open class Preprocessor { } } - // Delimited Continuations - Functor.of("reset/3") -> Reset(args[0] as Goal, args[1], args[2]) - Functor.of("shift/1") -> Shift(args[0]) - // IO Functor.of("write/1") -> Write(args[0]) Functor.of("nl/0") -> Nl - Functor.of("writeln/1") -> WriteLn(args[0]) Functor.of("read/1") -> Read(args[0]) - // Lists - Functor.of("member/2") -> Member(args[0], args[1] as PList) - - // Meta - Functor.of("call/1") -> Call(args[0]) - Functor.of("ignore/1") -> Ignore(args[0] as Goal) - // Other Functor.of("initialization/1") -> Initialization(args[0] as Goal) Functor.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal) diff --git a/src/parser/grammars/TermsGrammar.kt b/src/parser/grammars/TermsGrammar.kt index 9051484..8db42e9 100644 --- a/src/parser/grammars/TermsGrammar.kt +++ b/src/parser/grammars/TermsGrammar.kt @@ -3,10 +3,9 @@ package parser.grammars import com.github.h0tk3y.betterParse.combinators.* import com.github.h0tk3y.betterParse.grammar.parser import com.github.h0tk3y.betterParse.parser.Parser -import com.github.h0tk3y.betterParse.utils.Tuple2 import prolog.ast.arithmetic.Float import prolog.ast.arithmetic.Integer -import prolog.ast.lists.List as PList +import prolog.ast.lists.List import prolog.ast.terms.* import prolog.builtins.Dynamic import prolog.ast.lists.List.Empty @@ -97,19 +96,14 @@ open class TermsGrammar : Tokens() { if (t2 == null) t1 else CompoundTerm(Atom(t2!!.t1), listOf(t1, t2!!.t2)) } - protected val not: Parser by (notOp * parser(::term900)) use { - CompoundTerm(Atom(t1.text), listOf(t2)) - } - protected val term900: Parser by (not or term700) - protected val op1000: Parser by (comma) use { text } - protected val term1000: Parser by (term900 * zeroOrMore(op1000 * term900)) use { - constructRightAssociative(t1, t2) + protected val term1000: Parser by (term700 * zeroOrMore(op1000 * term700)) use { + t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) } } protected val op1100: Parser by (semicolon) use { text } protected val term1100: Parser by (term1000 * zeroOrMore(op1100 * term1000)) use { - constructRightAssociative(t1, t2) + t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) } } protected val dynamic: Parser by (dynamicOp * functor) use { @@ -123,12 +117,12 @@ open class TermsGrammar : Tokens() { } // Lists - protected val list: Parser by (-leftBracket * separated( + protected val list: Parser by (-leftBracket * separated( parser(::termNoConjunction), comma, acceptZero = true ) * -rightBracket) use { - var list: PList = Empty + var list: List = Empty // Construct the list in reverse order for (term in this.terms.reversed()) { list = Cons(term, list) @@ -145,12 +139,4 @@ open class TermsGrammar : Tokens() { protected val body: Parser by term use { this as Body } override val rootParser: Parser by term - - fun constructRightAssociative(left: Term, pairs: List>): Term { - if (pairs.isEmpty()) return left - - val (name, next) = pairs.first() - val remainingPairs = pairs.drop(1) - return CompoundTerm(Atom(name), listOf(left, constructRightAssociative(next, remainingPairs))) - } } diff --git a/src/parser/grammars/Tokens.kt b/src/parser/grammars/Tokens.kt index ccdbae9..b998fee 100644 --- a/src/parser/grammars/Tokens.kt +++ b/src/parser/grammars/Tokens.kt @@ -21,8 +21,6 @@ abstract class Tokens : Grammar() { protected val semicolon: Token by literalToken(";") // 1000 protected val comma: Token by literalToken(",") - // 900 - protected val notOp: Token by literalToken("\\+") // 700 protected val univOp: Token by literalToken("=..") protected val notEquivalent: Token by literalToken("\\==") diff --git a/src/prolog/builtins/controlOperators.kt b/src/prolog/builtins/controlOperators.kt index c1376f9..63908d1 100644 --- a/src/prolog/builtins/controlOperators.kt +++ b/src/prolog/builtins/controlOperators.kt @@ -9,7 +9,6 @@ import prolog.ast.terms.Atom import prolog.ast.terms.Body import prolog.ast.terms.Goal import prolog.flags.AppliedCut -import prolog.flags.AppliedShift import prolog.logic.applySubstitution import prolog.logic.numbervars @@ -18,7 +17,7 @@ import prolog.logic.numbervars */ object Fail : Atom("fail"), Body { override fun satisfy(subs: Substitutions): Answers = emptySequence() - override fun applySubstitution(subs: Substitutions): Fail = this + override fun applySubstitution(subs: Substitutions): Fail = Fail } /** @@ -31,7 +30,7 @@ typealias False = Fail */ object True : Atom("true"), Body { override fun satisfy(subs: Substitutions): Answers = sequenceOf(Result.success(emptyMap())) - override fun applySubstitution(subs: Substitutions): True = this + override fun applySubstitution(subs: Substitutions): True = True } // TODO Repeat/0 @@ -41,72 +40,62 @@ class Cut() : Atom("!") { return sequenceOf(Result.failure(AppliedCut(emptyMap()))) } - override fun applySubstitution(subs: Substitutions): Cut = this + override fun applySubstitution(subs: Substitutions): Cut = Cut() } /** * Conjunction (and). True if both Goal1 and Goal2 are true. */ -open class Conjunction(val left: LogicOperand, private val right: LogicOperand) : +class Conjunction(val left: LogicOperand, private val right: LogicOperand) : LogicOperator(Atom(","), left, right) { override fun satisfy(subs: Substitutions): Answers = sequence { - fun satisfyRight(leftSubs: Substitutions): Answers = sequence { - right.satisfy(subs + leftSubs).forEach { right -> - right.fold( - // If the right part succeeds, yield the result with the left substitutions - onSuccess = { rightSubs -> - yield(Result.success(leftSubs + rightSubs)) - }, - onFailure = { exception -> - // If the right part fails, check if it's a cut - if (exception is AppliedCut) { - // If it's a cut, yield the result with the left substitutions - val newSubs = if (exception.subs != null) leftSubs + exception.subs else null - yield(Result.failure(AppliedCut(newSubs))) - return@sequence - } - - // If it's not a cut, yield the failure - yield(Result.failure(exception)) - } - ) - } - } - - fun findNextCutSolution(appliedCut: AppliedCut): Answers = sequence { - val leftSubs = appliedCut.subs - right.satisfy(subs + (appliedCut.subs!!)).firstOrNull()?.map { rightSubs -> - // If the right part succeeds, yield the result with the left substitutions - yield(Result.success(leftSubs + rightSubs)) - return@sequence - } ?: yield(Result.failure(AppliedCut())) - } - // Satisfy the left part first, which either succeeds or fails left.satisfy(subs).forEach { left -> left.fold( // If it succeeds, satisfy the right part with the updated substitutions and return all results - onSuccess = { leftSubs -> yieldAll(satisfyRight(leftSubs)) }, + onSuccess = { leftSubs -> + right.satisfy(subs + leftSubs).forEach { right -> + right.fold( + // If the right part succeeds, yield the result with the left substitutions + onSuccess = { rightSubs -> + yield(Result.success(leftSubs + rightSubs)) + }, + // If the right part fails, check if it's a cut + onFailure = { exception -> + if (exception is AppliedCut) { + if (exception.subs != null) { + // If it's a cut, yield the result with the left substitutions + yield(Result.failure(AppliedCut(leftSubs + exception.subs))) + } else { + yield(Result.failure(AppliedCut())) + } + return@sequence + } + + // If it's not a cut, yield the failure + yield(Result.failure(exception)) + } + ) + } + }, // If it fails, check these conditions: onFailure = { exception -> - when (exception) { - // 1. If the left part is a cut, satisfy the right part ONCE, and stop searching for more solutions - is AppliedCut -> yieldAll(findNextCutSolution(exception)) - - // 2. If the left part is a shift, we need to check if the right part can be satisfied - is AppliedShift -> { - if (exception.cont == null) { - // Pass the right of our disjunction as the continuation - val newShift = AppliedShift(exception.subs, exception.ball, right as Goal) - yield(Result.failure(newShift)) - } else { - // Satisfy the right part with the updated substitutions - yieldAll(satisfyRight(exception.subs)) + // 1. If the left part is a cut, satisfy the right part ONCE, and stop searching for more solutions + if (exception is AppliedCut) { + right.satisfy(subs + (exception.subs!!)).firstOrNull()?.fold( + onSuccess = { + // If the right part succeeds, yield the result with the left substitutions + yield(Result.success(exception.subs + it)) + return@sequence + }, + onFailure = { + // If the right part fails, yield the failure + yield(Result.failure(it)) } - } - - // 3. Any other failure should be returned as is - else -> yield(Result.failure(exception)) + ) ?: yield(Result.failure(AppliedCut())) + } else { + // 2. Any other failure should be returned as is + yield(Result.failure(exception)) } } ) @@ -125,20 +114,7 @@ open class Conjunction(val left: LogicOperand, private val right: LogicOperand) open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) : LogicOperator(Atom(";"), left, right) { override fun satisfy(subs: Substitutions): Answers = sequence { - left.satisfy(subs).forEach { left -> - left.fold( - onSuccess = { leftSubs -> - yield(Result.success(leftSubs)) - }, - onFailure = { failure -> - if (failure is AppliedCut) { - val leftSubs = failure.subs - yield(Result.failure(AppliedCut(leftSubs))) - return@sequence - } - } - ) - } + yieldAll(left.satisfy(subs)) yieldAll(right.satisfy(subs)) } diff --git a/src/prolog/builtins/delimitedContinuationsOperators.kt b/src/prolog/builtins/delimitedContinuationsOperators.kt deleted file mode 100644 index 5a01eb4..0000000 --- a/src/prolog/builtins/delimitedContinuationsOperators.kt +++ /dev/null @@ -1,75 +0,0 @@ -package prolog.builtins - -import prolog.Answers -import prolog.Substitutions -import prolog.ast.arithmetic.Integer -import prolog.ast.terms.Atom -import prolog.ast.terms.Goal -import prolog.ast.terms.Structure -import prolog.ast.terms.Term -import prolog.flags.AppliedShift -import prolog.logic.applySubstitution -import prolog.logic.unifyLazy - -/** - * These classes provide support for delimited continuations in Prolog. - * More specifically, they implement the reset/3 and shift/1 operators. - * - * See also [SWI-Prolog 4.9 Delimited Continuations](https://www.swi-prolog.org/pldoc/man?section=delcont) - */ - -/** - * Call [Goal]. If Goal calls [Shift], and the arguments of Shift can be unified with Ball, Shift causes Reset to - * return, unifying Cont with a Goal that represents the continuation after shift. - */ -class Reset(private val goal: Goal, private val ball: Term, private val cont: Term) : - Structure(Atom("reset"), listOf(goal, ball, cont)) { - override fun satisfy(subs: Substitutions): Answers = sequence { - goal.satisfy(subs).forEach { goalResult -> - goalResult.fold( - // If Goal succeeds, then reset/3 also succeeds and binds Cont and Term1 to 0. - onSuccess = { goalSubs -> - unifyLazy(cont, Integer(0), goalSubs).forEach { contResult -> - contResult.map { contSubs -> - yield(Result.success(goalSubs + contSubs)) - } - } - }, - onFailure = { failure -> - // If at some point Goal calls shift(Term2), then its further execution is suspended. - if (failure is AppliedShift) { - require(failure.cont != null) { "Shift must have a continuation" } - // Reset/3 succeeds immediately, binding Term1 to Term2 and Cont to the remainder of Goal. - val shiftSubs = failure.subs - val appliedBall = applySubstitution(failure.ball, shiftSubs) - val appliedCont = applySubstitution(failure.cont, shiftSubs) - Conjunction(Unify(ball, appliedBall), Unify(appliedCont, cont)) - .satisfy(shiftSubs) - .forEach { conResult -> - conResult.map { conSubs -> - yield(Result.success(shiftSubs + conSubs)) - } - } - } else { - // If Goal fails, then reset also fails. - yield(Result.failure(failure)) - } - } - ) - } - } -} - -/** - * Variables that have been bound during the procedure called by reset/3 stay bound after a shift/1: - */ -class Shift(private val ball: Term) : Structure(Atom("shift"), listOf(ball)) { - override fun satisfy(subs: Substitutions): Answers = sequence { - val shift = AppliedShift( - subs = subs, - ball = ball, - cont = null - ) - yield(Result.failure(shift)) - } -} diff --git a/src/prolog/builtins/ioOperators.kt b/src/prolog/builtins/ioOperators.kt index 514690c..4c80d61 100644 --- a/src/prolog/builtins/ioOperators.kt +++ b/src/prolog/builtins/ioOperators.kt @@ -17,8 +17,6 @@ import prolog.logic.unifyLazy * Write [Term] to the current output, using brackets and operators where appropriate. */ class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable { - constructor(message: String) : this(Atom(message)) - override fun satisfy(subs: Substitutions): Answers { val t = applySubstitution(term, subs) @@ -47,12 +45,6 @@ object Nl : Atom("nl"), Satisfiable { override fun applySubstitution(subs: Substitutions): Nl = this } -class WriteLn(private val term: Term) : Conjunction(Write(term), Nl) { - constructor(message: String) : this(Atom(message)) - override fun applySubstitution(subs: Substitutions): WriteLn = WriteLn(applySubstitution(term, subs)) - override fun toString(): String = "writeln($term)" -} - /** * Read the next Prolog term from the current input stream and unify it with [Term]. * diff --git a/src/prolog/builtins/listOperators.kt b/src/prolog/builtins/listOperators.kt index d1a1944..4adbced 100644 --- a/src/prolog/builtins/listOperators.kt +++ b/src/prolog/builtins/listOperators.kt @@ -8,23 +8,20 @@ import prolog.ast.lists.List.Empty import prolog.ast.terms.Atom import prolog.ast.terms.Operator import prolog.ast.terms.Term -import prolog.logic.applySubstitution class Member(private val element: Term, private val list: List) : Operator(Atom("member"), element, list) { - private var solution: Operator = when (list) { - is Empty -> Disjunction(Fail, Fail) - is Cons -> Disjunction( + private var solution: Operator = if (list is Empty) { + Unify(element, list) + } else if (list is Cons) { + Disjunction( Unify(element, list.head), Member(element, list.tail) ) + } else { + throw IllegalArgumentException("Invalid list type: ${list::class.simpleName}") } override fun satisfy(subs: Substitutions): Answers = solution.satisfy(subs) - override fun applySubstitution(subs: Substitutions): Member = Member( - applySubstitution(element, subs), - applySubstitution(list, subs) as List - ) - override fun toString(): String = "$element ∈ $list" } diff --git a/src/prolog/builtins/metaOperators.kt b/src/prolog/builtins/metaOperators.kt deleted file mode 100644 index 047fa5f..0000000 --- a/src/prolog/builtins/metaOperators.kt +++ /dev/null @@ -1,44 +0,0 @@ -package prolog.builtins - -import prolog.Answers -import prolog.Substitutions -import prolog.ast.terms.Atom -import prolog.ast.terms.Goal -import prolog.ast.terms.Operator -import prolog.ast.terms.Term -import prolog.flags.AppliedCut -import prolog.logic.applySubstitution - -class Call(private val goal: Term) : Operator(Atom("call"), null, goal) { - override fun satisfy(subs: Substitutions): Answers { - val appliedGoal = applySubstitution(goal, subs) as Goal - return appliedGoal.satisfy(subs) - } -} - -/** - * Calls [Goal] once, but succeeds, regardless of whether Goal succeeded or not. - */ -class Ignore(goal: Goal) : Operator(Atom("ignore"), null, goal) { - private val disjunction = Disjunction( - Conjunction(Call(goal), Cut()), - True - ) - - override fun satisfy(subs: Substitutions): Answers = sequence { - disjunction.satisfy(subs).forEach { result -> - result.fold( - onSuccess = { newSubs -> - yield(Result.success(newSubs)) - }, - onFailure = { failure -> - if (failure is AppliedCut && failure.subs != null) { - yield(Result.success(failure.subs)) - } else { - yield(Result.failure(failure)) - } - } - ) - } - } -} diff --git a/src/prolog/flags/AppliedShift.kt b/src/prolog/flags/AppliedShift.kt deleted file mode 100644 index a96b55d..0000000 --- a/src/prolog/flags/AppliedShift.kt +++ /dev/null @@ -1,14 +0,0 @@ -package prolog.flags - -import prolog.Substitutions -import prolog.ast.terms.Goal -import prolog.ast.terms.Term - -/** - * An exception that indicates that a shift has been applied in the Prolog engine. - */ -data class AppliedShift( - val subs: Substitutions, - val ball: Term, - val cont: Term? = null, -) : Throwable() \ No newline at end of file diff --git a/src/prolog/logic/unification.kt b/src/prolog/logic/unification.kt index 1ac842b..8b9457a 100644 --- a/src/prolog/logic/unification.kt +++ b/src/prolog/logic/unification.kt @@ -7,11 +7,12 @@ import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Float import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Number +import prolog.ast.lists.List import prolog.ast.lists.List.Cons import prolog.ast.lists.List.Empty import prolog.ast.logic.Fact +import prolog.ast.logic.LogicOperator import prolog.ast.terms.* -import prolog.ast.lists.List as PList // Apply substitutions to a term fun applySubstitution(term: Term, subs: Substitutions): Term = when { @@ -28,8 +29,16 @@ fun applySubstitution(term: Term, subs: Substitutions): Term = when { else -> term } -fun applySubstitution(expr: Expression, subs: Substitutions): Expression { - return applySubstitution(expr as Term, subs) as Expression +//TODO Combine with the other applySubstitution function +fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when { + variable(expr, emptyMap()) -> applySubstitution(expr as Term, subs) as Expression + atomic(expr, subs) -> expr + expr is LogicOperator -> { + expr.arguments = expr.arguments.map { applySubstitution(it, subs) } + expr + } + + else -> expr } // Check if a variable occurs in a term @@ -81,7 +90,7 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence } } - t1 is PList && t2 is PList -> { + t1 is List && t2 is List -> { if (equivalent(t1, t2, subs)) { yield(Result.success(emptyMap())) } else if (t1.size == t2.size) { @@ -111,8 +120,8 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence } private fun unifyArgs( - args1: List, - args2: List, + args1: kotlin.collections.List, + args2: kotlin.collections.List, subs: Substitutions ): Answers = sequence { // Using the current subs, unify the first argument of each list @@ -162,7 +171,7 @@ fun equivalent(term1: Term, term2: Term, subs: Substitutions): Boolean { term1 is Variable && term2 is Variable -> term1 == term2 term1 is Variable -> term1 in subs && equivalent(subs[term1]!!, term2, subs) term2 is Variable -> term2 in subs && equivalent(subs[term2]!!, term1, subs) - term1 is PList && term2 is PList -> { + term1 is List && term2 is List -> { if (term1.isEmpty() && term2.isEmpty()) { true } else if (term1.isEmpty() || term2.isEmpty()) { diff --git a/src/repl/Repl.kt b/src/repl/Repl.kt index 688cea4..32326ba 100644 --- a/src/repl/Repl.kt +++ b/src/repl/Repl.kt @@ -6,7 +6,6 @@ import io.Terminal import parser.ReplParser import prolog.Answer import prolog.Answers -import prolog.flags.AppliedCut class Repl { private val io = Terminal() @@ -102,15 +101,8 @@ class Repl { } return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" } }, - onFailure = { failure -> - if (failure is AppliedCut) { - if (failure.subs != null) { - return prettyPrint(Result.success(failure.subs)) - } - return "false." - } - - return "ERROR: ${failure.message}" + onFailure = { + return "ERROR: ${it.message}" } ) } diff --git a/src/tool/mapEach.kt b/src/tool/mapEach.kt deleted file mode 100644 index 1786867..0000000 --- a/src/tool/mapEach.kt +++ /dev/null @@ -1,3 +0,0 @@ -package tool - -fun Sequence.mapEach(transform: (T) -> R): Sequence = map(transform) diff --git a/tests/e2e/Examples.kt b/tests/e2e/Examples.kt index 250f804..4c86e0d 100644 --- a/tests/e2e/Examples.kt +++ b/tests/e2e/Examples.kt @@ -27,7 +27,7 @@ class Examples { @Test fun debugHelper() { - loader.load("examples/meta/continuations.pl") + loader.load("examples/basics/arithmetics.pl") } @ParameterizedTest @@ -65,7 +65,6 @@ class Examples { ) fun meta() = listOf( - Arguments.of("continuations.pl", "Inside test\nEntering reset\nAfter reset\nCalling Cont(2)\nIn test X = 5; done\nCalling Cont(4)\nIn test X = 9; done\n"), Arguments.of("mib_voorbeelden.pl", "b\nf(b)\nf(g(a,a),h(c,d),i(e,f))\nf(g(a,a),h(c,c),i(e,f))\nf(g(a,a),h(c,c),i(e,e))\n") ) diff --git a/tests/parser/grammars/LogicGrammarTests.kt b/tests/parser/grammars/LogicGrammarTests.kt index 5ca662a..f5cde74 100644 --- a/tests/parser/grammars/LogicGrammarTests.kt +++ b/tests/parser/grammars/LogicGrammarTests.kt @@ -126,11 +126,10 @@ class LogicGrammarTests { val rule = result[0] as Rule 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 l0 = (rule.body as CompoundTerm) - val l1 = l0.arguments[0] as CompoundTerm - assertEquals(Functor.of("invited/2"), l1.functor, "Expected functor 'invited/2'") - val l2 = l0.arguments[1] as CompoundTerm - assertEquals(Functor.of(",/2"), l2.functor, "Expected functor ',/2'") + val l1 = (rule.body as CompoundTerm).arguments[0] as CompoundTerm + assertEquals(Functor.of(",/2"), l1.functor, "Expected functor ',/2'") + val l2 = l1.arguments[0] as CompoundTerm + assertEquals(Functor.of("invited/2"), l2.functor, "Expected functor 'invited/2'") } @Test diff --git a/tests/prolog/builtins/DelimitedContinuationsOperatorsTests.kt b/tests/prolog/builtins/DelimitedContinuationsOperatorsTests.kt deleted file mode 100644 index 994a992..0000000 --- a/tests/prolog/builtins/DelimitedContinuationsOperatorsTests.kt +++ /dev/null @@ -1,75 +0,0 @@ -package prolog.builtins - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import prolog.ast.Database.Program -import prolog.ast.arithmetic.Integer -import prolog.ast.lists.List.Cons -import prolog.ast.lists.List.Empty -import prolog.ast.logic.Rule -import prolog.ast.terms.Atom -import prolog.ast.terms.Structure -import prolog.ast.terms.Variable -import java.io.ByteArrayOutputStream -import java.io.PrintStream - -class DelimitedContinuationsOperatorsTests { - @BeforeEach - fun setup() { - Program.reset() - } - - @Test - fun `example member`() { - val member = Member(Variable("X"), Cons(Integer(1), Cons(Integer(2), Cons(Integer(3), Empty)))) - val reset = Reset(member, Variable("Ball"), Variable("Cont")) - - val result = reset.satisfy(emptyMap()).toList() - - assertEquals(3, result.size, "Expected 3 results") - - assertTrue(result[0].isSuccess, "Expected success for first result") - val subs0 = result[0].getOrNull()!! - assertEquals(2, subs0.size, "Expected 2 substitutions for first result") - assertEquals(Integer(1), subs0[Variable("X")]) - assertEquals(Integer(0), subs0[Variable("Cont")]) - - assertTrue(result[1].isSuccess, "Expected success for second result") - val subs1 = result[1].getOrNull()!! - assertEquals(2, subs1.size, "Expected 2 substitutions for second result") - assertEquals(Integer(2), subs1[Variable("X")]) - assertEquals(Integer(0), subs1[Variable("Cont")]) - - assertTrue(result[2].isSuccess, "Expected success for third result") - val subs2 = result[2].getOrNull()!! - assertEquals(2, subs2.size, "Expected 2 substitutions for third result") - assertEquals(Integer(3), subs2[Variable("X")]) - assertEquals(Integer(0), subs2[Variable("Cont")]) - } - - @Test - fun `example with output`() { - val reset = Reset(Atom("test_"), Variable("Term"), Variable("Cont")) - val isOp = Is(Variable("X"), Add(Integer(1), Multiply(Integer(2), Variable("Y")))) - Program.load( - listOf( - Rule( - Structure(Atom("test"), listOf(Variable("Cont"), Variable("Term"))), - Conjunction(WriteLn("Inside test"), Conjunction(reset, WriteLn("After reset"))) - ), - Rule( - Atom("test_"), - Conjunction(WriteLn("Entering reset"), Conjunction(Shift(Variable("Y")), Conjunction(isOp, Conjunction(Write("In test X = "), Conjunction(Write(Variable("X")), WriteLn("; done")))))) - ) - ) - ) - - val notNot2 = Not(Not(Conjunction(WriteLn("calling Cont(2)"), Conjunction(Unify(Variable("Term"), Integer(2)), Call(Variable("Cont")))))) - val notNot4 = Not(Not(Conjunction(WriteLn("calling Cont(4)"), Conjunction(Unify(Variable("Term"), Integer(4)), Call(Variable("Cont")))))) - val query = Conjunction(Structure(Atom("test"), listOf(Variable("Cont"), Variable("Term"))), Conjunction(notNot2, notNot4)) - - val result = query.satisfy(emptyMap()).toList() - } -} diff --git a/tests/prolog/builtins/MetaOperatorsTests.kt b/tests/prolog/builtins/MetaOperatorsTests.kt deleted file mode 100644 index aeb642c..0000000 --- a/tests/prolog/builtins/MetaOperatorsTests.kt +++ /dev/null @@ -1,34 +0,0 @@ -package prolog.builtins - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import prolog.ast.arithmetic.Integer -import prolog.ast.lists.List.Cons -import prolog.ast.lists.List.Empty -import prolog.ast.terms.Variable - -class MetaOperatorsTests { - @Test - fun `ignore of failing goal succeeds`() { - val goal = Member(Integer(4), Cons(Integer(1), Cons(Integer(2), Cons(Integer(3), Empty)))) - - val result = Ignore(goal).satisfy(emptyMap()).toList() - - assertEquals(1, result.size, "Should return one result") - assertTrue(result[0].isSuccess, "Result should be successful") - assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitutions") - } - - @Test - fun `ignore of succeeding goal returns first solution`() { - val goal = Member(Variable("X"), Cons(Integer(1), Cons(Integer(2), Cons(Integer(3), Empty)))) - - val result = Ignore(goal).satisfy(emptyMap()).toList() - - assertEquals(1, result.size, "Should return one result") - assertTrue(result[0].isSuccess, "Result should be successful") - val subs = result[0].getOrNull()!! - assertEquals(Integer(1), subs[Variable("X")], "Expected first solution to be 1") - } -} \ No newline at end of file