Checkpoint
This commit is contained in:
parent
9db1c66781
commit
724e911a6f
17 changed files with 288 additions and 95 deletions
|
@ -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)).
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -48,4 +48,10 @@ object Program : Resolvent {
|
||||||
|
|
||||||
correspondingDBs.forEach { it.clear() }
|
correspondingDBs.forEach { it.clear() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
clear()
|
||||||
|
variableRenamingStart = 0
|
||||||
|
storeNewLine = false
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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
46
tests/e2e/Examples.kt
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
package e2e
|
|
||||||
|
|
||||||
class myClass {
|
|
||||||
}
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Reference in a new issue