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 6f97a06c..0d5728d0 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt @@ -16,6 +16,8 @@ package jetbrains.mps.logic.reactor.core +import gnu.trove.TIntObjectHashMap +import gnu.trove.TObjectIntHashMap import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.logical.Logical import jetbrains.mps.logic.reactor.logical.MetaLogical @@ -33,6 +35,17 @@ import kotlin.collections.HashMap */ class RuleIndex(ruleLists: Iterable) : Iterable, RuleLookup { + private class IndexedRule { + + constructor(idx: Int, rule: Rule) { + this.idx = idx + this.rule = rule + } + + var idx: Int + val rule: Rule + } + // Terminology: // ruleBit - rule's index in the rules list // headPos - constraint's position in the rule's head (all slots together) @@ -42,21 +55,19 @@ class RuleIndex(ruleLists: Iterable) : Iterable, RuleLookup { private val tag2rule = LinkedHashMap() - // TODO replace with a trie? - private val segmentPath2ruleBits = HashMap, BitSet>() + private val tag2bit = TObjectIntHashMap() - // rule's index is rule's position in this list - private val rulesList = ArrayList() + var nextBit: Int = 0 - // every rule has a slot mask - // invariant: rulesList.size == slotMasksList.size - private val slotMasksList = ArrayList() + private val bit2ruleAndMask = TIntObjectHashMap>() + + private val allRules = LinkedList() /** the bit set that is going to be reused for all calls to [select] */ - private val ruleBits = BitSet(rulesList.size) + private val ruleBits = BitSet(allRules.size) /** the bit set to be used for temporary processing within [select]*/ - private val andRuleIndices = BitSet(rulesList.size) + private val andRuleIndices = BitSet(allRules.size) init { buildIndex(ruleLists) @@ -64,31 +75,18 @@ class RuleIndex(ruleLists: Iterable) : Iterable, RuleLookup { 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 (ruleBits, slotMasks) = 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 result = ArrayList() val it = ruleBits.allSetBits() - while (it.hasNext()) result.add(rulesList[it.next()]) - return result + while (it.hasNext()) bit2ruleAndMask[it.next()]?.let{ + result.add(it.first) + } + result.sortBy { it.idx } + return result.map { it.rule } } /** @@ -96,56 +94,91 @@ class RuleIndex(ruleLists: Iterable) : Iterable, RuleLookup { */ fun forOccurrenceWithMask(occ: ConstraintOccurrence): Iterable> { val (ruleBits, argSlotMasks) = 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 result = ArrayList>() val it = ruleBits.allSetBits() while (it.hasNext()) { val ruleBit = it.next() - slotMasksList[ruleBit][occ]?.let { mask -> - val effMask = argSlotMasks[ruleBit]?.copyApply { and(mask) } ?: mask - result.add(rulesList[ruleBit] to effMask) + bit2ruleAndMask[ruleBit]?.let {(irule, slotMask) -> + slotMask[occ]?.let { mask -> + val effMask = argSlotMasks[ruleBit]?.copyApply { and(mask) } ?: mask + result.add(irule to effMask) + } } - -// slotMasksList[ruleBit][occ]?.let { mask -> result.add(rulesList[ruleBit] to mask) } } - return result + result.sortBy { it.first.idx } + return result.map { it.first.rule to it.second } } - override fun iterator(): Iterator = rulesList.iterator() + override fun iterator(): Iterator = allRules.map { it.rule }.iterator() + + fun updateIndex(ruleLists: Iterable, removedTags: Set) { + val allRulesIt = allRules.listIterator() + var nextIdx = 0 + ruleLists.flatMap { it.rules() }.forEach { rule -> + var skip = false + while (allRulesIt.hasNext()) { + val irule = allRulesIt.next() + if (removedTags.contains(irule.rule.uniqueTag())) { + removeRuleFromIndex(irule) + allRulesIt.remove() + + } else if (!irule.rule.uniqueTag().equals(rule.uniqueTag())) { + allRulesIt.previous() + break + + } else { + irule.idx = nextIdx + skip = true + break + } + } + if (!skip) { + IndexedRule(nextIdx, rule).also { + addRuleToIndex(it) + allRulesIt.add(it) + } + } + nextIdx++ + } + while (allRulesIt.hasNext()) { + val irule = allRulesIt.next() + removeRuleFromIndex(irule) + allRulesIt.remove() + } + } private fun buildIndex(ruleLists: Iterable) { - var ruleBit = 0 - 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 ((headPos, cst) in head.withIndex()) { - symbol2index.getOrPut(cst.symbol()) { ArgumentRuleIndex(cst.symbol()) }.update(cst, ruleBit, headPos) - slotMask.update(cst, headPos) - } - - slotMasksList.add(slotMask) - - ruleBit += 1 - } + ruleLists.flatMap { it.rules() }.forEachIndexed { idx, rule -> + val irule = IndexedRule(idx, rule) + addRuleToIndex(irule) + allRules.add(irule) } } + private fun removeRuleFromIndex(irule: IndexedRule) { + val uniqueTag = irule.rule.uniqueTag() + tag2rule.remove(uniqueTag) + val bit = tag2bit.remove(uniqueTag) + bit2ruleAndMask.remove(bit) + } + + private fun addRuleToIndex(irule: IndexedRule) { + val uniqueTag = irule.rule.uniqueTag() + if (tag2rule.containsKey(uniqueTag)) throw IllegalStateException("duplicate rule tag $uniqueTag") + val head = irule.rule.headKept() + irule.rule.headReplaced() + val slotMask = SlotMask() + for ((headPos, cst) in head.withIndex()) { + symbol2index.getOrPut(cst.symbol()) { ArgumentRuleIndex(cst.symbol()) }.update(cst, nextBit, headPos) + slotMask.update(cst, headPos) + } + + tag2rule[uniqueTag] = irule.rule + tag2bit.put(uniqueTag, nextBit) + bit2ruleAndMask.put(nextBit, irule to slotMask) + + nextBit += 1 + } + /** * Represents a mask associated with a single rule. * The mask tells whether or not a particular constraint occurrence can match diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index 58a3d973..40de2e8f 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -131,14 +131,6 @@ fun rule(tag: String, vararg component: RuleBuilder.() -> Unit): () -> Rule = { rb.toRule() } -fun rule(tag: String, segmentPath: List, vararg component: RuleBuilder.() -> Unit): () -> Rule = { - val rb = RuleBuilder(tag, segmentPath) - for (cmp in component) { - rb.cmp() - } - rb.toRule() -} - fun headKept(vararg content: ConjBuilder.() -> Unit): RuleBuilder.() -> Unit = { appendHeadKept(* buildConjunction(Constraint::class.java, content).toArray()) } diff --git a/reactor/Test/test/TestProgram.kt b/reactor/Test/test/TestProgram.kt index ed848bb2..13e64f47 100644 --- a/reactor/Test/test/TestProgram.kt +++ b/reactor/Test/test/TestProgram.kt @@ -67,53 +67,6 @@ 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 index 80626330..55ea4d1d 100644 --- a/reactor/Test/test/TestRuleIndex.kt +++ b/reactor/Test/test/TestRuleIndex.kt @@ -15,16 +15,8 @@ */ 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 jetbrains.mps.unification.test.MockTermsParser.parseTerm import org.junit.Test -import java.util.* /* * Copyright 2014-2019 JetBrains s.r.o. @@ -49,74 +41,189 @@ import java.util.* class TestRuleIndex { @Test - fun testProgramWithSegments() { + fun testUpdateRuleIndexReplaceSingle() { 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}")) - )) + rule("rule0", + headReplaced(constraint("foo")), body()) + + ) to programWithRules( + rule("rule1", + headReplaced(constraint("bar")), body()) + )) { - val ruleIndex = RuleIndex(rulesLists) - // no segment - with (ruleIndex.forOccurrence(occurrence("main"))) { - map { it.tag() }.toSet() shouldBe setOf("rule0") + val ruleIndex = RuleIndex(first.rulesLists) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule0") } - with (ruleIndex.forOccurrence(occurrence("foo", parseTerm("f{g h}")))) { - map { it.tag() }.toSet() shouldBe setOf("rule1", "rule2") + with (ruleIndex.forOccurrence(occurrence("bar"))) { + iterator().hasNext() shouldBe false } - // segment1 - with (ruleIndex.forOccurrence(taggedOccurrence("rule0", "foo", parseTerm("f{g h}")))) { - map { it.tag() }.toSet() shouldBe setOf("rule1", "rule2") + ruleIndex.updateIndex(second.rulesLists, setOf("rule0")) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + iterator().hasNext() shouldBe false } - 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") + with (ruleIndex.forOccurrence(occurrence("bar"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1") } } } + @Test + fun testUpdateRuleIndexAddAtEnds() { + with(programWithRules( + rule("rule0", + headReplaced(constraint("foo")), body()) + + ) to programWithRules( + rule("rule1", + headReplaced(constraint("foo"), constraint("bar")), body()), + rule("rule0", + headReplaced(constraint("foo")), body()), + rule("rule2", + headReplaced(constraint("bar")), body()) + + )) + { + val ruleIndex = RuleIndex(first.rulesLists) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule0") + } + + ruleIndex.updateIndex(second.rulesLists, setOf()) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1","rule0") + } + with (ruleIndex.forOccurrence(occurrence("bar"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1","rule2") + } + } + } + + @Test + fun testUpdateRuleIndexRemoveInTheMiddle() { + with(programWithRules( + rule("rule1", + headReplaced(constraint("foo"), constraint("bar")), body()), + rule("rule0", + headReplaced(constraint("foo")), body()), + rule("rule2", + headReplaced(constraint("bar")), body()) + + ) to programWithRules( + rule("rule1", + headReplaced(constraint("foo"), constraint("bar")), body()), + rule("rule2", + headReplaced(constraint("bar")), body()) + + )) + { + val ruleIndex = RuleIndex(first.rulesLists) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1","rule0") + } + with (ruleIndex.forOccurrence(occurrence("bar"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1","rule2") + } + + ruleIndex.updateIndex(second.rulesLists, setOf("rule0")) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1") + } + } + } + + @Test + fun testUpdateRuleIndexRemoveFromEnds() { + with(programWithRules( + rule("rule1", + headReplaced(constraint("foo"), constraint("bar")), body()), + rule("rule0", + headReplaced(constraint("foo")), body()), + rule("rule2", + headReplaced(constraint("bar")), body()) + + ) to programWithRules( + rule("rule0", + headReplaced(constraint("foo")), body()) + + )) + { + val ruleIndex = RuleIndex(first.rulesLists) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1","rule0") + } + with (ruleIndex.forOccurrence(occurrence("bar"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1","rule2") + } + + ruleIndex.updateIndex(second.rulesLists, setOf("rule1", "rule2")) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule0") + } + with (ruleIndex.forOccurrence(occurrence("bar"))) { + iterator().hasNext() shouldBe false + } + } + } + + @Test + fun testUpdateRuleIndex() { + with(programWithRules( + rule("rule0", + headReplaced(constraint("foo")), body()), + rule("rule3", + headReplaced(constraint("foo"), constraint("bar")), body()), + rule("rule1", + headReplaced(constraint("baz")), body()), + rule("rule4", + headReplaced(constraint("qux")), body()) + + ) to programWithRules( + rule("rule5", + headReplaced(constraint("foo"), constraint("foo")), body()), + rule("rule0", + headReplaced(constraint("foo")), body()), + rule("rule2", + headReplaced(constraint("bar")), body()), + rule("rule4", + headReplaced(constraint("qux")), body()), + rule("rule6", + headReplaced(constraint("qux")), body()) + + )) + { + val ruleIndex = RuleIndex(first.rulesLists) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule0", "rule3") + } + with (ruleIndex.forOccurrence(occurrence("bar"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule3") + } + with (ruleIndex.forOccurrence(occurrence("baz"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule1") + } + with (ruleIndex.forOccurrence(occurrence("qux"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule4") + } + + ruleIndex.updateIndex(second.rulesLists, setOf("rule1", "rule3")) + with (ruleIndex.forOccurrence(occurrence("foo"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule5", "rule0") + } + with (ruleIndex.forOccurrence(occurrence("bar"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule2") + } + with (ruleIndex.forOccurrence(occurrence("baz"))) { + iterator().hasNext() shouldBe false + } + with (ruleIndex.forOccurrence(occurrence("qux"))) { + map { it.uniqueTag() }.toList() shouldBe listOf("rule4", "rule6") + } + } + + } + } \ No newline at end of file diff --git a/reactor/Test/test/TestRuleMatcher.kt b/reactor/Test/test/TestRuleMatcher.kt index d340e0fb..d74cfb71 100644 --- a/reactor/Test/test/TestRuleMatcher.kt +++ b/reactor/Test/test/TestRuleMatcher.kt @@ -927,82 +927,6 @@ 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(