Refactor MatchJournal.Pos. Make RulesDiff usable in coderules mps code. Extend EvaluationTrace.

This commit is contained in:
Grigorii Kirgizov 2019-06-28 16:59:36 +03:00 committed by Fedor Isakov
parent 49aa913556
commit ed93865359
7 changed files with 56 additions and 52 deletions

View File

@ -20,8 +20,10 @@ import jetbrains.mps.logic.reactor.program.Rule
data class RulesDiff(val added: Iterable<Rule>, val removed: Set<Any>) {
companion object {
@JvmStatic
fun emptyDiff() = RulesDiff(emptyList(), emptySet())
@JvmStatic
fun findDiff(old: Iterable<Any>, new: Iterable<Rule>): RulesDiff {
val oldSet = old.toHashSet()
val newSet = new.toHashSet()

View File

@ -103,21 +103,21 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
* Returns position of the first principal occurrence activated in provided chunk.
* Returns null if the chunk activates no principal occurrences.
*/
fun principalOccurrenceOf(chunk: Chunk): OccurrencePos?
fun principalOccurrenceOf(chunk: Chunk): Pos?
/**
* Find [OccurrencePos] by occurrence.
* Find [Pos] by occurrence.
*/
fun principalPos(occ: Id<Occurrence>): OccurrencePos? =
fun principalPos(occ: Id<Occurrence>): Pos? =
// just a composition of two operations
activatingChunkOf(occ)?.let { principalOccurrenceOf(it) }
/**
* Find [OccurrencePos] at which provided match was triggered.
* Find [Pos] at which provided match was triggered.
* Returned position specifies [Occurrence] which triggered the match.
* Works not for any match, must work for matches of principal rules.
*/
fun activationPos(match: RuleMatchEx): OccurrencePos? =
fun activationPos(match: RuleMatchEx): Pos? =
// The latest matched occurrence from match's head is(by definition)
// the occurrence which activated this match.
match.signature().mapNotNull { occSig ->
@ -136,14 +136,14 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
)
}
abstract class Chunk(val match: RuleMatch, val id: Int, val justifications: Justs) : MatchJournalChunk, Pos()
abstract class Chunk(val match: RuleMatch, val id: Int, val justifications: Justs) : MatchJournalChunk
{
override fun match(): RuleMatch = match
override fun id(): Int = id
override fun justifications(): TIntSet = justifications
data class Entry(val occ: Occurrence, val discarded: Boolean = false) : MatchJournalChunk.Entry {
override fun occ(): ConstraintOccurrence = occ
override fun occ(): Occurrence = occ
override fun discarded(): Boolean = discarded
override fun toString() = (if (discarded) '-' else '+') + occ.toString()
}
@ -151,6 +151,7 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
fun isDescendantOf(chunkId: Int): Boolean = justifications().contains(chunkId)
fun isTopLevel(): Boolean = justifications().size() <= 1 // this condition implies that there're no ancestor chunks
abstract override fun entriesLog(): List<Entry>
fun activatedLog(): List<Occurrence> = entriesLog().filter { !it.discarded() }.map { it.occ() as Occurrence }
fun discardedLog(): List<Occurrence> = entriesLog().filter { it.discarded() }.map { it.occ() as Occurrence }
@ -162,31 +163,19 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
override fun toString() = "(id=${id()}, ${justifications()}, ${match().rule().uniqueTag()}, ${entriesLog()})"
// fixme: just provide toPos() instead of override?
override val chunk: Chunk
get() = this
override val entriesInChunk: Int
get() = entriesLog().size
fun toPos(): Pos = Pos(this, entriesLog().size)
}
abstract class Pos {
abstract val chunk: Chunk
abstract val entriesInChunk: Int
open class Pos(val chunk: Chunk, val entriesCount: Int) {
val occ: Occurrence
get() = chunk.entriesLog()[entriesCount - 1].occ()
override fun equals(other: Any?) =
other is Pos
&& other.chunk === chunk
&& other.entriesInChunk == entriesInChunk
}
&& other.entriesCount == entriesCount
data class OccurrencePos(
val occ: Occurrence,
override val chunk: Chunk,
private val offset: Int): MatchJournal.Pos()
{
// fixme: remove 'entriesInChunk' everywhere, use just offset in Pos
override val entriesInChunk: Int
get() = offset + 1
override fun hashCode(): Int = 31 * chunk.hashCode() + entriesCount
}
}

View File

@ -91,7 +91,7 @@ internal open class MatchJournalImpl(
}
override fun currentPos(): MatchJournal.Pos = current
override fun currentPos(): MatchJournal.Pos = current.toPos()
override fun resetPos() {
posPtr = hist.listIterator()
@ -100,7 +100,7 @@ internal open class MatchJournalImpl(
override fun reset(pastPos: MatchJournal.Pos) {
while (posPtr.hasPrevious()) {
if (current === pastPos.chunk) {
current.entries = current.entries.subList(0, pastPos.entriesInChunk)
current.entries = current.entries.subList(0, pastPos.entriesCount)
return
}
current = posPtr.previous()
@ -113,7 +113,7 @@ internal open class MatchJournalImpl(
while (posPtr.hasNext()) {
current = posPtr.next()
if (futurePos.chunk === current) {
replayOccurrences(controller, current.entries.take(futurePos.entriesInChunk))
replayOccurrences(controller, current.entries.take(futurePos.entriesCount))
return
}
replayOccurrences(controller, current.entries)
@ -181,7 +181,7 @@ internal open class MatchJournalImpl(
// only for principal constraints
private val parentChunks: Map<Id<Occurrence>, MatchJournal.Chunk>
private val principalOccurrences: Map<Int, MatchJournal.OccurrencePos>
private val principalOccurrences: Map<Int, MatchJournal.Pos>
init {
chunkOrder = HashMap<Int, Int>().apply {
@ -189,13 +189,13 @@ internal open class MatchJournalImpl(
}
val m = HashMap<Id<Occurrence>, MatchJournal.Chunk>()
val m2 = HashMap<Int, MatchJournal.OccurrencePos>()
val m2 = HashMap<Int, MatchJournal.Pos>()
chunks.forEach { chunk ->
// actually there should be only a single principal occurrence, 'find' is enough
chunk.entriesLog().forEachIndexed { index, e ->
if (ispec.isPrincipal(e.occ.constraint()) && !e.discarded()) {
m[Id(e.occ)] = chunk
m2[chunk.id] = MatchJournal.OccurrencePos(e.occ, chunk, index)
m2[chunk.id] = MatchJournal.Pos(chunk, index + 1)
}
}
}
@ -210,7 +210,7 @@ internal open class MatchJournalImpl(
// todo: throw for invalid positions?
override fun compare(lhs: MatchJournal.Pos, rhs: MatchJournal.Pos): Int {
val co = compareBy<MatchJournal.Pos>{ chunkOrder[it.chunk.id] }.compare(lhs, rhs)
return if (co == 0) lhs.entriesInChunk.compareTo(rhs.entriesInChunk) else co
return if (co == 0) lhs.entriesCount.compareTo(rhs.entriesCount) else co
}
}

View File

@ -71,11 +71,13 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int)
// 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
/**
* 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 invalidateRuleMatches(ruleIds: Set<Any>) {
val justificationRoots = mutableListOf<Int>()
@ -101,7 +103,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
// (i.e. don't reactivate additional, inactive heads that only completed the match?)
execQueue.addAll(validOccs.mapNotNull { occ ->
journalIndex.activatingChunkOf(Id(occ))?.let { chunkPos ->
ExecPos(chunkPos, occ)
ExecPos(chunkPos.toPos(), occ)
}
})
}
@ -111,6 +113,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
if (toInvalidate) {
// Remove the chunk from the journal
it.remove()
trace.invalidate(chunk.match())
// 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.
@ -158,7 +161,8 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
// Also remember the parent justification of this rule candidate
// to drop it from monitoring when child chunks end.
activationCandidates.add(MatchCandidate(rule, pOcc, chunk.id))
// todo: also use the rule to help Dispatcher in future? i.e. try matching only on the candidate rule
// todo: also use the rule to help Dispatcher in future?
// i.e. try matching only on the candidate rule
}
}
}
@ -175,12 +179,13 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
val pos =
// We need the previous chunk as pos here (i.e. adding after it).
if (childChunksEnded || placeToInsertFound) prevChunk
if (childChunksEnded || placeToInsertFound) prevChunk.toPos()
// Case when adding at the very end
else if (!it.hasNext()) chunk
else if (!it.hasNext()) chunk.toPos()
else continue
execQueue.offer(ExecPos(pos, candOcc))
trace.potentialMatch(candOcc, candRule)
// Drop the candidate if appropriate activation place is found.
aIt.remove()
}
@ -195,17 +200,17 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
if (execQueue.isNotEmpty()) {
resetStore()
var prevPos: ExecPos? = null
var prevPos: MatchJournal.Pos? = null
do {
val execPos = execQueue.poll()
// Handles the case when several matches are added to the same position.
// Then shouldn't replay, because currentPos is valid and more recent (!) than execPos.
if (execPos != prevPos) {
if (execPos.pos != prevPos) {
replay(controller, execPos.pos)
lastIncrementalRootPos = execPos.pos
}
prevPos = execPos
prevPos = execPos.pos
// If the occurrence is still in the store after replay (i.e. if it's valid to activate it)
if (execPos.activeOcc.stored) {
@ -213,12 +218,13 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
// Incremental reactivation isn't like the usual reactivation,
// it should proceed more like usual activation.
this.dispatchingFront = dispatchingFront.forgetSeen(execPos.activeOcc)
trace.reactivateIncremental(execPos.activeOcc)
controller.reactivate(execPos.activeOcc)
}
} while (execQueue.isNotEmpty())
}
// Also replay to the end after queue is fully executed
replay(controller, this.last())
replay(controller, this.last().toPos())
// fixme: get FeedbackStatus out of reactivate()
return FeedbackStatus.NORMAL()
}

View File

@ -41,11 +41,11 @@ internal open class StoreAwareJournalImpl(private val journal: MatchJournal, pri
: MatchJournal by journal, StoreAwareJournal, ProcessingState by state
{
private class PosImpl(
private class FramePos(
val frame: StateFrame,
override val chunk: MatchJournal.Chunk,
override val entriesInChunk: Int = 0
) : MatchJournal.Pos()
chunk: MatchJournal.Chunk,
entriesCount: Int = 0
) : MatchJournal.Pos(chunk, entriesCount)
fun push() = state.push()
@ -60,12 +60,12 @@ internal open class StoreAwareJournalImpl(private val journal: MatchJournal, pri
override fun currentPos(): MatchJournal.Pos =
PosImpl(state.currentFrame(), journal.currentPos().chunk, journal.currentPos().entriesInChunk)
FramePos(state.currentFrame(), journal.currentPos().chunk, journal.currentPos().entriesCount)
// Throw away recently added chunks and reset store accordingly
// NB: not checking that chunks are actually recently added, from this exec session
override fun reset(pastPos: MatchJournal.Pos) {
if (pastPos is PosImpl) {
if (pastPos is FramePos) {
state.reset(pastPos.frame)
journal.reset(pastPos)
} else {

View File

@ -18,6 +18,7 @@ package jetbrains.mps.logic.reactor.evaluation;
import jetbrains.mps.logic.reactor.core.EvaluationFailure;
import jetbrains.mps.logic.reactor.program.Rule;
/**
* An interface to be implemented by clients wishing to be notified of the events during evaluation.
@ -51,4 +52,10 @@ public interface EvaluationTrace {
default void ask(boolean result, PredicateInvocation invocation) {}
default void feedback(EvaluationFeedback feedback) {}
default void invalidate(RuleMatch ruleMatch) {}
default void reactivateIncremental(ConstraintOccurrence occurrence) {}
default void potentialMatch(ConstraintOccurrence occurrence, Rule rule) {}
}

View File

@ -452,7 +452,7 @@ class TestStoreAwareJournal {
// continue from the second chunk (match of rule1)
val rmIt = iterator()
rmIt.next() // skip initial chunk
val continueFrom = rmIt.next()
val continueFrom = rmIt.next().toPos()
rmIt.next()
rmIt.remove()