From 658963903d46de852c91d5f4aa52c1fbb875b54a Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Sun, 12 May 2019 23:38:53 +0200 Subject: [PATCH] Refactor controller: extract component for managing dispatching front, drop Store, introduce ProcessingState and StateFrame. ProcessingState class to represent current state, including the dispatching front and all the logical observers. StateFrame is a persistent class that reacts to activated constraint and launches processing via Controller. Breaking change: the frames correspond to an activated constraint occurrence, and not to a triggered rule match as it has been the case before. --- .../mps/logic/reactor/core/Controller.kt | 3 +- .../mps/logic/reactor/core/Occurrence.kt | 4 +- ...{FrameObservable.kt => ProcessingState.kt} | 12 +- .../reactor/core/internal/ControllerImpl.kt | 103 ++-------- .../core/internal/EvaluationSessionImpl.kt | 8 +- .../mps/logic/reactor/core/internal/Frame.kt | 132 ------------- .../core/internal/ProcessingStateImpl.kt | 138 +++++++++++++ .../logic/reactor/core/internal/StateFrame.kt | 181 ++++++++++++++++++ .../mps/logic/reactor/core/internal/Store.kt | 152 --------------- reactor/Test/test/RulesHelper.kt | 23 +-- reactor/Test/test/TestController.kt | 25 +-- 11 files changed, 368 insertions(+), 413 deletions(-) rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{FrameObservable.kt => ProcessingState.kt} (65%) delete mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Frame.kt create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StateFrame.kt delete mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Store.kt diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt index 0d0ece4c..990336db 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt @@ -17,7 +17,6 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus -import jetbrains.mps.logic.reactor.core.internal.RuleMatchImpl import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation import jetbrains.mps.logic.reactor.evaluation.StoreView @@ -34,7 +33,7 @@ interface Controller { fun reactivate(occ: Occurrence) - fun currentFrame(): FrameObservable + fun state(): ProcessingState /** For tests only */ fun evaluate(occ: Occurrence): StoreView diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt index 0b69301b..4fb26dab 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt @@ -43,7 +43,7 @@ data class Occurrence (val controller: Controller, init { for (a in arguments) { if (a is Logical<*>) { - controller.currentFrame().addObserver(a) { this } + controller.state().addForwardingObserver(a, this) } } } @@ -69,7 +69,7 @@ data class Occurrence (val controller: Controller, fun terminate() { for (a in arguments) { if (a is Logical<*>) { - controller.currentFrame().removeObserver(a) { this } + controller.state().removeForwardingObserver(a, this) } } alive = false diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/FrameObservable.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt similarity index 65% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/FrameObservable.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt index 2da15e38..62a13612 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/FrameObservable.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt @@ -19,15 +19,13 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.logical.Logical /** - * Serves to add/remove observers of [Logical] that track the frame stack. + * @author Fedor Isakov */ -interface FrameObservable { - /** Returns the store associated with this frame */ - fun storeObserver(): LogicalObserver +interface ProcessingState { - fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) - - fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) + fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) + fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) + } \ No newline at end of file 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 f9acc68d..0a990905 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 @@ -30,24 +30,20 @@ import com.github.andrewoma.dexx.collection.Map as PersMap internal class ControllerImpl ( val supervisor: Supervisor, - val ruleIndex: RuleIndex, + val state: ProcessingStateImpl, val trace: EvaluationTrace = EvaluationTrace.NULL, val profiler: Profiler? = null) : Controller { - private var dispatchFront = Dispatcher(ruleIndex).front() - - // FIXME move to context - private val frameStack = FrameStack() - /** For tests only */ - override fun storeView(): StoreView = frameStack.current.store.view() + override fun storeView(): StoreView = state.storeView() /** For tests only */ override fun evaluate(occ: Occurrence): StoreView { // create the internal occurrence val active = occ.constraint().occurrence(this, occ.arguments()) - val status = process(active, NORMAL()) + + val status = state.processActivated(active, NORMAL()) if (status is FAILED) { throw status.failure.failureCause() } @@ -63,7 +59,8 @@ internal class ControllerImpl ( return context.currentStatus() } - override fun currentFrame(): FrameObservable = frameStack.current + override fun state(): ProcessingStateImpl = + state override fun ask(invocation: PredicateInvocation): Boolean { val solver = invocation.predicate().symbol().solver() @@ -80,35 +77,13 @@ internal class ControllerImpl ( override fun reactivate(occ: Occurrence) { // FIXME propagate the status further up the call stack - // TODO: introduce status to solver API? - // TODO update the stack - - val status = process(occ, NORMAL()) + val status = state.processActivated(occ, NORMAL()) if (status is FAILED) { throw status.failure.failureCause() } } - private fun processMatch(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus = - offerMatch(match, inStatus) - .let { when (it) { - is ABORTED -> { // guard is not satisfied - trace.reject(match) - return it.recover() // return from the enclosing method - - } is FAILED -> { // guard failed - trace.feedback(it.failure) - return it.recover() // return from the enclosing method - - } else -> it - } } - .also { consumeMatch(match) } - .also { trace.trigger(match) } - .also { processDiscarded(match) } - .then { processBody(match, it) } - .also { trace.finish(match) } - override fun offerMatch(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus = inStatus.then { checkMatchPreconditions(match, it) } .also { trace.trying(match) } @@ -154,8 +129,7 @@ internal class ControllerImpl ( } } - val savedFrame = frameStack.current - frameStack.push() + val savedFrame = state.currentFrame() for (item in body) { val itemOk = when (item) { @@ -201,7 +175,7 @@ internal class ControllerImpl ( if (!altOk) { // all constraints activated up to a failure are lost - frameStack.reset(savedFrame) + state.reset(savedFrame) } else { // body finished normally @@ -211,61 +185,12 @@ internal class ControllerImpl ( return context.currentStatus() } - - private fun process(active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus { - assert(active.alive) - - return profiler.profile("process_${active.constraint().symbol()}") { - - if (!active.stored) { - frameStack.current.store.store(active) - trace.activate(active) - } else { - trace.reactivate(active) - } - - val activatedFront = dispatchFront.expand(active) - this.dispatchFront = activatedFront - - val outStatus = activatedFront.matches().toList().fold(inStatus) { status, match -> - // TODO: paranoid check. should be isAlive() instead - // FIXME: move this check elsewhere - if (status.operational && active.stored && match.allStored()) - processMatch(match as RuleMatchImpl, status) - else - status - } - - // TODO: should be isAlive() - if (active.stored) { - trace.suspend(active) - } - - outStatus - } - } - - private fun consumeMatch(match: RuleMatchEx) { - this.dispatchFront = dispatchFront.consume(match) - } - - private fun processDiscarded (match: RuleMatchEx) { - match.forEachReplaced { occ -> - this.dispatchFront = dispatchFront.contract(occ) - - frameStack.current.store.discard(occ) - - trace.discard(occ) - } - } - + private fun activateConstraint(constraint: Constraint, context: Context) : Boolean { val args = supervisor.instantiateArguments(constraint.arguments(), context.logicalContext, context) return context.eval { status -> - // TODO update the state stack - val active = constraint.occurrence(this, args, context.logicalContext) - process(active, status) + state.processActivated(constraint.occurrence(this, args, context.logicalContext), status) } } @@ -364,6 +289,6 @@ fun createController( supervisor: Supervisor, ruleIndex: RuleIndex, trace: EvaluationTrace = EvaluationTrace.NULL, - profiler: Profiler? = null, - storeView: StoreView? = null) : Controller = - ControllerImpl(supervisor, ruleIndex, trace, profiler) + profiler: Profiler? = null) : Controller = + + ControllerImpl(supervisor, ProcessingStateImpl(Dispatcher(ruleIndex)), 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 5b01cb87..cf84566a 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 @@ -16,10 +16,8 @@ package jetbrains.mps.logic.reactor.core.internal +import jetbrains.mps.logic.reactor.core.* import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus.FAILED -import jetbrains.mps.logic.reactor.core.EvaluationSessionEx -import jetbrains.mps.logic.reactor.core.Feedback -import jetbrains.mps.logic.reactor.core.RuleIndex import jetbrains.mps.logic.reactor.evaluation.* import jetbrains.mps.logic.reactor.program.Constraint import jetbrains.mps.logic.reactor.program.Program @@ -44,7 +42,9 @@ internal class EvaluationSessionImpl private constructor ( override fun controller() = controller private fun launch(main: Constraint, profiler: Profiler?) : FeedbackStatus { - this.controller = ControllerImpl(supervisor, RuleIndex(program().handlers()), trace, profiler) + val dispatcher = Dispatcher(RuleIndex(program().handlers())) + val state = ProcessingStateImpl(dispatcher, trace, profiler) + this.controller = ControllerImpl(supervisor, state, trace, profiler) return controller.activate(main) } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Frame.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Frame.kt deleted file mode 100644 index 34b38601..00000000 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Frame.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.internal - -import com.github.andrewoma.dexx.collection.ConsList -import com.github.andrewoma.dexx.collection.Map -import com.github.andrewoma.dexx.collection.Maps -import jetbrains.mps.logic.reactor.core.FrameObservable -import jetbrains.mps.logic.reactor.core.LogicalObserver -import jetbrains.mps.logic.reactor.core.addObserver -import jetbrains.mps.logic.reactor.evaluation.StoreView -import jetbrains.mps.logic.reactor.logical.Logical -import jetbrains.mps.logic.reactor.util.Id -import jetbrains.mps.logic.reactor.util.cons -import jetbrains.mps.logic.reactor.util.remove -import java.util.* - -internal class Frame: LogicalObserver, FrameObservable { - - val stack: FrameStack - - val store: Store - - private var observers: Map>, ConsList<(FrameObservable) -> LogicalObserver>> - - constructor(stack: FrameStack) { - this.stack = stack - this.store = Store() { stack.current } - this.observers = Maps.of() - } - - constructor(stack: FrameStack, prev: Frame) { - this.stack = stack - this.store = Store(prev.store) { stack.current } - this.observers = prev.observers - } - - override fun storeObserver() = store - - override fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { - val logicalId = Id(logical) - if (!observers.containsKey(logicalId)) { - stack.addObserver(logical) - } - this.observers = observers.put(logicalId, - observers[logicalId]?.prepend(obs) ?: cons(obs)) - } - - override fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { - val logicalId = Id(logical) - observers[logicalId].remove(obs)?.let { newList -> - this.observers = observers.put(logicalId, newList) - if (newList.isEmpty) { - stack.removeObserver(logical) - } - } - } - - override fun valueUpdated(logical: Logical<*>) { - observers[Id(logical)]?.let { list -> - for (obs in list) { - obs(this).valueUpdated(logical) - } - } - } - - override fun parentUpdated(logical: Logical<*>) { - observers[Id(logical)]?.let { list -> - for (obs in list) { - obs(this).parentUpdated(logical) - } - } - } - -} - -internal class FrameStack : LogicalObserver { - - var current: Frame - - val observing = HashSet>>() - - init { - this.current = Frame(this) - } - - fun push(): Frame { - val frame = Frame(this, current) - this.current = frame - return frame - } - - fun reset(frame: Frame): Unit { - this.current = frame - } - - fun addObserver(logical: Logical<*>) { - val token = Id(logical) - if (!observing.contains(token)) { - logical.addObserver(this) - observing.add(token) - } - } - - fun removeObserver(logical: Logical<*>) { - // NOP - // yes, keep listening, the updates are still filtered down the stream - } - - override fun valueUpdated(logical: Logical<*>) { - current.valueUpdated(logical) - } - - override fun parentUpdated(logical: Logical<*>) { - current.parentUpdated(logical) - } - -} 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 new file mode 100644 index 00000000..56fa0bb6 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt @@ -0,0 +1,138 @@ +/* + * 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.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.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.util.Profiler +import java.util.* + + +/** + * Processing of constraints comprises several major activities: + * + * - finding match(-es) corresponding to an active occurrence + * - checking the guard condition + * - deactivating discarded occurrences from the match + * - processing the body of a constraints rule. + * + * A ProcessingState maintains a stack of [StateFrame] instances. + * It also serves as a proxy for observers of [Logical] that are added during program + * evaluation. + * + * @author Fedor Isakov + */ + +internal class ProcessingStateImpl private constructor(val trace: EvaluationTrace = EvaluationTrace.NULL, + val profiler: Profiler? = null) : + ProcessingState, LogicalObserver +{ + + // invariant: never empty + private val stateFrames = LinkedList() + + constructor(dispatcher: Dispatcher, + trace: EvaluationTrace = EvaluationTrace.NULL, + profiler: Profiler? = null) : + this(trace, profiler) + { + stateFrames.push(StateFrame(this, dispatcher)) + } + + fun processActivated(active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus = + push().processActivated(active, inStatus) + + fun currentFrame() : StateFrame = + stateFrames.first + + fun push() : StateFrame { + val newFrame = StateFrame(stateFrames.first) + stateFrames.push(newFrame) + return newFrame + } + + fun reset(frame: StateFrame) { + val it = stateFrames.iterator() + while (it.hasNext()) { + if (frame === it.next()) { // referential equality + return + } + it.remove() + } + throw IllegalStateException("invalid frame") + } + + override fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) { + if (!currentFrame().isObserving(logical)) { + logical.addObserver(this) + } + currentFrame().addForwardingObserver(logical, observer) + } + + override fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) { + currentFrame().removeForwardingObserver(logical, observer) + if (!currentFrame().isObserving(logical)) { + logical.removeObserver(this) + } + } + + override fun valueUpdated(logical: Logical<*>) { + // forward to the top frame + currentFrame().valueUpdated(logical) + } + + override fun parentUpdated(logical: Logical<*>) { + // forward to the top frame + currentFrame().parentUpdated(logical) + } + + fun storeView(): StoreView = + StoreViewImpl(allOccurrences()) + + private fun allOccurrences(): Sequence { + val dit = stateFrames.descendingIterator() + val set = HashSet>() + while (dit.hasNext()) { + val stateFrame = dit.next() + stateFrame.allActivated().forEach { set.add(Id(it)) } + stateFrame.allDeactivated().forEach { set.remove(Id(it)) } + } + return set.map { it.wrapped }.asSequence() + } + + private class StoreViewImpl(occurrences: Sequence) : StoreView { + + val allOccurrences = occurrences.toSet() + + val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet() + + override fun constraintSymbols(): Iterable = allSymbols + + override fun allOccurrences(): Iterable = allOccurrences + + override fun occurrences(symbol: ConstraintSymbol): Iterable = + allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet() + + } + + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StateFrame.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StateFrame.kt new file mode 100644 index 00000000..44abb8e8 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/StateFrame.kt @@ -0,0 +1,181 @@ +/* + * 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.internal + +import com.github.andrewoma.dexx.collection.ConsList +import com.github.andrewoma.dexx.collection.Map as PersMap +import com.github.andrewoma.dexx.collection.Maps +import jetbrains.mps.logic.reactor.core.* +import jetbrains.mps.logic.reactor.core.Dispatcher.DispatchingFront +import jetbrains.mps.logic.reactor.evaluation.RuleMatch +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.util.* + +/** + * A StateFrame captures a processing state corresponding to an event that consists + * of an activated constraint occurrence followed by a series of triggered rule + * matches. + * + * Each triggered match may result in one or more constraint occurrences + * to be deactivated/terminated. + * + * An activated or deactivated constraint occurrence updates the dispatching front -- + * an object that maintains a search tree for possible rule matches. + * + * @author Fedor Isakov + */ + +internal class StateFrame private constructor(val state: ProcessingStateImpl) : LogicalObserver +{ + private lateinit var dispatchingFront: DispatchingFront + + private var observers: PersMap>, ConsList> = Maps.of() + + private val activatedOccurrences = ArrayList() + + private val deactivatedOccurrences = ArrayList() + + constructor(state: ProcessingStateImpl, dispatcher: Dispatcher) : this(state) { + this.dispatchingFront = dispatcher.front() + } + + constructor(prototype: StateFrame) : this(prototype.state) { + this.dispatchingFront = prototype.dispatchingFront + this.observers = prototype.observers + } + + fun destroy() { + // TODO remove observers + + } + + fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) { + val logicalId = Id(logical) + this.observers = observers.put(logicalId, + observers[logicalId]?.prepend(observer) ?: cons(observer)) + } + + fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) { + val logicalId = Id(logical) + observers[logicalId]?.let { + this.observers = observers.put(logicalId, it.remove(observer)) + } + } + + fun isObserving(logical: Logical<*>) = + observers[Id(logical)] != null + + override fun valueUpdated(logical: Logical<*>) { + observers[Id(logical)]?.let { list -> + for (observer in list) { + observer.valueUpdated(logical) + } + } + } + + override fun parentUpdated(logical: Logical<*>) { + observers[Id(logical)]?.let { list -> + for (observer in list) { + observer.parentUpdated(logical) + } + } + } + + fun allActivated() : Sequence = + activatedOccurrences.asSequence() + + fun allDeactivated() : Sequence = + deactivatedOccurrences.asSequence() + + /** + * Called to update the state with the currently active constraint occurrence. + * 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(active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus { + assert(active.alive) + + return state.profiler.profile("activated_${active.constraint().symbol()}") { + + if (!active.stored) { + active.stored = true + state.trace.activate(active) + + } else { + state.trace.reactivate(active) + } + + activatedOccurrences += active + this.dispatchingFront = dispatchingFront.expand(active) + + val outStatus = dispatchingFront.matches().toList().fold(inStatus) { status, match -> + // TODO: paranoid check. should be isAlive() instead + // FIXME: move this check elsewhere + if (status.operational && active.stored && match.allStored()) + processMatch(active.controller, match, status) + else + status + } + + // TODO: should be isAlive() + if (active.stored) { + state.trace.suspend(active) + } + + outStatus + } + } + + 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 = + controller.offerMatch(match, inStatus) + .let { when (it) { + is FeedbackStatus.ABORTED -> { // guard is not satisfied + state.trace.reject(match) + return it.recover() // return from the enclosing method + + } is FeedbackStatus.FAILED -> { // guard failed + state.trace.feedback(it.failure) + return it.recover() // return from the enclosing method + + } else -> it + } } + .also { state.trace.trigger(match) } + .also { accept(match) } + .then { controller.processBody(match, it) } + .also { state.trace.finish(match) } + + + private fun accept (match: RuleMatchEx) { + this.dispatchingFront = dispatchingFront.consume(match) + + match.forEachReplaced { occ -> + this.dispatchingFront = dispatchingFront.contract(occ) + deactivatedOccurrences += occ + + occ.stored = false + occ.terminate() + + state.trace.discard(occ) + } + } + + private fun RuleMatch.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored } + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Store.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Store.kt deleted file mode 100644 index 151e8b79..00000000 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Store.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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.internal - -import com.github.andrewoma.dexx.collection.Maps -import jetbrains.mps.logic.reactor.core.FrameObservable -import jetbrains.mps.logic.reactor.core.LogicalObserver -import jetbrains.mps.logic.reactor.core.Occurrence -import jetbrains.mps.logic.reactor.core.occurrence -import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence -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.* -import java.util.* -import com.github.andrewoma.dexx.collection.Map as PersMap -import com.github.andrewoma.dexx.collection.Set as PersSet -import com.github.andrewoma.dexx.collection.Vector as PersVector - -/** - * @author Fedor Isakov - */ - -/** - * Constrants storeObserver. - * - * TODO: make this class persistent. - */ -internal class Store : LogicalObserver { - - val currentFrame: () -> FrameObservable - - var symbol2occurrences: PersMap> - - var logical2occurrences: PersMap>, IdHashSet> - - constructor(copyFrom: Store, currentFrame: () -> FrameObservable) { - this.currentFrame = currentFrame - this.symbol2occurrences = copyFrom.symbol2occurrences - this.logical2occurrences = copyFrom.logical2occurrences - } - - constructor(currentFrame: () -> FrameObservable) { - this.currentFrame = currentFrame - this.symbol2occurrences = Maps.of() - this.logical2occurrences = Maps.of() - } - - override fun valueUpdated(logical: Logical<*>) { - } - - override fun parentUpdated(logical: Logical<*>) { - // TODO: should we care about the order in which occurrences are stored? - val logicalId = Id(logical) - logical2occurrences[logicalId]?.let { toMerge -> - val rootId = Id(logical.findRoot()) - var newSet = logical2occurrences[rootId] ?: emptyIdSet() - for (log in toMerge) { - newSet = newSet.add(log) - } - this.logical2occurrences = logical2occurrences.remove(logicalId).put(rootId, newSet) - assert(logical2occurrences.containsKey(rootId)) - assert(!logical2occurrences.containsKey(logicalId)) - } - } - - fun store(occ: Occurrence) { - val symbol = occ.constraint().symbol() - - this.symbol2occurrences = symbol2occurrences.put(symbol, - symbol2occurrences[symbol]?.add(occ) ?: singletonIdSet(occ)) - - for (arg in occ.arguments()) { - // FIXME extracting the value is unnecessary here - val value = if (arg is Logical<*> && arg.isBound) arg.findRoot().value() else arg - when (value) { - is Logical<*> -> { - // free logical - val argId = Id(value.findRoot()) - this.logical2occurrences = logical2occurrences.put(argId, - logical2occurrences[argId]?.add(occ) ?: singletonIdSet(occ)) - currentFrame().addObserver(value) { frame -> frame.storeObserver() } - } - } - } - - occ.stored = true - } - - fun discard(occ: Occurrence, profiler: Profiler? = null, tag: String? = null): Unit { - val symbol = occ.constraint().symbol() - - symbol2occurrences[symbol]?.remove(occ)?.let { newList -> - this.symbol2occurrences = symbol2occurrences.put(symbol, newList) - } - - for (arg in occ.arguments()) { - when (arg) { - is Logical<*> -> { - val argId = Id(arg.findRoot()) - logical2occurrences[argId]?.remove(occ)?.let { newList -> - this.logical2occurrences = if (newList.isEmpty) { - logical2occurrences.remove(argId) - - } else { - logical2occurrences.put(argId, newList) - } - } - } - } - } - - occ.stored = false - occ.terminate() - } - - fun allOccurrences(): Sequence { - return symbol2occurrences.values().flatten().filter { co -> co.stored }.asSequence() - } - - fun view(): StoreView = StoreViewImpl(allOccurrences()) - -} - -private class StoreViewImpl(occurrences: Sequence) : StoreView { - - val allOccurrences = occurrences.toSet() - - val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet() - - override fun constraintSymbols(): Iterable = allSymbols - - override fun allOccurrences(): Iterable = allOccurrences - - override fun occurrences(symbol: ConstraintSymbol): Iterable = - allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet() - -} diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index 42b6ded2..611d5adb 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -127,20 +127,7 @@ fun sym2(id: String): ConstraintSymbol = ConstraintSymbol(id, 2) class MockController : Controller { - - override fun currentFrame(): FrameObservable = object : FrameObservable { - override fun storeObserver(): LogicalObserver { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { - } - - override fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - } - + override fun ask(invocation: PredicateInvocation): Boolean { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } @@ -157,6 +144,14 @@ class MockController : Controller { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } + override fun state(): ProcessingState = object : ProcessingState { + override fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) { + } + + override fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) { + } + } + override fun storeView(): StoreView { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } diff --git a/reactor/Test/test/TestController.kt b/reactor/Test/test/TestController.kt index 0b2e5fad..0eaeadf5 100644 --- a/reactor/Test/test/TestController.kt +++ b/reactor/Test/test/TestController.kt @@ -342,31 +342,34 @@ class TestController { val X = metaLogical("X") programWithRules( rule("zeroth", - headKept(constraint("foo")), body(statement({ x -> x.set(999) }, X), - constraint("bar", X)) + headKept(constraint("foo")), + body(statement({ x -> x.set(999) }, X), + constraint("bar", X)) ), rule("first", headKept(constraint("foo")), - body(constraint("bar", X), - constraint("qux", X)) + body(constraint("bar", X), + constraint("qux", X)) ), rule("second", headReplaced(constraint("qux", X)), - body(constraint("expected1"), - statement({ x -> x.set(123) }, X)) + body(constraint("expected1"), + statement({ x -> x.set(123) }, X)) ), rule("third", headReplaced(constraint("foo")), - body(constraint("unexpected")) + body(constraint("unexpected")) ), rule("fourth", headReplaced(constraint("foo")), - headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 123 }, X)), - body(constraint("expected2")) + headReplaced(constraint("bar", X)), + guard(expression({ x -> x.getNullable() == 123 }, X)), + body(constraint("expected2")) ), rule("fifth", - headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 999 }, X)), - body(constraint("expected3", X)) + headReplaced(constraint("bar", X)), + guard(expression({ x -> x.getNullable() == 999 }, X)), + body(constraint("expected3", X)) ) ).controller().evaluate(occurrence("foo")).run { assertEquals(3, allOccurrences().count())