diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt index 3339dc08..a72a6f0c 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt @@ -88,13 +88,6 @@ interface MatchJournal : MutableIterable, EvidenceSource { */ fun replay(observable: LogicalStateObservable, futurePos: Pos) - /** - * Same as [replay] but stops at the last descendant of [ancestor] if [until] isn't found before that. - * Suitable for "restricted" replay to [Pos] that can be already invalid (i.e. not in [MatchJournal]). - */ - fun replayDescendants(observable: LogicalStateObservable, ancestor: Chunk, until: Pos) - - /** * Returns snapshot of the journal. */ @@ -191,7 +184,7 @@ interface MatchJournal : MutableIterable, EvidenceSource { override var entries: MutableList = mutableListOf() - override fun toString() = "(id=$evidence, ${justifications()}, activation of $occ)" + override fun toString() = "(id=$evidence, ${justifications()}, activation of $occ, $entries)" } open class Pos(val chunk: Chunk, val entriesCount: Int) { diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt index cbc8be52..1ec9302d 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt @@ -120,6 +120,10 @@ internal open class MatchJournalImpl( override fun isFront(): Boolean = current == hist.last + + override fun reset(pastPos: MatchJournal.Pos) = resetPos(pastPos, true) + +// override fun resetPos() = resetPos(Pos(initialChunk(), 0), false) override fun resetPos() { // walk backwards and reset all occurrences posPtr = hist.listIterator(hist.size) @@ -129,56 +133,38 @@ internal open class MatchJournalImpl( } } - override fun reset(pastPos: MatchJournal.Pos) { - // walk backwards, reset occurrences and remove history from posPtr down to pastPos + private fun resetPos(pastPos: MatchJournal.Pos, drop: Boolean) { while (posPtr.hasPrevious()) { current = posPtr.previous() if (current === pastPos.chunk) { - // todo: need reversed? resetOccurrences(current.entries.drop(pastPos.entriesCount)) - current.entries = current.entries.subList(0, pastPos.entriesCount) + if (drop) current.entries = current.entries.subList(0, pastPos.entriesCount) posPtr.next() // make 'posPtr' to always point right after 'current' return } - // todo: need reversed? resetOccurrences(current.entries) - posPtr.remove() + if (drop) posPtr.remove() } if (currentPos() != pastPos) throw IllegalStateException() } - override fun replay(observable: LogicalStateObservable, futurePos: MatchJournal.Pos) { - while (posPtr.hasNext()) { - current = posPtr.next() + override fun replay(observable: LogicalStateObservable, futurePos: MatchJournal.Pos) = replayWith(futurePos, {}) + + private fun replayWith(futurePos: MatchJournal.Pos, action: (Chunk) -> Unit) { + do { if (futurePos.chunk === current) { - replayOccurrences(observable, current.entries.take(futurePos.entriesCount)) + replayOccurrences(current.entries.take(futurePos.entriesCount)) + action(current) return } - replayOccurrences(observable, current.entries) - } - if (currentPos() != futurePos) throw IllegalStateException() - } + replayOccurrences(current.entries) + action(current) - override fun replayDescendants(observable: LogicalStateObservable, ancestor: Chunk, until: MatchJournal.Pos) { - while (posPtr.hasNext()) { - val previous = current + if (!posPtr.hasNext()) break current = posPtr.next() + } while (true) - if (until.chunk === current) { - replayOccurrences(observable, current.entries.take(until.entriesCount)) - return - } - - if (previous.isDescendantOf(ancestor) && !current.isDescendantOf(ancestor)) { - // reset ptr so that it points after previous chunk - current = previous - posPtr.previous() - return - } - - replayOccurrences(observable, current.entries) - } - if (currentPos() != until) throw IllegalStateException() + if (currentPos() != futurePos) throw IllegalStateException() } override fun dropDescendantsWhile(ancestor: Chunk, dropIf: (Chunk) -> Boolean) { @@ -211,7 +197,8 @@ internal open class MatchJournalImpl( if (posPtr.hasNext()) posPtr.next() } - private fun resetOccurrences(occSpecs: Iterable) = + private fun resetOccurrences(occSpecs: Iterable) = + // todo: need iterating over reversed list? occSpecs.forEach { if (it.discarded) { it.occ.alive = true @@ -222,7 +209,7 @@ internal open class MatchJournalImpl( } } - private fun replayOccurrences(observable: LogicalStateObservable, occSpecs: Iterable) = + private fun replayOccurrences(occSpecs: Iterable) = occSpecs.forEach { if (it.discarded) { // it.occ.terminate(observable) diff --git a/reactor/Test/test/TestStoreAwareJournal.kt b/reactor/Test/test/TestStoreAwareJournal.kt index deb8a2a2..710d51fc 100644 --- a/reactor/Test/test/TestStoreAwareJournal.kt +++ b/reactor/Test/test/TestStoreAwareJournal.kt @@ -44,6 +44,9 @@ class TestStoreAwareJournal { d = d.expand(occ) } + fun logExpand(id: String, vararg args: Any) = + logExpand(occurrence(id, * args)) + fun logFirstMatch() = hist.logMatch(d.matches().first()) // log and expand occurrence while tracking its justifications @@ -189,6 +192,182 @@ class TestStoreAwareJournal { } } + @Test + fun testReplayToSamePosTwice() { + val mockController = MockController() + with(programWithRules( + rule("rule1", + headKept( + pconstraint("foo") + ), + body( + pconstraint("bar") + )), + rule("rule2", + headReplaced( + pconstraint("bar"), + pconstraint("foo") + ), + body( + pconstraint("qux") + )), + rule("rule3", + headKept( + pconstraint("qux") + ), + body( + pconstraint("lax") + )) + )) + { + with(JournalDispatcherHelper(Dispatcher(RuleIndex(rulesLists)))) { + + val initPos = hist.currentPos() + + logExpand(justifiedOccurrenceInit("foo")) + + val fooPos = hist.currentPos() + + logFirstMatch() + logExpandJustified("bar") + + logFirstMatch() + logExpandJustified("qux") + + val quxPos = hist.currentPos() + + logFirstMatch() + logExpandJustified("lax") + + val lastPos = hist.currentPos() + + // 'replay' to the saved pos after full 'resetStore' must restore the store + with(hist) { + + storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax")) + + resetStore() + + + replay(mockController.logicalStateObservable(), fooPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("foo")) + + // second replay to the same position, nothing should change + replay(mockController.logicalStateObservable(), fooPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("foo")) + + + replay(mockController.logicalStateObservable(), quxPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("qux")) + + // second replay to the same position, nothing should change + replay(mockController.logicalStateObservable(), quxPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("qux")) + } + } + } + } + + @Test + @Ignore("Journal in current state doesn't track precise position inside Chunk") + fun testReplayInsideChunk() { + val mockController = MockController() + with(programWithRules( + rule("rule1", + headKept( + pconstraint("foo") + ), + body( + pconstraint("bar") + )), + rule("rule2", + headReplaced( + pconstraint("bar"), + pconstraint("foo") + ), + body( + constraint("lax"), + constraint("qux"), + constraint("shwux"), + constraint("kex"), + pconstraint("buzz") + + )), + rule("rule3", + headKept( + constraint("qux") + ), + headReplaced( + constraint("lax"), + constraint("shwux") + ), + body( + )) + )) + { + with(JournalDispatcherHelper(Dispatcher(RuleIndex(rulesLists)))) { + + val initPos = hist.currentPos() + + logExpand(justifiedOccurrenceInit("foo")) + val fooPos = hist.currentPos() + + logFirstMatch() + logExpandJustified("bar") + + logFirstMatch() + logExpand("lax") + logExpand("qux") + val quxPos = hist.currentPos() + logExpand("shwux") + val shwuxPos = hist.currentPos() + + logFirstMatch() + logExpand("kex") + val kexPos = hist.currentPos() + logExpandJustified("buzz") + + val lastPos = hist.currentPos() + + // 'replay' to the saved pos after full 'resetStore' must restore the store + with(hist) { + + storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("kex"), sym0("buzz")) + + resetStore() + + + replay(mockController.logicalStateObservable(), quxPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax")) + + // second replay to the same position inside chunk, nothing should change + replay(mockController.logicalStateObservable(), quxPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax")) + + + replay(mockController.logicalStateObservable(), shwuxPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax"), sym0("shwux")) + + // replay inside chunk when non principal-match happenned + replay(mockController.logicalStateObservable(), kexPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("kex")) + + + replay(mockController.logicalStateObservable(), lastPos) + + storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("kex"), sym0("buzz")) + } + } + } + } @Test fun testResetStoreThenReplay() { @@ -272,7 +451,7 @@ class TestStoreAwareJournal { // execute program hist.testPush() - logExpand(occurrence("foo")) + logExpand("foo") logFirstMatch() // provide initial justification @@ -334,8 +513,8 @@ class TestStoreAwareJournal { // rule2 logFirstMatch() - logExpand(occurrence("bar")) - logExpand(occurrence("bazz")) + logExpand("bar") + logExpand("bazz") // use this production later, but create it now to get relevant justs val quxOcc = justifiedOccurrence("qux", hist.nextEvidence(), hist.justifications()) @@ -343,7 +522,7 @@ class TestStoreAwareJournal { val curChunk = hist.currentPos().chunk // rule3 logFirstMatch() - logExpand(occurrence("bazz")) + logExpand("bazz") // matched on rule with heads without justifications, should remain in the same chunk hist.currentPos().chunk shouldBeSame curChunk @@ -451,7 +630,7 @@ class TestStoreAwareJournal { // rule3, this rule match will remain in history untouched hist.logMatch(rule1matches.last()) - logExpand(occurrence("qux")) + logExpand("qux") // execution ended @@ -489,7 +668,7 @@ class TestStoreAwareJournal { d.matches().count() shouldBe 1 // rule2b, this rule match is added at the place of rule2a match logFirstMatch() - logExpand(occurrence("marker")) + logExpand("marker") // reexecution ended