Compare commits
No commits in common. "717e5e09545fe9b58ec8ffb5af00762fc423a8d6" and "9b454a9669c49842b4e4d64423ebe781c86b9cdd" have entirely different histories.
717e5e0954
...
9b454a9669
17 changed files with 83 additions and 415 deletions
|
@ -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).
|
|
@ -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)
|
||||
|
|
|
@ -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<Term> by (notOp * parser(::term900)) use {
|
||||
CompoundTerm(Atom(t1.text), listOf(t2))
|
||||
}
|
||||
protected val term900: Parser<Term> by (not or term700)
|
||||
|
||||
protected val op1000: Parser<String> by (comma) use { text }
|
||||
protected val term1000: Parser<Term> by (term900 * zeroOrMore(op1000 * term900)) use {
|
||||
constructRightAssociative(t1, t2)
|
||||
protected val term1000: Parser<Term> by (term700 * zeroOrMore(op1000 * term700)) use {
|
||||
t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
|
||||
}
|
||||
|
||||
protected val op1100: Parser<String> by (semicolon) use { text }
|
||||
protected val term1100: Parser<Term> 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<Term> by (dynamicOp * functor) use {
|
||||
|
@ -123,12 +117,12 @@ open class TermsGrammar : Tokens() {
|
|||
}
|
||||
|
||||
// Lists
|
||||
protected val list: Parser<PList> by (-leftBracket * separated(
|
||||
protected val list: Parser<List> 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<Body> by term use { this as Body }
|
||||
|
||||
override val rootParser: Parser<Any> by term
|
||||
|
||||
fun constructRightAssociative(left: Term, pairs: List<Tuple2<String, Term>>): 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)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ abstract class Tokens : Grammar<Any>() {
|
|||
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("\\==")
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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].
|
||||
*
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
|
@ -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<Argument>,
|
||||
args2: List<Argument>,
|
||||
args1: kotlin.collections.List<Argument>,
|
||||
args2: kotlin.collections.List<Argument>,
|
||||
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()) {
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package tool
|
||||
|
||||
fun <T, R> Sequence<T>.mapEach(transform: (T) -> R): Sequence<R> = map(transform)
|
|
@ -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")
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
Reference in a new issue