diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchHistory.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt similarity index 63% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchHistory.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt index 499a0786..41c3bdb8 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchHistory.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt @@ -25,15 +25,27 @@ import jetbrains.mps.logic.reactor.evaluation.RuleMatch import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.program.ConstraintSymbol import jetbrains.mps.logic.reactor.util.Id +import java.lang.IllegalArgumentException import java.util.* import kotlin.collections.ArrayList -interface MatchHistory { +interface MatchJournal : MutableIterable { + + fun logMatch(match: RuleMatch) + fun logOccurrence(occ: Occurrence) + + fun currentPos(): Pos + fun reset(pastPos: Pos) + fun rollTo(futurePos: Pos) + + fun view(): View + fun storeView(): StoreView + + data class View(val chunks: List, val nextChunkId: Int) - - data class Chunk(val match: RuleMatch, val id: Int, val justifications: TIntSet) { + data class Chunk(val match: RuleMatch, val id: Int, val justifications: TIntSet) : Pos { data class Entry(val occ: Occurrence, val isDiscarded: Boolean = false) { override fun toString() = (if (isDiscarded) '-' else '+') + occ.toString() } @@ -41,75 +53,112 @@ interface MatchHistory { var occurrences: MutableList = mutableListOf() override fun toString() = "(id=$id, $justifications, ${match.rule().tag()}, $occurrences)" + + override fun chunk(): Chunk = this + override fun occsRetained(): Int = occurrences.size } - class RemoveChunkIterator(val it: MutableListIterator): Iterator by it { - fun remove() = it.remove() - } - - interface HistoryPos { + interface Pos { fun chunk(): Chunk fun occsRetained(): Int } +} +interface StoreAwareJournal: MatchJournal { - fun removeIterator(): RemoveChunkIterator - fun resetStore() - fun storeView(): StoreView - fun view(): MatchHistory.View - - fun logMatch(match: RuleMatch) - fun logOccurrence(occ: Occurrence) - - fun current(): Chunk - fun currentPos(): HistoryPos fun testPush(): Unit - fun reset(timepoint: HistoryPos) - - fun rollTo(futurePos: HistoryPos) - fun rollTo(chunk: Chunk) + fun resetStore() companion object { - fun fromSeed(disp: Dispatcher, chunkIdSeed: Int = 0): MatchHistory = MatchHistoryImpl(ProcessingStateImpl(disp), chunkIdSeed) - fun fromView(disp: Dispatcher, view: MatchHistory.View): MatchHistory = MatchHistoryImpl(ProcessingStateImpl(disp), view) + fun fromSeed(disp: Dispatcher, chunkIdSeed: Int = 0): StoreAwareJournal = StoreAwareJournalImpl(ProcessingStateImpl(disp), chunkIdSeed) + fun fromView(disp: Dispatcher, view: MatchJournal.View): StoreAwareJournal = StoreAwareJournalImpl(ProcessingStateImpl(disp), view) } } -internal class MatchHistoryImpl(val state: ProcessingStateImpl, view: MatchHistory.View?): MatchHistory { +internal open class MatchJournalImpl(view: MatchJournal.View?): MatchJournal { - private val hist: MutableList - private var nextChunkId: Int - - // private var order: Map = emptyMap() + protected val hist: MutableList + protected var nextChunkId: Int init { if (view == null) { - hist = LinkedList() + hist = LinkedList() nextChunkId = 0 } else { - hist = LinkedList(view.chunks) + hist = LinkedList(view.chunks) nextChunkId = view.nextChunkId } } - constructor(state: ProcessingStateImpl, chunkIdSeed: Int) : this(state, null) { + constructor(chunkIdSeed: Int) : this(null) { nextChunkId = chunkIdSeed } // FIXME: provide initial chunk? possibly initialized from StoreView, with empty justifications - private lateinit var current: MatchHistory.Chunk - private var pos: MutableListIterator = hist.listIterator() + protected lateinit var current: MatchJournal.Chunk + protected var pos: MutableListIterator = hist.listIterator() - override fun removeIterator() = MatchHistory.RemoveChunkIterator(hist.listIterator()) + override fun iterator() = hist.iterator() - // Reset only store & history position, don't modify history - override fun resetStore() { - state.reset() - pos = hist.listIterator() + + override fun logMatch(match: RuleMatch) { + val m = match as RuleMatchImpl + // If the set of justifications isn't empty, then we deal with principal constraint + val justs = match.headJustifications() + if (!justs.isEmpty) { + justs.add(nextChunkId) + val newChunk = MatchJournal.Chunk(match, nextChunkId, justs) + pos.add(newChunk) + + ++nextChunkId + current = newChunk + } + + // Log discards + match.forEachReplaced {occ -> + // this.dispatchFringe = dispatchFringe.contract(occ) + current.occurrences.add(MatchJournal.Chunk.Entry(occ, true)) + occ.terminate() + } } + override fun logOccurrence(occ: Occurrence) { + current.occurrences.add(MatchJournal.Chunk.Entry(occ)) + occ.revive() + } + + + override fun currentPos(): MatchJournal.Pos = current + + override fun reset(pastPos: MatchJournal.Pos) { + while (pos.hasPrevious()) { + current = pos.previous() + if (current === pastPos.chunk()) break + pos.remove() + } + current.occurrences = current.occurrences.take(pastPos.occsRetained()) as MutableList + } + + override fun rollTo(futurePos: MatchJournal.Pos) { + while (pos.hasNext()) { + current = pos.next() + if (futurePos.chunk() === current) { + rollOccurrences(current.occurrences.take(futurePos.occsRetained())) + return + } + rollOccurrences(current.occurrences) + } + throw IllegalStateException() + } + + private fun rollOccurrences(occSpecs: Iterable) = + occSpecs.forEach { if (it.isDiscarded) it.occ.terminate() else it.occ.revive() } + + + override fun view() = MatchJournal.View(ArrayList(hist), nextChunkId) + override fun storeView(): StoreView = StoreViewImpl(allOccurrences()) private fun allOccurrences(): Sequence { @@ -122,96 +171,11 @@ internal class MatchHistoryImpl(val state: ProcessingStateImpl, view: MatchHisto chunk.occurrences.forEach { if (it.isDiscarded) set.remove(Id(it.occ)) else set.add(Id(it.occ)) } - if (chunk == current) break + if (chunk === current) break } return set.map { it.wrapped }.asSequence() } - override fun view() = MatchHistory.View(ArrayList(hist), nextChunkId) - - - override fun logMatch(match: RuleMatch) { - val m = match as RuleMatchImpl - // If the set of justifications isn't empty, then we deal with principal constraint - val justs = match.headJustifications() - if (!justs.isEmpty) { - justs.add(nextChunkId) - val newChunk = MatchHistory.Chunk(match, nextChunkId, justs) - pos.add(newChunk) - - ++nextChunkId - current = newChunk - } - - // Log discards - match.forEachReplaced {occ -> -// this.dispatchFringe = dispatchFringe.contract(occ) - current.occurrences.add(MatchHistory.Chunk.Entry(occ, true)) - occ.terminate() - } - } - - override fun logOccurrence(occ: Occurrence) { - current.occurrences.add(MatchHistory.Chunk.Entry(occ)) - occ.revive() - } - - - private data class HistoryPosImpl(val frame: StateFrame, val chunk: MatchHistory.Chunk, val occsRetained: Int = 0) : MatchHistory.HistoryPos { - override fun chunk(): MatchHistory.Chunk = chunk - override fun occsRetained(): Int = occsRetained - } - - // FrameStack API: currentFrame/currentPos, push, reset - fun currentFrame() = state.currentFrame() - - override fun current(): MatchHistory.Chunk = current - - override fun currentPos(): MatchHistory.HistoryPos = HistoryPosImpl(state.currentFrame(), current, current.occurrences.size) - - override fun testPush() { state.push() } - fun push() = state.push() - - // Throw away recently added chunks and reset store accordingly - // NB: not checking that chunks are actually recently added - override fun reset(timepoint: MatchHistory.HistoryPos) { - val tp = timepoint as HistoryPosImpl - state.reset(tp.frame) - - while (pos.hasPrevious()) { - current = pos.previous() - if (current == tp.chunk) break - pos.remove() - } - current.occurrences = current.occurrences.take(tp.occsRetained) as MutableList - } - - - override fun rollTo(futurePos: MatchHistory.HistoryPos) { - val fp = futurePos as HistoryPosImpl - while (pos.hasNext()) { - current = pos.next() - if (fp.chunk == current) { - rollOccurences(current.occurrences.take(fp.occsRetained)) - break - } - rollOccurences(current.occurrences) - } - } - - override fun rollTo(chunk: MatchHistory.Chunk) { - while (current != chunk && pos.hasNext()) { - current = pos.next() - rollOccurences(current.occurrences) - } - } - - private fun rollOccurences(occSpecs: Iterable) = - occSpecs.forEach { if (it.isDiscarded) it.occ.terminate() else it.occ.revive() } - - // fixme: does belong to here? -// fun resetOrder() { order = hist.mapToIndex() } - private class StoreViewImpl(occurrences: Sequence) : StoreView { @@ -229,6 +193,46 @@ internal class MatchHistoryImpl(val state: ProcessingStateImpl, view: MatchHisto } } + +internal class StoreAwareJournalImpl(val state: ProcessingStateImpl, view: MatchJournal.View?): StoreAwareJournal, MatchJournalImpl(view) { + + constructor(state: ProcessingStateImpl, chunkIdSeed: Int) : this(state, null) { + nextChunkId = chunkIdSeed + } + + + private data class PosImpl(val frame: StateFrame, val chunk: MatchJournal.Chunk, val occsRetained: Int = 0) : MatchJournal.Pos { + override fun chunk(): MatchJournal.Chunk = chunk + override fun occsRetained(): Int = occsRetained + } + + + // Reset only store & history position, don't modify history + override fun resetStore() { + state.reset() + pos = hist.listIterator() + } + + // FrameStack API: currentFrame/currentPos, push, reset + fun currentFrame() = state.currentFrame() + + override fun testPush() { state.push() } + fun push() = state.push() + + override fun currentPos(): MatchJournal.Pos = PosImpl(state.currentFrame(), current, current.occurrences.size) + + // Throw away recently added chunks and reset store accordingly + // NB: not checking that chunks are actually recently added + override fun reset(pastPos: MatchJournal.Pos) { + if (pastPos is PosImpl) { + state.reset(pastPos.frame) + super.reset(pastPos) + } else { + throw IllegalArgumentException() + } + } +} + private fun RuleMatch.headJustifications(): TIntSet { val res: TIntSet = TIntHashSet() this.matchHeadKept().forEach { it.justifications()?.let { res.addAll(it) } } diff --git a/reactor/Test/test/TestMatchHistory.kt b/reactor/Test/test/TestStoreAwareJournal.kt similarity index 88% rename from reactor/Test/test/TestMatchHistory.kt rename to reactor/Test/test/TestStoreAwareJournal.kt index b42f53f7..bce6c187 100644 --- a/reactor/Test/test/TestMatchHistory.kt +++ b/reactor/Test/test/TestStoreAwareJournal.kt @@ -1,6 +1,7 @@ import gnu.trove.set.hash.TIntHashSet import jetbrains.mps.logic.reactor.core.* -import jetbrains.mps.logic.reactor.core.internal.MatchHistory +import jetbrains.mps.logic.reactor.core.internal.MatchJournal +import jetbrains.mps.logic.reactor.core.internal.StoreAwareJournal import jetbrains.mps.logic.reactor.program.ConstraintSymbol import org.junit.Test import org.junit.Assert.* @@ -22,7 +23,9 @@ import org.junit.Assert.* */ -class TestMatchHistory { +fun MatchJournal.justs() = this.currentPos().chunk().justifications + +class TestStoreAwareJournal { @Test fun testJustificationTracking() { @@ -60,7 +63,7 @@ class TestMatchHistory { val disp = Dispatcher(RuleIndex(rulesLists)) var d = disp.front() - val hist = MatchHistory.fromSeed(disp) + val hist = StoreAwareJournal.fromSeed(disp) val mainOcc = justifiedOccurrence("main", setOf(0)) // hist.logOccurrence(mainOcc) // plays a role of the initial constraint, with no preceding RuleMatch d = d.expand(mainOcc) @@ -72,8 +75,8 @@ class TestMatchHistory { hist.logMatch(this) rule().tag() shouldBe "rule1" - hist.current().justifications shouldBe TIntHashSet(setOf(0)) - val fooOcc = justifiedOccurrence("foo", hist.current().justifications) + hist.justs() shouldBe TIntHashSet(setOf(0)) + val fooOcc = justifiedOccurrence("foo", hist.justs()) hist.logOccurrence(fooOcc) d = d.expand(fooOcc) } @@ -86,8 +89,8 @@ class TestMatchHistory { hist.logMatch(this) rule().tag() shouldBe "rule2" - hist.current().justifications shouldBe TIntHashSet(setOf(0,1)) - val barOcc = justifiedOccurrence("bar", hist.current().justifications) + hist.justs() shouldBe TIntHashSet(setOf(0,1)) + val barOcc = justifiedOccurrence("bar", hist.justs()) hist.logOccurrence(barOcc) d = d.expand(barOcc) @@ -100,8 +103,8 @@ class TestMatchHistory { hist.logMatch(this) rule().tag() shouldBe "rule3" - hist.current().justifications shouldBe TIntHashSet(setOf(0,2)) - val quxOcc = justifiedOccurrence("qux", hist.current().justifications) + hist.justs() shouldBe TIntHashSet(setOf(0,2)) + val quxOcc = justifiedOccurrence("qux", hist.justs()) hist.logOccurrence(quxOcc) d = d.expand(quxOcc) @@ -112,7 +115,7 @@ class TestMatchHistory { hist.logMatch(this) rule().tag() shouldBe "rule4" } - hist.current().justifications shouldBe TIntHashSet(setOf(0,1,2,3)) + hist.justs() shouldBe TIntHashSet(setOf(0,1,2,3)) } } } @@ -150,7 +153,7 @@ class TestMatchHistory { val disp = Dispatcher(RuleIndex(rulesLists)) var d = disp.front() - val hist = MatchHistory.fromSeed(disp) + val hist = StoreAwareJournal.fromSeed(disp) val mainOcc = justifiedOccurrence("main", setOf(0)) // hist.logOccurrence(mainOcc) // plays a role of the initial constraint, with no preceding RuleMatch d = d.expand(mainOcc) @@ -158,7 +161,7 @@ class TestMatchHistory { with(d.matches().first()) { hist.logMatch(this) } - val fooOcc = justifiedOccurrence("foo", hist.current().justifications) + val fooOcc = justifiedOccurrence("foo", hist.justs()) hist.logOccurrence(fooOcc) d = d.expand(fooOcc) @@ -166,14 +169,14 @@ class TestMatchHistory { with(d.matches().first()) { hist.logMatch(this) } - val barOcc = justifiedOccurrence("bar", hist.current().justifications) + val barOcc = justifiedOccurrence("bar", hist.justs()) hist.logOccurrence(barOcc) d = d.expand(barOcc) with(d.matches().first()) { hist.logMatch(this) } - val quxOcc = justifiedOccurrence("qux", hist.current().justifications) + val quxOcc = justifiedOccurrence("qux", hist.justs()) hist.logOccurrence(quxOcc) d = d.expand(quxOcc) @@ -233,7 +236,7 @@ class TestMatchHistory { val disp = Dispatcher(RuleIndex(rulesLists)) var d = disp.front() - val hist = MatchHistory.fromSeed(disp) + val hist = StoreAwareJournal.fromSeed(disp) val mainOcc = justifiedOccurrence("main", setOf(0)) d = d.expand(mainOcc) @@ -241,7 +244,7 @@ class TestMatchHistory { rule().tag() shouldBe "rule1" hist.logMatch(this) } - val fooOcc = justifiedOccurrence("foo", hist.current().justifications) + val fooOcc = justifiedOccurrence("foo", hist.justs()) hist.logOccurrence(fooOcc) d = d.expand(fooOcc) @@ -252,20 +255,20 @@ class TestMatchHistory { } val barOcc = occurrence("bar") val bazzOcc = occurrence("bazz") - val quxOcc = justifiedOccurrence("qux", hist.current().justifications) + val quxOcc = justifiedOccurrence("qux", hist.justs()) hist.logOccurrence(barOcc) d = d.expand(barOcc) hist.logOccurrence(bazzOcc) d = d.expand(bazzOcc) - val curChunk = hist.current() + val curChunk = hist.currentPos().chunk() with(d.matches().first()) { rule().tag() shouldBe "rule3" hist.logMatch(this) } // matched on rule with heads without justifications, should remain in the same chunk - hist.current() shouldBeSame curChunk + hist.currentPos().chunk() shouldBeSame curChunk val bazzOcc2 = occurrence("bazz") hist.logOccurrence(bazzOcc2) @@ -298,6 +301,7 @@ class TestMatchHistory { hist.view().chunks shouldBe oldState.chunks hist.storeView().allOccurrences() shouldBe oldStore +// hist.currentPos() shouldBeSame savedPos hist.currentPos().chunk() shouldBe savedPos.chunk() hist.currentPos().occsRetained() shouldBe savedPos.occsRetained() } @@ -370,7 +374,7 @@ class TestMatchHistory { val disp = Dispatcher(RuleIndex(rulesLists)) var d = disp.front() - val hist = MatchHistory.fromSeed(disp) + val hist = StoreAwareJournal.fromSeed(disp) val mainOcc = justifiedOccurrence("main", setOf(0)) d = d.expand(mainOcc) @@ -378,7 +382,7 @@ class TestMatchHistory { rule().tag() shouldBe "rule0" hist.logMatch(this) } - val fooOcc = justifiedOccurrence("foo", hist.current().justifications) + val fooOcc = justifiedOccurrence("foo", hist.justs()) hist.logOccurrence(fooOcc); d = d.expand(fooOcc) @@ -387,7 +391,7 @@ class TestMatchHistory { hist.logMatch(this) } val bar1Occ = occurrence("bar1") - val bazzOcc = justifiedOccurrence("bazz", hist.current().justifications) + val bazzOcc = justifiedOccurrence("bazz", hist.justs()) hist.logOccurrence(bar1Occ); d = d.expand(bar1Occ) hist.logOccurrence(bazzOcc); d = d.expand(bazzOcc) @@ -405,7 +409,7 @@ class TestMatchHistory { rule().tag() shouldBe "rule3" hist.logMatch(this) } - val quxOcc0 = justifiedOccurrence("qux", hist.current().justifications) + val quxOcc0 = justifiedOccurrence("qux", hist.justs()) hist.logOccurrence(quxOcc0); d = d.expand(quxOcc0) @@ -414,13 +418,13 @@ class TestMatchHistory { hist.logMatch(this) } // val pOcc1 = occurrence("p") - val pOcc1 = justifiedOccurrence("p", hist.current().justifications) + val pOcc1 = justifiedOccurrence("p", hist.justs()) hist.logOccurrence(pOcc1); d = d.expand(pOcc1) // execution has ended hist.view().chunks.size shouldBe 5 - val lastChunk = hist.current() + val lastPos = hist.currentPos() // bar1 was discarded by rule2a, so it shouldn't be in the store val bar1StoredBeforeRoll = hist.storeView().occurrences(ConstraintSymbol.symbol("bar1", 0)) @@ -428,7 +432,7 @@ class TestMatchHistory { // walk by history, remove the third chunk (i.e. match of rule2a) // continue from the second chunk (match of rule1) - val rmIt = hist.removeIterator() + val rmIt = hist.iterator() rmIt.next() val continueFrom = rmIt.next() rmIt.next() @@ -457,7 +461,7 @@ class TestMatchHistory { rule().tag() shouldBe "rule2b" hist.logMatch(this) } - val quxOcc1 = justifiedOccurrence("qux", hist.current().justifications) + val quxOcc1 = justifiedOccurrence("qux", hist.justs()) hist.logOccurrence(quxOcc1); d = d.expand(quxOcc1) hist.view().chunks.size shouldBe (3 + 1 + 1) // past + new added + future @@ -468,7 +472,7 @@ class TestMatchHistory { hist.logMatch(this) } // val pOcc2 = occurrence("p") - val pOcc2 = justifiedOccurrence("p", hist.current().justifications) + val pOcc2 = justifiedOccurrence("p", hist.justs()) hist.logOccurrence(pOcc2); d = d.expand(pOcc2) // only pOcc2 should be in the store @@ -477,11 +481,11 @@ class TestMatchHistory { // todo?: check history before roll // finally, purely go the the end, applying the rest of the history to the store - assertNotEquals(lastChunk, hist.current()) - hist.rollTo(lastChunk) + assertNotEquals(lastPos, hist.currentPos()) + hist.rollTo(lastPos) hist.view().chunks.size shouldBe 6 - hist.current() shouldBeSame lastChunk // we inserted in the middle -- the last chunk should remain the same + hist.currentPos().chunk() shouldBeSame lastPos.chunk() // we inserted in the middle -- the last chunk should remain the same println(hist.view().toString()) println(hist.storeView().allOccurrences().toString())