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

536 lines
20 KiB
Kotlin

import jetbrains.mps.logic.reactor.core.*
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.program.Constraint
import jetbrains.mps.logic.reactor.program.ConstraintSymbol
import jetbrains.mps.unification.Term
import jetbrains.mps.unification.Unification
import jetbrains.mps.unification.test.MockTermsParser.parse
import org.junit.Assert.*
import org.junit.Test
/**
* @author Fedor Isakov
*/
class TestMatcher {
@Test
fun matchSingle() {
programWithRules(
rule("main",
headReplaced(
constraint("main")
),
body(
constraint("foo")
))
).let { builder ->
builder.indices().run {
Matcher(first).matching(occurrence("main"), second).let { matches ->
val match = matches.single()
assertEquals(match.rule, builder.rules.first())
assertTrue(match.keptOccurrences.isEmpty())
val occ = match.discardedOccurrences.single()
assert(occ.constraint().symbol().id() == "main")
}
}
}
}
@Test
fun matchDiscardedKept() {
programWithRules(
rule("main1",
headReplaced(
constraint("main")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main")
),
body(
constraint("bar")
))
).let { builder ->
builder.indices().run {
Matcher(first).matching(occurrence("main"), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(builder.rules.toSet(), matches.map { m -> m.rule }.toSet())
matches.forEach { m -> assertTrue(m.keptOccurrences.size + m.discardedOccurrences.size == 1) }
matches.flatMap { m ->
(m.keptOccurrences.toList() + m.discardedOccurrences.toList()) }.forEach { occ ->
assert(occ.constraint().symbol().id() == "main")
}
}
}
}
}
@Test
fun matchComplementMissing() {
programWithRules(
rule("main1",
headKept(
constraint("main")
),
headReplaced(
constraint("secondary")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main")
),
body(
constraint("bar")
))
).let { builder ->
builder.indices().run {
Matcher(first).matching(occurrence("main"), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(builder.rules.drop(1).toSet(), matches.map { m -> m.rule }.toSet())
}
}
}
}
@Test
fun matchComplementPresent() {
programWithRules(
rule("main1",
headKept(
constraint("main")
),
headReplaced(
constraint("aux")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main")
),
body(
constraint("bar")
))
).let { builder ->
builder.indices(occurrence("aux")).run {
Matcher(first).matching(occurrence("main"), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(builder.rules.toSet(), matches.map { m -> m.rule }.toSet())
}
}
}
}
@Test
fun matchArgument() {
programWithRules(
rule("main1",
headKept(
constraint("main", "foo")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main", "bar")
),
body(
constraint("bar")
))
).let { builder ->
builder.indices().run{
Matcher(first).matching(occurrence("main", "bar"), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(builder.rules.drop(1), matches.map { m -> m.rule }.toList())
}
}
}
}
@Test
fun noMatchArgument() {
programWithRules(
rule("main1",
headKept(
constraint("main", "foo")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main", "bar")
),
body(
constraint("bar")
))
).indices().run{
Matcher(first).matching(occurrence("main", "qux"), second).let { matches ->
assertFalse(matches.any())
}
}
}
@Test
fun multipleHandlers() {
programWithHandlers(
handler("handler1", listOf(ConstraintSymbol("foo", 0)),
rule("main1",
headKept(
constraint("foo")
))
),
handler("handler2", listOf(ConstraintSymbol("bar", 0)),
rule("main2",
headKept(
constraint("bar")
))
)
).indices().run {
Matcher(first).matching(occurrence("qux"), second).let { matches ->
assertFalse(matches.any())
}
Matcher(first).matching(occurrence("foo"), second).let { matches ->
assertSame(1, matches.size)
}
Matcher(first).matching(occurrence("bar"), second).let { matches ->
assertSame(1, matches.size)
}
}
}
@Test
fun metaLogical() {
val (A, B, C) = metaLogical<String>("A", "B", "C")
val b = B.logical()
programWithRules(
rule("main",
headKept(
constraint("foo", A, B)
),
body(
constraint("bar", B)
)
)
).indices().run {
Matcher(first).matches(occurrence("foo", "blah", b), second).first().run {
assert(successful)
assertEquals("blah", logicalContext.variable(A).findRoot().value())
assertSame(b, logicalContext.variable(B))
assertEquals(C.logical().metaLogical(), logicalContext.variable(C).metaLogical())
}
}
}
@Test
fun matchMetaLogical() {
val (M, N) = metaLogical<Int>("M", "N")
programWithRules(
rule("main1",
headKept(
constraint("foo", M)
),
headReplaced(
constraint("foo", N)
),
body(
constraint("bar")
)),
rule("main2",
headKept(
constraint("foo", M)
),
headReplaced(
constraint("foo", M)
),
body(
constraint("bar")
))
).run {
indices().run {
Matcher(first).matching(occurrence("foo", 1), second).let { matches ->
assertFalse(matches.any())
}
}
// same parameter -- 4 matches (all permutations)
indices(occurrence("foo", 42)).run {
Matcher(first).matching(occurrence("foo", 42), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(4, matches.count())
assertTrue(matches.all { m -> m.allOccurrences().toSet().size == 2 })
}
}
indices(occurrence("foo", 42)).run {
Matcher(first).matching(occurrence("foo", 16), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(2, matches.count())
assertEquals(listOf("main1", "main1"), matches.map { m -> m.rule.tag() }.toList())
matches.map { m ->
setOf(M, N).map { lp ->
m.logicalContext.variable(lp).findRoot().value()
}
}.forEach { vals ->
assertEquals(setOf(42, 16), vals.toSet())
}
assertTrue(matches.all { m -> m.allOccurrences().toSet().size == 2 })
}
}
val (x, y) = logical<Int>("x", "y")
x.set(123)
y.set(456)
indices(occurrence("foo", x)).run {
Matcher(first).matching(occurrence("foo", y), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(2, matches.count())
assertEquals(listOf("main1", "main1"), matches.map { m -> m.rule.tag() }.toList())
assertTrue(matches.all { m -> m.allOccurrences().toSet().size == 2 })
}
}
val (v, w) = logical<Int>("v", "w")
w.findRoot().union(v)
indices(occurrence("foo", v)).run {
Matcher(first).matching(occurrence("foo", w), second).let { matches ->
assertTrue(matches.all {m -> m.successful})
assertEquals(2, matches.count())
assertTrue(matches.all{ m -> m.allOccurrences().toSet().size == 2 })
}
}
}
}
@Test
fun matchOccurrenceArguments() {
val (M, N) = metaLogical<Int>("M", "N")
val (O, P) = metaLogical<Int>("O", "P")
programWithRules(
rule("select_A_x",
headKept(
constraint("foo", "A", M)
),
headReplaced(
constraint("foo", "A", N)
),
body(
constraint("bar")
)),
rule("select_A_B",
headReplaced(
constraint("foo", "A", "B")
),
body(
constraint("bar")
)),
rule("select_x_B",
headKept(
constraint("foo", M, "B")
),
body(
constraint("bar")
)),
rule("select_B_x",
headKept(
constraint("foo", "B", M)
),
body(
constraint("bar")
)),
rule("select_ALL",
headKept(
constraint("foo", O, P)
),
body(
constraint("bar")
))
).indices().first.run {
val x = logical<Any>("X")
assertEquals(
listOf(byTag("select_A_x"), byTag("select_A_B"), byTag("select_x_B"), byTag("select_ALL")),
forOccurrence(occurrence("foo", "A", x)).toList())
assertEquals(
listOf(byTag("select_x_B"), byTag("select_B_x"), byTag("select_ALL")),
forOccurrence(occurrence("foo", "B", x)).toList())
assertEquals(
listOf(byTag("select_B_x"), byTag("select_ALL")),
forOccurrence(occurrence("foo", "B", "C")).toList())
}
}
@Test
fun matchOccurrenceTermArguments() {
val (M, N) = metaLogical<Int>("M", "N")
val (O, P) = metaLogical<Int>("O", "P")
programWithRules(
rule("select_fg_x",
headKept(
constraint("foo", parse("f{g}"), M)
),
body(
constraint("bar")
)),
rule("select_abX_x",
headKept(
constraint("foo", parse("a{b X}"), M)
),
body(
constraint("bar")
)),
rule("select_aYcde_x",
headKept(
constraint("foo", parse("a{Y c{d e}}"), M)
),
body(
constraint("bar")
)),
rule("select_aYcdd_fg",
headKept(
constraint("foo", parse("a{Y c{d d}}"), parse("f{g}"))
),
body(
constraint("bar")
))
).indices().first.run {
assertEquals(
listOf(byTag("select_fg_x")),
forOccurrence(occurrence("foo", parse("f{g}"), parse("u"))).toList())
assertEquals(
listOf(byTag("select_fg_x")),
forOccurrence(occurrence("foo", parse("f{Z}"), parse("u"))).toList())
assertEquals(
listOf(byTag("select_abX_x"), byTag("select_aYcde_x")),
forOccurrence(occurrence("foo", parse("a{b c{d Z}}"), parse("u"))).toList())
assertEquals(
listOf(byTag("select_aYcdd_fg")),
forOccurrence(occurrence("foo", parse("a{d c{d d}}"), parse("f{g}"))).toList())
}
}
@Test
fun matchTermArguments() {
programWithRules(
rule("select_abcde",
headKept(
constraint("foo", parse("a{b{h} c{d e}}"))
),
body(
constraint("bar")
)),
rule("select_abcdf",
headKept(
constraint("foo", parse("a{b{h} c{d f}}"))
),
body(
constraint("bar")
)),
rule("select_agcde",
headKept(
constraint("foo", parse("a{g{h} c{d e}}"))
),
body(
constraint("bar")
))
).indices().first.run {
assertEquals(
listOf(byTag("select_abcde")),
forOccurrence(occurrence("foo", parse("a{b{h} c{d e}}"))).toList())
assertEquals(
listOf(byTag("select_abcdf")),
forOccurrence(occurrence("foo", parse("a{b{h} c{d f}}"))).toList())
assertEquals(
listOf(byTag("select_agcde")),
forOccurrence(occurrence("foo", parse("a{g{h} c{d e}}"))).toList())
}
}
@Test
fun matchConstraintsWrongOrder() {
val (b, c) = metaLogical<String>("b", "c")
val (x, y) = metaLogical<String>("x", "y")
val program = programWithRules(
rule("foo2",
headKept(
constraint("foo", b, parse("a{b}")),
constraint("foo", c, parse("a{c}"))
),
body(
constraint("done")
)
)
)
program.indices(occurrence("foo", x, parse("a{c}"))).run {
Matcher(first).matching(occurrence("foo", y, parse("a{b}")), second).let { matches ->
assertEquals("foo2", matches.single().rule.tag())
}
}
program.indices(occurrence("foo", x, parse("a{b}"))).run {
Matcher(first).matching(occurrence("foo", y, parse("a{c}")), second).let { matches ->
assertEquals("foo2", matches.single().rule.tag())
}
}
}
private fun Builder.indices(vararg occurrence: ConstraintOccurrence): Pair<RuleIndex, OccurrenceIndex> {
val stored = occurrence.toList()
val aux = object : OccurrenceIndex {
override fun forSymbol(symbol: ConstraintSymbol): Iterable<ConstraintOccurrence> =
stored.filter { co -> co.constraint().symbol() == symbol }
override fun forLogical(logical: Logical<*>): Iterable<ConstraintOccurrence> =
stored.filter { co ->
co.arguments().any { it is Logical<*> && it.isBound && it.findRoot() == logical.findRoot() }
}
override fun forTerm(term: Term): Iterable<ConstraintOccurrence> =
stored.filter { co ->
co.arguments().any { it is Term && Unification.unify(it, term).isSuccessful }
}
override fun forTermAndConstraint(term: Term, cst: Constraint): Iterable<ConstraintOccurrence> =
stored.filter { co ->
co.constraint().symbol() == cst.symbol() && co.arguments().any { it is Term && Unification.unify(it, term).isSuccessful }
}
override fun forValue(value: Any): Iterable<ConstraintOccurrence> =
stored.filter { co -> co.arguments().contains(value) }
}
return RuleIndex(handlers).to(aux)
}
private fun Match.allOccurrences() = (keptOccurrences + discardedOccurrences)
private fun Matcher.matching(activeOcc: ConstraintOccurrence, aux: OccurrenceIndex) = this.matches(activeOcc, aux).filter { m -> m.successful }
}