Initial support for expressions

This commit is contained in:
Fedor Isakov 2015-12-09 17:04:28 +01:00
parent a7708f3974
commit fc372c1420
6 changed files with 397 additions and 225 deletions

View File

@ -11,16 +11,19 @@ import java.util.*
class RuleHandler {
private val sessionSolver: SessionSolver
private val occurrenceFactory: (Constraint) -> ConstraintOccurrence
private val rules : MutableList<Rule> = ArrayList<Rule>()
private val stored : MutableList<ConstraintOccurrence> = ArrayList<ConstraintOccurrence>()
constructor(
sessionSolver: SessionSolver,
programRules: Iterable<Rule>,
occurrenceFactory: (Constraint) -> ConstraintOccurrence,
// for testing purposes only
occurrences: Iterable<ConstraintOccurrence>? = null)
{
this.sessionSolver = sessionSolver
this.occurrenceFactory = occurrenceFactory
this.rules.addAll(programRules)
if (occurrences != null) {
@ -51,16 +54,27 @@ class RuleHandler {
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(item: AndItem) {
when(item) {
is Constraint -> process(activate(item))
is Predicate -> tellPredicate(item)
else -> throw IllegalArgumentException("unknown item ${item}")
}
}
private fun activate(constraint: Constraint): ConstraintOccurrence = occurrenceFactory(constraint)
private fun tellPredicate(predicate: Predicate) {
when (predicate.symbol()) {
is JavaPredicateSymbol -> evalJava(predicate)
else -> TODO()
}
}
private fun evalJava(expr: Predicate) {
sessionSolver.tell(expr.symbol(), * expr.arguments().toTypedArray())
}
fun lookupMatches(occ: ConstraintOccurrence): Iterable<PartialMatch> {
val partialMatches = rules.flatMap { r ->
val matchedKept = r.headKept().filter { cst -> cst.matches(occ) }

View File

@ -6,7 +6,17 @@ import jetbrains.mps.logic.reactor.constraint.*
* @author Fedor Isakov
*/
class ReactorSessionSolver : SessionSolver() {
class ReactorSessionSolver(val expressionSolver: Queryable) : SessionSolver() {
constructor() : this(object: Queryable {
override fun ask(predicateSymbol: PredicateSymbol?, vararg args: Any?): Boolean {
throw UnsupportedOperationException()
}
override fun tell(symbol: Symbol?, vararg args: Any?) {
throw UnsupportedOperationException()
}
})
override fun solverClass(predicateSymbol: PredicateSymbol?): Class<out Queryable>? {
throw UnsupportedOperationException()
@ -14,10 +24,9 @@ class ReactorSessionSolver : SessionSolver() {
override fun registerSymbol(predicateSymbol: PredicateSymbol, computingTracer: ComputingTracer?) {
when (predicateSymbol) {
is JavaPredicateSymbol -> registerSolver(predicateSymbol, expressionSolver)
else -> throw UnsupportedOperationException("not implemented")
}
throw UnsupportedOperationException()
}
}

View File

@ -2,14 +2,28 @@ import jetbrains.mps.logic.reactor.constraint.*
import jetbrains.mps.logic.reactor.rule.Rule
import jetbrains.mps.logic.reactor.rule.RuleBuilder
import java.util.*
import kotlin.reflect.KClass
/**
* @author Fedor Isakov
*/
fun rule(tag: String, vararg component:RB.() -> Unit): Rule {
val rb = RB(tag)
fun program(vararg ruleBuilders : Environment.() -> Rule): Program {
val env = Environment()
val rules = ArrayList<Rule>()
with (env) {
for (rb in ruleBuilders) {
rules.add(rb())
}
}
return Program(env, rules)
}
fun rule(tag: String, vararg component:RB.() -> Unit): Environment.() -> Rule = {
rule(tag, this, * component)
}
fun rule(tag: String, env: Environment, vararg component:RB.() -> Unit): Rule {
val rb = RB(tag, env)
for (cmp in component) {
rb.cmp()
}
@ -17,55 +31,89 @@ fun rule(tag: String, vararg component:RB.() -> Unit): Rule {
}
fun headKept(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = {
appendHeadKept( * buildConjunction(Constraint::class.java, content).toArray())
appendHeadKept( * buildConjunction(Constraint::class.java, env, content).toArray())
}
fun headReplaced(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = {
appendHeadReplaced( * buildConjunction(Constraint::class.java, content).toArray())
appendHeadReplaced( * buildConjunction(Constraint::class.java, env, content).toArray())
}
fun guard(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = {
appendGuard( * buildConjunction(AndItem::class.java, content).toArray())
appendGuard( * buildConjunction(Predicate::class.java, env, content).toArray())
}
fun body(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = {
appendBody( * buildConjunction(AndItem::class.java, content).toArray())
appendBody( * buildConjunction(AndItem::class.java, env, content).toArray())
}
fun constraint(id: String): ConjBuilder.() -> Unit = {
add(TestConstraint(ConstraintSymbol.symbol(id, 0)))
}
fun constraint(id: String, arg: Any): ConjBuilder.() -> Unit = {
add(TestConstraint(ConstraintSymbol.symbol(id, 1), arg))
}
fun constraint(id: String, arg1: Any, arg2: Any): ConjBuilder.() -> Unit = {
add(TestConstraint(ConstraintSymbol.symbol(id, 2), arg1, arg2))
}
fun constraint(id: String, arg1: Any, arg2: Any, arg3: Any): ConjBuilder.() -> Unit = {
add(TestConstraint(ConstraintSymbol.symbol(id, 3), arg1, arg2, arg3))
}
fun constraint(id: String, args: Array<out Any>): ConjBuilder.() -> Unit = {
fun constraint(id: String, vararg args: Any): ConjBuilder.() -> Unit = {
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))
fun expression(body: () -> Boolean): ConjBuilder.() -> Unit = {
add(TestJavaPredicate(JavaPredicateSymbol(1),body))
}
class RB(tag: String) : RuleBuilder(tag) {}
fun expression(body: (Any) -> Boolean, vararg args: Any): ConjBuilder.() -> Unit = {
add(TestJavaPredicate(JavaPredicateSymbol(2), body, * args))
}
class ConjBuilder (val type: Class<out AndItem>) {
fun expression(body: (Any, Any) -> Boolean, vararg args: Any): ConjBuilder.() -> Unit = {
add(TestJavaPredicate(JavaPredicateSymbol(3), body, * args))
}
fun expression(body: (Any, Any, Any) -> Boolean, vararg args: Any): ConjBuilder.() -> Unit = {
add(TestJavaPredicate(JavaPredicateSymbol(4), body, * args))
}
class Program(val env: Environment, val rules: List<Rule>) {
fun occurrenceFactory() : (Constraint) -> ConstraintOccurrence = { cst -> TestOccurrence(cst) }
}
class Environment() {
val javaPredicates = HashMap<Any, TestJavaPredicate>()
fun expressionSolver(): Queryable = object : Queryable {
override fun ask(predicateSymbol: PredicateSymbol, vararg args: Any): Boolean {
return javaPredicates[predicateSymbol]?.expr?.invoke(listOf(* args)) ?:
ERROR("no such symbol $predicateSymbol")
}
override fun tell(symbol: Symbol, vararg args: Any) {
when (symbol) {
is JavaPredicateSymbol -> javaPredicates[args[0]]?.expr?.invoke(listOf(* args).drop(1))
else -> ERROR("uknown symbol $symbol")
}
}
private fun ERROR(msg: String) : Nothing = throw IllegalArgumentException(msg)
}
fun addJavaPredicate(javaPredicate: TestJavaPredicate) {
javaPredicates[javaPredicate.args[0]] = javaPredicate
}
}
class RB(tag: String, val env: Environment?) : RuleBuilder(tag) {}
class ConjBuilder {
val constraints = ArrayList<AndItem>()
fun add(constraint: AndItem): Unit {
if (!type.isAssignableFrom(constraint.javaClass))
throw IllegalArgumentException("unexpected constraint class '${constraint.javaClass}'")
constraints.add(constraint)
val type: Class<out AndItem>
val _env: Environment?
val env: Environment
get() { return _env ?: throw IllegalStateException("no enviroment") }
constructor(type: Class<out AndItem>, env: Environment?) {
this.type = type
this._env = env
}
fun add(item: AndItem): Unit {
if (!type.isAssignableFrom(item.javaClass))
throw IllegalArgumentException("unexpected constraint class '${item.javaClass}'")
constraints.add(item)
if (item is TestJavaPredicate) {
env.addJavaPredicate(item)
}
}
fun <T : AndItem> toArray(): Array<T> =
if (Constraint::class.java.isAssignableFrom(type))
@ -79,9 +127,10 @@ class ConjBuilder (val type: Class<out AndItem>) {
}
private fun buildConjunction(type: Class<out AndItem>,
env: Environment?,
content: Array<out ConjBuilder.() -> Unit>): ConjBuilder
{
var conjBuilder = ConjBuilder(type)
var conjBuilder = ConjBuilder(type, env)
for (c in content) {
conjBuilder.c()
}
@ -116,3 +165,55 @@ data class TestConstraint(val symbol: ConstraintSymbol, val arguments: List<Any>
override fun toString(): String = "${symbol()}(${arguments().joinToString()})"
}
interface JavaExpression {
fun invoke(args: List<Any>): Boolean
}
class JavaExpression0(val code: () -> Boolean) : JavaExpression {
override fun invoke(args: List<Any>): Boolean {
if (args.size != 0) throw IllegalArgumentException("arity mismatch")
return code()
}
}
class JavaExpression1(val code: (Any) -> Boolean) : JavaExpression {
override fun invoke(args: List<Any>): Boolean {
if (args.size != 1) throw IllegalArgumentException("arity mismatch")
return code(args[0])
}
}
class JavaExpression2(val code: (Any, Any) -> Boolean) : JavaExpression {
override fun invoke(args: List<Any>): Boolean {
if (args.size != 2) throw IllegalArgumentException("arity mismatch")
return code(args[0], args[1])
}
}
class JavaExpression3(val code: (Any, Any, Any) -> Boolean) : JavaExpression {
override fun invoke(args: List<Any>): Boolean {
if (args.size != 3) throw IllegalArgumentException("arity mismatch")
return code(args[0], args[1], args[2])
}
}
data class TestJavaPredicate(val symbol: JavaPredicateSymbol, val expr: JavaExpression, val args: List<Any>) : Predicate {
constructor(symbol: JavaPredicateSymbol, code: () -> Boolean, vararg args: Any) :
this(symbol, JavaExpression0(code), listOf(code.toString()) + listOf(* args)) {}
constructor(symbol: JavaPredicateSymbol, code: (Any) -> Boolean, vararg args: Any) :
this(symbol, JavaExpression1(code), listOf(code.toString()) + listOf(* args)) {}
constructor(symbol: JavaPredicateSymbol, code: (Any, Any) -> Boolean, vararg args: Any) :
this(symbol, JavaExpression2(code), listOf(code.toString()) + listOf(* args)) {}
constructor(symbol: JavaPredicateSymbol, code: (Any, Any, Any) -> Boolean, vararg args: Any) :
this(symbol, JavaExpression3(code), listOf(code.toString()) + listOf(* args)) {}
override fun arguments(): List<Any> = args
override fun symbol(): PredicateSymbol = symbol
}

View File

@ -27,16 +27,16 @@ class TestBasicProgram {
}
@Before fun beforeTest() {
program = PlanningSession.newSession("test", ReactorSessionSolver())
evalConfig = EvaluationSession.newSession(program)
planningSession = PlanningSession.newSession("test", ReactorSessionSolver())
evalConfig = EvaluationSession.newSession(planningSession)
}
lateinit var program: PlanningSession
lateinit var planningSession: PlanningSession
lateinit var evalConfig: EvaluationSession.Config
@Test
fun replace() {
program.addRules(arrayListOf(
program(
rule("main",
headReplaced(
constraint("main")
@ -50,12 +50,11 @@ class TestBasicProgram {
),
body(
constraint("bar")
))
))
assertEquals(program.rules().count(), 2)
val session = evalConfig.start()
))).run {
planningSession.addRules(rules)
assertEquals(planningSession.rules().count(), 2)
val session = evalConfig.start()
}
}
}

View File

@ -35,18 +35,20 @@ class TestPlanningSession {
@Test(expected = InvalidRuleException::class)
fun emptyBody() {
session.addRules(arrayListOf(
program(
rule("foo",
headKept(
constraint("bar")
))
))
assertEquals(session.rules().count(), 1)
))).run {
session.addRules(rules)
assertEquals(session.rules().count(), 1)
}
}
@Test
fun replace() {
session.addRules(arrayListOf(
program(
rule("foo",
headReplaced(
constraint("bar")
@ -63,22 +65,26 @@ class TestPlanningSession {
),
body(
constraint("blah")
))
))
assertEquals(session.rules().count(), 2)
))).run {
session.addRules(rules)
assertEquals(session.rules().count(), 2)
}
}
@Test(expected = InvalidConstraintException::class)
fun fail() {
session.addRules(arrayListOf(
program(
rule("foo",
headReplaced(
constraint("bar", 1)
),
body(
constraint("bar", "1")
))
))
))).run {
session.addRules(rules)
}
}
}

View File

@ -1,6 +1,11 @@
import jetbrains.mps.logic.reactor.constraint.Constraint
import jetbrains.mps.logic.reactor.constraint.ConstraintOccurrence
import jetbrains.mps.logic.reactor.constraint.JavaPredicateSymbol
import jetbrains.mps.logic.reactor.constraint.Queryable
import jetbrains.mps.logic.reactor.constraint.SessionSolver
import jetbrains.mps.logic.reactor.core.RuleHandler
import jetbrains.mps.logic.reactor.predicate.ReactorSessionSolver
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
@ -13,207 +18,245 @@ import kotlin.test.assertTrue
class TestRuleHandler {
@Test
fun matchSingle() {
val main = rule("main",
headReplaced(
constraint("main")
),
body(
constraint("foo")
))
fun occurrence(id: String, vararg args: Any) : TestOccurrence = TestOccurrence(id, * args)
val handler = RuleHandler(listOf(main), factory())
fun sessionSolver(exprSolver: Queryable) : SessionSolver =
ReactorSessionSolver(exprSolver).apply { init(JavaPredicateSymbol.EXPRESSION0, JavaPredicateSymbol.EXPRESSION1, JavaPredicateSymbol.EXPRESSION2, JavaPredicateSymbol.EXPRESSION3) }
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")
fun Program.handler(vararg occurrences: ConstraintOccurrence): RuleHandler =
RuleHandler(sessionSolver(env.expressionSolver()), rules, occurrenceFactory(), listOf(* occurrences))
companion object {
@BeforeClass @JvmStatic fun setup() {
}
}
@Before fun beforeTest() {
}
@Test
fun matchDiscardedKept() {
val main1 = rule("main1",
headReplaced(
constraint("main")
),
body(
constraint("foo")
))
val main2 = rule("main2",
headKept(
constraint("main")
),
body(
constraint("bar")
))
fun matchSingle() {
program(
rule("main",
headReplaced(
constraint("main")
),
body(
constraint("foo")
))).run {
val handler = RuleHandler(listOf(main1, main2), factory())
val main = rules.first()
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
val matches = handler().lookupMatches(occurrence("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() {
program(
rule("main1",
headReplaced(
constraint("main")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main")
),
body(
constraint("bar")
))).run {
val matches = handler().lookupMatches(occurrence("main"))
assertFalse { matches.any { m -> m.isPartial() } }
assertEquals(rules, 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")
))
program(
rule("main1",
headKept(
constraint("main")
),
headReplaced(
constraint("secondary")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main")
),
body(
constraint("bar")
))).run {
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 })
val matches = handler().lookupMatches(occurrence("main"))
assertFalse { matches.any { m -> m.isPartial() } }
assertEquals(rules.drop(1), 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")
))
program(
rule("main1",
headKept(
constraint("main")
),
headReplaced(
constraint("aux")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main")
),
body(
constraint("bar")
))).run {
val handler = RuleHandler(listOf(main1, main2), factory(), listOf(TestOccurrence("secondary")))
val matches = handler(TestOccurrence("aux")).lookupMatches(occurrence("main"))
val matches = handler.lookupMatches(TestOccurrence("main"))
assertFalse { matches.any { m -> m.isPartial() } }
assertEquals(listOf(main1, main2), matches.map { m -> m.rule })
assertFalse { matches.any { m -> m.isPartial() } }
assertEquals(rules, 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")
))
program(
rule("main1",
headKept(
constraint("main", "foo")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main", "bar")
),
body(
constraint("bar")
))).run {
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 })
val matches = handler().lookupMatches(occurrence("main", "bar"))
assertFalse { matches.any { m -> m.isPartial() } }
assertEquals(rules.drop(1), 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")
))
program(
rule("main1",
headKept(
constraint("main", "foo")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("main", "bar")
),
body(
constraint("bar")
))).run {
val handler = RuleHandler(listOf(main1, main2), factory())
val matches = handler.lookupMatches(TestOccurrence("main", "qux"))
assertFalse(matches.any())
val matches = handler().lookupMatches(occurrence("main", "qux"))
assertFalse(matches.any())
}
}
@Test
fun processSingle() {
val main1 = rule("main1",
headKept(
constraint("main")
),
body(
constraint("foo")
))
program(
rule("main1",
headKept(
constraint("main")
),
body(
constraint("foo")
))).run {
val handler = RuleHandler(listOf(main1), factory())
val rh = handler()
val result = rh.process(occurrence("main"))
val result = handler.process(TestOccurrence("main"))
val occurrences = handler.occurrences()
val expected = setOf(TestOccurrence("main"), TestOccurrence("foo"))
assertEquals(expected, occurrences)
val occurrences = rh.occurrences()
val expected = setOf(occurrence("main"), occurrence("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")
))
program(
rule("main1",
headKept(
constraint("main")
),
body(
constraint("foo")
)),
rule("main2",
headKept(
constraint("foo")
),
headReplaced(
constraint("main")
),
body(
constraint("bar")
))).run {
val handler = RuleHandler(listOf(main1, main2), factory())
val rh = handler()
val result = rh.process(occurrence("main"))
val result = handler.process(TestOccurrence("main"))
val occurrences = handler.occurrences()
val expected = setOf(TestOccurrence("bar"), TestOccurrence("foo"))
assertEquals(expected, occurrences)
val occurrences = rh.occurrences()
val expected = setOf(occurrence("bar"), occurrence("foo"))
assertEquals(expected, occurrences)
}
}
@Test
fun basicExpression() {
var test : String = "not initialized"
program(
rule("main",
headKept(
constraint("main")
),
body(
expression { -> test = "value"; true }
))).run {
val result = handler().process(occurrence("main"))
assertEquals("value", test)
}
}
}
fun factory() : (Constraint) -> ConstraintOccurrence = { cst -> TestOccurrence(cst) }