Checkpoint

This commit is contained in:
Tibo De Peuter 2025-05-01 21:16:48 +02:00
parent 9db1c66781
commit 724e911a6f
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
17 changed files with 288 additions and 95 deletions

View file

@ -21,3 +21,7 @@ main :-
write("but "), write("but "),
did_not_get_an_a(Y), did_not_get_an_a(Y),
write(Y), write(" did not get an A, "), fail; write("unfortunately."), nl. write(Y), write(" did not get an A, "), fail; write("unfortunately."), nl.
:- initialization(main).
main :- write('gpl zegt: '), groet(wereld), nl.
groet(X) :- write(dag(X)).

View file

@ -66,6 +66,14 @@ open class Preprocessor {
when { when {
// TODO Remove hardcoding by storing the functors as constants in operators? // TODO Remove hardcoding by storing the functors as constants in operators?
// Logic // Logic
term.functor == "=/2" -> {
Unify(args[0], args[1])
}
term.functor == "\\=/2" -> {
NotUnify(args[0], args[1])
}
term.functor == ",/2" -> { term.functor == ",/2" -> {
Conjunction(args[0] as LogicOperand, args[1] as LogicOperand) Conjunction(args[0] as LogicOperand, args[1] as LogicOperand)
} }
@ -92,14 +100,6 @@ open class Preprocessor {
// Arithmetic // Arithmetic
term.functor == "=/2" && args.all { it is Expression } -> {
Unify(args[0] as Expression, args[1] as Expression)
}
term.functor == "\\=/2" && args.all { it is Expression } -> {
NotUnify(args[0] as Expression, args[1] as Expression)
}
term.functor == "-/1" && args.all { it is Expression } -> { term.functor == "-/1" && args.all { it is Expression } -> {
Negate(args[0] as Expression) Negate(args[0] as Expression)
} }

View file

@ -7,6 +7,32 @@ import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer import prolog.ast.arithmetic.Integer
import prolog.ast.terms.* import prolog.ast.terms.*
/**
* Precedence is based on the following table:
*
* | Precedence | Type | Operators |
* |------------|------|-----------------------------------------------------------------------------------------------|
* | 1200 | xfx | --\>, :-, =\>, ==\> |
* | 1200 | fx | :-, ?- |
* | 1105 | xfy | \| |
* | 1100 | xfy | ; |
* | 1050 | xfy | -\>, \*-\> |
* | 1000 | xfy | , |
* | 990 | xfx | := |
* | 900 | fy | \\+ |
* | 700 | xfx | \<, =, =.., =:=, =\<, ==, =\\=, \>, \>=, \\=, \\==, as, is, \>:\<, :\< |
* | 600 | xfy | : |
* | 500 | yfx | +, -, /\\, \\/, xor |
* | 500 | fx | ? |
* | 400 | yfx | \*, /, //, div, rdiv, \<\<, \>\>, mod, rem |
* | 200 | xfx | \*\* |
* | 200 | xfy | ^ |
* | 200 | fy | +, -, \\ |
* | 100 | yfx | . |
* | 1 | fx | $ |
*
* @see [SWI-Prolog Predicate op/3](https://www.swi-prolog.org/pldoc/man?predicate=op/3)
*/
open class TermsGrammar : Tokens() { open class TermsGrammar : Tokens() {
// Basic named terms // Basic named terms
@ -19,7 +45,7 @@ open class TermsGrammar : Tokens() {
) use { Atom(text.substring(1, text.length - 1)) } ) use { Atom(text.substring(1, text.length - 1)) }
protected val atom: Parser<Atom> by (quotedAtom or simpleAtom) protected val atom: Parser<Atom> by (quotedAtom or simpleAtom)
protected val compound: Parser<Structure> by (atom * -leftParenthesis * separated( protected val compound: Parser<Structure> by (atom * -leftParenthesis * separated(
parser(::term), parser(::termNoConjunction),
comma, comma,
acceptZero = true acceptZero = true
) * -rightParenthesis) use { ) * -rightParenthesis) use {
@ -30,51 +56,57 @@ open class TermsGrammar : Tokens() {
protected val int: Parser<Integer> by integerToken use { Integer(text.toInt()) } protected val int: Parser<Integer> by integerToken use { Integer(text.toInt()) }
protected val float: Parser<Float> by floatToken use { Float(text.toFloat()) } protected val float: Parser<Float> by floatToken use { Float(text.toFloat()) }
// Operators // Base terms (atoms, compounds, variables, numbers)
protected val ops: Parser<String> by (dummy protected val baseTerm: Parser<Term> by (dummy
// Logic or (-leftParenthesis * parser(::term) * -rightParenthesis)
or comma
or semicolon
// Arithmetic
or plus
or equals
or notEquals
) use { this.text }
protected val simpleOperand: Parser<Operand> by (dummy
// Logic
or compound or compound
or atom or atom
or variable or variable
// Arithmetic
or int
or float or float
or int
) )
protected val operand: Parser<Operand> by (dummy
or parser(::operator) // Level 200 - prefix operators (+, -, \)
or simpleOperand protected val op200: Parser<CompoundTerm> by ((plus or minus) * parser(::term200)) use {
) CompoundTerm(Atom(t1.text), listOf(t2))
protected val operator: Parser<CompoundTerm> by (simpleOperand * ops * operand) use { }
CompoundTerm(Atom(t2), listOf(t1, t3)) protected val term200: Parser<Term> by (op200 or baseTerm)
// Level 400 - multiplication, division
protected val op400: Parser<String> by (multiply or divide) use { text }
protected val term400: Parser<Term> by (term200 * zeroOrMore(op400 * term200)) use {
t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
} }
// Parts // Level 500 - addition, subtraction
protected val head: Parser<Head> by (dummy protected val op500: Parser<String> by (plus or minus) use { text }
or compound protected val term500: Parser<Term> by (term400 * zeroOrMore(op500 * term400)) use {
or atom t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
) }
protected val body: Parser<Body> by (dummy
or operator
or head
or variable
) use { this as Body }
protected val term: Parser<Term> by (dummy // Level 700 - comparison operators
or float protected val op700: Parser<String> by (equals or notEquals) use { text }
or int protected val term700: Parser<Term> by (term500 * optional(op700 * term500)) use {
or variable if (t2 == null) t1 else CompoundTerm(Atom(t2!!.t1), listOf(t1, t2!!.t2))
or compound }
or atom
) // Level 1000 - conjunction (,)
protected val term1000: Parser<Term> by (term700 * zeroOrMore(comma * term700)) use {
t2.fold(t1) { acc, (_, term) -> CompoundTerm(Atom(","), listOf(acc, term)) }
}
// Level 1100 - disjunction (;)
protected val term1100: Parser<Term> by (term1000 * zeroOrMore(semicolon * term1000)) use {
t2.fold(t1) { acc, (_, term) -> CompoundTerm(Atom(";"), listOf(acc, term)) }
}
// Term - highest level expression
protected val term: Parser<Term> by term1100
protected val termNoConjunction: Parser<Term> by term700
// Parts for clauses
protected val head: Parser<Head> by (compound or atom)
protected val body: Parser<Body> by term use { this as Body }
override val rootParser: Parser<Any> by term override val rootParser: Parser<Any> by term
} }

View file

@ -25,6 +25,9 @@ abstract class Tokens : Grammar<Any>() {
protected val equals: Token by literalToken("=") protected val equals: Token by literalToken("=")
protected val notEquals: Token by literalToken("\\=") protected val notEquals: Token by literalToken("\\=")
protected val plus: Token by literalToken("+") protected val plus: Token by literalToken("+")
protected val minus: Token by literalToken("-")
protected val multiply: Token by literalToken("*")
protected val divide: Token by literalToken("/")
protected val dot by literalToken(".") protected val dot by literalToken(".")
// Ignored tokens // Ignored tokens

View file

@ -48,4 +48,10 @@ object Program : Resolvent {
correspondingDBs.forEach { it.clear() } correspondingDBs.forEach { it.clear() }
} }
fun reset() {
clear()
variableRenamingStart = 0
storeNewLine = false
}
} }

View file

@ -1,11 +1,15 @@
package prolog.ast.arithmetic package prolog.ast.arithmetic
import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.logic.LogicOperand
data class Integer(override val value: Int) : Number { data class Integer(override val value: Int) : Number, LogicOperand() {
// Integers are already evaluated // Integers are already evaluated
override fun simplify(subs: Substitutions): Simplification = Simplification(this, this) override fun simplify(subs: Substitutions): Simplification = Simplification(this, this)
override fun satisfy(subs: Substitutions): Answers = sequenceOf(Result.success(emptyMap()))
override fun toString(): String = value.toString() override fun toString(): String = value.toString()
override operator fun plus(other: Number): Number = when (other) { override operator fun plus(other: Number): Number = when (other) {

View file

@ -8,6 +8,7 @@ import prolog.builtins.True
import prolog.flags.AppliedCut import prolog.flags.AppliedCut
import prolog.logic.applySubstitution import prolog.logic.applySubstitution
import prolog.logic.numbervars import prolog.logic.numbervars
import prolog.logic.occurs
import prolog.logic.unifyLazy import prolog.logic.unifyLazy
/** /**
@ -46,7 +47,7 @@ abstract class Clause(val head: Head, val body: Body) : Resolvent {
.mapValues { reverse[it.value] ?: it.value } .mapValues { reverse[it.value] ?: it.value }
result = result.map { it.key to applySubstitution(it.value, result) } result = result.map { it.key to applySubstitution(it.value, result) }
.toMap() .toMap()
.filterNot { it.key in renamed.keys } .filterNot { it.key in renamed.keys && !occurs(it.key as Variable, goal, emptyMap())}
yield(Result.success(result)) yield(Result.success(result))
}, },
onFailure = { error -> onFailure = { error ->

View file

@ -4,8 +4,9 @@ import prolog.Answers
import prolog.Substitutions import prolog.Substitutions
import prolog.ast.arithmetic.Expression import prolog.ast.arithmetic.Expression
import prolog.ast.arithmetic.Simplification import prolog.ast.arithmetic.Simplification
import prolog.ast.logic.LogicOperand
data class Variable(val name: String) : Term, Body, Expression { data class Variable(val name: String) : Term, Body, Expression, LogicOperand() {
override fun simplify(subs: Substitutions): Simplification { override fun simplify(subs: Substitutions): Simplification {
// If the variable is bound, return the value of the binding // If the variable is bound, return the value of the binding
// If the variable is not bound, return the variable itself // If the variable is not bound, return the variable itself

View file

@ -18,7 +18,12 @@ import prolog.logic.unifyLazy
*/ */
class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable { class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable {
override fun satisfy(subs: Substitutions): Answers { override fun satisfy(subs: Substitutions): Answers {
val t = applySubstitution(term, subs) var t = term
var temp = applySubstitution(t, subs)
while (t != temp) {
t = temp
temp = applySubstitution(t, subs)
}
Terminal().say(t.toString()) Terminal().say(t.toString())

View file

@ -2,11 +2,13 @@
# This script is expected to be run from the root of the project. # This script is expected to be run from the root of the project.
WATCH_ALONG=0
# Paths to the two implementations # Paths to the two implementations
GPL="src/gpl" GPL="src/gpl"
SPL="swipl" SPL="swipl"
GPL_FLAGS=("--debug") GPL_FLAGS=("--error")
SPL_FLAGS=("--quiet" "-t" "'true'") SPL_FLAGS=("--quiet" "-t" "'true'")
# Directory containing test files # Directory containing test files
@ -14,7 +16,11 @@ TEST_DIR="examples"
# Temporary files for storing outputs # Temporary files for storing outputs
GPL_OUT=$(mktemp) GPL_OUT=$(mktemp)
GPL_ERR=$(mktemp)
SPL_OUT=$(mktemp) SPL_OUT=$(mktemp)
SPL_ERR=$(mktemp)
touch "$GPL_OUT" "$GPL_ERR" "$SPL_OUT" "$SPL_ERR"
# Flag to track if all tests pass # Flag to track if all tests pass
PASSED=0 PASSED=0
@ -22,27 +28,43 @@ FAILED=0
# Iterate over all test files in the test directory # Iterate over all test files in the test directory
#for TESTFILE in $(find ${TEST_DIR} -type f); do #for TESTFILE in $(find ${TEST_DIR} -type f); do
files=("examples/program.pl" "examples/basics/disjunction.pl" "examples/basics/fraternity.pl") files=(
"examples/program.pl"
"examples/basics/disjunction.pl"
"examples/basics/fraternity.pl"
"examples/basics/unification.pl"
"examples/basics/write.pl"
)
for TESTFILE in "${files[@]}"; do for TESTFILE in "${files[@]}"; do
# Run both programs with the test file # Run both programs with the test file
"${SPL}" "${SPL_FLAGS[@]}" "$TESTFILE" > "${SPL_OUT}" 2>&1 "${SPL}" "${SPL_FLAGS[@]}" "$TESTFILE" > "${SPL_OUT}" 2> "${SPL_ERR}"
"${GPL}" "${GPL_FLAGS[@]}" -s "$TESTFILE" > "${GPL_OUT}" 2>&1 "${GPL}" "${GPL_FLAGS[@]}" -s "$TESTFILE" > "${GPL_OUT}" 2> "${GPL_ERR}"
# Compare the outputs # Compare the outputs
if diff -q "$SPL_OUT" "$GPL_OUT" > /dev/null; then was_different="$(
PASSED=$((PASSED + 1)) diff -q "$SPL_OUT" "$GPL_OUT" > /dev/null
else echo $?
echo "Test failed! Outputs differ for $TESTFILE" )"
printf "\nTest:\n%s\n" "$(cat "$TESTFILE")" if [[ "${was_different}" -ne 0 || "${WATCH_ALONG}" -eq 1 ]]; then
printf "\nExpected:\n%s\n" "$(cat "$SPL_OUT")" if [ "${was_different}" -ne 0 ]; then
printf "\nGot:\n%s\n" "$(cat "$GPL_OUT")" message="TEST FAILED"
fi
printf "%s for %s\n" "${message:="Result"}" "${TESTFILE}"
printf "\nTest:\n%s\n" "$(cat "${TESTFILE}")"
printf "\nExpected:\n%s\n" "$(cat "${SPL_OUT}" && cat "${SPL_ERR}")"
printf "\nGot:\n%s\n" "$(cat "${GPL_OUT}" && cat "${GPL_ERR}")"
echo "-----------------------------------------" echo "-----------------------------------------"
fi
if [ "${was_different}" -ne 0 ]; then
FAILED=$((FAILED + 1)) FAILED=$((FAILED + 1))
else
PASSED=$((PASSED + 1))
fi fi
done done
# Clean up temporary files # Clean up temporary files
rm "$SPL_OUT" "$GPL_OUT" rm "$SPL_OUT" "$GPL_OUT" "$SPL_ERR" "$GPL_ERR"
# Final result, summary # Final result, summary
if [ $FAILED -eq 0 ]; then if [ $FAILED -eq 0 ]; then

46
tests/e2e/Examples.kt Normal file
View file

@ -0,0 +1,46 @@
package e2e
import interpreter.FileLoader
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import prolog.Program
import java.io.ByteArrayOutputStream
import java.io.PrintStream
class Examples {
private val loader = FileLoader()
@BeforeEach
fun setup() {
Program.reset()
}
@Test
fun fraternity() {
val inputFile = "examples/basics/fraternity.pl"
loader.load(inputFile)
}
@Test
fun unification() {
val inputFile = "examples/basics/unification.pl"
loader.load(inputFile)
}
@Test
fun write() {
val expected = "gpl zegt: dag(wereld)\n"
val outStream = ByteArrayOutputStream()
System.setOut(PrintStream(outStream))
val inputFile = "examples/basics/write.pl"
// Compare the stdio output with the expected output
loader.load(inputFile)
assertEquals(expected, outStream.toString())
}
}

View file

@ -1,4 +0,0 @@
package e2e
class myClass {
}

View file

@ -1,5 +1,6 @@
package parser package parser
import com.github.h0tk3y.betterParse.combinators.use
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 com.github.h0tk3y.betterParse.parser.Parser import com.github.h0tk3y.betterParse.parser.Parser
@ -13,7 +14,7 @@ import prolog.ast.terms.Structure
class OperatorParserTests { class OperatorParserTests {
class OperatorParser: TermsGrammar() { class OperatorParser: TermsGrammar() {
override val rootParser: Parser<CompoundTerm> by operator override val rootParser: Parser<CompoundTerm> by body use { this as CompoundTerm }
} }
private var parser = OperatorParser() as Grammar<CompoundTerm> private var parser = OperatorParser() as Grammar<CompoundTerm>

View file

@ -40,7 +40,7 @@ class LogicGrammarTests {
assertEquals(1, result.size, "Expected 1 fact") assertEquals(1, result.size, "Expected 1 fact")
assertTrue(result[0] is Fact, "Expected a fact") assertTrue(result[0] is Fact, "Expected a fact")
assertEquals(input, "${result[0].toString()}.", "Expected fact to be '$input'") assertEquals(input, "${result[0]}.", "Expected fact to be '$input'")
} }
@ParameterizedTest @ParameterizedTest
@ -125,9 +125,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
assertInstanceOf(CompoundTerm::class.java, rule.body, "Expected body to be a conjunction") assertEquals("guest/2", rule.head.functor, "Expected functor 'guest/2'")
val conjunction = rule.body as CompoundTerm assertEquals(",/2", (rule.body as CompoundTerm).functor, "Expected functor ',/2'")
assertEquals("invited/2", (conjunction.arguments[0] as CompoundTerm).functor, "Expected functor 'invited/2'") val l1 = (rule.body as CompoundTerm).arguments[0] as CompoundTerm
assertEquals(",/2", l1.functor, "Expected functor ',/2'")
val l2 = l1.arguments[0] as CompoundTerm
assertEquals("invited/2", l2.functor, "Expected functor 'invited/2'")
} }
@Test @Test

View file

@ -2,10 +2,11 @@ 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 com.github.h0tk3y.betterParse.parser.Parser
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
@ -17,7 +18,6 @@ import prolog.ast.terms.Structure
import prolog.ast.terms.Term import prolog.ast.terms.Term
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import prolog.logic.equivalent import prolog.logic.equivalent
import kotlin.test.assertEquals
class TermsGrammarTests { class TermsGrammarTests {
private lateinit var parser: Grammar<Term> private lateinit var parser: Grammar<Term>
@ -32,7 +32,7 @@ class TermsGrammarTests {
fun `parse atom`(name: String) { fun `parse atom`(name: String) {
val result = parser.parseToEnd(name) val result = parser.parseToEnd(name)
Assertions.assertEquals(Atom(name), result, "Expected atom '$name'") assertEquals(Atom(name), result, "Expected atom '$name'")
} }
@ParameterizedTest @ParameterizedTest
@ -52,7 +52,7 @@ class TermsGrammarTests {
val expected = input.substring(1, input.length - 1) val expected = input.substring(1, input.length - 1)
Assertions.assertEquals(Atom(expected), result, "Expected atom") assertEquals(Atom(expected), result, "Expected atom")
} }
@ParameterizedTest @ParameterizedTest
@ -60,7 +60,7 @@ class TermsGrammarTests {
fun `parse variable`(name: String) { fun `parse variable`(name: String) {
val result = parser.parseToEnd(name) val result = parser.parseToEnd(name)
Assertions.assertEquals(Variable(name), result, "Expected atom '$name'") assertEquals(Variable(name), result, "Expected atom '$name'")
} }
@Test @Test
@ -69,10 +69,7 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertTrue( assertEquals(Structure(Atom("f"), emptyList()), result, "Expected atom 'f'")
equivalent(Structure(Atom("f"), emptyList()), result, emptyMap()),
"Expected atom 'f'"
)
} }
@Test @Test
@ -81,10 +78,7 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertTrue( assertEquals(Structure(Atom("f"), listOf(Atom("a"))), result, "Expected atom 'f(a)'")
equivalent(Structure(Atom("f"), listOf(Atom("a"))), result, emptyMap()),
"Expected atom 'f(a)'"
)
} }
@Test @Test
@ -93,8 +87,9 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertTrue( assertEquals(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Atom("b"))), result, emptyMap()), Structure(Atom("f"), listOf(Atom("a"), Atom("b"))),
result,
"Expected atom 'f(a, b)'" "Expected atom 'f(a, b)'"
) )
} }
@ -105,7 +100,7 @@ class TermsGrammarTests {
val result = parser.parseToEnd(input) val result = parser.parseToEnd(input)
Assertions.assertTrue( assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Variable("X"))), result, emptyMap()), equivalent(Structure(Atom("f"), listOf(Atom("a"), Variable("X"))), result, emptyMap()),
"Expected atom 'f(a, X)'" "Expected atom 'f(a, X)'"
) )
@ -179,4 +174,59 @@ class TermsGrammarTests {
fun `parse unification`(input: String) { fun `parse unification`(input: String) {
assertDoesNotThrow { parser.parseToEnd(input) } assertDoesNotThrow { parser.parseToEnd(input) }
} }
@Nested
class `Operator precedence` {
private lateinit var parser: Grammar<Term>
@BeforeEach
fun setup() {
parser = TermsGrammar() as Grammar<Term>
}
@Test
fun `parse addition and multiplication`() {
val input = "1 + 2 * 3"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom("+"), listOf(Integer(1), Structure(Atom("*"), listOf(Integer(2), Integer(3))))),
result,
"Expected addition and multiplication"
)
}
@Test
fun `parse multiplication and addition`() {
val input = "1 * 2 + 3"
val result = parser.parseToEnd(input)
assertEquals(
Structure(Atom("+"), listOf(Structure(Atom("*"), listOf(Integer(1), Integer(2))), Integer(3))),
result,
"Expected multiplication and addition"
)
}
@Test
fun `complex expression`() {
val input = "1 + 2 * 3 - 4 / 5"
val result = parser.parseToEnd(input)
assertEquals(
Structure(
Atom("-"),
listOf(
Structure(Atom("+"), listOf(Integer(1), Structure(Atom("*"), listOf(Integer(2), Integer(3))))),
Structure(Atom("/"), listOf(Integer(4), Integer(5)))
)
),
result,
"Expected complex expression"
)
}
}
} }

View file

@ -390,5 +390,27 @@ class EvaluationTests {
assertEquals(1, subs5.size, "Expected 1 substitution, especially without 'Person'") assertEquals(1, subs5.size, "Expected 1 substitution, especially without 'Person'")
assertEquals(Atom("bob"), subs5[Variable("X")], "Expected bob") assertEquals(Atom("bob"), subs5[Variable("X")], "Expected bob")
} }
@Test
fun `likes_italian_food(Person)`() {
val result = Program.query(Structure(Atom("likes_italian_food"), listOf(Variable("Person")))).toList()
assertEquals(3, result.size, "Expected 3 results")
assertTrue(result[0].isSuccess, "Expected success")
val subs3 = result[0].getOrNull()!!
assertEquals(1, subs3.size, "Expected 1 substitution")
assertEquals(Atom("alice"), subs3[Variable("Person")], "Expected alice")
assertTrue(result[1].isSuccess, "Expected success")
val subs4 = result[1].getOrNull()!!
assertEquals(1, subs4.size, "Expected 1 substitution")
assertEquals(Atom("alice"), subs4[Variable("Person")], "Expected alice")
assertTrue(result[2].isSuccess, "Expected success")
val subs5 = result[2].getOrNull()!!
assertEquals(1, subs5.size, "Expected 1 substitution")
assertEquals(Atom("bob"), subs5[Variable("Person")], "Expected bob")
}
} }
} }

View file

@ -1,21 +1,18 @@
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.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource import org.junit.jupiter.params.provider.ValueSource
import prolog.ast.arithmetic.Float import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.CompoundTerm import prolog.ast.terms.CompoundTerm
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Variable import prolog.ast.terms.Variable
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.PrintStream
class IoOperatorsTests { class IoOperatorsTests {
private var outStream = ByteArrayOutputStream() private var outStream = ByteArrayOutputStream()