This commit is contained in:
Tibo De Peuter 2025-05-07 21:15:10 +02:00
parent 65c4d925d3
commit 752c278cb0
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
2 changed files with 218 additions and 41 deletions

View file

@ -3,56 +3,43 @@ package prolog.builtins
import prolog.Answers import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.terms.AnonymousVariable import prolog.ast.terms.*
import prolog.ast.terms.Atom import prolog.logic.*
import prolog.ast.terms.Head import java.util.Locale
import prolog.ast.terms.Structure import java.util.Locale.getDefault
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.logic.applySubstitution
import prolog.logic.atomic
import prolog.logic.nonvariable
import prolog.logic.unifyLazy
import prolog.logic.variable
/**
* [True] when [Term] is a term with [FunctorInfo] Name/Arity.
*
* If Term is a [Variable] it is unified with a new term whose arguments are all different variables.
* If Term is [atomic], Arity will be unified with the integer 0, and Name will be unified with Term.
*/
class Functor(private val term: Term, private val functorName: Term, private val functorArity: Term) : class Functor(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 {
if (nonvariable(term, subs)) { return when {
nonvariable(term, subs) -> {
val t = applySubstitution(term, subs) as Head val t = applySubstitution(term, subs) as Head
return Conjunction( Conjunction(
Unify(t.functor.arity, functorArity), Unify(t.functor.arity, functorArity),
Unify(t.functor.name, functorName) Unify(t.functor.name, functorName)
).satisfy(subs) ).satisfy(subs)
} }
if (variable(term, subs)) { variable(term, subs) -> {
require(atomic(functorName, subs) && atomic(functorArity, subs)) { require(atomic(functorName, subs) && atomic(functorArity, subs)) {
"Arguments are not sufficiently instantiated" "Arguments are not sufficiently instantiated"
} }
val name = applySubstitution(functorName, subs) as Atom val name = applySubstitution(functorName, subs) as Atom
val arity = applySubstitution(functorArity, subs) as Integer val arity = applySubstitution(functorArity, subs) as Integer
val result = Structure(name, List(arity.value) { AnonymousVariable.create() })
val result = if (arity.value == 0) { sequenceOf(Result.success(mapOf(term to result)))
functorName
} else {
val argList = mutableListOf<Term>()
for (i in 1..arity.value) {
argList.add(AnonymousVariable.create())
}
Structure(name, argList)
} }
if (nonvariable(term, subs)) { else -> throw IllegalStateException()
return unifyLazy(term, result, subs)
} }
return sequenceOf(Result.success(mapOf(term to result)))
}
throw IllegalStateException()
} }
override fun applySubstitution(subs: Substitutions): Functor = Functor( override fun applySubstitution(subs: Substitutions): Functor = Functor(
@ -60,6 +47,51 @@ class Functor(private val term: Term, private val functorName: Term, private val
functorName.applySubstitution(subs), functorName.applySubstitution(subs),
functorArity.applySubstitution(subs) functorArity.applySubstitution(subs)
) )
}
override fun toString(): String = "functor($term, $functorName, $functorArity)"
class Arg(private val arg: Term, private val term: Term, private val value: Term) :
Structure(Atom("arg"), listOf(arg, term, value)) {
override fun satisfy(subs: Substitutions): Answers = sequence {
require(nonvariable(term, subs)) { "Arguments are not sufficiently instantiated" }
require(compound(term, subs)) {
val expected = CompoundTerm::class.simpleName?.lowercase()
val actual = term::class.simpleName?.lowercase()
"Type error: `$expected' expected, found `$term' ($actual)"
}
val t = applySubstitution(term, subs) as Structure
when {
variable(arg, subs) -> {
// Value will be unified with the successive arguments of term.
// On successful unification, arg is unified with the argument number.
// Backtracking yields alternative solutions.
var count = 0
for (argument in t.arguments) {
unifyLazy(value, argument, subs).forEach { result ->
result.map {
val sub = arg to Integer(count + 1)
yield(Result.success(it + sub))
}
}
count++
}
}
else -> {
val a = applySubstitution(arg, subs) as Integer
require(0 <= a.value) { "Domain error: not_less_than_zero" }
// Fail silently if the argument is out of bounds
if (0 == a.value || t.arguments.size < a.value) {
return@sequence
}
val argument = t.arguments[a.value - 1]
yieldAll(unifyLazy(argument, value, subs))
}
}
}
} }

View file

@ -4,6 +4,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.terms.AnonymousVariable import prolog.ast.terms.AnonymousVariable
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
@ -88,4 +89,148 @@ class AnalysingAndConstructionOperatorsTests {
assertTrue(result.isEmpty(), "Expected no results") assertTrue(result.isEmpty(), "Expected no results")
} }
@Test
fun `functor(X, Y, 1)`() {
val functor = Functor(Variable("X"), Variable("Y"), Integer(1))
val exception = assertThrows<Exception> {
functor.satisfy(emptyMap()).toList()
}
assertEquals("Arguments are not sufficiently instantiated", exception.message)
}
@Test
fun `arg without variables`() {
val arg = Arg(
Integer(1),
Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))),
Atom("a")
)
val result = arg.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 no substitutions")
}
@Test
fun `arg with variable value`() {
val arg = Arg(
Integer(1),
Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))),
Variable("Term")
)
val result = arg.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("Term")])
}
@Test
fun `arg with variable arg`() {
val arguments = listOf(Atom("a"), Atom("b"), Atom("c"))
for (i in arguments.indices) {
val arg = Arg(
Variable("Arg"),
Structure(Atom("f"), arguments),
arguments[i]
)
val result = arg.satisfy(emptyMap()).toList()
assertEquals(1, result.size, "Expected 1 result for arg $i")
assertTrue(result[0].isSuccess, "Expected success for arg $i")
val subs = result[0].getOrNull()!!
assertEquals(1, subs.size, "Expected 1 substitution for arg $i")
assertEquals(Integer(i + 1), subs[Variable("Arg")], "Expected arg to be $i + 1")
}
}
@Test
fun `arg with backtracking`() {
val arg = Arg(
Variable("Position"),
Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))),
Variable("Term")
)
val result = arg.satisfy(emptyMap()).toList()
assertEquals(3, result.size, "Expected 3 results")
assertTrue(result[0].isSuccess, "Expected success")
val subs1 = result[0].getOrNull()!!
assertEquals(2, subs1.size, "Expected 2 substitutions")
assertEquals(Integer(1), subs1[Variable("Position")])
assertEquals(Atom("a"), subs1[Variable("Term")])
assertTrue(result[1].isSuccess, "Expected success")
val subs2 = result[1].getOrNull()!!
assertEquals(2, subs2.size, "Expected 2 substitutions")
assertEquals(Integer(2), subs2[Variable("Position")])
assertEquals(Atom("b"), subs2[Variable("Term")])
assertTrue(result[2].isSuccess, "Expected success")
val subs3 = result[2].getOrNull()!!
assertEquals(2, subs3.size, "Expected 2 substitutions")
assertEquals(Integer(3), subs3[Variable("Position")])
assertEquals(Atom("c"), subs3[Variable("Term")])
}
@Test
fun `arg raises error if Arg is not compound`() {
val arg = Arg(Integer(1), Atom("foo"), Variable("X"))
val exception = assertThrows<IllegalArgumentException> {
arg.satisfy(emptyMap()).toList()
}
assertEquals("Type error: `structure' expected, found `foo' (atom)", exception.message)
}
@Test
fun `arg fails silently if arg = 0`() {
val arg = Arg(
Integer(0),
Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))),
Variable("Term")
)
val result = arg.satisfy(emptyMap()).toList()
assertTrue(result.isEmpty(), "Expected no results")
}
@Test
fun `arg fails silently if arg gt arity`() {
val arg = Arg(
Integer(4),
Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))),
Variable("Term")
)
val result = arg.satisfy(emptyMap()).toList()
assertTrue(result.isEmpty(), "Expected no results")
}
@Test
fun `arg raises error if arg lt 0`() {
val arg = Arg(
Integer(-1),
Structure(Atom("f"), listOf(Atom("a"), Atom("b"), Atom("c"))),
Variable("Term")
)
val exception = assertThrows<IllegalArgumentException> {
arg.satisfy(emptyMap()).toList()
}
assertEquals("Domain error: not_less_than_zero", exception.message)
}
} }