Add tracking of last justifying MatchChunk during program eval to avoid searching for it in Journal
Such machinery is needed inside ControllerImpl.processBody to provide nearest relevant match to feedback handling. Also required for a fix for MPSCR-47. Also enables back one assertion in incremental processing on rm stage.
This commit is contained in:
parent
a9af387816
commit
ccd3ed6d45
|
|
@ -17,6 +17,7 @@
|
|||
package jetbrains.mps.logic.reactor.core
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus
|
||||
import jetbrains.mps.logic.reactor.core.internal.MatchJournal
|
||||
import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation
|
||||
import jetbrains.mps.logic.reactor.evaluation.StoreView
|
||||
|
||||
|
|
@ -54,6 +55,6 @@ interface Controller {
|
|||
* its status allowed the operation.
|
||||
* The returned status captures the result of evaluating the rule's body.
|
||||
*/
|
||||
fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus): FeedbackStatus
|
||||
fun processBody(match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus): FeedbackStatus
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,4 +35,9 @@ interface RuleMatchEx : RuleMatch {
|
|||
|
||||
// TODO better be an inline extension fun
|
||||
fun forEachReplaced(action: (Occurrence) -> Unit)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun RuleMatch.allHeads() = (matchHeadKept().asSequence() + matchHeadReplaced().asSequence()) as Sequence<Occurrence>
|
||||
|
||||
fun RuleMatch.allStored() = allHeads().all { it.stored }
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
private data class MatchCandidate(val rule: Rule, val occChunk: MatchJournal.OccChunk)
|
||||
|
||||
|
||||
fun reactivate(controller: Controller, activeOcc: Occurrence): FeedbackStatus {
|
||||
fun reactivate(controller: Controller, activeOcc: Occurrence, parent: MatchJournal.MatchChunk): FeedbackStatus {
|
||||
assert(activeOcc.stored)
|
||||
|
||||
// Forget that occ was seen. Otherwise it will be
|
||||
|
|
@ -68,7 +68,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
|
||||
trace.reactivateIncremental(activeOcc)
|
||||
|
||||
return processActivated(controller, activeOcc, FeedbackStatus.NORMAL())
|
||||
return processActivated(controller, activeOcc, parent, FeedbackStatus.NORMAL())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -118,11 +118,11 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
|
||||
// We removed the match, so need to reactivate all still valid occurrences from the head
|
||||
// by definition of Chunk and principal rule, all occurrences from the head are principal
|
||||
val matchedOccs = chunk.match.allHeads() as Iterable<Occurrence>
|
||||
val matchedOccs = chunk.match.allHeads().asIterable()
|
||||
val validOccs = matchedOccs.filter { occ ->
|
||||
!occ.justifiedByAny(justificationRoots)
|
||||
}
|
||||
// assert(matchedOccs.all { it.isPrincipal() })
|
||||
assert(matchedOccs.all { it.isPrincipal() })
|
||||
|
||||
execQueue.offerAll(validOccs)
|
||||
}
|
||||
|
|
@ -213,7 +213,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
* Calls the controller to process matches (if any) that were triggered.
|
||||
* This method may be called at most once for a fresh state frame.
|
||||
*/
|
||||
fun processActivated(controller: Controller, active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus {
|
||||
fun processActivated(controller: Controller, active: Occurrence, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus) : FeedbackStatus {
|
||||
push()
|
||||
assert(active.alive)
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
if (isFront() || !active.isPrincipal()) {
|
||||
matches
|
||||
} else {
|
||||
assert( matches.all { ispec.isPrincipal(it.rule()) } )
|
||||
assert( matches.all { it.isPrincipal() } )
|
||||
execQueue.postponeFutureMatches(matches)
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
// TODO: paranoid check. should be isAlive() instead
|
||||
// FIXME: move this check elsewhere
|
||||
if (status.operational && active.stored && match.allStored())
|
||||
processMatch(controller, match, status)
|
||||
processMatch(controller, match, parent, status)
|
||||
else
|
||||
status
|
||||
}
|
||||
|
|
@ -279,7 +279,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
private inline fun FeedbackStatus.then(action: (FeedbackStatus) -> FeedbackStatus) : FeedbackStatus =
|
||||
if (operational) action(this) else this
|
||||
|
||||
private fun processMatch(controller: Controller, match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus =
|
||||
private fun processMatch(controller: Controller, match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus) : FeedbackStatus =
|
||||
controller.offerMatch(match, inStatus)
|
||||
.let {
|
||||
when (it) {
|
||||
|
|
@ -300,7 +300,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
accept(controller, match)
|
||||
}
|
||||
.then {
|
||||
controller.processBody(match, it)
|
||||
controller.processBody(match, parent, it)
|
||||
}
|
||||
.also { trace.finish(match) }
|
||||
|
||||
|
|
@ -341,9 +341,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di
|
|||
}
|
||||
|
||||
|
||||
private fun RuleMatch.isPrincipal() = ispec.isPrincipal(this.rule())
|
||||
|
||||
private fun Occurrence.isPrincipal() = ispec.isPrincipal(this.constraint())
|
||||
|
||||
private fun RuleMatch.allHeads() = matchHeadKept() + matchHeadReplaced()
|
||||
|
||||
private fun RuleMatch.allStored() = allHeads().all { co -> (co as Occurrence).stored }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ internal class ControllerImpl (
|
|||
})
|
||||
|
||||
trace.activate(occ)
|
||||
val status = processing.processActivated(this, active, NORMAL())
|
||||
val status = processing.processActivated(this, active, processing.initialChunk(), NORMAL())
|
||||
if (status is FAILED) {
|
||||
throw status.failure.failureCause()
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ internal class ControllerImpl (
|
|||
// FIXME noLogicalContext
|
||||
val context = Context(NORMAL(), noLogicalContext, null, trace)
|
||||
|
||||
activateConstraint(constraint, processing.justifications(), context)
|
||||
activateConstraint(constraint, processing.initialChunk(), justsCopy(processing.justifications()), context)
|
||||
|
||||
return context.currentStatus()
|
||||
}
|
||||
|
|
@ -102,7 +102,8 @@ internal class ControllerImpl (
|
|||
profiler.profile<FeedbackStatus>("reactivate_${occ.constraint.symbol()}") {
|
||||
|
||||
trace.reactivate(occ)
|
||||
processing.processActivated(this, occ, NORMAL())
|
||||
// FIXME: propagate parent MatchChunk through call to reactivate()
|
||||
processing.processActivated(this, occ, processing.parentChunk(), NORMAL())
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +136,7 @@ internal class ControllerImpl (
|
|||
return context.currentStatus()
|
||||
}
|
||||
|
||||
override fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus {
|
||||
override fun processBody(match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus) : FeedbackStatus {
|
||||
val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace)
|
||||
|
||||
val altIt = match.rule().bodyAlternation().iterator()
|
||||
|
|
@ -153,15 +154,22 @@ internal class ControllerImpl (
|
|||
}
|
||||
|
||||
val savedPos = processing.currentPos()
|
||||
var newParent: MatchJournal.MatchChunk = parent
|
||||
|
||||
val currentJusts = processing.justifications()
|
||||
if (match.isPrincipal()) {
|
||||
// This match corresponds to the last added chunk
|
||||
assert( (savedPos.chunk as? MatchJournal.MatchChunk)?.match === match )
|
||||
newParent = savedPos.chunk as MatchJournal.MatchChunk
|
||||
}
|
||||
|
||||
val justifications = processing.justifications()
|
||||
|
||||
for (item in body) {
|
||||
val itemOk = when (item) {
|
||||
is Constraint -> {
|
||||
// track justifications only for principal constraints
|
||||
val justs = if (ispec.isPrincipal(item)) currentJusts else emptyJustifications()
|
||||
activateConstraint(item, justs, context)
|
||||
val js = if (item.isPrincipal()) justsCopy(justifications) else emptyJustifications()
|
||||
activateConstraint(item, newParent, js, context)
|
||||
}
|
||||
is Predicate -> tellPredicate(item, context)
|
||||
else -> throw IllegalArgumentException("unknown item ${item}")
|
||||
|
|
@ -170,7 +178,7 @@ internal class ControllerImpl (
|
|||
if (itemOk) {
|
||||
context.withStatus { status ->
|
||||
if (status.feedback?.alreadyHandled() == false) {
|
||||
status.feedback.handle(match, processing.ancestorMatch().match, supervisor)
|
||||
status.feedback.handle(match, newParent.match, supervisor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +199,7 @@ internal class ControllerImpl (
|
|||
|
||||
// if failure can be handled here then recover
|
||||
} else if (status.feedback?.alreadyHandled() == false
|
||||
&& status.failure.handle(match, processing.ancestorMatch().match, supervisor)) {
|
||||
&& status.failure.handle(match, newParent.match, supervisor)) {
|
||||
|
||||
status.recover()
|
||||
|
||||
|
|
@ -218,15 +226,15 @@ internal class ControllerImpl (
|
|||
return context.currentStatus()
|
||||
}
|
||||
|
||||
private fun activateConstraint(constraint: Constraint, justifications: Justifications, context: Context) : Boolean {
|
||||
private fun activateConstraint(constraint: Constraint, parent: MatchJournal.MatchChunk, justifications: Justifications, context: Context) : Boolean {
|
||||
val args = supervisor.instantiateArguments(constraint.arguments(), context.logicalContext, context)
|
||||
return context.eval { status ->
|
||||
|
||||
profiler.profile<FeedbackStatus>("activate_${constraint.symbol()}") {
|
||||
|
||||
constraint.occurrence(logicalStateObservable(), args, processing.nextEvidence(), justsCopy(justifications), context.logicalContext, context.ruleUniqueTag).let { occ ->
|
||||
constraint.occurrence(logicalStateObservable(), args, processing.nextEvidence(), justifications, context.logicalContext, context.ruleUniqueTag).let { occ ->
|
||||
trace.activate(occ)
|
||||
processing.processActivated(this, occ, status)
|
||||
processing.processActivated(this, occ, parent, status)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -268,7 +276,10 @@ internal class ControllerImpl (
|
|||
it.first.patternPredicates(it.second.arguments())
|
||||
}.toList()
|
||||
|
||||
private fun RuleMatch.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored }
|
||||
|
||||
private fun RuleMatch.isPrincipal() = ispec.isPrincipal(this.rule())
|
||||
|
||||
private fun Occurrence.isPrincipal() = ispec.isPrincipal(this.constraint())
|
||||
|
||||
|
||||
inner private class Context(inStatus: FeedbackStatus,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ internal class ExecutionQueue(
|
|||
private val postponedMatches: MutableMap<Id<Occurrence>, List<RuleMatchEx>> = HashMap()
|
||||
|
||||
private val execQueue: Queue<ExecPos> =
|
||||
PriorityQueue<ExecPos>(1 + journalIndex.size / 2) { // just an estimate
|
||||
PriorityQueue<ExecPos>(1 + journalIndex.size / 8) { // just an estimate
|
||||
lhs, rhs -> journalIndex.compare(lhs.pos, rhs.pos)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,8 @@ internal class ExecutionQueue(
|
|||
|
||||
// If the occurrence is still in the store after replay (i.e. if it's valid to activate it)
|
||||
if (execPos.activeOcc.stored) {
|
||||
status = processing.reactivate(controller, execPos.activeOcc)
|
||||
// fixme: parentChunk can also be tracked
|
||||
status = processing.reactivate(controller, execPos.activeOcc, processing.parentChunk())
|
||||
// Leave journal processing as it was at the point of failure
|
||||
if (!status.operational) return status
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,11 +39,16 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk>, EvidenceSource {
|
|||
fun logActivation(occ: Occurrence): OccChunk?
|
||||
|
||||
/**
|
||||
* Returns nearest MatchChunk which justifies current chunk.
|
||||
* Returns nearest [MatchChunk] which justifies current chunk.
|
||||
* If current chunk is [MatchChunk], then it is returned.
|
||||
* May return initial chunk. For initial chunk case returns it.
|
||||
* May return initial chunk.
|
||||
*/
|
||||
fun ancestorMatch(): MatchChunk
|
||||
fun parentChunk(): MatchChunk
|
||||
|
||||
/**
|
||||
* Returns the initial chunk which is always present in history
|
||||
*/
|
||||
fun initialChunk(): MatchChunk
|
||||
|
||||
/**
|
||||
* Current position at the journal.
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ internal open class MatchJournalImpl(
|
|||
return added
|
||||
}
|
||||
|
||||
override fun ancestorMatch(): MatchChunk {
|
||||
override fun initialChunk(): MatchChunk = this.first() as MatchChunk
|
||||
|
||||
override fun parentChunk(): MatchChunk {
|
||||
if (current is MatchChunk) {
|
||||
return current as MatchChunk
|
||||
}
|
||||
|
|
@ -111,7 +113,7 @@ internal open class MatchJournalImpl(
|
|||
return prev
|
||||
}
|
||||
}
|
||||
return hist.first() as MatchChunk // initial chunk
|
||||
return initialChunk()
|
||||
}
|
||||
|
||||
override fun currentPos(): MatchJournal.Pos = current.toPos()
|
||||
|
|
@ -333,9 +335,6 @@ internal open class MatchJournalImpl(
|
|||
|
||||
fun MatchJournal.justifications() = this.currentPos().chunk.justifications()
|
||||
|
||||
fun RuleMatch.justifications(): Justifications {
|
||||
val res: Justifications = justsOf()
|
||||
this.matchHeadKept().forEach { res.addAll( (it as Occurrence).justifications() ) }
|
||||
this.matchHeadReplaced().forEach { res.addAll( (it as Occurrence).justifications() ) }
|
||||
return res
|
||||
}
|
||||
fun RuleMatch.justifications(): Justifications = justsOf().also { allJss ->
|
||||
this.allHeads().forEach { allJss.addAll(it.justifications()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import jetbrains.mps.logic.reactor.core.*
|
||||
import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus
|
||||
import jetbrains.mps.logic.reactor.core.internal.MatchJournal
|
||||
import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation
|
||||
import jetbrains.mps.logic.reactor.evaluation.StoreView
|
||||
import jetbrains.mps.logic.reactor.logical.Logical
|
||||
|
|
@ -237,7 +238,7 @@ class MockController : Controller {
|
|||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus): FeedbackStatus {
|
||||
override fun processBody(match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus): FeedbackStatus {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue