diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RulesDiff.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RulesDiff.kt index 334d4cc7..9664f97b 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RulesDiff.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RulesDiff.kt @@ -20,8 +20,10 @@ import jetbrains.mps.logic.reactor.program.Rule data class RulesDiff(val added: Iterable, val removed: Set) { companion object { + @JvmStatic fun emptyDiff() = RulesDiff(emptyList(), emptySet()) + @JvmStatic fun findDiff(old: Iterable, new: Iterable): RulesDiff { val oldSet = old.toHashSet() val newSet = new.toHashSet() 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 486d78b1..846369cb 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 @@ -103,21 +103,21 @@ interface MatchJournal : MutableIterable { * 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): OccurrencePos? = + fun principalPos(occ: Id): 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 { ) } - 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 { 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 fun activatedLog(): List = entriesLog().filter { !it.discarded() }.map { it.occ() as Occurrence } fun discardedLog(): List = entriesLog().filter { it.discarded() }.map { it.occ() as Occurrence } @@ -162,31 +163,19 @@ interface MatchJournal : MutableIterable { 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 } } 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 0dd81b85..a06fd667 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 @@ -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, MatchJournal.Chunk> - private val principalOccurrences: Map + private val principalOccurrences: Map init { chunkOrder = HashMap().apply { @@ -189,13 +189,13 @@ internal open class MatchJournalImpl( } val m = HashMap, MatchJournal.Chunk>() - val m2 = HashMap() + val m2 = HashMap() 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{ 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 } } 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 4738aeae..b45540f3 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 @@ -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) { val justificationRoots = mutableListOf() @@ -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() } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt index 50046f61..133dc54d 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StoreAwareJournal.kt @@ -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 { diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java index 62139ff4..4d06c75b 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java @@ -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) {} } diff --git a/reactor/Test/test/TestStoreAwareJournal.kt b/reactor/Test/test/TestStoreAwareJournal.kt index b6816f43..5cd45a8b 100644 --- a/reactor/Test/test/TestStoreAwareJournal.kt +++ b/reactor/Test/test/TestStoreAwareJournal.kt @@ -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()