Minor refactoring: move OccurrenceContractObserver from ConstraintsProcessing into ProcessingStrategy

Rename NonIncrementalProcessing -> DefaultProcessing
Add GroundProcessing as common superclass to handle OccurrenceContractObserver
Add another test for this occurrence contract logic.
This commit is contained in:
Grigorii Kirgizov 2020-12-15 19:36:41 +03:00
parent 273d4dc4db
commit eb1e646cd4
5 changed files with 112 additions and 50 deletions

View File

@ -46,12 +46,7 @@ internal class ConstraintsProcessing(
) : StoreAwareJournalImpl(journal, logicalState), IncrSpecHolder {
private var incrementalProcessing: ProcessingStrategy = NonIncrementalProcessing()
// todo: move to IncrementalProcessing
private val occurrenceContractObserver: OccurrenceContractObserver? =
if (ispec.assertLevel().assertContracts()) OccurrenceContractObserver(logicalState, ispec) else null
private var incrementalProcessing: ProcessingStrategy = DefaultProcessing()
fun setStrategy(strategy: ProcessingStrategy) { this.incrementalProcessing = strategy }
@ -108,9 +103,7 @@ internal class ConstraintsProcessing(
active.stored = true
logActivation(active)
active.revive(logicalState)
// fixme: move to incremental facade
occurrenceContractObserver?.onActivated(active)
incrementalProcessing.processActivated(active, logicalState)
}
assert(active.alive)
@ -194,7 +187,8 @@ internal class ConstraintsProcessing(
profiler.profile("terminateOccurrence") {
occ.clearLogicalState(logicalState)
occ.terminate(logicalState)
incrementalProcessing.processDiscarded(occ, logicalState)
}
@ -204,18 +198,11 @@ internal class ConstraintsProcessing(
}
}
private fun Occurrence.clearLogicalState(observable: LogicalStateObservable) {
terminate(observable)
if (occurrenceContractObserver != null && this.isPrincipal) {
occurrenceContractObserver.onDiscarded(this)
}
}
inner class ProgramStateCleaner{
fun erase(occurrence: Occurrence) {
dispatchingFront = dispatchingFront.forget(occurrence)
occurrence.clearLogicalState(logicalState)
occurrence.terminate(logicalState)
incrementalProcessing.processDiscarded(occurrence, logicalState)
}
fun erase(match: RuleMatchEx) {

View File

@ -114,7 +114,7 @@ internal class EvaluationSessionImpl private constructor (
val logicalState = LogicalState()
val dispatchingFront = Dispatcher(ruleIndex).front()
val processingStrategy = NonIncrementalProcessing()
val processingStrategy = GroundProcessing(incrementality)
val processing = ConstraintsProcessing(dispatchingFront, journal, logicalState, incrementality, trace, profiler)
processing.setStrategy(processingStrategy)
@ -168,7 +168,6 @@ internal class EvaluationSessionImpl private constructor (
override fun endSession(session: SessionParts): SessionToken = with(session) {
val histView = journal.view()
processing.resetStore() // clear observers
// todo: need clearing occurrenceContractObservers? (MPSCR-66)
val principalState = sessionState(frontState).resetLookup()
return SessionTokenImpl(histView, emptyList(), ruleIndex.toRules(), principalState, logicalState, ruleIndex)
}
@ -213,7 +212,6 @@ internal class EvaluationSessionImpl private constructor (
outputOccurrences.forEach{ it.terminate(logicalState) }
processing.resetStore() // clear observers
// todo: need clearing occurrenceContractObservers? (MPSCR-66)
logicalState.reset()
val rules = ruleIndex.toRules().filter(preambleInfo::inPreamble)

View File

@ -33,16 +33,16 @@ import kotlin.collections.HashSet
* must be immutable, that is, logicals can't be unified
* with either ground or free other logicals.
*/
internal class OccurrenceContractObserver(private val observable: LogicalStateObservable, override val ispec: IncrementalSpec): IncrSpecHolder {
internal class OccurrenceContractObserver(override val ispec: IncrementalSpec): IncrSpecHolder {
private val observers: HashMap<Int, UnmodifiableLogicalObserver> = hashMapOf()
fun onActivated(occ: Occurrence) {
fun onActivated(occ: Occurrence, observable: LogicalStateObservable) {
if (occ.isPrincipal) {
observers[occ.identity] = UnmodifiableLogicalObserver(occ, observable)
}
}
fun onDiscarded(occ: Occurrence) {
fun onDiscarded(occ: Occurrence, observable: LogicalStateObservable) {
// NB: works between incremental sessions only if this instance is preserved between them
// observers.remove(occ.identity)?.removeObservers(observable)

View File

@ -54,13 +54,26 @@ internal interface ProcessingStrategy {
* Should return a filtered [matches] list.
*/
fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>): List<RuleMatchEx>
/**
* Called on new activated [Occurrence]. Allows to handle program's
* logical state, e.g. add processing-specific observers with [observable].
*/
fun processActivated(active: Occurrence, observable: LogicalStateObservable): Unit
/**
* Called for each replaced [Occurrence] in match's head.
* It's a pair method for [processActivated] to discharge its effects,
* e.g. for clearing program logical state.
*/
fun processDiscarded(occ: Occurrence, observable: LogicalStateObservable): Unit
}
/**
* Default non-incremental processing with stubs.
*/
internal class NonIncrementalProcessing: ProcessingStrategy {
internal open class DefaultProcessing: ProcessingStrategy {
override fun invalidatedFeedback(): FeedbackKeySet = emptySet()
@ -75,6 +88,31 @@ internal class NonIncrementalProcessing: ProcessingStrategy {
override fun processMatch(match: RuleMatchEx) {}
override fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>): List<RuleMatchEx> = matches
override fun processActivated(active: Occurrence, observable: LogicalStateObservable) {}
override fun processDiscarded(occ: Occurrence, observable: LogicalStateObservable) {}
}
/**
* Processing strategy that observes logical vars and ensures basic incremental contract.
*/
internal open class GroundProcessing(override val ispec: IncrementalSpec): DefaultProcessing(), IncrSpecHolder {
private val occurrenceContractObserver: OccurrenceContractObserver? =
if (ispec.assertLevel().assertContracts()) OccurrenceContractObserver(ispec) else null
override fun processActivated(active: Occurrence, observable: LogicalStateObservable) {
occurrenceContractObserver?.onActivated(active, observable)
}
override fun processDiscarded(occ: Occurrence, observable: LogicalStateObservable) {
if (occ.isPrincipal) {
occurrenceContractObserver?.onDiscarded(occ, observable)
}
}
}
@ -97,14 +135,14 @@ internal class NonIncrementalProcessing: ProcessingStrategy {
* in [IncrementalProcessing].
*/
internal class IncrementalProcessing(
override val ispec: IncrementalSpec,
ispec: IncrementalSpec,
val journal: MatchJournal,
newRules: Iterable<Rule>,
droppedRules: Iterable<Any>,
stateCleaner: ConstraintsProcessing.ProgramStateCleaner,
ruleIndex: RuleIndex,
trace: EvaluationTrace
): ProcessingStrategy, IncrSpecHolder {
): GroundProcessing(ispec) {
private val journalIndex = journal.index()
private val ruleOrdering = RuleOrdering(ruleIndex)
@ -127,6 +165,7 @@ internal class IncrementalProcessing(
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
@ -188,15 +227,15 @@ internal class IncrementalProcessing(
*
* Journal invalidation and injected intermediate processing with
* [processMatch] & [processOccurrenceMatches] are not needed for this.
* So this strategy is very close to the default [NonIncrementalProcessing].
* So this strategy is very close to the default [DefaultProcessing].
*/
internal class PreambleProcessing(
override val ispec: IncrementalSpec,
ispec: IncrementalSpec,
val journal: MatchJournal,
newRules: Iterable<Rule>,
ruleIndex: RuleIndex,
trace: EvaluationTrace
): ProcessingStrategy, IncrSpecHolder {
): GroundProcessing(ispec) {
private val journalIndex = journal.index()
private val ruleOrdering = RuleOrdering(ruleIndex)
@ -204,16 +243,6 @@ internal class PreambleProcessing(
private val continuator = ContinueOccurrencesStage(ispec, journalIndex)
private val adder = AdditionStage(ispec, newRules, continuator, ruleOrdering, ruleIndex, trace)
override fun invalidatedFeedback(): FeedbackKeySet = emptySet()
override fun invalidatedRules(): List<Any> = emptyList()
override fun processMatch(match: RuleMatchEx) = Unit
override fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>) = matches
override fun run(processing: ConstraintsProcessing, controller: Controller, main: Constraint): FeedbackStatus {
var status: FeedbackStatus = FeedbackStatus.NORMAL()
val cursor = journal.cursor
@ -243,21 +272,15 @@ internal class PreambleProcessing(
* Importantly, one ensured by [OccurrenceContractObserver].
*/
internal class CachedOccurrencesProcessing(
override val ispec: IncrementalSpec,
ispec: IncrementalSpec,
private val occurrences: OccurrenceStore
): ProcessingStrategy, IncrSpecHolder {
): GroundProcessing(ispec) {
private var inPreamble = true
private val postponedMatches: MutableList<Pair<Occurrence, List<RuleMatchEx>>> = mutableListOf()
override fun invalidatedFeedback(): FeedbackKeySet = emptySet()
override fun invalidatedRules(): List<Any> = emptyList()
override fun processMatch(match: RuleMatchEx) = Unit
override fun processOccurrenceMatches(active: Occurrence, matches: List<RuleMatchEx>) =
if (inPreamble && matches.isNotEmpty()) {
postponedMatches.add(active to matches)

View File

@ -1574,11 +1574,65 @@ class TestIncrementalProgram {
))
).launch("violate contract assertion", progSpec) { result ->
result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound"))
//NB: this check isn't supposed to be even run because of exception
//result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound"))
}
}
@Test(expected = EvaluationFailureException::class)
fun violatePrincipalLogicalContractOnRelaunch() {
val progSpec = MockIncrProgSpec(
setOf("main", "produceBound"),
setOf(sym1("foo"))
).withContractChecks()
val (X, Y) = metaLogical<Int>("X", "Y")
val fooMatch =
rule("produceBound",
headKept(
pconstraint("foo", X)
),
body(
statement({ x, y -> eq(x,y) }, X, Y),
constraint("hasBound", Y)
))
programWithRules(
rule("main",
headReplaced(
constraint("main")
),
body(
pconstraint("foo", X)
)),
// place for inserting 'fooMatch' rule
rule("bindVar",
headKept(
constraint("hasBound", Y)
),
body(
statement({ y -> y.set(42) }, Y)
))
).launch("normal run", progSpec) { result ->
result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"))
}.let { (builder, evalRes) ->
builder
.insertRulesAt(1, fooMatch)
.relaunch("violate contract assertion", progSpec, evalRes.token()) { result ->
//NB: this check isn't supposed to be even run because of exception
//result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound"))
}
}
}
@Test
fun expectTypeGenericNonprincipal() {