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(