Reactor: drop most of incrementality- and preamble-related stuff
Neither incremental evaluation of program or "preamble" features are useful, but they complicate things and cause major bugs.
This commit is contained in:
parent
49357ab87a
commit
4d1511d794
|
|
@ -109,30 +109,7 @@ class Dispatcher (val ruleIndex: RuleIndex, prevState: DispatchingFrontState = e
|
|||
occId2tags.remove(discarded.identity)
|
||||
?.mapNotNull { ruleTag -> ruletag2probe[ruleTag] }
|
||||
?.map { probe -> probe.contract(discarded) })
|
||||
|
||||
/**
|
||||
* Returns a [DispatchingFront] instance which "forgot" that it already expanded the occurrence.
|
||||
* After that the occurrence can be expanded again without triggering observer reactivation logic.
|
||||
* Needed to discern incremental reactivation from observers reactivation.
|
||||
* In other words, the state of [DispatchingFront] connected with this occurrence
|
||||
* transitions from "fully-expanded" to "partially-expanded".
|
||||
*/
|
||||
internal fun forgetExpanded(dropped: Occurrence): DispatchingFront = DispatchingFront(this,
|
||||
ruleIndex.forOccurrence(dropped)
|
||||
.mapNotNull { rule -> ruletag2probe[rule.uniqueTag()] }
|
||||
.map { probe -> probe.forgetExpanded(dropped) }
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns a new [DispatchingFront] instance which contracts state with this occurrence
|
||||
* and "forgets" that has seen it or that consumed any matches involving it.
|
||||
* Needed for pruning outdated unrelevant state on incremental reactivations.
|
||||
*/
|
||||
internal fun forget(dropped: Occurrence): DispatchingFront = DispatchingFront(this,
|
||||
ruleIndex.forOccurrence(dropped)
|
||||
.mapNotNull { rule -> ruletag2probe[rule.uniqueTag()] }
|
||||
.map { probe -> probe.forget(dropped) })
|
||||
|
||||
|
||||
/**
|
||||
* Serves to indicate that the specified [RuleMatchEx] has been processed (consumed) and has to
|
||||
* be excluded from any further "match" set returned by [matches].
|
||||
|
|
@ -147,19 +124,6 @@ class Dispatcher (val ruleIndex: RuleIndex, prevState: DispatchingFrontState = e
|
|||
return DispatchingFront(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Forgets that the specified [RuleMatchEx] has been consumed.
|
||||
*/
|
||||
internal fun forget(consumedMatch: RuleMatchEx): DispatchingFront {
|
||||
ruletag2probe[consumedMatch.rule().uniqueTag()]?.let {
|
||||
val probe = it.forget(consumedMatch)
|
||||
if (RULE_MATCHER_PROBE_PERSISTENT) {
|
||||
ruletag2probe[consumedMatch.rule().uniqueTag()] = probe
|
||||
}
|
||||
}
|
||||
return DispatchingFront(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,19 +54,6 @@ interface RuleMatchingProbe {
|
|||
*/
|
||||
fun consume(ruleMatch: RuleMatchEx): RuleMatchingProbe
|
||||
|
||||
/**
|
||||
* Clears all internal state related to [ruleMatch].
|
||||
* Effect is as if [ruleMatch] was never seen.
|
||||
*/
|
||||
fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe
|
||||
|
||||
/**
|
||||
* Clears all state related to [Occurrence] [occ].
|
||||
* Same as [contract], but also clears internal state.
|
||||
* Effect is as if [occ] was never seen.
|
||||
*/
|
||||
fun forget(occ: Occurrence): RuleMatchingProbe
|
||||
|
||||
fun expand(occ: Occurrence): RuleMatchingProbe
|
||||
|
||||
fun expand(occ: Occurrence, mask: BitSet, profiler: Profiler? = null): RuleMatchingProbe
|
||||
|
|
@ -75,22 +62,4 @@ interface RuleMatchingProbe {
|
|||
* Tells the probe that [occ] can't be used for finding matches.
|
||||
*/
|
||||
fun contract(occ: Occurrence): RuleMatchingProbe
|
||||
|
||||
/**
|
||||
* Turns the Probe's processing-related state linked with
|
||||
* [occ] from "fully-expanded" to "partially-expanded".
|
||||
* Doesn't modify internal state related to [matches], instead
|
||||
* modifies how the next [expand] of [occ] will be processed.
|
||||
*
|
||||
* Notion of "partially-expanded" state is important
|
||||
* only for incremental execution.
|
||||
*/
|
||||
fun forgetExpanded(occ: Occurrence): RuleMatchingProbe
|
||||
|
||||
/**
|
||||
* Clears internal state related to [consume]
|
||||
* as if [occ] were never seen by this probe.
|
||||
*/
|
||||
fun forgetConsumed(occ: Occurrence): RuleMatchingProbe
|
||||
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core
|
||||
|
||||
import jetbrains.mps.logic.reactor.program.Rule
|
||||
|
||||
|
||||
@Deprecated("superfluous")
|
||||
class RulesDiff(
|
||||
preserved: Iterable<Rule>,
|
||||
val added: Iterable<Rule>,
|
||||
val removed: Set<Any>
|
||||
) {
|
||||
private val preserved: Map<Any, Rule> = HashMap<Any, Rule>().apply {
|
||||
preserved.forEach { put(it.uniqueTag(), it) }
|
||||
}
|
||||
|
||||
fun getPreservedRule(utag: Any): Rule? = preserved[utag]
|
||||
|
||||
fun getPreserved(): Set<Any> = preserved.keys
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun emptyDiff() = RulesDiff(emptyList(), emptyList(), emptySet())
|
||||
|
||||
@JvmStatic
|
||||
fun findDiff(old: Iterable<Rule>, new: Iterable<Rule>): RulesDiff {
|
||||
val oldTagsSet = old.map { it.uniqueTag() }.toHashSet()
|
||||
val newTagsSet = new.map { it.uniqueTag() }.toHashSet()
|
||||
|
||||
val added = new.filter { !oldTagsSet.contains(it.uniqueTag()) }
|
||||
val (preserved, removed) = old.partition { newTagsSet.contains(it.uniqueTag()) }
|
||||
val removedTags: Set<Any> = removed.map { it.uniqueTag() }.toSet()
|
||||
|
||||
return RulesDiff(preserved, added, removedTags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,8 +51,6 @@ internal class ConstraintsProcessing(
|
|||
|
||||
fun setStrategy(strategy: ProcessingStrategy) { this.incrementalProcessing = strategy }
|
||||
|
||||
fun getStateCleaner(): ProgramStateCleaner = ProgramStateCleaner()
|
||||
|
||||
fun getFrontState(): DispatchingFrontState = dispatchingFront.state()
|
||||
|
||||
fun engage(controller: Controller) {
|
||||
|
|
@ -63,18 +61,6 @@ internal class ConstraintsProcessing(
|
|||
logicalState.clearController(controller)
|
||||
}
|
||||
|
||||
fun activateContinue(controller: Controller, activeOcc: Occurrence, parent: MatchJournal.MatchChunk): FeedbackStatus {
|
||||
assert(activeOcc.stored)
|
||||
|
||||
// Forget that occurrence was seen. Otherwise it will be
|
||||
// processed as with reactivation through observers.
|
||||
this.dispatchingFront = dispatchingFront.forgetExpanded(activeOcc)
|
||||
|
||||
trace.activateContinue(activeOcc)
|
||||
|
||||
return processActivated(controller, activeOcc, parent, FeedbackStatus.NORMAL())
|
||||
}
|
||||
|
||||
fun evaluate(controller: Controller, prototype: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus =
|
||||
profiler.profile<FeedbackStatus>("activate_${prototype.constraint().symbol()}") {
|
||||
// fixme: ensure justifications are tracked (incremented) correctly in processing & creator
|
||||
|
|
@ -185,17 +171,11 @@ internal class ConstraintsProcessing(
|
|||
profiler.profile("discardOccurrence") {
|
||||
|
||||
match.forEachReplaced { occ ->
|
||||
// Principal occurrences must be preserved for future incremental evaluation sessions
|
||||
if (!occ.isPrincipal) {
|
||||
this.dispatchingFront = dispatchingFront.contract(occ)
|
||||
}
|
||||
|
||||
this.dispatchingFront = dispatchingFront.contract(occ)
|
||||
occ.stored = false
|
||||
|
||||
profiler.profile("terminateOccurrence") {
|
||||
|
||||
occ.terminate(logicalState)
|
||||
|
||||
}
|
||||
|
||||
trace.discard(occ)
|
||||
|
|
@ -203,19 +183,7 @@ internal class ConstraintsProcessing(
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
inner class ProgramStateCleaner{
|
||||
fun erase(occurrence: Occurrence) {
|
||||
dispatchingFront = dispatchingFront.forget(occurrence)
|
||||
occurrence.terminate(logicalState)
|
||||
incrementalProcessing.processInvalidated(occurrence, logicalState)
|
||||
}
|
||||
|
||||
fun erase(match: RuleMatchEx) {
|
||||
dispatchingFront = dispatchingFront.forget(match)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulates logic for deriving [Evidence] and [Justifications] for a new [Occurrence].
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.Occurrence
|
||||
|
||||
internal interface ContinuedActivationSink {
|
||||
|
||||
fun offerAll(continueFromPos: MatchJournal.Pos, occs: Iterable<Occurrence>)
|
||||
|
||||
fun offer(continueFromPos: MatchJournal.Pos, ancestor: MatchJournal.OccChunk): Boolean
|
||||
}
|
||||
|
|
@ -65,7 +65,6 @@ internal interface SessionManager {
|
|||
* Bundle of all entities involved in a session.
|
||||
*/
|
||||
internal data class SessionParts(
|
||||
val preambleInfo: PreambleInfo,
|
||||
val ruleIndex: RuleIndex,
|
||||
val journal: MatchJournal,
|
||||
val logicalState: LogicalState,
|
||||
|
|
@ -89,45 +88,10 @@ internal class EvaluationSessionImpl private constructor (
|
|||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> parameter(key: ParameterKey<T>): T? = params ?.get(key) as T
|
||||
|
||||
private fun SessionToken?.hasBindingObservers() =
|
||||
(this as? SessionTokenImpl)?.principalObservers?.isNotEmpty() ?: false
|
||||
|
||||
private fun launch(token: SessionToken?, store: OccurrenceStore, main: Constraint): EvaluationResult {
|
||||
val sessionProcessing: SessionManager =
|
||||
with(incrementality) {
|
||||
when {
|
||||
ability().allowed() -> when {
|
||||
// it's a too restrictive place for fallback, happens almost always
|
||||
//token.hasBindingObservers() -> FallbackProcessingSession()
|
||||
incrLevel() == IncrementalSpec.IncrLevel.Full -> IncrementalProcessingSession()
|
||||
incrLevel() == IncrementalSpec.IncrLevel.Preamble -> PreambleProcessingSession()
|
||||
else -> DefaultProcessingSession()
|
||||
}
|
||||
else -> DefaultProcessingSession()
|
||||
}
|
||||
}
|
||||
|
||||
val session =
|
||||
with(sessionProcessing) {
|
||||
if (token != null) {
|
||||
(token as? SessionTokenImpl)?.setStore(store)
|
||||
nextSession(token)
|
||||
} else {
|
||||
firstSession()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return sessionProcessing.runSession(session, main)
|
||||
|
||||
// fixme: exception is a dirty way to abort incremental processing.
|
||||
} catch (e: IncrementalContractViolationException) {
|
||||
with(FallbackProcessingSession(session)) {
|
||||
val fallbackSession =
|
||||
if (token != null) nextSession(token) else firstSession()
|
||||
return runSession(fallbackSession, main)
|
||||
}
|
||||
}
|
||||
private fun launch(token: SessionToken?, main: Constraint): EvaluationResult {
|
||||
val sessionProcessing: SessionManager = DefaultProcessingSession()
|
||||
val session = sessionProcessing.firstSession()
|
||||
return sessionProcessing.runSession(session, main)
|
||||
}
|
||||
|
||||
open inner class DefaultProcessingSession: SessionManager {
|
||||
|
|
@ -152,11 +116,11 @@ internal class EvaluationSessionImpl private constructor (
|
|||
|
||||
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
|
||||
|
||||
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, PrincipalObserverDispatcher.EMPTY)
|
||||
return SessionParts(ruleIndex, journal, logicalState, controller, processing, processingStrategy, PrincipalObserverDispatcher.EMPTY)
|
||||
}
|
||||
|
||||
override fun endSession(session: SessionParts): SessionToken = with(session) {
|
||||
SessionTokenImpl(journal.view(), emptyList(), ruleIndex.toRules(), emptyFrontState(), ruleIndex, logicalState, principalObservers.apply { clearTriggerReceiver() })
|
||||
SessionTokenImpl(journal.view(), ruleIndex.toRules(), emptyFrontState(), ruleIndex, logicalState, principalObservers.apply { clearTriggerReceiver() })
|
||||
}
|
||||
|
||||
override fun runSession(session: SessionParts, main: Constraint): EvaluationResult = with(session) {
|
||||
|
|
@ -169,190 +133,7 @@ internal class EvaluationSessionImpl private constructor (
|
|||
protected fun SessionParts.run(main: Constraint): FeedbackStatus = strategy.run(processing, controller, main)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as [DefaultProcessingSession], but also returns
|
||||
* [EvaluationResult.invalidFeedbackKeys] & [EvaluationResult.invalidRules]
|
||||
* to signify that results of a previous session must be cleared.
|
||||
*/
|
||||
inner class FallbackProcessingSession(private val abortedSession: SessionParts?): DefaultProcessingSession() {
|
||||
|
||||
constructor(): this(null)
|
||||
|
||||
val invalidatedFeedback: MutableFeedbackKeySet = mutableSetOf()
|
||||
val invalidatedRules: MutableList<Any> = mutableListOf()
|
||||
|
||||
|
||||
override fun nextSession(token: SessionToken): SessionParts {
|
||||
|
||||
invalidateToken(token.journalView as MatchJournal.View)
|
||||
invalidateAborted()
|
||||
|
||||
return super.nextSession(token)
|
||||
}
|
||||
|
||||
override fun runSession(session: SessionParts, main: Constraint): EvaluationResult = with(session) {
|
||||
val status = run(main)
|
||||
controller.shutDown()
|
||||
val newToken = endSession(session)
|
||||
|
||||
invalidatedFeedback.addAll(strategy.invalidatedFeedback())
|
||||
invalidatedRules.addAll(strategy.invalidatedRules())
|
||||
|
||||
return EvaluationResultImpl(newToken, status, invalidatedFeedback, invalidatedRules)
|
||||
}
|
||||
|
||||
private fun invalidateAborted() {
|
||||
if(abortedSession != null) {
|
||||
with (abortedSession) {
|
||||
invalidatedFeedback.addAll(strategy.invalidatedFeedback())
|
||||
invalidatedRules.addAll(strategy.invalidatedRules())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidateToken(view: MatchJournal.View) {
|
||||
for(chunk in view.chunks) {
|
||||
if (chunk is MatchJournal.MatchChunk) {
|
||||
invalidatedFeedback.add(chunk.match.feedbackKey)
|
||||
invalidatedRules.add(chunk.ruleUniqueTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open inner class IncrementalProcessingSession(): DefaultProcessingSession() {
|
||||
|
||||
/**
|
||||
* Same as [DefaultProcessingSession.firstSession],
|
||||
* but uses [GroundProcessing] instead of [EmptyProcessing]
|
||||
*/
|
||||
override fun firstSession(): SessionParts {
|
||||
val ruleIndex = RuleIndex(program.rules())
|
||||
val journal = MatchJournalImpl(incrementality)
|
||||
val logicalState = LogicalState()
|
||||
val dispatchingFront = Dispatcher(ruleIndex).front()
|
||||
|
||||
val principalObservers = LogicalBindObserverDispatcher()
|
||||
val processingStrategy = GroundProcessing(incrementality, principalObservers)
|
||||
|
||||
val processing = ConstraintsProcessing(dispatchingFront, journal, logicalState, incrementality, trace, profiler)
|
||||
processing.setStrategy(processingStrategy)
|
||||
|
||||
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
|
||||
|
||||
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, principalObservers)
|
||||
}
|
||||
|
||||
override fun nextSession(token: SessionToken): SessionParts {
|
||||
val tkn = token as SessionTokenImpl
|
||||
val logicalState = tkn.logicalState
|
||||
|
||||
val ruleIndex = tkn.ruleIndex.apply { updateIndexFromRules(program.rules()) }
|
||||
val journal = MatchJournalImpl(incrementality, tkn.journalView as MatchJournal.View)
|
||||
val front = Dispatcher(ruleIndex, tkn.getFrontState()).front()
|
||||
val processing = ConstraintsProcessing(front, journal, logicalState, incrementality, trace, profiler)
|
||||
|
||||
val principalObservers = tkn.principalObservers
|
||||
val processingStrategy = IncrementalProcessing(
|
||||
incrementality, journal, program.newRules(), program.droppedRules(),
|
||||
processing.getStateCleaner(), ruleIndex, principalObservers, trace
|
||||
)
|
||||
processing.setStrategy(processingStrategy)
|
||||
|
||||
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
|
||||
|
||||
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, principalObservers)
|
||||
}
|
||||
|
||||
override fun endSession(session: SessionParts): SessionToken = with(session) {
|
||||
val histView = journal.view()
|
||||
processing.resetStore() // clear observers
|
||||
val principalState = sessionState(frontState).resetLookup()
|
||||
principalObservers.clearTriggerReceiver()
|
||||
return SessionTokenImpl(histView, emptyList(), ruleIndex.toRules(), principalState, ruleIndex, logicalState, principalObservers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve data needed between sessions:
|
||||
* preserve only relevant and non-empty RuleMatchers
|
||||
*/
|
||||
protected fun sessionState(frontState: DispatchingFrontState) =
|
||||
frontState.filterValues { ruleMatcher ->
|
||||
incrementality.isPrincipal(ruleMatcher.rule()) || ruleMatcher.probe().hasOccurrences()
|
||||
}
|
||||
}
|
||||
|
||||
inner class PreambleProcessingSession(): IncrementalProcessingSession() {
|
||||
private var inputStore: OccurrenceStore = emptyStore()
|
||||
|
||||
override fun nextSession(token: SessionToken): SessionParts {
|
||||
val tkn = token as SessionTokenImpl
|
||||
val logicalState = LogicalState()
|
||||
|
||||
// fixme: ensure why ruleIndex.update here leads to errors
|
||||
val ruleIndex = tkn.ruleIndex.apply{ updateIndexFromRules(program.rules()) }
|
||||
// val ruleIndex = RuleIndex(program.rules())
|
||||
val front = Dispatcher(ruleIndex).front() // new dispatcher front
|
||||
val journal = MatchJournalImpl(incrementality) // new journal
|
||||
val processing = ConstraintsProcessing(front, journal, logicalState, incrementality, trace, profiler)
|
||||
|
||||
this.inputStore = tkn.principalStore
|
||||
val processingStrategy = CachedOccurrencesProcessing(incrementality, inputStore)
|
||||
processing.setStrategy(processingStrategy)
|
||||
|
||||
val controller = ControllerImpl(supervisor, processing, incrementality, trace, profiler)
|
||||
|
||||
return SessionParts(program.preambleInfo(), ruleIndex, journal, logicalState, controller, processing, processingStrategy, tkn.principalObservers)
|
||||
}
|
||||
|
||||
override fun endSession(session: SessionParts): SessionToken = with(session) {
|
||||
val histView = journal.view()
|
||||
val outputOccurrences = histView.filterOccurrences(inputStore)
|
||||
|
||||
outputOccurrences.forEach{ it.terminate(logicalState) }
|
||||
processing.resetStore() // clear observers
|
||||
logicalState.reset()
|
||||
principalObservers.clearTriggerReceiver()
|
||||
|
||||
val rules = ruleIndex.toRules().filter(preambleInfo::inPreamble)
|
||||
|
||||
SessionTokenImpl(histView, outputOccurrences, rules, emptyFrontState(), ruleIndex, LogicalState())
|
||||
}
|
||||
|
||||
private fun MatchJournal.View.filterOccurrences(without: OccurrenceStore = emptyStore()): OccurrenceStore {
|
||||
val withoutSources = without.asSequence().map { it.cacheKey() }.toHashSet()
|
||||
return chunks.asSequence()
|
||||
.mapNotNull { (it as? MatchJournal.OccChunk)?.occ }
|
||||
.filter { it.stored }
|
||||
.filter { !withoutSources.contains(it.cacheKey()) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun ConstraintOccurrence.cacheKey() = sourceRule() to constraint().symbol()
|
||||
|
||||
private fun getPreamble(session: SessionParts): MatchJournal.View = with(session) {
|
||||
val journalView = journal.view()
|
||||
val preambleView = journalView.getPreamble(preambleInfo)
|
||||
// unnecessary work if dispatcher state isn't passed in SessionToken
|
||||
// cleanNonPreamble(processing, journalView.chunks, preambleView)
|
||||
preambleView
|
||||
}
|
||||
|
||||
private fun cleanNonPreamble(processing: ConstraintsProcessing, chunks: Iterable<MatchJournal.Chunk>, preambleView: MatchJournal.View) {
|
||||
val preambleSet = preambleView.chunks.toSet()
|
||||
val cleaner = processing.getStateCleaner()
|
||||
|
||||
chunks.asSequence().filterNot(preambleSet::contains).forEach {
|
||||
it.activatedLog().forEach(cleaner::erase)
|
||||
if (it is MatchJournal.MatchChunk) {
|
||||
cleaner.erase(it.match as RuleMatchEx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class Config(val program: Program) : EvaluationSession.Config() {
|
||||
|
||||
val parameters = HashMap<ParameterKey<*>, Any>()
|
||||
|
|
@ -363,22 +144,11 @@ internal class EvaluationSessionImpl private constructor (
|
|||
|
||||
var token: SessionToken? = null
|
||||
|
||||
var principalStore: Collection<Occurrence> = emptyList()
|
||||
|
||||
override fun withTrace(computingTracer: EvaluationTrace): EvaluationSession.Config {
|
||||
this.evaluationTrace = computingTracer
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withStoreView(storeView: StoreView): EvaluationSession.Config {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withStore(store: Collection<ConstraintOccurrence>): EvaluationSession.Config {
|
||||
this.principalStore = store as Collection<Occurrence>
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withSessionToken(token: SessionToken?): EvaluationSession.Config {
|
||||
this.token = token
|
||||
return this
|
||||
|
|
@ -407,7 +177,7 @@ internal class EvaluationSessionImpl private constructor (
|
|||
Backend.ourBackend.ourSession.set(session)
|
||||
try {
|
||||
val main = parameters[ParameterKey.of("main", Constraint::class.java)] as Constraint
|
||||
return session.launch(token, principalStore, main)
|
||||
return session.launch(token, main)
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import jetbrains.mps.logic.reactor.program.IncrementalContractViolationException
|
|||
import jetbrains.mps.logic.reactor.program.IncrementalSpec
|
||||
import jetbrains.mps.logic.reactor.program.Rule
|
||||
|
||||
@Deprecated("obsolete class")
|
||||
interface IncrSpecHolder {
|
||||
val ispec: IncrementalSpec
|
||||
|
||||
|
|
@ -34,18 +35,3 @@ interface IncrSpecHolder {
|
|||
val Rule.isWeakPrincipal get() = ispec.isWeakPrincipal(this)
|
||||
|
||||
}
|
||||
|
||||
|
||||
inline fun checkContract(value: Boolean) {
|
||||
checkContract(value) { "Contract assertion failed" }
|
||||
}
|
||||
inline fun checkContract(value: Boolean, lazyMsg: () -> String) {
|
||||
if (!value) throw IncrementalContractViolationException(lazyMsg())
|
||||
}
|
||||
|
||||
inline fun IncrSpecHolder.assertContract(lazyValue: () -> Boolean) {
|
||||
assertContract(lazyValue) { "Contract assertion failed" }
|
||||
}
|
||||
inline fun IncrSpecHolder.assertContract(lazyValue: () -> Boolean, noinline lazyMsg: () -> String) {
|
||||
if (ispec.assertLevel().assertContracts()) checkContract(lazyValue(), lazyMsg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,489 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014-2020 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.*
|
||||
import jetbrains.mps.logic.reactor.evaluation.EvaluationTrace
|
||||
import jetbrains.mps.logic.reactor.program.IncrementalSpec
|
||||
import jetbrains.mps.logic.reactor.program.Rule
|
||||
import java.util.PriorityQueue
|
||||
|
||||
|
||||
/**
|
||||
* Incremental stages handle different parts of incremental processing.
|
||||
*
|
||||
* Each incremental stage is applied at each journal cursor position
|
||||
* with read-only rights (see [ChunkReader]) while traversing [MatchJournal].
|
||||
*/
|
||||
internal interface IncrementalStage: IncrSpecHolder {
|
||||
|
||||
// fun receive(data: Iterable<R>): Boolean
|
||||
|
||||
// fun onNext(reader: MatchJournal.ChunkReader): Collection<T>
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invalidation stage includes several activities:
|
||||
* - removing from chunks (i.e. principal matches) that correspond
|
||||
* to rules removed from program, their dependent chunks, and those
|
||||
* dependent on additional invalidated [Justified] entities from [receive];
|
||||
* - reactivating occurrences that led to invalidated matches (through [activationSink]);
|
||||
* - pruning invalidated occurrences and matches from [Dispatcher.DispatchingFront]'s state.
|
||||
*/
|
||||
internal class InvalidationStage(
|
||||
override val ispec: IncrementalSpec,
|
||||
private val posTracker: PosTracking,
|
||||
private val invalidRuleIds: Set<Any>,
|
||||
private val activationSink: ContinuedActivationSink,
|
||||
private val stateCleaner: ConstraintsProcessing.ProgramStateCleaner,
|
||||
private val trace: EvaluationTrace
|
||||
): IncrementalStage {
|
||||
|
||||
private val invalidJustifications = mutableListOf<Justified>()
|
||||
|
||||
private val invalidFeedbackKeys: MutableSet<Any> = mutableSetOf<Any>()
|
||||
|
||||
private val invalidRuleIdsAll: MutableList<Any> = mutableListOf<Any>()
|
||||
|
||||
|
||||
fun invalidatedFeedback(): FeedbackKeySet = invalidFeedbackKeys.toHashSet()
|
||||
|
||||
fun invalidatedRules(): List<Any> = invalidRuleIdsAll.toList()
|
||||
|
||||
fun receive(invalid: Iterable<Justified>) { invalidJustifications.addAll(invalid) }
|
||||
|
||||
fun receive(invalid: Justified) { invalidJustifications.add(invalid) }
|
||||
|
||||
/**
|
||||
* Invalidates next chunk, if needed.
|
||||
* Doesn't remove the [Chunk] from the [Journal].
|
||||
* Returns [true] if chunk is invalidated and must be removed.
|
||||
*/
|
||||
fun onNext(reader: ChunkReader): Boolean {
|
||||
val chunk = reader.next
|
||||
if (chunk is MatchJournal.MatchChunk && chunk.dependsOnAny(invalidRuleIds)) {
|
||||
invalidJustifications.add(chunk)
|
||||
}
|
||||
|
||||
// Invalidating dependent chunks
|
||||
if (chunk.justifiedByAny(invalidJustifications)) {
|
||||
|
||||
val validOccs = invalidateChunk(chunk)
|
||||
activationSink.offerAll(reader.current.toPos(), validOccs)
|
||||
|
||||
// Remove chunk from the journal
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun invalidateChunk(chunk: MatchJournal.Chunk): Iterable<Occurrence> {
|
||||
// 'Undo' all activated in this chunk occurrences: clear Dispatcher & LogicalState
|
||||
chunk.activatedLog().forEach(stateCleaner::erase)
|
||||
|
||||
val validOccs: Sequence<Occurrence>
|
||||
if (chunk is MatchJournal.MatchChunk) {
|
||||
with (chunk.match) {
|
||||
trace.invalidate(this)
|
||||
|
||||
// Don't accidentaly invalidate new rules.
|
||||
// New rules themselves remain valid, only their effects must be cleared.
|
||||
if (posTracker.isOld(chunk)) {
|
||||
invalidRuleIdsAll.add(rule().uniqueTag())
|
||||
}
|
||||
// So invalidate feedback they produced
|
||||
invalidFeedbackKeys.add(feedbackKey)
|
||||
|
||||
stateCleaner.erase(this as RuleMatchEx)
|
||||
|
||||
// Valid head occurrences could match more rules
|
||||
// without this match, so need to reactivate them.
|
||||
// E.g. occurrences discarded in this match on
|
||||
// previous run but revived here can match more rules.
|
||||
validOccs = allHeads().filter { occ ->
|
||||
!occ.justifiedByAny(invalidJustifications)
|
||||
}
|
||||
// By definition of Chunk and principal rule,
|
||||
// all occurrences from the head are principal.
|
||||
assert(allHeads().all { it.isPrincipal })
|
||||
}
|
||||
} else validOccs = emptySequence()
|
||||
return validOccs.asIterable()
|
||||
}
|
||||
|
||||
private fun MatchJournal.MatchChunk.dependsOnAny(utags: Iterable<Any>): Boolean =
|
||||
utags.contains(ruleUniqueTag) || utags.any(::dependsOnRule)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Addition stage includes:
|
||||
* - searching for potential matches for new [addedRules] (see [addRuleCandidates])
|
||||
* - receiving additional [MatchCandidates] (e.g. postponed matches)
|
||||
* - offering both to [ContinuedActivationSink] at right positions in [MatchJournal]
|
||||
* and in correct order according to rule priorities (see [offerCandidates])
|
||||
*/
|
||||
internal class AdditionStage(
|
||||
override val ispec: IncrementalSpec,
|
||||
private val posTracker: PosTracking,
|
||||
private val addedRules: Iterable<Rule>,
|
||||
private val activationSink: ContinuedActivationSink,
|
||||
private val ruleOrdering: RuleOrdering,
|
||||
private val ruleIndex: RuleIndex,
|
||||
private val trace: EvaluationTrace
|
||||
): IncrementalStage {
|
||||
|
||||
interface MatchCandidate {
|
||||
val rule: Rule
|
||||
val occChunk: MatchJournal.OccChunk
|
||||
}
|
||||
|
||||
private data class PotentialMatch(
|
||||
override val rule: Rule,
|
||||
override val occChunk: MatchJournal.OccChunk
|
||||
): MatchCandidate
|
||||
|
||||
|
||||
private val activationCandidates = PriorityQueue<MatchCandidate>{
|
||||
lhs, rhs -> ruleOrdering.compare(lhs.rule, rhs.rule)
|
||||
}
|
||||
|
||||
private val hasRules = addedRules.iterator().hasNext() // it's constant
|
||||
|
||||
|
||||
fun onNext(reader: ChunkReader) {
|
||||
val chunk = reader.current
|
||||
if (hasRules && chunk is MatchJournal.OccChunk) {
|
||||
addRuleCandidates(chunk)
|
||||
}
|
||||
offerCandidates(reader)
|
||||
}
|
||||
|
||||
fun onRewind(reader: ChunkReader) = with(posTracker) {
|
||||
activationCandidates.removeIf {
|
||||
isNew(it.occChunk) || isFuture(it.occChunk)
|
||||
}
|
||||
}
|
||||
|
||||
fun receive(candidates: Iterable<MatchCandidate>) =
|
||||
activationCandidates.addAll(candidates)
|
||||
|
||||
private fun addRuleCandidates(chunk: MatchJournal.OccChunk) {
|
||||
// filters out rules using occurrence's arguments
|
||||
val allRuleCandidates = ruleIndex.forOccurrence(chunk.occ).map { it.uniqueTag() }.toHashSet()
|
||||
|
||||
for (rule in addedRules) {
|
||||
if (allRuleCandidates.contains(rule.uniqueTag()) && rule.canMatch(chunk.occ.constraint)) {
|
||||
// Can this rule be matched by principal occurrence?
|
||||
// Then we will need to find the place among existing child chunks
|
||||
// (i.e. among some number of following ones)
|
||||
// to activate this occurrence, to (possibly) match this rule.
|
||||
// Also remember the parent justification of this rule candidate
|
||||
// to drop it from monitoring when child chunks end.
|
||||
activationCandidates.add(PotentialMatch(rule, chunk))
|
||||
// todo: also use the rule to help Dispatcher in future?
|
||||
// i.e. try matching only on the candidate rule
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun offerCandidates(reader: ChunkReader) {
|
||||
val aIt = activationCandidates.iterator()
|
||||
while (aIt.hasNext()) {
|
||||
val candidate = aIt.next()
|
||||
val candidateRule = candidate.rule
|
||||
val occChunk = candidate.occChunk
|
||||
|
||||
val pos =
|
||||
if (ruleOrdering.canBeInserted(candidateRule, occChunk, reader.next) || reader.atEnd())
|
||||
reader.current.toPos()
|
||||
else
|
||||
continue
|
||||
|
||||
activationSink.offer(pos, occChunk)
|
||||
trace.potentialMatch(occChunk.occ, candidateRule)
|
||||
// Drop the candidate if appropriate activation place is found.
|
||||
aIt.remove()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// todo; extract more restricted interface (w/o rewind) for use in stages
|
||||
internal class PosTracking(
|
||||
val index: MatchJournal.Index,
|
||||
private val journal: MatchJournal,
|
||||
initPos: MatchJournal.Pos = journal.initialChunk().toPos()
|
||||
) {
|
||||
|
||||
/**
|
||||
* Serves as a reference point for determining [isFuture] and [isPast].
|
||||
* Contract: [MatchJournal.Index.isKnown] is always `true` for [lastVisited].
|
||||
* Updated on each [onNext].
|
||||
*/
|
||||
private var lastVisited: MatchJournal.Pos = initPos
|
||||
|
||||
/**
|
||||
* Contract: [MatchJournal.Index.isKnown] is always `true` for [front].
|
||||
* Updated only on [rewind], not on each [onNext].
|
||||
*/
|
||||
private var front: MatchJournal.Pos = initPos
|
||||
|
||||
|
||||
private fun updateReferencePos(current: MatchJournal.Chunk) {
|
||||
if (index.isKnown(current)) {
|
||||
lastVisited = current.toPos()
|
||||
}
|
||||
}
|
||||
|
||||
fun rewind(pos: MatchJournal.Pos) = with(index) {
|
||||
assert(isKnown(pos.chunk))
|
||||
assert(pos before lastVisited)
|
||||
|
||||
journal.resetCursor(pos)
|
||||
|
||||
if (lastVisited after front) { // rewind can happen inside another rewind and appear inferior
|
||||
front = lastVisited
|
||||
}
|
||||
lastVisited = pos
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks [MatchJournal] position as a reference point for [isFuture].
|
||||
*/
|
||||
fun onNext(reader: ChunkReader): Unit =
|
||||
updateReferencePos(reader.current)
|
||||
|
||||
|
||||
fun isOld(chunk: MatchJournal.Chunk): Boolean =
|
||||
index.isKnown(chunk)
|
||||
fun isOld(occ: Occurrence): Boolean =
|
||||
index.isKnown(occ)
|
||||
|
||||
fun isNew(chunk: MatchJournal.Chunk): Boolean =
|
||||
!index.isKnown(chunk)
|
||||
fun isNew(occ: Occurrence): Boolean =
|
||||
!index.isKnown(occ)
|
||||
|
||||
|
||||
fun isFront(): Boolean =
|
||||
with(index) { lastVisited afterOrEq front }
|
||||
|
||||
fun isFuture(pos: MatchJournal.Pos): Boolean =
|
||||
with(index) { pos after lastVisited }
|
||||
fun isFuture(chunk: MatchJournal.Chunk) = isFuture(chunk.toPos())
|
||||
|
||||
fun isPast(pos: MatchJournal.Pos): Boolean =
|
||||
with(index) { pos before lastVisited }
|
||||
fun isPast(chunk: MatchJournal.Chunk) = isPast(chunk.toPos())
|
||||
}
|
||||
|
||||
|
||||
internal class PostponeMatchesStage(
|
||||
override val ispec: IncrementalSpec,
|
||||
private val posTracker: PosTracking,
|
||||
private val ruleOrdering: RuleOrdering
|
||||
): IncrementalStage {
|
||||
|
||||
private data class PostponedMatch(
|
||||
val match: RuleMatchEx,
|
||||
override val occChunk: MatchJournal.OccChunk
|
||||
): AdditionStage.MatchCandidate {
|
||||
override val rule: Rule = match.rule()
|
||||
|
||||
init { assert(match.allHeads().contains(occChunk.occ)) }
|
||||
}
|
||||
|
||||
|
||||
private val postponedMatches: MutableMap<Int, MutableCollection<RuleMatchEx>> = hashMapOf()
|
||||
|
||||
|
||||
fun onNext(reader: ChunkReader): Collection<AdditionStage.MatchCandidate> {
|
||||
return (reader.current as? MatchJournal.OccChunk)?.let { occChunk ->
|
||||
postponedMatches.remove(occChunk.identity)?.map { PostponedMatch(it, occChunk) }
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Postpones future matches from [matches] on [active]
|
||||
* and adds postponed matches to the resulting list (if there're any).
|
||||
*
|
||||
* @return matches ready for processing, sorted according to rule priorities.
|
||||
*/
|
||||
fun process(active: Occurrence, matches: List<RuleMatchEx>): List<RuleMatchEx> =
|
||||
postponeFutureMatches(matches).withPostponedMatches(active)
|
||||
|
||||
/**
|
||||
* Determines, filters out, and postpones future matches.
|
||||
* Returns only current matches.
|
||||
*
|
||||
* Future match is a match, which has heads that are not yet
|
||||
* activated according to current [MatchJournal] position.
|
||||
*/
|
||||
fun postponeFutureMatches(matches: List<RuleMatchEx>): List<RuleMatchEx> {
|
||||
val currentMatches = mutableListOf<RuleMatchEx>()
|
||||
for (m in matches) {
|
||||
// Returns null for matches with occurrences only from this session
|
||||
// because journalIndex indexes only previous session.
|
||||
val occChunk = posTracker.index.activationPos(m)
|
||||
if (occChunk != null && posTracker.isFuture(occChunk.toPos())) {
|
||||
postponedMatches.getOrPut(occChunk.identity, ::mutableListOf).add(m)
|
||||
|
||||
} else {
|
||||
currentMatches.add(m)
|
||||
}
|
||||
}
|
||||
return currentMatches
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds previously postponed matches on [active] [Occurrence], if any, or returns original list.
|
||||
*
|
||||
* @return matches sorted according to rule priorities or original list.
|
||||
*/
|
||||
private fun List<RuleMatchEx>.withPostponedMatches(active: Occurrence): List<RuleMatchEx> =
|
||||
postponedMatches.remove(active.identity)?.let { postponed ->
|
||||
(this + postponed).sortedWith(ruleOrdering.matchComparator)
|
||||
} ?: this
|
||||
|
||||
private val MatchJournal.OccChunk.identity get() = this.occ.identity
|
||||
}
|
||||
|
||||
|
||||
internal class ContinueOccurrencesStage(
|
||||
override val ispec: IncrementalSpec,
|
||||
private val journalIndex: MatchJournal.Index
|
||||
): IncrementalStage, ContinuedActivationSink {
|
||||
|
||||
/**
|
||||
* Specifies position in [MatchJournal] for continuing program evaluation.
|
||||
* Position of [reactivated] in journal must precede execution position [continueFrom].
|
||||
* (see contract in [assertValid]).
|
||||
*/
|
||||
private data class ExecPos(val continueFrom: MatchJournal.Pos, val reactivated: MatchJournal.OccChunk) {
|
||||
val reactivatedOcc: Occurrence get() = reactivated.occ
|
||||
}
|
||||
|
||||
private infix fun ChunkReader.at(execPos: ExecPos) =
|
||||
this at execPos.continueFrom.chunk
|
||||
|
||||
private fun ExecPos.assertValid() {
|
||||
assert(journalIndex.isKnown(reactivated)) {
|
||||
"only occurrences from previous session can be incrementally continued"
|
||||
}
|
||||
|
||||
assert({ // lazily
|
||||
if (!isNew(continueFrom.chunk)) { // can't assert it for new chunks (i.e. those from this session)
|
||||
val isAncestor = continueFrom.chunk.justifiedBy(reactivated)
|
||||
val isPredecessor = journalIndex.compare(continueFrom, reactivated.toPos()) >= 0
|
||||
isAncestor || isPredecessor
|
||||
} else true
|
||||
}())
|
||||
}
|
||||
|
||||
|
||||
private val queue: MutableList<ExecPos> = ArrayList<ExecPos>(1 + journalIndex.size / 8) // rough estimate
|
||||
|
||||
private val seen: MutableSet<ExecPos> = HashSet<ExecPos>()
|
||||
|
||||
|
||||
fun onNext(reader: ChunkReader) = mutableListOf<Occurrence>().apply {
|
||||
while (queue.isNotEmpty() && reader at queue.top()) {
|
||||
add(queue.pop().reactivatedOcc)
|
||||
}
|
||||
}
|
||||
|
||||
fun onRewind(reader: ChunkReader): Unit = with(journalIndex) {
|
||||
val rewindPos = reader.next.toPos()
|
||||
|
||||
// clear queue
|
||||
queue.removeIf {
|
||||
it.reactivated.toPos() afterOrEq rewindPos
|
||||
}
|
||||
|
||||
// clear seen
|
||||
seen.removeIf {
|
||||
isNew(it.continueFrom.chunk) || it.continueFrom afterOrEq rewindPos
|
||||
}
|
||||
}
|
||||
|
||||
override fun offerAll(continueFromPos: MatchJournal.Pos, occs: Iterable<Occurrence>) =
|
||||
occs.mapNotNull(journalIndex::activatingChunkOf)
|
||||
.sortedWith(journalIndex.chunkComparator)
|
||||
.forEach { offer(continueFromPos, it) }
|
||||
|
||||
override fun offer(continueFromPos: MatchJournal.Pos, ancestor: MatchJournal.OccChunk): Boolean =
|
||||
ExecPos(continueFromPos, ancestor).let {
|
||||
when {
|
||||
seen.add(it) -> {
|
||||
it.assertValid()
|
||||
queue.push(it)
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNew(chunk: MatchJournal.Chunk) = !journalIndex.isKnown(chunk)
|
||||
|
||||
private fun <E> MutableList<E>.top(): E = this.last()
|
||||
private fun <E> MutableList<E>.pop(): E = this.removeAt(this.size - 1)
|
||||
private fun <E> MutableList<E>.push(element: E): Boolean = this.add(element)
|
||||
}
|
||||
|
||||
|
||||
internal class RewindStage(
|
||||
override val ispec: IncrementalSpec,
|
||||
private val posTracker: PosTracking,
|
||||
private val principalObserver: PrincipalObserverDispatcher
|
||||
): IncrementalStage {
|
||||
|
||||
private val toRewind: PriorityQueue<MatchJournal.MatchChunk> = PriorityQueue(posTracker.index.chunkComparator)
|
||||
|
||||
private val seen = hashSetOf<MatchJournal.Chunk>()
|
||||
|
||||
// precondition: all received occurrences are principal and valid (i.e. not invalidated)
|
||||
fun receive(occs: Sequence<Occurrence>): Boolean =
|
||||
occs.filter(::takeVolatile)
|
||||
.mapNotNull(posTracker.index::activatingChunkOf) // get chunk corresponding to occurrence activation
|
||||
.mapNotNull(posTracker.index::matchChunkOf) // get chunk that activated it
|
||||
.filter(seen::add) // filter already seen
|
||||
.toList().let(toRewind::addAll)
|
||||
|
||||
/**
|
||||
* Returns collection of past chunks for rewind
|
||||
* in sorted order from earliest to latest.
|
||||
*/
|
||||
fun onNext(reader: ChunkReader): Collection<MatchJournal.MatchChunk> =
|
||||
if (needRewind()) {
|
||||
toRewind.toList().also { toRewind.clear() }
|
||||
} else emptyList()
|
||||
|
||||
fun needRewind(): Boolean = toRewind.peek()?.let {
|
||||
posTracker.isPast(it)
|
||||
} ?: false
|
||||
|
||||
|
||||
private fun takeVolatile(occ: Occurrence) =
|
||||
// NB: only occurrences from previous session are considered
|
||||
posTracker.isOld(occ) &&
|
||||
with(principalObserver) { removeTriggered(occ) }
|
||||
|
||||
}
|
||||
|
|
@ -182,27 +182,6 @@ interface MatchJournal : EvidenceSource {
|
|||
override fun getStoreView(): StoreView = StoreViewImpl(
|
||||
chunks.flatMap { it.entries() }.allOccurrences().asSequence()
|
||||
)
|
||||
override fun getPreamble(info: PreambleInfo): View {
|
||||
val cornerChunk = chunks.first() // corner chunk at beginning
|
||||
val initialChunk = chunks[1]
|
||||
val preambleChunks = arrayListOf<Chunk>(cornerChunk, initialChunk)
|
||||
|
||||
for (chunk in chunks.asSequence().drop(preambleChunks.size)) {
|
||||
// chunks which depend only on preamble are added to preamble
|
||||
if (chunk.justifiedOnlyBy(preambleChunks)) {
|
||||
if (chunk is MatchChunk && info.inPreamble(chunk.match.rule())
|
||||
|| chunk is OccChunk)
|
||||
{
|
||||
preambleChunks.add(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
val evidenceSeed = preambleChunks.last().evidence;
|
||||
preambleChunks.add(chunks.last()) // corner chunk at end
|
||||
assert(evidenceSeed == preambleChunks.maxBy { it.evidence }?.evidence)
|
||||
|
||||
return View(preambleChunks, evidenceSeed + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -51,130 +51,3 @@ interface PrincipalObserverDispatcher {
|
|||
override fun removeTriggered(occ: Occurrence): Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures important contract of incremental algorithm.
|
||||
*
|
||||
* Contract states that arguments of principal [Occurrence]s
|
||||
* must be immutable, that is, logicals can't be unified
|
||||
* with either ground or free other logicals.
|
||||
*/
|
||||
internal class LogicalBindObserverDispatcher : PrincipalObserverDispatcher {
|
||||
|
||||
private var receiver: ObserverTriggeredHandler = { false }
|
||||
|
||||
private val observing: HashMap<Int, LogicalBindObserver> = hashMapOf()
|
||||
private val triggered: HashSet<Int> = hashSetOf()
|
||||
|
||||
override fun toString(): String =
|
||||
"${javaClass.name}(observed: ${observing.size}, triggered: ${triggered.size})"
|
||||
|
||||
override fun setTriggerReceiver(receiver: ObserverTriggeredHandler) { this.receiver = receiver }
|
||||
|
||||
override fun clearTriggerReceiver() { receiver = { false } }
|
||||
|
||||
override fun onActivated(occ: Occurrence, observable: LogicalStateObservable) {
|
||||
if (observing.containsKey(occ.identity)) return
|
||||
|
||||
LogicalBindObserver(this, occ, observable).let {
|
||||
if (it.observes) {
|
||||
observing[occ.identity] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInvalidated(occ: Occurrence, observable: LogicalStateObservable) {
|
||||
// NB: works between incremental sessions only if this instance is preserved between them
|
||||
observing.remove(occ.identity)?.removeObservers(observable)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean = triggered.isEmpty() //&& observing.isEmpty()
|
||||
|
||||
override fun isObserving(occ: Occurrence): Boolean = observing.containsKey(occ.identity)
|
||||
|
||||
override fun isTriggered(occ: Occurrence): Boolean = triggered.contains(occ.identity)
|
||||
|
||||
override fun removeTriggered(occ: Occurrence): Boolean = triggered.remove(occ.identity)
|
||||
|
||||
|
||||
fun onTrigger(source: Occurrence, logical: Logical<*>) {
|
||||
observing.remove(source.identity) // logical observers are already removed
|
||||
if (!receiver(source)) {
|
||||
// if receiver didn't accept occurrence,
|
||||
// then remember it for later requests
|
||||
triggered.add(source.identity)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class LogicalBindObserver(
|
||||
private val logicalDispatcher: LogicalBindObserverDispatcher,
|
||||
val source: Occurrence,
|
||||
observable: LogicalStateObservable
|
||||
): ForwardingLogicalObserver {
|
||||
|
||||
private val observed: MutableSet<Logical<*>> = hashSetOf()
|
||||
|
||||
init {
|
||||
for (unboundLogical in source.usedUnboundLogicals()) {
|
||||
observe(unboundLogical, observable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observe(arg: Logical<*>, observable: LogicalStateObservable) {
|
||||
if (!observed.contains(arg)) {
|
||||
observable.addForwardingObserver(arg, this)
|
||||
observed.add(arg)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeObservers(observable: LogicalStateObservable) {
|
||||
for (logical in observed) {
|
||||
observable.removeForwardingObserver(logical, this)
|
||||
}
|
||||
observed.clear()
|
||||
}
|
||||
|
||||
val observes: Boolean = observed.isNotEmpty()
|
||||
|
||||
|
||||
override fun valueUpdated(logical: Logical<*>, controller: Controller) = onUpdated(logical, controller)
|
||||
|
||||
override fun parentUpdated(logical: Logical<*>, controller: Controller) = onUpdated(logical, controller)
|
||||
|
||||
private fun onUpdated(logical: Logical<*>, controller: Controller) {
|
||||
removeObservers(controller.logicalStateObservable())
|
||||
logicalDispatcher.onTrigger(source, logical)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun Occurrence.removeContractObservers(observable: LogicalStateObservable) {
|
||||
for (observedLogical in this.usedUnboundLogicals()) {
|
||||
observable.removeForwardingObserversWhere(observedLogical) { observer ->
|
||||
observer is LogicalBindObserver && observer.source.identity == this.identity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun Occurrence.usedUnboundLogicals(): Set<Logical<*>> {
|
||||
val unique = hashSetOf<Logical<*>>()
|
||||
for (arg in arguments) {
|
||||
|
||||
val argLogicals = when (arg) {
|
||||
is Logical<*> ->
|
||||
if (!arg.isBound) listOf(arg)
|
||||
else when(val value = arg.findRoot().value()) {
|
||||
is Term -> value.unboundLogicals()
|
||||
else -> emptyList()
|
||||
}
|
||||
is Term -> arg.unboundLogicals()
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
unique.addAll(argLogicals)
|
||||
}
|
||||
return unique
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,269 +148,3 @@ internal open class EmptyProcessing: ProcessingStrategy {
|
|||
|
||||
override fun processInvalidated(occ: Occurrence, observable: LogicalStateObservable) {}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strategy that observes logical vars used in principal constraints
|
||||
* with a help of [PrincipalObserverDispatcher].
|
||||
*
|
||||
* Required for [IncrementalProcessing] for an initial session run.
|
||||
*/
|
||||
internal open class GroundProcessing(
|
||||
override val ispec: IncrementalSpec,
|
||||
private val principalObservers: PrincipalObserverDispatcher = PrincipalObserverDispatcher.EMPTY
|
||||
): EmptyProcessing(), IncrSpecHolder {
|
||||
|
||||
override fun processActivated(active: Occurrence, observable: LogicalStateObservable) {
|
||||
if (active.isPrincipal) {
|
||||
principalObservers.onActivated(active, observable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun processInvalidated(occ: Occurrence, observable: LogicalStateObservable) {
|
||||
if (occ.isPrincipal) {
|
||||
principalObservers.onInvalidated(occ, observable)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Facade implementation for incremental processing algorithm.
|
||||
*
|
||||
* It includes 5 stages that operate on [MatchJournalImpl.Cursor].
|
||||
* Stages are:
|
||||
* - [RewindStage]
|
||||
* - [InvalidationStage]
|
||||
* - [AdditionStage]
|
||||
* - [PostponeMatchesStage]
|
||||
* - [ContinueOccurrencesStage]
|
||||
*
|
||||
* Main loop [run] defines relations between stages.
|
||||
* After invalidation and addition control flow is passed to
|
||||
* general processing in [Controller] & [ConstraintsProcessing].
|
||||
*
|
||||
* Methods [processMatch] & [processOccurrenceMatches] serve as
|
||||
* a bridge back from [ConstraintsProcessing] to specific stages.
|
||||
*/
|
||||
internal class IncrementalProcessing(
|
||||
ispec: IncrementalSpec,
|
||||
val journal: MatchJournal,
|
||||
newRules: Iterable<Rule>,
|
||||
droppedRules: Iterable<Any>,
|
||||
stateCleaner: ConstraintsProcessing.ProgramStateCleaner,
|
||||
ruleIndex: RuleIndex,
|
||||
principalObservers: PrincipalObserverDispatcher,
|
||||
trace: EvaluationTrace
|
||||
): GroundProcessing(ispec, principalObservers) {
|
||||
|
||||
private val journalIndex = journal.index()
|
||||
private val ruleOrdering = RuleOrdering(ruleIndex)
|
||||
|
||||
private val posTracker = PosTracking(journalIndex, journal)
|
||||
private val continuator = ContinueOccurrencesStage(ispec, journalIndex)
|
||||
private val invalidator = InvalidationStage(ispec, posTracker, droppedRules.toSet(), continuator, stateCleaner, trace)
|
||||
private val adder = AdditionStage(ispec, posTracker, newRules, continuator, ruleOrdering, ruleIndex, trace)
|
||||
private val postponer = PostponeMatchesStage(ispec, posTracker, ruleOrdering)
|
||||
private val rewinder = RewindStage(ispec, posTracker, principalObservers)
|
||||
|
||||
init {
|
||||
principalObservers.setTriggerReceiver(this::receiveBindTriggered)
|
||||
}
|
||||
|
||||
|
||||
override fun invalidatedFeedback(): FeedbackKeySet =
|
||||
invalidator.invalidatedFeedback()
|
||||
|
||||
override fun invalidatedRules(): List<Any> =
|
||||
invalidator.invalidatedRules()
|
||||
|
||||
override fun offerMatch(match: RuleMatchEx) =
|
||||
!match.allHeads().filter { it.isPrincipal }.toList().let {
|
||||
if (it.isNotEmpty()) {
|
||||
rewinder.receiveStrict(it.asSequence(), match)
|
||||
} else false
|
||||
}
|
||||
|
||||
override fun processMatch(match: RuleMatchEx) =
|
||||
continueReplacedHeadsImpl(match)
|
||||
|
||||
override fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>) =
|
||||
postponeFutureMatchesImpl(active, matches)
|
||||
|
||||
|
||||
override fun run(processing: ConstraintsProcessing, controller: Controller, main: Constraint): FeedbackStatus {
|
||||
var status: FeedbackStatus = FeedbackStatus.NORMAL()
|
||||
val cursor = journal.cursor
|
||||
while (true) {
|
||||
posTracker.onNext(cursor)
|
||||
|
||||
rewind(cursor)
|
||||
invalidate(cursor)
|
||||
|
||||
val postponedMatches = postponer.onNext(cursor)
|
||||
adder.receive(postponedMatches)
|
||||
// Adder step must work on the incremental front.
|
||||
// If rewind happened, then must skip this.
|
||||
if (posTracker.isFront()) { adder.onNext(cursor) }
|
||||
|
||||
// fixme: pass inStatus?
|
||||
status = continuator.runContinued(processing, controller, cursor)
|
||||
// continuator may request invalidating more chunks
|
||||
val haveChanges = invalidate(cursor)
|
||||
|
||||
if (!status.operational) break
|
||||
if (!rewinder.needRewind() && cursor.atEnd()) break
|
||||
// These changes (if present) must operate on
|
||||
// current cursor position, so don't advance it.
|
||||
if (!rewinder.needRewind() && !haveChanges) cursor.next()
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
private fun receiveBindTriggered(occ: Occurrence): Boolean =
|
||||
if (journalIndex.isKnown(occ)) {
|
||||
invalidator.receive(occ)
|
||||
true
|
||||
} else false
|
||||
|
||||
private fun continueReplacedHeadsImpl(match: RuleMatchEx) {
|
||||
if (requiresIncrementalProcessing(match)) {
|
||||
val invalidJustifications = match.matchHeadReplaced().filter { it.isPrincipal }
|
||||
invalidator.receive(invalidJustifications)
|
||||
}
|
||||
}
|
||||
|
||||
private fun postponeFutureMatchesImpl(active: Occurrence, matches: List<RuleMatchEx>) =
|
||||
if (requiresIncrementalProcessing(active)) {
|
||||
postponer.postponeFutureMatches(matches)
|
||||
} else matches
|
||||
|
||||
private fun RewindStage.receiveStrict(occs: Sequence<Occurrence>, match: RuleMatchEx) =
|
||||
rewinder.receive(occs).also {
|
||||
if (ispec.assertLevel() == IncrementalSpec.AssertLevel.AssertContracts) {
|
||||
throw IncrementalContractViolationException(
|
||||
"Incremental processing can't guarantee correctness for match ${match.rule().uniqueTag()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rewind(cursor: ChunkReader) {
|
||||
val toRewind = rewinder.onNext(cursor)
|
||||
|
||||
toRewind.firstOrNull()?.let { earliest ->
|
||||
// This is the only place where journal can be reset to past
|
||||
// fixme: modifying not through cursor; not the cleanest way
|
||||
posTracker.rewind(earliest.toPos())
|
||||
|
||||
// Will invalidate these chunks and reevaluate them
|
||||
invalidator.receive(toRewind)
|
||||
|
||||
// Some stages on rewind need to clear internal state
|
||||
adder.onRewind(cursor)
|
||||
continuator.onRewind(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidate(cursor: RemovingJournalIterator): Boolean {
|
||||
var haveChanges = false
|
||||
do {
|
||||
// handle all new chunks that were invalidated by rewind
|
||||
if (posTracker.isNew(cursor.next)) {
|
||||
invalidator.receive(cursor.next)
|
||||
}
|
||||
|
||||
if (invalidator.onNext(cursor)) {
|
||||
cursor.removeNext()
|
||||
haveChanges = true
|
||||
} else break
|
||||
} while (true)
|
||||
return haveChanges
|
||||
}
|
||||
|
||||
private fun requiresIncrementalProcessing(match: RuleMatchEx) = !journal.isFront() && ispec.ability().allowed() && match.isPrincipal
|
||||
|
||||
private fun requiresIncrementalProcessing(occ: Occurrence) = !journal.isFront() && ispec.ability().allowed() && occ.isPrincipal
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strategy that works with occurrence store.
|
||||
* Doesn't directly work with [MatchJournal] except for default logging.
|
||||
* Strategy simply activates [Occurrence]s passed as input store
|
||||
*
|
||||
* Caller is responsible for handling new computed principal [Occurrences]:
|
||||
* it can get them from [MatchJournal] and output for putting into cache.
|
||||
*
|
||||
* It requires that program in question adheres to incremental contracts.
|
||||
* Importantly, one ensured by [LogicalBindObserverDispatcher].
|
||||
*/
|
||||
internal class CachedOccurrencesProcessing(
|
||||
ispec: IncrementalSpec,
|
||||
private val occurrences: OccurrenceStore
|
||||
): GroundProcessing(ispec) {
|
||||
|
||||
private var inPreamble = true
|
||||
|
||||
private val postponedMatches: MutableList<Pair<Occurrence, List<RuleMatchEx>>> = mutableListOf()
|
||||
|
||||
|
||||
override fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>) =
|
||||
if (inPreamble && matches.isNotEmpty()) {
|
||||
postponedMatches.add(active to matches)
|
||||
emptyList<RuleMatchEx>()
|
||||
} else matches
|
||||
|
||||
|
||||
override fun run(processing: ConstraintsProcessing, controller: Controller, main: Constraint): FeedbackStatus {
|
||||
var status: FeedbackStatus = FeedbackStatus.NORMAL()
|
||||
|
||||
// NB: assume evaluation order doesn't matter for these occurrences
|
||||
|
||||
// first activate cached occurrences,
|
||||
// but don't process their matches right away
|
||||
this.inPreamble = true
|
||||
for (occ in occurrences) {
|
||||
if (occ.constraint().symbol() == main.symbol())
|
||||
continue
|
||||
status = processing.evaluate(controller, occ, status)
|
||||
if (!status.operational)
|
||||
return status
|
||||
}
|
||||
this.inPreamble = false
|
||||
|
||||
// then continue matches caused by cached occurrences
|
||||
status = continueMatches(processing, controller, status)
|
||||
|
||||
// then proceed with normal execution
|
||||
if (status.operational)
|
||||
status = controller.activate(main)
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
private fun continueMatches(processing: ConstraintsProcessing, controller: Controller, inStatus: FeedbackStatus): FeedbackStatus {
|
||||
val parentChunk = processing.initialChunk() // fixme: get activation chunk of active occ?
|
||||
val status = postponedMatches.fold(inStatus) { status, (active, matches) ->
|
||||
processing.processMatches(controller, active, matches, parentChunk, status)
|
||||
}
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun ContinueOccurrencesStage.runContinued(processing: ConstraintsProcessing, controller: Controller, chunkReader: ChunkReader): FeedbackStatus {
|
||||
var status: FeedbackStatus = FeedbackStatus.NORMAL()
|
||||
val parentChunk = processing.parentChunk()
|
||||
|
||||
for (continuedOcc in this.onNext(chunkReader)) {
|
||||
if (continuedOcc.stored) {
|
||||
status = processing.activateContinue(controller, continuedOcc, parentChunk)
|
||||
|
||||
if (!status.operational) break
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import kotlin.NoSuchElementException
|
|||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* An alternative implementation of RuleMatcherImpl. Has similar asymptotic characteristics as the "naïve" implementation.
|
||||
* An alternative implementation of RuleMatcher. Has similar asymptotic characteristics as the "naïve" implementation.
|
||||
*
|
||||
* Loosely based on "Rete network" algorithm.
|
||||
*
|
||||
|
|
@ -77,6 +77,7 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
|
|||
|
||||
var lastGeneration = Generation(Layer(InitialNode()))
|
||||
|
||||
/* FIXME MEMLEAK */
|
||||
val consumedSignatures = IndexedSignatureSet()
|
||||
|
||||
abstract inner class ReteNode
|
||||
|
|
@ -234,8 +235,10 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
|
|||
*/
|
||||
inner class Layer() {
|
||||
|
||||
/* FIXME MEMLEAK */
|
||||
private val introTrail: Trail = trailOf()
|
||||
|
||||
/* FIXME MEMLEAK */
|
||||
private val introNodes = HashMap<Int, MutableList<ReteNode>>()
|
||||
|
||||
private val nodeList = UnionFindLinkedList<ReteNode>()
|
||||
|
|
@ -255,12 +258,7 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
|
|||
fun containsOccurrence(occ: Occurrence): Boolean {
|
||||
return introTrail.contains(occ.identity)
|
||||
}
|
||||
|
||||
fun forgetContains(occ: Occurrence) {
|
||||
// FIXME this breaks the internal invariant
|
||||
introTrail.remove(occ.identity)
|
||||
}
|
||||
|
||||
|
||||
fun iterate(): MutableIterator<ReteNode> = nodeList.iterator()
|
||||
|
||||
fun nextNode(it: MutableIterator<ReteNode>): ReteNode? {
|
||||
|
|
@ -449,23 +447,7 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
|
|||
consumedSignatures.removeAllWith(occurrence.identity)
|
||||
return nextGeneration().reset()
|
||||
}
|
||||
|
||||
/*
|
||||
* Allows to avoid triggering reactivation logic in the next call of "introduce" for this occurrence.
|
||||
*/
|
||||
fun forgetIntroduced(occurrence: Occurrence): Generation {
|
||||
for (layer in layers) {
|
||||
layer.forgetContains(occurrence)
|
||||
}
|
||||
if (lastIntroduced === occurrence) lastIntroduced = null
|
||||
return nextGeneration()
|
||||
}
|
||||
|
||||
fun erase(occurrence: Occurrence): Generation {
|
||||
if (lastIntroduced === occurrence) lastIntroduced = null
|
||||
return drop(occurrence).clearInvalidNodes()
|
||||
}
|
||||
|
||||
|
||||
private fun clearInvalidNodes(): Generation {
|
||||
while (nodesIt.hasNext()) {
|
||||
val n = nodesIt.next()
|
||||
|
|
@ -528,21 +510,6 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
|
|||
return this
|
||||
}
|
||||
|
||||
override fun forgetExpanded(occ: Occurrence): ReteNetwork {
|
||||
this.lastGeneration.forgetIntroduced(occ)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun forgetConsumed(occ: Occurrence): ReteNetwork {
|
||||
consumedSignatures.removeAllWith(occ.identity)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun forget(occ: Occurrence): RuleMatchingProbe {
|
||||
this.lastGeneration = lastGeneration.erase(occ)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun hasOccurrences(): Boolean {
|
||||
return this.lastGeneration.hasOccurrences()
|
||||
}
|
||||
|
|
@ -556,11 +523,6 @@ internal class ReteRuleMatcherImpl(private var ruleLookup: RuleLookup?,
|
|||
return this
|
||||
}
|
||||
|
||||
override fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe {
|
||||
consumedSignatures.remove(ruleMatch.signatureArray().toSignature())
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.Occurrence
|
||||
import jetbrains.mps.logic.reactor.core.canMatch
|
||||
import jetbrains.mps.logic.reactor.evaluation.RuleMatch
|
||||
import jetbrains.mps.logic.reactor.program.Rule
|
||||
|
||||
|
||||
internal class RuleOrdering(order: Iterable<Rule>): ComparatorExt<Rule> {
|
||||
|
||||
private val ruleOrder: Map<Any, Int> = HashMap<Any, Int>().apply {
|
||||
put(MatchJournalImpl.InitRuleMatch.rule().uniqueTag(), -1) // less than anything
|
||||
order.forEachIndexed { index, rule -> put(rule.uniqueTag(), index) }
|
||||
}
|
||||
|
||||
val ruleTags: Set<Any> = order.map { it.uniqueTag() }.toHashSet() // NB: without initial rule
|
||||
|
||||
override fun compare(lhs: Rule, rhs: Rule): Int = compareBy<Rule>(this::orderOfThrow).compare(lhs, rhs)
|
||||
|
||||
val matchComparator: Comparator<RuleMatch> get() = compareBy<RuleMatch>{ orderOfThrow(it.rule()) }
|
||||
|
||||
|
||||
private fun orderOf(rule: Rule): Int? = ruleOrder[rule.uniqueTag()]
|
||||
|
||||
private fun orderOfThrow(rule: Rule): Int = when (val res = orderOf(rule)) {
|
||||
null -> throw IllegalStateException("Compared rule (${rule.uniqueTag()}) must be present in rule index!")
|
||||
else -> res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether [candidateRule] can be inserted in journal as a child
|
||||
* of [parentChunk] before one of its child chunks, [beforeChunk].
|
||||
* It is assumed that [candidateRule] can be matched by [Occurrence] from [parentChunk].
|
||||
*/
|
||||
internal fun RuleOrdering.canBeInserted(candidateRule: Rule, parentChunk: MatchJournal.OccChunk, beforeChunk: MatchJournal.Chunk): Boolean {
|
||||
// Place to try activating candidate rule is:
|
||||
// either according to the ordering between rules
|
||||
// or as the last one, after all existing activations
|
||||
assert(candidateRule.canMatch(parentChunk.occ.constraint))
|
||||
|
||||
val placeToInsertFound = beforeChunk is MatchJournal.MatchChunk
|
||||
&& candidateRule before beforeChunk.match.rule()
|
||||
|
||||
val isDescendant = beforeChunk.justifiedBy(parentChunk)
|
||||
val isSibling = beforeChunk.evidence == parentChunk.evidence && beforeChunk != parentChunk
|
||||
val childChunksEnded = !isDescendant || isSibling
|
||||
|
||||
return (childChunksEnded || placeToInsertFound)
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ import jetbrains.mps.logic.reactor.program.Rule
|
|||
|
||||
data class SessionTokenImpl(
|
||||
private val journalView: MatchJournal.View,
|
||||
private var store: Collection<Occurrence>,
|
||||
private val rules: Iterable<Rule>,
|
||||
private val frontState: DispatchingFrontState,
|
||||
val ruleIndex: RuleIndex,
|
||||
|
|
@ -35,8 +34,4 @@ data class SessionTokenImpl(
|
|||
{
|
||||
override fun getJournalView(): MatchJournalView = journalView
|
||||
override fun getRules(): Iterable<Rule> = rules
|
||||
override fun getPrincipalStore(): Collection<Occurrence> = store
|
||||
|
||||
fun getFrontState(): DispatchingFrontState = frontState
|
||||
fun setStore(newStore: Collection<Occurrence>) { this.store = newStore }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,14 +78,6 @@ public abstract class EvaluationSession {
|
|||
|
||||
public abstract Config withTrace(EvaluationTrace computingTracer);
|
||||
|
||||
/**
|
||||
* @deprecated passing store view is deprecated and doesn't have an effect
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Config withStoreView(StoreView storeView);
|
||||
|
||||
public Config withStore(Collection<ConstraintOccurrence> store) { return this; }
|
||||
|
||||
public Config withSessionToken(SessionToken token) { return this; }
|
||||
|
||||
public Config withIncrSpec(IncrementalSpec ispec) { return this; }
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@
|
|||
|
||||
package jetbrains.mps.logic.reactor.evaluation;
|
||||
|
||||
import jetbrains.mps.logic.reactor.program.PreambleInfo;
|
||||
|
||||
public interface MatchJournalView {
|
||||
StoreView getStoreView();
|
||||
MatchJournalView getPreamble(PreambleInfo info);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,4 @@ public interface SessionToken {
|
|||
MatchJournalView getJournalView();
|
||||
@NotNull()
|
||||
Iterable<Rule> getRules();
|
||||
@NotNull
|
||||
Collection<ConstraintOccurrence> getPrincipalStore();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014-2020 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.program;
|
||||
|
||||
/**
|
||||
* Provides required but incomplete information to determine preamble.
|
||||
* Aimed at filtering rules with origins which do belong to preamble.
|
||||
*
|
||||
* Together with justifications from journal provides complete information.
|
||||
* See implementation for details.
|
||||
*/
|
||||
public interface PreambleInfo {
|
||||
|
||||
boolean inPreamble(Rule rule);
|
||||
|
||||
static final PreambleInfo EMPTY = new PreambleInfo() {
|
||||
@Override
|
||||
public boolean inPreamble(Rule rule) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -16,15 +16,8 @@
|
|||
|
||||
package jetbrains.mps.logic.reactor.program;
|
||||
|
||||
import javaslang.collection.List;
|
||||
import javaslang.collection.Stream;
|
||||
import jetbrains.mps.logic.reactor.core.RulesDiff;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A collection of rulesLists that constitute a constraint rules program.
|
||||
* A collection of rules that constitute a constraint rules program.
|
||||
*
|
||||
* @author Fedor Isakov
|
||||
*/
|
||||
|
|
@ -32,39 +25,6 @@ public abstract class Program {
|
|||
|
||||
public abstract String name();
|
||||
|
||||
@Deprecated
|
||||
public abstract Iterable<RulesList> rulesLists();
|
||||
|
||||
public abstract PreambleInfo preambleInfo();
|
||||
|
||||
@Deprecated
|
||||
public Program withRulesDiff(RulesDiff diff) { return this; };
|
||||
|
||||
@Deprecated
|
||||
public RulesDiff incrementalDiff() { return RulesDiff.emptyDiff(); };
|
||||
|
||||
public Iterable<Rule> rules() {
|
||||
ArrayList<Rule> allRules = new ArrayList<Rule>();
|
||||
for (RulesList rulesList : rulesLists()) {
|
||||
for (Rule rule : rulesList.rules()) {
|
||||
allRules.add(rule);
|
||||
}
|
||||
}
|
||||
return allRules;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns rules that have been created since the last evaluation of this program.
|
||||
*/
|
||||
public Iterable<Rule> newRules () {
|
||||
return incrementalDiff().getAdded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns objects that identify rules removed from the previous invocation.
|
||||
*/
|
||||
public Iterable<Object> droppedRules() {
|
||||
return incrementalDiff().getRemoved();
|
||||
}
|
||||
abstract public Iterable<Rule> rules();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
* @author Fedor Isakov
|
||||
*/
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.RulesDiff
|
||||
import jetbrains.mps.logic.reactor.evaluation.EvaluationFeedback
|
||||
import jetbrains.mps.logic.reactor.evaluation.InvocationContext
|
||||
import jetbrains.mps.logic.reactor.evaluation.RuleMatch
|
||||
|
|
@ -103,21 +102,8 @@ class MockRule(
|
|||
}
|
||||
|
||||
class MockProgram(val name: String, val rulesLists: List<RulesList>, val registry: MockConstraintRegistry) : Program() {
|
||||
private var rulesDiff: RulesDiff = RulesDiff.emptyDiff()
|
||||
|
||||
override fun preambleInfo(): PreambleInfo = PreambleInfo.EMPTY
|
||||
|
||||
override fun incrementalDiff(): RulesDiff = rulesDiff
|
||||
|
||||
override fun withRulesDiff(diff: RulesDiff): MockProgram {
|
||||
this.rulesDiff = diff
|
||||
return this
|
||||
}
|
||||
|
||||
override fun name(): String = name
|
||||
|
||||
override fun rulesLists(): Iterable<RulesList> = unmodifiableCollection(rulesLists)
|
||||
|
||||
override fun rules(): MutableIterable<Rule> = unmodifiableCollection(rulesLists.flatMap { it.rules() })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import jetbrains.mps.logic.reactor.program.IncrementalSpec
|
||||
import jetbrains.mps.logic.reactor.core.Occurrence
|
||||
import jetbrains.mps.logic.reactor.core.ReactorLifecycle
|
||||
import jetbrains.mps.logic.reactor.core.RulesDiff
|
||||
import jetbrains.mps.logic.reactor.core.internal.MatchJournal
|
||||
import jetbrains.mps.logic.reactor.evaluation.*
|
||||
import jetbrains.mps.logic.reactor.logical.Logical
|
||||
import jetbrains.mps.logic.reactor.program.Constraint
|
||||
import jetbrains.mps.logic.reactor.program.ConstraintSymbol
|
||||
import jetbrains.mps.logic.reactor.program.IncrementalContractViolationException
|
||||
import org.junit.*
|
||||
import org.junit.Assert.*
|
||||
import program.MockConstraint
|
||||
|
|
@ -33,6 +31,7 @@ import solver.tellEquals
|
|||
* @author Fedor Isakov
|
||||
*/
|
||||
|
||||
@Ignore
|
||||
class TestIncrementalProgram {
|
||||
|
||||
companion object {
|
||||
|
|
@ -64,9 +63,7 @@ class TestIncrementalProgram {
|
|||
private fun Builder.relaunch(name: String, incrSpec: IncrementalSpec, sessionToken: SessionToken, resultHandler: (EvaluationResult) -> Unit )
|
||||
: Pair<Builder, EvaluationResult>
|
||||
{
|
||||
val prog = program(name).withRulesDiff(
|
||||
RulesDiff.findDiff(sessionToken.rules, rules)
|
||||
)
|
||||
val prog = program(name)
|
||||
val result = EvaluationSession.newSession(prog)
|
||||
.withParameter(EvaluationSession.ParameterKey.of("main", Constraint::class.java), MockConstraint(ConstraintSymbol("main", 0)))
|
||||
.withIncrSpec(incrSpec)
|
||||
|
|
|
|||
Loading…
Reference in New Issue