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:
parent
a17d36fe94
commit
c8d7e52b6f
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,4 +53,4 @@ class MatchRuleImpl(val origin: Any,
|
|||
|
||||
override fun logicalContext(): LogicalContext = logicalContext
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> =
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue