diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Handler.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Handler.kt index 78e0c7c8..175f552f 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Handler.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Handler.kt @@ -3,6 +3,7 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation import jetbrains.mps.logic.reactor.evaluation.SessionSolver +import jetbrains.mps.logic.reactor.logical.Logical import jetbrains.mps.logic.reactor.logical.LogicalContext import jetbrains.mps.logic.reactor.logical.LogicalPattern import jetbrains.mps.logic.reactor.program.* @@ -14,10 +15,14 @@ import java.util.* class Handler { - private val sessionSolver: SessionSolver + val sessionSolver: SessionSolver + private val rules : MutableList = ArrayList() + private val stored : MutableList = ArrayList() + private val activeQueue : Queue = LinkedList() + constructor( sessionSolver: SessionSolver, programRules: Iterable, @@ -33,8 +38,17 @@ class Handler { fun occurrences(): Set = stored.toSet() - fun process(active: ConstraintOccurrence) { - store(active) + fun queue(occurrence: ConstraintOccurrence) { + activeQueue.add(occurrence) + while (activeQueue.isNotEmpty()) { + process(activeQueue.poll()) + } + } + + private fun process(active: ConstraintOccurrence) { + if (!active.isStored()) { + store(active) + } val matcher = object : Matcher(rules) { override fun findOccurrences(constraint: Constraint, acceptable: (ConstraintOccurrence) -> Boolean): @@ -43,8 +57,8 @@ class Handler { } for (match in matcher.lookupMatches(active).filter { pm -> pm.rule.checkGuard(pm.logicalContext()) }) { - if (!active.isAlive()) return - if (match.occurrences().any{ co -> !co.isAlive() }) continue + if (!active.isStored()) return + if (match.occurrences().any{ co -> !co.isStored() }) continue for ((cst, occ) in match.discarded) { discard(occ) @@ -62,11 +76,12 @@ class Handler { private fun discard(occ: ConstraintOccurrence) { stored.remove(occ) + occ.terminate() } private fun activate(item: AndItem, logicalContext: LogicalContext) { when(item) { - is Constraint -> process(item.occurrence(logicalContext)) + is Constraint -> process(item.occurrence(this@Handler, logicalContext)) is Predicate -> tellPredicate(item.invocation(logicalContext)) else -> throw IllegalArgumentException("unknown item ${item}") } @@ -82,7 +97,7 @@ class Handler { sessionSolver.tell(invocation.predicate().symbol(), * invocation.arguments().toTypedArray()) } - private fun ConstraintOccurrence.isAlive(): Boolean = + private fun ConstraintOccurrence.isStored(): Boolean = stored.contains(this) } @@ -91,8 +106,8 @@ private fun AndItem.argumentValues(context: LogicalContext): List = arguments().map { arg -> if (arg is LogicalPattern<*>) context.valueFor(arg) else arg!! }.toList() -private fun Constraint.occurrence(context: LogicalContext): ConstraintOccurrence = - ReactorConstraintOccurrence(this, argumentValues(context)) +private fun Constraint.occurrence(handler: Handler, context: LogicalContext): ConstraintOccurrence = + MemConstraintOccurrence(handler, this, argumentValues(context)) private fun Predicate.invocation(logicalContext: LogicalContext): PredicateInvocation { @@ -104,20 +119,52 @@ private fun Predicate.invocation(logicalContext: LogicalContext): PredicateInvoc } } +fun ConstraintOccurrence.terminate() { + if (this is MemConstraintOccurrence) { + _terminate() + } +} -private data class ReactorConstraintOccurrence(val constraint: Constraint, val arguments: List, val id: Int) : ConstraintOccurrence { +private data class MemConstraintOccurrence(val handler: Handler, val constraint: Constraint, val arguments: List, val id: Int) : + ConstraintOccurrence, + LogicalValueObserver +{ + + var alive = true companion object { val random = Random() } - constructor(constraint: Constraint, arguments: List) : - this(constraint, arguments, random.nextInt()) {} + constructor(handler: Handler, constraint: Constraint, arguments: List) : + this(handler, constraint, arguments, random.nextInt()) + { + for (a in arguments) { + if (a is Logical<*>) { + a.addObserver(this) + } + } + } override fun constraint(): Constraint = constraint override fun arguments(): Collection = arguments + override fun valueUpdated(logical: Logical<*>) { + handler.queue(this) + } + + + fun _terminate() { + for (a in arguments) { + if (a is Logical<*>) { + a.removeObserver(this) + } + } + alive = false + } + + override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})" } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MemLogical.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt similarity index 62% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/MemLogical.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt index b95c7ccb..ecadca6c 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MemLogical.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt @@ -4,11 +4,26 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.logical.Logical import jetbrains.mps.logic.reactor.logical.LogicalPattern import jetbrains.mps.logic.reactor.logical.SolverLogical +import java.util.* /** * @author Fedor Isakov */ +internal interface LogicalValueObserver { + + fun valueUpdated(logical: Logical<*>) + +} + +internal fun Logical<*>.addObserver(observer: LogicalValueObserver) { + (this as MemLogical<*>).observers.add(observer) +} + +internal fun Logical<*>.removeObserver(observer: LogicalValueObserver) { + (this as MemLogical<*>).observers.remove(observer) +} + fun LogicalPattern.logical(): Logical = MemLogical(name()) class MemLogical : SolverLogical { @@ -27,6 +42,8 @@ class MemLogical : SolverLogical { var rank = 0 + internal val observers = ArrayList() + constructor(value: T) { this.name = "$${anonIdx++}" this._value = value @@ -55,15 +72,33 @@ class MemLogical : SolverLogical { override fun setParent(parent: SolverLogical) { this._parent = parent as MemLogical + if (find().isBound) { + notifyObservers() + } } - override fun setValue(newValue: T) { this._value = newValue } + override fun setValue(newValue: T) { + this._value = newValue + notifyObservers() + } override fun rank(): Int = rank override fun incRank() { rank++ } - override fun mergeObservers(mergeFrom: SolverLogical) = TODO() + override fun mergeObservers(mergeFrom: SolverLogical) { + val other = mergeFrom as MemLogical + observers.addAll(other.observers) + other.observers.clear() + } + + private fun notifyObservers() { + val obs = ArrayList(observers) + this.observers.clear() + for (o in obs) { + o.valueUpdated(this) + } + } private fun find(): MemLogical { val tmp = _parent diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MemEvaluationSession.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/MemEvaluationSession.kt index c60ff902..eaa29df1 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MemEvaluationSession.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/MemEvaluationSession.kt @@ -72,7 +72,7 @@ class MemEvaluationSession : EvaluationSession { fun launch(main: ConstraintOccurrence) { this.handler = Handler(sessionSolver(), program.rules()) - handler.process(main) + handler.queue(main) // FIXME: shutdown the session properly ourBackend.ourSession.set(null) } diff --git a/reactor/Test/src/program/MemConstraint.kt b/reactor/Test/src/program/MemConstraint.kt index 97033a28..19df3f65 100644 --- a/reactor/Test/src/program/MemConstraint.kt +++ b/reactor/Test/src/program/MemConstraint.kt @@ -23,22 +23,3 @@ data class MemConstraint(val symbol: ConstraintSymbol, val arguments: List) } -data class MemConstraintOccurrence(val constraint: Constraint, val arguments: List, val id: Int) : ConstraintOccurrence { - - companion object { - val random = Random() - } - - constructor(constraint: Constraint, arguments: List) : - this(constraint, arguments, random.nextInt()) {} - - constructor(id: String, vararg args: Any) : - this(MemConstraint(ConstraintSymbol.symbol(id, args.size)), listOf(* args), random.nextInt()) {} - - override fun constraint(): Constraint = constraint - - override fun arguments(): Collection = arguments - - override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})" - -} diff --git a/reactor/Test/test/EqualsSolver.kt b/reactor/Test/src/solver/EqualsSolver.kt similarity index 92% rename from reactor/Test/test/EqualsSolver.kt rename to reactor/Test/src/solver/EqualsSolver.kt index 759ced70..5569ac26 100644 --- a/reactor/Test/test/EqualsSolver.kt +++ b/reactor/Test/src/solver/EqualsSolver.kt @@ -1,3 +1,5 @@ +package solver + import jetbrains.mps.logic.reactor.evaluation.EvaluationSession import jetbrains.mps.logic.reactor.evaluation.Queryable import jetbrains.mps.logic.reactor.logical.Logical @@ -74,6 +76,8 @@ class EqualsSolver : Queryable { } else { left.setParent(right) + // the representative has all the observers + right.mergeObservers(left); } } @@ -94,6 +98,7 @@ class EqualsSolver : Queryable { right.findRoot().setValue(left) } } + fun tell_value_value(left: Any?, right: Any?) { check(left == right) } @@ -108,3 +113,7 @@ class EqualsSolver : Queryable { infix fun Logical.eq(value: T) { EvaluationSession.current().sessionSolver().tell(PredicateSymbol("equals", 2), this, value) } + +infix fun Logical.eq(other: Logical) { + EvaluationSession.current().sessionSolver().tell(PredicateSymbol("equals", 2), this, other) +} diff --git a/reactor/Test/test/LogicalHelper.kt b/reactor/Test/test/LogicalHelper.kt index 5e66ee3b..d4d93463 100644 --- a/reactor/Test/test/LogicalHelper.kt +++ b/reactor/Test/test/LogicalHelper.kt @@ -33,6 +33,8 @@ inline fun logicalPattern(name1: String, name2: String, name3: fun Logical.get(): T = findRoot().value() +fun Logical.getNullable(): T? = findRoot().value() + fun Logical.set(t: T) { if (this is SolverLogical) findRoot().setValue(t) diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index 8aa55c27..7238a1c4 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -3,7 +3,8 @@ import jetbrains.mps.logic.reactor.logical.LogicalContext import jetbrains.mps.logic.reactor.logical.LogicalPattern import jetbrains.mps.logic.reactor.program.* import program.MemConstraint -import program.MemConstraintOccurrence +import TestConstraintOccurrence +import solver.EqualsSolver import java.util.* /** @@ -73,7 +74,7 @@ fun equals(left: Any, right: Any): ConjBuilder.() -> Unit = { add(TestEqPredicate(left, right)) } -fun occurrence(id: String, vararg args: Any) : ConstraintOccurrence = MemConstraintOccurrence(id, * args) +fun occurrence(id: String, vararg args: Any) : ConstraintOccurrence = TestConstraintOccurrence(id, * args) class RB(tag: String, val env: Environment) : RuleBuilder(tag) { @@ -128,3 +129,23 @@ private fun buildConjunction(type: Class, } return conjBuilder } + +data class TestConstraintOccurrence(val constraint: Constraint, val arguments: List, val id: Int) : ConstraintOccurrence { + + companion object { + val random = Random() + } + + constructor(constraint: Constraint, arguments: List) : + this(constraint, arguments, random.nextInt()) {} + + constructor(id: String, vararg args: Any) : + this(MemConstraint(ConstraintSymbol.symbol(id, args.size)), listOf(* args), random.nextInt()) {} + + override fun constraint(): Constraint = constraint + + override fun arguments(): Collection = arguments + + override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})" + +} \ No newline at end of file diff --git a/reactor/Test/test/TestHandler.kt b/reactor/Test/test/TestHandler.kt index 7b7d8634..455ce1c1 100644 --- a/reactor/Test/test/TestHandler.kt +++ b/reactor/Test/test/TestHandler.kt @@ -20,13 +20,17 @@ import org.junit.Test class TestHandler { - fun sessionSolver(exprSolver: Queryable, equalsSolver: Queryable) : SessionSolver = + private fun sessionSolver(exprSolver: Queryable, equalsSolver: Queryable) : SessionSolver = MemSessionSolver(exprSolver, equalsSolver).apply { init(PredicateSymbol("equals", 2), JavaPredicateSymbol.EXPRESSION0, JavaPredicateSymbol.EXPRESSION1, JavaPredicateSymbol.EXPRESSION2, JavaPredicateSymbol.EXPRESSION3) } private fun Builder.handler(vararg occurrences: ConstraintOccurrence): Handler = Handler(sessionSolver(env.expressionSolver, env.equalsSolver), rules, listOf(* occurrences)) + private fun Handler.eq(left: Logical, right: Logical) { + sessionSolver.tell(PredicateSymbol("equals", 2), left, right) + } + companion object { @BeforeClass @JvmStatic fun setup() { @@ -48,7 +52,7 @@ class TestHandler { constraint("foo") )) ).run { - handler().apply { process(occurrence("main")) }.let { rh -> + handler().apply { queue(occurrence("main")) }.let { rh -> assertEquals( setOf(ConstraintSymbol("main", 0), ConstraintSymbol("foo", 0)), rh.occurrences().map { it.constraint().symbol() }.toSet()) @@ -77,7 +81,7 @@ class TestHandler { constraint("bar") )) ).run { - handler().apply { process(occurrence("main")) }.let { rh -> + handler().apply { queue(occurrence("main")) }.let { rh -> assertEquals( setOf(ConstraintSymbol("bar", 0), ConstraintSymbol("foo", 0)), rh.occurrences().map { it.constraint().symbol() }.toSet()) @@ -97,7 +101,7 @@ class TestHandler { statement { test = "value" } )) ).run { - handler().process(occurrence("main")) + handler().queue(occurrence("main")) assertEquals("value", test) } } @@ -115,7 +119,7 @@ class TestHandler { statement ({ test.set("value") }) )) ).run { - handler().process(occurrence("main")) + handler().queue(occurrence("main")) assertEquals("value", test.get()) } } @@ -134,7 +138,7 @@ class TestHandler { statement ({ test = x.get() } ) )) ).run { - handler().process(occurrence("main")) + handler().queue(occurrence("main")) assertEquals("expected", test) } } @@ -161,7 +165,7 @@ class TestHandler { statement ({ test = y.get() }) )) ).run { - handler().apply { process(occurrence("main")) }.let { rh -> + handler().apply { queue(occurrence("main")) }.let { rh -> assertEquals( setOf(ConstraintSymbol("main", 0), ConstraintSymbol("next", 0)), rh.occurrences().map { it.constraint().symbol() }.toSet()) @@ -196,7 +200,7 @@ class TestHandler { statement { test2 = "expected" } )) ).run { - handler().process(occurrence("main")) + handler().queue(occurrence("main")) assertEquals("not initialized 1", test1) assertEquals("expected", test2) } @@ -218,7 +222,7 @@ class TestHandler { ).handler().run { val a = logical("a") a.set("value") - process(occurrence("foo", a)) + queue(occurrence("foo", a)) assertEquals(1, occurrences().size) val co = occurrences().first() assertEquals(ConstraintSymbol("bar",1), co.constraint().symbol()) @@ -247,7 +251,7 @@ class TestHandler { body( constraint("expected2") ) ) ).handler().run { - process(occurrence("foo")) + queue(occurrence("foo")) assertEquals( setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0)), occurrences().map { co -> co.constraint().symbol() }.toSet()) @@ -272,13 +276,139 @@ class TestHandler { body( constraint("expected2") ) ) ).handler().run { - process(occurrence("foo")) + queue(occurrence("foo")) assertEquals( setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0)), occurrences().map { co -> co.constraint().symbol() }.toSet()) } } + @Test + fun occurrenceReactivated() { + val X = logicalPattern("X") + program( + rule("zeroth", + headKept( constraint("foo") ), body( statement({ x -> x.set(999) }, X), + constraint("bar", X)) + ), + rule("first", + headKept( constraint("foo") ), + body( constraint("bar", X), + constraint("qux", X)) + ), + rule("second", + headReplaced( constraint("qux", X) ), + body( constraint("expected1"), + statement({ x -> x.set(123) }, X)) + ), + rule("third", + headReplaced( constraint("foo") ), + body( constraint("unexpected")) + ), + rule("fourth", + headReplaced( constraint("foo") ), + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), + body( constraint("expected2") ) + ), + rule("fifth", + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 999 }, X)), + body( constraint("expected3", X)) + ) + ).handler().run { + queue(occurrence("foo")) + assertEquals(3, occurrences().count()) + assertEquals( + setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)), + occurrences().map { co -> co.constraint().symbol() }.toSet()) + val ex3 = occurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first() + assertEquals(999, (ex3.arguments().first() as Logical).value()) + } + } + + @Test + fun occurrenceReactivatedAfterUnion() { + val (X, Y) = logicalPattern("X", "Y") + var handler : Handler? = null + program( + rule("first", + headKept( constraint("foo") ), + body( constraint("bar", X), + constraint("qux", Y), + statement({ x, y -> handler!!.eq(x, y) }, X, Y)) + ), + rule("second", + headReplaced( constraint("qux", Y) ), + body( constraint("expected1"), + statement({ y -> y.set(123) }, Y)) + ), + rule("third", + headReplaced( constraint("foo") ), + body( constraint("unexpected")) + ), + rule("fourth", + headReplaced( constraint("foo") ), + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), + body( constraint("expected2") ) + ), + rule("fifth", + headKept( constraint("bar", X) ), + body( constraint("expected3", X)) + ) + ).handler().run { + handler = this + queue(occurrence("foo")) + assertEquals(3, occurrences().count()) + assertEquals( + setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)), + occurrences().map { co -> co.constraint().symbol() }.toSet()) + val ex3 = occurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first() + assertEquals(null, (ex3.arguments().first() as Logical).value()) + } + } + + @Test + fun occurrenceReactivatedAfterUnionUnbound() { + val (X, Y) = logicalPattern("X", "Y") + var handler : Handler? = null + program( + rule("first", + headKept( constraint("foo") ), + body( constraint("bar", X), + statement({ x, y -> handler!!.eq(x, y) }, X, Y), + constraint("qux", Y)) + ), + rule("second", + headReplaced( constraint("qux", Y) ), + body( constraint("expected1"), + statement({ y -> y.set(123) }, Y)) + ), + rule("third", + headReplaced( constraint("foo") ), + body( constraint("unexpected")) + ), + rule("fourth", + headReplaced( constraint("foo") ), + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), + body( constraint("expected2") ) + ), + rule("fifth", + headKept( constraint("bar", X) ), + body( constraint("expected3", X)) + ) + ).handler().run { + handler = this + queue(occurrence("foo")) + assertEquals(3, occurrences().count()) + assertEquals( + setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)), + occurrences().map { co -> co.constraint().symbol() }.toSet()) + val ex3 = occurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first() + assertEquals(null, (ex3.arguments().first() as Logical).value()) + } + } + + + } diff --git a/reactor/Test/test/TestProgram.kt b/reactor/Test/test/TestProgram.kt index d253c6ef..f45d2ea8 100644 --- a/reactor/Test/test/TestProgram.kt +++ b/reactor/Test/test/TestProgram.kt @@ -7,6 +7,7 @@ import jetbrains.mps.logic.reactor.program.JavaPredicateSymbol import jetbrains.mps.logic.reactor.program.PredicateSymbol import org.junit.* import org.junit.Assert.* +import solver.eq /** * @author Fedor Isakov