Introduce feedback handling API to facilitate processing of messages during program evaluation.

Minor refactorings in conreactor. Drop deprecated stuff. Default implementations for deprecated methods.
This commit is contained in:
Fedor Isakov 2019-02-14 15:02:05 +01:00
parent a17d36fe94
commit c8d7e52b6f
24 changed files with 854 additions and 244 deletions

View File

@ -0,0 +1,132 @@
/*
* 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.
*/
package jetbrains.mps.logic.reactor.evaluation;
import jetbrains.mps.logic.reactor.program.Rule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Fedor Isakov
*/
public class CompositeFeedback extends EvaluationFeedback {
/**
* Composition is associative but not commutative.
*/
public static EvaluationFeedback of(EvaluationFeedback left, EvaluationFeedback right) {
if (left == null || right == null) {
return left != null ? left : right;
} else if (left instanceof CompositeFeedback) {
return ((CompositeFeedback) left).composeRight(right);
} else if (right instanceof CompositeFeedback) {
return ((CompositeFeedback) right).composeLeft(left);
} else {
return new CompositeFeedback(Arrays.asList(left, right));
}
}
private CompositeFeedback(List<EvaluationFeedback> newElements) {
this.severity = maxSeverity(newElements);
this.elements = newElements;
}
@Override
public boolean handle(Rule rule, EvaluationFeedbackHandler handler) {
int unhandled = 0;
for (EvaluationFeedback feedback : elements) {
if (!feedback.alreadyHandled()) {
unhandled += 1;
if (handler.handleFeedback(rule, feedback)) {
feedback.setHandled();
unhandled -= 1;
}
}
}
return unhandled == 0;
}
@Override
public Severity getSeverity() {
return severity;
}
@Override
public String getMessage() {
return composeMessages(elements);
}
public List<EvaluationFeedback> elements() {
return elements;
}
@Override
public String toString() {
return elements.toString();
}
private CompositeFeedback composeLeft(EvaluationFeedback left) {
ArrayList<EvaluationFeedback> newElements = new ArrayList<>();
if (left instanceof CompositeFeedback) {
newElements.addAll(((CompositeFeedback) left).elements);
} else {
newElements.add(left);
}
newElements.addAll(elements);
return new CompositeFeedback(newElements);
}
private CompositeFeedback composeRight(EvaluationFeedback right) {
ArrayList<EvaluationFeedback> newElements = new ArrayList<>(elements);
if (right instanceof CompositeFeedback) {
newElements.addAll(((CompositeFeedback) right).elements);
} else {
newElements.add(right);
}
return new CompositeFeedback(newElements);
}
private static Severity maxSeverity(List<EvaluationFeedback> efs) {
Severity sev = Severity.DEBUG;
for(EvaluationFeedback ef : efs) {
if (ef.getSeverity().compareTo(sev) > 0) {
sev = ef.getSeverity();
}
}
return sev;
}
private static String composeMessages(List<EvaluationFeedback> efs) {
StringBuilder sb = new StringBuilder();
String sep = "";
for(EvaluationFeedback ef : efs) {
sb.append(sep).append(ef.getMessage());
sep = "; ";
}
return sb.toString();
}
private final List<EvaluationFeedback> elements;
private final Severity severity;
}

View File

@ -0,0 +1,57 @@
/*
* 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.
*/
package jetbrains.mps.logic.reactor.evaluation;
import jetbrains.mps.logic.reactor.program.Rule;
/**
* Encapsulates a detailed report to be provided by the code being evaluated.
* Does not affect the evaluation flow.
*
* @author Fedor Isakov
*/
public class DetailedFeedback extends EvaluationFeedback {
public DetailedFeedback(String message) {
this.message = message;
this.severity = Severity.INFO;
}
public DetailedFeedback(String message, Severity severity) {
this.message = message;
this.severity = severity;
}
@Override
public Severity getSeverity() {
return severity;
}
@Override
public String getMessage() {
return message;
}
@Override
public String toString() {
return getSeverity() + " " + message;
}
private final String message;
private final Severity severity;
}

View File

@ -16,6 +16,8 @@
package jetbrains.mps.logic.reactor.evaluation;
import jetbrains.mps.logic.reactor.program.Rule;
import java.util.ArrayList;
import java.util.List;
@ -31,19 +33,20 @@ import java.util.List;
*
* @author Fedor Isakov
*/
public class EvaluationFailure {
public class EvaluationFailure extends EvaluationFeedback {
public EvaluationFailure(EvaluationFailureException ex) {
this.message = ex.getMessage();
this.cause = ex;
this.message = ex.getMessage();
}
public EvaluationFailure(String message, EvaluationFailureException ex) {
this.message = message;
this.cause = ex;
this.message = message;
}
/**
* // FIXME need a smarter way to supercede previously reported failure with a more specific one
* Constructs a more specific failure given a generic one.
*/
public EvaluationFailure(EvaluationFailure reason, String message) {
@ -51,6 +54,16 @@ public class EvaluationFailure {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
@Override
public Severity getSeverity() {
return Severity.ERROR;
}
/**
* Returns all messages, from more specific to more generic.
*/
@ -64,8 +77,9 @@ public class EvaluationFailure {
return result;
}
public String getMessage() {
return message;
@Override
public Throwable failureCause() {
return getCause();
}
public EvaluationFailureException getCause() {
@ -81,5 +95,4 @@ public class EvaluationFailure {
private EvaluationFailure reason;
private String message;
private EvaluationFailureException cause;
}

View File

@ -0,0 +1,83 @@
/*
* 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.
*/
package jetbrains.mps.logic.reactor.evaluation;
import jetbrains.mps.logic.reactor.program.Rule;
/**
* Abstract feedback to be provided by the code being evaluated.
*
* @author Fedor Isakov
*/
abstract public class EvaluationFeedback {
public boolean alreadyHandled() {
return alreadyHandled;
}
protected void setHandled() {
// TODO: default logging?
this.alreadyHandled = true;
}
/**
* Returns true if the feedback has been handled.
*/
public boolean handle(Rule rule, EvaluationFeedbackHandler handler) {
if (!alreadyHandled() && handler.handleFeedback(rule, this)) {
setHandled();
}
return alreadyHandled();
}
abstract public Severity getSeverity();
abstract public String getMessage();
public boolean isFailure() {
return Severity.ERROR.compareTo(getSeverity()) <= 0;
}
public Throwable failureCause() { throw new UnsupportedOperationException(); }
public static enum Severity {
DEBUG(0, "[debug]"),
INFO(1, "[info]"),
WARN(2, "[warning]"),
ERROR(3, "[error]"),
FATAL(4, "[fatal]")
;
private Severity(int level, String title) {
this.level = level;
this.title = title;
}
@Override
public String toString() {
return title;
}
private final int level;
private final String title;
}
private boolean alreadyHandled;
}

View File

@ -0,0 +1,32 @@
/*
* 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.
*/
package jetbrains.mps.logic.reactor.evaluation;
import jetbrains.mps.logic.reactor.program.Rule;
/**
* @author Fedor Isakov
*/
public interface EvaluationFeedbackHandler {
/**
* Override this method in order to "handle" the feedback.
* Returns true if the method has handled (consumed) the feedback.
*/
boolean handleFeedback(Rule rule, EvaluationFeedback feedback);
}

View File

@ -22,6 +22,7 @@ import jetbrains.mps.logic.reactor.program.Constraint;
import jetbrains.mps.logic.reactor.program.Predicate;
import jetbrains.mps.logic.reactor.program.PredicateSymbol;
import jetbrains.mps.logic.reactor.program.Program;
import org.jetbrains.annotations.NotNull;
/**
* The starting point to evaluate a program.
@ -68,9 +69,25 @@ public abstract class EvaluationSession {
public abstract StoreView storeView();
public abstract PredicateInvocation invocation(Predicate predicate, LogicalContext logicalContext);
@Deprecated
public PredicateInvocation invocation(Predicate predicate, LogicalContext logicalContext) {
// FIXME delete the method after all code has been migrated
// keep compatibility with existing code
throw new UnsupportedOperationException();
}
public abstract ConstraintOccurrence occurrence(Constraint constraint, LogicalContext logicalContext);
@Deprecated
public ConstraintOccurrence occurrence(Constraint constraint, LogicalContext logicalContext) {
// FIXME delete the method after all code has been migrated
// keep compatibility with existing code
throw new UnsupportedOperationException();
}
public Program program() {
// FIXME delete the implementation after all code has been migrated
// keep compatibility with existing code
throw new UnsupportedOperationException();
}
protected interface Backend {

View File

@ -20,8 +20,10 @@ import jetbrains.mps.logic.reactor.program.Rule;
/**
* @author Fedor Isakov
* @deprecated Use {@link EvaluationFeedbackHandler} instead.
*/
public interface FailureHandler {
@Deprecated
public interface FailureHandler extends EvaluationFeedbackHandler {
/**
* This method is called in order to decide what to do when a failure occurs during rule evaluation.
@ -32,4 +34,12 @@ public interface FailureHandler {
*/
EvaluationFailure handleFailure(EvaluationFailure failure, Rule rule);
@Override
default boolean handleFeedback(Rule rule, EvaluationFeedback feedback) {
// compatibility adapter
if (feedback instanceof EvaluationFailure) {
return handleFailure((EvaluationFailure) feedback, rule) == null;
}
return false;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.
*/
package jetbrains.mps.logic.reactor.evaluation;
/**
* @author Fedor Isakov
*/
public interface InvocationContext {
void report(EvaluationFeedback feedback);
}

View File

@ -35,4 +35,10 @@ public interface PredicateInvocation {
LogicalContext logicalContext();
default InvocationContext invocationContext() {
// FIXME delete the implementation after all code has been migrated
// keep compatibitily with existing code
throw new UnsupportedOperationException();
};
}

View File

@ -26,8 +26,10 @@ import java.util.HashMap;
import java.util.Map;
/**
* // FIXME to be merged with EvaluationSession
* Is used to provide an interface for handlers and solvers working together in a single session.
*
*
* @author Fedor Isakov
*/
public abstract class SessionSolver implements Queryable, Instructible {
@ -59,28 +61,6 @@ public abstract class SessionSolver implements Queryable, Instructible {
handler.tell(invocation);
}
/**
* @deprecated FIXME unused, delete this method
*/
@Deprecated
public boolean ask(Predicate predicate, LogicalContext logicalContext) {
return ask(EvaluationSession.current().invocation(predicate, logicalContext));
}
/**
* @deprecated FIXME unused, delete this method
*/
@Deprecated
public void tell(AndItem item, LogicalContext logicalContext) {
if (item instanceof Predicate) {
tell(EvaluationSession.current().invocation((Predicate) item, logicalContext));
} else {
// FIXME: implement me
throw new UnsupportedOperationException("not implemented");
}
}
protected abstract void registerSymbol(PredicateSymbol predicateSymbol, EvaluationTrace computingTracer);
protected void registerSolver(PredicateSymbol constraint, AbstractSolver solver) {

View File

@ -18,6 +18,7 @@ package jetbrains.mps.logic.reactor.logical;
/**
* // FIXME to be renamed to MutableLogical
* A logical variable that can be joined with another variable to produce a union.
*
* @param <T> the value type

View File

@ -29,7 +29,9 @@ public abstract class Handler {
public abstract String name();
@Deprecated
public abstract Iterable<ConstraintSymbol> primarySymbols();
public Iterable<ConstraintSymbol> primarySymbols() {
throw new UnsupportedOperationException();
}
public abstract Iterable<Rule> rules();

View File

@ -35,7 +35,9 @@ public abstract class Rule {
public abstract Iterable<Predicate> guard();
@Deprecated
public abstract Iterable<AndItem> body();
public Iterable<AndItem> body() {
throw new UnsupportedOperationException();
}
public abstract Iterable<? extends Iterable<AndItem>> bodyAlternation();

View File

@ -24,7 +24,6 @@ import jetbrains.mps.logic.reactor.logical.MetaLogical
import jetbrains.mps.logic.reactor.program.Constraint
import jetbrains.mps.logic.reactor.program.Predicate
import jetbrains.mps.logic.reactor.program.Program
import jetbrains.mps.logic.reactor.program.Rule
import jetbrains.mps.logic.reactor.util.Profiler
import jetbrains.mps.logic.reactor.util.profile
import com.github.andrewoma.dexx.collection.Map as PersMap
@ -34,27 +33,77 @@ class Controller(
val trace: EvaluationTrace = EvaluationTrace.NULL,
val profiler: Profiler? = null,
val storeView: StoreView? = null,
val failureHandler: FailureHandler? = null)
val feedbackHandler: EvaluationFeedbackHandler? = null)
{
private inner class Context(inState: ProcessingState,
val logicalContext: LogicalContext) : InvocationContext
{
private var state = inState
fun currentState(): ProcessingState = state
override fun report(feedback: EvaluationFeedback) {
when (feedback) {
is EvaluationFailure -> this.state = state.fail(feedback)
is DetailedFeedback -> this.state = state.report(feedback)
}
}
inline fun withState(block: (ProcessingState) -> Unit) {
block.invoke(state)
}
inline fun updateState(block: (ProcessingState) -> ProcessingState) : Boolean {
this.state = block.invoke(state)
return state.operational
}
inline fun evalSafe(block: (ProcessingState) -> ProcessingState) : Boolean {
if (state.operational) {
try {
this.state = block.invoke(state)
} catch (ex: EvaluationFailureException) {
this.state = state.fail(EvaluationFailure(ex))
}
}
return state.operational
}
inline fun runSafe(block: () -> Unit) : Boolean {
if (state.operational) {
try {
block()
} catch (ex: EvaluationFailureException) {
this.state = state.fail(EvaluationFailure(ex))
}
}
return state.operational
}
}
// FIXME move to parameter
private val session: EvaluationSession = EvaluationSession.current()
private val ruleIndex: RuleIndex = RuleIndex(program.handlers())
private val frameStack = FrameStack(storeView)
private var dispatchFringe = Dispatcher(ruleIndex).fringe()
internal fun currentFrame(): Frame = frameStack.current
// FIXME move to context
private val frameStack = FrameStack(storeView)
fun storeView(): StoreView = frameStack.current.store.view()
fun activate(constraint: Constraint) : ProcessingState =
try {
process(session.occurrence(constraint, noLogicalContext), NORMAL()) // FIXME noLogicalContext
}
catch (t: Throwable) {
throw t
}
fun activate(constraint: Constraint) : ProcessingState {
// FIXME noLogicalContext
val context = Context(NORMAL(), noLogicalContext)
activateConstraint(constraint, context)
return context.currentState()
}
fun reactivate(occurrence: ConstraintOccurrence) {
// FIXME propagate the processing state further up the call stack
@ -74,7 +123,7 @@ class Controller(
return storeView()
}
private fun process(active: ConstraintOccurrence, instate: ProcessingState) : ProcessingState {
private fun process(active: ConstraintOccurrence, inState: ProcessingState) : ProcessingState {
assert(active.isAlive())
return profiler.profile<ProcessingState>("process_${active.constraint().symbol()}") {
@ -89,93 +138,13 @@ class Controller(
val activatedFringe = dispatchFringe.expand(active)
this.dispatchFringe = activatedFringe
var state : ProcessingState = instate
for (match in activatedFringe.matches().toList()) {
if (!state.operational) break
val outState = activatedFringe.matches().toList().fold(inState) { state, match ->
// TODO: paranoid check. should be isAlive() instead
if (!active.isStored()) break
// if (!match.successful) continue
// FIXME: move this check elsewhere
if ((match.matchHeadKept() + match.matchHeadReplaced()).any { co -> !co.isStored() }) continue
// TODO: prophistory
// if (propHistory.isRecorded(match)) continue
trace.trying(match)
state = match.patternPredicates().fold(state, { st, prd ->
st.eval { tellPredicate(prd, match.logicalContext(), it) } })
state = state.eval { match.rule().checkGuard(match.logicalContext(), it) }
if (state is ABORTED) { // guard is not satisfied
trace.reject(match)
state = state.reset()
continue
} else if (state is FAILED) { // guard failed
trace.failure(state.failure)
state = state.reset()
continue
}
this.dispatchFringe = dispatchFringe.consume(match)
trace.trigger(match)
for (occ in match.matchHeadReplaced()) {
this.dispatchFringe = dispatchFringe.contract(occ)
frameStack.current.store.discard(occ)
trace.discard(occ)
}
val altIt = match.rule().bodyAlternation().iterator()
while (altIt.hasNext()) {
val body = altIt.next()
if (state is FAILED) {
trace.retry(match)
state = state.reset()
}
val savedFrame = frameStack.current
frameStack.push()
state = body.fold(state) { st, item ->
if (st.operational)
when (item) {
is Constraint -> process(session.occurrence(item, match.logicalContext()), state)
is Predicate -> tellPredicate(item, match.logicalContext(), state)
else -> throw IllegalArgumentException("unknown item ${item}")
}
else st
}
if (state is FAILED) {
trace.failure(state.failure)
if (!altIt.hasNext()) {
// last alternative
if (failureHandler != null) {
// FIXME: failure handler may replace the failure
val updatedFailure = failureHandler.handleFailure(state.failure, match.rule())
if (updatedFailure == null) {
state = state.reset()
}
}
}
if (state is FAILED) {
frameStack.reset(savedFrame)
}
} else {
break
}
}
trace.finish(match)
if (state.operational && active.isStored() && match.allStored())
processMatch(state, match)
else
state
}
// TODO: should be isAlive()
@ -183,51 +152,153 @@ class Controller(
trace.suspend(active)
}
state
outState
}
}
private fun Rule.checkGuard(logicalContext: LogicalContext, state: ProcessingState): ProcessingState =
guard().fold(state) { st, prd ->
if (st.operational) askPredicate(prd, logicalContext, st) else st
private fun processMatch(inState: ProcessingState, match: MatchRule) : ProcessingState {
val context = Context(inState, match.logicalContext())
trace.trying(match)
// invoke matched pattern predicates
for (prd in match.patternPredicates()) {
if (!tellPredicate(prd, context)) break
}
private fun askPredicate(predicate: Predicate,
logicalContext: LogicalContext,
instate: ProcessingState): ProcessingState
{
return profiler.profile<ProcessingState>("ask_${predicate.symbol()}", {
// check guard
for (gprd in match.rule().guard()) {
if (!askPredicate(gprd, context)) break
}
try {
if(session.sessionSolver().ask(session.invocation(predicate, logicalContext))) {
instate
context.updateState { state ->
when (state) {
is ABORTED -> { // guard is not satisfied
trace.reject(match)
return state.recover()
}
is FAILED -> { // guard failed
trace.failure(state.failure)
return state.recover()
}
else -> state
}
}
this.dispatchFringe = dispatchFringe.consume(match)
trace.trigger(match)
for (occ in match.matchHeadReplaced()) {
this.dispatchFringe = dispatchFringe.contract(occ)
frameStack.current.store.discard(occ)
trace.discard(occ)
}
val altIt = match.rule().bodyAlternation().iterator()
while (altIt.hasNext()) {
val body = altIt.next()
context.updateState { state ->
if (state is FAILED) {
trace.retry(match)
state.recover()
} else {
instate.abort()
state
}
}
val savedFrame = frameStack.current
frameStack.push()
for (item in body) {
val itemOk = when (item) {
is Constraint -> activateConstraint(item, context)
is Predicate -> tellPredicate(item, context)
else -> throw IllegalArgumentException("unknown item ${item}")
}
} catch (ex: EvaluationFailureException) {
instate.fail(EvaluationFailure(ex))
if (itemOk) {
context.withState { state ->
if (feedbackHandler != null && state.feedback?.alreadyHandled() == false) {
state.feedback.handle(match.rule(), feedbackHandler)
}
}
} else {
// state is not operational after constraint/predicate processing
break
}
}
})
val altOk = context.updateState { state ->
if (state is FAILED) {
trace.failure(state.failure)
if (altIt.hasNext()) {
// clear the failure handled status
state.failure.handle(match.rule()) { _, _ -> true }
state
} else if (feedbackHandler != null && state.feedback?.alreadyHandled() == false &&
state.failure.handle(match.rule(), feedbackHandler))
{
state.recover()
} else {
state
}
} else {
state
}
}
if (!altOk) {
// all constraints activated up to a failure are lost
frameStack.reset(savedFrame)
} else {
// body finished normally
break
}
}
trace.finish(match)
return context.currentState()
}
private fun tellPredicate(predicate: Predicate,
logicalContext: LogicalContext,
instate: ProcessingState) : ProcessingState
{
return profiler.profile<ProcessingState>("tell_${predicate.symbol()}") {
private fun activateConstraint(constraint: Constraint, context: Context) : Boolean {
val args = program.instantiateArguments(constraint.arguments(), context.logicalContext)
return context.updateState { state ->
process(constraint.occurrence(context.logicalContext, args, frameStack), state)
}
}
try {
session.sessionSolver().tell(session.invocation(predicate, logicalContext))
instate
private fun askPredicate(predicate: Predicate, context: Context) : Boolean =
profiler.profile<Boolean>("ask_${predicate.symbol()}") {
context.evalSafe { state ->
val args = program.instantiateArguments(predicate.arguments(), context.logicalContext)
if (session.sessionSolver().ask(predicate.invocation(args, context.logicalContext, context)))
state
else
state.abort(DetailedFeedback("predicate not satisfied"))
}
}
} catch (ex: EvaluationFailureException) {
instate.fail(EvaluationFailure(ex))
private fun tellPredicate(predicate: Predicate, context: Context) : Boolean =
profiler.profile<Boolean>("tell_${predicate.symbol()}") {
context.runSafe {
val args = program.instantiateArguments(predicate.arguments(), context.logicalContext)
session.sessionSolver().tell(predicate.invocation(args, context.logicalContext, context))
}
}
}
private val noLogicalContext: LogicalContext = object: LogicalContext {
override fun <V : Any> variable(metaLogical: MetaLogical<V>): Logical<V> = TODO()
@ -238,4 +309,5 @@ class Controller(
it.first.patternPredicates(it.second.arguments())
}.toList()
private fun MatchRule.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> co.isStored() }
}

View File

@ -131,15 +131,11 @@ class EvaluationSessionImpl private constructor (
override fun sessionSolver(): SessionSolver = sessionSolver
override fun program(): Program = program
override fun storeView(): StoreView =
controller.storeView()
override fun invocation(predicate: Predicate, logicalContext: LogicalContext): PredicateInvocation =
predicate.invocation(program.instantiateArguments(predicate.arguments(), logicalContext), logicalContext)
override fun occurrence(constraint: Constraint, logicalContext: LogicalContext): ConstraintOccurrence =
constraint.occurrence(controller, program.instantiateArguments(constraint.arguments(), logicalContext), logicalContext)
private class Backend : EvaluationSession.Backend {
val ourSession = ThreadLocal<EvaluationSessionImpl>()

View File

@ -16,6 +16,7 @@
package jetbrains.mps.logic.reactor.core
import jetbrains.mps.logic.reactor.evaluation.InvocationContext
import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.program.Predicate
@ -24,16 +25,22 @@ import jetbrains.mps.logic.reactor.program.Predicate
* @author Fedor Isakov
*/
fun Predicate.invocation(arguments: List<*>, logicalContext: LogicalContext): PredicateInvocation =
Invocation(this, arguments, logicalContext)
fun Predicate.invocation(arguments: List<*>,
logicalContext: LogicalContext,
invocationContext: InvocationContext): PredicateInvocation =
Invocation(this, arguments, logicalContext, invocationContext)
private data class Invocation(val predicate: Predicate,
val invocationArguments: List<*>,
val logicalContext: LogicalContext) : PredicateInvocation {
val logicalContext: LogicalContext,
val invocationContext: InvocationContext) : PredicateInvocation
{
override fun predicate(): Predicate = predicate
override fun arguments(): List<*> = invocationArguments
override fun logicalContext(): LogicalContext = logicalContext
override fun invocationContext(): InvocationContext = invocationContext
}

View File

@ -53,4 +53,4 @@ class MatchRuleImpl(val origin: Any,
override fun logicalContext(): LogicalContext = logicalContext
}
}

View File

@ -26,13 +26,13 @@ import jetbrains.mps.logic.reactor.program.Constraint
* @author Fedor Isakov
*/
fun Constraint.occurrence(controller: Controller, arguments: List<*>, logicalContext: LogicalContext): ConstraintOccurrence =
Occurrence({ controller.currentFrame() }, this, arguments, logicalContext)
internal fun Constraint.occurrence(logicalContext: LogicalContext, arguments: List<*>, frameStack: FrameStack): ConstraintOccurrence =
Occurrence(this, logicalContext, arguments, frameStack)
private data class Occurrence (val currentFrame: () -> Frame,
val constraint: Constraint,
private data class Occurrence (val constraint: Constraint,
val logicalContext: LogicalContext,
val arguments: List<*>,
val logicalContext: LogicalContext) :
val frameStack: FrameStack) :
ConstraintOccurrence,
LogicalObserver,
StoreItem
@ -43,7 +43,7 @@ private data class Occurrence (val currentFrame: () -> Frame,
init {
for (a in arguments) {
if (a is Logical<*>) {
currentFrame().addObserver(a) { this }
frameStack.current.addObserver(a) { this }
}
}
}
@ -69,7 +69,7 @@ private data class Occurrence (val currentFrame: () -> Frame,
override fun terminate() {
for (a in arguments) {
if (a is Logical<*>) {
currentFrame().removeObserver(a) { this }
frameStack.current.removeObserver(a) { this }
}
}
alive = false

View File

@ -16,36 +16,56 @@
package jetbrains.mps.logic.reactor.core
import jetbrains.mps.logic.reactor.evaluation.CompositeFeedback
import jetbrains.mps.logic.reactor.evaluation.DetailedFeedback
import jetbrains.mps.logic.reactor.evaluation.EvaluationFailure
import jetbrains.mps.logic.reactor.evaluation.EvaluationFeedback
abstract class ProcessingState() {
abstract class ProcessingState(val feedback : EvaluationFeedback?) {
abstract val operational : Boolean
inline fun eval(block: (ProcessingState) -> ProcessingState): ProcessingState =
if (operational) block(this) else this
// inline fun eval(block: (ProcessingState) -> ProcessingState): ProcessingState =
// if (operational) block(this) else this
/** Stop what is being done because a required condition is not satisfied. */
fun abort() : ProcessingState = ABORTED(this)
/** Failure occurred during processing. */
fun fail(failure: EvaluationFailure) : ProcessingState = FAILED(failure, this)
/** Reset after a failure or a cancellation, bring the state back to operational. */
abstract fun reset() : ProcessingState
open fun abort(details: EvaluationFeedback) : ProcessingState = throw IllegalStateException()
class NORMAL : ProcessingState() {
/** Failure occurred during processing. */
open fun fail(failure: EvaluationFailure) : ProcessingState = throw IllegalStateException()
/** Provide detailed feedback. */
open fun report(details: DetailedFeedback) : ProcessingState = throw IllegalStateException()
/** Move back to normal. */
open fun recover() : ProcessingState = throw IllegalStateException()
class NORMAL(feedback: EvaluationFeedback? = null) : ProcessingState(feedback) {
override val operational = true
override fun reset(): ProcessingState = this
override fun abort(details: EvaluationFeedback): ProcessingState = ABORTED(this, details)
override fun fail(failure: EvaluationFailure): ProcessingState = FAILED(this, failure)
override fun report(details: DetailedFeedback): ProcessingState = NORMAL(compose(this.feedback, details))
}
class FAILING(failed: FAILED) : ProcessingState() {
override val operational = true
override fun reset(): ProcessingState = this
}
class FAILED(val failure: EvaluationFailure, val going: ProcessingState) : ProcessingState() {
class FAILED(state: ProcessingState, val failure: EvaluationFailure) : ProcessingState(compose(state.feedback, failure)) {
override val operational = false
override fun reset(): ProcessingState = FAILING(this)
/** Recover after a failure or a cancellation, bring the state back to operational. */
override fun recover(): ProcessingState = NORMAL(feedback)
}
class ABORTED(val going: ProcessingState) : ProcessingState() {
class ABORTED(state: ProcessingState, val reason: EvaluationFeedback) : ProcessingState(compose(state.feedback, reason)) {
override val operational = false
override fun reset(): ProcessingState = going
override fun recover(): ProcessingState = NORMAL(feedback)
}
}
}
internal fun compose(left: EvaluationFeedback?, right: EvaluationFeedback?) = CompositeFeedback.of(left, right)
data class ReportMessage(val severity: Severity, val message: String)
enum class Severity(val level: Int) {
INFO(0),
WARNING(1),
ERROR(2),
FATAL(3)
}

View File

@ -66,8 +66,6 @@ class MockHandler(
override fun name(): String = name
override fun primarySymbols(): Iterable<ConstraintSymbol> = primary
override fun rules(): Iterable<Rule> = rules
}
@ -88,8 +86,6 @@ class MockRule(
override fun guard(): Iterable<Predicate> = guard
override fun body(): Iterable<AndItem> = body.first()
override fun bodyAlternation(): Iterable<Iterable<AndItem>> = body
override fun all(): Iterable<AndItem> =

View File

@ -1,9 +1,7 @@
package solver
import jetbrains.mps.logic.reactor.evaluation.AbstractSolver
import jetbrains.mps.logic.reactor.evaluation.EvaluationFailureException
import jetbrains.mps.logic.reactor.evaluation.EvaluationSession
import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation
import jetbrains.mps.logic.reactor.core.invocation
import jetbrains.mps.logic.reactor.evaluation.*
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.logical.MetaLogical
@ -106,11 +104,23 @@ class EqualsSolver : AbstractSolver() {
}
infix fun <T : Any> T.is_eq(value: T): Boolean = EvaluationSession.current().let { session ->
session.sessionSolver().ask(session.invocation(EqualsSolver.eq(this, value), mockLogicalContext()))
val logicalContext = mockLogicalContext()
val predicate = EqualsSolver.eq(this, value)
val args = session.program().instantiateArguments(predicate.arguments(), logicalContext)
val inv = predicate.invocation(args, logicalContext, object : InvocationContext {
override fun report(feedback: EvaluationFeedback) = TODO()
})
session.sessionSolver().ask(inv)
}
infix fun <T : Any> T.eq(value: T) = EvaluationSession.current().let { session ->
session.sessionSolver().tell(session.invocation(EqualsSolver.eq(this, value), mockLogicalContext()))
val logicalContext = mockLogicalContext()
val predicate = EqualsSolver.eq(this, value)
val args = session.program().instantiateArguments(predicate.arguments(), logicalContext)
val inv = predicate.invocation(args, logicalContext, object : InvocationContext {
override fun report(feedback: EvaluationFeedback) = TODO()
})
session.sessionSolver().tell(inv)
}
private fun mockLogicalContext(): LogicalContext {

View File

@ -13,61 +13,61 @@ import java.util.*
*/
fun expression(body: () -> Boolean): ConjBuilder.() -> Unit = {
fun expression(body: PredicateInvocation.() -> Boolean): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(0).withCode(body))
}
fun <X, LX: Logical<X>, LPX: MetaLogical<X>>
expression(body: (LX) -> Boolean, x: LPX): ConjBuilder.() -> Unit = {
expression(body: PredicateInvocation.(LX) -> Boolean, x: LPX): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(1).withCode(body, x))
}
fun <X, LX: Logical<X>, LPX: MetaLogical<X>,
Y, LY: Logical<Y>, LPY: MetaLogical<Y>>
expression(body: (LX, LY) -> Boolean, x: LPX, y: LPY): ConjBuilder.() -> Unit = {
expression(body: PredicateInvocation.(LX, LY) -> Boolean, x: LPX, y: LPY): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(2).withCode(body, x, y))
}
fun <X, LX: Logical<X>, LPX: MetaLogical<X>,
Y, LY: Logical<Y>, LPY: MetaLogical<Y>,
Z, LZ: Logical<Z>, LPZ: MetaLogical<Z>>
expression(body: (LX, LY, LZ) -> Boolean, x: LPX, y: LPY, z: LPZ): ConjBuilder.() -> Unit = {
expression(body: PredicateInvocation.(LX, LY, LZ) -> Boolean, x: LPX, y: LPY, z: LPZ): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(3).withCode(body, x, y, z))
}
fun statement(body: () -> Unit): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(0).withCode { body.invoke(); true })
fun statement(body: PredicateInvocation.() -> Unit): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(0).withCode { body(); true })
}
fun <X, LPX: MetaLogical<X>>
statement(
body: (Logical<X>) -> Unit, x: LPX): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(1).withCode({ x -> body.invoke(x); true }, x))
body: PredicateInvocation.(Logical<X>) -> Unit, x: LPX): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(1).withCode({ x -> body(x); true }, x))
}
fun <X, LPX: MetaLogical<X>,
Y, LPY: MetaLogical<Y>>
statement(body: (Logical<X>, Logical<Y>) -> Unit, x: LPX, y: LPY): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(2).withCode({ x, y -> body.invoke(x, y); true }, x, y))
statement(body: PredicateInvocation.(Logical<X>, Logical<Y>) -> Unit, x: LPX, y: LPY): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(2).withCode({ x, y -> body(x, y); true }, x, y))
}
fun <X, LPX: MetaLogical<X>,
Y, LPY: MetaLogical<Y>,
Z, LPZ: MetaLogical<Z>>
statement(body: (Logical<X>, Logical<Y>, Logical<Z>) -> Unit, x: LPX, y: LPY, z: LPZ): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(3).withCode({ x, y, z -> body.invoke(x, y, z); true }, x, y, z))
statement(body: PredicateInvocation.(Logical<X>, Logical<Y>, Logical<Z>) -> Unit, x: LPX, y: LPY, z: LPZ): ConjBuilder.() -> Unit = {
add(JavaPredicateSymbol.withArity(3).withCode({ x, y, z -> body(x, y, z); true }, x, y, z))
}
class ExpressionSolver : AbstractSolver() {
override fun ask(invocation: PredicateInvocation): Boolean {
return javaPredicates[invocation.arguments().get(0)]?.expr?.invoke(invocation.arguments().drop(1)) ?:
return javaPredicates[invocation.arguments().get(0)]?.expr?.invoke(invocation, invocation.arguments().drop(1)) ?:
ERROR("no such symbol ${invocation.predicate().symbol()}")
}
override fun tell(invocation: PredicateInvocation) {
when (invocation.predicate().symbol()) {
is JavaPredicateSymbol -> javaPredicates[invocation.arguments().get(0)]?.expr?.invoke(invocation.arguments().drop(1))
is JavaPredicateSymbol -> javaPredicates[invocation.arguments().get(0)]?.expr?.invoke(invocation, invocation.arguments().drop(1))
else -> ERROR("uknown symbol ${invocation.predicate().symbol()}")
}
}
@ -88,7 +88,7 @@ class ExpressionSolver : AbstractSolver() {
}
interface JavaExpression {
fun invoke(args: List<*>): Boolean
fun invoke(invocation: PredicateInvocation, args: List<*>): Boolean
}
data class TestJavaPredicate(val symbol: JavaPredicateSymbol, val expr: JavaExpression, val args: List<*>) : Predicate {
@ -99,49 +99,49 @@ data class TestJavaPredicate(val symbol: JavaPredicateSymbol, val expr: JavaExpr
}
private fun JavaPredicateSymbol.withCode(code: () -> Boolean) =
private fun JavaPredicateSymbol.withCode(code: PredicateInvocation.() -> Boolean) =
TestJavaPredicate(this, JavaExpression0(code), listOf(System.identityHashCode(code)))
private fun <X, LX: Logical<X>, LPX: MetaLogical<X>>
JavaPredicateSymbol.withCode(code: (LX) -> Boolean, x: LPX) =
JavaPredicateSymbol.withCode(code: PredicateInvocation.(LX) -> Boolean, x: LPX) =
TestJavaPredicate(this, JavaExpression1(code), listOf(System.identityHashCode(code), x))
private fun <X, LX: Logical<X>, LPX: MetaLogical<X>,
Y, LY: Logical<Y>, LPY: MetaLogical<Y>>
JavaPredicateSymbol.withCode(code: (LX, LY) -> Boolean, x: LPX, y: LPY) =
JavaPredicateSymbol.withCode(code: PredicateInvocation.(LX, LY) -> Boolean, x: LPX, y: LPY) =
TestJavaPredicate(this, JavaExpression2(code), listOf(System.identityHashCode(code), x, y))
private fun <X, LX: Logical<X>, LPX: MetaLogical<X>,
Y, LY: Logical<Y>, LPY: MetaLogical<Y>,
Z, LZ: Logical<Z>, LPZ: MetaLogical<Z>>
JavaPredicateSymbol.withCode(code: (LX, LY, LZ) -> Boolean, x: LPX, y: LPY, z: LPZ) =
JavaPredicateSymbol.withCode(code: PredicateInvocation.(LX, LY, LZ) -> Boolean, x: LPX, y: LPY, z: LPZ) =
TestJavaPredicate(this, JavaExpression3(code), listOf(System.identityHashCode(code), x, y, z))
private class JavaExpression0(val code: () -> Boolean) : JavaExpression {
override fun invoke(args: List<*>): Boolean {
private class JavaExpression0(val code: PredicateInvocation.() -> Boolean) : JavaExpression {
override fun invoke(invocation: PredicateInvocation, args: List<*>): Boolean {
if (args.size != 0) throw IllegalArgumentException("arity mismatch")
return code()
return invocation.code()
}
}
private class JavaExpression1<X>(val code: (X) -> Boolean) : JavaExpression {
override fun invoke(args: List<*>): Boolean {
private class JavaExpression1<X>(val code: PredicateInvocation.(X) -> Boolean) : JavaExpression {
override fun invoke(invocation: PredicateInvocation, args: List<*>): Boolean {
if (args.size != 1) throw IllegalArgumentException("arity mismatch")
return code(args[0] as X)
return invocation.code(args[0] as X)
}
}
private class JavaExpression2<X,Y>(val code: (X, Y) -> Boolean) : JavaExpression {
override fun invoke(args: List<*>): Boolean {
private class JavaExpression2<X,Y>(val code: PredicateInvocation.(X, Y) -> Boolean) : JavaExpression {
override fun invoke(invocation: PredicateInvocation, args: List<*>): Boolean {
if (args.size != 2) throw IllegalArgumentException("arity mismatch")
return code(args[0] as X, args[1] as Y)
return invocation.code(args[0] as X, args[1] as Y)
}
}
private class JavaExpression3<X,Y,Z>(val code: (X, Y, Z) -> Boolean) : JavaExpression {
override fun invoke(args: List<*>): Boolean {
private class JavaExpression3<X,Y,Z>(val code: PredicateInvocation.(X, Y, Z) -> Boolean) : JavaExpression {
override fun invoke(invocation: PredicateInvocation, args: List<*>): Boolean {
if (args.size != 3) throw IllegalArgumentException("arity mismatch")
return code(args[0] as X, args[1] as Y, args[2] as Z)
return invocation.code(args[0] as X, args[1] as Y, args[2] as Z)
}
}

View File

@ -1,5 +1,6 @@
import jetbrains.mps.logic.reactor.core.StoreItem
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
import jetbrains.mps.logic.reactor.evaluation.EvaluationFeedbackHandler
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.program.*
import program.MockConstraint

View File

@ -1,9 +1,7 @@
import jetbrains.mps.logic.reactor.core.*
import jetbrains.mps.logic.reactor.evaluation.*
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.program.*
import jetbrains.mps.logic.reactor.util.cons
import jetbrains.mps.unification.Term
import jetbrains.mps.unification.test.MockTerm.*
import org.junit.After
@ -34,6 +32,7 @@ class TestController {
lateinit var controller: Controller
override fun controller(): Controller = controller
override fun sessionSolver(): SessionSolver = solver
override fun program(): Program = program
override fun storeView(): StoreView = TODO()
class MockBackend(val session: MockSession) : Backend {
@ -41,12 +40,6 @@ class TestController {
override fun createConfig(program: Program): Config = TODO()
}
override fun invocation(predicate: Predicate, logicalContext: LogicalContext): PredicateInvocation =
predicate.invocation(program.instantiateArguments(predicate.arguments(), logicalContext), logicalContext)
override fun occurrence(constraint: Constraint, logicalContext: LogicalContext): ConstraintOccurrence =
constraint.occurrence(controller, program.instantiateArguments(constraint.arguments(), logicalContext), logicalContext)
companion object {
lateinit var ourBackend : MockBackend
@ -74,6 +67,22 @@ class TestController {
return controller
}
private fun Builder.controllerWithFeedback(feedbackHandler: EvaluationFeedbackHandler,
vararg occurrences: ConstraintOccurrence): Controller
{
val solver = sessionSolver(env.expressionSolver, env.equalsSolver)
val program = MockProgram("test", handlers, registry = MockConstraintRegistry(solver))
MockSession.init(program, solver)
val controller = Controller(program, storeView = MockStoreView(listOf(* occurrences)), feedbackHandler = feedbackHandler)
MockSession.ourBackend.session.controller = controller
return controller
}
private fun detailFeedback(msg: String): DetailedFeedback = object : DetailedFeedback(msg) {
override fun toString(): String = "{" + msg + "}"
}
private class MockStoreView(val occurrences: List<ConstraintOccurrence>) : StoreView {
val symbols = occurrences.map { it.constraint().symbol() }.toSet()
@ -807,5 +816,143 @@ class TestController {
}
}
}
@Test(expected = EvaluationFailureException::class)
fun failureHandler() {
val failureHandler = object : FailureHandler {
val failures = ArrayList<Pair<EvaluationFailure, String>>()
override fun handleFailure(failure: EvaluationFailure, rule: Rule): EvaluationFailure? {
failures.add(failure to rule.tag())
return failure
}
}
programWithRules(
rule("main",
headReplaced(constraint("main")),
body (
constraint("foo")
)
),
rule("rule1",
headReplaced(constraint("foo")),
body(
constraint("yes"),
constraint("bar"),
constraint("no")
)
),
rule("rule2",
headReplaced(constraint("bar")),
body(
statement { throw EvaluationFailureException("unhandled") }
)
)
).controllerWithFeedback(failureHandler).run {
try {
evaluate(occurrence("main"))
} finally {
failureHandler.failures.map { (f, t) -> "${f.cause.message}@${t}" }.toList() shouldBe
listOf("unhandled@rule2", "unhandled@rule1", "unhandled@main")
}
}
}
@Test
fun failureHandlerRecover() {
val failureHandler = object : FailureHandler {
val failures = ArrayList<Pair<EvaluationFailure, String>>()
override fun handleFailure(failure: EvaluationFailure, rule: Rule): EvaluationFailure? {
failures.add(failure to rule.tag())
if (rule.tag()?.startsWith("recoverable") == true) return null
return failure
}
}
programWithRules(
rule("main",
headReplaced(constraint("main")),
body (
constraint("foo")
)
),
rule("rule1",
headReplaced(constraint("foo")),
body(
statement {
// this failure does not propagate because of the alt body branch
throw EvaluationFailureException("unhandled")
}
),
altBody(
constraint("recovered", 1),
constraint("bar"),
constraint("recovered", 2)
)
),
rule ("recoverable",
headReplaced(constraint("bar")),
body(
constraint("bazz")
)
),
rule("rule3",
headReplaced(constraint("bazz")),
body(
statement { throw EvaluationFailureException("handled") }
)
)
).controllerWithFeedback(failureHandler).run {
evaluate(occurrence("main"))
storeView().run {
val recovered = ConstraintSymbol("recovered", 1)
constraintSymbols() shouldBe setOf(recovered)
occurrences(recovered).map { it.arguments()[0] }.toSet() shouldBe setOf(1, 2)
}
}
failureHandler.failures.map { (f, t) -> "${f.cause.message}@$t"}.toList() shouldBe
listOf("handled@rule3", "handled@recoverable")
}
@Test
fun detailsFeedbackHandler() {
val feedbackHandler = object : EvaluationFeedbackHandler {
val feedbacks = arrayListOf<Pair<EvaluationFeedback, String>>()
override fun handleFeedback(rule: Rule, feedback: EvaluationFeedback): Boolean {
feedbacks.add(feedback to rule.tag())
return feedback.message.startsWith("catchme")
}
}
programWithRules(
rule("main",
headReplaced(constraint("main")),
body (
constraint("foo")
)
),
rule("rule1",
headReplaced(constraint("foo")),
body(
statement { invocationContext().report(detailFeedback("catchme")) },
constraint("bar")
)
),
rule("rule2",
headReplaced(constraint("bar")),
body(
statement { invocationContext().report(detailFeedback("propagateme")) }
)
)
).controllerWithFeedback(feedbackHandler).run {
evaluate(occurrence("main"))
}
feedbackHandler.feedbacks.map { (f, t) -> "${f.message}@$t"}.toList() shouldBe
listOf("catchme@rule1", "propagateme@rule2", "propagateme@rule1", "propagateme@main")
}
}