diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt index 2de3a1b2..b51d2e68 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt @@ -66,9 +66,16 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp private data class ExecPos(val pos: MatchJournal.Pos, val activeOcc: Occurrence) + private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int) + + // fixme: wtf, why idea's compiler complains??? override fun index(): MatchJournal.Index = journalIndex + // Invalidation includes several activities: + // - removing chunks (i.e. principal matches) corresponding to removed rules and chunks depending on them from journal + // - reactivating occurrences that led to invalidated matches + // - pruning invalidated occurrences and matches from Dispatcher's state fun invalidateByRules(ruleIds: Set) { val justificationRoots = mutableListOf() @@ -86,23 +93,25 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp // We removed the match, so need to reactivate all still valid occurrences from the head // by definition of Chunk and principal rule, all occurrences from the head are principal - // todo: check/assert it - val matchedOccs = chunk.match.allHeads() as Iterable + assert(matchedOccs.all { it.isPrincipal() }) + val (invalidatedOccs, validOccs) = matchedOccs.partition { occ -> occ.justifications().intersects(justificationRoots) } // todo: do need to reactivate only the main, matching~activating match? // (i.e. don't reactivate additional, inactive heads that only completed the match?) - execQueue.addAll(validOccs.map { occ -> - val chunkPos = journalIndex.activatingChunkOf(Id(occ)) - if (chunkPos != null) ExecPos(chunkPos, occ) else null - }.filterNotNull()) + execQueue.addAll(validOccs.mapNotNull { occ -> + journalIndex.activatingChunkOf(Id(occ))?.let { chunkPos -> + ExecPos(chunkPos, occ) + } + }) continue } + // Invalidating dependent chunks val toInvalidate = chunk.justifications.intersects(justificationRoots) if (toInvalidate) { // Remove the chunk from the journal @@ -123,8 +132,6 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp } } - private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int) - private fun canMatch(rule: Rule, occ: Occurrence): Boolean = (rule.headKept() + rule.headReplaced()).find { it.symbol() == occ.constraint.symbol() } != null @@ -220,7 +227,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp } fun snapshot(): SessionToken { - return SessionToken(view(), ruleOrdering.ruleTags(), dispatchingFront.state()) + return SessionToken(view(), ruleOrdering.ruleTags, dispatchingFront.state()) } /** diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleOrdering.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleOrdering.kt index 9960c62c..567aba0b 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleOrdering.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleOrdering.kt @@ -22,13 +22,13 @@ import jetbrains.mps.logic.reactor.program.Rule internal class RuleOrdering(order: Iterable): Comparator { private val ruleOrder: Map = HashMap().apply { - put(MatchJournalImpl.InitRuleMatch.rule().uniqueTag(), -1) + put(MatchJournalImpl.InitRuleMatch.rule().uniqueTag(), -1) // less than anything order.forEachIndexed { index, rule -> put(rule.uniqueTag(), index) } } - fun orderOf(rule: Rule): Int? = ruleOrder[rule.uniqueTag()] + val ruleTags: Set = order.map { it.uniqueTag() }.toHashSet() // NB: without initial rule - fun ruleTags(): Set = ruleOrder.keys + fun orderOf(rule: Rule): Int? = ruleOrder[rule.uniqueTag()] override fun compare(lhs: Rule, rhs: Rule): Int { val lhsRuleOrder = orderOf(lhs) diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index 8c191d88..75a2e00c 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -1,5 +1,3 @@ -import gnu.trove.set.TIntSet -import gnu.trove.set.hash.TIntHashSet import jetbrains.mps.logic.reactor.core.* import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation @@ -84,6 +82,22 @@ private fun insertRulesInHandler(at: Int, name: String, rulesList: RulesList, va hb.toHandler() } +private fun removeRulesByIndices(at: Iterable, name: String, rulesList: RulesList): () -> RulesList = { + val hb = HandlerBuilder(name) + rulesList.rules().forEachIndexed { index, rule -> + if (!at.contains(index)) hb.appendRule(rule) + } + hb.toHandler() +} + +private fun removeRulesByTags(tags: Iterable, name: String, rulesList: RulesList): () -> RulesList = { + val hb = HandlerBuilder(name) + rulesList.rules().forEach { rule -> + if (!tags.contains(rule.uniqueTag())) hb.appendRule(rule) + } + hb.toHandler() +} + private fun insertRulesInHandlerWhen(at: (Rule) -> Boolean, name: String, rulesList: RulesList, vararg ruleBlocks: () -> Rule): () -> RulesList = insertRulesInHandler(rulesList.rules().indexOfFirst(at), name, rulesList, * ruleBlocks) @@ -95,6 +109,12 @@ fun programWithRules(vararg ruleBuilders: () -> Rule): Builder = fun Builder.programWithRules(vararg ruleBuilders: () -> Rule): Builder = updateBuilder(this, arrayOf(updateHandler("test", rulesLists.first(), * ruleBuilders))) +fun Builder.removeRulesAt(at: Iterable): Builder = + updateBuilder(this, arrayOf(removeRulesByIndices(at, "test", rulesLists.first()))) + +fun Builder.removeRules(tags: Iterable): Builder = + updateBuilder(this, arrayOf(removeRulesByTags(tags, "test", rulesLists.first()))) + fun Builder.insertRulesAt(at: Int, vararg ruleBuilders: () -> Rule): Builder = updateBuilder(this, arrayOf(insertRulesInHandler(at, "test", rulesLists.first(), * ruleBuilders))) diff --git a/reactor/Test/test/TestIncrementalProgram.kt b/reactor/Test/test/TestIncrementalProgram.kt index a5fa07e5..6fc697bb 100644 --- a/reactor/Test/test/TestIncrementalProgram.kt +++ b/reactor/Test/test/TestIncrementalProgram.kt @@ -83,6 +83,8 @@ class TestIncrementalProgram { private fun Iterable.constraintSymbols() = this.map { it.constraint.symbol() } + private fun EvaluationResult.lastChunkSymbols() = this.lastChunk().activatedLog().constraintSymbols() + @Test fun addNewMatchAtTheEnd() { @@ -100,7 +102,7 @@ class TestIncrementalProgram { )) ).launch("addRule", progSpec) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("foo")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("foo")) + result.lastChunkSymbols() shouldBe listOf(sym0("foo")) }.also { (builder, evalRes) -> builder.programWithRules( @@ -114,7 +116,7 @@ class TestIncrementalProgram { ) ).relaunch("test1", progSpec, evalRes.token()) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("bar")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("bar")) + result.lastChunkSymbols() shouldBe listOf(sym0("bar")) } } } @@ -143,7 +145,7 @@ class TestIncrementalProgram { ) ).launch("addRule", progSpec) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("bar")) + result.lastChunkSymbols() shouldBe listOf(sym0("bar")) }.also { (builder, evalRes) -> builder.programWithRules( @@ -157,7 +159,7 @@ class TestIncrementalProgram { ) ).relaunch("test1", progSpec, evalRes.token()) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("baz")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("baz")) + result.lastChunkSymbols() shouldBe listOf(sym0("baz")) } } } @@ -199,7 +201,7 @@ class TestIncrementalProgram { ) ).relaunch("test1", progSpec, evalRes.token()) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("baz")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("baz")) + result.lastChunkSymbols() shouldBe listOf(sym0("baz")) } } } @@ -223,7 +225,7 @@ class TestIncrementalProgram { result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main")) result.countChunks() shouldBe 2 - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("foo")) + result.lastChunkSymbols() shouldBe listOf(sym0("foo")) }.also { (builder, evalRes) -> builder.programWithRules( @@ -239,7 +241,7 @@ class TestIncrementalProgram { result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main"), sym0("bar")) result.countChunks() shouldBe 3 - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("bar")) + result.lastChunkSymbols() shouldBe listOf(sym0("bar")) } } } @@ -315,7 +317,7 @@ class TestIncrementalProgram { ).launch("initial run", progSpec) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"), sym0("dummy")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("dummy")) + result.lastChunkSymbols() shouldBe listOf(sym0("dummy")) }.also { (builder, evalRes) -> val nPrincipalMatches = evalRes.countChunks() @@ -332,7 +334,7 @@ class TestIncrementalProgram { ).relaunch("test1", progSpec, evalRes.token()) { result -> result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("baz"), sym0("dummy")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("dummy")) + result.lastChunkSymbols() shouldBe listOf(sym0("dummy")) result.countChunks() shouldBe (1 + nPrincipalMatches) } } @@ -509,7 +511,7 @@ class TestIncrementalProgram { // if "foobar" happens too early, "1st" occ won't be produced result.storeView().constraintSymbols() shouldBe setOf(sym0("start"), sym0("1st"), sym0("2nd")) // ensure right rule match order: the last chunk must contain "2nd" - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("2nd")) + result.lastChunkSymbols() shouldBe listOf(sym0("2nd")) } } } @@ -568,10 +570,58 @@ class TestIncrementalProgram { // if "foobar" happens too early, "1st" occ won't be produced result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("marker")) - result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("marker")) + result.lastChunkSymbols() shouldBe listOf(sym0("marker")) result.countChunks() shouldBe (2 + nPrincipalMatches) // +[.bar, foobar] } } } + + @Test + fun rmSingleKeptMatch() { + val progSpec = MockIncrProgSpec( + setOf("main", "foo.bar", "foo.baz", "baz.qux"), + setOf(sym0("foo"), sym0("baz")) + ) + programWithRules( + rule("main", + headReplaced( + constraint("main") + ), + body( + princConstraint("foo") + )), + rule("foo.baz", + headReplaced( + princConstraint("foo") + ), + body( + princConstraint("baz") + )), + rule("baz.qux", + headKept( + princConstraint("baz") + ), + body( + constraint("qux") + ) + ) + ).launch("initial run", progSpec) { result -> + + result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"), sym0("qux")) + result.lastChunkSymbols() shouldBe listOf(sym0("qux")) + + }.also { (builder, evalRes) -> + val nPrincipalMatches = evalRes.countChunks() + + // Insert rule before foo.baz: produce bar before discarding foo in foo.baz + builder.removeRules(listOf("baz.qux")) + .relaunch("removed", progSpec, evalRes.token()) { result -> + + result.storeView().constraintSymbols() shouldBe setOf(sym0("baz")) + result.lastChunkSymbols() shouldBe listOf(sym0("baz")) + result.countChunks() shouldBe (nPrincipalMatches - 1) + } + } + } }