package interpreter import com.github.h0tk3y.betterParse.grammar.parseToEnd import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import parser.grammars.TermsGrammar import prolog.ast.arithmetic.Integer import prolog.ast.logic.Fact import prolog.ast.logic.Rule import prolog.ast.terms.* import prolog.builtins.* class PreprocessorTests { val preprocessor = OpenPreprocessor() companion object { val preprocessor = OpenPreprocessor() fun test(tests: Map) { for ((input, expected) in tests) { val result = preprocessor.preprocess(input) assertEquals(expected, result, "Expected preprocessed") assertEquals(expected::class, result::class, "Expected same class") } } } @Test fun `can preprocess anonymous variable`() { val input = Variable("_") val result = preprocessor.preprocess(input) assertInstanceOf(AnonymousVariable::class.java, result, "Expected anonymous variable") assertTrue((result as Variable).name.matches("_\\d+".toRegex()), "Expected anonymous variable name") } @Test fun `multiple anonymous variables should be unique`() { val input = CompoundTerm(Atom("foo"), listOf(Variable("_"), Variable("_"))) val result = preprocessor.preprocess(input) assertInstanceOf(CompoundTerm::class.java, result, "Expected compound term") assertEquals(2, (result as CompoundTerm).arguments.size, "Expected two terms") for (argument in result.arguments) { assertTrue( (argument as Variable).name.matches("_\\d+".toRegex()), "Expected anonymous variable name, but got ${argument.name}" ) } val first = result.arguments[0] as Variable val second = result.arguments[1] as Variable assertNotEquals(first.name, second.name, "Expected different anonymous variable names") } @Test fun `can preprocess nested anonymous variables`() { val input = TermsGrammar().parseToEnd("name(character(Name, _, _, _))") as Term val result = preprocessor.preprocess(input) assertInstanceOf(CompoundTerm::class.java, result, "Expected compound term") assertEquals(1, (result as CompoundTerm).arguments.size, "Expected one term") assertInstanceOf(CompoundTerm::class.java, result.arguments[0], "Expected compound term") val inner = result.arguments[0] as CompoundTerm assertEquals(4, inner.arguments.size, "Expected four terms") for (argument in inner.arguments) { if ((argument as Variable).name != "Name") { assertTrue( (argument as Variable).name.matches("_\\d+".toRegex()), "Expected anonymous variable name, but got ${argument.name}" ) } } } @Nested class `Arithmetic operators` { @Test fun `evaluates to different`() { test( mapOf( Atom("=\\=") to Atom("=\\="), CompoundTerm(Atom("=\\="), emptyList()) to CompoundTerm(Atom("=\\="), emptyList()), Atom("EvaluatesToDifferent") to Atom("EvaluatesToDifferent"), CompoundTerm(Atom("EvaluatesToDifferent"), emptyList()) to CompoundTerm( Atom("EvaluatesToDifferent"), emptyList() ), CompoundTerm(Atom("=\\="), listOf(Atom("a"))) to CompoundTerm( Atom("=\\="), listOf(Atom("a")) ), CompoundTerm(Atom("=\\="), listOf(Integer(1))) to CompoundTerm( Atom("=\\="), listOf(Integer(1)) ), CompoundTerm(Atom("=\\="), listOf(Atom("=\\="))) to CompoundTerm( Atom("=\\="), listOf(Atom("=\\=")) ), CompoundTerm(Atom("=\\="), listOf(Integer(1), Integer(2))) to EvaluatesToDifferent( Integer(1), Integer(2) ) ) ) } @Test fun `evaluates to`() { test( mapOf( Atom("=:=") to Atom("=:="), CompoundTerm(Atom("=:="), emptyList()) to CompoundTerm(Atom("=:="), emptyList()), Atom("EvaluatesTo") to Atom("EvaluatesTo"), CompoundTerm(Atom("EvaluatesTo"), emptyList()) to CompoundTerm( Atom("EvaluatesTo"), emptyList() ), CompoundTerm(Atom("=:="), listOf(Atom("a"))) to CompoundTerm( Atom("=:="), listOf(Atom("a")) ), CompoundTerm(Atom("=:="), listOf(Atom("=:="))) to CompoundTerm( Atom("=:="), listOf(Atom("=:=")) ), CompoundTerm(Atom("=:="), listOf(Integer(1), Integer(2))) to EvaluatesTo( Integer(1), Integer(2) ) ) ) } @Test fun `is`() { test( mapOf( Atom("is") to Atom("is"), CompoundTerm(Atom("is"), emptyList()) to CompoundTerm(Atom("is"), emptyList()), Atom("Is") to Atom("Is"), CompoundTerm(Atom("Is"), emptyList()) to CompoundTerm(Atom("Is"), emptyList()), CompoundTerm(Atom("is"), listOf(Atom("a"))) to CompoundTerm( Atom("is"), listOf(Atom("a")) ), CompoundTerm(Atom("is"), listOf(Integer(1))) to CompoundTerm( Atom("is"), listOf(Integer(1)) ), CompoundTerm(Atom("is"), listOf(Atom("is"))) to CompoundTerm( Atom("is"), listOf(Atom("is")) ), CompoundTerm(Atom("is"), listOf(Integer(1), Integer(2))) to Is( Integer(1), Integer(2) ) ) ) } @Test fun `negate and subtract`() { test( mapOf( Atom("-") to Atom("-"), CompoundTerm(Atom("-"), emptyList()) to CompoundTerm(Atom("-"), emptyList()), Atom("Negate") to Atom("Negate"), CompoundTerm(Atom("Negate"), emptyList()) to CompoundTerm( Atom("Negate"), emptyList() ), CompoundTerm(Atom("-"), listOf(Atom("a"))) to CompoundTerm( Atom("-"), listOf(Atom("a")) ), CompoundTerm(Atom("-"), listOf(Integer(1))) to Negate(Integer(1)), CompoundTerm(Atom("-"), listOf(Atom("-"))) to CompoundTerm( Atom("-"), listOf(Atom("-")) ), CompoundTerm(Atom("-"), listOf(Integer(1), Integer(2))) to Subtract( Integer(1), Integer(2) ), CompoundTerm(Atom("-"), listOf(Atom("1"), Atom("2"))) to CompoundTerm( Atom("-"), listOf(Atom("1"), Atom("2")) ), CompoundTerm(Atom("-"), listOf(Integer(1), Integer(2), Integer(3))) to CompoundTerm( Atom("-"), listOf(Integer(1), Integer(2), Integer(3)) ) ) ) } @Test fun `positive and add`() { test( mapOf( Atom("+") to Atom("+"), CompoundTerm(Atom("+"), emptyList()) to CompoundTerm(Atom("+"), emptyList()), Atom("Positive") to Atom("Positive"), CompoundTerm(Atom("Positive"), emptyList()) to CompoundTerm( Atom("Positive"), emptyList() ), CompoundTerm(Atom("+"), listOf(Atom("a"))) to CompoundTerm( Atom("+"), listOf(Atom("a")) ), CompoundTerm(Atom("+"), listOf(Integer(1))) to Positive(Integer(1)), CompoundTerm(Atom("+"), listOf(Atom("+"))) to CompoundTerm( Atom("+"), listOf(Atom("+")) ), CompoundTerm(Atom("+"), listOf(Integer(1), Integer(2))) to Add( Integer(1), Integer(2) ), CompoundTerm(Atom("+"), listOf(Atom("1"), Atom("2"))) to CompoundTerm( Atom("+"), listOf(Atom("1"), Atom("2")) ), CompoundTerm(Atom("+"), listOf(Integer(1), Integer(2), Integer(3))) to CompoundTerm( Atom("+"), listOf(Integer(1), Integer(2), Integer(3)) ) ) ) } @Test fun multiply() { test( mapOf( Atom("*") to Atom("*"), CompoundTerm(Atom("*"), emptyList()) to CompoundTerm(Atom("*"), emptyList()), Atom("Multiply") to Atom("Multiply"), CompoundTerm(Atom("Multiply"), emptyList()) to CompoundTerm( Atom("Multiply"), emptyList() ), CompoundTerm(Atom("*"), listOf(Atom("a"))) to CompoundTerm( Atom("*"), listOf(Atom("a")) ), CompoundTerm(Atom("*"), listOf(Integer(1))) to CompoundTerm(Atom("*"), listOf(Integer(1))), CompoundTerm(Atom("*"), listOf(Atom("*"))) to CompoundTerm( Atom("*"), listOf(Atom("*")) ), CompoundTerm(Atom("*"), listOf(Integer(1), Integer(2))) to Multiply( Integer(1), Integer(2) ), CompoundTerm(Atom("*"), listOf(Atom("1"), Atom("2"))) to CompoundTerm( Atom("*"), listOf(Atom("1"), Atom("2")) ), CompoundTerm(Atom("*"), listOf(Integer(1), Integer(2), Integer(3))) to CompoundTerm( Atom("*"), listOf(Integer(1), Integer(2), Integer(3)) ) ) ) } @Test fun divide() { test( mapOf( Atom("/") to Atom("/"), CompoundTerm(Atom("/"), emptyList()) to CompoundTerm(Atom("/"), emptyList()), Atom("Divide") to Atom("Divide"), CompoundTerm(Atom("Divide"), emptyList()) to CompoundTerm( Atom("Divide"), emptyList() ), CompoundTerm(Atom("/"), listOf(Atom("a"))) to CompoundTerm( Atom("/"), listOf(Atom("a")) ), CompoundTerm(Atom("/"), listOf(Integer(1))) to CompoundTerm(Atom("/"), listOf(Integer(1))), CompoundTerm(Atom("/"), listOf(Atom("/"))) to CompoundTerm( Atom("/"), listOf(Atom("/")) ), CompoundTerm(Atom("/"), listOf(Integer(1), Integer(2))) to Divide( Integer(1), Integer(2) ), CompoundTerm(Atom("/"), listOf(Atom("1"), Atom("2"))) to CompoundTerm( Atom("/"), listOf(Atom("1"), Atom("2")) ), CompoundTerm(Atom("/"), listOf(Integer(1), Integer(2), Integer(3))) to CompoundTerm( Atom("/"), listOf(Integer(1), Integer(2), Integer(3)) ) ) ) } @Test fun between() { test( mapOf( Atom("between") to Atom("between"), CompoundTerm(Atom("between"), emptyList()) to CompoundTerm( Atom("between"), emptyList() ), Atom("Between") to Atom("Between"), CompoundTerm(Atom("Between"), emptyList()) to CompoundTerm( Atom("Between"), emptyList() ), CompoundTerm(Atom("between"), listOf(Atom("a"))) to CompoundTerm( Atom("between"), listOf(Atom("a")) ), CompoundTerm(Atom("between"), listOf(Integer(1))) to CompoundTerm( Atom("between"), listOf(Integer(1)) ), CompoundTerm(Atom("between"), listOf(Atom("between"))) to CompoundTerm( Atom("between"), listOf(Atom("between")) ), CompoundTerm(Atom("between"), listOf(Integer(1), Integer(2))) to CompoundTerm( Atom("between"), listOf(Integer(1), Integer(2)) ), CompoundTerm(Atom("between"), listOf(Integer(1), Integer(2), Integer(3))) to Between( Integer(1), Integer(2), Integer(3) ), ) ) } @Test fun `fun combinations`() { /* * [X - 1] is [(1 + 2) * ((12 / 3) - 0)] * should return * X = 13 */ val sum_ = CompoundTerm(Atom("+"), listOf(Integer(1), Integer(2))) val sum = Add(Integer(1), Integer(2)) val div_ = CompoundTerm(Atom("/"), listOf(Integer(12), Integer(3))) val div = Divide(Integer(12), Integer(3)) val sub_ = CompoundTerm(Atom("-"), listOf(div_, Integer(0))) val sub = Subtract(div, Integer(0)) val right_ = CompoundTerm(Atom("*"), listOf(sum_, sub_)) val right = Multiply(sum, sub) val left_ = CompoundTerm(Atom("-"), listOf(Variable("X"), Integer(1))) val left = Subtract(Variable("X"), Integer(1)) val expr_ = CompoundTerm(Atom("is"), listOf(left_, right_)) val expr = Is(left, right) val result = OpenPreprocessor().preprocess(expr_) assertEquals(expr, result) assertEquals(Is::class, result::class) val `is` = result as Is assertEquals(left, `is`.number) assertEquals(Subtract::class, `is`.number::class) assertEquals(right, `is`.expr) assertEquals(Multiply::class, `is`.expr::class) val multiply = `is`.expr as Multiply assertEquals(sum, multiply.expr1) assertEquals(Add::class, multiply.expr1::class) } } @Nested class `Control operators` { private var preprocessor = OpenPreprocessor() @Test fun fail() { test( mapOf( Atom("fail") to Fail, CompoundTerm(Atom("fail"), emptyList()) to Fail, Atom("Fail") to Atom("Fail"), CompoundTerm(Atom("Fail"), emptyList()) to CompoundTerm(Atom("Fail"), emptyList()), CompoundTerm(Atom("fail"), listOf(Atom("a"))) to CompoundTerm(Atom("fail"), listOf(Atom("a"))), CompoundTerm(Atom("fail"), listOf(Atom("fail"))) to CompoundTerm(Atom("fail"), listOf(Fail)) ) ) } @Test fun `true`() { test( mapOf( Atom("true") to True, CompoundTerm(Atom("true"), emptyList()) to True, Atom("True") to Atom("True"), CompoundTerm(Atom("True"), emptyList()) to CompoundTerm(Atom("True"), emptyList()), CompoundTerm(Atom("true"), listOf(Atom("a"))) to CompoundTerm(Atom("true"), listOf(Atom("a"))), CompoundTerm(Atom("true"), listOf(Atom("true"))) to CompoundTerm(Atom("true"), listOf(True)) ) ) } @Test fun cut() { test( mapOf( Atom("!") to Cut(), CompoundTerm(Atom("!"), emptyList()) to Cut(), CompoundTerm(Atom("!"), listOf(Atom("a"))) to CompoundTerm(Atom("!"), listOf(Atom("a"))), CompoundTerm(Atom("!"), listOf(Atom("!"))) to CompoundTerm(Atom("!"), listOf(Cut())) ) ) } @Test fun conjunction() { test( mapOf( CompoundTerm(Atom(","), listOf(Atom("a"), Atom("b"))) to Conjunction(Atom("a"), Atom("b")), CompoundTerm(Atom(","), listOf(Atom("a"), Atom("b"), Atom("c"))) to CompoundTerm( Atom(","), listOf(Atom("a"), Atom("b"), Atom("c")) ), // Nested conjunctions CompoundTerm( Atom(","), listOf(Atom("a"), CompoundTerm(Atom(","), listOf(Atom("b"), Atom("c")))) ) to Conjunction(Atom("a"), Conjunction(Atom("b"), Atom("c"))), ) ) } @Test fun disjunction() { test( mapOf( CompoundTerm(Atom(";"), listOf(Atom("a"), Atom("b"))) to Disjunction(Atom("a"), Atom("b")), CompoundTerm(Atom(";"), listOf(Atom("a"), Atom("b"), Atom("c"))) to CompoundTerm( Atom(";"), listOf(Atom("a"), Atom("b"), Atom("c")) ), // Nested disjunctions CompoundTerm( Atom(";"), listOf(Atom("a"), CompoundTerm(Atom(";"), listOf(Atom("b"), Atom("c")))) ) to Disjunction(Atom("a"), Disjunction(Atom("b"), Atom("c"))), ) ) } @Test fun not() { test( mapOf( CompoundTerm(Atom("\\+"), listOf(Atom("a"))) to Not(Atom("a")), CompoundTerm(Atom("\\+"), listOf(Atom("a"), Atom("b"))) to CompoundTerm( Atom("\\+"), listOf(Atom("a"), Atom("b")) ), // Nested not CompoundTerm( Atom("foo"), listOf( Atom("bar"), CompoundTerm(Atom("\\+"), listOf(CompoundTerm(Atom("\\+"), listOf(Atom("baz"))))) ) ) to CompoundTerm(Atom("foo"), listOf(Atom("bar"), Not(Not(Atom("baz"))))), ) ) } @Test fun `is`() { test( mapOf( CompoundTerm(Atom("is"), listOf(Variable("T"), Integer(1))) to Is(Variable("T"), Integer(1)), CompoundTerm(Atom("is"), listOf(Variable("T"), Add(Variable("HP"), Integer(5)))) to Is( Variable("T"), Add(Variable("HP"), Integer(5)) ), CompoundTerm(Atom("is"), listOf(Variable("T"), Subtract(Variable("HP"), Integer(5)))) to Is( Variable("T"), Subtract(Variable("HP"), Integer(5)) ), ) ) } } @Nested class `Database operators` { private val preprocessor = OpenPreprocessor() @Test fun `assert(fact)`() { val input = Structure( Atom("assert"), listOf( Structure( Atom(":-"), listOf( Atom("a"), Atom("b") ) ) ) ) val expected = Assert( Rule( Atom("a"), Atom("b") ) ) val result = preprocessor.preprocess(input) assertEquals(expected, result) } } }