diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt index 3cf2dbd3..dc7a0f41 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt @@ -106,23 +106,32 @@ class Dispatcher (val ruleIndex: RuleIndex) { * Returns a new [DispatchingFront] instance that is "contracted": all matches corresponding to the * specified discarded constraint occurrence are eliminated. */ - fun contract(discarded: Occurrence) = DispatchingFront(this, - ruleIndex.forOccurrence(discarded).mapNotNull { rule -> - ruletag2probe[rule.uniqueTag()] - }.map { probe -> + fun contract(discarded: Occurrence) = + forRelatedProbe(discarded) { probe -> probe.contract(discarded) - }) + } /** * Returns a new [DispatchingFront] instance which "forgot" that it seen occurrence. * Need it for incremental reactivations to discern them from reactivations due to logicals. */ - internal fun forgetSeen(dropped: Occurrence) = DispatchingFront(this, - ruleIndex.forOccurrence(dropped).mapNotNull { rule -> - ruletag2probe[rule.uniqueTag()] - }.map { probe -> + internal fun forgetSeen(dropped: Occurrence) = + forRelatedProbe(dropped) { probe -> probe.forgetSeen(dropped) - }) + } + + internal fun forget(dropped: Occurrence) = + forRelatedProbe(dropped) { probe -> + probe.contract(dropped).forgetSeen(dropped).forgetConsumed(dropped) + } + + private fun forRelatedProbe(occ: Occurrence, action: (RuleMatchingProbe) -> RuleMatchingProbe) = + DispatchingFront(this, + ruleIndex.forOccurrence(occ).mapNotNull { rule -> + ruletag2probe[rule.uniqueTag()] + }.map { probe -> + action(probe) + }) /** * Serves to indicate that the specified [RuleMatchEx] has been processed (consumed) and has to diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt index 04f764cd..e3d50798 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt @@ -43,4 +43,6 @@ interface RuleMatchingProbe { fun forgetSeen(occ: Occurrence): RuleMatchingProbe + fun forgetConsumed(occ: Occurrence): RuleMatchingProbe + } \ No newline at end of file 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 7f54d051..c22c0755 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 @@ -52,7 +52,7 @@ internal class ControllerImpl ( fun incrLaunch(constraint: Constraint, rulesDiff: RulesDiff): FeedbackStatus { // todo: use profiler here? - state.invalidateByRules(rulesDiff.removed) + state.invalidateRuleMatches(rulesDiff.removed) state.addRuleMatches(rulesDiff.added) val status = state.launchQueue(this) return status 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 6c85786d..da10409c 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 @@ -50,6 +50,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp : StoreAwareJournalImpl(journal) { private val ruleOrdering: RuleOrdering = RuleOrdering(ruleIndex) + private val journalIndex: MatchJournal.Index = journal.index() // It is a position in Journal from previous session, @@ -69,14 +70,12 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp 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 + // - 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) { + fun invalidateRuleMatches(ruleIds: Set) { val justificationRoots = mutableListOf() val it = this.iterator() @@ -92,11 +91,10 @@ 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 val matchedOccs = chunk.match.allHeads() as Iterable - assert(matchedOccs.all { it.isPrincipal() }) - val (invalidatedOccs, validOccs) = matchedOccs.partition { occ -> occ.justifications().intersects(justificationRoots) } + assert(matchedOccs.all { it.isPrincipal() }) // todo: do need to reactivate only the main, matching~activating match? // (i.e. don't reactivate additional, inactive heads that only completed the match?) @@ -113,20 +111,20 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp // Remove the chunk from the journal it.remove() - // Need to 'cancel' discarding: contract nodes with discarded occurrences. - // These nodes may become valid and will be processed due to reactivation of needed occurrences. - chunk.match.matchHeadReplaced().forEach { - dispatchingFront = dispatchingFront.contract(it as Occurrence) - } - // Seems, it's not strictly necessary, because some of its head occurrences are anyway invalidated forever // and storing this invalid consumed match can make no harm, except some memory overhead. // fixme: move Chunk's interface to RuleMatchEx instead of RuleMatch dispatchingFront = dispatchingFront.forget(chunk.match as RuleMatchEx) + // Need to 'cancel' discarding. + // These nodes may become valid and will be processed due to reactivation of needed occurrences. + chunk.match.matchHeadReplaced().forEach { + dispatchingFront = dispatchingFront.forget(it as Occurrence) + } // 'Undo' all activated in this chunk occurrences + // todo: also forget all consumed matches invo chunk.activated().forEach { - dispatchingFront = dispatchingFront.contract(it).forgetSeen(it) + dispatchingFront = dispatchingFront.forget(it) } } @@ -137,9 +135,6 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp private fun canMatch(rule: Rule, occ: Occurrence): Boolean = (rule.headKept() + rule.headReplaced()).find { it.symbol() == occ.constraint.symbol() } != null - // todo: what if non-principal rules will be passed as input there? - // only rules that can match on principal occurrences will pass through this method to the queue - // so, no non-principal rule should pass it. fun addRuleMatches(rules: Iterable) { val activationCandidates = mutableListOf() diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt index 77f9748e..4642f4be 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt @@ -351,6 +351,10 @@ internal class ReteRuleMatcherImpl(val rule: Rule) : RuleMatcher { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } + override fun forgetConsumed(occ: Occurrence): ReteNetwork { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + override fun matches(): Collection = generations.last().matches() override fun consume(ruleMatch: RuleMatchEx): RuleMatchingProbe { diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt index 546b7491..a01a899a 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt @@ -100,11 +100,10 @@ internal class RuleMatcherImpl(private val ruleLookup: RuleLookup, return RuleMatchFront(newNodes, seenOccurrences, consumedSignatures, genId + 1) } - // todo: need it for incremental removal? -// fun forgetConsumed(occ: Occurrence): RuleMatchFront { -// val newConsumed = Sets.copyOf(consumedSignatures.filter{ !it.contains(Id(occ)) }) -// return RuleMatchFront(nodes, seenOccurrences, newConsumed, genId) // NB: not modifying genId -// } + override fun forgetConsumed(occ: Occurrence): RuleMatchFront { + val newConsumed = Sets.copyOf(consumedSignatures.filter{ !it.contains(Id(occ)) }) + return RuleMatchFront(nodes, seenOccurrences, newConsumed, genId) // NB: not modifying genId + } override fun forgetSeen(occ: Occurrence): RuleMatchFront { val newSeen = seenOccurrences.remove(Id(occ)) diff --git a/reactor/Test/test/TestIncrementalProgram.kt b/reactor/Test/test/TestIncrementalProgram.kt index c3b8cae6..56623c3d 100644 --- a/reactor/Test/test/TestIncrementalProgram.kt +++ b/reactor/Test/test/TestIncrementalProgram.kt @@ -457,7 +457,7 @@ class TestIncrementalProgram { } @Test - fun relaunchTwice() { + fun addAndRelaunchTwice() { val progSpec = MockIncrProgSpec( setOf("main", "foo.bar", "foo.baz", "baz.lax"), setOf(sym0("foo"), sym0("baz")) @@ -582,7 +582,7 @@ class TestIncrementalProgram { // Test on complex condition involving consumedSignatures and 'reactivation' flag in RuleMatchFront.expand @Test - fun reactivatePartialMatchOfPropagationRule() { + fun completePartialMatch() { val progSpec = MockIncrProgSpec( setOf(".foo", ".bar", "foobar"), setOf(sym0("start"), sym0("foo"), sym0("bar")) @@ -613,8 +613,6 @@ class TestIncrementalProgram { )) ).launch("withoutBar", progSpec) { result -> - println(result.token().journalView.chunks) - // "foobar" hasn't matched without 'bar, so 'foo' is still here result.storeView().constraintSymbols() shouldBe setOf(sym0("foo")) @@ -632,8 +630,6 @@ class TestIncrementalProgram { )) ).relaunch("withBar", progSpec, evalRes.token()) { result -> - println(result.token().journalView.chunks) - // if "foobar" happens too early, "1st" occ won't be produced result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("marker")) result.lastChunkSymbols() shouldBe listOf(sym0("marker")) @@ -642,6 +638,71 @@ class TestIncrementalProgram { } } + // Same as 'completePartialMatch' test, except it discards 'foo' at the end. + @Test + @Ignore("Shouldn't pass by design. Incrementality-enabled programs are not supposed to have such rule sequences.") + fun completePartialMatchBeforeReplaced() { + val progSpec = MockIncrProgSpec( + setOf(".foo", ".bar", "foobar"), + setOf(sym0("start"), sym0("foo"), sym0("bar")) + ) + programWithRules( + rule("main", + headReplaced( + constraint("main") + ), + body( + princConstraint("start") + )), + rule(".foo", + headReplaced( + princConstraint("start") + ), + body( + princConstraint("foo") + )), + // Propagation rule that doesn't match the first time due to the lack of 'bar' + rule("foobar", + headKept( + princConstraint("foo"), + princConstraint("bar") + ), + body( + constraint("marker") + )), + rule("rmfoo", + headReplaced( + princConstraint("foo") + ), + body() + ) + ).launch("withoutBar", progSpec) { result -> + + // "foobar" hasn't matched without 'bar, so 'foo' is still here + result.storeView().constraintSymbols() shouldBe emptySet() + + }.also { (builder, evalRes) -> + val nPrincipalMatches = evalRes.countChunks() + + // Provides 'bar' by reactivating 'foo' + builder.insertRulesAt(1, + rule(".bar", + headKept( + princConstraint("foo") + ), + body( + princConstraint("bar") + )) + ).relaunch("withBar", progSpec, evalRes.token()) { result -> + + // if "foobar" happens too early, "1st" occ won't be produced + result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("marker")) + result.lastChunkSymbols() shouldBe listOf(sym0("marker")) + result.countChunks() shouldBe (2 + nPrincipalMatches) // +[.bar, foobar] + } + } + } + @Test fun rmSinglePropagationMatch() { @@ -739,7 +800,7 @@ class TestIncrementalProgram { } @Test - fun rmMatchWithDependencies() { + fun rmWithDependencies() { val progSpec = MockIncrProgSpec( setOf("main", "foo.baz", "foo.bar", "barbaz.qux", "baz.lax"), setOf(sym0("foo"), sym0("baz"), sym0("bar")) @@ -851,8 +912,6 @@ class TestIncrementalProgram { builder.removeRules(listOf("foo.bar")) .relaunch("removed", progSpec, evalRes.token()) { result -> - println(result.token().journalView.chunks) - result.storeView().constraintSymbols() shouldBe setOf(sym0("lax")) result.lastChunkSymbols() shouldBe listOf(sym0("lax")) result.countChunks() shouldBe (nPrincipalMatches - 1 + 2) @@ -965,8 +1024,6 @@ class TestIncrementalProgram { )) ).relaunch("substituted", progSpec, evalRes.token()) { result -> - println(result.token().journalView.chunks) - result.countChunks() shouldBe (nPrincipalMatches - 1) result.lastChunkSymbols() shouldBe listOf(sym0("bar")) } @@ -1011,8 +1068,6 @@ class TestIncrementalProgram { ) ).launch("initial run", progSpec) { result -> - println(result.token().journalView.chunks) - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz")) result.lastChunkSymbols() shouldBe listOf(sym0("baz")) @@ -1038,7 +1093,7 @@ class TestIncrementalProgram { )) ).relaunch("substituted", progSpec, evalRes.token()) { result -> - println(result.token().journalView.chunks) +// println(result.token().journalView.chunks) result.countChunks() shouldBe (nPrincipalMatches) result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"))