Call & Ignore
This commit is contained in:
parent
9b454a9669
commit
5bfeb96176
7 changed files with 118 additions and 28 deletions
|
@ -3,6 +3,7 @@ package interpreter
|
||||||
import io.Logger
|
import io.Logger
|
||||||
import prolog.ast.arithmetic.Expression
|
import prolog.ast.arithmetic.Expression
|
||||||
import prolog.ast.arithmetic.Integer
|
import prolog.ast.arithmetic.Integer
|
||||||
|
import prolog.ast.lists.List as PList
|
||||||
import prolog.ast.logic.Clause
|
import prolog.ast.logic.Clause
|
||||||
import prolog.ast.logic.Fact
|
import prolog.ast.logic.Fact
|
||||||
import prolog.ast.logic.LogicOperand
|
import prolog.ast.logic.LogicOperand
|
||||||
|
@ -50,9 +51,9 @@ open class Preprocessor {
|
||||||
protected open fun preprocess(term: Term, nested: Boolean = false): Term {
|
protected open fun preprocess(term: Term, nested: Boolean = false): Term {
|
||||||
// TODO Remove hardcoding by storing the functors as constants in operators?
|
// TODO Remove hardcoding by storing the functors as constants in operators?
|
||||||
|
|
||||||
val prepped = when {
|
val prepped = when (term) {
|
||||||
term == Variable("_") -> AnonymousVariable.create()
|
Variable("_") -> AnonymousVariable.create()
|
||||||
term is Atom || term is Structure -> {
|
is Atom, is Structure -> {
|
||||||
// Preprocess the arguments first to recognize builtins
|
// Preprocess the arguments first to recognize builtins
|
||||||
val args = if (term is Structure) {
|
val args = if (term is Structure) {
|
||||||
term.arguments.map { preprocess(it, nested = true) }
|
term.arguments.map { preprocess(it, nested = true) }
|
||||||
|
@ -149,6 +150,13 @@ open class Preprocessor {
|
||||||
Functor.of("nl/0") -> Nl
|
Functor.of("nl/0") -> Nl
|
||||||
Functor.of("read/1") -> Read(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] as Goal)
|
||||||
|
Functor.of("ignore/1") -> Ignore(args[0] as Goal)
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
Functor.of("initialization/1") -> Initialization(args[0] as Goal)
|
Functor.of("initialization/1") -> Initialization(args[0] as Goal)
|
||||||
Functor.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal)
|
Functor.of("forall/2") -> ForAll(args[0] as LogicOperand, args[1] as Goal)
|
||||||
|
|
|
@ -114,7 +114,20 @@ class Conjunction(val left: LogicOperand, private val right: LogicOperand) :
|
||||||
open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) :
|
open class Disjunction(private val left: LogicOperand, private val right: LogicOperand) :
|
||||||
LogicOperator(Atom(";"), left, right) {
|
LogicOperator(Atom(";"), left, right) {
|
||||||
override fun satisfy(subs: Substitutions): Answers = sequence {
|
override fun satisfy(subs: Substitutions): Answers = sequence {
|
||||||
yieldAll(left.satisfy(subs))
|
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(right.satisfy(subs))
|
yieldAll(right.satisfy(subs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,12 @@ import prolog.ast.terms.Operator
|
||||||
import prolog.ast.terms.Term
|
import prolog.ast.terms.Term
|
||||||
|
|
||||||
class Member(private val element: Term, private val list: List) : Operator(Atom("member"), element, list) {
|
class Member(private val element: Term, private val list: List) : Operator(Atom("member"), element, list) {
|
||||||
private var solution: Operator = if (list is Empty) {
|
private var solution: Operator = when (list) {
|
||||||
Unify(element, list)
|
is Empty -> Disjunction(Fail, Fail)
|
||||||
} else if (list is Cons) {
|
is Cons -> Disjunction(
|
||||||
Disjunction(
|
|
||||||
Unify(element, list.head),
|
Unify(element, list.head),
|
||||||
Member(element, list.tail)
|
Member(element, list.tail)
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
throw IllegalArgumentException("Invalid list type: ${list::class.simpleName}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun satisfy(subs: Substitutions): Answers = solution.satisfy(subs)
|
override fun satisfy(subs: Substitutions): Answers = solution.satisfy(subs)
|
||||||
|
|
39
src/prolog/builtins/metaOperators.kt
Normal file
39
src/prolog/builtins/metaOperators.kt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
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.flags.AppliedCut
|
||||||
|
|
||||||
|
class Call(private val goal: Goal) : Operator(Atom("call"), null, goal) {
|
||||||
|
override fun satisfy(subs: Substitutions): Answers = goal.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,12 +7,11 @@ import prolog.ast.arithmetic.Expression
|
||||||
import prolog.ast.arithmetic.Float
|
import prolog.ast.arithmetic.Float
|
||||||
import prolog.ast.arithmetic.Integer
|
import prolog.ast.arithmetic.Integer
|
||||||
import prolog.ast.arithmetic.Number
|
import prolog.ast.arithmetic.Number
|
||||||
import prolog.ast.lists.List
|
|
||||||
import prolog.ast.lists.List.Cons
|
import prolog.ast.lists.List.Cons
|
||||||
import prolog.ast.lists.List.Empty
|
import prolog.ast.lists.List.Empty
|
||||||
import prolog.ast.logic.Fact
|
import prolog.ast.logic.Fact
|
||||||
import prolog.ast.logic.LogicOperator
|
|
||||||
import prolog.ast.terms.*
|
import prolog.ast.terms.*
|
||||||
|
import prolog.ast.lists.List as PList
|
||||||
|
|
||||||
// Apply substitutions to a term
|
// Apply substitutions to a term
|
||||||
fun applySubstitution(term: Term, subs: Substitutions): Term = when {
|
fun applySubstitution(term: Term, subs: Substitutions): Term = when {
|
||||||
|
@ -29,16 +28,8 @@ fun applySubstitution(term: Term, subs: Substitutions): Term = when {
|
||||||
else -> term
|
else -> term
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Combine with the other applySubstitution function
|
fun applySubstitution(expr: Expression, subs: Substitutions): Expression {
|
||||||
fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when {
|
return applySubstitution(expr as Term, subs) as Expression
|
||||||
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
|
// Check if a variable occurs in a term
|
||||||
|
@ -90,7 +81,7 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t1 is List && t2 is List -> {
|
t1 is PList && t2 is PList -> {
|
||||||
if (equivalent(t1, t2, subs)) {
|
if (equivalent(t1, t2, subs)) {
|
||||||
yield(Result.success(emptyMap()))
|
yield(Result.success(emptyMap()))
|
||||||
} else if (t1.size == t2.size) {
|
} else if (t1.size == t2.size) {
|
||||||
|
@ -120,8 +111,8 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unifyArgs(
|
private fun unifyArgs(
|
||||||
args1: kotlin.collections.List<Argument>,
|
args1: List<Argument>,
|
||||||
args2: kotlin.collections.List<Argument>,
|
args2: List<Argument>,
|
||||||
subs: Substitutions
|
subs: Substitutions
|
||||||
): Answers = sequence {
|
): Answers = sequence {
|
||||||
// Using the current subs, unify the first argument of each list
|
// Using the current subs, unify the first argument of each list
|
||||||
|
@ -171,7 +162,7 @@ fun equivalent(term1: Term, term2: Term, subs: Substitutions): Boolean {
|
||||||
term1 is Variable && term2 is Variable -> term1 == term2
|
term1 is Variable && term2 is Variable -> term1 == term2
|
||||||
term1 is Variable -> term1 in subs && equivalent(subs[term1]!!, term2, subs)
|
term1 is Variable -> term1 in subs && equivalent(subs[term1]!!, term2, subs)
|
||||||
term2 is Variable -> term2 in subs && equivalent(subs[term2]!!, term1, subs)
|
term2 is Variable -> term2 in subs && equivalent(subs[term2]!!, term1, subs)
|
||||||
term1 is List && term2 is List -> {
|
term1 is PList && term2 is PList -> {
|
||||||
if (term1.isEmpty() && term2.isEmpty()) {
|
if (term1.isEmpty() && term2.isEmpty()) {
|
||||||
true
|
true
|
||||||
} else if (term1.isEmpty() || term2.isEmpty()) {
|
} else if (term1.isEmpty() || term2.isEmpty()) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.Terminal
|
||||||
import parser.ReplParser
|
import parser.ReplParser
|
||||||
import prolog.Answer
|
import prolog.Answer
|
||||||
import prolog.Answers
|
import prolog.Answers
|
||||||
|
import prolog.flags.AppliedCut
|
||||||
|
|
||||||
class Repl {
|
class Repl {
|
||||||
private val io = Terminal()
|
private val io = Terminal()
|
||||||
|
@ -101,8 +102,15 @@ class Repl {
|
||||||
}
|
}
|
||||||
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
|
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = { failure ->
|
||||||
return "ERROR: ${it.message}"
|
if (failure is AppliedCut) {
|
||||||
|
if (failure.subs != null) {
|
||||||
|
return prettyPrint(Result.success(failure.subs))
|
||||||
|
}
|
||||||
|
return "false."
|
||||||
|
}
|
||||||
|
|
||||||
|
return "ERROR: ${failure.message}"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
34
tests/prolog/builtins/MetaOperatorsTests.kt
Normal file
34
tests/prolog/builtins/MetaOperatorsTests.kt
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
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