diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt index e9b8c6bc..10a2b9c1 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt @@ -17,9 +17,13 @@ package jetbrains.mps.logic.reactor.core import com.github.andrewoma.dexx.collection.Maps +import javaslang.collection.Map import jetbrains.mps.logic.reactor.core.internal.RuleMatchImpl import com.github.andrewoma.dexx.collection.Map as PersMap + +typealias DispatchingFrontState = PersMap + /** * A front-end interface to [RuleMatcher]. * @@ -41,12 +45,23 @@ class Dispatcher (val ruleIndex: RuleIndex) { */ fun front() = DispatchingFront() + fun frontFromState(predState: DispatchingFrontState) = DispatchingFront(predState) + inner class DispatchingFront { private var ruletag2probe: PersMap private val allMatches = arrayListOf() + // fixme: make it a default constructor also, with empty input? + constructor(predState: DispatchingFrontState) { + this.ruletag2probe = Maps.of() + ruletag2matcher.entries.forEach { e -> + val probe = predState[e.key] ?: e.value.probe() + this.ruletag2probe = ruletag2probe.put(e.key, probe) + } + } + constructor() { this.ruletag2probe = Maps.of() ruletag2matcher.entries.forEach { e -> @@ -74,6 +89,8 @@ class Dispatcher (val ruleIndex: RuleIndex) { */ fun matches() : Iterable = allMatches + fun state() : DispatchingFrontState = ruletag2probe + /** * Returns a new [DispatchingFront] instance that is "expanded" with matches corresponding to the * specified active constraint occurrence. diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/IncrementalProgramSpec.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/IncrementalProgramSpec.kt new file mode 100644 index 00000000..c8b9d50a --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/IncrementalProgramSpec.kt @@ -0,0 +1,32 @@ +/* + * 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.Constraint +import jetbrains.mps.logic.reactor.program.Rule + +interface IncrementalProgramSpec +{ + fun isPrincipal(ctr: Constraint): Boolean + fun isPrincipal(rule: Rule): Boolean + + object NonIncrSpec : IncrementalProgramSpec + { + override fun isPrincipal(ctr: Constraint): Boolean = false + override fun isPrincipal(rule: Rule): Boolean = false + } +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RulesDiff.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RulesDiff.kt new file mode 100644 index 00000000..334d4cc7 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RulesDiff.kt @@ -0,0 +1,35 @@ +/* + * 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 + +data class RulesDiff(val added: Iterable, val removed: Set) { + companion object { + fun emptyDiff() = RulesDiff(emptyList(), emptySet()) + + fun findDiff(old: Iterable, new: Iterable): RulesDiff { + val oldSet = old.toHashSet() + val newSet = new.toHashSet() + + val added = new.filter { !oldSet.contains(it.uniqueTag()) } + val removed = oldSet.minus(newSet.map { it.uniqueTag() }) + + return RulesDiff(added, removed) + } + } +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/SessionToken.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/SessionToken.kt new file mode 100644 index 00000000..bd74aca4 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/SessionToken.kt @@ -0,0 +1,21 @@ +/* + * 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.core.internal.MatchJournal + +data class SessionToken(val journalView: MatchJournal.View, val ruleTags: Iterable, val frontState: DispatchingFrontState) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt index 3ceea3d1..b68f5d81 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt @@ -31,12 +31,10 @@ import com.github.andrewoma.dexx.collection.Map as PersMap internal class ControllerImpl ( val supervisor: Supervisor, val state: ProcessingStateImpl, + val ispec: IncrementalProgramSpec = IncrementalProgramSpec.NonIncrSpec, val trace: EvaluationTrace = EvaluationTrace.NULL, val profiler: Profiler? = null) : Controller { - // fixme -// val journal: StoreAwareJournalImpl = StoreAwareJournalImpl(state, null) - /** For tests only */ override fun storeView(): StoreView = state.storeView() @@ -52,6 +50,13 @@ internal class ControllerImpl ( return storeView() } + fun incrLaunch(constraint: Constraint, rulesDiff: RulesDiff): FeedbackStatus { + // todo: use profiler here? + state.invalidateByRules(rulesDiff.removed) + state.addRuleApplications(rulesDiff.added) + return state.launchQueue(this) + } + fun activate(constraint: Constraint) : FeedbackStatus { // FIXME noLogicalContext val context = Context(NORMAL(), noLogicalContext) @@ -139,7 +144,8 @@ internal class ControllerImpl ( val itemOk = when (item) { is Constraint -> { // track justifications only for principal constraints - val justs = if (item.isPrincipal) currentJusts else emptyJusts() +// val justs = if (item.isPrincipal) currentJusts else emptyJusts() + val justs = if (ispec.isPrincipal(item)) currentJusts else emptyJusts() activateConstraint(item, justs, context) } is Predicate -> tellPredicate(item, context) @@ -298,4 +304,10 @@ fun createController( trace: EvaluationTrace = EvaluationTrace.NULL, profiler: Profiler? = null) : Controller = - ControllerImpl(supervisor, ProcessingStateImpl(MatchJournalImpl(), Dispatcher(ruleIndex)), trace, profiler) + ControllerImpl( + supervisor, + ProcessingStateImpl(Dispatcher(ruleIndex).front(), MatchJournalImpl(), ruleIndex, trace, profiler), + IncrementalProgramSpec.NonIncrSpec, + trace, + profiler + ) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/EvaluationSessionImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/EvaluationSessionImpl.kt index 71fdbb09..ebe32615 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/EvaluationSessionImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/EvaluationSessionImpl.kt @@ -41,12 +41,34 @@ internal class EvaluationSessionImpl private constructor ( override fun controller() = controller - private fun launch(main: Constraint, profiler: Profiler?) : FeedbackStatus { - val dispatcher = Dispatcher(RuleIndex(program().handlers())) - val journal = MatchJournalImpl() - val state = ProcessingStateImpl(journal, dispatcher, trace, profiler) - this.controller = ControllerImpl(supervisor, state, trace, profiler) - return controller.activate(main) +// private fun launch(main: Constraint, profiler: Profiler?) : FeedbackStatus { +// val journal = MatchJournalImpl() +// val state = ProcessingStateImpl(journal, dispatcher, trace, profiler) +// this.controller = ControllerImpl(supervisor, state, trace, profiler) +// return controller.activate(main) +// } + + private fun incrLaunch(main: Constraint, profiler: Profiler?, token: SessionToken?, ispec: IncrementalProgramSpec) : FeedbackStatus { + val ruleIndex = RuleIndex(program().handlers()) + val dispatcher = Dispatcher(ruleIndex) + + if (ispec is IncrementalProgramSpec.NonIncrSpec || token == null) { + val state = ProcessingStateImpl(dispatcher.front(), MatchJournalImpl(ispec), ruleIndex, trace, profiler) + + this.controller = ControllerImpl(supervisor, state, ispec, trace, profiler) + return controller.activate(main) + + } else { + val state = ProcessingStateImpl( + dispatcher.frontFromState(token.frontState), + MatchJournalImpl(ispec, token.journalView), + ruleIndex, trace, profiler + ) + + val rulesDiff = RulesDiff.findDiff(token.ruleTags, ruleIndex) + this.controller = ControllerImpl(supervisor, state, ispec, trace, profiler) + return controller.incrLaunch(main, rulesDiff) + } } private class Config(val program: Program) : EvaluationSession.Config() { @@ -55,6 +77,10 @@ internal class EvaluationSessionImpl private constructor ( var evaluationTrace: EvaluationTrace = EvaluationTrace.NULL + var ispec: IncrementalProgramSpec = IncrementalProgramSpec.NonIncrSpec + + var token: SessionToken? = null + override fun withTrace(computingTracer: EvaluationTrace): EvaluationSession.Config { this.evaluationTrace = computingTracer return this @@ -64,6 +90,15 @@ internal class EvaluationSessionImpl private constructor ( return this } + override fun withSessionToken(token: SessionToken): EvaluationSession.Config { + this.token = token + return this + } + override fun withIncrSpec(ispec: IncrementalProgramSpec): EvaluationSession.Config { + this.ispec = ispec + return this + } + override fun withParameter(key: ParameterKey, value: T): EvaluationSession.Config { this.parameters.put(key, value as Any) return this @@ -83,7 +118,8 @@ internal class EvaluationSessionImpl private constructor ( var failure: Feedback? = null try { val main = parameters[ParameterKey.of("main", Constraint::class.java)] as Constraint - val status = session.launch(main, profiler) +// val status = session.launch(main, profiler) + val status = session.incrLaunch(main, profiler, token, ispec) if (status is FAILED) { failure = status.failure } @@ -102,6 +138,8 @@ internal class EvaluationSessionImpl private constructor ( } return object : EvaluationResult { + override fun token(): SessionToken = session.controller.state.snapshot() + override fun storeView(): StoreView? = session.controller.storeView() override fun feedback(): EvaluationFeedback? = failure diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt index e21e4fab..ba531ab6 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt @@ -16,6 +16,7 @@ package jetbrains.mps.logic.reactor.core.internal +import jetbrains.mps.logic.reactor.core.IncrementalProgramSpec import jetbrains.mps.logic.reactor.core.Justs import jetbrains.mps.logic.reactor.core.Occurrence import jetbrains.mps.logic.reactor.core.justsOf @@ -47,13 +48,16 @@ interface MatchJournal : MutableIterable { data class View(val chunks: List, val nextChunkId: Int) - data class Chunk(val match: RuleMatch, val id: Int, val justifications: Justs) : Pos { + class Chunk(val match: RuleMatch, val id: Int, val justifications: Justs) : Pos { data class Entry(val occ: Occurrence, val isDiscarded: Boolean = false) { override fun toString() = (if (isDiscarded) '-' else '+') + occ.toString() } var occurrences: MutableList = mutableListOf() + fun findOccurrence(ctr: Constraint): Occurrence? = + occurrences.find { !it.isDiscarded && it.occ.constraint.symbol() == ctr.symbol() }?.occ + override fun toString() = "(id=$id, $justifications, ${match.rule().uniqueTag()}, $occurrences)" override fun equals(other: Any?) = other is Chunk @@ -72,7 +76,10 @@ interface MatchJournal : MutableIterable { } -internal open class MatchJournalImpl(view: MatchJournal.View? = null): MatchJournal +internal open class MatchJournalImpl( + private val ispec: IncrementalProgramSpec, + view: MatchJournal.View? = null +) : MatchJournal { // invariant: never empty private val hist: MutableList @@ -91,21 +98,24 @@ internal open class MatchJournalImpl(view: MatchJournal.View? = null): MatchJour } } + constructor(view: MatchJournal.View? = null) : this(IncrementalProgramSpec.NonIncrSpec, view) private var pos: MutableListIterator = hist.listIterator() private var current: MatchJournal.Chunk = pos.next() // take the initial chunk, move pos - // Forbid access to the initial chunk - override fun iterator() = hist.iterator().apply { next() } + override fun iterator() = hist.iterator() override fun logMatch(match: RuleMatch) { // TODO: think, what about principal (i.e. 'matching') rules, but with empty head justs? - // If the set of justifications isn't empty, then we deal with principal rule + // Two cases when a new chunk is created: + // either the set of justifications isn't empty + // or we directly know that we deal with a principal rule. val justs = match.headJustifications() - if (!justs.isEmpty) { + if (ispec.isPrincipal(match.rule()) || !justs.isEmpty) { + justs.add(nextChunkId) val newChunk = MatchJournal.Chunk(match, nextChunkId, justs) pos.add(newChunk) @@ -114,9 +124,8 @@ internal open class MatchJournalImpl(view: MatchJournal.View? = null): MatchJour current = newChunk } - val m = match as RuleMatchImpl - // Log discards - match.forEachReplaced {occ -> + // Log discards + (match as RuleMatchImpl).forEachReplaced {occ -> current.occurrences.add(MatchJournal.Chunk.Entry(occ, true)) occ.terminate() } @@ -133,7 +142,6 @@ internal open class MatchJournalImpl(view: MatchJournal.View? = null): MatchJour // fixme: unclear, whether this makes sense here, in "pure" journal without store? along with reset and replay? // reset to the beginning, even before the initial chunk, because 'replay' after 'resetPos' is expected override fun resetPos() { pos = hist.listIterator() } -// override fun resetPos() { pos = hist.listIterator().apply { next() } } override fun reset(pastPos: MatchJournal.Pos) { while (pos.hasPrevious()) { diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt index 3dd95695..73e4ced7 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt @@ -17,15 +17,14 @@ package jetbrains.mps.logic.reactor.core.internal import jetbrains.mps.logic.reactor.core.* -import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.EvaluationTrace import jetbrains.mps.logic.reactor.evaluation.RuleMatch -import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.logical.Logical -import jetbrains.mps.logic.reactor.program.ConstraintSymbol -import jetbrains.mps.logic.reactor.util.Id +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.Rule import jetbrains.mps.logic.reactor.util.Profiler import jetbrains.mps.logic.reactor.util.profile +import org.jetbrains.kotlin.utils.mapToIndex import java.util.* @@ -103,15 +102,115 @@ internal open class StateFrameStack() : ProcessingState, LogicalObserver } -internal class ProcessingStateImpl(journal: MatchJournal, - dispatcher: Dispatcher, +internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.DispatchingFront, + journal: MatchJournal, + ruleIndex: RuleIndex, val trace: EvaluationTrace = EvaluationTrace.NULL, val profiler: Profiler? = null) : StoreAwareJournalImpl(journal) { + private val ruleOrder: Map = ruleIndex.map { it.uniqueTag() }.mapToIndex() - private var dispatchingFront: Dispatcher.DispatchingFront = dispatcher.front() + private val execQueue: Queue = LinkedList() + private data class ExecPos(val pos: MatchJournal.Pos, val activeOcc: Occurrence) + + // only for tests + fun pushActivateFirstOccOf(ctr: Constraint): Boolean { + val pos = currentPos() + val occ = pos.chunk().findOccurrence(ctr) + if (occ != null) { + execQueue.offer(ExecPos(pos, occ)) + return true + } + return false + } + + fun invalidateByRules(ruleIds: Set) { +// for (ruleTag in rulesDiff.removed) { +// +// } + } + + private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int) + + fun addRuleApplications(rules: Iterable) { + val activationCandidates = mutableListOf() + + val it = journal.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 chunkRule = chunk.match.rule() + val pp = chunkRule.principalProduction() + + // Does this chunk have princ. production and can activate anything at all? + if (pp != null) { + for (rule in rules) { + // Can this rule be matched by 'pp'? + // fixme: maybe use RuleIndex here? + if (rule.headKept().contains(pp) || rule.headReplaced().contains(pp)) { + + val princOcc = chunk.findOccurrence(pp) + ?: throw IllegalStateException("Chunk with principal production must have it in activated occurrences!") + + // 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)) + // todo: also use the rule to help Dispatcher in future? i.e. try matching only on the candidate rule + } + } + } + + val it = activationCandidates.listIterator() + while (it.hasNext()) { + val (candRule, candOcc, parentId) = it.next() + + // Place to activate candidate: + // either as the last one, after all existing activations + // or according to the ordering between rules. + + val childChunksEnded = !chunk.justifications.contains(parentId) + + val currRuleOrder = ruleOrder[chunkRule.uniqueTag()] + ?: throw(IllegalStateException("There can be no chunks with rules not in rule index!")) + val candRuleOrder = ruleOrder[candRule.uniqueTag()] + ?: throw(IllegalStateException("Match candidate rule must be present in the rule index!")) + + if (childChunksEnded || currRuleOrder < candRuleOrder) { + // 'replay' replays the whole chunk, so we need the previous chunk as pos (i.e. adding after it). + execQueue.offer(ExecPos(prevChunk, candOcc)) + // Drop the candidate after appropriate activation place is found. + it.remove() + } + } + + prevChunk = chunk + } + } + + fun launchQueue(controller: Controller): FeedbackStatus.NORMAL { + if (execQueue.isNotEmpty()) { + resetStore() + + do { + val execPos = execQueue.poll() + replay(execPos.pos) + controller.reactivate(execPos.activeOcc) + } while (execQueue.isNotEmpty()) + } + // todo: replay to the end after queue is fully executed? + // fixme: get FeedbackStatus out of reactivate() + return FeedbackStatus.NORMAL() + } + + fun snapshot(): SessionToken { + return SessionToken(view(), ruleOrder.keys, dispatchingFront.state()) + } /** * Called to update the state with the currently active constraint occurrence. @@ -191,4 +290,14 @@ internal class ProcessingStateImpl(journal: MatchJournal, private fun RuleMatch.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored } + + private fun Rule.headAll() = headKept() + headReplaced() + + // todo: use IncrementalProgramSpec; move method to Chunk, supposedly? + private fun Rule.principalProduction(): Constraint? { + val princProds = bodyAlternation().first().filter { + it is Constraint && it.isPrincipal() + } + return if (princProds.isNotEmpty()) princProds.first() as Constraint else null + } } \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationResult.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationResult.java index eece2fd9..f8732a4f 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationResult.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationResult.java @@ -16,13 +16,15 @@ package jetbrains.mps.logic.reactor.evaluation; -import jetbrains.mps.logic.reactor.core.EvaluationFailure; +import jetbrains.mps.logic.reactor.core.SessionToken; /** * @author Fedor Isakov */ public interface EvaluationResult { + public SessionToken token(); + public StoreView storeView(); public EvaluationFeedback feedback(); diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java index 5ddd5261..e0b5b42d 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java @@ -17,6 +17,8 @@ package jetbrains.mps.logic.reactor.evaluation; +import jetbrains.mps.logic.reactor.core.IncrementalProgramSpec; +import jetbrains.mps.logic.reactor.core.SessionToken; import jetbrains.mps.logic.reactor.program.Program; /** @@ -94,6 +96,10 @@ public abstract class EvaluationSession { @Deprecated public abstract Config withStoreView(StoreView storeView); + public abstract Config withSessionToken(SessionToken token); + + public abstract Config withIncrSpec(IncrementalProgramSpec ispec); + public abstract EvaluationResult start(Supervisor supervisor); }