diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt index 4fcb8a04..4747ed51 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Justified.kt @@ -69,36 +69,14 @@ interface Justified { fun justifiedBy(other: Justified): Boolean = this.justifications().contains(other.evidence) - /** - * Checks whether this [Justified] entity is supported by any of the [others] [Justified] entities. - */ - fun justifiedByAny(others: Collection): Boolean = - others.any { this.justifiedBy(it) } - - /** - * Checks whether this [Justified] entity is supported by the [others], and only by them. - */ - fun justifiedOnlyBy(others: Collection): Boolean { - val allEvidence = justsFromCollection(others.map { it.evidence }) - allEvidence.add(evidence) - return allEvidence.containsAll(justifications()) - } - - /** - * Append [Justifications] from [other] entity to justifications of this [Justified]. - * After the operation this.justifiedBy(other) returns true. - */ - fun justifyBy(other: Justified): Unit { - // ensure operation doesn't break antisymmetric property of relation - assert(!other.justifiedBy(this) || this.evidence == other.evidence) - - justifications().addAll(other.justifications()) - } - /** * Append [Justifications] from all [others] entities to justifications of this [Justified] */ - fun justifyByAll(others: Iterable): Unit = others.forEach { justifyBy(it) } + fun addJustifications(others: Iterable): Unit = others.forEach { + // ensure operation doesn't break antisymmetric property of relation + assert(!it.justifiedBy(this) || evidence == it.evidence) + justifications().addAll(it.justifications()) + } } 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 e266f64f..42aed9a7 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt @@ -94,6 +94,7 @@ class Occurrence (observable: LogicalStateObservable, private fun handleFeedbackStatus(status: FeedbackStatus) { if (status is FeedbackStatus.FAILED) { + // FIXME failureCause() is nullable, can't throw null throw status.failure.failureCause() } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt index 0dc47ed2..69baf96d 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt @@ -56,23 +56,6 @@ internal class ConstraintsProcessing( logicalState.clearController(controller) } - fun evaluate(controller: Controller, prototype: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus = - profiler.profile("activate_${prototype.constraint().symbol()}") { - // fixme: ensure justifications are tracked (incremented) correctly in processing & creator - // NB: provide new justifications instead of occ.justifications - with(JustifiedOccurrenceCreator(evidence(), initialChunk().justifications())) { - - prototype.constraint.occurrence( - controller.logicalStateObservable(), prototype.arguments(), prototype.logicalContext(), prototype.sourceRule() - ).let { occ -> - trace.activate(occ) - processActivated(controller, occ, initialChunk(), inStatus) - } - - } - } - - /** * Called to update the state with the currently active constraint occurrence. * Calls the controller to process matches (if any) that were triggered. 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 347f3f0e..8847eb81 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 @@ -117,7 +117,7 @@ internal class EvaluationSessionImpl private constructor ( } override fun endSession(session: SessionParts): SessionToken = with(session) { - SessionTokenImpl(journal.view(), ruleIndex.toRules(), emptyFrontState(), ruleIndex, logicalState, principalObservers.apply { clearTriggerReceiver() }) + SessionTokenImpl(journal.storeView(), ruleIndex.toRules(), emptyFrontState(), ruleIndex, logicalState, principalObservers.apply { clearTriggerReceiver() }) } override fun runSession(session: SessionParts, main: Constraint): EvaluationResult = with(session) { @@ -222,7 +222,7 @@ internal class EvaluationSessionImpl private constructor ( val invalidRules: Collection ): EvaluationResult { override fun token() = token - override fun storeView(): StoreView = token.journalView.storeView + override fun storeView(): StoreView = token.storeView override fun feedback(): EvaluationFeedback? = if (status is FAILED) status.failure else null override fun invalidFeedbackKeys(): Collection = invalidFeedbackKeys override fun invalidRules(): Collection = invalidRules diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/IncrSpecHolder.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/IncrSpecHolder.kt index b6a4d3f0..501177dd 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/IncrSpecHolder.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/IncrSpecHolder.kt @@ -30,7 +30,7 @@ interface IncrSpecHolder { val RuleMatch.isPrincipal get() = ispec.isPrincipal(this.rule()) - val Rule.isPrincipal get() = ispec.isPrincipal(this) +// val Rule.isPrincipal get() = ispec.isPrincipal(this) val RuleMatch.isWeakPrincipal get() = ispec.isWeakPrincipal(this.rule()) 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 bfc93baa..bfb050fe 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 @@ -31,9 +31,8 @@ interface MatchJournal : EvidenceSource { // for tests companion object { fun fromView( - ispec: IncrementalSpec = IncrementalSpec.DefaultSpec, - view: MatchJournal.View? = null - ): MatchJournal = MatchJournalImpl(ispec, EvaluationTrace.NULL, view) + ispec: IncrementalSpec = IncrementalSpec.DefaultSpec + ): MatchJournal = MatchJournalImpl(ispec, EvaluationTrace.NULL) } /** @@ -41,13 +40,13 @@ interface MatchJournal : EvidenceSource { * Log occurrences discarded by match in current [Chunk]. * Returns added [Chunk], null for non-principal rules. */ - fun logMatch(match: RuleMatch): MatchChunk? + fun logMatch(match: RuleMatch) /** * Log occurrence activation in current [Chunk]. * Returns added [Chunk], null for non-principal occurrences. */ - fun logActivation(occ: Occurrence): OccChunk? + fun logActivation(occ: Occurrence) /** * Returns nearest [MatchChunk] which justifies current chunk. @@ -86,6 +85,7 @@ interface MatchJournal : EvidenceSource { /** * Returns snapshot of the journal. + * FOR TESTS ONLY */ fun view(): View @@ -96,15 +96,17 @@ interface MatchJournal : EvidenceSource { fun storeView(): StoreView fun basisRuleTags(chunk: Chunk): List - + + // NB: returns same collection of justifications, not copy + fun justifications() = this.currentPos().chunk.justifications() + + fun evidence() = this.currentPos().chunk.evidence + /** * Immutable snapshot of [MatchJournal]. + * FOR TESTS ONLY */ - data class View(val chunks: List, val evidenceSeed: Evidence) : MatchJournalView { - - override fun getStoreView(): StoreView = StoreViewImpl( - chunks.flatMap { it.entries() }.allOccurrences().asSequence() - ) + data class View(val chunks: List, val evidenceSeed: Evidence) { } /** @@ -176,46 +178,3 @@ interface ChunkIterator { infix fun at(chunk: MatchJournal.Chunk) = current === chunk } - -interface MutableChunkIterator: ChunkIterator { - fun addChunk(chunk: MatchJournal.Chunk) -} - -internal infix fun ChunkIterator.assertAt(pos: MatchJournal.Pos) { - if (!(this at pos)) - throw IllegalStateException("Position wasn't found in journal: $pos") -} - -internal 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() - -} - - -private fun Iterable.allOccurrences(): List { - val set = HashSet>() - for (it in this) { - val idOcc = Id(it.occ) - if (it.discarded) set.remove(idOcc) else set.add(idOcc) - } - return set.map { it.wrapped } -} - - -// NB: returns same collection of justifications, not copy -fun MatchJournal.justifications() = this.currentPos().chunk.justifications() -fun MatchJournal.evidence() = this.currentPos().chunk.evidence -// todo: this should be more correct -//fun MatchJournal.justifications() = this.parentChunk().justifications() -//fun MatchJournal.evidence() = this.parentChunk().evidence - diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt index 2c23fdb2..b821e9c1 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt @@ -32,8 +32,7 @@ import java.util.* internal open class MatchJournalImpl( override val ispec: IncrementalSpec, - val trace: EvaluationTrace = EvaluationTrace.NULL, - view: MatchJournal.View? = null + val trace: EvaluationTrace = EvaluationTrace.NULL ) : MatchJournal, IncrSpecHolder { private abstract class ChunkImpl : Chunk { @@ -82,17 +81,12 @@ internal open class MatchJournalImpl( final override fun nextEvidence(): Evidence = evidenceSeed++ init { - if (view == null) { - val initChunk = MatchChunkImpl(nextEvidence(), InitRuleMatch) - hist = IteratorMutableList(LinkedList().apply { - add(CornerChunk) - add(initChunk) - add(CornerChunk) - }) - } else { - hist = IteratorMutableList(LinkedList(view.chunks as List)) - evidenceSeed = view.evidenceSeed - } + val initChunk = MatchChunkImpl(nextEvidence(), InitRuleMatch) + hist = IteratorMutableList(LinkedList().apply { + add(CornerChunk) + add(initChunk) + add(CornerChunk) + }) ancestorChunksStack = mutableListOf() __cursor = Cursor(hist.listIterator()) @@ -100,31 +94,25 @@ internal open class MatchJournalImpl( assert(initialChunk.match is InitRuleMatch) } - constructor(trace: EvaluationTrace = EvaluationTrace.NULL, - view: MatchJournal.View? = null) : this(IncrementalSpec.DefaultSpec, trace, view) - - override fun logMatch(match: RuleMatch): MatchChunk? { - val added: MatchChunk? + constructor(trace: EvaluationTrace = EvaluationTrace.NULL) : this(IncrementalSpec.DefaultSpec, trace) + override fun logMatch(match: RuleMatch) { if (match.isPrincipal) { - added = MatchChunkImpl(nextEvidence(), match) - __cursor.addChunk(added) - trackAncestor(added) - logAncestor(added) + val nextChunk = MatchChunkImpl(nextEvidence(), match) + __cursor.addChunk(nextChunk) + resetParentChunk(nextChunk) + pushParentChunk(nextChunk) } else { - added = null val dummy: Justified = MatchChunkImpl(nullEvidence, match) // collects justifications - trackAncestor(dummy) + resetParentChunk(dummy) // If a non-principal match has any principal // occurrences in head -- they must be tracked - logJustificationsFrom(match) + collectJustificationsFrom(match) } // Log discarded occurrences (match as RuleMatchImpl).forEachReplaced { occ -> __cursor.addEntryToCurrent(Chunk.Entry(occ, true)) } - - return added } private fun indexChunk(chunk: Chunk) { @@ -134,18 +122,18 @@ internal open class MatchJournalImpl( private fun lookupChunkByEvidence(evidence: Evidence) = chunkIndex.get(evidence) - private fun trackAncestor(logEvent: Justified) { + private fun resetParentChunk(logEvent: Justified) { while (!logEvent.justifiedBy(parentChunk())) { ancestorChunksStack.pop() } assert(logEvent.justifiedBy(parentChunk())) } - private fun logAncestor(nextChunk: MatchChunk) { + private fun pushParentChunk(nextChunk: MatchChunk) { ancestorChunksStack.push(nextChunk) } - private fun logJustificationsFrom(match: RuleMatch) { + private fun collectJustificationsFrom(match: RuleMatch) { val parent: MatchChunk = parentChunk() val moreJustified = match.allHeads().filter { @@ -155,24 +143,17 @@ internal open class MatchJournalImpl( if (moreJustified.isNotEmpty()) { __cursor.applyToPast(parent.toPos()) { child -> - child.justifyByAll(moreJustified) + child.addJustifications(moreJustified) } } } - override fun logActivation(occ: Occurrence): OccChunk? { - val added: OccChunk? - - trackAncestor(occ) + override fun logActivation(occ: Occurrence) { + resetParentChunk(occ) if (occ.isPrincipal) { - added = OccChunkImpl(occ) - __cursor.addChunk(added) - } else { - added = null + __cursor.addChunk(OccChunkImpl(occ)) } __cursor.addEntryToCurrent(Chunk.Entry(occ)) - - return added } internal fun resetOccurrences(occSpecs: List) = @@ -204,7 +185,7 @@ internal open class MatchJournalImpl( override fun reset(pastPos: MatchJournal.Pos) { __cursor.moveToPastRemoving(pastPos) { - popAncestor() + popParentChunk() resetOccurrences(it.entries()) } // drop occurrences at final chunk @@ -215,7 +196,7 @@ internal open class MatchJournalImpl( __cursor assertAt pastPos } - private fun popAncestor() { + private fun popParentChunk() { if (__cursor at ancestorChunksStack.peek()) ancestorChunksStack.pop() } @@ -291,24 +272,45 @@ internal open class MatchJournalImpl( } } + internal infix fun ChunkIterator.assertAt(pos: MatchJournal.Pos) { + if (!(this at pos)) + throw IllegalStateException("Position wasn't found in journal: $pos") + } + + + internal 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() + + } + + /** * Provides [MatchJournal] modification and look-ahead traverse with [Chunk] replay. * Represents [MatchJournal] state: additions & removals happen at pointed-to position. * [Cursor] points at position between [current] & [initial]. */ - private inner class Cursor(private val iter: MutableListIterator) : AbstractChunkIterator(iter), - MutableChunkIterator { + private inner class Cursor(private val iter: MutableListIterator) : AbstractChunkIterator(iter){ /** * Replays [Chunk]s while iterating */ fun initial(): Chunk { assert(atStart()) assert(__next is MatchChunk) - logAncestor(__next as MatchChunk) + pushParentChunk(__next as MatchChunk) return nextImpl() } - override fun addChunk(chunk: Chunk) { + fun addChunk(chunk: Chunk) { iter.add(chunk as ChunkImpl) __current = chunk indexChunk(chunk) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/SessionTokenImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/SessionTokenImpl.kt index f0fc6d49..61774fb3 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/SessionTokenImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/SessionTokenImpl.kt @@ -17,14 +17,13 @@ package jetbrains.mps.logic.reactor.core.internal import jetbrains.mps.logic.reactor.core.DispatchingFrontState -import jetbrains.mps.logic.reactor.core.Occurrence import jetbrains.mps.logic.reactor.core.RuleIndex -import jetbrains.mps.logic.reactor.evaluation.MatchJournalView import jetbrains.mps.logic.reactor.evaluation.SessionToken +import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.program.Rule data class SessionTokenImpl( - private val journalView: MatchJournal.View, + private val storeView: StoreView, private val rules: Iterable, private val frontState: DispatchingFrontState, val ruleIndex: RuleIndex, @@ -32,6 +31,6 @@ data class SessionTokenImpl( val principalObservers: PrincipalObserverDispatcher = PrincipalObserverDispatcher.EMPTY ) : SessionToken { - override fun getJournalView(): MatchJournalView = journalView + override fun getStoreView(): StoreView = storeView override fun getRules(): Iterable = rules } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/MatchJournalView.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/MatchJournalView.java deleted file mode 100644 index 2086fa21..00000000 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/MatchJournalView.java +++ /dev/null @@ -1,21 +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.evaluation; - -public interface MatchJournalView { - StoreView getStoreView(); -} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/SessionToken.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/SessionToken.java index 1605ef23..950ea317 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/SessionToken.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/SessionToken.java @@ -21,8 +21,9 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; public interface SessionToken { - @NotNull() - MatchJournalView getJournalView(); + @NotNull + StoreView getStoreView(); + @NotNull() Iterable getRules(); } diff --git a/reactor/Test/test/MockIncrProgSpec.kt b/reactor/Test/test/MockIncrProgSpec.kt deleted file mode 100644 index fb18e28d..00000000 --- a/reactor/Test/test/MockIncrProgSpec.kt +++ /dev/null @@ -1,41 +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. - */ - -import jetbrains.mps.logic.reactor.program.Constraint -import jetbrains.mps.logic.reactor.program.ConstraintSymbol -import jetbrains.mps.logic.reactor.program.IncrementalSpec -import jetbrains.mps.logic.reactor.program.Rule - -class MockIncrProgSpec( - val principalRuleTags: Set, - val principalCtrSyms: Set, - val weakPrincipalRuleTags: Set, - private var strictness: IncrementalSpec.AssertLevel = IncrementalSpec.AssertLevel.Normal -) : IncrementalSpec { - - constructor(principalRuleTags: Set, principalCtrSyms: Set) : - this(principalRuleTags, principalCtrSyms, emptySet()) - - fun withContractChecks() = this.also { strictness = IncrementalSpec.AssertLevel.AssertContracts } - - override fun isPrincipal(ctr: Constraint): Boolean = principalCtrSyms.contains(ctr.symbol()) - override fun isPrincipal(rule: Rule): Boolean = principalRuleTags.contains(rule.uniqueTag()) - override fun isWeakPrincipal(rule: Rule): Boolean = weakPrincipalRuleTags.contains(rule.uniqueTag()) - - override fun ability(): IncrementalSpec.Enabled = IncrementalSpec.Enabled.Yes - override fun incrLevel(): IncrementalSpec.IncrLevel = IncrementalSpec.IncrLevel.Full - override fun assertLevel(): IncrementalSpec.AssertLevel = strictness -} \ No newline at end of file diff --git a/reactor/Test/test/TestIncrementalProgram.kt b/reactor/Test/test/TestIncrementalProgram.kt deleted file mode 100644 index d97e5c42..00000000 --- a/reactor/Test/test/TestIncrementalProgram.kt +++ /dev/null @@ -1,2253 +0,0 @@ -import jetbrains.mps.logic.reactor.program.IncrementalSpec -import jetbrains.mps.logic.reactor.core.Occurrence -import jetbrains.mps.logic.reactor.core.ReactorLifecycle -import jetbrains.mps.logic.reactor.core.internal.MatchJournal -import jetbrains.mps.logic.reactor.evaluation.* -import jetbrains.mps.logic.reactor.logical.Logical -import jetbrains.mps.logic.reactor.program.Constraint -import jetbrains.mps.logic.reactor.program.ConstraintSymbol -import org.junit.* -import org.junit.Assert.* -import program.MockConstraint -import solver.tellEquals - -/* - * 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. - */ - -/** - * @author Fedor Isakov - */ - -@Ignore -class TestIncrementalProgram { - - companion object { - @BeforeClass - @JvmStatic - fun setup() { - ReactorLifecycle.init(); - } - - @AfterClass - @JvmStatic - fun teardown() { - ReactorLifecycle.deinit(); - } - } - - private fun Builder.launch(name: String, incrSpec: IncrementalSpec, resultHandler: (EvaluationResult) -> Unit) - : Pair { - val result = EvaluationSession.newSession(program(name)) - .withParameter(EvaluationSession.ParameterKey.of("main", Constraint::class.java), MockConstraint(ConstraintSymbol("main", 0))) - .withIncrSpec(incrSpec) - .start(MockSupervisor()) - result.feedback()?.let { if (it.isFailure) throw it.failureCause() } - resultHandler(result) - return this to result - } - - private fun Builder.relaunch(name: String, incrSpec: IncrementalSpec, sessionToken: SessionToken, resultHandler: (EvaluationResult) -> Unit) - : Pair { - val prog = program(name) - val result = EvaluationSession.newSession(prog) - .withParameter(EvaluationSession.ParameterKey.of("main", Constraint::class.java), MockConstraint(ConstraintSymbol("main", 0))) - .withIncrSpec(incrSpec) - .withSessionToken(sessionToken) - .start(MockSupervisor()) - result.feedback()?.let { if (it.isFailure) throw it.failureCause() } - resultHandler(result) - return this to result - } - - private fun SessionToken.chunks(): List = - (this.journalView as MatchJournal.View).chunks - - private fun EvaluationResult.chunksSymbolView() = this.token().chunks().map { - it.entries().map { entry -> !entry.discarded to entry.occ.constraint().symbol() } - } - - // private fun EvaluationResult.lastChunk() = this.token().chunks().last() - private fun EvaluationResult.lastChunk() = this.token().chunks().let { it[it.size - 2] } - - private fun EvaluationResult.countChunks() = this.token().chunks().size - - private fun Iterable.constraintSymbols() = this.map { it.constraint.symbol() } - - private fun EvaluationResult.lastChunkSymbols() = lastChunk() - .entries() - .filter { !it.discarded } - .map { it.occ } - .constraintSymbols() - - private fun ConstraintOccurrence.firstValue(): Any? = (arguments().first() as? Logical<*>)?.value() - - - private fun PredicateInvocation.eq(left: T, right: T) = invocationContext().tellEquals(left, right) - - - @Test - fun noChangesRelaunch() { - lateinit var storeSnapshot: Iterable - - val progSpec = MockIncrProgSpec( - setOf("main"), - setOf(sym0("foo")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )) - ).launch("addRule", progSpec) { result -> - storeSnapshot = result.storeView().constraintSymbols() - storeSnapshot shouldBe setOf(sym0("foo")) - - }.also { (builder, evalRes) -> - builder.relaunch("no changes", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe storeSnapshot - } - } - } - - @Test - fun rmUnusedRule() { - lateinit var storeSnapshot: Iterable - - val progSpec = MockIncrProgSpec( - setOf("main", "bar"), - setOf(sym0("foo"), sym0("bar")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("bar", - headReplaced( - pconstraint("bar") - ), - body()) - ).launch("addRule", progSpec) { result -> - storeSnapshot = result.storeView().constraintSymbols() - storeSnapshot shouldBe setOf(sym0("foo")) - - }.also { (builder, evalRes) -> - builder - .removeRules(listOf("bar")) - .relaunch("no changes", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe storeSnapshot - } - } - } - - @Test - fun addNewMatchAtTheEnd() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar"), - setOf(sym0("foo")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )) - ).launch("addRule", progSpec) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo")) - result.lastChunkSymbols() shouldBe listOf(sym0("foo")) - - }.also { (builder, evalRes) -> - builder.programWithRules( - rule("foo.bar", - headReplaced( - pconstraint("foo") - ), - body( - constraint("bar") - ) - ) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar")) - result.lastChunkSymbols() shouldBe listOf(sym0("bar")) - } - } - } - - @Test - fun addOneMoreMatch() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz"), - setOf(sym0("foo")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.bar", - headKept( - pconstraint("foo") - ), - body( - constraint("bar") - ) - ) - ).launch("addRule", progSpec) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar")) - result.lastChunkSymbols() shouldBe listOf(sym0("bar")) - - }.also { (builder, evalRes) -> - builder.programWithRules( - rule("foo.baz", - headKept( - pconstraint("foo") - ), - body( - constraint("baz") - ) - ) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("baz")) - result.lastChunkSymbols() shouldBe listOf(sym0("baz")) - } - } - } - - @Test - fun addTwoDependentMatches() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz"), - setOf(sym0("foo")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )) - ).launch("addRule", progSpec) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo")) - - }.also { (builder, evalRes) -> - builder.programWithRules( - rule("foo.bar", - headKept( - pconstraint("foo") - ), - body( - constraint("bar") - )), - rule("foo.baz", - headKept( - pconstraint("foo"), - constraint("bar") // depends on the previous match being correctly logged - ), - body( - constraint("baz") - ) - ) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("baz")) - result.lastChunkSymbols() shouldBe listOf(sym0("baz")) - } - } - } - - @Test - fun addAtStartMatch() { - var nPrincipalMatches: Int = 0 - - val progSpec = MockIncrProgSpec( - setOf("main.foo", "main.bar"), - setOf(sym0("main")) - ) - programWithRules( - // 'at-start' rules are launched with a 'main' occurrence, their heads aren't actually empty - rule("main.foo", - headKept( - pconstraint("main") - ), - body( - constraint("foo") - )) - ).launch("addRule", progSpec) { result -> - nPrincipalMatches = result.countChunks() - - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main")) - result.lastChunkSymbols() shouldBe listOf(sym0("foo")) - - }.also { (builder, evalRes) -> - builder.programWithRules( - rule("main.bar", - headKept( - pconstraint("main") - ), - body( - constraint("bar") - ) - ) - ).relaunch("atStart", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("main"), sym0("bar")) - result.countChunks() shouldBe nPrincipalMatches + 1 - result.lastChunkSymbols() shouldBe listOf(sym0("bar")) - } - } - } - - - @Test - fun addAfterReplaced() { - val progSpec = MockIncrProgSpec( - setOf("main", "main.bar"), - setOf(sym0("main")) - ) - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - constraint("foo") - )) - ).launch("addRule", progSpec) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - builder.programWithRules( - rule("main.bar", - headKept( - pconstraint("main") - ), - body( - constraint("bar") - ) - ) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - // no new matches: 'main' has been already discarded - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo")) - result.countChunks() shouldBe nPrincipalMatches - } - } - } - - // Tests that incremental reactivation of discarded occurrences is handled correctly. - @Test - fun addKeptBeforeReplaced() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "baz.dummy"), - setOf(sym0("foo"), sym0("baz")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - rule("baz.dummy", - headKept( - pconstraint("baz") - ), - body( - constraint("dummy") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"), sym0("dummy")) - result.lastChunkSymbols() shouldBe listOf(sym0("dummy")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - // Insert rule before foo.baz: produce bar before discarding foo in foo.baz - builder.insertRulesAt(1, - rule("foo.bar", - headKept( - pconstraint("foo") - ), - body( - constraint("bar") - )) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("baz"), sym0("dummy")) - result.lastChunkSymbols() shouldBe listOf(sym0("dummy")) - result.countChunks() shouldBe (1 + nPrincipalMatches) - } - } - } - - @Test - fun addReplacedBeforeReplaced() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "baz.dummy"), - setOf(sym0("foo"), sym0("baz")) - ) - // Program is almost identical to the one in previous test, - // except added rule discards its head. - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - rule("baz.dummy", - headKept( - pconstraint("baz") - ), - body( - constraint("dummy") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"), sym0("dummy")) - result.lastChunkSymbols() shouldBe listOf(sym0("dummy")) - - }.also { (builder, evalRes) -> - - builder.insertRulesAt(1, - rule("foo.bar", - // 'foo' will be discarded before existing foo-rules, so they shouldn't match & must be removed - headReplaced( - pconstraint("foo") - ), - body( - constraint("bar") - )) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar")) - result.lastChunkSymbols() shouldBe listOf(sym0("bar")) - } - } - } - - @Test - fun addAfterKeptBeforeReplaced() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "foo.qux"), - setOf(sym0("foo")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.bar", - headKept( - pconstraint("foo") - ), - body( - constraint("bar") - )), - rule("foo.qux", - headReplaced( - pconstraint("foo") - ), - body( - constraint("qux") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("qux")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - builder.insertRulesAt(2, - rule("foo.baz", - headKept( - pconstraint("foo") - ), - body( - constraint("baz") - )) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("baz"), sym0("qux")) - result.countChunks() shouldBe (1 + nPrincipalMatches) - } - } - } - - @Test - fun addAndRelaunchTwice() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "baz.lax"), - setOf(sym0("foo"), sym0("baz")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.bar", - headKept( - pconstraint("foo") - ), - body( - constraint("bar") - ) - ) - ).launch("addRule", progSpec) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar")) - - // Add single rule and relaunch - }.let { (builder, evalRes) -> - builder.programWithRules( - rule("foo.baz", - headKept( - pconstraint("foo") - ), - body( - pconstraint("baz") - ) - ) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("baz")) - - } - }.let { (builder, evalRes) -> - builder.programWithRules( - rule("baz.lax", - headReplaced( - pconstraint("foo") - ), - headKept( - pconstraint("baz") - ), - body( - constraint("lax") - ) - ) - ).relaunch("test2", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("baz"), sym0("lax")) - } - } - } - - // Weaker matches - @Test - fun invalidateStaleDiscardingMatches() { - val progSpec = MockIncrProgSpec( - setOf("main", "has_type", "no_type", "typeOf"), - setOf(sym0("check"), sym0("recover"), sym0("expectType"), sym0("typeOf")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("check"), - pconstraint("expectType") - )), - rule("has_type", - headReplaced( - pconstraint("expectType") - ), - headKept( - pconstraint("typeOf") - ), - body( - constraint("has_type") - )), - rule("no_type", - headReplaced( - pconstraint("expectType") - ), - body( - constraint("no_type") // not expected after increm launch - )) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("no_type"), sym0("check")) - - }.also { (builder, evalRes) -> - - builder.insertRulesAt(1, - rule("typeOf", - headKept( - pconstraint("check") - ), - body( - pconstraint("typeOf") - )) - ).relaunch("test1", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("has_type"), sym0("check"), sym0("typeOf")) - } - } - } - - // Description: due to incremental launch, match on 'foobar' will be known - // before it should actually happen. If it happens earlier than needed, - // 'bar' will be discarded too early and program results will be incorrect. - // Tests postponedMatches logic. - @Test - fun futureMatchInDueTime() { - val progSpec = MockIncrProgSpec( - setOf(".foo", ".bar", "foobar", "bar.1st"), - setOf(sym0("start"), sym0("foo"), sym0("bar")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("start") - )), - rule(".foo", - headKept( - pconstraint("start") - ), - body( - pconstraint("foo") - )), - rule("foobar", - headReplaced( - pconstraint("foo"), - pconstraint("bar") - ), - body( - constraint("2nd") - )), - rule("bar.1st", - headKept( - pconstraint("bar") - ), - body( - constraint("1st") - ) - ) - ).launch("withoutBar", progSpec) { result -> - - // "foobar" hasn't matched without 'bar, so 'foo' is still here - result.storeView().constraintSymbols() shouldBe setOf(sym0("start"), sym0("foo")) - - }.also { (builder, evalRes) -> - builder.insertRulesAt(1, - rule(".bar", - headKept( - pconstraint("start") - ), - body( - pconstraint("bar") - )) - ).relaunch("withBar", progSpec, evalRes.token()) { result -> - - // if "foobar" happens too early, "1st" occ won't be produced - result.storeView().constraintSymbols() shouldBe setOf(sym0("start"), sym0("1st"), sym0("2nd")) - // ensure right rule match order: the last chunk must contain "2nd" - result.lastChunkSymbols() shouldBe listOf(sym0("2nd")) - } - } - } - - // Test on complex condition involving consumedSignatures and 'reactivation' flag in RuleMatchFront.expand - @Test - fun completePartialMatch() { - val progSpec = MockIncrProgSpec( - setOf(".foo", ".bar", "foobar"), - setOf(sym0("start"), sym0("foo"), sym0("bar")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("start") - )), - rule(".foo", - headReplaced( - pconstraint("start") - ), - body( - pconstraint("foo") - )), - // Propagation rule that doesn't match the first time due to the lack of 'bar' - rule("foobar", - headKept( - pconstraint("foo"), - pconstraint("bar") - ), - body( - constraint("marker") - )) - ).launch("withoutBar", progSpec) { result -> - - // "foobar" hasn't matched without 'bar, so 'foo' is still here - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - // Provides 'bar' by reactivating 'foo' - builder.insertRulesAt(1, - rule(".bar", - headKept( - pconstraint("foo") - ), - body( - pconstraint("bar") - )) - ).relaunch("withBar", progSpec, evalRes.token()) { result -> - - // if "foobar" happens too early, "1st" occ won't be produced - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("bar"), sym0("marker")) - result.lastChunkSymbols() shouldBe listOf(sym0("marker")) - result.countChunks() shouldBe (2 + 1 + nPrincipalMatches) // +[.bar, foobar] +activation of bar - } - } - } - - // Same as 'completePartialMatch' test, except it discards 'foo' at the end. - @Test - fun completePartialMatchBeforeReplaced() { - val progSpec = MockIncrProgSpec( - setOf(".foo", ".bar", "foobar", "rmfoo"), - setOf(sym0("start"), sym0("foo"), sym0("bar")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("start") - )), - rule(".foo", - headReplaced( - pconstraint("start") - ), - body( - pconstraint("foo") - )), - // Propagation rule that doesn't match the first time due to the lack of 'bar' - rule("foobar", - headKept( - pconstraint("foo"), - pconstraint("bar") - ), - body( - constraint("marker") - )), - rule("rmfoo", - headReplaced( - pconstraint("foo") - ), - body() - ) - ).launch("withoutBar", progSpec) { result -> - - // "foobar" hasn't matched without 'bar, so 'foo' is still here - result.storeView().constraintSymbols() shouldBe emptySet() - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - // Provides 'bar' by reactivating 'foo' - builder.insertRulesAt(1, - rule(".bar", - headKept( - pconstraint("foo") - ), - body( - pconstraint("bar") - )) - ).relaunch("withBar", progSpec, evalRes.token()) { result -> - - // if "foobar" happens too early, "1st" occ won't be produced - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("marker")) - result.countChunks() shouldBe (3 + nPrincipalMatches) // +[.bar, foobar, rmfoo] - } - } - } - - - @Test - fun rmSinglePropagationMatch() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "baz.qux"), - setOf(sym0("foo"), sym0("baz")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - rule("baz.qux", - headKept( - pconstraint("baz") - ), - body( - constraint("qux") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"), sym0("qux")) - result.lastChunkSymbols() shouldBe listOf(sym0("qux")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - builder.removeRules(listOf("baz.qux")) - .relaunch("removed", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz")) - result.lastChunkSymbols() shouldBe listOf(sym0("baz")) - result.countChunks() shouldBe (nPrincipalMatches - 1) - } - } - } - - @Test - fun rmSingleSimplificationMatch() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "baz.qux"), - setOf(sym0("foo"), sym0("baz")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - rule("baz.qux", - headReplaced( - pconstraint("baz") - ), - body( - constraint("qux") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("qux")) - result.lastChunkSymbols() shouldBe listOf(sym0("qux")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - builder.removeRules(listOf("baz.qux")) - .relaunch("removed", progSpec, evalRes.token()) { result -> - - // 'baz' must be restored - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz")) - result.lastChunkSymbols() shouldBe listOf(sym0("baz")) - result.countChunks() shouldBe (nPrincipalMatches - 1) - } - } - } - - @Test - fun rmWithDependencies() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.baz", "foo.bar", "barbaz.qux", "baz.lax"), - setOf(sym0("foo"), sym0("baz"), sym0("bar")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.bar", - headKept( - pconstraint("foo") - ), - body( - pconstraint("bar") - )), - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - rule("barbaz.qux", - headKept( - pconstraint("bar"), - pconstraint("baz") - ), - body( - constraint("qux") - )), - rule("baz.lax", - headReplaced( - pconstraint("baz") - ), - body( - constraint("lax") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("qux"), sym0("lax")) - result.lastChunkSymbols() shouldBe listOf(sym0("lax")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - // "barbaz.qux" rule match is removed as a dependency of "foo.bar" match - builder.removeRules(listOf("foo.bar")) - .relaunch("removed", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("lax")) - result.lastChunkSymbols() shouldBe listOf(sym0("lax")) - result.countChunks() shouldBe (nPrincipalMatches - 3) - } - } - } - - @Test - fun rmReplacedBeforeReplaced() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.baz", "foo.bar", "baz.lax"), - setOf(sym0("foo"), sym0("baz")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - // Note two discarding rules one after another. - // After removing "foo.bar" "foo.baz" must match. - rule("foo.bar", - headReplaced( - pconstraint("foo") - ), - body( - constraint("bar") - )), - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - rule("baz.lax", - headReplaced( - pconstraint("baz") - ), - body( - constraint("lax") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar")) - result.lastChunkSymbols() shouldBe listOf(sym0("bar")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - builder.removeRules(listOf("foo.bar")) - .relaunch("removed", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("lax")) - result.lastChunkSymbols() shouldBe listOf(sym0("lax")) - result.countChunks() shouldBe (nPrincipalMatches - 1 + 3) - } - } - } - - @Test - fun rmAndAddSamePropagationMatch() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "baz.qux"), - setOf(sym0("foo"), sym0("baz")) - ) - val readdedRule = - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - readdedRule, - rule("baz.qux", - headKept( - pconstraint("baz") - ), - body( - constraint("qux") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"), sym0("qux")) - result.lastChunkSymbols() shouldBe listOf(sym0("qux")) - - }.also { (builder, evalRes) -> - val oldChunksView = evalRes.chunksSymbolView() - val oldLastChunk = evalRes.lastChunk() - - builder.removeRules(listOf("foo.baz")) - .relaunch("removed", progSpec, evalRes.token()) { result -> - - // ensure something changed - result.lastChunkSymbols() shouldBe listOf(sym0("foo")) - - }.let { (builder, evalRes) -> - builder.insertRulesAt(1, readdedRule) - .relaunch("readded", progSpec, evalRes.token()) { result -> - - // ensure chunks are essentially the same, but have different identities - result.chunksSymbolView() shouldBe oldChunksView - assertNotEquals(result.lastChunk().evidence, oldLastChunk.evidence) - } - } - } - } - - @Test - fun rmAndAddDifferentMatch() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.bar", "foo.baz", "baz.qux"), - setOf(sym0("foo"), sym0("baz")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - rule("baz.qux", - headKept( - pconstraint("baz") - ), - body( - constraint("qux") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz"), sym0("qux")) - result.lastChunkSymbols() shouldBe listOf(sym0("qux")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - builder.removeRules(listOf("foo.baz")).insertRulesAt(1, - rule("foo.bar", - headReplaced( - pconstraint("foo") - ), - body( - constraint("bar") - )) - ).relaunch("substituted", progSpec, evalRes.token()) { result -> - - result.countChunks() shouldBe (nPrincipalMatches - 3 + 1) - result.lastChunkSymbols() shouldBe listOf(sym0("bar")) - } - } - } - - @Test - fun rmAndTryMatchWithRemovedOccurrences() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo.baz", "barbaz.cant", "baz.lax2"), - setOf(sym0("foo"), sym0("baz"), sym0("bar")) - ) - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo") - )), - // <<< removed - rule("foo.baz", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("baz") - )), - // >>> - - // Can't match at first run, there's no 'bar'. - // Should't match at second run, there's no 'baz' anymore. - rule("barbaz.cant", - headKept( - pconstraint("bar"), - pconstraint("baz") - ), - body( - constraint("cant") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("baz")) - result.lastChunkSymbols() shouldBe listOf(sym0("baz")) - - }.also { (builder, evalRes) -> - val nPrincipalMatches = evalRes.countChunks() - - builder.removeRules(listOf("foo.baz")).programWithRules( - // produces 'bar' - rule("foo.bar", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("bar") - )), - // 'baz' is removed, shouldn't match - rule("baz.lax2", - headKept( - pconstraint("baz") - ), - body( - constraint("lax2") - )) - ).relaunch("substituted", progSpec, evalRes.token()) { result -> - -// println(result.token().journalView.chunks) - - result.countChunks() shouldBe (nPrincipalMatches - 1) - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar")) - result.lastChunkSymbols() shouldBe listOf(sym0("bar")) - } - } - } - - @Test - fun completeNonprincipalRuleMatch() { - // Tests that state of non principal rules (i.e. their rule matchers) is preserved between sessions; - // that is, that partially matched non-principal rules can be fully matched in later sessions. - // This is a feature, not a necessity. For example, it could be mandated that - // such rules which should preserve their state can be only principal rules. - val progSpec = MockIncrProgSpec( - setOf("main", "foo", "bar"), - setOf(sym0("foo"), sym0("bar")) - ) - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("foo"), - pconstraint("bar") - )), - rule("foo", - headReplaced( - pconstraint("foo") - ), - body( - constraint("hasBound"), - constraint("eliminateBound") - )), - // matches on two hasBound, but the first program produces only a single bound - rule("eliminateBounds", - headReplaced( - constraint("eliminateBound"), - constraint("hasBound"), - constraint("hasBound") - ), - body( - constraint("expected") - )) - ).launch("singleBound", progSpec) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("eliminateBound"), sym0("hasBound")) - result.storeView().allOccurrences().count() shouldBe 3 - - }.also { (builder, evalRes) -> - builder.programWithRules( - rule("bar", - headReplaced( - pconstraint("bar") - ), - body( - constraint("hasBound") - )) - ).relaunch("addSecondBound", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("expected")) - result.storeView().allOccurrences().count() shouldBe 1 - } - } - } - - @Test - fun trackEvidenceForNonprincipalMatch() { - // Tests that justifications from principal occurrences in non-principal matches - // are not lost and tracked in the last parent match. - // This logic makes sense only if non-principal matches can match on principal occurrences. - val progSpec = MockIncrProgSpec( - setOf("main", "foo", "bar"), - setOf(sym0("main"), sym0("foo"), sym0("bar"), sym0("important")) - ) - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("bar"), - pconstraint("foo") - )), - rule("bar", - headReplaced( - pconstraint("bar") - ), - body( - pconstraint("important") - )), - rule("foo", - headReplaced( - pconstraint("foo") - ), - body( - constraint("expected"), - constraint("doInfluence") - )), - rule("influenceResult", - // note: non-principal rule which nonetheless matches on a principal constraint - headKept( - pconstraint("important") - ), - headReplaced( - constraint("doInfluence"), - constraint("expected") - ), - body( - )), - rule("influenceResult_default", - // note: non-principal rule which nonetheless matches on a principal constraint - headReplaced( - constraint("doInfluence") - ), - body( - )) - ).launch("launch", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("important")) - assertTrue(result.lastChunk().let { it is MatchJournal.MatchChunk && it.match.rule().uniqueTag().toString() == "foo" }) - - }.also { (builder, evalRes) -> - - // Premises of "foo" rule don't change (foo constraint is intact) on 2nd session, - // but premises of one of its children, "influenceResult" rule, do change. - // Rule "influenceResult" isn't principal, so it isn't tracked as a chunk in journal. - // So what can be done --- is its principal parent can be dropped, i.e. chunk of "foo" rule. - // - // Essentially, what is checked is: "important" constraint is invalidated => "foo" rule is invalidated. - - builder.removeRules( - listOf("bar") - ).relaunch("invalidate bar", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("expected")) - } - } - } - - @Test - fun trackEvidenceForNonprincipalChildMatch() { - val progSpec = MockIncrProgSpec( - setOf("main", "bar"), - setOf(sym0("main"), sym0("bar"), sym0("important"), sym0("expected")) - ) - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("bar"), - constraint("doInfluence"), - pconstraint("expected") - )), - rule("bar", - headReplaced( - pconstraint("bar") - ), - body( - pconstraint("important") - )), - rule("influenceResult", - // note: non-principal rule which nonetheless matches on a principal constraint - headKept( - pconstraint("important") - ), - headReplaced( - constraint("doInfluence") - ), - body( - )) - ).launch("launch", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("important"), sym0("expected")) - - }.also { (builder, evalRes) -> - - builder.removeRules( - listOf("bar") - ).relaunch("addSecondBound", progSpec, evalRes.token()) { result -> - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("expected")) - } - } - } - - @Test - fun trackEvidenceForNonprincipalMatchAndItsChildren() { - // Tests that justifications from principal occurrences in non-principal matches - // are not lost and tracked in the last parent match AND its dependent chunks. - // This logic makes sense only if non-principal matches can match on principal occurrences. - val progSpec = MockIncrProgSpec( - setOf("main", "foo", "fooChild", "bar"), - setOf(sym0("main"), sym0("foo"), sym0("fooChild"), sym0("bar"), sym0("important")) - ) - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("bar"), - pconstraint("foo") - )), - rule("bar", - headReplaced( - pconstraint("bar") - ), - body( - pconstraint("important") - )), - rule("foo", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("fooChild"), - constraint("expected"), - constraint("doInfluence") - )), - rule("fooChild", - headReplaced( - pconstraint("fooChild") - ), - body( - constraint("expected2") - )), - rule("influenceResult", - // note: non-principal rule which nonetheless matches on a principal constraint - headKept( - pconstraint("important") - ), - headReplaced( - constraint("doInfluence"), - constraint("expected") - ), - body( - )), - rule("influenceResult_default", - headReplaced( - constraint("doInfluence") - ), - body( - )) - ).launch("launch", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("important"), sym0("expected2")) - - }.also { (builder, evalRes) -> - - // instance of occurrence from the first run - val occInstance1 = evalRes.storeView().allOccurrences().find { it.constraint().symbol() == sym0("expected2") } - occInstance1 shouldNotBeSame null - - builder.removeRules( - listOf("bar") - ).relaunch("invalidate bar", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("bar"), sym0("expected"), sym0("expected2")) - val occInstances2 = result.storeView().allOccurrences().filter { it.constraint().symbol() == sym0("expected2") } - // instance of occurrence from the second run must be different --- - // if it's same it means that "fooChild" rule wasn't invalidated! - occInstances2.count() shouldBe 1 - occInstances2.first() shouldNotBeSame occInstance1 - } - } - } - - @Test - fun observerCantReactivateFutureOccurrence() { - val progSpec = MockIncrProgSpec( - setOf("main", "bindVar", "produceBound", "eliminateBound"), - setOf(sym1("foo")) - ) - val X = metaLogical("X") - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo", X) - )), - rule("produceBound", - headKept( - pconstraint("foo", X) - ), - body( - pconstraint("hasBound", X) - )), - rule("eliminateBound", - headReplaced( - pconstraint("hasBound", X) - ), - guard( - expression({ x -> x.isBound }, X) - ), - body( - constraint("unexpected") - ) - ) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound")) - - }.also { (builder, evalRes) -> - // Insert rule before produceBound: test whether - // occurrence reactivation due to var bind can occur - // before this occurrence is actually activated, - // which would lead, of course, to inconsistent state. - builder.insertRulesAt(1, - rule("bindVar", - headKept( - pconstraint("foo", X) - ), - body( - statement({ x -> x.set(42) }, X) - )) - ).relaunch("test reactivation", progSpec, evalRes.token()) { result -> - - // NB: this is correct if incremental processing - // takes into account rule dependencies due to logicals. - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo")) - } - } - } - - @Test - fun inference_insertLogicalUni_2() { - val progSpec = MockIncrProgSpec( - setOf("main", "produceBound"), - setOf(sym0("main"), sym1("foo")) - ) - - val (X, Y) = metaLogical("X", "Y") - - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("foo", X) - )), - rule("bindVar", - headKept( - constraint("hasBound", Y) - ), - body( - statement({ y -> y.set(42) }, Y) - )) - - ).launch("normal run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo")) - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, - rule("produceBound", - headKept( - pconstraint("foo", X) - ), - body( - statement({ x, y -> eq(x, y) }, X, Y), - constraint("hasBound", Y) - )) - - ) - .relaunch("violate contract assertion", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound")) - } - } - } - - - @Test - fun inference_rewindLogicalBind() { - val progSpec = MockIncrProgSpec( - setOf("main", "bindVar", "beforeBind"), - setOf(sym0("main"), sym1("foo")) - ) - - val X = metaLogical("X") - - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("foo", X) - )), - rule("bindVar", - headKept( - pconstraint("foo", X) - ), - body( - statement({ x -> x.set(42) }, X) - )) - - ).launch("normal run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo")) - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, - rule("beforeBind", - headKept( - pconstraint("foo", X) - ), - guard( - expression({ x -> !x.isBound }, X) - ), - body( - constraint("expected") - )) - ) - .relaunch("relaunch", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("expected"), sym1("foo")) - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - } - } - } - - /** - * Opposite to previous test [inference_insertLogicalBind] - */ - @Test - fun inference_insertLogicalBind() { - val progSpec = MockIncrProgSpec( - setOf("main", "bindVar", "checkFresh"), - setOf(sym0("main"), sym1("foo")) - ) - - val X = metaLogical("X") - - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("foo", X) - )), - rule("checkFresh", - headReplaced( - pconstraint("foo", X) - ), - guard( - expression({ x -> !x.isBound }, X) - ), - body( - constraint("unexpected") - )) - - ).launch("normal run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("unexpected")) - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, - rule("bindVar", - headKept( - pconstraint("foo", X) - ), - body( - statement({ x -> x.set(42) }, X) - )) - ) - .relaunch("relaunch", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo")) - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - } - } - } - - @Test - fun inference_rewindLogicalBind_indirectUni() { - val progSpec = MockIncrProgSpec( - setOf("main", "produceBound", "beforeProduceBound"), - setOf(sym0("main"), sym1("foo")) - ) - - val (X, Y) = metaLogical("X", "Y") - - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("foo", X) - )), - rule("produceBound", - headKept( - pconstraint("foo", X) - ), - body( - statement({ x, y -> eq(x, y) }, X, Y), - constraint("hasBound", Y) - )), - rule("bindVar", - headKept( - constraint("hasBound", Y) - ), - body( - statement({ y -> y.set(42) }, Y) - )) - - ).launch("normal run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("hasBound")) - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, - rule("beforeProduceBound", - headKept( - pconstraint("foo", X) - ), - guard( - expression({ x -> !x.isBound }, X) - ), - body( - constraint("expected") - )) - ) - .relaunch("relaunch", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("expected"), sym1("foo"), sym1("hasBound")) - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - } - } - } - - @Test - fun inference_rewindLogicalBind_onDiscarded() { - val progSpec = MockIncrProgSpec( - setOf("main", "bindVar", "beforeBind"), - setOf(sym0("main"), sym1("foo")) - ) - - val X = metaLogical("X") - - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("foo", X) - )), - rule("bindVar", - headReplaced( - pconstraint("foo", X) - ), - body( - statement({ x -> x.set(42) }, X), - constraint("expectedValue", X) - )) - - ).launch("normal run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("expectedValue")) - result.storeView().occurrences(sym1("expectedValue")).first().firstValue() shouldBe 42 - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, - rule("beforeBind", - headKept( - pconstraint("foo", X) - ), - guard( - expression({ x -> !x.isBound }, X) - ), - body( - constraint("expected") - )) - ) - .relaunch("relaunch", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("expected"), sym1("expectedValue")) - result.storeView().occurrences(sym1("expectedValue")).first().firstValue() shouldBe 42 - } - } - } - - @Test - fun inference_rewindLogicalBind_indirectMatch() { - val progSpec = MockIncrProgSpec( - setOf("main", "produceBound", "beforeProduceBound", "runFoo"), - setOf(sym0("main"), sym1("foo"), sym0("runFoo")) - ) - - val (X, Y) = metaLogical("X", "Y") - - programWithRules( - rule("main", - headKept( - pconstraint("main") - ), - body( - pconstraint("foo", X) - )), - rule("beforeProduceBound", - headReplaced( - pconstraint("runFoo") - ), - headKept( - pconstraint("foo", X) - ), - guard( - expression({ x -> !x.isBound }, X) - ), - body( - constraint("expected") - )), - rule("produceBound", - headKept( - pconstraint("foo", X) - ), - body( - statement({ x, y -> eq(x, y) }, X, Y), - constraint("hasBound", Y) - )), - rule("bindVar", - headKept( - constraint("hasBound", Y) - ), - body( - statement({ y -> y.set(42) }, Y) - )) - - ).launch("normal run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("main"), sym1("foo"), sym1("hasBound")) - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(0, - rule("runFoo", - headKept( - pconstraint("main") - ), - body( - pconstraint("runFoo") - )) - ) - .relaunch("relaunch", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("main"), sym0("expected"), sym1("foo"), sym1("hasBound")) - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - } - } - } - - @Test - fun inference_insertLogicalUni_affectFuture() { - val progSpec = MockIncrProgSpec( - setOf("main", "produceBound", "uniVars", "bindVar", "checkFresh"), - setOf(sym0("main"), sym1("foo"), sym1("bar")) - ) - - val (X, Y) = metaLogical("X", "Y") - - programWithRules( - rule("main", - headReplaced( - pconstraint("main") - ), - body( - pconstraint("bar", Y), - pconstraint("foo", X) - )), - rule("bindVar", - headKept( - pconstraint("bar", Y) - ), - body( - statement({ y -> y.set(42) }, Y) - )), - rule("checkFresh", - headKept( - pconstraint("foo", X) - ), - guard( - expression({ x -> !x.isBound }, X) - ), - body( - constraint("unexpected") - )) - - ).launch("normal run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("bar"), sym0("unexpected")) - result.storeView().occurrences(sym1("bar")).first().firstValue() shouldBe 42 - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe null - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, - rule("uniVars", - headKept( - pconstraint("foo", X), - pconstraint("bar", Y) - ), - body( - statement({ x, y -> eq(x, y) }, X, Y) - )) - ) - .relaunch("relaunch", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym1("foo"), sym1("bar")) - result.storeView().occurrences(sym1("bar")).first().firstValue() shouldBe 42 - result.storeView().occurrences(sym1("foo")).first().firstValue() shouldBe 42 - } - } - } - - - @Test - fun expectTypeGenericNonprincipal() { - val progSpec = MockIncrProgSpec( - setOf("main", "foo", "expectType_ok", "expectType_default"), - setOf(sym0("foo"), sym0("typeOf"), sym0("expectType")) - ) - - val fooRuleBuilder = - rule("foo", - headReplaced( - pconstraint("foo") - ), - body( - pconstraint("typeOf") - )) - - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("foo"), - pconstraint("expectType") - )), - - fooRuleBuilder, - - rule("expectType_ok", - headReplaced( - pconstraint("expectType") - ), - headKept( - pconstraint("typeOf") - ), - body( - constraint("hasType") - )), - rule("expectType_default", - headReplaced( - pconstraint("expectType") - ), - body( - constraint("noType") - )) - - ).launch("initial run with typeOf", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("typeOf"), sym0("hasType")) - - }.let { (builder, evalRes) -> - builder - .removeRules(listOf("foo")) - .relaunch("removed typeOf", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("foo"), sym0("noType")) - } - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, fooRuleBuilder) - .relaunch("added typeOf again", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("typeOf"), sym0("hasType")) - } - } - } - - // Substructural type system rules: - // any number of resource reads are allowed - // at most one write of resource is allowed - - // Tests that if write-match is dropped, then read-matches preceding it mustn't be affected - @Test - fun substructuralTS_dropWrite() { - val progSpec = MockIncrProgSpec( - setOf("main", "readResource", "writeResource", "process"), - setOf(sym0("resource"), sym0("read"), sym0("write"), sym0("doProcess")) - ) - - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("resource"), - pconstraint("read"), - pconstraint("read"), - pconstraint("doProcess"), - pconstraint("read") - )), - rule("readResource", - headReplaced( - pconstraint("read") - ), - headKept( - pconstraint("resource") - ), - body( - constraint("hasRead") - )), - rule("writeResource", - headReplaced( - pconstraint("write"), - pconstraint("resource") - ), - body( - constraint("hasWrite") - )), - rule("process", - headReplaced( - pconstraint("doProcess") - ), - body( - pconstraint("write") - ) - ) - ).launch("initial run with write", progSpec) { result -> - - // one 'read' is left unprocessed because of exhausted resource - result.storeView().constraintSymbols() shouldBe setOf(sym0("hasRead"), sym0("hasWrite"), sym0("read")) - result.storeView().occurrences(sym0("hasRead")).count() shouldBe 2 - - }.also { (builder, evalRes) -> - builder - .removeRules(listOf("process")) - .relaunch("removed write", progSpec, evalRes.token()) { result -> - - // without 'write' resource isn't exhausted - result.storeView().constraintSymbols() shouldBe setOf(sym0("hasRead"), sym0("resource"), sym0("doProcess")) - result.storeView().occurrences(sym0("hasRead")).count() shouldBe 3 - } - } - } - - @Test - fun substructuralTS_insertWriteBeforeRW() { - /* Expected test program execution: - - writeA produced - => writeResourceA consumes resource - => readResource & writeResourceB can't match - - writeA retracted - => resource appears again (internally 'resource' constraint is reexecuted) - => readResource & writeResourceB match again - */ - - val progSpec = MockIncrProgSpec( - setOf("main", "readResource", "writeResourceA", "writeResourceB", "doWriteA"), - setOf(sym0("resource"), sym0("read"), sym0("writeA"), sym0("writeB"), sym0("doProcess")) - ) - - val doProcessRuleBuilder = - rule("doWriteA", - headReplaced( - pconstraint("doProcess") - ), - body( - pconstraint("writeA") - )) - - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("resource"), - pconstraint("doProcess"), - pconstraint("read"), - pconstraint("writeB") - )), - rule("writeResourceA", - headReplaced( - pconstraint("writeA"), - pconstraint("resource") - ), - body( - constraint("hasWriteA") - )), - rule("readResource", - headReplaced( - pconstraint("read") - ), - headKept( - pconstraint("resource") - ), - body( - constraint("hasRead") - )), - rule("writeResourceB", - headReplaced( - pconstraint("writeB"), - pconstraint("resource") - ), - body( - constraint("hasWriteB") - )) - ).launch("initial run without writeA", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("hasRead"), sym0("hasWriteB"), sym0("doProcess")) - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(0, doProcessRuleBuilder) - .relaunch("inserted writeA", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("hasWriteA"), sym0("read"), sym0("writeB")) - } - }.let { (builder, evalRes) -> - builder - .removeRules(listOf("doWriteA")) - .relaunch("removed writeA", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("hasRead"), sym0("hasWriteB"), sym0("doProcess")) - } - } - } - - - @Test -// @Ignore("waiting for complete fix for MPSCR-65") - fun substructuralTS_indirectResourceDependency() { - /* Expected test program execution: - Write of resource2 depends on write of resource1 - so, when write1 can't happen, so write2 won't happen. - - C1 consumes res1 before W1 - => W1 match invalidated - => write2 isn't produced - => W2 match invalidated - => res1 remains intact (internally 'res1' constraint is reexecuted) - => R2 can match and matches - */ - - val progSpec = MockIncrProgSpec( - setOf("main", "R2", "W1", "W2", "Consume1"), - setOf( - sym0("resource1"), sym0("write1"), - sym0("resource2"), sym0("read2"), sym0("write2") - ) - ) - - val doConsumeRuleBuilder = - rule("Consume1", - headReplaced( - pconstraint("write1"), - pconstraint("resource1") - ), - body() - ) - - programWithRules( - rule("main", - headReplaced( - constraint("main") - ), - body( - pconstraint("resource1"), - pconstraint("resource2"), - pconstraint("write1"), - pconstraint("read2") - )), - rule("W1", - headReplaced( - pconstraint("write1"), - pconstraint("resource1") - ), - body( - pconstraint("write2") - )), - rule("W2", - headReplaced( - pconstraint("write2"), - pconstraint("resource2") - ), - body( - constraint("hasWrite2") - )), - rule("R2", - headReplaced( - pconstraint("read2") - ), - headKept( - pconstraint("resource2") - ), - body( - constraint("hasRead2") - )) - ).launch("initial run", progSpec) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("hasWrite2"), sym0("read2")) - - }.let { (builder, evalRes) -> - builder - .insertRulesAt(1, doConsumeRuleBuilder) - .relaunch("consume resource1 before anyone", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("resource2"), sym0("hasRead2")) - } - }.let { (builder, evalRes) -> - builder - .removeRules(listOf("Consume1")) - .relaunch("as initial run", progSpec, evalRes.token()) { result -> - - result.storeView().constraintSymbols() shouldBe setOf(sym0("hasWrite2"), sym0("read2")) - } - } - } - -}