From c8d7e52b6fb2623f38179ae99c1c51d8501f9186 Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Thu, 14 Feb 2019 15:02:05 +0100 Subject: [PATCH] Introduce feedback handling API to facilitate processing of messages during program evaluation. Minor refactorings in conreactor. Drop deprecated stuff. Default implementations for deprecated methods. --- .../reactor/evaluation/CompositeFeedback.java | 132 +++++++ .../reactor/evaluation/DetailedFeedback.java | 57 ++++ .../reactor/evaluation/EvaluationFailure.java | 25 +- .../evaluation/EvaluationFeedback.java | 83 +++++ .../evaluation/EvaluationFeedbackHandler.java | 32 ++ .../reactor/evaluation/EvaluationSession.java | 21 +- .../reactor/evaluation/FailureHandler.java | 12 +- .../reactor/evaluation/InvocationContext.java | 26 ++ .../evaluation/PredicateInvocation.java | 6 + .../reactor/evaluation/SessionSolver.java | 24 +- .../reactor/logical/JoinableLogical.java | 1 + .../mps/logic/reactor/program/Handler.java | 4 +- .../mps/logic/reactor/program/Rule.java | 4 +- .../mps/logic/reactor/core/Controller.kt | 322 +++++++++++------- .../reactor/core/EvaluationSessionImpl.kt | 8 +- .../mps/logic/reactor/core/Invocation.kt | 15 +- .../mps/logic/reactor/core/MatchRuleImpl.kt | 2 +- .../mps/logic/reactor/core/Occurrence.kt | 14 +- .../mps/logic/reactor/core/ProcessingState.kt | 58 ++-- reactor/Test/src/program/MockProgram.kt | 4 - reactor/Test/src/solver/EqualsSolver.kt | 22 +- reactor/Test/test/JavaExpressionHelper.kt | 62 ++-- reactor/Test/test/RulesHelper.kt | 1 + reactor/Test/test/TestController.kt | 163 ++++++++- 24 files changed, 854 insertions(+), 244 deletions(-) create mode 100644 reactor/API/src/jetbrains/mps/logic/reactor/evaluation/CompositeFeedback.java create mode 100644 reactor/API/src/jetbrains/mps/logic/reactor/evaluation/DetailedFeedback.java create mode 100644 reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedback.java create mode 100644 reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedbackHandler.java create mode 100644 reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/CompositeFeedback.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/CompositeFeedback.java new file mode 100644 index 00000000..f29f58fb --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/CompositeFeedback.java @@ -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 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 elements() { + return elements; + } + + @Override + public String toString() { + return elements.toString(); + } + + private CompositeFeedback composeLeft(EvaluationFeedback left) { + ArrayList 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 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 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 efs) { + StringBuilder sb = new StringBuilder(); + String sep = ""; + for(EvaluationFeedback ef : efs) { + sb.append(sep).append(ef.getMessage()); + sep = "; "; + } + return sb.toString(); + } + + private final List elements; + private final Severity severity; +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/DetailedFeedback.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/DetailedFeedback.java new file mode 100644 index 00000000..3e72694f --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/DetailedFeedback.java @@ -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; + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailure.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailure.java index e68b50a2..87395ddc 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailure.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailure.java @@ -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; - } diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedback.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedback.java new file mode 100644 index 00000000..62d3845e --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedback.java @@ -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; +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedbackHandler.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedbackHandler.java new file mode 100644 index 00000000..6956eecb --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFeedbackHandler.java @@ -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); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java index 7e880c34..de2f16e3 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java @@ -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 { diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/FailureHandler.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/FailureHandler.java index c53219a2..2d6f9ff4 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/FailureHandler.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/FailureHandler.java @@ -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; + } } diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java new file mode 100644 index 00000000..18569c2f --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java @@ -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); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java index 1b0def4b..4cc275bd 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java @@ -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(); + }; + } diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java index 02ff5275..24049125 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java @@ -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) { diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java index 3db39328..ad46740f 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java @@ -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 the value type diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java index 5aaaa16f..a7997b19 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java @@ -29,7 +29,9 @@ public abstract class Handler { public abstract String name(); @Deprecated - public abstract Iterable primarySymbols(); + public Iterable primarySymbols() { + throw new UnsupportedOperationException(); + } public abstract Iterable rules(); diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java index e00405ae..788c2cbc 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java @@ -35,7 +35,9 @@ public abstract class Rule { public abstract Iterable guard(); @Deprecated - public abstract Iterable body(); + public Iterable body() { + throw new UnsupportedOperationException(); + } public abstract Iterable> bodyAlternation(); diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt index df808d11..9e6170e5 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt @@ -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("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("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("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("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("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 variable(metaLogical: MetaLogical): Logical = TODO() @@ -238,4 +309,5 @@ class Controller( it.first.patternPredicates(it.second.arguments()) }.toList() + private fun MatchRule.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> co.isStored() } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt index 8c305064..2d6efaef 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt @@ -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() diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt index ca11984a..ebd35da4 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt @@ -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 + } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchRuleImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchRuleImpl.kt index a6c59653..6aeb02a4 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchRuleImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchRuleImpl.kt @@ -53,4 +53,4 @@ class MatchRuleImpl(val origin: Any, override fun logicalContext(): LogicalContext = logicalContext -} \ No newline at end of file +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt index 615bc651..b1796259 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt @@ -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 diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt index 1ed05a85..3b678384 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt @@ -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) } -} \ No newline at end of file +} + +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) +} diff --git a/reactor/Test/src/program/MockProgram.kt b/reactor/Test/src/program/MockProgram.kt index 365e8348..3ea7fa1d 100644 --- a/reactor/Test/src/program/MockProgram.kt +++ b/reactor/Test/src/program/MockProgram.kt @@ -66,8 +66,6 @@ class MockHandler( override fun name(): String = name - override fun primarySymbols(): Iterable = primary - override fun rules(): Iterable = rules } @@ -88,8 +86,6 @@ class MockRule( override fun guard(): Iterable = guard - override fun body(): Iterable = body.first() - override fun bodyAlternation(): Iterable> = body override fun all(): Iterable = diff --git a/reactor/Test/src/solver/EqualsSolver.kt b/reactor/Test/src/solver/EqualsSolver.kt index 58a0243d..0232f45d 100644 --- a/reactor/Test/src/solver/EqualsSolver.kt +++ b/reactor/Test/src/solver/EqualsSolver.kt @@ -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.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.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 { diff --git a/reactor/Test/test/JavaExpressionHelper.kt b/reactor/Test/test/JavaExpressionHelper.kt index 5e7dcad3..17a21868 100644 --- a/reactor/Test/test/JavaExpressionHelper.kt +++ b/reactor/Test/test/JavaExpressionHelper.kt @@ -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 , LPX: MetaLogical> - 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 , LPX: MetaLogical, Y, LY: Logical, LPY: MetaLogical> - 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 , LPX: MetaLogical, Y, LY: Logical, LPY: MetaLogical, Z, LZ: Logical, LPZ: MetaLogical> - 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 > statement( - body: (Logical) -> Unit, x: LPX): ConjBuilder.() -> Unit = { - add(JavaPredicateSymbol.withArity(1).withCode({ x -> body.invoke(x); true }, x)) + body: PredicateInvocation.(Logical) -> Unit, x: LPX): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(1).withCode({ x -> body(x); true }, x)) } fun , Y, LPY: MetaLogical> - statement(body: (Logical, Logical) -> 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, Logical) -> Unit, x: LPX, y: LPY): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(2).withCode({ x, y -> body(x, y); true }, x, y)) } fun , Y, LPY: MetaLogical, Z, LPZ: MetaLogical> - statement(body: (Logical, Logical, Logical) -> 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, Logical, Logical) -> 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 , LPX: MetaLogical> - 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 , LPX: MetaLogical, Y, LY: Logical, LPY: MetaLogical> - 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 , LPX: MetaLogical, Y, LY: Logical, LPY: MetaLogical, Z, LZ: Logical, LPZ: MetaLogical> - 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(val code: (X) -> Boolean) : JavaExpression { - override fun invoke(args: List<*>): Boolean { +private class JavaExpression1(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(val code: (X, Y) -> Boolean) : JavaExpression { - override fun invoke(args: List<*>): Boolean { +private class JavaExpression2(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(val code: (X, Y, Z) -> Boolean) : JavaExpression { - override fun invoke(args: List<*>): Boolean { +private class JavaExpression3(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) } } diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index 39af8f02..353bd85b 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -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 diff --git a/reactor/Test/test/TestController.kt b/reactor/Test/test/TestController.kt index 3aa902a0..7170d993 100644 --- a/reactor/Test/test/TestController.kt +++ b/reactor/Test/test/TestController.kt @@ -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) : 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>() + 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>() + 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>() + 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") + } }