mps-coderules/reactor/Test/test/TestRuleMatcher.kt

660 lines
23 KiB
Kotlin

import jetbrains.mps.logic.reactor.core.*
import jetbrains.mps.logic.reactor.program.ConstraintSymbol.symbol
import jetbrains.mps.unification.Term
import jetbrains.mps.unification.test.MockTerm.*
import jetbrains.mps.unification.test.MockTermsParser.*
import org.junit.Assert.assertEquals
import org.junit.Test
/*
* Copyright 2014-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Fedor Isakov
*/
class TestRuleMatcher {
infix fun <A, B> A.shouldBe(that: B) = assertEquals(that, this)
@Test
fun testExpand() {
with(programWithRules(
rule("main",
headReplaced(
constraint("foo"),
constraint("bar")
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
expand(occurrence("foo")) }.apply {
matches().size shouldBe 0 }.run {
expand(occurrence("bar")) }.apply {
matches().size shouldBe 1 }.run {
expand(occurrence("bazz")) }.apply {
matches().size shouldBe 0 }.run {
}
}
}
@Test
fun testSameOccurrence() {
with(programWithRules(
rule("rule1",
headReplaced(
constraint("foo"),
constraint("foo")
),
body(
constraint("qux")
))))
{
val foo = occurrence("foo")
with(ruleMatcher().probe()) {
expand(foo) }.apply {
matches().size shouldBe 0 }.run {
expand(foo) }.apply {
matches().size shouldBe 0 }.run {
expand(occurrence("foo")) }.apply {
matches().size shouldBe 2 }.run {
}
}
}
@Test
fun testTermArgument() {
with(programWithRules(
rule("main",
headReplaced(
constraint("foo", parseTerm("f{g h}")),
constraint("bar", parseTerm("g"))
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
expand(occurrence("foo", parseTerm("f{g g}"))) }.apply {
matches().size shouldBe 0 }.run {
expand(occurrence("bar", parseTerm("g"))) }.apply {
matches().size shouldBe 0 }.run {
expand(occurrence("foo", parseTerm("f{g h}"))) }.apply {
matches().size shouldBe 1 }.run {
}
}
}
@Test
fun testTermRefArgument() {
with(programWithRules(
rule("main",
headReplaced(
constraint("foo", parseTerm("f{g h{k}}"))
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
val termh = parseTerm("h{k}")
expand(occurrence("foo", term("f", parseTerm("g"), ref(termh)))) }.apply {
matches().size shouldBe 1
}
}
}
@Test
fun testShrink() {
with(programWithRules(
rule("main",
headReplaced(
constraint("foo"),
constraint("bar", "a"),
constraint("bazz")
),
body(
constraint("qux")
))))
{
val bara = occurrence("bar", "a")
with(ruleMatcher().probe()) {
expand(occurrence("foo")) }.apply {
matches().size shouldBe 0 }.run {
expand(bara) }.run {
expand(occurrence("bar", "b")) }.apply {
matches().size shouldBe 0 }.run {
contract(bara) }.apply {
matches().size shouldBe 0 }.run {
expand(occurrence("bazz")) }.apply {
matches().size shouldBe 0 }.run {
expand(bara) }.apply {
matches().size shouldBe 1
with(matches().first()) {
matchHeadKept().count() shouldBe 0
matchHeadReplaced().count() shouldBe 3
matchHeadReplaced().first().constraint().symbol() shouldBe symbol("foo", 0)
}
}
}
}
@Test
fun testMetaLogical() {
val (X, Y) = metaLogical<String>("X", "Y")
with(programWithRules(
rule("main",
headReplaced(
constraint("foo", X),
constraint("bar", X, Y),
constraint("bazz", Y)
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
expand(occurrence("foo", "a")) }.run {
expand(occurrence("bazz", "b")) }.run {
expand(occurrence("bar", "a", "c")) }.apply {
matches().size shouldBe 0 }.run {
expand(occurrence("bar", "d", "b")) }.apply {
matches().size shouldBe 0 }.run {
expand(occurrence("bar", "a", "b")) }.apply {
matches().size shouldBe 1
with(matches().first()) {
with(matchHeadReplaced().drop(2).first())
{
constraint().symbol() shouldBe symbol("bazz", 1)
arguments().count() shouldBe 1
arguments().first() shouldBe "b"
}
with(logicalContext()) {
variable(X).findRoot().value() shouldBe "a"
variable(Y).findRoot().value() shouldBe "b"
}
}
}.run {
expand(occurrence("foo", "d")) }.apply {
matches().size shouldBe 1
with(matches().first()) {
with(matchHeadReplaced().drop(1).first())
{
constraint().symbol() shouldBe symbol("bar", 2)
arguments().count() shouldBe 2
arguments().toList() shouldBe listOf("d", "b")
}
with(logicalContext()) {
variable(X).findRoot().value() shouldBe "d"
variable(Y).findRoot().value() shouldBe "b"
}
}
}
}
}
@Test
fun testTermMetaLogical() {
val (X, Y) = metaLogical<Term>("X", "Y")
val yLogical = Y.logical()
with(programWithRules(
rule("rule1",
headReplaced(
constraint("foo", term("f", metaVar(X)) ),
constraint("bar", parseTerm("g{h}"))
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
expand(occurrence("foo", parseTerm("f{g{h}}"))) }.apply {
matches().size shouldBe 0
}.run {
expand(occurrence("bar", parseTerm("g{h}"))) }.apply {
matches().size shouldBe 1
with(matches().first()) {
with(logicalContext()) {
variable(X).findRoot().value() shouldBe parseTerm("g{h}")
}
}
}
}
}
@Test
fun testTermRefLogicalMetaLogical() {
val (X, Y) = metaLogical<Term>("X", "Y")
val yLogical = Y.logical()
with(programWithRules(
rule("rule1",
headReplaced(
constraint("bar", parseTerm("h{k}"), metaVar(X)),
constraint("foo", term("f", metaVar(X), term("g")))
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
expand(occurrence("bar", parseTerm("h{k}"), parseTerm("h{k}"))) }.apply {
matches().size shouldBe 0 }.run {
yLogical.set(parseTerm("h{k}"))
val termh = logicalVar(yLogical)
expand(occurrence("foo", term("f", ref(termh), term("g")))) }.apply {
matches().size shouldBe 1
}
}
}
@Test
fun testTermRefLogicalMetaLogical2() {
val (X, Y) = metaLogical<Term>("X", "Y")
val yLogical = Y.logical()
with(programWithRules(
rule("rule1",
headReplaced(
constraint("foo", term("f", term("g", metaVar(X), term("h"))))
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
expand(occurrence("foo", term("f", ref(logicalVar(yLogical)), term("h")))) }.apply {
matches().size shouldBe 0 }.run {
yLogical.set(parseTerm("g{k h}"))
expand(occurrence("foo", term("f", ref(logicalVar(yLogical)), term("h")))) }.apply {
matches().size shouldBe 1 }.run {
}
}
}
@Test
fun testTermRefLogicalMetaLogical3() {
val (X, Y) = metaLogical<Term>("X", "Y")
val yLogical = Y.logical()
with(programWithRules(
rule("rule1",
headReplaced(
constraint("foo", term("f", ref(metaVar(X)), term("g"))),
constraint("bar", term("h", term("k"), metaVar(X)))
),
body(
constraint("qux")
))))
{
with(ruleMatcher().probe()) {
expand(occurrence("foo", term("f", logicalVar(yLogical), term("g")))) }.apply {
matches().size shouldBe 0 }.run {
yLogical.set(parseTerm("p{q r}"))
val termh = logicalVar(yLogical)
expand(occurrence("bar", parseTerm("h{k p{q r}}"))) }.apply {
matches().size shouldBe 1
}
}
}
@Test
fun testTermLogical() {
val (X, Y) = metaLogical<Term>("X", "Y")
val yLogical = Y.logical()
with(programWithRules(
rule("rule1",
headReplaced(
constraint("foo", term("f", term("k", metaVar(X)))),
constraint("bar", parseTerm("g{h}")),
constraint("bazz", term("k", metaVar(X)))
),
body(
constraint("qux")
))))
{
val occ2reactivate = occurrence("bazz", term("k", logicalVar(yLogical)))
with(ruleMatcher().probe()) {
expand(occ2reactivate) }.apply {
matches().size shouldBe 0
yLogical.findRoot().value() shouldBe null
}.run {
expand(occurrence("foo", parseTerm("f{k{h}}"))) }.apply {
matches().size shouldBe 0
}.run {
expand(occurrence("bar", parseTerm("g{h}"))) }.apply {
matches().size shouldBe 0
yLogical.findRoot().value() shouldBe null
// test reactivate occurrence
yLogical.findRoot().set(parseTerm("h"))
}.run {
expand(occ2reactivate) }.apply {
matches().size shouldBe 1
with(matches().first()) {
rule().tag() shouldBe "rule1"
with(logicalContext()) {
variable(X).findRoot().value() shouldBe parseTerm("h")
}
}
}
}
}
@Test
fun testDispatcher() {
with(programWithRules(
rule("main",
headReplaced(
constraint("foo"),
constraint("bar")
),
body(
constraint("qux")
))))
{
with(Dispatcher(RuleIndex(handlers)).fringe()) {
activated(occurrence("foo")) }.apply {
matches().count() shouldBe 0 }.run {
activated(occurrence("bar")) }.apply {
matches().count() shouldBe 1
with(matches().first()) {
rule().tag() shouldBe "main"
matchHeadKept().count() shouldBe 0
matchHeadReplaced().count() shouldBe 2
}
}
}
}
@Test
fun testDispatcherManyRules() {
val X = metaLogical<String>("X")
with(programWithRules(
rule("rule1",
headKept(
constraint("foo"),
constraint("bar", X)
),
body(
constraint("qux")
)),
rule("rule2",
headReplaced(
constraint("foo"),
constraint("bar", X),
constraint("bazz")
),
body(
constraint("qux")
))))
{
with(Dispatcher(RuleIndex(handlers)).fringe()) {
activated(occurrence("foo")) }.apply {
matches().count() shouldBe 0 }.run {
activated(occurrence("bar", "a")) }.apply {
matches().count() shouldBe 1
with(matches().first()) {
rule().tag() shouldBe "rule1"
}
}.run {
activated(occurrence("bazz")) }.apply {
matches().count() shouldBe 1
with(matches().first()) {
rule().tag() shouldBe "rule2"
logicalContext().variable(X).value() shouldBe "a"
}
}.run {
activated(occurrence("bar", "b")) }.apply {
matches().count() shouldBe 2
with(matches().drop(1).first()) {
rule().tag() shouldBe "rule2"
logicalContext().variable(X).value() shouldBe "b"
}
}
}
}
@Test
fun testDispatcherRulesOrder() {
with(programWithRules(
rule("rule1",
headKept(
constraint("foo"),
constraint("bar"),
constraint("bazz")
),
body(
constraint("qux")
)),
rule("rule2",
headReplaced(
constraint("foo"),
constraint("bar")
),
body(
constraint("qux")
)),
rule("rule3",
headReplaced(
constraint("foo"),
constraint("bar"),
constraint("bazz"),
constraint("blin")
),
body(
constraint("qux")
))))
{
with(Dispatcher(RuleIndex(handlers)).fringe()) {
activated(occurrence("blin")) }.apply {
matches().count() shouldBe 0 }.run {
activated(occurrence("bar")) }.apply {
matches().count() shouldBe 0 }.run {
activated(occurrence("bazz")) }.apply {
matches().count() shouldBe 0 }.run {
activated(occurrence("foo")) }.apply {
matches().count() shouldBe 3 }.run {
matches().map { it.rule().tag() }.toList() shouldBe listOf("rule1", "rule2", "rule3")
}
}
}
@Test
fun testDispatcherIncremental() {
with(programWithRules(
rule("rule1",
headKept(
constraint("foo"),
constraint("bar"),
constraint("bazz")
),
body(
constraint("qux")
)),
rule("rule2",
headReplaced(
constraint("foo"),
constraint("bar")
),
body(
constraint("qux")
)),
rule("rule3",
headReplaced(
constraint("foo"),
constraint("bar"),
constraint("bazz"),
constraint("blin")
),
body(
constraint("qux")
))))
{
with(Dispatcher(RuleIndex(handlers)).fringe()) {
activated(occurrence("foo")) }.apply {
matches().count() shouldBe 0 }.run {
activated(occurrence("bar")) }.apply {
matches().count() shouldBe 1
matches().first().rule().tag() shouldBe "rule2" }.run {
activated(occurrence("bazz")) }.apply {
matches().count() shouldBe 1
matches().first().rule().tag() shouldBe "rule1" }.run {
activated(occurrence("blin")) }.apply {
matches().count() shouldBe 1
matches().first().rule().tag() shouldBe "rule3"
}
}
}
@Test
fun testDispatcherIncrementalDiscard() {
with(programWithRules(
rule("rule1",
headKept(
constraint("foo"),
constraint("bar"),
constraint("bazz")
),
body(
constraint("qux")
)),
rule("rule2",
headReplaced(
constraint("foo"),
constraint("bar")
),
body(
constraint("qux")
)),
rule("rule3",
headReplaced(
constraint("foo"),
constraint("bar"),
constraint("bazz"),
constraint("blin")
),
body(
constraint("qux")
))))
{
val bar = occurrence("bar")
with(Dispatcher(RuleIndex(handlers)).fringe()) {
activated(occurrence("foo")) }.apply {
matches().count() shouldBe 0 }.run {
activated(bar) }.apply {
matches().count() shouldBe 1
matches().first().rule().tag() shouldBe "rule2" }.run {
activated(occurrence("bazz")) }.apply {
matches().count() shouldBe 1
matches().first().rule().tag() shouldBe "rule1" }.run {
discarded(bar) }.apply {
matches().count() shouldBe 0 }.run {
activated(occurrence("blin")) }.apply {
matches().count() shouldBe 0
}.run {
activated(bar) }.apply {
matches().count() shouldBe 3
matches().map { it.rule().tag() }.toList() shouldBe listOf("rule1", "rule2", "rule3")
}
}
}
private fun Builder.ruleMatcher() = Matcher(rules.first())
}