Extract frame-independent logic from MatchHistory to its base class MatchJournal

Small changes: rename MatchHistory -> StateAwareJournal; use referential
eq when iterating through chunks; provide only Pos, not Chunk from
current()/currentPos(); fix tests according to all this.
This commit is contained in:
Grigorii Kirgizov 2019-05-20 18:21:03 +03:00
parent e5942d2c77
commit d3883effaf
2 changed files with 164 additions and 156 deletions

View File

@ -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<MatchJournal.Chunk> {
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<Chunk>, 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<Entry> = 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<Chunk>): Iterator<Chunk> 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<MatchHistory.Chunk>
private var nextChunkId: Int
// private var order: Map<Chunk, Int> = emptyMap()
protected val hist: MutableList<MatchJournal.Chunk>
protected var nextChunkId: Int
init {
if (view == null) {
hist = LinkedList<MatchHistory.Chunk>()
hist = LinkedList<MatchJournal.Chunk>()
nextChunkId = 0
} else {
hist = LinkedList<MatchHistory.Chunk>(view.chunks)
hist = LinkedList<MatchJournal.Chunk>(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<MatchHistory.Chunk> = hist.listIterator()
protected lateinit var current: MatchJournal.Chunk
protected var pos: MutableListIterator<MatchJournal.Chunk> = 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<MatchJournal.Chunk.Entry>
}
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<MatchJournal.Chunk.Entry>) =
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<Occurrence> {
@ -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<MatchHistory.Chunk.Entry>
}
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<MatchHistory.Chunk.Entry>) =
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<Occurrence>) : 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) } }

View File

@ -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())