Fix bits of logic responsible for launching 'at-start' rules through 'main'. Fix handling of discarded occs

This commit is contained in:
Grigorii Kirgizov 2019-06-05 20:46:15 +03:00
parent 767eb057f5
commit 5a9019e85c
4 changed files with 54 additions and 56 deletions

View File

@ -62,7 +62,9 @@ internal class ControllerImpl (
// FIXME noLogicalContext
val context = Context(NORMAL(), noLogicalContext)
activateConstraint(constraint, emptyJusts(), context)
// fixme: is it valid to always provide current justifications?
// while this method is used only in one place at program kick-off, yes, it's initial justs provided.
activateConstraint(constraint, state.justs(), context)
return context.currentStatus()
}

View File

@ -74,7 +74,7 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
}.map { it.wrapped }
abstract fun findOccurrence(ctr: Constraint): Occurrence?
abstract fun principalConstraint(): Constraint?
abstract fun principalOccurrence(): Occurrence?
override fun toString() = "(id=$id, $justifications, ${match.rule().uniqueTag()}, ${entriesLog()})"
@ -126,7 +126,8 @@ internal open class MatchJournalImpl(
if (view == null) {
hist = LinkedList()
nextChunkId = 0
val initChunk = ChunkImpl(ispec, InitRuleMatch, nextChunkId++, justsOf())
val initChunk = ChunkImpl(ispec, InitRuleMatch, nextChunkId, justsOf(nextChunkId))
nextChunkId++
hist.add(initChunk)
} else {
// fixme: check somehow that initial chunk is present?
@ -201,7 +202,15 @@ internal open class MatchJournalImpl(
}
private fun replayOccurrences(controller: Controller, occSpecs: Iterable<MatchJournal.Chunk.Entry>) =
occSpecs.forEach { if (it.isDiscarded) it.occ.terminate(controller) else it.occ.revive(controller) }
occSpecs.forEach {
if (it.isDiscarded) {
it.occ.terminate(controller)
it.occ.stored = false
} else {
it.occ.revive(controller)
it.occ.stored = true
}
}
override fun view() = MatchJournal.View(ArrayList(hist), nextChunkId)
@ -236,17 +245,10 @@ internal open class MatchJournalImpl(
override fun entriesLog(): List<MatchJournal.Chunk.Entry> = occurrences
override fun findOccurrence(ctr: Constraint): Occurrence? =
occurrences.find { !it.isDiscarded && it.occ.constraint.symbol() == ctr.symbol() }?.occ
activatedLog().find { it.constraint.symbol() == ctr.symbol() }
override fun principalConstraint(): Constraint? {
try {
val body = match.rule().bodyAlternation().first()
val pProds = body.filter { it is Constraint && it.isPrincipal() }
return pProds.first() as Constraint
} catch (e: NoSuchElementException) {
return null
}
}
override fun principalOccurrence(): Occurrence? =
activatedLog().find { ispec.isPrincipal(it.constraint) }
}
class IndexImpl(ispec: IncrementalProgramSpec, chunks: Iterable<MatchJournal.Chunk>): MatchJournal.Index

View File

@ -110,6 +110,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
: StoreAwareJournalImpl(journal)
{
private val ruleOrder: Map<Any, Int> = HashMap<Any, Int>().apply {
put(MatchJournalImpl.InitRuleMatch.rule().uniqueTag(), -1)
ruleIndex.forEachIndexed { index, rule -> put(rule.uniqueTag(), index) }
}
@ -196,39 +197,35 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int)
private fun canMatch(rule: Rule, occ: Occurrence): Boolean =
(rule.headKept() + rule.headReplaced()).find { it.symbol() == occ.constraint.symbol() } != null
// todo: what if non-principal rules will be passed as input there?
// only rules that can match on principal occurrences will pass through this method to the queue
// so, no non-principal rule should pass it.
fun addRuleMatches(rules: Iterable<Rule>) {
val activationCandidates = mutableListOf<MatchCandidate>()
val (headedRules, headlessRules) = rules.partition { rule ->
rule.headKept().count() != 0 || rule.headReplaced().count() != 0
}
val it = this.iterator()
var prevChunk = it.next() // skip initial chunk
while (it.hasNext()) { // note: if there's only an initial chunk, we have nothing to do
val chunk = it.next()
val pCtr = chunk.principalConstraint()
var chunk = it.next() // initial chunk
var prevChunk = chunk
while (true) {
// Does this chunk have principal occurrence and can activate anything at all?
if (pCtr != null) {
for (rule in headedRules) {
val pOcc = chunk.principalOccurrence()
if (pOcc != null) {
for (rule in rules) {
// Can this rule be matched by principal occurrence?
// fixme: maybe use RuleIndex here?
if (rule.headKept().contains(pCtr) || rule.headReplaced().contains(pCtr)) {
val princOcc = chunk.findOccurrence(pCtr)
?: throw IllegalStateException("Chunk with principal occurrence must have it in activated occurrences!")
if (canMatch(rule, pOcc)) {
// 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(MatchCandidate(rule, princOcc, chunk.id))
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
}
}
@ -256,19 +253,9 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
aIt.remove()
}
// Case for headless rules, which are activated at the 'top' level according to rule order in RuleIndex
if (chunk.isTopLevel()) {
for (candRule in headlessRules) {
val placeToInsertFound = compareRuleOrders(chunk.match.rule(), candRule) > 0
if (placeToInsertFound) {
// TODO: how 'at start' rules get activated?
// execQueue.offer(ExecPos(prevChunk, candOcc))
// todo: remove added rule from headlessRules
}
}
}
if (!it.hasNext()) break
prevChunk = chunk
chunk = it.next()
}
}
@ -292,12 +279,13 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
// 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 != prevPos)
replay(controller, execPos.pos)
}
prevPos = execPos
controller.reactivate(execPos.activeOcc)
// If the occurrence is still in the store after replay (i.e. if it's valid to activate it)
if (execPos.activeOcc.stored)
controller.reactivate(execPos.activeOcc)
} while (execQueue.isNotEmpty())
}
// Also replay to the end after queue is fully executed

View File

@ -203,35 +203,41 @@ class TestIncrementalProgram {
}
@Test
@Ignore("unresolved yet")
fun addAtStartMatch() {
val progSpec = MockIncrProgSpec(
setOf("main", "empty-head.bar"),
setOf(sym0("foo"))
setOf("main.foo", "main.bar"),
setOf(sym0("main"))
)
programWithRules(
rule("main",
headReplaced(
constraint("main")
// 'at-start' rules are launched with a 'main' occurrence, their heads aren't actually empty
rule("main.foo",
headKept(
princConstraint("main")
),
body(
princConstraint("foo")
constraint("foo")
))
).launch("addRule", progSpec) { result ->
result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"))
println(result.token().journalView)
result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main"))
result.token().journalView.chunks.size shouldBe 2
result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("foo"))
}.also { (builder, evalRes) ->
builder.programWithRules(
rule("empty-head.bar",
headKept(),
rule("main.bar",
headKept(
princConstraint("main")
),
body(
constraint("bar")
)
)
).relaunch("atStart", progSpec, evalRes.token()) { result ->
result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"))
println(result.token().journalView)
result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main"), sym0("bar"))
result.token().journalView.chunks.size shouldBe 3
result.lastChunk().activatedLog().constraintSymbols() shouldBe listOf(sym0("bar"))
}