Adapting to the latest reactor API. RuleHandler and tests (no predicates yet).

This commit is contained in:
Fedor Isakov 2015-12-09 00:26:37 +01:00
parent 1ce311a0bf
commit a7708f3974
7 changed files with 495 additions and 21 deletions

View File

@ -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<PredicateSymbol>()
val myPredicateSymbols = ArrayList<PredicateSymbol>()
val myParameters = HashMap<String, Any?>()
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<PredicateSymbol>(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<ConstraintSymbol>? {
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)
}
}

View File

@ -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<ConstraintSymbol> =
myRegistry.constraintSymbols()
@ -114,7 +116,7 @@ private class ConstraintRegistry(val sessionSolver: SessionSolver) {
unmodifiableSet(myConstraintArgTypes.keys)
fun constraintArgTypes(symbol: ConstraintSymbol): List<Class<*>> =
unmodifiableList(myConstraintArgTypes.getOrDefault(symbol, emptyList()))
unmodifiableList(myConstraintArgTypes.getOrDefault(symbol, Collections.emptyList()))
fun predicateSymbols(): Iterable<PredicateSymbol> =
unmodifiableSet(myPredicateSolvers.keys)

View File

@ -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<Rule> = ArrayList<Rule>()
private val stored : MutableList<ConstraintOccurrence> = ArrayList<ConstraintOccurrence>()
constructor(
programRules: Iterable<Rule>,
occurrenceFactory: (Constraint) -> ConstraintOccurrence,
// for testing purposes only
occurrences: Iterable<ConstraintOccurrence>? = null)
{
this.occurrenceFactory = occurrenceFactory
this.rules.addAll(programRules)
if (occurrences != null) {
this.stored.addAll(occurrences)
}
}
fun occurrences(): Set<ConstraintOccurrence> = 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<PartialMatch> {
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<PartialMatch> {
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<ConstraintOccurrence> =
stored.filter { co -> constraint.matches(co) && predicate(co) }
class PartialMatch(val rule: Rule) {
var kept = ConsList.empty<Pair<Constraint, ConstraintOccurrence>>()
private set
var discarded = ConsList.empty<Pair<Constraint, ConstraintOccurrence>>()
private set
private constructor(
original : PartialMatch,
keep: Pair<Constraint, ConstraintOccurrence>?,
discard: Pair<Constraint, ConstraintOccurrence>?) : 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()
}

View File

@ -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<out Any>): 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<out AndItem>,
return conjBuilder
}
data class TestOccurrence(val arguments : List<Any>, 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<Any> = arguments
override fun toString(): String = "#${constraint().symbol()}(${arguments().joinToString()})"
}
data class TestConstraint(val symbol: ConstraintSymbol, val arguments: List<Any>) : Constraint {
constructor(symbol: ConstraintSymbol, vararg args: Any) : this(symbol, listOf(* args)) {}
override fun arguments(): List<Any> = arguments
override fun symbol(): ConstraintSymbol = symbol
override fun argumentTypes(): List<Class<*>> = arguments.map { arg -> arg.javaClass }
override fun toString(): String = "${symbol()}(${arguments().joinToString()})"
}

View File

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

View File

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

View File

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