From 7d308cb0e305c92eccb58cc550b67e56158aee17 Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Mon, 23 Sep 2019 16:09:45 +0200 Subject: [PATCH] Experimental feature: Rules segmentation. The rules index is extended to observe the segment path a rule belongs to. An occurrence is bound to the rule that activated it and thus has an associated segment path. Rules to be picked for activation can be selected on the basis of matched segment path. --- .../mps/logic/reactor/core/Occurrence.kt | 22 +-- .../mps/logic/reactor/core/RuleIndex.kt | 144 ++++++++++++------ .../reactor/core/internal/ControllerImpl.kt | 19 ++- .../evaluation/ConstraintOccurrence.java | 7 +- .../mps/logic/reactor/program/Rule.java | 14 ++ reactor/Test/src/program/MockProgram.kt | 9 +- reactor/Test/test/RulesHelper.kt | 30 +++- reactor/Test/test/TestProgram.kt | 47 ++++++ reactor/Test/test/TestRuleIndex.kt | 122 +++++++++++++++ reactor/Test/test/TestRuleMatcher.kt | 76 +++++++++ 10 files changed, 411 insertions(+), 79 deletions(-) create mode 100644 reactor/Test/test/TestRuleIndex.kt diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt index 704a0f23..b4515fc4 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt @@ -65,7 +65,8 @@ class Occurrence (controller: Controller, val constraint: Constraint, val logicalContext: LogicalContext, val arguments: List<*>, - val justifications: Justs): + val justifications: Justs, + val ruleUniqueTag: Any? = null): ConstraintOccurrence { @@ -85,6 +86,8 @@ class Occurrence (controller: Controller, override fun logicalContext(): LogicalContext = logicalContext + override fun ruleUniqueTag(): Any? = ruleUniqueTag + override fun justifications(): Justs = justifications fun terminate(controller: Controller) { @@ -114,18 +117,7 @@ class Occurrence (controller: Controller, fun Constraint.occurrence(controller: Controller, arguments: List<*>, justifications: Justs, - logicalContext: LogicalContext): Occurrence = - Occurrence(controller, this, logicalContext, arguments, justifications) + logicalContext: LogicalContext, + ruleUniqueTag: Any? = null): Occurrence = + Occurrence(controller, this, logicalContext, arguments, justifications, ruleUniqueTag) -fun Constraint.occurrence(controller: Controller, - arguments: List<*>, - justifications: Justs): Occurrence = - Occurrence(controller, this, noLogicalContext, arguments, justifications) - -fun Constraint.occurrence(controller: Controller, - arguments: List<*>): Occurrence = - Occurrence(controller, this, noLogicalContext, arguments, justsOf()) - -private val noLogicalContext: LogicalContext = object: LogicalContext { - override fun variable(metaLogical: MetaLogical): Logical? = null -} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt index 4d8df0c3..0970e615 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt @@ -31,12 +31,20 @@ import kotlin.collections.HashMap * * @author Fedor Isakov */ -class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { +class RuleIndex(ruleLists: Iterable) : Iterable, RuleLookup { + + // Terminology: + // ruleBit - rule's index in the rules list + // headPos - constraint's position in the rule's head (all slots together) + // argIdx - constraint's argument index private val symbol2index = HashMap() private val tag2rule = LinkedHashMap() + // TODO replace with a trie? + private val segmentPath2ruleBits = HashMap, BitSet>() + // rule's index is rule's position in this list private val rulesList = ArrayList() @@ -45,24 +53,40 @@ class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { private val slotMasksList = ArrayList() /** the bit set that is going to be reused for all calls to [select] */ - private val ruleIndices = BitSet(rulesList.size) + private val ruleBits = BitSet(rulesList.size) /** the bit set to be used for temporary processing within [select]*/ private val andRuleIndices = BitSet(rulesList.size) init { - buildIndex(handlers) + buildIndex(ruleLists) } override fun lookupRuleByTag(tag: Any): Rule? = tag2rule[tag] + private fun List.isPrefixOf(that: List): Boolean { + val thisIt = this@isPrefixOf.iterator() + val thatIt = that.iterator() + while(thisIt.hasNext() && thatIt.hasNext()) { + if (thisIt.next() != thatIt.next()) return false + } + return thisIt.hasNext() == thatIt.hasNext() || thatIt.hasNext() + } + /** * Returns instances of [Rule] that can potentially match the specified [ConstraintOccurrence]. */ fun forOccurrence(occ: ConstraintOccurrence): Iterable { - val ruleIndices = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList() + val ruleBits = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList() + tag2rule[occ.ruleUniqueTag()]?.segmentPath()?.let { + andRuleIndices.clear() + for ((path, bits) in segmentPath2ruleBits.entries) { + if (!it.isPrefixOf(path)) andRuleIndices.or(bits) + } + ruleBits.andNot(andRuleIndices) + } val result = ArrayList() - val it = ruleIndices.allSetBits() + val it = ruleBits.allSetBits() while (it.hasNext()) result.add(rulesList[it.next()]) return result } @@ -72,6 +96,13 @@ class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { */ fun forOccurrenceWithMask(occ: ConstraintOccurrence): Iterable> { val ruleBits = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList() + tag2rule[occ.ruleUniqueTag()]?.segmentPath()?.let { + andRuleIndices.clear() + for ((path, bits) in segmentPath2ruleBits.entries) { + if (!it.isPrefixOf(path)) andRuleIndices.or(bits) + } + ruleBits.andNot(andRuleIndices) + } val result = ArrayList>() val it = ruleBits.allSetBits() while (it.hasNext()) { @@ -83,19 +114,24 @@ class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { override fun iterator(): Iterator = rulesList.iterator() - private fun buildIndex(handlers: Iterable) { + private fun buildIndex(ruleLists: Iterable) { var ruleBit = 0 - for (h in handlers) { + for (h in ruleLists) { for (rule in h.rules()) { if (tag2rule.containsKey(rule.uniqueTag())) throw IllegalStateException("duplicate rule tag ${rule.uniqueTag()}") tag2rule[rule.uniqueTag()] = rule + rule.segmentPath()?.let { segmentPath -> + if (segmentPath.isNotEmpty()) { + segmentPath2ruleBits.getOrPut(segmentPath) { BitSet() }.set(ruleBit) + } + } rulesList.add(rule) val head = rule.headKept() + rule.headReplaced() val slotMask = SlotMask() - for ((pos, cst) in head.withIndex()) { + for ((headPos, cst) in head.withIndex()) { symbol2index.getOrPut(cst.symbol()) { ArgumentRuleIndex(cst.symbol()) }.update(cst, ruleBit) - slotMask.update(cst, pos) + slotMask.update(cst, headPos) } slotMasksList.add(slotMask) @@ -126,9 +162,9 @@ class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { } /** - * Represents a list of rules with at least one constraint matching the 'symbol'. - * The list is indexed by all values extracted from the constraint arguments. - * The list can be selected based on the argument(s) of a constraint occurrence. + * Represents an index over rules with at least one constraint matching the 'symbol'. + * The index is over all values extracted from the constraint's arguments. + * The index is selectable by a constraint occurrence. */ inner class ArgumentRuleIndex(val symbol: ConstraintSymbol) { @@ -138,6 +174,8 @@ class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { val wildcardSelectors = ArrayList() + val noArgSelector = BitSet() + init { for (idx in 1..symbol.arity()) { anySelectors.add(HashMap()) @@ -147,19 +185,24 @@ class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { } fun update(cst: Constraint, ruleBit: Int) { - for ((idx, arg) in cst.arguments().withIndex()) { - val value2indices = anySelectors[idx] - when (arg) { - is MetaLogical<*> -> - // all values should be accepted by a meta logical - wildcardSelectors[idx].set(ruleBit) - is Term -> - termSelectors.set(idx, termSelectors[idx].put(arg, ruleBit)) - is Any -> - value2indices.getOrPut(arg) { BitSet() }.apply { set(ruleBit) } - else -> - throw NullPointerException() // never happens + if (cst.arguments().isEmpty()) { + noArgSelector.set(ruleBit) + + } else { + for ((argIdx, arg) in cst.arguments().withIndex()) { + val value2indices = anySelectors[argIdx] + when (arg) { + is MetaLogical<*> -> + // all values should be accepted by a meta logical + wildcardSelectors[argIdx].set(ruleBit) + is Term -> + termSelectors.set(argIdx, termSelectors[argIdx].put(arg, ruleBit)) + is Any -> + value2indices.getOrPut(arg) { BitSet() }.apply { set(ruleBit) } + else -> + throw NullPointerException() // never happens + } } } } @@ -172,34 +215,39 @@ class RuleIndex(handlers: Iterable) : Iterable, RuleLookup { // initially select all rules val upToBit = rulesList.size - ruleIndices.set(0, upToBit) + ruleBits.set(0, upToBit) - for ((idx, arg) in occ.arguments().withIndex()) { - val value2indices = anySelectors[idx] - val termIndices = termSelectors[idx] - val wildcardIndices = wildcardSelectors[idx] - if (arg is Logical<*> && !arg.isBound) { - // ALL values must be selected for a free logical - continue + if (occ.arguments().isEmpty()) { + ruleBits.and(noArgSelector) + + } else { + for ((argIdx, arg) in occ.arguments().withIndex()) { + val value2indices = anySelectors[argIdx] + val termIndices = termSelectors[argIdx] + val wildcardIndices = wildcardSelectors[argIdx] + if (arg is Logical<*> && !arg.isBound) { + // ALL values must be selected for a free logical + continue + } + + andRuleIndices.clear(0, upToBit) + andRuleIndices.or(wildcardIndices) + + val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg + when (argVal) { + is Term -> + termIndices.lookupValues(argVal).forEach { andRuleIndices.set(it) } + is Any -> + // ensure only rules with either matching values or wildcard arguments get selected + if (value2indices.containsKey(argVal)) andRuleIndices.or(value2indices[argVal]) + } + ruleBits.and(andRuleIndices) + + if (ruleBits.isEmpty) break } - - andRuleIndices.clear(0, upToBit) - andRuleIndices.or(wildcardIndices) - - val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg - when (argVal) { - is Term -> - termIndices.lookupValues(argVal).forEach { andRuleIndices.set(it) } - is Any -> - // ensure only rules with either matching values or wildcard arguments get selected - if (value2indices.containsKey(argVal)) andRuleIndices.or(value2indices[argVal]) - } - ruleIndices.and(andRuleIndices) - - if (ruleIndices.isEmpty) break } - return ruleIndices + return ruleBits } } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt index 0d681dd6..1f46c67f 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt @@ -42,7 +42,9 @@ internal class ControllerImpl ( /** For tests only */ override fun evaluate(occ: Occurrence): StoreView { // create the internal occurrence - val active = occ.constraint().occurrence(this, occ.arguments(), occ.justifications()) + val active = occ.constraint().occurrence(this, occ.arguments(), occ.justifications(), object: LogicalContext { + override fun variable(metaLogical: MetaLogical): Logical? = null + }) val status = state.processActivated(this, active, NORMAL()) if (status is FAILED) { @@ -65,7 +67,7 @@ internal class ControllerImpl ( fun activate(constraint: Constraint) : FeedbackStatus { // FIXME noLogicalContext - val context = Context(NORMAL(), noLogicalContext, trace) + val context = Context(NORMAL(), noLogicalContext, null, trace) // fixme: is it valid to always provide current justifications? // while this method is used only in one place at program kick-off, yes, it's initial justs provided. @@ -99,7 +101,7 @@ internal class ControllerImpl ( .then { processGuard(match, it) } private fun checkMatchPreconditions(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus { - val context = Context(inStatus, match.logicalContext(), trace) + val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace) // invoke matched pattern predicates for (prd in match.patternPredicates()) { @@ -111,7 +113,7 @@ internal class ControllerImpl ( } private fun processGuard(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus { - val context = Context(inStatus, match.logicalContext(), trace) + val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace) // check guard for (gprd in match.rule().guard()) { @@ -122,7 +124,7 @@ internal class ControllerImpl ( } override fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus { - val context = Context(inStatus, match.logicalContext(), trace) + val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace) val altIt = match.rule().bodyAlternation().iterator() while (altIt.hasNext()) { @@ -206,7 +208,7 @@ internal class ControllerImpl ( val args = supervisor.instantiateArguments(constraint.arguments(), context.logicalContext, context) return context.eval { status -> - state.processActivated(this, constraint.occurrence(this, args, justsCopy(justs), context.logicalContext), status) + state.processActivated(this, constraint.occurrence(this, args, justsCopy(justs), context.logicalContext, context.ruleUniqueTag), status) } } @@ -249,8 +251,9 @@ internal class ControllerImpl ( inner private class Context(inStatus: FeedbackStatus, - val logicalContext: LogicalContext, - val trace: EvaluationTrace = EvaluationTrace.NULL) : InvocationContext + val logicalContext: LogicalContext, + val ruleUniqueTag: Any? = null, + val trace: EvaluationTrace = EvaluationTrace.NULL) : InvocationContext { private var status = inStatus diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java index ecf3458b..4a19ce4a 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java @@ -38,7 +38,12 @@ public interface ConstraintOccurrence { LogicalContext logicalContext(); + default Object ruleUniqueTag() { + return null; + } + + default @NotNull - default TIntSet justifications() { return new TIntHashSet(); } + TIntSet justifications() { return new TIntHashSet(); } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/program/Rule.java b/reactor/Core/src/jetbrains/mps/logic/reactor/program/Rule.java index af50b3f2..95dbc4ba 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/program/Rule.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/program/Rule.java @@ -17,6 +17,9 @@ package jetbrains.mps.logic.reactor.program; +import java.util.Collections; +import java.util.List; + /** * A constraint rule description. * @@ -26,6 +29,17 @@ public abstract class Rule { public abstract Rule.Kind kind(); + /** + * A list of objects identifying the segment this rule belongs to. An empty segment path signifies the root segment. + * An occurrence produced from the root segment can be processed by any rule in the program. + * An occurrence produced from segment identified by a path P can be processed by a rule from any segment that + * has P as the path prefix. + */ + // TODO Make abstract + public List segmentPath() { + return Collections.emptyList(); + } + /** * A tag uniquely identifies the rule. */ diff --git a/reactor/Test/src/program/MockProgram.kt b/reactor/Test/src/program/MockProgram.kt index 39a9cf5d..7525ea21 100644 --- a/reactor/Test/src/program/MockProgram.kt +++ b/reactor/Test/src/program/MockProgram.kt @@ -41,7 +41,7 @@ class HandlerBuilder(val name: String) { fun toHandler(): RulesList = MockHandler(name, rules.values.toList()) } -class RuleBuilder(val tag: String) { +class RuleBuilder(val tag: String, val segmentPath: List) { val kept = ArrayList() val replaced = ArrayList() val guard = ArrayList() @@ -60,7 +60,7 @@ class RuleBuilder(val tag: String) { if (alt || body.isEmpty()) body.add(ArrayList()) body.last().addAll(andItem) } - fun toRule(): Rule = MockRule(tag, kept, replaced, guard, body) + fun toRule(): Rule = MockRule(tag, segmentPath, kept, replaced, guard, body) } class MockHandler( @@ -74,13 +74,16 @@ class MockHandler( class MockRule( val tag: String, + val segmentPath: List, val kept: Collection, val replaced: Collection, val guard: Collection, val body: Collection>) : Rule() { override fun kind(): Kind = TODO() - + + override fun segmentPath(): List = segmentPath + override fun uniqueTag() = tag override fun tag(): String = tag diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index b4f0038f..73feb077 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -3,6 +3,8 @@ import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.logical.MetaLogical import jetbrains.mps.logic.reactor.program.* import program.MockConstraint import solver.TestEqPredicate @@ -122,7 +124,15 @@ fun Builder.insertRulesWhen(at: (Rule) -> Boolean, vararg ruleBuilders: () -> Ru updateBuilder(this, arrayOf(insertRulesInHandlerWhen(at, "test", rulesLists.first(), * ruleBuilders))) fun rule(tag: String, vararg component: RuleBuilder.() -> Unit): () -> Rule = { - val rb = RuleBuilder(tag) + val rb = RuleBuilder(tag, emptyList()) + for (cmp in component) { + rb.cmp() + } + rb.toRule() +} + +fun rule(tag: String, segmentPath: List, vararg component: RuleBuilder.() -> Unit): () -> Rule = { + val rb = RuleBuilder(tag, segmentPath) for (cmp in component) { rb.cmp() } @@ -163,11 +173,19 @@ fun equals(left: Any, right: Any): ConjBuilder.() -> Unit = { } fun occurrence(id: String, vararg args: Any): Occurrence = - MockConstraint(ConstraintSymbol.symbol(id, args.size)).occurrence(MockController(), listOf(* args)) + MockConstraint(ConstraintSymbol.symbol(id, args.size)) + .occurrence(MockController(), listOf(* args), justsOf(), noLogicalContext) + +fun taggedOccurrence(ruleUniqueTag: Any, id: String, vararg args: Any): Occurrence = + MockConstraint(ConstraintSymbol.symbol(id, args.size)) + .occurrence(MockController(), listOf(* args), justsOf(), noLogicalContext, ruleUniqueTag) fun justifiedOccurrence(id: String, justs: Justs, vararg args: Any): Occurrence = - MockConstraint(ConstraintSymbol.symbol(id, args.size), true).occurrence(MockController(), listOf(* args), justs) -fun justifiedOccurrence(id: String, justs: Collection, vararg args: Any): Occurrence = justifiedOccurrence(id, justsFromCollection(justs), * args) + MockConstraint(ConstraintSymbol.symbol(id, args.size), true) + .occurrence(MockController(), listOf(* args), justs, noLogicalContext) + +fun justifiedOccurrence(id: String, justs: Collection, vararg args: Any): Occurrence = + justifiedOccurrence(id, justsFromCollection(justs), * args) fun sym0(id: String): ConstraintSymbol = ConstraintSymbol(id, 0) @@ -178,6 +196,10 @@ fun sym1(id: String): ConstraintSymbol = fun sym2(id: String): ConstraintSymbol = ConstraintSymbol(id, 2) +private val noLogicalContext = object : LogicalContext { + override fun variable(metaLogical: MetaLogical): Logical? = null +} + class MockController : Controller { override fun ask(invocation: PredicateInvocation): Boolean { diff --git a/reactor/Test/test/TestProgram.kt b/reactor/Test/test/TestProgram.kt index 13e64f47..ed848bb2 100644 --- a/reactor/Test/test/TestProgram.kt +++ b/reactor/Test/test/TestProgram.kt @@ -67,6 +67,53 @@ class TestProgram { } } + @Test + fun segmented() { + programWithRules( + rule("main.foo", + headReplaced( + constraint("main") + ), + body( + constraint("foo") + )), + rule("foo.bar", listOf("segment1"), + headKept( + constraint("foo") + ), + body( + constraint("bar") + ) + ), + rule("bar.qux", listOf("segment1"), + headKept( + constraint("bar") + ), + body( + constraint("qux") + ) + ), + rule("bar.dux", listOf("segment2"), + headKept( + constraint("bar") + ), + body( + constraint("dux") + ) + ), + rule("bar.doh", + headKept( + constraint("bar") + ), + body( + constraint("doh") + ) + ) + ).session("segmented").run { + constraintSymbols().map { it.id() }.toSet() shouldBe setOf("foo", "bar", "qux", "doh") + } + } + @Test fun logicalValue() { val (X, Y, Z) = metaLogical("X", "Y", "Z") diff --git a/reactor/Test/test/TestRuleIndex.kt b/reactor/Test/test/TestRuleIndex.kt new file mode 100644 index 00000000..80626330 --- /dev/null +++ b/reactor/Test/test/TestRuleIndex.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2014-2019 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. + */ + +import jetbrains.mps.logic.reactor.core.RuleIndex +import jetbrains.mps.logic.reactor.core.internal.logical +import jetbrains.mps.logic.reactor.util.bitSet +import jetbrains.mps.unification.Term +import jetbrains.mps.unification.test.MockTerm +import jetbrains.mps.unification.test.MockTerm.* +import jetbrains.mps.unification.test.MockTermsParser +import jetbrains.mps.unification.test.MockTermsParser.* +import org.jetbrains.kotlin.js.parser.parse +import org.junit.Test +import java.util.* + +/* + * Copyright 2014-2019 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 TestRuleIndex { + + @Test + fun testProgramWithSegments() { + with(programWithRules( + rule("rule0", emptyList(), + headReplaced( + constraint("main") + ), + body( + constraint("bar") + )), + rule("rule1", listOf("segment1"), + headReplaced( + constraint("foo", parseTerm("f{g h}")) + ), + body( + constraint("qux") + )), + rule("rule2", listOf("segment2"), + headReplaced( + constraint("foo", parseTerm("f{g h}")) + ), + body( + constraint("blah") + )), + rule("rule3", listOf("segment1"), + headReplaced( + constraint("blin") + ), + body( + constraint("foo", parseTerm("f{g h}")) + )), + rule("rule4", listOf("segment2"), + headReplaced( + constraint("fooblin") + ), + body( + constraint("foo", parseTerm("f{g h}")) + )) + )) + { + val ruleIndex = RuleIndex(rulesLists) + // no segment + with (ruleIndex.forOccurrence(occurrence("main"))) { + map { it.tag() }.toSet() shouldBe setOf("rule0") + } + with (ruleIndex.forOccurrence(occurrence("foo", parseTerm("f{g h}")))) { + map { it.tag() }.toSet() shouldBe setOf("rule1", "rule2") + } + + // segment1 + with (ruleIndex.forOccurrence(taggedOccurrence("rule0", "foo", parseTerm("f{g h}")))) { + map { it.tag() }.toSet() shouldBe setOf("rule1", "rule2") + } + with (ruleIndex.forOccurrence(taggedOccurrence("rule0", "main"))) { + map { it.tag() }.toSet() shouldBe setOf("rule0") + } + with (ruleIndex.forOccurrence(taggedOccurrence("rule3", "foo", parseTerm("f{g h}")))) { + map { it.tag() }.toSet() shouldBe setOf("rule1") + } + with (ruleIndex.forOccurrence(taggedOccurrence("rule3", "main"))) { + map { it.tag() }.toSet() shouldBe setOf("rule0") + } + + // segment2 + with (ruleIndex.forOccurrence(taggedOccurrence("rule4", "foo", parseTerm("f{g h}")))) { + map { it.tag() }.toSet() shouldBe setOf("rule2") + } + } + + } + +} \ No newline at end of file diff --git a/reactor/Test/test/TestRuleMatcher.kt b/reactor/Test/test/TestRuleMatcher.kt index d60810bc..5138f441 100644 --- a/reactor/Test/test/TestRuleMatcher.kt +++ b/reactor/Test/test/TestRuleMatcher.kt @@ -676,6 +676,82 @@ class TestRuleMatcher { } } + + @Test + fun testDispatcherWithSegment1() { + with(programWithRules( + rule("rule0", emptyList(), + headReplaced( + constraint("foo") + ), + body( + constraint("bar") + )), + rule("rule1", listOf("segment1"), + headReplaced( + constraint("fooblin") + ), + body( + constraint("doh") + )) + )) + { + with(Dispatcher(RuleIndex(rulesLists)).front()) { + expand(occurrence("foo")) }.apply { + matches().count() shouldBe 1 }.run { + } + + with(Dispatcher(RuleIndex(rulesLists)).front()) { + expand(taggedOccurrence("rule1", "foo")) }.apply { + matches().count() shouldBe 1 }.run { + } + } + } + + @Test + fun testExpandWithSegment2() { + with(programWithRules( + rule("rule1", listOf("segment1"), + headReplaced( + constraint("foo") + ), + body( + constraint("bar") + )), + rule("rule2", listOf("segment1"), + headReplaced( + constraint("fooblin") + ), + body( + constraint("doh") + )), + rule("rule3", listOf("segment2"), + headReplaced( + constraint("fooblin") + ), + body( + constraint("doh") + )) + )) + { + with(Dispatcher(RuleIndex(rulesLists)).front()) { + expand(occurrence("foo")) }.apply { + matches().count() shouldBe 1 }.run { + } + with(Dispatcher(RuleIndex(rulesLists)).front()) { + expand(taggedOccurrence("rule2", "foo")) }.apply { + matches().count() shouldBe 1 }.run { + + } + with(Dispatcher(RuleIndex(rulesLists)).front()) { + expand(taggedOccurrence("rule3", "foo")) }.apply { + matches().count() shouldBe 0 }.run { + } + } + } + + + @Test fun testDispatcherIncremental() { with(programWithRules(