From a7708f39744202b83c0c0513f120834cdfcb24d7 Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Wed, 9 Dec 2015 00:26:37 +0100 Subject: [PATCH] Adapting to the latest reactor API. RuleHandler and tests (no predicates yet). --- .../reactor/core/ReactorEvaluationSession.kt | 35 ++- .../reactor/core/ReactorPlanningSession.kt | 4 +- .../mps/logic/reactor/core/RuleHandler.kt | 137 +++++++++++ reactor/Test/test/Rules.kt | 53 ++++- reactor/Test/test/TestBasicProgram.kt | 61 +++++ reactor/Test/test/TestPlanningSession.kt | 7 +- reactor/Test/test/TestRuleHandler.kt | 219 ++++++++++++++++++ 7 files changed, 495 insertions(+), 21 deletions(-) create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleHandler.kt create mode 100644 reactor/Test/test/TestBasicProgram.kt create mode 100644 reactor/Test/test/TestRuleHandler.kt diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorEvaluationSession.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorEvaluationSession.kt index 4611e0d5..1f4b2720 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorEvaluationSession.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorEvaluationSession.kt @@ -1,8 +1,12 @@ package jetbrains.mps.logic.reactor.core +import com.github.andrewoma.dexx.collection.ConsList +import com.github.andrewoma.dexx.collection.List as PList +import com.github.andrewoma.dexx.collection.LinkedList as PLinkedList import jetbrains.mps.logic.reactor.constraint.* -import jetbrains.mps.logic.reactor.predicate.ReactorSessionSolver import jetbrains.mps.logic.reactor.program.EvaluationSession +import jetbrains.mps.logic.reactor.program.PlanningSession +import jetbrains.mps.logic.reactor.rule.Rule import java.util.* /** @@ -10,13 +14,13 @@ import java.util.* */ -private class ReactorEvaluationSession(val sessionSolver: SessionSolver) : EvaluationSession() { +class ReactorEvaluationSession(val program: PlanningSession) : EvaluationSession() { - class Config : EvaluationSession.Config() { + class Config(val program: PlanningSession) : EvaluationSession.Config() { - val myPredicateSymbols = ArrayList() + val myPredicateSymbols = ArrayList() val myParameters = HashMap() - var myComputingTracer : ComputingTracer? = null + var myComputingTracer: ComputingTracer? = null override fun withPredicates(vararg predicateSymbols: PredicateSymbol): EvaluationSession.Config { myPredicateSymbols.addAll(Arrays.asList(* predicateSymbols)) @@ -38,19 +42,28 @@ private class ReactorEvaluationSession(val sessionSolver: SessionSolver) : Evalu if (session != null) throw IllegalStateException("session already active") val predicateSymbols = myPredicateSymbols.toArray(arrayOfNulls(myPredicateSymbols.size)) - val sessionSolver = ReactorSessionSolver() + val sessionSolver = program.sessionSolver() sessionSolver.init(myComputingTracer, * predicateSymbols) - session = ReactorEvaluationSession(sessionSolver) + session = ReactorEvaluationSession(program) ourBackend.ourSession.set(session) + + session.launch() + return session } } - override fun sessionSolver(): SessionSolver? { - throw UnsupportedOperationException() + fun launch() { + + + } + + + override fun sessionSolver(): SessionSolver = program.sessionSolver() + override fun constraintSymbols(): MutableIterable? { throw UnsupportedOperationException() } @@ -69,7 +82,7 @@ private class ReactorEvaluationSession(val sessionSolver: SessionSolver) : Evalu override fun current(): EvaluationSession = ourSession.get() ?: throw IllegalStateException("no session") - override fun createConfig(): EvaluationSession.Config = Config() + override fun createConfig(program: PlanningSession): EvaluationSession.Config = Config(program) } companion object { @@ -79,7 +92,7 @@ private class ReactorEvaluationSession(val sessionSolver: SessionSolver) : Evalu setBackend(ourBackend) } - fun deinit () { + fun deinit() { clearBackend(ourBackend) } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorPlanningSession.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorPlanningSession.kt index 9b676497..1ce2525d 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorPlanningSession.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorPlanningSession.kt @@ -25,6 +25,8 @@ class ReactorPlanningSession(val name: String, val sessionSolver: SessionSolver) override fun name(): String = name + override fun sessionSolver(): SessionSolver= sessionSolver + override fun constraintSymbols(): Iterable = myRegistry.constraintSymbols() @@ -114,7 +116,7 @@ private class ConstraintRegistry(val sessionSolver: SessionSolver) { unmodifiableSet(myConstraintArgTypes.keys) fun constraintArgTypes(symbol: ConstraintSymbol): List> = - unmodifiableList(myConstraintArgTypes.getOrDefault(symbol, emptyList())) + unmodifiableList(myConstraintArgTypes.getOrDefault(symbol, Collections.emptyList())) fun predicateSymbols(): Iterable = unmodifiableSet(myPredicateSolvers.keys) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleHandler.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleHandler.kt new file mode 100644 index 00000000..9e02fcf3 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleHandler.kt @@ -0,0 +1,137 @@ +package jetbrains.mps.logic.reactor.core + +import com.github.andrewoma.dexx.collection.ConsList +import jetbrains.mps.logic.reactor.constraint.* +import jetbrains.mps.logic.reactor.rule.Rule +import java.util.* + +/** + * @author Fedor Isakov + */ + +class RuleHandler { + + private val occurrenceFactory: (Constraint) -> ConstraintOccurrence + private val rules : MutableList = ArrayList() + private val stored : MutableList = ArrayList() + + constructor( + programRules: Iterable, + occurrenceFactory: (Constraint) -> ConstraintOccurrence, + // for testing purposes only + occurrences: Iterable? = null) + { + this.occurrenceFactory = occurrenceFactory + this.rules.addAll(programRules) + if (occurrences != null) { + this.stored.addAll(occurrences) + } + } + + fun occurrences(): Set = stored.toSet() + + fun process(active: ConstraintOccurrence): Boolean { + stored.add(active) + val match = lookupMatches(active).find { pm -> pm.isGuardSatisfied() } + + if (match != null) { + for ((cst, occ) in match.discarded) { + discard(occ) + } + for (item in match.rule.body()) { + activate(item) + } + + return false + } + else return true + } + + private fun discard(occ: ConstraintOccurrence) { + stored.remove(occ) + } + + private fun activate(item: AndItem): Boolean { + return when(item) { + is Constraint -> process(activate(item)) + is Predicate -> true + else -> throw IllegalArgumentException("unknown item ${item}") + } + } + + private fun activate(constraint: Constraint): ConstraintOccurrence = occurrenceFactory(constraint) + + fun lookupMatches(occ: ConstraintOccurrence): Iterable { + val partialMatches = rules.flatMap { r -> + val matchedKept = r.headKept().filter { cst -> cst.matches(occ) } + val matchedDiscarded = r.headReplaced().filter { cst -> cst.matches(occ) } + + matchedKept.map { cst -> PartialMatch(r).keep(cst, occ) } + + matchedDiscarded.map { cst -> PartialMatch(r).discard(cst, occ) } + } + return partialMatches.flatMap { pm -> completeMatch(pm) } + } + + private fun completeMatch(match: PartialMatch) : Iterable { + if (!match.isPartial()) return listOf(match) + + val keptToSatisfy = match.rule.headKept().filter { cst -> !match.kept.any { p -> cst === p.first } } + val matchesFromKept = keptToSatisfy.flatMap { cst -> + findOccurrences(cst, { occ -> !match.hasOccurrence(occ) }).flatMap { occ -> completeMatch(match.keep(cst, occ)) } + } + + val discardedToSatisfy = match.rule.headReplaced().filter { cst -> !match.discarded.any { p -> cst === p.first } } + val matchesFromDiscarded = discardedToSatisfy.flatMap { cst -> + findOccurrences(cst, { occ -> !match.hasOccurrence(occ) }).flatMap { occ -> completeMatch(match.discard(cst, occ)) } + } + + return matchesFromKept + matchesFromDiscarded + } + + private fun findOccurrences(constraint: Constraint, predicate: (ConstraintOccurrence) -> Boolean) : Iterable = + stored.filter { co -> constraint.matches(co) && predicate(co) } + + class PartialMatch(val rule: Rule) { + + var kept = ConsList.empty>() + private set + var discarded = ConsList.empty>() + private set + + private constructor( + original : PartialMatch, + keep: Pair?, + discard: Pair?) : this(original.rule) + { + kept = if (keep != null) original.kept.append(keep) else original.kept + discarded = if (discard != null) original.discarded.append(discard) else original.discarded + } + + fun clone() : PartialMatch = PartialMatch(this, null, null) + + fun keep (constraint: Constraint, occ: ConstraintOccurrence) = PartialMatch(this, Pair(constraint, occ), null) + + fun discard (constraint: Constraint, occ: ConstraintOccurrence) = PartialMatch(this, null, Pair(constraint, occ)) + + fun hasOccurrence(occ: ConstraintOccurrence): Boolean { + return kept.any { p -> p.second === occ} || discarded.any { p -> p.second === occ } + } + + fun isPartial() : Boolean = + rule.headKept().any { cst -> !kept.any { p -> p.first === cst } } || + rule.headReplaced().any { cst -> !discarded.any { p -> p.first === cst } } + + fun isGuardSatisfied() : Boolean = true + } + +} + +/** + * True iff the constraint matches the occurrence. + */ +fun Constraint.matches(that: ConstraintOccurrence): Boolean { + if (symbol() != that.constraint().symbol()) + return false + else + return arguments() == that.arguments() +} diff --git a/reactor/Test/test/Rules.kt b/reactor/Test/test/Rules.kt index b588d954..75963bfe 100644 --- a/reactor/Test/test/Rules.kt +++ b/reactor/Test/test/Rules.kt @@ -20,7 +20,7 @@ fun headKept(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { appendHeadKept( * buildConjunction(Constraint::class.java, content).toArray()) } fun headReplaced(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { - appendHeadKept( * buildConjunction(Constraint::class.java, content).toArray()) + appendHeadReplaced( * buildConjunction(Constraint::class.java, content).toArray()) } fun guard(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { appendGuard( * buildConjunction(AndItem::class.java, content).toArray()) @@ -30,19 +30,32 @@ fun body(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { } fun constraint(id: String): ConjBuilder.() -> Unit = { - add(AbstractConstraint(ConstraintSymbol.symbol(id, 0))) + add(TestConstraint(ConstraintSymbol.symbol(id, 0))) } fun constraint(id: String, arg: Any): ConjBuilder.() -> Unit = { - add(AbstractConstraint(ConstraintSymbol.symbol(id, 1), arg)) + add(TestConstraint(ConstraintSymbol.symbol(id, 1), arg)) } fun constraint(id: String, arg1: Any, arg2: Any): ConjBuilder.() -> Unit = { - add(AbstractConstraint(ConstraintSymbol.symbol(id, 2), arg1, arg2)) + add(TestConstraint(ConstraintSymbol.symbol(id, 2), arg1, arg2)) } fun constraint(id: String, arg1: Any, arg2: Any, arg3: Any): ConjBuilder.() -> Unit = { - add(AbstractConstraint(ConstraintSymbol.symbol(id, 3), arg1, arg2, arg3)) + add(TestConstraint(ConstraintSymbol.symbol(id, 3), arg1, arg2, arg3)) } fun constraint(id: String, args: Array): ConjBuilder.() -> Unit = { - add(AbstractConstraint(ConstraintSymbol.symbol(id, args.size), * args)) + add(TestConstraint(ConstraintSymbol.symbol(id, args.size), * args)) +} + +fun expression(id: String): ConjBuilder.() -> Unit = { + add(JavaPredicate(JavaPredicateSymbol(0), id)) +} +fun expression(id: String, arg: Any): ConjBuilder.() -> Unit = { + add(JavaPredicate(JavaPredicateSymbol(1), id, arg)) +} +fun expression(id: String, arg1: Any, arg2: Any): ConjBuilder.() -> Unit = { + add(JavaPredicate(JavaPredicateSymbol(2), id, arg1, arg2)) +} +fun expression(id: String, arg1: Any, arg2: Any, arg3: Any): ConjBuilder.() -> Unit = { + add(JavaPredicate(JavaPredicateSymbol(3), id, arg1, arg2, arg3)) } class RB(tag: String) : RuleBuilder(tag) {} @@ -75,3 +88,31 @@ private fun buildConjunction(type: Class, return conjBuilder } +data class TestOccurrence(val arguments : List, val constraint : Constraint) : ConstraintOccurrence { + + constructor(id: String, vararg args: Any) : + this(listOf(* args), TestConstraint(ConstraintSymbol.symbol(id, args.size))) {} + + constructor(constraint: Constraint) : this(constraint.arguments(), constraint) {} + + override fun constraint(): Constraint = constraint + + override fun arguments(): List = arguments + + override fun toString(): String = "#${constraint().symbol()}(${arguments().joinToString()})" + +} + +data class TestConstraint(val symbol: ConstraintSymbol, val arguments: List) : Constraint { + + constructor(symbol: ConstraintSymbol, vararg args: Any) : this(symbol, listOf(* args)) {} + + override fun arguments(): List = arguments + + override fun symbol(): ConstraintSymbol = symbol + + override fun argumentTypes(): List> = arguments.map { arg -> arg.javaClass } + + override fun toString(): String = "${symbol()}(${arguments().joinToString()})" + +} diff --git a/reactor/Test/test/TestBasicProgram.kt b/reactor/Test/test/TestBasicProgram.kt new file mode 100644 index 00000000..cf655289 --- /dev/null +++ b/reactor/Test/test/TestBasicProgram.kt @@ -0,0 +1,61 @@ +import jetbrains.mps.logic.reactor.core.ReactorEvaluationSession +import jetbrains.mps.logic.reactor.core.ReactorPlanningSession +import jetbrains.mps.logic.reactor.predicate.ReactorSessionSolver +import jetbrains.mps.logic.reactor.program.EvaluationSession +import jetbrains.mps.logic.reactor.program.PlanningSession +import org.junit.AfterClass +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import kotlin.test.assertEquals + +/** + * @author Fedor Isakov + */ + +class TestBasicProgram { + + companion object { + @BeforeClass @JvmStatic fun setup() { + ReactorPlanningSession.init(); + ReactorEvaluationSession.init(); + } + @AfterClass @JvmStatic fun teardown() { + ReactorEvaluationSession.deinit(); + ReactorPlanningSession.deinit(); + } + } + + @Before fun beforeTest() { + program = PlanningSession.newSession("test", ReactorSessionSolver()) + evalConfig = EvaluationSession.newSession(program) + } + + lateinit var program: PlanningSession + lateinit var evalConfig: EvaluationSession.Config + + @Test + fun replace() { + program.addRules(arrayListOf( + rule("main", + headReplaced( + constraint("main") + ), + body( + constraint("foo") + )), + rule("main.foo", + headReplaced( + constraint("foo") + ), + body( + constraint("bar") + )) + )) + assertEquals(program.rules().count(), 2) + val session = evalConfig.start() + + + + } +} diff --git a/reactor/Test/test/TestPlanningSession.kt b/reactor/Test/test/TestPlanningSession.kt index d82a4995..a9c5b5cb 100644 --- a/reactor/Test/test/TestPlanningSession.kt +++ b/reactor/Test/test/TestPlanningSession.kt @@ -2,6 +2,7 @@ import jetbrains.mps.logic.reactor.program.PlanningSession import jetbrains.mps.logic.reactor.rule.InvalidConstraintException import jetbrains.mps.logic.reactor.rule.InvalidRuleException import jetbrains.mps.logic.reactor.core.ReactorPlanningSession +import jetbrains.mps.logic.reactor.predicate.ReactorSessionSolver import org.junit.Before import org.junit.BeforeClass import org.junit.Test @@ -14,7 +15,7 @@ import kotlin.test.expect * @author Fedor Isakov */ -class Simple { +class TestPlanningSession { companion object { @BeforeClass @JvmStatic fun setup() { @@ -23,10 +24,10 @@ class Simple { } @Before fun beforeTest() { - session = PlanningSession.newSession("test", null) + session = PlanningSession.newSession("test", ReactorSessionSolver()) } - lateinit var session:PlanningSession + lateinit var session: PlanningSession @Test fun empty() { session.addRules(ArrayList()) diff --git a/reactor/Test/test/TestRuleHandler.kt b/reactor/Test/test/TestRuleHandler.kt new file mode 100644 index 00000000..1a7b2571 --- /dev/null +++ b/reactor/Test/test/TestRuleHandler.kt @@ -0,0 +1,219 @@ +import jetbrains.mps.logic.reactor.constraint.Constraint +import jetbrains.mps.logic.reactor.constraint.ConstraintOccurrence +import jetbrains.mps.logic.reactor.core.RuleHandler +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * @author Fedor Isakov + */ + + +class TestRuleHandler { + + @Test + fun matchSingle() { + val main = rule("main", + headReplaced( + constraint("main") + ), + body( + constraint("foo") + )) + + val handler = RuleHandler(listOf(main), factory()) + + val matches = handler.lookupMatches(TestOccurrence("main")) + val match = matches.single() + assertFalse(match.isPartial()) + assertEquals(match.rule, main) + assertTrue(match.kept.isEmpty) + val (cst, occ) = match.discarded.single() + assert(cst.symbol().id() == "main") + } + + @Test + fun matchDiscardedKept() { + val main1 = rule("main1", + headReplaced( + constraint("main") + ), + body( + constraint("foo") + )) + val main2 = rule("main2", + headKept( + constraint("main") + ), + body( + constraint("bar") + )) + + val handler = RuleHandler(listOf(main1, main2), factory()) + + val matches = handler.lookupMatches(TestOccurrence("main")) + assertFalse { matches.any { m -> m.isPartial() } } + assertEquals(listOf(main1, main2), matches.map { m -> m.rule }) + matches.forEach { m -> assertTrue { m.kept.size() + m.discarded.size() == 1 } } + matches.flatMap { m -> m.kept + m.discarded }.forEach { pair -> + val (cst, occ) = pair + assert(cst.symbol().id() == "main") + } + } + + @Test + fun matchComplementMissing() { + val main1 = rule("main1", + headKept( + constraint("main") + ), + headReplaced( + constraint("secondary") + ), + body( + constraint("foo") + )) + val main2 = rule("main2", + headKept( + constraint("main") + ), + body( + constraint("bar") + )) + + val handler = RuleHandler(listOf(main1, main2), factory()) + + val matches = handler.lookupMatches(TestOccurrence("main")) + assertFalse { matches.any { m -> m.isPartial() } } + assertEquals(listOf(main2), matches.map { m -> m.rule }) + } + + @Test + fun matchComplementPresent() { + val main1 = rule("main1", + headKept( + constraint("main") + ), + headReplaced( + constraint("secondary") + ), + body( + constraint("foo") + )) + val main2 = rule("main2", + headKept( + constraint("main") + ), + body( + constraint("bar") + )) + + val handler = RuleHandler(listOf(main1, main2), factory(), listOf(TestOccurrence("secondary"))) + + val matches = handler.lookupMatches(TestOccurrence("main")) + assertFalse { matches.any { m -> m.isPartial() } } + assertEquals(listOf(main1, main2), matches.map { m -> m.rule }) + } + + @Test + fun matchArgument() { + val main1 = rule("main1", + headKept( + constraint("main", "foo") + ), + body( + constraint("foo") + )) + val main2 = rule("main2", + headKept( + constraint("main", "bar") + ), + body( + constraint("bar") + )) + + val handler = RuleHandler(listOf(main1, main2), factory()) + + val matches = handler.lookupMatches(TestOccurrence("main", "bar")) + assertFalse { matches.any { m -> m.isPartial() } } + assertEquals(listOf(main2), matches.map { m -> m.rule }) + } + + @Test + fun noMatchArgument() { + val main1 = rule("main1", + headKept( + constraint("main", "foo") + ), + body( + constraint("foo") + )) + val main2 = rule("main2", + headKept( + constraint("main", "bar") + ), + body( + constraint("bar") + )) + + val handler = RuleHandler(listOf(main1, main2), factory()) + + val matches = handler.lookupMatches(TestOccurrence("main", "qux")) + assertFalse(matches.any()) + } + + @Test + fun processSingle() { + val main1 = rule("main1", + headKept( + constraint("main") + ), + body( + constraint("foo") + )) + + val handler = RuleHandler(listOf(main1), factory()) + + val result = handler.process(TestOccurrence("main")) + + val occurrences = handler.occurrences() + val expected = setOf(TestOccurrence("main"), TestOccurrence("foo")) + assertEquals(expected, occurrences) + } + + @Test + fun processReplaced() { + val main1 = rule("main1", + headKept( + constraint("main") + ), + body( + constraint("foo") + )) + val main2 = rule("main2", + headKept( + constraint("foo") + ), + headReplaced( + constraint("main") + ), + body( + constraint("bar") + )) + + val handler = RuleHandler(listOf(main1, main2), factory()) + + val result = handler.process(TestOccurrence("main")) + + val occurrences = handler.occurrences() + val expected = setOf(TestOccurrence("bar"), TestOccurrence("foo")) + assertEquals(expected, occurrences) + } + + + +} + +fun factory() : (Constraint) -> ConstraintOccurrence = { cst -> TestOccurrence(cst) } \ No newline at end of file