Experimental feature: Rules segmentation.

The rules index is extended to observe the segment path a rule belongs to.
An occurrence is bound to the rule that activated it and thus has an
associated segment path.
Rules to be picked for activation can be selected on the basis
of matched segment path.
This commit is contained in:
Fedor Isakov 2019-09-23 16:09:45 +02:00
parent 6b2a14511f
commit 7d308cb0e3
10 changed files with 411 additions and 79 deletions

View File

@ -65,7 +65,8 @@ class Occurrence (controller: Controller,
val constraint: Constraint,
val logicalContext: LogicalContext,
val arguments: List<*>,
val justifications: Justs):
val justifications: Justs,
val ruleUniqueTag: Any? = null):
ConstraintOccurrence
{
@ -85,6 +86,8 @@ class Occurrence (controller: Controller,
override fun logicalContext(): LogicalContext = logicalContext
override fun ruleUniqueTag(): Any? = ruleUniqueTag
override fun justifications(): Justs = justifications
fun terminate(controller: Controller) {
@ -114,18 +117,7 @@ class Occurrence (controller: Controller,
fun Constraint.occurrence(controller: Controller,
arguments: List<*>,
justifications: Justs,
logicalContext: LogicalContext): Occurrence =
Occurrence(controller, this, logicalContext, arguments, justifications)
logicalContext: LogicalContext,
ruleUniqueTag: Any? = null): Occurrence =
Occurrence(controller, this, logicalContext, arguments, justifications, ruleUniqueTag)
fun Constraint.occurrence(controller: Controller,
arguments: List<*>,
justifications: Justs): Occurrence =
Occurrence(controller, this, noLogicalContext, arguments, justifications)
fun Constraint.occurrence(controller: Controller,
arguments: List<*>): Occurrence =
Occurrence(controller, this, noLogicalContext, arguments, justsOf())
private val noLogicalContext: LogicalContext = object: LogicalContext {
override fun <V : Any> variable(metaLogical: MetaLogical<V>): Logical<V>? = null
}

View File

@ -31,12 +31,20 @@ import kotlin.collections.HashMap
*
* @author Fedor Isakov
*/
class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
class RuleIndex(ruleLists: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
// Terminology:
// ruleBit - rule's index in the rules list
// headPos - constraint's position in the rule's head (all slots together)
// argIdx - constraint's argument index
private val symbol2index = HashMap<ConstraintSymbol, ArgumentRuleIndex>()
private val tag2rule = LinkedHashMap<Any, Rule>()
// TODO replace with a trie?
private val segmentPath2ruleBits = HashMap<List<Any>, BitSet>()
// rule's index is rule's position in this list
private val rulesList = ArrayList<Rule>()
@ -45,24 +53,40 @@ class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
private val slotMasksList = ArrayList<SlotMask>()
/** the bit set that is going to be reused for all calls to [select] */
private val ruleIndices = BitSet(rulesList.size)
private val ruleBits = BitSet(rulesList.size)
/** the bit set to be used for temporary processing within [select]*/
private val andRuleIndices = BitSet(rulesList.size)
init {
buildIndex(handlers)
buildIndex(ruleLists)
}
override fun lookupRuleByTag(tag: Any): Rule? = tag2rule[tag]
private fun List<Any>.isPrefixOf(that: List<Any>): Boolean {
val thisIt = this@isPrefixOf.iterator()
val thatIt = that.iterator()
while(thisIt.hasNext() && thatIt.hasNext()) {
if (thisIt.next() != thatIt.next()) return false
}
return thisIt.hasNext() == thatIt.hasNext() || thatIt.hasNext()
}
/**
* Returns instances of [Rule] that can potentially match the specified [ConstraintOccurrence].
*/
fun forOccurrence(occ: ConstraintOccurrence): Iterable<Rule> {
val ruleIndices = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList()
val ruleBits = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList()
tag2rule[occ.ruleUniqueTag()]?.segmentPath()?.let {
andRuleIndices.clear()
for ((path, bits) in segmentPath2ruleBits.entries) {
if (!it.isPrefixOf(path)) andRuleIndices.or(bits)
}
ruleBits.andNot(andRuleIndices)
}
val result = ArrayList<Rule>()
val it = ruleIndices.allSetBits()
val it = ruleBits.allSetBits()
while (it.hasNext()) result.add(rulesList[it.next()])
return result
}
@ -72,6 +96,13 @@ class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
*/
fun forOccurrenceWithMask(occ: ConstraintOccurrence): Iterable<Pair<Rule, BitSet>> {
val ruleBits = symbol2index[occ.constraint().symbol()]?.select(occ) ?: return emptyList()
tag2rule[occ.ruleUniqueTag()]?.segmentPath()?.let {
andRuleIndices.clear()
for ((path, bits) in segmentPath2ruleBits.entries) {
if (!it.isPrefixOf(path)) andRuleIndices.or(bits)
}
ruleBits.andNot(andRuleIndices)
}
val result = ArrayList<Pair<Rule, BitSet>>()
val it = ruleBits.allSetBits()
while (it.hasNext()) {
@ -83,19 +114,24 @@ class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
override fun iterator(): Iterator<Rule> = rulesList.iterator()
private fun buildIndex(handlers: Iterable<RulesList>) {
private fun buildIndex(ruleLists: Iterable<RulesList>) {
var ruleBit = 0
for (h in handlers) {
for (h in ruleLists) {
for (rule in h.rules()) {
if (tag2rule.containsKey(rule.uniqueTag())) throw IllegalStateException("duplicate rule tag ${rule.uniqueTag()}")
tag2rule[rule.uniqueTag()] = rule
rule.segmentPath()?.let { segmentPath ->
if (segmentPath.isNotEmpty()) {
segmentPath2ruleBits.getOrPut(segmentPath) { BitSet() }.set(ruleBit)
}
}
rulesList.add(rule)
val head = rule.headKept() + rule.headReplaced()
val slotMask = SlotMask()
for ((pos, cst) in head.withIndex()) {
for ((headPos, cst) in head.withIndex()) {
symbol2index.getOrPut(cst.symbol()) { ArgumentRuleIndex(cst.symbol()) }.update(cst, ruleBit)
slotMask.update(cst, pos)
slotMask.update(cst, headPos)
}
slotMasksList.add(slotMask)
@ -126,9 +162,9 @@ class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
}
/**
* Represents a list of rules with at least one constraint matching the 'symbol'.
* The list is indexed by all values extracted from the constraint arguments.
* The list can be selected based on the argument(s) of a constraint occurrence.
* Represents an index over rules with at least one constraint matching the 'symbol'.
* The index is over all values extracted from the constraint's arguments.
* The index is selectable by a constraint occurrence.
*/
inner class ArgumentRuleIndex(val symbol: ConstraintSymbol) {
@ -138,6 +174,8 @@ class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
val wildcardSelectors = ArrayList<BitSet>()
val noArgSelector = BitSet()
init {
for (idx in 1..symbol.arity()) {
anySelectors.add(HashMap())
@ -147,19 +185,24 @@ class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
}
fun update(cst: Constraint, ruleBit: Int) {
for ((idx, arg) in cst.arguments().withIndex()) {
val value2indices = anySelectors[idx]
when (arg) {
is MetaLogical<*> ->
// all values should be accepted by a meta logical
wildcardSelectors[idx].set(ruleBit)
is Term ->
termSelectors.set(idx, termSelectors[idx].put(arg, ruleBit))
is Any ->
value2indices.getOrPut(arg) { BitSet() }.apply { set(ruleBit) }
else ->
throw NullPointerException() // never happens
if (cst.arguments().isEmpty()) {
noArgSelector.set(ruleBit)
} else {
for ((argIdx, arg) in cst.arguments().withIndex()) {
val value2indices = anySelectors[argIdx]
when (arg) {
is MetaLogical<*> ->
// all values should be accepted by a meta logical
wildcardSelectors[argIdx].set(ruleBit)
is Term ->
termSelectors.set(argIdx, termSelectors[argIdx].put(arg, ruleBit))
is Any ->
value2indices.getOrPut(arg) { BitSet() }.apply { set(ruleBit) }
else ->
throw NullPointerException() // never happens
}
}
}
}
@ -172,34 +215,39 @@ class RuleIndex(handlers: Iterable<RulesList>) : Iterable<Rule>, RuleLookup {
// initially select all rules
val upToBit = rulesList.size
ruleIndices.set(0, upToBit)
ruleBits.set(0, upToBit)
for ((idx, arg) in occ.arguments().withIndex()) {
val value2indices = anySelectors[idx]
val termIndices = termSelectors[idx]
val wildcardIndices = wildcardSelectors[idx]
if (arg is Logical<*> && !arg.isBound) {
// ALL values must be selected for a free logical
continue
if (occ.arguments().isEmpty()) {
ruleBits.and(noArgSelector)
} else {
for ((argIdx, arg) in occ.arguments().withIndex()) {
val value2indices = anySelectors[argIdx]
val termIndices = termSelectors[argIdx]
val wildcardIndices = wildcardSelectors[argIdx]
if (arg is Logical<*> && !arg.isBound) {
// ALL values must be selected for a free logical
continue
}
andRuleIndices.clear(0, upToBit)
andRuleIndices.or(wildcardIndices)
val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg
when (argVal) {
is Term ->
termIndices.lookupValues(argVal).forEach { andRuleIndices.set(it) }
is Any ->
// ensure only rules with either matching values or wildcard arguments get selected
if (value2indices.containsKey(argVal)) andRuleIndices.or(value2indices[argVal])
}
ruleBits.and(andRuleIndices)
if (ruleBits.isEmpty) break
}
andRuleIndices.clear(0, upToBit)
andRuleIndices.or(wildcardIndices)
val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg
when (argVal) {
is Term ->
termIndices.lookupValues(argVal).forEach { andRuleIndices.set(it) }
is Any ->
// ensure only rules with either matching values or wildcard arguments get selected
if (value2indices.containsKey(argVal)) andRuleIndices.or(value2indices[argVal])
}
ruleIndices.and(andRuleIndices)
if (ruleIndices.isEmpty) break
}
return ruleIndices
return ruleBits
}
}
}

View File

@ -42,7 +42,9 @@ internal class ControllerImpl (
/** For tests only */
override fun evaluate(occ: Occurrence): StoreView {
// create the internal occurrence
val active = occ.constraint().occurrence(this, occ.arguments(), occ.justifications())
val active = occ.constraint().occurrence(this, occ.arguments(), occ.justifications(), object: LogicalContext {
override fun <V : Any> variable(metaLogical: MetaLogical<V>): Logical<V>? = null
})
val status = state.processActivated(this, active, NORMAL())
if (status is FAILED) {
@ -65,7 +67,7 @@ internal class ControllerImpl (
fun activate(constraint: Constraint) : FeedbackStatus {
// FIXME noLogicalContext
val context = Context(NORMAL(), noLogicalContext, trace)
val context = Context(NORMAL(), noLogicalContext, null, trace)
// fixme: is it valid to always provide current justifications?
// while this method is used only in one place at program kick-off, yes, it's initial justs provided.
@ -99,7 +101,7 @@ internal class ControllerImpl (
.then { processGuard(match, it) }
private fun checkMatchPreconditions(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus {
val context = Context(inStatus, match.logicalContext(), trace)
val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace)
// invoke matched pattern predicates
for (prd in match.patternPredicates()) {
@ -111,7 +113,7 @@ internal class ControllerImpl (
}
private fun processGuard(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus {
val context = Context(inStatus, match.logicalContext(), trace)
val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace)
// check guard
for (gprd in match.rule().guard()) {
@ -122,7 +124,7 @@ internal class ControllerImpl (
}
override fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus {
val context = Context(inStatus, match.logicalContext(), trace)
val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace)
val altIt = match.rule().bodyAlternation().iterator()
while (altIt.hasNext()) {
@ -206,7 +208,7 @@ internal class ControllerImpl (
val args = supervisor.instantiateArguments(constraint.arguments(), context.logicalContext, context)
return context.eval { status ->
state.processActivated(this, constraint.occurrence(this, args, justsCopy(justs), context.logicalContext), status)
state.processActivated(this, constraint.occurrence(this, args, justsCopy(justs), context.logicalContext, context.ruleUniqueTag), status)
}
}
@ -249,8 +251,9 @@ internal class ControllerImpl (
inner private class Context(inStatus: FeedbackStatus,
val logicalContext: LogicalContext,
val trace: EvaluationTrace = EvaluationTrace.NULL) : InvocationContext
val logicalContext: LogicalContext,
val ruleUniqueTag: Any? = null,
val trace: EvaluationTrace = EvaluationTrace.NULL) : InvocationContext
{
private var status = inStatus

View File

@ -38,7 +38,12 @@ public interface ConstraintOccurrence {
LogicalContext logicalContext();
default Object ruleUniqueTag() {
return null;
}
default
@NotNull
default TIntSet justifications() { return new TIntHashSet(); }
TIntSet justifications() { return new TIntHashSet(); }
}

View File

@ -17,6 +17,9 @@
package jetbrains.mps.logic.reactor.program;
import java.util.Collections;
import java.util.List;
/**
* A constraint rule description.
*
@ -26,6 +29,17 @@ public abstract class Rule {
public abstract Rule.Kind kind();
/**
* A list of objects identifying the segment this rule belongs to. An empty segment path signifies the root segment.
* An occurrence produced from the root segment can be processed by any rule in the program.
* An occurrence produced from segment identified by a path P can be processed by a rule from any segment that
* has P as the path prefix.
*/
// TODO Make abstract
public List<Object> segmentPath() {
return Collections.emptyList();
}
/**
* A tag uniquely identifies the rule.
*/

View File

@ -41,7 +41,7 @@ class HandlerBuilder(val name: String) {
fun toHandler(): RulesList = MockHandler(name, rules.values.toList())
}
class RuleBuilder(val tag: String) {
class RuleBuilder(val tag: String, val segmentPath: List<Any>) {
val kept = ArrayList<Constraint>()
val replaced = ArrayList<Constraint>()
val guard = ArrayList<Predicate>()
@ -60,7 +60,7 @@ class RuleBuilder(val tag: String) {
if (alt || body.isEmpty()) body.add(ArrayList<AndItem>())
body.last().addAll(andItem)
}
fun toRule(): Rule = MockRule(tag, kept, replaced, guard, body)
fun toRule(): Rule = MockRule(tag, segmentPath, kept, replaced, guard, body)
}
class MockHandler(
@ -74,13 +74,16 @@ class MockHandler(
class MockRule(
val tag: String,
val segmentPath: List<Any>,
val kept: Collection<Constraint>,
val replaced: Collection<Constraint>,
val guard: Collection<Predicate>,
val body: Collection<Collection<AndItem>>) : Rule() {
override fun kind(): Kind = TODO()
override fun segmentPath(): List<Any> = segmentPath
override fun uniqueTag() = tag
override fun tag(): String = tag

View File

@ -3,6 +3,8 @@ import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus
import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation
import jetbrains.mps.logic.reactor.evaluation.StoreView
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.logical.MetaLogical
import jetbrains.mps.logic.reactor.program.*
import program.MockConstraint
import solver.TestEqPredicate
@ -122,7 +124,15 @@ fun Builder.insertRulesWhen(at: (Rule) -> Boolean, vararg ruleBuilders: () -> Ru
updateBuilder(this, arrayOf(insertRulesInHandlerWhen(at, "test", rulesLists.first(), * ruleBuilders)))
fun rule(tag: String, vararg component: RuleBuilder.() -> Unit): () -> Rule = {
val rb = RuleBuilder(tag)
val rb = RuleBuilder(tag, emptyList())
for (cmp in component) {
rb.cmp()
}
rb.toRule()
}
fun rule(tag: String, segmentPath: List<Any>, vararg component: RuleBuilder.() -> Unit): () -> Rule = {
val rb = RuleBuilder(tag, segmentPath)
for (cmp in component) {
rb.cmp()
}
@ -163,11 +173,19 @@ fun equals(left: Any, right: Any): ConjBuilder.() -> Unit = {
}
fun occurrence(id: String, vararg args: Any): Occurrence =
MockConstraint(ConstraintSymbol.symbol(id, args.size)).occurrence(MockController(), listOf(* args))
MockConstraint(ConstraintSymbol.symbol(id, args.size))
.occurrence(MockController(), listOf(* args), justsOf(), noLogicalContext)
fun taggedOccurrence(ruleUniqueTag: Any, id: String, vararg args: Any): Occurrence =
MockConstraint(ConstraintSymbol.symbol(id, args.size))
.occurrence(MockController(), listOf(* args), justsOf(), noLogicalContext, ruleUniqueTag)
fun justifiedOccurrence(id: String, justs: Justs, vararg args: Any): Occurrence =
MockConstraint(ConstraintSymbol.symbol(id, args.size), true).occurrence(MockController(), listOf(* args), justs)
fun justifiedOccurrence(id: String, justs: Collection<Int>, vararg args: Any): Occurrence = justifiedOccurrence(id, justsFromCollection(justs), * args)
MockConstraint(ConstraintSymbol.symbol(id, args.size), true)
.occurrence(MockController(), listOf(* args), justs, noLogicalContext)
fun justifiedOccurrence(id: String, justs: Collection<Int>, vararg args: Any): Occurrence =
justifiedOccurrence(id, justsFromCollection(justs), * args)
fun sym0(id: String): ConstraintSymbol =
ConstraintSymbol(id, 0)
@ -178,6 +196,10 @@ fun sym1(id: String): ConstraintSymbol =
fun sym2(id: String): ConstraintSymbol =
ConstraintSymbol(id, 2)
private val noLogicalContext = object : LogicalContext {
override fun <V : Any> variable(metaLogical: MetaLogical<V>): Logical<V>? = null
}
class MockController : Controller {
override fun ask(invocation: PredicateInvocation): Boolean {

View File

@ -67,6 +67,53 @@ class TestProgram {
}
}
@Test
fun segmented() {
programWithRules(
rule("main.foo",
headReplaced(
constraint("main")
),
body(
constraint("foo")
)),
rule("foo.bar", listOf("segment1"),
headKept(
constraint("foo")
),
body(
constraint("bar")
)
),
rule("bar.qux", listOf("segment1"),
headKept(
constraint("bar")
),
body(
constraint("qux")
)
),
rule("bar.dux", listOf("segment2"),
headKept(
constraint("bar")
),
body(
constraint("dux")
)
),
rule("bar.doh",
headKept(
constraint("bar")
),
body(
constraint("doh")
)
)
).session("segmented").run {
constraintSymbols().map { it.id() }.toSet() shouldBe setOf("foo", "bar", "qux", "doh")
}
}
@Test
fun logicalValue() {
val (X, Y, Z) = metaLogical<Int>("X", "Y", "Z")

View File

@ -0,0 +1,122 @@
/*
* 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.core.RuleIndex
import jetbrains.mps.logic.reactor.core.internal.logical
import jetbrains.mps.logic.reactor.util.bitSet
import jetbrains.mps.unification.Term
import jetbrains.mps.unification.test.MockTerm
import jetbrains.mps.unification.test.MockTerm.*
import jetbrains.mps.unification.test.MockTermsParser
import jetbrains.mps.unification.test.MockTermsParser.*
import org.jetbrains.kotlin.js.parser.parse
import org.junit.Test
import java.util.*
/*
* 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
*/
class TestRuleIndex {
@Test
fun testProgramWithSegments() {
with(programWithRules(
rule("rule0", emptyList(),
headReplaced(
constraint("main")
),
body(
constraint("bar")
)),
rule("rule1", listOf("segment1"),
headReplaced(
constraint("foo", parseTerm("f{g h}"))
),
body(
constraint("qux")
)),
rule("rule2", listOf("segment2"),
headReplaced(
constraint("foo", parseTerm("f{g h}"))
),
body(
constraint("blah")
)),
rule("rule3", listOf("segment1"),
headReplaced(
constraint("blin")
),
body(
constraint("foo", parseTerm("f{g h}"))
)),
rule("rule4", listOf("segment2"),
headReplaced(
constraint("fooblin")
),
body(
constraint("foo", parseTerm("f{g h}"))
))
))
{
val ruleIndex = RuleIndex(rulesLists)
// no segment
with (ruleIndex.forOccurrence(occurrence("main"))) {
map { it.tag() }.toSet() shouldBe setOf("rule0")
}
with (ruleIndex.forOccurrence(occurrence("foo", parseTerm("f{g h}")))) {
map { it.tag() }.toSet() shouldBe setOf("rule1", "rule2")
}
// segment1
with (ruleIndex.forOccurrence(taggedOccurrence("rule0", "foo", parseTerm("f{g h}")))) {
map { it.tag() }.toSet() shouldBe setOf("rule1", "rule2")
}
with (ruleIndex.forOccurrence(taggedOccurrence("rule0", "main"))) {
map { it.tag() }.toSet() shouldBe setOf("rule0")
}
with (ruleIndex.forOccurrence(taggedOccurrence("rule3", "foo", parseTerm("f{g h}")))) {
map { it.tag() }.toSet() shouldBe setOf("rule1")
}
with (ruleIndex.forOccurrence(taggedOccurrence("rule3", "main"))) {
map { it.tag() }.toSet() shouldBe setOf("rule0")
}
// segment2
with (ruleIndex.forOccurrence(taggedOccurrence("rule4", "foo", parseTerm("f{g h}")))) {
map { it.tag() }.toSet() shouldBe setOf("rule2")
}
}
}
}

View File

@ -676,6 +676,82 @@ class TestRuleMatcher {
}
}
@Test
fun testDispatcherWithSegment1() {
with(programWithRules(
rule("rule0", emptyList(),
headReplaced(
constraint("foo")
),
body(
constraint("bar")
)),
rule("rule1", listOf("segment1"),
headReplaced(
constraint("fooblin")
),
body(
constraint("doh")
))
))
{
with(Dispatcher(RuleIndex(rulesLists)).front()) {
expand(occurrence("foo")) }.apply {
matches().count() shouldBe 1 }.run {
}
with(Dispatcher(RuleIndex(rulesLists)).front()) {
expand(taggedOccurrence("rule1", "foo")) }.apply {
matches().count() shouldBe 1 }.run {
}
}
}
@Test
fun testExpandWithSegment2() {
with(programWithRules(
rule("rule1", listOf("segment1"),
headReplaced(
constraint("foo")
),
body(
constraint("bar")
)),
rule("rule2", listOf("segment1"),
headReplaced(
constraint("fooblin")
),
body(
constraint("doh")
)),
rule("rule3", listOf("segment2"),
headReplaced(
constraint("fooblin")
),
body(
constraint("doh")
))
))
{
with(Dispatcher(RuleIndex(rulesLists)).front()) {
expand(occurrence("foo")) }.apply {
matches().count() shouldBe 1 }.run {
}
with(Dispatcher(RuleIndex(rulesLists)).front()) {
expand(taggedOccurrence("rule2", "foo")) }.apply {
matches().count() shouldBe 1 }.run {
}
with(Dispatcher(RuleIndex(rulesLists)).front()) {
expand(taggedOccurrence("rule3", "foo")) }.apply {
matches().count() shouldBe 0 }.run {
}
}
}
@Test
fun testDispatcherIncremental() {
with(programWithRules(