Handle journal reset for occurrences: upd their status (eg !alive). Add test for one error caused by it.

Occurrences which weren't yet activated (according to current journal position) can't be reactivated.
Test program is an example of such case.
This commit is contained in:
Grigorii Kirgizov 2020-01-21 15:57:20 +03:00
parent ba8d00c224
commit 79c31a44ba
2 changed files with 87 additions and 1 deletions

View File

@ -91,17 +91,25 @@ internal open class MatchJournalImpl(
override fun isFront(): Boolean = current == hist.last
override fun resetPos() {
posPtr = hist.listIterator()
// walk backwards and reset all occurrences
posPtr = hist.listIterator(hist.size)
while (posPtr.hasPrevious()) {
current = posPtr.previous()
resetOccurrences(current.entries)
}
}
override fun reset(pastPos: MatchJournal.Pos) {
// walk backwards, reset occurrences and remove history from posPtr down to pastPos
while (posPtr.hasPrevious()) {
current = posPtr.previous()
if (current === pastPos.chunk) {
resetOccurrences(current.entries.drop(pastPos.entriesCount))
current.entries = current.entries.subList(0, pastPos.entriesCount)
posPtr.next() // make 'posPtr' to always point right after 'current'
return
}
resetOccurrences(current.entries)
posPtr.remove()
}
if (currentPos() != pastPos) throw IllegalStateException()
@ -119,6 +127,17 @@ internal open class MatchJournalImpl(
if (currentPos() != futurePos) throw IllegalStateException()
}
private fun resetOccurrences(occSpecs: Iterable<MatchJournal.Chunk.Entry>) =
occSpecs.forEach {
if (it.discarded) {
it.occ.alive = true
it.occ.stored = true
} else {
it.occ.alive = false
it.occ.stored = false
}
}
private fun replayOccurrences(observable: LogicalStateObservable, occSpecs: Iterable<MatchJournal.Chunk.Entry>) =
occSpecs.forEach {
if (it.discarded) {

View File

@ -1158,4 +1158,71 @@ class TestIncrementalProgram {
}
}
}
@Test
fun observerReactivatesFutureOccurrence() {
val progSpec = MockIncrProgSpec(
setOf("main", "bindVar", "produceBound", "eliminateBound"),
setOf(sym1("foo"))
)
val X = metaLogical<Int>("X")
programWithRules(
rule("main",
headReplaced(
constraint("main")
),
body(
princConstraint("foo", X)
)),
rule("produceBound",
headKept(
princConstraint("foo", X)
),
body(
princConstraint("hasBound", X)
)),
rule("eliminateBound",
headReplaced(
princConstraint("hasBound", X)
),
guard(
expression({ x -> x.isBound }, X)
),
body(
constraint("unexpected")
)
)
).launch("initial run", progSpec) { result ->
result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound"))
}.also { (builder, evalRes) ->
// Insert rule before produceBound: test whether
// occurrence reactivation due to var bind can occur
// before this occurrence is actually activated,
// which leads, of course, to inconsistent state.
builder.insertRulesAt(1,
rule("bindVar",
headKept(
princConstraint("foo", X)
),
body(
statement({ x -> x.set(42) }, X)
))
).relaunch("test reactivation", progSpec, evalRes.token()) { result ->
// Currently incremental processing can't handle such rule dependencies
// stemming from incremental changes to logicals,
// so "eliminateBound" rule won't be reexecuted.
// But reactivation of hasBound shouldn't pass either,
// because 'hasBound' isn't in store at the point of tried reactivation.
result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound"))
// NB: this is correct if incremental processing
// takes into account rule dependencies due to logicals.
// result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym0("unexpected"))
}
}
}
}