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.
This commit is contained in:
Fedor Isakov 2019-05-12 23:38:53 +02:00
parent 11a9505ee9
commit 658963903d
11 changed files with 368 additions and 413 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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<FeedbackStatus>("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)

View File

@ -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)
}

View File

@ -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<Id<Logical<*>>, 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<Id<Logical<*>>>()
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)
}
}

View File

@ -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<StateFrame>()
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<Occurrence> {
val dit = stateFrames.descendingIterator()
val set = HashSet<Id<Occurrence>>()
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<Occurrence>) : StoreView {
val allOccurrences = occurrences.toSet()
val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet()
override fun constraintSymbols(): Iterable<ConstraintSymbol> = allSymbols
override fun allOccurrences(): Iterable<ConstraintOccurrence> = allOccurrences
override fun occurrences(symbol: ConstraintSymbol): Iterable<ConstraintOccurrence> =
allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet()
}
}

View File

@ -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<Id<Logical<*>>, ConsList<LogicalObserver>> = Maps.of()
private val activatedOccurrences = ArrayList<Occurrence>()
private val deactivatedOccurrences = ArrayList<Occurrence>()
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<Occurrence> =
activatedOccurrences.asSequence()
fun allDeactivated() : Sequence<Occurrence> =
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<FeedbackStatus>("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 }
}

View File

@ -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<ConstraintSymbol, IdHashSet<Occurrence>>
var logical2occurrences: PersMap<Id<Logical<*>>, IdHashSet<Occurrence>>
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<ConstraintOccurrence> {
return symbol2occurrences.values().flatten().filter { co -> co.stored }.asSequence()
}
fun view(): StoreView = StoreViewImpl(allOccurrences())
}
private class StoreViewImpl(occurrences: Sequence<ConstraintOccurrence>) : StoreView {
val allOccurrences = occurrences.toSet()
val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet()
override fun constraintSymbols(): Iterable<ConstraintSymbol> = allSymbols
override fun allOccurrences(): Iterable<ConstraintOccurrence> = allOccurrences
override fun occurrences(symbol: ConstraintSymbol): Iterable<ConstraintOccurrence> =
allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet()
}

View File

@ -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.
}

View File

@ -342,31 +342,34 @@ class TestController {
val X = metaLogical<Int>("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())