From a9a7f66a3a620e81a78d38a92e20a62fcafd256d Mon Sep 17 00:00:00 2001 From: Fedor Isakov Date: Wed, 24 Apr 2019 20:23:57 +0200 Subject: [PATCH] Refactorings and code cleanup in reactor lib. Documenting the code. Breaking change: ReactorLifecycle to be used to initialize session backend. Remove deprecated stuff, deprecate some more. Consolidate useless interfaces into bigger ones. Extract interfaces for core classes, move implementation into "internal" subpackage. --- .../reactor/evaluation/EvaluationSession.java | 51 +- .../reactor/evaluation/InvocationContext.java | 3 + .../logic/reactor/evaluation/MatchRule.java | 2 +- .../reactor/evaluation/SessionSolver.java | 32 +- .../mps/logic/reactor/program/Handler.java | 8 - .../mps/logic/reactor/core/Controller.kt | 304 +---------- .../mps/logic/reactor/core/Dispatcher.kt | 17 +- .../logic/reactor/core/EvaluationSessionEx.kt | 52 ++ .../mps/logic/reactor/core/FrameObservable.kt | 33 ++ .../mps/logic/reactor/core/Invocation.kt | 16 +- .../mps/logic/reactor/core/LogicalObserver.kt | 45 ++ .../mps/logic/reactor/core/MatchingProbe.kt | 20 +- .../mps/logic/reactor/core/Occurrence.kt | 44 +- .../logic/reactor/core/OccurrenceMatcher.kt | 173 +----- .../logic/reactor/core/ReactorLifecycle.kt} | 23 +- .../mps/logic/reactor/core/RuleIndex.kt | 3 +- .../mps/logic/reactor/core/RuleMatcher.kt | 171 +----- .../reactor/core/internal/ControllerImpl.kt | 332 +++++++++++ .../{ => internal}/EvaluationSessionImpl.kt | 97 ++-- .../reactor/core/{ => internal}/Frame.kt | 19 +- .../{Logical.kt => internal/LogicalImpl.kt} | 51 +- .../core/{ => internal}/MatchRuleImpl.kt | 21 +- .../core/internal/OccurrenceMatcherImpl.kt | 181 ++++++ .../core/{ => internal}/ProcessingState.kt | 4 +- .../ReteRuleMatcherImpl.kt} | 81 ++- .../reactor/core/internal/RuleMatcherImpl.kt | 187 +++++++ .../reactor/core/{ => internal}/Store.kt | 93 ++-- reactor/Test/src/program/MockProgram.kt | 2 +- reactor/Test/src/solver/EqualsSolver.kt | 4 +- reactor/Test/src/solver/MockSessionSolver.kt | 15 - reactor/Test/test/LogicalHelper.kt | 40 +- reactor/Test/test/RulesHelper.kt | 87 ++- reactor/Test/test/TestController.kt | 515 +++++++++--------- reactor/Test/test/TestLogical.kt | 11 +- reactor/Test/test/TestLogicalObserver.kt | 7 +- reactor/Test/test/TestProgram.kt | 89 ++- reactor/Test/test/TestProgramBuilder.kt | 7 +- reactor/Test/test/TestRuleMatcher.kt | 43 +- .../mps/unification/test/SolverTests.java | 57 +- 39 files changed, 1567 insertions(+), 1373 deletions(-) create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionEx.kt create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/FrameObservable.kt create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/LogicalObserver.kt rename reactor/{API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java => Core/src/jetbrains/mps/logic/reactor/core/ReactorLifecycle.kt} (56%) create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{ => internal}/EvaluationSessionImpl.kt (59%) rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{ => internal}/Frame.kt (86%) rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{Logical.kt => internal/LogicalImpl.kt} (85%) rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{ => internal}/MatchRuleImpl.kt (73%) create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/OccurrenceMatcherImpl.kt rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{ => internal}/ProcessingState.kt (97%) rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{ReteNetwork.kt => internal/ReteRuleMatcherImpl.kt} (84%) create mode 100644 reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt rename reactor/Core/src/jetbrains/mps/logic/reactor/core/{ => internal}/Store.kt (71%) delete mode 100644 reactor/Test/src/solver/MockSessionSolver.kt 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 e846cdc7..09fbb3b7 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java @@ -22,7 +22,6 @@ 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. @@ -35,7 +34,12 @@ import org.jetbrains.annotations.NotNull; */ public abstract class EvaluationSession { - private static EvaluationSession.Backend ourBackend; + private static EvaluationSession.Backend ourBackend; + + @SuppressWarnings("unchecked") + public static S current(Class sessionClass) { + return (S) current(); + } public static EvaluationSession current() { if (ourBackend == null) { @@ -51,47 +55,32 @@ public abstract class EvaluationSession { return ourBackend.createConfig(program); } - protected static void setBackend(EvaluationSession.Backend backend) { + protected static void setBackend(EvaluationSession.Backend backend) { if (ourBackend != null) { throw new IllegalStateException("backend already assigned"); } ourBackend = backend; } - protected static void clearBackend(EvaluationSession.Backend backend) { + protected static void clearBackend(EvaluationSession.Backend backend) { if (ourBackend != backend) { throw new IllegalStateException("illegal access"); } ourBackend = null; } + @Deprecated public abstract SessionSolver sessionSolver(); - public abstract StoreView storeView(); + public abstract Program program(); - @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 boolean ask(PredicateInvocation invocation); - @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 abstract void tell(PredicateInvocation invocation); - public Program program() { - // FIXME delete the implementation after all code has been migrated - // keep compatibility with existing code - throw new UnsupportedOperationException(); - } + public interface Backend { - protected interface Backend { - - EvaluationSession current(); + S current(); EvaluationSession.Config createConfig(Program program); @@ -99,11 +88,6 @@ public abstract class EvaluationSession { public static abstract class Config { - @Deprecated - public EvaluationSession.Config withPredicates(PredicateSymbol... predicateSymbols){ - return this; - } - public EvaluationSession.Config withTrace(EvaluationTrace computingTracer) { return this; } @@ -112,10 +96,6 @@ public abstract class EvaluationSession { return this; } - public EvaluationSession.Config withFailureHandler(FailureHandler handler) { - return this; - } - public EvaluationSession.Config withFeedbackHandler(EvaluationFeedbackHandler handler) { return this; } @@ -124,8 +104,11 @@ public abstract class EvaluationSession { return this; } + @Deprecated public abstract EvaluationResult start(SessionSolver sessionSolver); + public abstract EvaluationResult start(); + } } diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java index 18569c2f..9649c306 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/InvocationContext.java @@ -23,4 +23,7 @@ public interface InvocationContext { void report(EvaluationFeedback feedback); + + + } diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java index 0d94ac37..e60bdc65 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java @@ -21,7 +21,7 @@ import jetbrains.mps.logic.reactor.logical.LogicalContext; import jetbrains.mps.logic.reactor.program.Rule; /** - * A binding of a rule and the constraints that matched its head. + * A binding of a rule and the constraint occurrences that matched its head. * * @author Fedor Isakov */ 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 78b3e9be..454c6a1d 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java @@ -31,7 +31,9 @@ import java.util.Map; * * * @author Fedor Isakov + * @deprecated Use EvalutionSession */ +@Deprecated public class SessionSolver implements Solver { private EvaluationTrace tracer = EvaluationTrace.NULL; @@ -40,17 +42,13 @@ public class SessionSolver implements Solver { tracer = evaluationTrace; } - @Deprecated - public void init(PredicateSymbol... predicateSymbols) { - } - - @Deprecated - public void init(EvaluationTrace evaluationTrace, PredicateSymbol... predicateSymbols) { - tracer = evaluationTrace; - init(predicateSymbols); - } - + /** + * @deprecated Use EvaluationSession + * @param invocation + * @return + */ @Override + @Deprecated public boolean ask(PredicateInvocation invocation) { Solver solver = invocation.predicate().symbol().solver(); boolean result = solver.ask(invocation); @@ -58,20 +56,16 @@ public class SessionSolver implements Solver { return result; } + /** + * @deprecated Use EvaluationSession + * @param invocation + */ @Override + @Deprecated public void tell(PredicateInvocation invocation) { Solver solver = invocation.predicate().symbol().solver(); tracer.tell(invocation); solver.tell(invocation); } - @Deprecated - protected void registerSymbol(PredicateSymbol predicateSymbol, EvaluationTrace computingTracer) { - throw new UnsupportedOperationException(); - } - - @Deprecated - protected void registerSolver(PredicateSymbol constraint, AbstractSolver solver) { - } - } 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 a7997b19..21073763 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java @@ -19,20 +19,12 @@ package jetbrains.mps.logic.reactor.program; /** * A handler is a container of rules. *

- * If the {@code primarySymbols} is a non-empty collection, only constraints with these symbols are processed - * by this handler. - * * @author Fedor Isakov */ public abstract class Handler { public abstract String name(); - @Deprecated - public Iterable primarySymbols() { - throw new UnsupportedOperationException(); - } - public abstract Iterable rules(); } 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 33ef4df2..ff295eb7 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 JetBrains s.r.o. + * 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. @@ -16,299 +16,23 @@ package jetbrains.mps.logic.reactor.core -import jetbrains.mps.logic.reactor.core.ProcessingState.* -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 -import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.program.Predicate -import jetbrains.mps.logic.reactor.program.Program -import jetbrains.mps.logic.reactor.util.Profiler -import jetbrains.mps.logic.reactor.util.profile -import com.github.andrewoma.dexx.collection.Map as PersMap -class Controller( - val program: Program, - val trace: EvaluationTrace = EvaluationTrace.NULL, - val profiler: Profiler? = null, - val storeView: StoreView? = null, - val feedbackHandler: EvaluationFeedbackHandler? = null) -{ - private inner class Context(inState: ProcessingState, - val logicalContext: LogicalContext) : InvocationContext - { - private var state = inState +/** + * Allows access to the internal mechanics of the reactor. + * + * @author Fedor Isakov + */ +interface Controller { - fun currentState(): ProcessingState = state + fun reactivate(occ: Occurrence) - override fun report(feedback: EvaluationFeedback) { - when (feedback) { - is EvaluationFailure -> this.state = state.fail(feedback) - is DetailedFeedback -> this.state = state.report(feedback) - } - } + /** For tests only */ + fun evaluate(occ: Occurrence): StoreView - inline fun withState(block: (ProcessingState) -> Unit) { - block.invoke(state) - } + /** For tests only */ + fun storeView(): StoreView - 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 var dispatchFringe = Dispatcher(ruleIndex).fringe() - - // FIXME move to context - private val frameStack = FrameStack(storeView) - - fun storeView(): StoreView = frameStack.current.store.view() - - 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 - // TODO: introduce processing state to solver API? - val state = process(occurrence, NORMAL()) - if (state is FAILED) { - throw state.failure.cause - } - } - - /** For tests only */ - fun evaluate(occurrence: ConstraintOccurrence): StoreView { - val state = process(occurrence, NORMAL()) - if (state is FAILED) { - throw state.failure.cause - } - return storeView() - } - - private fun process(active: ConstraintOccurrence, inState: ProcessingState) : ProcessingState { - assert(active.isAlive()) - - return profiler.profile("process_${active.constraint().symbol()}") { - - if (!active.isStored()) { - frameStack.current.store.store(active) - trace.activate(active) - } else { - trace.reactivate(active) - } - - val activatedFringe = dispatchFringe.expand(active) - this.dispatchFringe = activatedFringe - - val outState = activatedFringe.matches().toList().fold(inState) { state, match -> - // TODO: paranoid check. should be isAlive() instead - // FIXME: move this check elsewhere - if (state.operational && active.isStored() && match.allStored()) - processMatch(state, match) - else - state - } - - // TODO: should be isAlive() - if (active.isStored()) { - trace.suspend(active) - } - - outState - } - } - - private fun processMatch(inState: ProcessingState, match: MatchRule) : ProcessingState { - val context = Context(inState, match.logicalContext()) - - // invoke matched pattern predicates - for (prd in match.patternPredicates()) { - if (!tellPredicate(prd, context)) break - } - - trace.trying(match) - - // check guard - for (gprd in match.rule().guard()) { - if (!askPredicate(gprd, context)) break - } - - 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 { - 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}") - } - - 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 activateConstraint(constraint: Constraint, context: Context) : Boolean { - val args = program.instantiateArguments(constraint.arguments(), context.logicalContext, context) - return context.updateState { state -> - process(constraint.occurrence(context.logicalContext, args, frameStack), state) - } - } - - private fun askPredicate(predicate: Predicate, context: Context) : Boolean = - profiler.profile("ask_${predicate.symbol()}") { - - context.evalSafe { state -> - val args = program.instantiateArguments(predicate.arguments(), context.logicalContext, context) - if (session.sessionSolver().ask(predicate.invocation(args, context.logicalContext, context))) - state - else - state.abort(DetailedFeedback("predicate not satisfied")) - } - - } - - private fun tellPredicate(predicate: Predicate, context: Context) : Boolean = - profiler.profile("tell_${predicate.symbol()}") { - - context.runSafe { - val args = program.instantiateArguments(predicate.arguments(), context.logicalContext, context) - session.sessionSolver().tell(predicate.invocation(args, context.logicalContext, context)) - } - - } - - private val noLogicalContext: LogicalContext = object: LogicalContext { - override fun variable(metaLogical: MetaLogical): Logical = TODO() - } - - private fun MatchRule.patternPredicates() = - (rule().headKept() + rule().headReplaced()).zip(matchHeadKept() + matchHeadReplaced()).flatMap { - 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/Dispatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt index 1f562389..9135cf2b 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt @@ -17,17 +17,17 @@ package jetbrains.mps.logic.reactor.core import com.github.andrewoma.dexx.collection.Maps +import jetbrains.mps.logic.reactor.core.internal.MatchRuleImpl +import jetbrains.mps.logic.reactor.core.internal.RuleMatcherImpl import com.github.andrewoma.dexx.collection.Map as PersMap -import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.MatchRule import jetbrains.mps.logic.reactor.program.Rule /** + * A front-end interface to [RuleMatcher]. + * * @author Fedor Isakov */ - -typealias Matcher = RuleMatcher - class Dispatcher (val ruleIndex: RuleIndex) { private val rule2matcher = HashMap() @@ -43,7 +43,7 @@ class Dispatcher (val ruleIndex: RuleIndex) { private var rule2probe: PersMap - private val allMatches = arrayListOf() + private val allMatches = arrayListOf() constructor() { this.rule2probe = Maps.of() @@ -56,7 +56,7 @@ class Dispatcher (val ruleIndex: RuleIndex) { this.rule2probe = pred.rule2probe matching.forEach { probe -> this.rule2probe = rule2probe.put(probe.rule(), probe) - allMatches.addAll(probe.matches()) + allMatches.addAll(probe.matches() as Collection) } } @@ -71,12 +71,13 @@ class Dispatcher (val ruleIndex: RuleIndex) { fun consume(matchRule: MatchRule) = DispatchFringe(this, matchRule) - fun expand(activated: ConstraintOccurrence) = DispatchFringe(this, + fun expand(activated: Occurrence) = DispatchFringe(this, ruleIndex.forOccurrenceWithMask(activated).mapNotNull { (rule, mask) -> rule2probe[rule]?.expand(activated, mask) + rule2probe[rule]?.expand(activated, mask) }) - fun contract(discarded: ConstraintOccurrence) = DispatchFringe(this, + fun contract(discarded: Occurrence) = DispatchFringe(this, ruleIndex.forOccurrence(discarded).mapNotNull { rule -> rule2probe[rule] }.map { probe -> diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionEx.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionEx.kt new file mode 100644 index 00000000..34f5d86b --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionEx.kt @@ -0,0 +1,52 @@ +/* + * 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.core + +import jetbrains.mps.logic.reactor.evaluation.EvaluationSession +import jetbrains.mps.logic.reactor.evaluation.EvaluationTrace +import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation +import jetbrains.mps.logic.reactor.evaluation.SessionSolver +import jetbrains.mps.logic.reactor.program.Program + +/** + * An extension of [EvaluationSession] with ability to access [Controller]. + * @author Fedor Isakov + */ +abstract class EvaluationSessionEx(val program: Program, + val trace: EvaluationTrace, + val sessionSolver: SessionSolver? = null) : EvaluationSession() { + + abstract fun controller(): Controller + + override fun program(): Program = program + + override fun sessionSolver(): SessionSolver = sessionSolver ?: SessionSolver() + + override fun ask(invocation: PredicateInvocation): Boolean { + val solver = invocation.predicate().symbol().solver() + val result = solver.ask(invocation) + trace.ask(result, invocation) + return result + } + + override fun tell(invocation: PredicateInvocation) { + val solver = invocation.predicate().symbol().solver() + trace.tell(invocation) + solver.tell(invocation) + } + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/FrameObservable.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/FrameObservable.kt new file mode 100644 index 00000000..2da15e38 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/FrameObservable.kt @@ -0,0 +1,33 @@ +/* + * 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.core + +import jetbrains.mps.logic.reactor.logical.Logical + +/** + * Serves to add/remove observers of [Logical] that track the frame stack. + */ +interface FrameObservable { + + /** Returns the store associated with this frame */ + fun storeObserver(): LogicalObserver + + fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) + + fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) + +} \ No newline at end of file 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 ebd35da4..ae469464 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt @@ -22,19 +22,16 @@ import jetbrains.mps.logic.reactor.logical.LogicalContext import jetbrains.mps.logic.reactor.program.Predicate /** + * Data class representing a single invocation of a predicate. + * * @author Fedor Isakov */ - -fun Predicate.invocation(arguments: List<*>, - logicalContext: LogicalContext, - invocationContext: InvocationContext): PredicateInvocation = - Invocation(this, arguments, logicalContext, invocationContext) - -private data class Invocation(val predicate: Predicate, +data class Invocation(val predicate: Predicate, val invocationArguments: List<*>, val logicalContext: LogicalContext, val invocationContext: InvocationContext) : PredicateInvocation { + override fun predicate(): Predicate = predicate override fun arguments(): List<*> = invocationArguments @@ -44,3 +41,8 @@ private data class Invocation(val predicate: Predicate, override fun invocationContext(): InvocationContext = invocationContext } + +fun Predicate.invocation(arguments: List<*>, + logicalContext: LogicalContext, + invocationContext: InvocationContext): PredicateInvocation = + Invocation(this, arguments, logicalContext, invocationContext) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/LogicalObserver.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/LogicalObserver.kt new file mode 100644 index 00000000..2017a7a5 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/LogicalObserver.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2014-2017 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.core + +import jetbrains.mps.logic.reactor.core.internal.LogicalImpl +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.logical.JoinableLogical + +/** + * An observer interface of a [Logical] instance. + * + * @author Fedor Isakov + */ +interface LogicalObserver { + + fun valueUpdated(logical: Logical<*>) + + fun parentUpdated(logical: Logical<*>) + +} + +fun Logical<*>.addObserver(observer: LogicalObserver) { + (this as LogicalImpl<*>).valueObservers.add(this.to(observer)) + (this as LogicalImpl<*>).parentObservers.add(this.to(observer)) +} + +fun Logical<*>.removeObserver(observer: LogicalObserver) { + (this as LogicalImpl<*>).valueObservers.removeAll { p -> p.second == observer } + (this as LogicalImpl<*>).parentObservers.removeAll { p -> p.second == observer } +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchingProbe.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchingProbe.kt index 67cb7783..c6de0d65 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchingProbe.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchingProbe.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 JetBrains s.r.o. + * 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. @@ -19,24 +19,26 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.MatchRule import jetbrains.mps.logic.reactor.program.Rule -import java.util.* + +import java.util.BitSet /** + * Provides functionality relevant to finding match(-es) for a rule. + * * @author Fedor Isakov */ - interface MatchingProbe { - - fun rule() : Rule - fun matches() : Collection + fun rule(): Rule + + fun matches(): Collection fun consumed(matchRule: MatchRule): MatchingProbe - fun expand(occ: ConstraintOccurrence) : MatchingProbe + fun expand(occ: Occurrence): MatchingProbe - fun expand(occ: ConstraintOccurrence, mask: BitSet) : MatchingProbe + fun expand(occ: Occurrence, mask: BitSet): MatchingProbe - fun contract(occ: ConstraintOccurrence): MatchingProbe + fun contract(occ: Occurrence): MatchingProbe } \ 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 b1796259..815d8857 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt @@ -17,33 +17,35 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence + import jetbrains.mps.logic.reactor.evaluation.EvaluationSession + import jetbrains.mps.logic.reactor.logical.Logical import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.logical.MetaLogical import jetbrains.mps.logic.reactor.program.Constraint /** + * Data class representing a single constraint occurrence. + * * @author Fedor Isakov */ - -internal fun Constraint.occurrence(logicalContext: LogicalContext, arguments: List<*>, frameStack: FrameStack): ConstraintOccurrence = - Occurrence(this, logicalContext, arguments, frameStack) - -private data class Occurrence (val constraint: Constraint, +data class Occurrence (val constraint: Constraint, val logicalContext: LogicalContext, val arguments: List<*>, - val frameStack: FrameStack) : + val currentFrame: () -> FrameObservable) : ConstraintOccurrence, - LogicalObserver, - StoreItem + LogicalObserver { - override var alive = true - override var stored = false + + var alive = true + + var stored = false init { for (a in arguments) { if (a is Logical<*>) { - frameStack.current.addObserver(a) { this } + currentFrame().addObserver(a) { this } } } } @@ -56,20 +58,20 @@ private data class Occurrence (val constraint: Constraint, override fun valueUpdated(logical: Logical<*>) { if (alive) { - (EvaluationSession.current() as SessionObjects).controller().reactivate(this) + EvaluationSession.current(EvaluationSessionEx::class.java).controller().reactivate(this) } } override fun parentUpdated(logical: Logical<*>) { if (alive) { - (EvaluationSession.current() as SessionObjects).controller().reactivate(this) + EvaluationSession.current(EvaluationSessionEx::class.java).controller().reactivate(this) } } - override fun terminate() { + fun terminate() { for (a in arguments) { if (a is Logical<*>) { - frameStack.current.removeObserver(a) { this } + currentFrame().removeObserver(a) { this } } } alive = false @@ -79,3 +81,15 @@ private data class Occurrence (val constraint: Constraint, } +fun Constraint.occurrence(arguments: List<*>, + currentFrame: () -> FrameObservable, + logicalContext: LogicalContext): Occurrence = + Occurrence(this, logicalContext, arguments, currentFrame) + +fun Constraint.occurrence(arguments: List<*>, + currentFrame: () -> FrameObservable): Occurrence = + Occurrence(this, noLogicalContext, arguments, currentFrame) + +private val noLogicalContext: LogicalContext = object: LogicalContext { + override fun variable(metaLogical: MetaLogical): Logical? = null +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/OccurrenceMatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/OccurrenceMatcher.kt index f0413bb5..dad4089d 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/OccurrenceMatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/OccurrenceMatcher.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 JetBrains s.r.o. + * 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. @@ -17,162 +17,23 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence -import jetbrains.mps.logic.reactor.logical.Logical -import jetbrains.mps.logic.reactor.logical.LogicalOwner import jetbrains.mps.logic.reactor.logical.MetaLogical import jetbrains.mps.logic.reactor.program.Constraint -import jetbrains.mps.unification.Term -import java.util.* + +/** + * Abstracts an algorithm for recursive matching of a [Constraint] and a [ConstraintOccurrence] + * or a pattern [Term] against a [Term] in a constraint occurrence's arguments. + * + * @author Fedor Isakov + */ +interface OccurrenceMatcher { + + fun substitution(): Subst + + fun matches(cst: Constraint, occ: ConstraintOccurrence): Boolean + + fun match(left: Any?, right: Any?): Boolean + +} typealias Subst = Map, Any> - -fun emptySubst() = HashMap, Any>(4) - -class OccurrenceMatcher(val contextSubst: Subst? = null) { - - companion object { - val EMPTY_SUBST : Subst = Collections.emptyMap() - } - - private var matchSubst : MutableMap, Any>? = null - - fun substitution(): Subst = matchSubst ?: (contextSubst ?: EMPTY_SUBST) - - /** - * Matches constraint and occurrence. - * Recursively processes all arguments, including terms. - * Returns substitution of MetaLogical instances on success, null otherwise. - */ - fun matches(cst: Constraint, occ: ConstraintOccurrence): Boolean - { - if (cst.symbol() != occ.constraint().symbol()) return false - - return zipWhileTrue(cst.arguments(), occ.arguments()) { cstarg, occarg -> - ptnMatchAny(cstarg, occarg) - } - } - - - /** - * Returns true for matching left and right parameters, false otherwise. - */ - fun match(left: Any?, right: Any?): Boolean { - return matchAny(left, right) - } - - /** - * Matches target against pattern. - * Recursively iterates terms. - * Respects substitutions for MetaLogical instances. - * Returns either new substitution on successful match, or null. - */ - private fun ptnMatchAny(ptn: Any?, trg: Any?): Boolean = - when (ptn) { - is MetaLogical<*> -> { - // recursion with existing substitution or new substitution - if (matchSubst == null) { - this.matchSubst = if (contextSubst != null) HashMap(contextSubst) else emptySubst() - } - - if (matchSubst!!.containsKey(ptn)) - matchSubst!![ptn].let { matchAny(it, trg) } - else - matchSubst!!.put(ptn, trg!!).run { true } - } - is Term -> - when { - ptn.`is`(Term.Kind.REF) -> ptnMatchAny(resolve(ptn), trg) - else -> ptnMatchTerm(ptn, trg) // recursion into the term - } - else -> - when { - trg is Logical<*> -> ptnMatchAny(ptn, resolve(trg)) - else -> - // compare two arbitrary values - (ptn == trg) - } - } - - private fun matchAny(left: Any?, right: Any?): Boolean = - when (left) { - is Logical<*> -> - // match logical or its value - matchLogical(left.findRoot(), right) - is Term -> - when { - left.`is`(Term.Kind.REF) -> matchAny(resolve(left), right) - else -> matchTerm(left, right) // recursion into the term - } - else -> - when { - right is Logical<*> -> matchAny(left, resolve(right)) - else -> // compare two arbitrary values - (left == right) - } - } - - private fun ptnMatchTerm(ptn: Term, trg: Any?): Boolean { - if (trg == null) return false - - val trgval = resolve(trg) - if (!(trgval is Term)) return false - - if (ptn.`is`(Term.Kind.VAR)) return ptnMatchAny(ptn.get().symbol(), trgval) - - if (!ptnMatchAny(ptn.get().symbol(), trgval.symbol())) return false - - // FIXME: reversing the order of arguments leads to infinite cycle - // Example: two terms of the form f(... V_1 ...) and f(... V_2 ...) where - // V_1 is bound to g(... W_1 ...), V_2 -> g(... W_2 ...), W_1 -> f(... V_1 ...), and W_2 -> f(... V_2 ...) - return zipWhileTrue(ptn.get().arguments(), trgval.arguments()) { ptnarg, trgarg -> - ptnMatchAny (ptnarg, trgarg) - } - } - - private fun matchTerm(left: Term, right: Any?): Boolean { - if (right == null) return false - - val rval = resolve(right) - if (!(rval is Term)) return false - - if (!matchAny(left.get().symbol(), rval.symbol())) return false - - // FIXME: reversing the order of arguments leads to infinite cycle - // Example: two terms of the form f(... V_1 ...) and f(... V_2 ...) where - // V_1 is bound to g(... W_1 ...), V_2 -> g(... W_2 ...), W_1 -> f(... V_1 ...), and W_2 -> f(... V_2 ...) - return zipWhileTrue(left.get().arguments(), rval.arguments()) { larg, rarg -> - matchAny (larg, rarg) - } - } - - private fun matchLogical(left: Logical<*>, right: Any?): Boolean = - when { - right is Logical<*> -> - when { - left.isBound -> matchAny(left.findRoot().value(), - right.findRoot().value()) - left.findRoot() === right.findRoot() -> true // reference equality - else -> false - } - left.isBound -> matchAny(left.findRoot().value(), right) - else -> false - } - - private fun resolve(obj: Any?): Any? = - when (obj) { - is LogicalOwner -> if (obj.logical().isBound) resolve(obj.logical()) else obj - is Logical<*> -> resolve(obj.findRoot().value()) - is Term -> if (obj.`is`(Term.Kind.REF)) resolve(obj.get()) else obj - else -> obj - } - - private inline fun zipWhileTrue(first: Iterable, second: Iterable, action: (S, T) -> Boolean): Boolean { - val firstIt = first.iterator() - val secondIt = second.iterator() - while(firstIt.hasNext() && secondIt.hasNext()) { - if (!action(firstIt.next(), secondIt.next())) return false - } - return true - } - -} \ No newline at end of file diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorLifecycle.kt similarity index 56% rename from reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorLifecycle.kt index f46f6aaf..e2929399 100644 --- a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReactorLifecycle.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 JetBrains s.r.o. + * 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. @@ -14,17 +14,24 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.evaluation; +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.core.internal.EvaluationSessionImpl /** - * Not very useful now. - * Serves as an abstract superclass for implementations, as a mixin of Instructible and Queryable. + * Must be invoked by the code responsible for application's initialization and teardown. * - * @deprecated use Solver instead. * @author Fedor Isakov */ -@Deprecated -public abstract class AbstractSolver implements Solver { +class ReactorLifecycle { + companion object { + fun init() { + EvaluationSessionImpl.Backend.init() + } -} + fun deinit() { + EvaluationSessionImpl.Backend.deinit() + } + } +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt index d7d6e8f5..e596213e 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt @@ -29,9 +29,10 @@ import kotlin.collections.ArrayList import kotlin.collections.HashMap /** + * A container for [Rule] instances with the ability to look up by [ConstraintOccurrence]. + * * @author Fedor Isakov */ - class RuleIndex(handlers: Iterable) : Iterable { private val symbol2index = HashMap() diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt index 3abeef91..e39a1bfc 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatcher.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 JetBrains s.r.o. + * 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. @@ -16,171 +16,20 @@ package jetbrains.mps.logic.reactor.core -import com.github.andrewoma.dexx.collection.Sets -import com.github.andrewoma.dexx.collection.Map as PersMap -import com.github.andrewoma.dexx.collection.Set as PersSet -import com.github.andrewoma.dexx.collection.List as PersList -import com.github.andrewoma.dexx.collection.Vector as PersVector -import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence -import jetbrains.mps.logic.reactor.evaluation.MatchRule -import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.core.internal.RuleMatcherImpl import jetbrains.mps.logic.reactor.program.Rule -import jetbrains.mps.logic.reactor.util.* -import java.util.* -import kotlin.collections.ArrayList /** + * Abstracts a rule matching algorithm. + * * @author Fedor Isakov */ +interface RuleMatcher { -class RuleMatcher(val rule: Rule) { - - val head = rule.headKept().toCollection(ArrayList(4)).apply { - addAll(rule.headReplaced()) } as List - - val propagation = rule.headReplaced().count() == 0 - - fun probe(): MatchingProbe = RuleMatchFringe(listOf(MatchNode(emptySubst())), - Sets.of(), - Sets.of(), - 0) - - inner class RuleMatchFringe(val nodes: List, - val seen: PersSet>, - val consumed: PersSet?>>, - val genId: Int) : MatchingProbe - { - override fun rule(): Rule = rule - - override fun matches(): Collection { - return nodes.filter { it is ActiveMatchNode && it.complete && it.genId == genId } - .map { (it as ActiveMatchNode).toMatchRule() } - } - - override fun consumed(matchRule: MatchRule): MatchingProbe = - RuleMatchFringe(nodes, - seen, - consumed.add(((matchRule as MatchRuleImpl).origin as ActiveMatchNode).signature), - genId) - - override fun expand(occ: ConstraintOccurrence): MatchingProbe = - expand(occ, bitSetOfOnes(head.size)) - - /** - * Expands the fringe by creating new leaf nodes that match the occurrence. - * Mask specifies possible slots for the occurrence. - */ - override fun expand(occ: ConstraintOccurrence, mask: BitSet): RuleMatchFringe { - val reactivated = seen.contains(IdWrapper(occ)) - val newSeen = if (reactivated) seen else seen.add(IdWrapper(occ)) - val newNodes = ArrayList(nodes) - - val allSignatures = newNodes.map { it.signature }.toHashSet() - for (n in nodes) { - n.expand(occ, genId + 1, n.matchingVacant(mask)) - .filter { allSignatures.add(it.signature) || reactivated } // ensure reactivated have effect - .filter { !(propagation && reactivated && consumed.contains(it.signature)) } // ...unless propagation - .forEach { newNodes.add(it) } - } - - return RuleMatchFringe(newNodes, newSeen, consumed, genId + 1) - } - - override fun contract(occ: ConstraintOccurrence): RuleMatchFringe { - val newNodes = nodes.mapNotNull { it.unrelatedOrNull(occ) } - return RuleMatchFringe(newNodes, seen, consumed,genId + 1) - } - - } - - open inner class MatchNode(val subst: Subst, val vacant: BitSet = bitSetOfOnes(head.size)) - { - open val signature: ArrayList?> = - arrayListOf(* arrayOfNulls?>(head.size)) - - /** - * Returns the additional nodes built from this node on adding the occurrence. - * If the occurrence is already in the path, return empty sequence. - */ - fun expand(occ: ConstraintOccurrence, genId: Int, matchingVacant: BitSet): List = - unrelatedOrNull(occ)?.let { n -> - ArrayList().also { expanded -> - for (headIdx in matchingVacant.allSetBits()) { - OccurrenceMatcher(subst).run { - if (matches(head[headIdx], occ)) { - expanded.add(ActiveMatchNode(substitution(), n, occ, headIdx, genId)) - } - } - } - } - } ?: emptyList() - - - /** - * Returns this node if it doesn't have the occurrence in its path, null otherwise. - */ - open fun unrelatedOrNull(occ: ConstraintOccurrence): MatchNode? = this - - fun matchingVacant(mask: BitSet) = mask.copyApply { and(vacant) } - - /** - * Folds the path to the root. - */ - inline protected fun fold(init: T, action: (T, ActiveMatchNode) -> T): T { - var rn = this - var curr = init - while (rn is ActiveMatchNode) { - curr = action(curr, rn) - rn = rn.parent - } - return curr - } - - /** - * Folds the path to the root. If an iteration yields null, fold is stopped and null is returned. - */ - inline protected fun foldUntilNull(init: T, action: (T, ActiveMatchNode) -> T?): T? { - var rn = this - var curr: T? = init - while (rn is ActiveMatchNode) { - curr = action(curr !!, rn) - if (curr == null) return null - rn = rn.parent - } - return curr - } - } - - inner class ActiveMatchNode(subst: Subst, - val parent: MatchNode, - val occurrence: ConstraintOccurrence, - val headIndex: Int, - val genId: Int) : - MatchNode(subst, parent.vacant.clearBit(headIndex)) - { - val complete = vacant.cardinality() == 0 - - override val signature: ArrayList?> = - (parent.signature.clone() as ArrayList?>) - .also { it[headIndex] = IdWrapper(occurrence) } - - fun constraint(): Constraint = head[headIndex] - - override fun unrelatedOrNull(occ: ConstraintOccurrence): ActiveMatchNode? = - foldUntilNull(this) { acc, rn -> if (rn.occurrence === occ) null else acc } - - - fun toMatchRule(): MatchRule { - val matched: Array = - fold(arrayOfNulls(head.size)) { arr, rn -> arr[rn.headIndex] = rn.occurrence; arr } - - return MatchRuleImpl(this, - rule, - subst, - ArrayList(matched.take(rule.headKept().count())), - ArrayList(matched.takeLast(rule.headReplaced().count()))) - } - } - + fun probe(): MatchingProbe } + +fun createRuleMatcher(rule: Rule): RuleMatcher = Matcher(rule) + +internal typealias Matcher = RuleMatcherImpl diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt new file mode 100644 index 00000000..d692d1a8 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt @@ -0,0 +1,332 @@ +/* + * 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.core.internal + +import jetbrains.mps.logic.reactor.core.* +import jetbrains.mps.logic.reactor.core.internal.ProcessingState.* +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 +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.util.Profiler +import jetbrains.mps.logic.reactor.util.profile +import com.github.andrewoma.dexx.collection.Map as PersMap + +internal class ControllerImpl ( + val program: Program, + val trace: EvaluationTrace = EvaluationTrace.NULL, + val profiler: Profiler? = null, + val storeView: StoreView? = null, + val feedbackHandler: EvaluationFeedbackHandler? = null) : Controller +{ + + + // FIXME move to parameter + private val session: EvaluationSession = EvaluationSession.current() + + + private val ruleIndex: RuleIndex = RuleIndex(program.handlers()) + + + private var dispatchFringe = Dispatcher(ruleIndex).fringe() + + // FIXME move to context + private val frameStack = FrameStack(storeView) + + /** For tests only */ + override fun storeView(): StoreView = frameStack.current.store.view() + + /** For tests only */ + override fun evaluate(occ: Occurrence): StoreView { + // create the internal occurrence + val active = occ.constraint().occurrence(occ.arguments(), { frameStack.current }) + val state = process(active, NORMAL()) + if (state is FAILED) { + throw state.failure.cause + } + return storeView() + } + + fun activate(constraint: Constraint) : ProcessingState { + // FIXME noLogicalContext + val context = Context(NORMAL(), noLogicalContext) + activateConstraint(constraint, context) + return context.currentState() + } + + override fun reactivate(occ: Occurrence) { + // FIXME propagate the processing state further up the call stack + // TODO: introduce processing state to solver API? + val state = process(occ, NORMAL()) + if (state is FAILED) { + throw state.failure.cause + } + } + + private fun process(active: Occurrence, inState: ProcessingState) : ProcessingState { + assert(active.alive) + + return profiler.profile("process_${active.constraint().symbol()}") { + + if (!active.stored) { + frameStack.current.store.store(active) + trace.activate(active) + } else { + trace.reactivate(active) + } + + val activatedFringe = dispatchFringe.expand(active) + this.dispatchFringe = activatedFringe + + val outState = activatedFringe.matches().toList().fold(inState) { state, match -> + // TODO: paranoid check. should be isAlive() instead + // FIXME: move this check elsewhere + if (state.operational && active.stored && match.allStored()) + processMatch(state, match as MatchRuleImpl) + else + state + } + + // TODO: should be isAlive() + if (active.stored) { + trace.suspend(active) + } + + outState + } + } + + private fun processMatch(inState: ProcessingState, match: MatchRuleImpl) : ProcessingState { + val context = Context(inState, match.logicalContext()) + + // invoke matched pattern predicates + for (prd in match.patternPredicates()) { + if (!tellPredicate(prd, context)) break + } + + trace.trying(match) + + // check guard + for (gprd in match.rule().guard()) { + if (!askPredicate(gprd, context)) break + } + + 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.headReplaced) { + 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 { + 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}") + } + + 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 activateConstraint(constraint: Constraint, context: Context) : Boolean { + val args = program.instantiateArguments(constraint.arguments(), context.logicalContext, context) + return context.updateState { state -> + val active = constraint.occurrence(args, { frameStack.current }, context.logicalContext) + process(active, state) + } + } + + private fun askPredicate(predicate: Predicate, context: Context) : Boolean = + profiler.profile("ask_${predicate.symbol()}") { + + context.evalSafe { state -> + val args = program.instantiateArguments(predicate.arguments(), context.logicalContext, context) + if (session.ask(predicate.invocation(args, context.logicalContext, context))) + state + else + state.abort(DetailedFeedback("predicate not satisfied")) + } + + } + + private fun tellPredicate(predicate: Predicate, context: Context) : Boolean = + profiler.profile("tell_${predicate.symbol()}") { + + context.runSafe { + val args = program.instantiateArguments(predicate.arguments(), context.logicalContext, context) + session.tell(predicate.invocation(args, context.logicalContext, context)) + } + + } + + private val noLogicalContext: LogicalContext = object: LogicalContext { + override fun variable(metaLogical: MetaLogical): Logical? = null + } + + private fun MatchRule.patternPredicates() = + (rule().headKept() + rule().headReplaced()).zip(matchHeadKept() + matchHeadReplaced()).flatMap { + it.first.patternPredicates(it.second.arguments()) + }.toList() + + private fun MatchRule.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored } + +} + +private 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 + } + +} + +/** Used to create controller from tests */ +fun createController( + program: Program, + trace: EvaluationTrace = EvaluationTrace.NULL, + profiler: Profiler? = null, + storeView: StoreView? = null, + feedbackHandler: EvaluationFeedbackHandler? = null) : Controller = + ControllerImpl(program, trace, profiler, storeView, feedbackHandler) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/EvaluationSessionImpl.kt similarity index 59% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/EvaluationSessionImpl.kt index 99e32219..87109321 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/EvaluationSessionImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 JetBrains s.r.o. + * 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. @@ -14,16 +14,15 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.core +package jetbrains.mps.logic.reactor.core.internal -import jetbrains.mps.logic.reactor.core.ProcessingState.FAILED +import jetbrains.mps.logic.reactor.core.internal.ProcessingState.FAILED +import jetbrains.mps.logic.reactor.core.EvaluationSessionEx import jetbrains.mps.logic.reactor.evaluation.* import jetbrains.mps.logic.reactor.program.Constraint -import jetbrains.mps.logic.reactor.program.PredicateSymbol import jetbrains.mps.logic.reactor.program.Program import jetbrains.mps.logic.reactor.util.Profiler import java.util.* -import kotlin.collections.ArrayList import com.github.andrewoma.dexx.collection.LinkedList as PLinkedList import com.github.andrewoma.dexx.collection.List as PList @@ -31,38 +30,27 @@ import com.github.andrewoma.dexx.collection.List as PList * @author Fedor Isakov */ -interface SessionObjects { - - fun controller(): Controller - -} - -class EvaluationSessionImpl private constructor ( - val program: Program, - val sessionSolver: SessionSolver, - val trace: EvaluationTrace, - val failureHandler: EvaluationFeedbackHandler?) : EvaluationSession(), SessionObjects +internal class EvaluationSessionImpl private constructor ( + program: Program, + trace: EvaluationTrace, + sessionSolver: SessionSolver? = null) : EvaluationSessionEx(program, trace, sessionSolver) { - lateinit var controller: Controller + lateinit var controller: ControllerImpl - private fun launch(main: Constraint, profiler: Profiler?, storeView: StoreView?) : ProcessingState { - this.controller = Controller(program, trace, profiler, storeView, failureHandler) + override fun controller() = controller + + private fun launch(main: Constraint, profiler: Profiler?, storeView: StoreView?, feedbackHandler: EvaluationFeedbackHandler?) : ProcessingState { + this.controller = ControllerImpl(program, trace, profiler, storeView, feedbackHandler) return controller.activate(main) } private class Config(val program: Program) : EvaluationSession.Config() { - - val predicateSymbols = ArrayList() val parameters = HashMap() var evaluationTrace: EvaluationTrace = EvaluationTrace.NULL var storeView: StoreView? = null - var feedbackHandler: EvaluationFeedbackHandler? = null - override fun withPredicates(vararg predicateSymbols: PredicateSymbol): EvaluationSession.Config { - this.predicateSymbols.addAll(Arrays.asList(* predicateSymbols)) - return this - } + var feedbackHandler: EvaluationFeedbackHandler? = null override fun withTrace(computingTracer: EvaluationTrace): EvaluationSession.Config { this.evaluationTrace = computingTracer @@ -74,11 +62,6 @@ class EvaluationSessionImpl private constructor ( return this } - override fun withFailureHandler(handler: FailureHandler): EvaluationSession.Config { - this.feedbackHandler = handler - return this - } - override fun withFeedbackHandler(handler: EvaluationFeedbackHandler?): EvaluationSession.Config { this.feedbackHandler = handler return this @@ -89,22 +72,24 @@ class EvaluationSessionImpl private constructor ( return this } - override fun start(sessionSolver: SessionSolver): EvaluationResult { - var session = ourBackend.ourSession.get() + override fun start(): EvaluationResult = start(SessionSolver()) + + override fun start(sessionSolver: SessionSolver?): EvaluationResult { + var session = Backend.ourBackend.ourSession.get() if (session != null) throw IllegalStateException("session already active") - sessionSolver.init(evaluationTrace, * predicateSymbols.toArray(arrayOfNulls(predicateSymbols.size))) + sessionSolver?.init(evaluationTrace) @Suppress("UNCHECKED_CAST") val durations = parameters.get("profiling.data") as MutableMap? val profiler = durations?.let { Profiler() } - session = EvaluationSessionImpl(program, sessionSolver, evaluationTrace, feedbackHandler) - ourBackend.ourSession.set(session) + session = EvaluationSessionImpl(program, evaluationTrace, sessionSolver) + Backend.ourBackend.ourSession.set(session) var failure: EvaluationFailure? = null try { - val state = session.launch(parameters["main"] as Constraint, profiler, storeView) + val state = session.launch(parameters["main"] as Constraint, profiler, storeView, feedbackHandler) if (state is FAILED) { failure = state.failure } @@ -119,44 +104,38 @@ class EvaluationSessionImpl private constructor ( catch (t: Throwable) { // avoid nested failure } - ourBackend.ourSession.set(null) + Backend.ourBackend.ourSession.set(null) } return object : EvaluationResult { - override fun storeView(): StoreView? = session.storeView() + override fun storeView(): StoreView? = session.controller.storeView() override fun failure(): EvaluationFailure? = failure } } + } - override fun controller() = controller - - override fun sessionSolver(): SessionSolver = sessionSolver - - override fun program(): Program = program - - override fun storeView(): StoreView = - controller.storeView() - - private class Backend : EvaluationSession.Backend { + internal class Backend : EvaluationSession.Backend { val ourSession = ThreadLocal() - override fun current(): EvaluationSession = ourSession.get() ?: throw IllegalStateException("no session") + override fun current(): EvaluationSessionImpl = ourSession.get() ?: throw IllegalStateException("no session") override fun createConfig(program: Program): EvaluationSession.Config = Config(program) - } - companion object { - private val ourBackend = Backend() + companion object { + val ourBackend = Backend() - fun init() { - setBackend(ourBackend) + fun init() { + EvaluationSession.setBackend(ourBackend) + } + + fun deinit() { + EvaluationSession.clearBackend(ourBackend) + } } - fun deinit() { - clearBackend(ourBackend) - } } -} \ No newline at end of file + +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Frame.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Frame.kt similarity index 86% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/Frame.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Frame.kt index 28d71442..8e9c2fee 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Frame.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Frame.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 JetBrains s.r.o. + * 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. @@ -14,11 +14,14 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.core +package jetbrains.mps.logic.reactor.core.internal import com.github.andrewoma.dexx.collection.ConsList import com.github.andrewoma.dexx.collection.Map import com.github.andrewoma.dexx.collection.Maps +import jetbrains.mps.logic.reactor.core.FrameObservable +import jetbrains.mps.logic.reactor.core.LogicalObserver +import jetbrains.mps.logic.reactor.core.addObserver import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.logical.Logical import jetbrains.mps.logic.reactor.util.IdWrapper @@ -26,13 +29,13 @@ import jetbrains.mps.logic.reactor.util.cons import jetbrains.mps.logic.reactor.util.remove import java.util.* -internal class Frame: LogicalObserver, StoreKeeper { +internal class Frame: LogicalObserver, FrameObservable { val stack: FrameStack val store: Store - private var observers: Map>, ConsList<(StoreKeeper) -> LogicalObserver>> + private var observers: Map>, ConsList<(FrameObservable) -> LogicalObserver>> constructor(stack: FrameStack) { this.stack = stack @@ -52,9 +55,9 @@ internal class Frame: LogicalObserver, StoreKeeper { this.observers = Maps.of() } - override fun store() = store + override fun storeObserver() = store - override fun addObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) { + override fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { val logicalId = IdWrapper(logical) if (!observers.containsKey(logicalId)) { stack.addObserver(logical) @@ -63,7 +66,7 @@ internal class Frame: LogicalObserver, StoreKeeper { observers[logicalId]?.prepend(obs) ?: cons(obs)) } - override fun removeObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) { + override fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { val logicalId = IdWrapper(logical) observers[logicalId].remove(obs)?.let { newList -> this.observers = observers.put(logicalId, newList) @@ -132,4 +135,4 @@ internal class FrameStack(storeView: StoreView?) : LogicalObserver { current.parentUpdated(logical) } -} \ No newline at end of file +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/LogicalImpl.kt similarity index 85% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/LogicalImpl.kt index 3eceec55..5385e96b 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/LogicalImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 JetBrains s.r.o. + * 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. @@ -14,40 +14,14 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.core +package jetbrains.mps.logic.reactor.core.internal -import jetbrains.mps.logic.reactor.logical.Logical -import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.core.LogicalObserver import jetbrains.mps.logic.reactor.logical.JoinableLogical -import java.util.* +import jetbrains.mps.logic.reactor.logical.MetaLogical +import java.util.ArrayList -/** - * @author Fedor Isakov - */ - -interface LogicalObserver { - - fun valueUpdated(logical: Logical<*>) - - fun parentUpdated(logical: Logical<*>) - -} - -fun Logical<*>.addObserver(observer: LogicalObserver) { - (this as LogicalImpl<*>).valueObservers.add(this.to(observer)) - (this as LogicalImpl<*>).parentObservers.add(this.to(observer)) -} - -fun Logical<*>.removeObserver(observer: LogicalObserver) { - (this as LogicalImpl<*>).valueObservers.removeAll { p -> p.second == observer } - (this as LogicalImpl<*>).parentObservers.removeAll { p -> p.second == observer } -} - -fun MetaLogical.logical(): Logical = LogicalImpl(this) - -fun MetaLogical.logical(value: V): Logical = LogicalImpl(name(), value) - -class LogicalImpl : JoinableLogical { +internal class LogicalImpl : JoinableLogical { companion object { var lastIdx = 0 @@ -225,4 +199,15 @@ class LogicalImpl : JoinableLogical { } -class DefaultMetaLogical (val name: String) : MetaLogical(name, Object::class.java as Class) {} \ No newline at end of file +class DefaultMetaLogical (val name: String) : MetaLogical(name, Object::class.java as Class) {} + +// Used from tests + +fun anonLogical(value: V): JoinableLogical = LogicalImpl(value) + +fun namedLogical(name: String): JoinableLogical = LogicalImpl(name) + +fun MetaLogical.logical(): JoinableLogical = LogicalImpl(this) + +fun MetaLogical.logical(value: V): JoinableLogical = LogicalImpl(name(), value) + diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchRuleImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchRuleImpl.kt similarity index 73% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchRuleImpl.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchRuleImpl.kt index 6aeb02a4..9e1ef134 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/MatchRuleImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchRuleImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 JetBrains s.r.o. + * 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. @@ -14,8 +14,10 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.core +package jetbrains.mps.logic.reactor.core.internal +import jetbrains.mps.logic.reactor.core.Occurrence +import jetbrains.mps.logic.reactor.core.Subst import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.MatchRule import jetbrains.mps.logic.reactor.logical.Logical @@ -24,11 +26,10 @@ import jetbrains.mps.logic.reactor.logical.LogicalOwner import jetbrains.mps.logic.reactor.logical.MetaLogical import jetbrains.mps.logic.reactor.program.Rule -class MatchRuleImpl(val origin: Any, - val rule: Rule, - val subst: Subst, - val headKept: MutableIterable, - val headReplaced: MutableIterable) : MatchRule { +internal class MatchRuleImpl(val rule: Rule, + val subst: Subst, + val headKept: Iterable, + val headReplaced: Iterable) : MatchRule { private val logicalContext = object : LogicalContext { @@ -47,10 +48,10 @@ class MatchRuleImpl(val origin: Any, override fun rule(): Rule = rule - override fun matchHeadKept(): MutableIterable = headKept - - override fun matchHeadReplaced(): MutableIterable = headReplaced + override fun matchHeadKept(): Iterable = headKept + override fun matchHeadReplaced(): Iterable = headReplaced + override fun logicalContext(): LogicalContext = logicalContext } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/OccurrenceMatcherImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/OccurrenceMatcherImpl.kt new file mode 100644 index 00000000..24c677fe --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/OccurrenceMatcherImpl.kt @@ -0,0 +1,181 @@ +/* + * 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.core.internal + +import jetbrains.mps.logic.reactor.core.OccurrenceMatcher +import jetbrains.mps.logic.reactor.core.Subst +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalOwner +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.unification.Term +import java.util.* + +internal class OccurrenceMatcherImpl(val contextSubst: Subst? = null) : OccurrenceMatcher { + + companion object { + + val EMPTY_SUBST : Subst = Collections.emptyMap() + } + private var matchSubst : MutableMap, Any>? = null + + override fun substitution(): Subst = matchSubst ?: (contextSubst ?: EMPTY_SUBST) + + /** + * Matches constraint and occurrence. + * Recursively processes all arguments, including terms. + * Returns substitution of MetaLogical instances on success, null otherwise. + */ + override fun matches(cst: Constraint, occ: ConstraintOccurrence): Boolean + { + if (cst.symbol() != occ.constraint().symbol()) return false + + return zipWhileTrue(cst.arguments(), occ.arguments()) { cstarg, occarg -> + ptnMatchAny(cstarg, occarg) + } + } + + /** + * Returns true for matching left and right parameters, false otherwise. + */ + override fun match(left: Any?, right: Any?): Boolean { + return matchAny(left, right) + } + + /** + * Matches target against pattern. + * Recursively iterates terms. + * Respects substitutions for MetaLogical instances. + * Returns either new substitution on successful match, or null. + */ + private fun ptnMatchAny(ptn: Any?, trg: Any?): Boolean = + when (ptn) { + is MetaLogical<*> -> { + // recursion with existing substitution or new substitution + if (matchSubst == null) { + this.matchSubst = if (contextSubst != null) HashMap(contextSubst) else emptySubst() + } + + if (matchSubst!!.containsKey(ptn)) + matchSubst!![ptn].let { matchAny(it, trg) } + else + matchSubst!!.put(ptn, trg!!).run { true } + } + is Term -> + when { + ptn.`is`(Term.Kind.REF) -> ptnMatchAny(resolve(ptn), trg) + else -> ptnMatchTerm(ptn, trg) // recursion into the term + } + else -> + when { + trg is Logical<*> -> ptnMatchAny(ptn, resolve(trg)) + else -> + // compare two arbitrary values + (ptn == trg) + } + } + + + private fun matchAny(left: Any?, right: Any?): Boolean = + when (left) { + is Logical<*> -> + // match logical or its value + matchLogical(left.findRoot(), right) + is Term -> + when { + left.`is`(Term.Kind.REF) -> matchAny(resolve(left), right) + else -> matchTerm(left, right) // recursion into the term + } + else -> + when { + right is Logical<*> -> matchAny(left, resolve(right)) + else -> // compare two arbitrary values + (left == right) + } + } + + private fun ptnMatchTerm(ptn: Term, trg: Any?): Boolean { + if (trg == null) return false + + val trgval = resolve(trg) + if (!(trgval is Term)) return false + + if (ptn.`is`(Term.Kind.VAR)) return ptnMatchAny(ptn.get().symbol(), trgval) + + if (!ptnMatchAny(ptn.get().symbol(), trgval.symbol())) return false + + // FIXME: reversing the order of arguments leads to infinite cycle + // Example: two terms of the form f(... V_1 ...) and f(... V_2 ...) where + // V_1 is bound to g(... W_1 ...), V_2 -> g(... W_2 ...), W_1 -> f(... V_1 ...), and W_2 -> f(... V_2 ...) + return zipWhileTrue(ptn.get().arguments(), trgval.arguments()) { ptnarg, trgarg -> + ptnMatchAny (ptnarg, trgarg) + } + } + + private fun matchTerm(left: Term, right: Any?): Boolean { + if (right == null) return false + + val rval = resolve(right) + if (!(rval is Term)) return false + + if (!matchAny(left.get().symbol(), rval.symbol())) return false + + // FIXME: reversing the order of arguments leads to infinite cycle + // Example: two terms of the form f(... V_1 ...) and f(... V_2 ...) where + // V_1 is bound to g(... W_1 ...), V_2 -> g(... W_2 ...), W_1 -> f(... V_1 ...), and W_2 -> f(... V_2 ...) + return zipWhileTrue(left.get().arguments(), rval.arguments()) { larg, rarg -> + matchAny (larg, rarg) + } + } + + private fun matchLogical(left: Logical<*>, right: Any?): Boolean = + when { + right is Logical<*> -> + when { + left.isBound -> matchAny(left.findRoot().value(), + right.findRoot().value()) + left.findRoot() === right.findRoot() -> true // reference equality + else -> false + } + left.isBound -> matchAny(left.findRoot().value(), right) + else -> false + } + + private fun resolve(obj: Any?): Any? = + when (obj) { + is LogicalOwner -> if (obj.logical().isBound) resolve(obj.logical()) else obj + is Logical<*> -> resolve(obj.findRoot().value()) + is Term -> if (obj.`is`(Term.Kind.REF)) resolve(obj.get()) else obj + else -> obj + } + + private inline fun zipWhileTrue(first: Iterable, second: Iterable, action: (S, T) -> Boolean): Boolean { + val firstIt = first.iterator() + val secondIt = second.iterator() + while(firstIt.hasNext() && secondIt.hasNext()) { + if (!action(firstIt.next(), secondIt.next())) return false + } + return true + } + +} + +fun emptySubst() = HashMap, Any>(4) + +fun createOccurrenceMatcher(contextSubst: Subst? = null): OccurrenceMatcher = + OccurrenceMatcherImpl(contextSubst) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingState.kt similarity index 97% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingState.kt index 3b678384..8ffd1f40 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ProcessingState.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 JetBrains s.r.o. + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.core +package jetbrains.mps.logic.reactor.core.internal import jetbrains.mps.logic.reactor.evaluation.CompositeFeedback import jetbrains.mps.logic.reactor.evaluation.DetailedFeedback diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReteNetwork.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt similarity index 84% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/ReteNetwork.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt index e4362341..ae8e7ed6 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/ReteNetwork.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 JetBrains s.r.o. + * 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. @@ -14,29 +14,34 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.core +package jetbrains.mps.logic.reactor.core.internal +import jetbrains.mps.logic.reactor.core.* import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.MatchRule import jetbrains.mps.logic.reactor.logical.MetaLogical import jetbrains.mps.logic.reactor.program.Rule -import jetbrains.mps.logic.reactor.util.* +import jetbrains.mps.logic.reactor.util.allSetBits +import jetbrains.mps.logic.reactor.util.bitSet +import jetbrains.mps.logic.reactor.util.bitSetOfOnes +import jetbrains.mps.logic.reactor.util.copyApply import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashMap /** + * An alternative implementation of RuleMatcherImpl. Has similar asymptotic characteristics as the default implementation, + * but in practice is a bit slower. + * + * Loosely based on "Rete network" algorithm. + * * @author Fedor Isakov */ - - -class ReteRuleMatcher(val rule: Rule) { +internal class ReteRuleMatcherImpl(val rule: Rule) : RuleMatcher { val head = (rule.headKept() + rule.headReplaced()).toList() val propagation = rule.headReplaced().count() == 0 - - fun probe(): ReteNetwork = ReteNetwork(head.size) + + override fun probe(): ReteNetwork = ReteNetwork(head.size) inner class ReteNetwork(val headSize: Int) : MatchingProbe { @@ -69,7 +74,7 @@ class ReteRuleMatcher(val rule: Rule) { if (thisMetaIndices.cardinality() != 0 && thatMetaIndices.cardinality() != 0) { for (shared in thisMetaIndices.copyApply { and(thatMetaIndices) }.allSetBits()) { - if (!OccurrenceMatcher().match(this.getSubst(shared), that.getSubst(shared))) return false + if (!createOccurrenceMatcher().match(this.getSubst(shared), that.getSubst(shared))) return false } } @@ -86,7 +91,7 @@ class ReteRuleMatcher(val rule: Rule) { abstract fun combine(that: AlphaNode): ReteNode - open fun collectData(occArray: Array, allSubst: MutableMap, Any>) {} + open fun collectData(occArray: Array, allSubst: MutableMap, Any>) {} } @@ -107,13 +112,13 @@ class ReteRuleMatcher(val rule: Rule) { /** * A network node corresponding to a single occurrence matched against a constraint. */ - inner class AlphaNode(val occurrence: ConstraintOccurrence, + inner class AlphaNode(val occurrence: Occurrence, val posInHead: Int, val subst: Subst) : ReteNode() { val metaIndices: BitSet? = if (subst.isNotEmpty()) bitSet(subst.keys.map { metaLogical -> indexOf(metaLogical) }) else null - + val occIdx = indexOf(occurrence) private val idx2subst = HashMap() @@ -139,7 +144,7 @@ class ReteRuleMatcher(val rule: Rule) { override fun combine(that: AlphaNode): ReteNode = BetaNode(this, that) - override fun collectData(occArray: Array, allSubst: MutableMap, Any>) { + override fun collectData(occArray: Array, allSubst: MutableMap, Any>) { occArray[posInHead] = occurrence for ((k, v) in subst) { allSubst.put(k, v) @@ -152,7 +157,7 @@ class ReteRuleMatcher(val rule: Rule) { * A "deep" network node. Always has two parents, one of which is always an AlphaNode. */ inner class BetaNode : ReteNode { - + val left: ReteNode val right: AlphaNode @@ -200,7 +205,7 @@ class ReteRuleMatcher(val rule: Rule) { override fun combine(that: AlphaNode): ReteNode = BetaNode(this, that) - override fun collectData(occArray: Array, allSubst: MutableMap, Any>) { + override fun collectData(occArray: Array, allSubst: MutableMap, Any>) { right.collectData(occArray, allSubst) left.collectData(occArray, allSubst) } @@ -222,7 +227,7 @@ class ReteRuleMatcher(val rule: Rule) { fun addNode(n: ReteNode) { nodesList.add(n) } - + fun nodes() : Iterable = nodesList.subList(startIdx, nodesList.size) fun allNodes() : Iterable = nodesList @@ -234,7 +239,7 @@ class ReteRuleMatcher(val rule: Rule) { init { assert(layers.isNotEmpty()) } - + fun introduce(occIdx: Int, alphaNodes: Collection): Generation { // propagation history val initLayer = layers.last() @@ -280,27 +285,26 @@ class ReteRuleMatcher(val rule: Rule) { } - fun matches(): Collection { + fun matches(): Collection { val topLayer = layers.first() if (topLayer.final) { - val matches = ArrayList() + val matches = ArrayList() for (n in topLayer.nodes()) { // any excluded occurrences? if (n.containsOccurrence(skipOccIndices)) continue val allSubst = HashMap, Any>() - val occArray = arrayOfNulls(headSize) + val occArray = arrayOfNulls(headSize) n.collectData(occArray, allSubst) - - val occList = occArray.toMutableList() + + val occList = occArray.toList() as List val keptCount = rule.headKept().count() - matches.add(MatchRuleImpl(n, - rule, - allSubst, - occList.subList(0, keptCount), - occList.subList(keptCount, occList.size))) + matches.add(MatchRuleImpl(rule, + allSubst, + occList.subList(0, keptCount), + occList.subList(keptCount, occList.size))) } return matches @@ -315,10 +319,10 @@ class ReteRuleMatcher(val rule: Rule) { override fun rule(): Rule = rule // for tests only - override fun expand(occ: ConstraintOccurrence): ReteNetwork = + override fun expand(occ: Occurrence): ReteNetwork = expand(occ, bitSetOfOnes(headSize)) - override fun expand(occ: ConstraintOccurrence, mask: BitSet): ReteNetwork { + override fun expand(occ: Occurrence, mask: BitSet): ReteNetwork { // raising from the dead, huh? val occIdx = indexOf(occ) skipOccIndices.clear(occIdx) @@ -326,7 +330,7 @@ class ReteRuleMatcher(val rule: Rule) { val alphaNodes = arrayListOf() for (posInHead in mask.allSetBits()) { - val matcher = OccurrenceMatcher(emptySubst()) + val matcher = createOccurrenceMatcher(emptySubst()) if (matcher.matches(head[posInHead], occ)) { alphaNodes.add(AlphaNode(occ, posInHead, matcher.substitution())) } @@ -338,12 +342,12 @@ class ReteRuleMatcher(val rule: Rule) { return this } - override fun contract(occ: ConstraintOccurrence): ReteNetwork { + override fun contract(occ: Occurrence): ReteNetwork { skipOccIndices.set(indexOf(occ)) return this } - override fun matches(): Collection = generations.last().matches() + override fun matches(): Collection = generations.last().matches() override fun consumed(matchRule: MatchRule): MatchingProbe { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. @@ -358,11 +362,4 @@ class ReteRuleMatcher(val rule: Rule) { } -} - - - - - - - +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt new file mode 100644 index 00000000..f4b7ddf0 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt @@ -0,0 +1,187 @@ +/* + * 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.core.internal + +import com.github.andrewoma.dexx.collection.Sets +import jetbrains.mps.logic.reactor.core.* +import jetbrains.mps.logic.reactor.evaluation.MatchRule +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.logic.reactor.util.* +import java.util.* +import kotlin.collections.ArrayList +import com.github.andrewoma.dexx.collection.List as PersList +import com.github.andrewoma.dexx.collection.Map as PersMap +import com.github.andrewoma.dexx.collection.Set as PersSet +import com.github.andrewoma.dexx.collection.Vector as PersVector + +/** + * @author Fedor Isakov + */ +internal class RuleMatcherImpl(val rule: Rule) : RuleMatcher { + + val head = rule.headKept().toCollection(ArrayList(4)).apply { addAll(rule.headReplaced()) } + + val propagation = rule.headReplaced().count() == 0 + + val origins = IdentityHashMap() + + + override fun probe(): MatchingProbe = RuleMatchFringe(listOf(MatchNode(emptySubst())), + Sets.of(), + Sets.of(), + 0) + + inner class RuleMatchFringe(val nodes: List, + val seen: PersSet>, + val consumed: PersSet?>>, + val genId: Int) : MatchingProbe { + override fun rule(): Rule = rule + + override fun matches(): Collection { + return nodes.filter { it is ActiveMatchNode && it.complete && it.genId == genId } + .map { (it as ActiveMatchNode).toMatchRule() } + } + + override fun consumed(matchRule: MatchRule): MatchingProbe = + RuleMatchFringe(nodes, + seen, + consumed.add(origins.get(matchRule)?.signature!!), +// ((matchRule as MatchRuleImpl).origin as ActiveMatchNode).signature), + genId) + + override fun expand(occ: Occurrence): MatchingProbe = + expand(occ, bitSetOfOnes(head.size)) + + /** + * Expands the fringe by creating new leaf nodes that match the occurrence. + * Mask specifies possible slots for the occurrence. + */ + override fun expand(occ: Occurrence, mask: BitSet): RuleMatchFringe { + val reactivated = seen.contains(IdWrapper(occ)) + val newSeen = if (reactivated) seen else seen.add(IdWrapper(occ)) + val newNodes = ArrayList(nodes) + + val allSignatures = newNodes.map { it.signature }.toHashSet() + for (n in nodes) { + n.expand(occ, genId + 1, n.matchingVacant(mask)) + .filter { allSignatures.add(it.signature) || reactivated } // ensure reactivated have effect + .filter { !(propagation && reactivated && consumed.contains(it.signature)) } // ...unless propagation + .forEach { newNodes.add(it) } + } + + return RuleMatchFringe(newNodes, newSeen, consumed, genId + 1) + } + + override fun contract(occ: Occurrence): RuleMatchFringe { + val newNodes = nodes.mapNotNull { it.unrelatedOrNull(occ) } + return RuleMatchFringe(newNodes, seen, consumed, genId + 1) + } + + } + + open inner class MatchNode(val subst: Subst, val vacant: BitSet = bitSetOfOnes(head.size)) { + open val signature: ArrayList?> = + arrayListOf(* arrayOfNulls(head.size)) + + /** + * Returns the additional nodes built from this node on adding the occurrence. + * If the occurrence is already in the path, return empty sequence. + */ + fun expand(occ: Occurrence, genId: Int, matchingVacant: BitSet): List = + unrelatedOrNull(occ)?.let { n -> + ArrayList().also { expanded -> + for (headIdx in matchingVacant.allSetBits()) { + createOccurrenceMatcher(subst).run { + if (matches(head[headIdx], occ)) { + expanded.add(ActiveMatchNode(substitution(), n, occ, headIdx, genId)) + } + } + } + } + } ?: emptyList() + + + /** + * Returns this node if it doesn't have the occurrence in its path, null otherwise. + */ + open fun unrelatedOrNull(occ: Occurrence): MatchNode? = this + + fun matchingVacant(mask: BitSet) = mask.copyApply { and(vacant) } + + /** + * Folds the path to the root. + */ + inline protected fun fold(init: T, action: (T, ActiveMatchNode) -> T): T { + var rn = this + var curr = init + while (rn is ActiveMatchNode) { + curr = action(curr, rn) + rn = rn.parent + } + return curr + } + + /** + * Folds the path to the root. If an iteration yields null, fold is stopped and null is returned. + */ + inline protected fun foldUntilNull(init: T, action: (T, ActiveMatchNode) -> T?): T? { + var rn = this + var curr: T? = init + while (rn is ActiveMatchNode) { + curr = action(curr!!, rn) + if (curr == null) return null + rn = rn.parent + } + return curr + } + } + + inner class ActiveMatchNode(subst: Subst, + val parent: MatchNode, + val occurrence: Occurrence, + val headIndex: Int, + val genId: Int) : + MatchNode(subst, parent.vacant.clearBit(headIndex)) { + val complete = vacant.cardinality() == 0 + + override val signature: ArrayList?> = + ArrayList(parent.signature).also { it[headIndex] = IdWrapper(occurrence) } + + fun constraint(): Constraint = head[headIndex] + + override fun unrelatedOrNull(occ: Occurrence): ActiveMatchNode? = + foldUntilNull(this) { acc, rn -> if (rn.occurrence === occ) null else acc } + + + fun toMatchRule(): MatchRuleImpl { + val matched: Array = + fold(arrayOfNulls(head.size)) { arr, rn -> + arr[rn.headIndex] = rn.occurrence; arr + } as Array + + val mr = MatchRuleImpl(rule, + subst, + ArrayList(matched.take(rule.headKept().count())), + ArrayList(matched.takeLast(rule.headReplaced().count()))) + origins.put(mr, this) + return mr + } + } + + +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Store.kt similarity index 71% rename from reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt rename to reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Store.kt index 6fafb630..28165dd7 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/Store.kt @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 JetBrains s.r.o. + * 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. @@ -14,16 +14,19 @@ * limitations under the License. */ -package jetbrains.mps.logic.reactor.core +package jetbrains.mps.logic.reactor.core.internal import com.github.andrewoma.dexx.collection.Maps +import jetbrains.mps.logic.reactor.core.FrameObservable +import jetbrains.mps.logic.reactor.core.LogicalObserver +import jetbrains.mps.logic.reactor.core.Occurrence +import jetbrains.mps.logic.reactor.core.occurrence import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.logical.Logical -import jetbrains.mps.logic.reactor.program.Constraint import jetbrains.mps.logic.reactor.program.ConstraintSymbol -import jetbrains.mps.unification.Term import jetbrains.mps.logic.reactor.util.* +import java.util.* import com.github.andrewoma.dexx.collection.Map as PersMap import com.github.andrewoma.dexx.collection.Set as PersSet import com.github.andrewoma.dexx.collection.Vector as PersVector @@ -32,73 +35,53 @@ import com.github.andrewoma.dexx.collection.Vector as PersVector * @author Fedor Isakov */ -fun ConstraintOccurrence.isStored(): Boolean = - // TODO: superfluous cast - (this as StoreItem).stored - -fun ConstraintOccurrence.isAlive(): Boolean = - // TODO: superfluous cast - (this as StoreItem).alive - -interface StoreItem { - - var alive: Boolean - - var stored: Boolean - - fun terminate() - -} - -interface StoreKeeper { - - fun store(): Store - - fun addObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) - - fun removeObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) - -} - /** + * Constrants storeObserver. + * * TODO: make this class persistent. */ -class Store : LogicalObserver { +internal class Store : LogicalObserver { - val currentFrame: () -> StoreKeeper + val currentFrame: () -> FrameObservable - var symbol2occurrences: PersMap> + var symbol2occurrences: PersMap> - var logical2occurrences: PersMap>, IdHashSet> + var logical2occurrences: PersMap>, IdHashSet> - constructor(copyFrom: Store, currentFrame: () -> StoreKeeper) { + constructor(copyFrom: Store, currentFrame: () -> FrameObservable) { this.currentFrame = currentFrame this.symbol2occurrences = copyFrom.symbol2occurrences this.logical2occurrences = copyFrom.logical2occurrences } - constructor(copyFrom: StoreView, currentFrame: () -> StoreKeeper) { + + constructor(copyFrom: StoreView, currentFrame: () -> FrameObservable) { this.currentFrame = currentFrame - this.symbol2occurrences = copyFrom.constraintSymbols() - .fold(Maps.of()) { map, sym -> map.put(sym, IdHashSet(copyFrom.occurrences(sym))) } - - var l2o = Maps.of>, IdHashSet>() - var v2o = Maps.of>() - + var l2o = Maps.of>, IdHashSet>() + val storeItems = IdentityHashMap() copyFrom.allOccurrences().forEach { occ -> + val item = occ.constraint().occurrence(occ.arguments(), currentFrame) + storeItems.put(occ, item) occ.arguments().forEach { arg -> when (arg) { - is Logical<*> -> l2o = l2o.put(IdWrapper(arg.findRoot()), l2o[IdWrapper(arg.findRoot())]?.add(occ) ?: singletonIdSet(occ)) - is Any -> v2o = v2o.put(arg, v2o[arg]?.add(occ) ?: singletonIdSet(occ)) + is Logical<*> -> { + l2o = l2o.put(IdWrapper(arg.findRoot()), + l2o[IdWrapper(arg.findRoot())]?.add(item) ?: singletonIdSet(item)) + } } } } - this.logical2occurrences = l2o + this.symbol2occurrences = copyFrom.constraintSymbols().fold(Maps.of()) { map, sym -> + val copyFrom1 = copyFrom.occurrences(sym).map { occ -> storeItems.get(occ)!! } + val idHashSet = IdHashSet( copyFrom1 ) + + map.put(sym, idHashSet) + } } - constructor(currentFrame: () -> StoreKeeper) { + constructor(currentFrame: () -> FrameObservable) { this.currentFrame = currentFrame this.symbol2occurrences = Maps.of() this.logical2occurrences = Maps.of() @@ -122,7 +105,7 @@ class Store : LogicalObserver { } } - fun store(occ: ConstraintOccurrence) { + fun store(occ: Occurrence) { val symbol = occ.constraint().symbol() this.symbol2occurrences = symbol2occurrences.put(symbol, @@ -136,16 +119,15 @@ class Store : LogicalObserver { val argId = IdWrapper(value.findRoot()) this.logical2occurrences = logical2occurrences.put(argId, logical2occurrences[argId]?.add(occ) ?: singletonIdSet(occ)) - currentFrame().addObserver(value) { frame -> frame.store() } + currentFrame().addObserver(value) { frame -> frame.storeObserver() } } } } - // TODO: superfluous cast - (occ as StoreItem).stored = true + occ.stored = true } - fun discard(occ: ConstraintOccurrence, profiler: Profiler? = null, tag: String? = null): Unit { + fun discard(occ: Occurrence, profiler: Profiler? = null, tag: String? = null): Unit { val symbol = occ.constraint().symbol() symbol2occurrences[symbol]?.remove(occ)?.let { newList -> @@ -168,13 +150,12 @@ class Store : LogicalObserver { } } - // TODO: superfluous cast - (occ as StoreItem).stored = false + occ.stored = false occ.terminate() } fun allOccurrences(): Sequence { - return symbol2occurrences.values().flatten().filter { co -> co.isStored() }.asSequence() + return symbol2occurrences.values().flatten().filter { co -> co.stored }.asSequence() } fun view(): StoreView = StoreViewImpl(allOccurrences()) diff --git a/reactor/Test/src/program/MockProgram.kt b/reactor/Test/src/program/MockProgram.kt index 3ea7fa1d..a5dfcbc4 100644 --- a/reactor/Test/src/program/MockProgram.kt +++ b/reactor/Test/src/program/MockProgram.kt @@ -116,7 +116,7 @@ class MockProgram(val name: String, val handlers: List, val registry: M } -class MockConstraintRegistry(val sessionSolver: SessionSolver) { +class MockConstraintRegistry() { private val myConstraintArgTypes = HashMap>>().withDefault { Collections.emptyList() } diff --git a/reactor/Test/src/solver/EqualsSolver.kt b/reactor/Test/src/solver/EqualsSolver.kt index aa804284..a46466bd 100644 --- a/reactor/Test/src/solver/EqualsSolver.kt +++ b/reactor/Test/src/solver/EqualsSolver.kt @@ -117,7 +117,7 @@ infix fun T.is_eq(value: T): Boolean = EvaluationSession.current().let } val args = session.program().instantiateArguments(predicate.arguments(), logicalContext, invocationContext) val inv = predicate.invocation(args, logicalContext, invocationContext) - session.sessionSolver().ask(inv) + session.ask(inv) } infix fun T.eq(value: T) = EvaluationSession.current().let { session -> @@ -128,7 +128,7 @@ infix fun T.eq(value: T) = EvaluationSession.current().let { session - } val args = session.program().instantiateArguments(predicate.arguments(), logicalContext, invocationContext) val inv = predicate.invocation(args, logicalContext, invocationContext) - session.sessionSolver().tell(inv) + session.tell(inv) } private fun mockLogicalContext(): LogicalContext { diff --git a/reactor/Test/src/solver/MockSessionSolver.kt b/reactor/Test/src/solver/MockSessionSolver.kt deleted file mode 100644 index 66f1a919..00000000 --- a/reactor/Test/src/solver/MockSessionSolver.kt +++ /dev/null @@ -1,15 +0,0 @@ -package solver - -import jetbrains.mps.logic.reactor.evaluation.AbstractSolver -import jetbrains.mps.logic.reactor.evaluation.EvaluationTrace -import jetbrains.mps.logic.reactor.evaluation.SessionSolver -import jetbrains.mps.logic.reactor.program.PredicateSymbol - -/** - * @author Fedor Isakov - */ - -open class MockSessionSolver() : SessionSolver() { - - -} diff --git a/reactor/Test/test/LogicalHelper.kt b/reactor/Test/test/LogicalHelper.kt index ee6971c8..da2c3e04 100644 --- a/reactor/Test/test/LogicalHelper.kt +++ b/reactor/Test/test/LogicalHelper.kt @@ -1,9 +1,9 @@ import jetbrains.mps.logic.reactor.core.LogicalObserver -import jetbrains.mps.logic.reactor.core.LogicalImpl -import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation -import jetbrains.mps.logic.reactor.logical.* -import jetbrains.mps.logic.reactor.program.Predicate -import jetbrains.mps.logic.reactor.program.PredicateSymbol +import jetbrains.mps.logic.reactor.core.internal.anonLogical +import jetbrains.mps.logic.reactor.core.internal.namedLogical +import jetbrains.mps.logic.reactor.logical.JoinableLogical +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.MetaLogical import java.util.* /** @@ -11,31 +11,31 @@ import java.util.* */ -fun anon(value: T) = LogicalImpl(value) +fun anon(value: T) = anonLogical(value) -fun logical(name: String) = LogicalImpl(name) +fun logical(name: String) = namedLogical(name) -fun logical(name1: String, name2: String) = Pair(LogicalImpl(name1), LogicalImpl(name2)) +fun logical(name1: String, name2: String) = Pair(namedLogical(name1), namedLogical(name2)) -fun logical(name1: String, name2: String, name3: String) = - Triple(LogicalImpl(name1), LogicalImpl(name2), LogicalImpl(name3)) +fun logical(name1: String, name2: String, name3: String) = + Triple(namedLogical(name1), namedLogical(name2), namedLogical(name3)) -inline fun metaLogical(name: String) = MetaLogical(name, T::class.java) +inline fun metaLogical(name: String) = MetaLogical(name, T::class.java) -inline fun metaLogical(name1: String, name2: String) = +inline fun metaLogical(name1: String, name2: String) = Pair(MetaLogical(name1, T::class.java), MetaLogical(name2, T::class.java)) -inline fun metaLogical(name1: String, name2: String, name3: String) = +inline fun metaLogical(name1: String, name2: String, name3: String) = Triple( MetaLogical(name1, T::class.java), MetaLogical(name2, T::class.java), MetaLogical(name3, T::class.java)) -fun Logical.get(): T = findRoot().value() +fun Logical.get(): T = findRoot().value() -fun Logical.getNullable(): T? = findRoot().value() +fun Logical.getNullable(): T? = findRoot().value() -fun Logical.set(t: T) { +fun Logical.set(t: T) { if (this is JoinableLogical) findRoot().setValue(t) else @@ -52,9 +52,13 @@ class MockObserver : LogicalObserver { val events = ArrayList() - override fun valueUpdated(logical: Logical<*>) { events.add(value(logical))} + override fun valueUpdated(logical: Logical<*>) { + events.add(value(logical)) + } - override fun parentUpdated(logical: Logical<*>) { events.add(parent(logical))} + override fun parentUpdated(logical: Logical<*>) { + events.add(parent(logical)) + } fun getAndClearEvents(): Set { val tmp = ArrayList(events) diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index 7f6b5ef2..b55b2f2f 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -1,10 +1,12 @@ -import jetbrains.mps.logic.reactor.core.StoreItem +import jetbrains.mps.logic.reactor.core.FrameObservable +import jetbrains.mps.logic.reactor.core.LogicalObserver +import jetbrains.mps.logic.reactor.core.Occurrence +import jetbrains.mps.logic.reactor.core.occurrence import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence -import jetbrains.mps.logic.reactor.evaluation.EvaluationFeedbackHandler +import jetbrains.mps.logic.reactor.logical.Logical import jetbrains.mps.logic.reactor.logical.LogicalContext import jetbrains.mps.logic.reactor.program.* import program.MockConstraint -import solver.EqualsSolver import solver.TestEqPredicate import java.util.* @@ -22,11 +24,11 @@ class Builder(val env: Environment, val handlers: List) { class Environment(val programBuilder: ProgramBuilder? = null) { } -fun programWithRules(vararg ruleBuilders : Environment.() -> Rule): Builder { +fun programWithRules(vararg ruleBuilders: Environment.() -> Rule): Builder { return programWithRules(Environment(), ruleBuilders) } -fun programWithRules(pb: ProgramBuilder, vararg ruleBuilders : Environment.() -> Rule): Builder { +fun programWithRules(pb: ProgramBuilder, vararg ruleBuilders: Environment.() -> Rule): Builder { return programWithRules(Environment(pb), ruleBuilders) } @@ -34,13 +36,13 @@ private fun programWithRules(env: Environment, ruleBuilders: Array Handler): Builder { +fun programWithHandlers(vararg handlerBuilders: Environment.() -> Handler): Builder { return builder(Environment(), handlerBuilders) } private fun builder(env: Environment, handlerBlocks: Array Handler>): Builder { val handlers = ArrayList() - with (env) { + with(env) { for (block in handlerBlocks) { handlers.add(block()) } @@ -56,7 +58,7 @@ fun handler(name: String, primary: Iterable, vararg ruleBlocks hb.toHandler() } -fun rule(tag: String, vararg component:RB.() -> Unit): Environment.() -> Rule = { +fun rule(tag: String, vararg component: RB.() -> Unit): Environment.() -> Rule = { val rb = RB(this, tag) for (cmp in component) { rb.cmp() @@ -64,23 +66,23 @@ fun rule(tag: String, vararg component:RB.() -> Unit): Environment.() -> Rule = rb.toRule() } -fun headKept(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { - appendHeadKept( * buildConjunction(Constraint::class.java, env, content).toArray()) +fun headKept(vararg content: ConjBuilder.() -> Unit): RB.() -> Unit = { + appendHeadKept(* buildConjunction(Constraint::class.java, env, content).toArray()) } -fun headReplaced(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { - appendHeadReplaced( * buildConjunction(Constraint::class.java, env, content).toArray()) +fun headReplaced(vararg content: ConjBuilder.() -> Unit): RB.() -> Unit = { + appendHeadReplaced(* buildConjunction(Constraint::class.java, env, content).toArray()) } -fun guard(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { - appendGuard( * buildConjunction(Predicate::class.java, env, content).toArray()) +fun guard(vararg content: ConjBuilder.() -> Unit): RB.() -> Unit = { + appendGuard(* buildConjunction(Predicate::class.java, env, content).toArray()) } -fun body(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { +fun body(vararg content: ConjBuilder.() -> Unit): RB.() -> Unit = { appendBody(false, * buildConjunction(AndItem::class.java, env, content).toArray()) } -fun altBody(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { +fun altBody(vararg content: ConjBuilder.() -> Unit): RB.() -> Unit = { appendBody(true, * buildConjunction(AndItem::class.java, env, content).toArray()) } @@ -93,7 +95,21 @@ fun equals(left: Any, right: Any): ConjBuilder.() -> Unit = { add(TestEqPredicate(left, right)) } -fun occurrence(id: String, vararg args: Any) : ConstraintOccurrence = TestConstraintOccurrence(id, * args) +fun occurrence(id: String, vararg args: Any): Occurrence = + MockConstraint(ConstraintSymbol.symbol(id, args.size)).occurrence(listOf(* args), { fooObservable }) + +object fooObservable : FrameObservable { + override fun storeObserver(): LogicalObserver { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { + } + + override fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} class RB(val env: Environment, tag: String) : RuleBuilder(tag) { @@ -103,9 +119,8 @@ class ConjBuilder(val type: Class, val env: Environment) { val constraints = ArrayList() fun createConstraint(args: Array, id: String): Constraint { - return env.programBuilder ?. - constraint(ConstraintSymbol(id, args.size), * args) ?: - MockConstraint(ConstraintSymbol(id, args.size), * args) + return env.programBuilder?.constraint(ConstraintSymbol(id, args.size), * args) + ?: MockConstraint(ConstraintSymbol(id, args.size), * args) } fun add(item: AndItem): Unit { @@ -133,40 +148,10 @@ class ConjBuilder(val type: Class, val env: Environment) { private fun buildConjunction(type: Class, env: Environment, - content: Array Unit>): ConjBuilder -{ + content: Array Unit>): ConjBuilder { val conjBuilder = ConjBuilder(type, env) for (c in content) { conjBuilder.c() } return conjBuilder } - -data class TestConstraintOccurrence(val constraint: Constraint, val arguments: List, val id: Int) : - ConstraintOccurrence, - StoreItem -{ - override var alive: Boolean = true - - override var stored: Boolean = false - - companion object { - val random = Random() - } - - constructor(id: String, vararg args: Any) : - this(MockConstraint(ConstraintSymbol.symbol(id, args.size)), listOf(* args), random.nextInt()) {} - - override fun constraint(): Constraint = constraint - - override fun arguments(): List = arguments - - override fun logicalContext(): LogicalContext = TODO() - - override fun terminate() { - this.alive = false - } - - override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})" - -} \ No newline at end of file diff --git a/reactor/Test/test/TestController.kt b/reactor/Test/test/TestController.kt index e468c1cc..cb0e8a2f 100644 --- a/reactor/Test/test/TestController.kt +++ b/reactor/Test/test/TestController.kt @@ -1,15 +1,18 @@ -import jetbrains.mps.logic.reactor.core.* +import jetbrains.mps.logic.reactor.core.Controller +import jetbrains.mps.logic.reactor.core.EvaluationSessionEx +import jetbrains.mps.logic.reactor.core.internal.createController +import jetbrains.mps.logic.reactor.core.internal.logical import jetbrains.mps.logic.reactor.evaluation.* import jetbrains.mps.logic.reactor.logical.Logical -import jetbrains.mps.logic.reactor.program.* +import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.logic.reactor.program.Program +import jetbrains.mps.logic.reactor.program.Rule import jetbrains.mps.unification.Term import jetbrains.mps.unification.test.MockTerm.* import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Test -import solver.EqualsSolver -import solver.MockSessionSolver import solver.eq import solver.is_eq @@ -21,30 +24,31 @@ import solver.is_eq class TestController { - @Before fun beforeTest() { + @Before + fun beforeTest() { } - @After fun afterTest() { + @After + fun afterTest() { MockSession.deinit() } - private class MockSession(val program: Program, val solver: SessionSolver) : EvaluationSession(), SessionObjects { + private class MockSession(program: Program) : + EvaluationSessionEx(program, EvaluationTrace.NULL) { 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 { - override fun current(): EvaluationSession = session + override fun controller(): Controller = controller + + class MockBackend(val session: MockSession) : Backend { + override fun current(): MockSession = session override fun createConfig(program: Program): Config = TODO() } companion object { - lateinit var ourBackend : MockBackend + lateinit var ourBackend: MockBackend - fun init(program: Program, solver: SessionSolver) { - ourBackend = MockBackend(MockSession(program, solver)) + fun init(program: Program) { + ourBackend = MockBackend(MockSession(program)) setBackend(ourBackend) } @@ -54,24 +58,19 @@ class TestController { } } - private fun sessionSolver(): SessionSolver = MockSessionSolver() - private fun Builder.controller(vararg occurrences: ConstraintOccurrence): Controller { - val solver = sessionSolver() - val program = MockProgram("test", handlers, registry = MockConstraintRegistry(solver)) - MockSession.init(program, solver) - val controller = Controller(program, storeView = MockStoreView(listOf(* occurrences))) + val program = MockProgram("test", handlers, registry = MockConstraintRegistry()) + MockSession.init(program) + val controller = createController(program, storeView = MockStoreView(listOf(* occurrences))) MockSession.ourBackend.session.controller = controller return controller } private fun Builder.controllerWithFeedback(feedbackHandler: EvaluationFeedbackHandler, - vararg occurrences: ConstraintOccurrence): Controller - { - val solver = sessionSolver() - val program = MockProgram("test", handlers, registry = MockConstraintRegistry(solver)) - MockSession.init(program, solver) - val controller = Controller(program, storeView = MockStoreView(listOf(* occurrences)), feedbackHandler = feedbackHandler) + vararg occurrences: ConstraintOccurrence): Controller { + val program = MockProgram("test", handlers, registry = MockConstraintRegistry()) + MockSession.init(program) + val controller = createController(program, storeView = MockStoreView(listOf(* occurrences)), feedbackHandler = feedbackHandler) MockSession.ourBackend.session.controller = controller return controller } @@ -146,7 +145,7 @@ class TestController { @Test fun basicExpression() { - var test : String = "not initialized" + var test: String = "not initialized" programWithRules( rule("main", headKept( @@ -171,7 +170,7 @@ class TestController { constraint("main") ), body( - statement ({ test.set("value") }) + statement({ test.set("value") }) )) ).run { controller().evaluate(occurrence("main")) @@ -181,7 +180,7 @@ class TestController { @Test fun basicLogical() { - var test : String? = "not initialized" + var test: String? = "not initialized" val x = logical("x") x.setValue("expected") programWithRules( @@ -190,7 +189,7 @@ class TestController { constraint("main") ), body( - statement ({ test = x.get() } ) + statement({ test = x.get() }) )) ).run { controller().evaluate(occurrence("main")) @@ -200,8 +199,8 @@ class TestController { @Test fun logicalCopy() { - var test : String? = "not initialized" - val (x,y) = logical("x", "y") + var test: String? = "not initialized" + val (x, y) = logical("x", "y") x.setValue("expected") programWithRules( rule("main", @@ -217,7 +216,7 @@ class TestController { constraint("next") ), body( - statement ({ test = y.get() }) + statement({ test = y.get() }) )) ).run { controller().evaluate(occurrence("main")).run { @@ -231,8 +230,8 @@ class TestController { @Test fun basicGuard() { - var test1 : String = "not initialized 1" - var test2 : String = "not initialized 2" + var test1: String = "not initialized 1" + var test2: String = "not initialized 2" programWithRules( rule("main1", headKept( @@ -242,14 +241,14 @@ class TestController { expression { false } ), body( - statement { test1 = "not expected" } + statement { test1 = "not expected" } )), rule("main2", headKept( constraint("main") ), guard( - expression { true } + expression { true } ), body( statement { test2 = "expected" } @@ -276,10 +275,11 @@ class TestController { ) ) ).controller().apply { - a.set("value") }.evaluate(occurrence("foo", a)).run { + a.set("value") + }.evaluate(occurrence("foo", a)).run { assertSame(1, allOccurrences().toList().size) val co = allOccurrences().first() - assertEquals(ConstraintSymbol("bar",1), co.constraint().symbol()) + assertEquals(ConstraintSymbol("bar", 1), co.constraint().symbol()) assertEquals(1, co.arguments().size) val arg = co.arguments().first() assertNotEquals(a, arg) @@ -291,18 +291,18 @@ class TestController { fun occurrenceTerminated() { programWithRules( rule("first", - headKept( constraint("foo") ), body( constraint("expected1") ) + headKept(constraint("foo")), body(constraint("expected1")) ), rule("second", - headKept( constraint("foo") ), body( constraint("bar") ) + headKept(constraint("foo")), body(constraint("bar")) ), rule("third", - headKept( constraint("foo") ), body( constraint("unexpected") ) + headKept(constraint("foo")), body(constraint("unexpected")) ), rule("fourth", - headReplaced( constraint("bar"), - constraint("foo") ), - body( constraint("expected2") ) + headReplaced(constraint("bar"), + constraint("foo")), + body(constraint("expected2")) ) ).controller().evaluate(occurrence("foo")).run { assertEquals( @@ -315,18 +315,18 @@ class TestController { fun occurrenceKeptActive() { programWithRules( rule("first", - headKept( constraint("foo") ), body( constraint("bar") ) + headKept(constraint("foo")), body(constraint("bar")) ), rule("second", - headReplaced( constraint("foo") ), body( constraint("expected1") ) + headReplaced(constraint("foo")), body(constraint("expected1")) ), rule("third", - headReplaced( constraint("foo") ), body( constraint("unexpected") ) + headReplaced(constraint("foo")), body(constraint("unexpected")) ), rule("fourth", - headKept( constraint("foo") ), - headReplaced( constraint("bar") ), - body( constraint("expected2") ) + headKept(constraint("foo")), + headReplaced(constraint("bar")), + body(constraint("expected2")) ) ).controller().evaluate(occurrence("foo")).run { assertEquals( @@ -340,31 +340,31 @@ class TestController { val X = metaLogical("X") programWithRules( rule("zeroth", - headKept( constraint("foo") ), body( statement({ x -> x.set(999) }, X), - constraint("bar", X)) + headKept(constraint("foo")), body(statement({ x -> x.set(999) }, X), + constraint("bar", X)) ), rule("first", - headKept( constraint("foo") ), - body( constraint("bar", X), - constraint("qux", X)) + headKept(constraint("foo")), + body(constraint("bar", X), + constraint("qux", X)) ), rule("second", - headReplaced( constraint("qux", X) ), - body( constraint("expected1"), - statement({ x -> x.set(123) }, X)) + headReplaced(constraint("qux", X)), + body(constraint("expected1"), + statement({ x -> x.set(123) }, X)) ), rule("third", - headReplaced( constraint("foo") ), - body( constraint("unexpected")) + headReplaced(constraint("foo")), + body(constraint("unexpected")) ), rule("fourth", - headReplaced( constraint("foo") ), - headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), - body( constraint("expected2") ) + headReplaced(constraint("foo")), + headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 123 }, X)), + body(constraint("expected2")) ), rule("fifth", - headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 999 }, X)), - body( constraint("expected3", X)) + headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 999 }, X)), + body(constraint("expected3", X)) ) ).controller().evaluate(occurrence("foo")).run { assertEquals(3, allOccurrences().count()) @@ -381,28 +381,28 @@ class TestController { val (X, Y) = metaLogical("X", "Y") programWithRules( rule("first", - headKept( constraint("foo") ), - body( constraint("bar", X), - constraint("qux", Y), - statement({ x, y -> eq(x, y) }, X, Y)) + headKept(constraint("foo")), + body(constraint("bar", X), + constraint("qux", Y), + statement({ x, y -> eq(x, y) }, X, Y)) ), rule("second", - headReplaced( constraint("qux", Y) ), - body( constraint("expected1"), - statement({ y -> y.set(123) }, Y)) + headReplaced(constraint("qux", Y)), + body(constraint("expected1"), + statement({ y -> y.set(123) }, Y)) ), rule("third", - headReplaced( constraint("foo") ), - body( constraint("unexpected")) + headReplaced(constraint("foo")), + body(constraint("unexpected")) ), rule("fourth", - headReplaced( constraint("foo") ), - headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), - body( constraint("expected2") ) + headReplaced(constraint("foo")), + headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 123 }, X)), + body(constraint("expected2")) ), rule("fifth", - headKept( constraint("bar", X) ), - body( constraint("expected3", X)) + headKept(constraint("bar", X)), + body(constraint("expected3", X)) ) ).controller().evaluate(occurrence("foo")).run { assertEquals(3, allOccurrences().count()) @@ -419,35 +419,35 @@ class TestController { val (X, Y) = metaLogical("X", "Y") programWithRules( rule("first", - headKept( constraint("foo") ), - body( constraint("bar", X), - statement({ x, y -> eq(x, y) }, X, Y), - constraint("qux", Y)) + headKept(constraint("foo")), + body(constraint("bar", X), + statement({ x, y -> eq(x, y) }, X, Y), + constraint("qux", Y)) ), rule("second", - headReplaced( constraint("qux", Y) ), - body( constraint("expected1"), - statement({ y -> y.set(123) }, Y)) + headReplaced(constraint("qux", Y)), + body(constraint("expected1"), + statement({ y -> y.set(123) }, Y)) ), rule("third", - headReplaced( constraint("foo") ), - body( constraint("unexpected")) + headReplaced(constraint("foo")), + body(constraint("unexpected")) ), rule("fourth", - headReplaced( constraint("foo") ), - headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), - body( constraint("expected2") ) + headReplaced(constraint("foo")), + headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 123 }, X)), + body(constraint("expected2")) ), rule("fifth", - headKept( constraint("bar", X) ), - body( constraint("expected3", X)) + headKept(constraint("bar", X)), + body(constraint("expected3", X)) ) ).controller().evaluate(occurrence("foo")).run { assertEquals(3, allOccurrences().count()) - assertEquals(setOf( ConstraintSymbol("expected1", 0), - ConstraintSymbol("expected2", 0), - ConstraintSymbol("expected3", 1)), - allOccurrences().map { co -> co.constraint().symbol() }.toSet()) + assertEquals(setOf(ConstraintSymbol("expected1", 0), + ConstraintSymbol("expected2", 0), + ConstraintSymbol("expected3", 1)), + allOccurrences().map { co -> co.constraint().symbol() }.toSet()) val ex3 = allOccurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first() assertEquals(123, (ex3.arguments().first() as Logical).value()) } @@ -456,27 +456,27 @@ class TestController { @Test fun correctRulesOrder() { - val X= metaLogical("X") + val X = metaLogical("X") programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x -> x.set(1) }, X), - constraint("bar"), - constraint("foo", X) ) + headReplaced(constraint("main")), body(statement({ x -> x.set(1) }, X), + constraint("bar"), + constraint("foo", X)) ), rule("foo_if_zero", - headReplaced( constraint("foo", X) ), guard( expression({ x -> x.get() == 0 }, X) ), - body( constraint("foo_zero") ) + headReplaced(constraint("foo", X)), guard(expression({ x -> x.get() == 0 }, X)), + body(constraint("foo_zero")) ), rule("foo_and_bar", - headReplaced( constraint("foo", X) ), - headKept( constraint("bar") ), - body( constraint("foo_and_bar") ) + headReplaced(constraint("foo", X)), + headKept(constraint("bar")), + body(constraint("foo_and_bar")) ), rule("foo_if_non_zero", - headReplaced( constraint("foo", X) ), - guard( expression({ x -> x.get() != 0 }, X) ), - body( constraint("foo_non_zero") ) + headReplaced(constraint("foo", X)), + guard(expression({ x -> x.get() != 0 }, X)), + body(constraint("foo_non_zero")) ) ).controller().evaluate(occurrence("main")).run { assertEquals(setOf(ConstraintSymbol("bar", 0), ConstraintSymbol("foo_and_bar", 0)), constraintSymbols()) @@ -491,19 +491,19 @@ class TestController { val W = metaLogical("W") programWithRules( - rule("main", - headReplaced(constraint("main")), body( statement({ w -> w.set(42) }, W), - constraint("foo", W, "a{c}"), - constraint("foo", W, "a{b}"), - constraint("foo", W, "a{d}")) - ), - rule("expected", - headReplaced( - constraint("foo", X, "a{d}"), - constraint("foo", Y, "a{b}"), - constraint("foo", Z, "a{c}")), body(constraint("done"))) - - ).controller().evaluate(occurrence("main")).run { + rule("main", + headReplaced(constraint("main")), body(statement({ w -> w.set(42) }, W), + constraint("foo", W, "a{c}"), + constraint("foo", W, "a{b}"), + constraint("foo", W, "a{d}")) + ), + rule("expected", + headReplaced( + constraint("foo", X, "a{d}"), + constraint("foo", Y, "a{b}"), + constraint("foo", Z, "a{c}")), body(constraint("done"))) + + ).controller().evaluate(occurrence("main")).run { assertEquals(setOf(ConstraintSymbol("done", 0)), constraintSymbols()) assertEquals(1, occurrences(ConstraintSymbol.symbol("done", 0)).count()) } @@ -511,38 +511,38 @@ class TestController { @Test fun reactivateOnUnion() { - val (X1,Y1,Z1) = metaLogical("X1", "Y1", "Z1") - val (X2,Y2,Z2) = metaLogical("X2", "Y2", "Z2") - val (X3,Y3,Z3) = metaLogical("X3", "Y3", "Z3") + val (X1, Y1, Z1) = metaLogical("X1", "Y1", "Z1") + val (X2, Y2, Z2) = metaLogical("X2", "Y2", "Z2") + val (X3, Y3, Z3) = metaLogical("X3", "Y3", "Z3") var count = 0 programWithRules( rule("main", - headReplaced( constraint("main") ), - body( - statement({ z -> z.set(0) }, Z1), - constraint("foo", X1, Z1), - constraint("foo", Y1, Z1), - statement({ x, y -> eq(x, y) }, X1, Y1) ) + headReplaced(constraint("main")), + body( + statement({ z -> z.set(0) }, Z1), + constraint("foo", X1, Z1), + constraint("foo", Y1, Z1), + statement({ x, y -> eq(x, y) }, X1, Y1)) ), rule("capture_foo", - headKept( constraint("foo", X2, Y2) ), - body( - statement({ z -> z.set(count++) }, Z2), - constraint("capture", Z2) ) + headKept(constraint("foo", X2, Y2)), + body( + statement({ z -> z.set(count++) }, Z2), + constraint("capture", Z2)) ), rule("capture_foo_foo", - headKept( constraint("foo", X3, Z3) ), - headReplaced( constraint("foo", Y3, Z3) ), - guard( - expression({ x, y -> is_eq(x, y) }, X3, Y3)), - body( - constraint("replaced") ) + headKept(constraint("foo", X3, Z3)), + headReplaced(constraint("foo", Y3, Z3)), + guard( + expression({ x, y -> is_eq(x, y) }, X3, Y3)), + body( + constraint("replaced")) ) ).controller().evaluate(occurrence("main")).run { - assertEquals(setOf( ConstraintSymbol("foo", 2), - ConstraintSymbol("capture", 1), - ConstraintSymbol("replaced", 0)), - constraintSymbols()) + assertEquals(setOf(ConstraintSymbol("foo", 2), + ConstraintSymbol("capture", 1), + ConstraintSymbol("replaced", 0)), + constraintSymbols()) assertEquals(1, occurrences(ConstraintSymbol("foo", 2)).count()) assertEquals(2, occurrences(ConstraintSymbol("capture", 1)).count()) assertEquals(1, occurrences(ConstraintSymbol("replaced", 0)).count()) @@ -551,25 +551,25 @@ class TestController { @Test fun propagationHistory() { - val (X,Y,Z) = metaLogical("X", "Y", "Z") + val (X, Y, Z) = metaLogical("X", "Y", "Z") programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x, y -> eq(x, y) }, X, Y), // rank(X) = 1 - constraint("foo", Y), - constraint("bar", Z), - // update Z's parent - statement({ x, z -> eq(x, z) }, X, Z) ) + headReplaced(constraint("main")), body(statement({ x, y -> eq(x, y) }, X, Y), // rank(X) = 1 + constraint("foo", Y), + constraint("bar", Z), + // update Z's parent + statement({ x, z -> eq(x, z) }, X, Z)) ), rule("foobar", - headKept( constraint("foo", X) ), - headKept( constraint("bar", Y) ), - body( constraint("foobar") ) + headKept(constraint("foo", X)), + headKept(constraint("bar", Y)), + body(constraint("foobar")) ) ).controller().evaluate(occurrence("main")).run { - assertEquals(setOf( ConstraintSymbol("foo", 1), - ConstraintSymbol("bar", 1), - ConstraintSymbol("foobar", 0)), - constraintSymbols()) + assertEquals(setOf(ConstraintSymbol("foo", 1), + ConstraintSymbol("bar", 1), + ConstraintSymbol("foobar", 0)), + constraintSymbols()) assertEquals(1, occurrences(ConstraintSymbol("foo", 1)).count()) assertEquals(1, occurrences(ConstraintSymbol("bar", 1)).count()) assertEquals(1, occurrences(ConstraintSymbol("foobar", 0)).count()) @@ -581,19 +581,19 @@ class TestController { val X = metaLogical("X") programWithRules( rule("main", - headReplaced( constraint("main") ), body( constraint("foo", X), - statement({ x -> eq(x, "doh") }, X) - ) + headReplaced(constraint("main")), body(constraint("foo", X), + statement({ x -> eq(x, "doh") }, X) + ) ), rule("foobar", - headKept( constraint("foo", X) ), - guard( expression ({ x -> x.isBound }, X) ), - body( constraint("foobar") ) + headKept(constraint("foo", X)), + guard(expression({ x -> x.isBound }, X)), + body(constraint("foobar")) ) ).controller().evaluate(occurrence("main")).run { - assertEquals(setOf( ConstraintSymbol("foo", 1), - ConstraintSymbol("foobar", 0)), - constraintSymbols()) + assertEquals(setOf(ConstraintSymbol("foo", 1), + ConstraintSymbol("foobar", 0)), + constraintSymbols()) assertEquals(1, occurrences(ConstraintSymbol("foo", 1)).count()) assertEquals(1, occurrences(ConstraintSymbol("foobar", 0)).count()) } @@ -604,23 +604,23 @@ class TestController { val (X, Y) = metaLogical("X", "Y") programWithRules( rule("main", - headReplaced( constraint("main") ), body( constraint("foo", X), - constraint("bar", Y), - statement({ x -> eq(x, "doh") }, X) - ) + headReplaced(constraint("main")), body(constraint("foo", X), + constraint("bar", Y), + statement({ x -> eq(x, "doh") }, X) + ) ), rule("foobar", - headKept( constraint("foo", X), - constraint("bar", Y) + headKept(constraint("foo", X), + constraint("bar", Y) ), - guard( expression ({ x -> x.isBound }, X) ), - body( constraint("foobar") ) + guard(expression({ x -> x.isBound }, X)), + body(constraint("foobar")) ) ).controller().evaluate(occurrence("main")).run { - assertEquals(setOf( ConstraintSymbol("foo", 1), - ConstraintSymbol("bar", 1), - ConstraintSymbol("foobar", 0)), - constraintSymbols()) + assertEquals(setOf(ConstraintSymbol("foo", 1), + ConstraintSymbol("bar", 1), + ConstraintSymbol("foobar", 0)), + constraintSymbols()) assertEquals(1, occurrences(ConstraintSymbol("foo", 1)).count()) assertEquals(1, occurrences(ConstraintSymbol("bar", 1)).count()) assertEquals(1, occurrences(ConstraintSymbol("foobar", 0)).count()) @@ -629,37 +629,37 @@ class TestController { @Test fun removeObserver() { - val (X1,Y1,Z1) = metaLogical("X1", "Y1", "Z1") + val (X1, Y1, Z1) = metaLogical("X1", "Y1", "Z1") val X2 = metaLogical("X2") - val (X3,Y3) = metaLogical("X3", "Y3") + val (X3, Y3) = metaLogical("X3", "Y3") val X4 = metaLogical("X4") programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x, y -> eq(x, y) }, X1, Y1), // rank(X) = 1 - statement({ x -> x.set(42) }, X1), - constraint("match", Z1, X1), - constraint("trigger", Z1) ) + headReplaced(constraint("main")), body(statement({ x, y -> eq(x, y) }, X1, Y1), // rank(X) = 1 + statement({ x -> x.set(42) }, X1), + constraint("match", Z1, X1), + constraint("trigger", Z1)) ), rule("trigger", - headReplaced( constraint("trigger", X2) ), - body( constraint("foobar", X2) ) + headReplaced(constraint("trigger", X2)), + body(constraint("foobar", X2)) ), rule("nofoobar", - headReplaced( constraint("foobar", X3), - constraint("match", X3, Y3) ), - body( constraint("expected"), - constraint("blah"), - statement({ x, z -> eq(x, z) }, X3, Y3) ) + headReplaced(constraint("foobar", X3), + constraint("match", X3, Y3)), + body(constraint("expected"), + constraint("blah"), + statement({ x, z -> eq(x, z) }, X3, Y3)) ), rule("blah", - headReplaced( constraint("blah") ), - headReplaced( constraint("foobar", X4) ), - body( constraint("unexpected") ) + headReplaced(constraint("blah")), + headReplaced(constraint("foobar", X4)), + body(constraint("unexpected")) ) ).controller().evaluate(occurrence("main")).run { - assertEquals(setOf( ConstraintSymbol("blah", 0), - ConstraintSymbol("expected", 0)), - constraintSymbols()) + assertEquals(setOf(ConstraintSymbol("blah", 0), + ConstraintSymbol("expected", 0)), + constraintSymbols()) assertEquals(1, occurrences(ConstraintSymbol("blah", 0)).count()) assertEquals(1, occurrences(ConstraintSymbol("expected", 0)).count()) } @@ -667,26 +667,26 @@ class TestController { @Test fun reactivateOnUnionKeepValue() { - val (X,Y,Z) = metaLogical("X", "Y", "Z") + val (X, Y, Z) = metaLogical("X", "Y", "Z") programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x, y -> eq(x, y) }, X, Y), // rank(X) = 1 - statement({ z -> z.set(42) }, Z), - constraint("foo", Z), - statement({ x, z -> eq(x, z) }, X, Z) ) + headReplaced(constraint("main")), body(statement({ x, y -> eq(x, y) }, X, Y), // rank(X) = 1 + statement({ z -> z.set(42) }, Z), + constraint("foo", Z), + statement({ x, z -> eq(x, z) }, X, Z)) ), rule("capture_foo_free", - headKept( constraint("foo", X) ), guard( expression({ x -> x.getNullable() == null }, X) ), - body( constraint("free") ) + headKept(constraint("foo", X)), guard(expression({ x -> x.getNullable() == null }, X)), + body(constraint("free")) ), rule("capture_foo_assigned", - headKept( constraint("foo", X) ), guard( expression({ x -> x.getNullable() != null }, X) ), - body( constraint("assigned") ) + headKept(constraint("foo", X)), guard(expression({ x -> x.getNullable() != null }, X)), + body(constraint("assigned")) ) ).controller().evaluate(occurrence("main")).run { - assertEquals(setOf( ConstraintSymbol("foo", 1), - ConstraintSymbol("assigned", 0)), - constraintSymbols()) + assertEquals(setOf(ConstraintSymbol("foo", 1), + ConstraintSymbol("assigned", 0)), + constraintSymbols()) assertEquals(1, occurrences(ConstraintSymbol("foo", 1)).count()) assertEquals(1, occurrences(ConstraintSymbol("assigned", 0)).count()) } @@ -698,14 +698,14 @@ class TestController { programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x -> x.set(7) }, X), - statement({ y -> y.set(7) }, Y), - constraint("aux", X, Y) ) + headReplaced(constraint("main")), body(statement({ x -> x.set(7) }, X), + statement({ y -> y.set(7) }, Y), + constraint("aux", X, Y)) ), rule("aux", - headReplaced( constraint("aux", X, Y) ), body( equals(X, Y), - constraint("expected") ), - altBody( constraint("unexpected") ) + headReplaced(constraint("aux", X, Y)), body(equals(X, Y), + constraint("expected")), + altBody(constraint("unexpected")) ) ).controller().evaluate(occurrence("main")).run { assertEquals(setOf(ConstraintSymbol("expected", 0)), constraintSymbols()) @@ -719,14 +719,14 @@ class TestController { programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x -> x.set(7) }, X), - statement({ y -> y.set(13) }, Y), - constraint("aux", X, Y) ) + headReplaced(constraint("main")), body(statement({ x -> x.set(7) }, X), + statement({ y -> y.set(13) }, Y), + constraint("aux", X, Y)) ), rule("aux", - headReplaced( constraint("aux", X, Y) ), body( equals(X, Y), - constraint("unexpected") ), - altBody( constraint("expected") ) + headReplaced(constraint("aux", X, Y)), body(equals(X, Y), + constraint("unexpected")), + altBody(constraint("expected")) ) ).controller().evaluate(occurrence("main")).run { assertEquals(setOf(ConstraintSymbol("expected", 0)), constraintSymbols()) @@ -740,22 +740,21 @@ class TestController { programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x -> x.set(7) }, X), - statement({ y -> y.set(13) }, Y), - statement({ z -> z.set(17) }, Z), - constraint("aux", X, Y, Z) ) + headReplaced(constraint("main")), body(statement({ x -> x.set(7) }, X), + statement({ y -> y.set(13) }, Y), + statement({ z -> z.set(17) }, Z), + constraint("aux", X, Y, Z)) ), rule("aux", - headReplaced( constraint("aux", X, Y, Z) ), body( equals(X, Y), - constraint("unexpected1") ), - altBody( equals(X, Z), - constraint("unexpected2") ) + headReplaced(constraint("aux", X, Y, Z)), body(equals(X, Y), + constraint("unexpected1")), + altBody(equals(X, Z), + constraint("unexpected2")) ) ).controller().run { try { evaluate(occurrence("main")) - } - finally { + } finally { assertEquals(emptySet(), storeView().constraintSymbols()) } } @@ -783,7 +782,7 @@ class TestController { constraint("foo", term("bar", metaVar(X))) ), guard( - expression ({ x, y -> x.findRoot() == y.findRoot() }, X, Y) + expression({ x, y -> x.findRoot() == y.findRoot() }, X, Y) ), body( constraint("triggered1", X, Y) @@ -795,15 +794,14 @@ class TestController { constraint("foo", X) ), guard( - expression ({ x, y -> !y.isBound && x.findRoot() == y.findRoot() }, X, Y) + expression({ x, y -> !y.isBound && x.findRoot() == y.findRoot() }, X, Y) ), body( constraint("triggered2", X, Y) ) ) ).controller().run { - evaluate(occurrence("main")) - storeView().run { + evaluate(occurrence("main")).run { val foo = ConstraintSymbol("foo", 1) val t1 = ConstraintSymbol("triggered1", 2) constraintSymbols() shouldBe setOf(t1, foo) @@ -817,18 +815,20 @@ class TestController { @Test(expected = EvaluationFailureException::class) fun failureHandler() { - val failureHandler = object : FailureHandler { + val failureHandler = object : EvaluationFeedbackHandler { val failures = ArrayList>() - override fun handleFailure(failure: EvaluationFailure, rule: Rule): EvaluationFailure? { - failures.add(failure to rule.tag()) - return failure + override fun handleFeedback(rule: Rule, feedback: EvaluationFeedback): Boolean { + if (feedback is EvaluationFailure) { + failures.add(feedback to rule.tag()) + } + return false } } programWithRules( rule("main", headReplaced(constraint("main")), - body ( + body( constraint("foo") ) ), @@ -859,19 +859,21 @@ class TestController { @Test fun failureHandlerRecover() { - val failureHandler = object : FailureHandler { + val failureHandler = object : EvaluationFeedbackHandler { 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 + override fun handleFeedback(rule: Rule, feedback: EvaluationFeedback): Boolean { + if (feedback is EvaluationFailure) { + failures.add(feedback to rule.tag()) + return (rule.tag()?.startsWith("recoverable") == true) + } + return false } } programWithRules( rule("main", headReplaced(constraint("main")), - body ( + body( constraint("foo") ) ), @@ -889,7 +891,7 @@ class TestController { constraint("recovered", 2) ) ), - rule ("recoverable", + rule("recoverable", headReplaced(constraint("bar")), body( constraint("bazz") @@ -902,14 +904,13 @@ class TestController { ) ) ).controllerWithFeedback(failureHandler).run { - evaluate(occurrence("main")) - storeView().run { + evaluate(occurrence("main")).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 + failureHandler.failures.map { (f, t) -> "${f.cause.message}@$t" }.toList() shouldBe listOf("handled@rule3", "handled@recoverable") } @@ -926,7 +927,7 @@ class TestController { programWithRules( rule("main", headReplaced(constraint("main")), - body ( + body( constraint("foo") ) ), @@ -949,8 +950,8 @@ class TestController { } - feedbackHandler.feedbacks.map { (f, t) -> "${f.message}@$t"}.toList() shouldBe + feedbackHandler.feedbacks.map { (f, t) -> "${f.message}@$t" }.toList() shouldBe listOf("catchme@rule1", "propagateme@rule2", "propagateme@rule1", "propagateme@main") - } + } } diff --git a/reactor/Test/test/TestLogical.kt b/reactor/Test/test/TestLogical.kt index 1c2dbf04..afb9f44d 100644 --- a/reactor/Test/test/TestLogical.kt +++ b/reactor/Test/test/TestLogical.kt @@ -1,10 +1,7 @@ -import jetbrains.mps.logic.reactor.core.LogicalImpl import jetbrains.mps.logic.reactor.core.LogicalObserver import jetbrains.mps.logic.reactor.core.addObserver -import jetbrains.mps.logic.reactor.logical.JoinableLogical import jetbrains.mps.logic.reactor.logical.Logical import org.junit.Test -import kotlin.math.log /* * Copyright 2014-2019 JetBrains s.r.o. @@ -28,9 +25,7 @@ import kotlin.math.log class TestLogical { - inline fun logical(name: String): JoinableLogical = LogicalImpl(name) - - class Observer(logical: Logical<*>): LogicalObserver { + class Observer(logical: Logical<*>) : LogicalObserver { var parentUpdated: Pair, Logical<*>>? = null var valueUpdated: Pair, Any>? = null @@ -52,7 +47,7 @@ class TestLogical { } @Test - fun union_notifies_observers () { + fun union_notifies_observers() { val xLogical = logical("X") val yLogical = logical("Y") val zLogical = logical("Z") @@ -85,7 +80,7 @@ class TestLogical { xLogical.union(yLogical) observerY.parentUpdated shouldBe (yLogical to xLogical) observerX.parentUpdated shouldBe (null) - yLogical.findRoot() shouldBeSame xLogical + yLogical.findRoot() shouldBeSame xLogical yLogical.findRoot().setValue("foobar") observerX.valueUpdated shouldBe (xLogical to "foobar") diff --git a/reactor/Test/test/TestLogicalObserver.kt b/reactor/Test/test/TestLogicalObserver.kt index 71c1906a..da98265b 100644 --- a/reactor/Test/test/TestLogicalObserver.kt +++ b/reactor/Test/test/TestLogicalObserver.kt @@ -1,10 +1,7 @@ -import jetbrains.mps.logic.reactor.core.LogicalObserver -import jetbrains.mps.logic.reactor.core.LogicalImpl import jetbrains.mps.logic.reactor.core.addObserver -import jetbrains.mps.logic.reactor.logical.Logical +import org.junit.Assert.assertEquals +import org.junit.Assert.assertSame import org.junit.Test -import org.junit.Assert.* -import java.util.* /** * @author Fedor Isakov diff --git a/reactor/Test/test/TestProgram.kt b/reactor/Test/test/TestProgram.kt index dba6e7d7..ee6b43f9 100644 --- a/reactor/Test/test/TestProgram.kt +++ b/reactor/Test/test/TestProgram.kt @@ -1,15 +1,14 @@ -import jetbrains.mps.logic.reactor.core.EvaluationSessionImpl -import jetbrains.mps.logic.reactor.evaluation.AbstractSolver +import jetbrains.mps.logic.reactor.core.ReactorLifecycle import jetbrains.mps.logic.reactor.evaluation.EvaluationSession +import jetbrains.mps.logic.reactor.evaluation.SessionSolver import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.logical.Logical -import solver.MockSessionSolver import jetbrains.mps.logic.reactor.program.ConstraintSymbol -import jetbrains.mps.logic.reactor.program.PredicateSymbol -import org.junit.* -import org.junit.Assert.* +import org.junit.AfterClass +import org.junit.Assert.assertEquals +import org.junit.BeforeClass +import org.junit.Test import program.MockConstraint -import solver.EqualsSolver import solver.eq /** @@ -19,22 +18,25 @@ import solver.eq class TestProgram { companion object { - @BeforeClass @JvmStatic fun setup() { - EvaluationSessionImpl.init(); + @BeforeClass + @JvmStatic + fun setup() { + ReactorLifecycle.init(); } - @AfterClass @JvmStatic fun teardown() { - EvaluationSessionImpl.deinit(); + + @AfterClass + @JvmStatic + fun teardown() { + ReactorLifecycle.deinit(); } } private fun Builder.session(name: String): StoreView { - val sessionSolver = MockSessionSolver() - val programBuilder = ProgramBuilder(MockConstraintRegistry(sessionSolver)) + val programBuilder = ProgramBuilder(MockConstraintRegistry()) for (h in handlers) { programBuilder.addHandler(h) } - val session = EvaluationSession.newSession(programBuilder.program(name)). - withParam("main", MockConstraint(ConstraintSymbol("main", 0))).start(sessionSolver) + val session = EvaluationSession.newSession(programBuilder.program(name)).withParam("main", MockConstraint(ConstraintSymbol("main", 0))).start() return session.storeView() } @@ -79,7 +81,7 @@ class TestProgram { constraint("foo", X) ), body( - statement({x, y -> y eq (x.get() * 2) }, X, Y), + statement({ x, y -> y eq (x.get() * 2) }, X, Y), constraint("bar", Y) ) ) @@ -97,14 +99,14 @@ class TestProgram { programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ x -> x.set(5) }, X), - constraint("val", X) ) + headReplaced(constraint("main")), body(statement({ x -> x.set(5) }, X), + constraint("val", X)) ), rule("dec", - headReplaced( constraint("val", X) ), guard( expression({ x -> x.get() > 0 }, X) ), - body( constraint("trail", X), - statement({ x, y -> y.set(x.get() - 1)}, X, Y), - constraint("val", Y) ) + headReplaced(constraint("val", X)), guard(expression({ x -> x.get() > 0 }, X)), + body(constraint("trail", X), + statement({ x, y -> y.set(x.get() - 1) }, X, Y), + constraint("val", Y)) ) ).session("dec").run { assertEquals(setOf(ConstraintSymbol("val", 1), ConstraintSymbol("trail", 1)), constraintSymbols()) @@ -120,20 +122,20 @@ class TestProgram { val (M, N, TMP) = metaLogical("M", "N", "TMP") programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ m, n -> m.set(21); n.set(35) }, M, N), - constraint("gcd", M), - constraint("gcd", N) ) + headReplaced(constraint("main")), body(statement({ m, n -> m.set(21); n.set(35) }, M, N), + constraint("gcd", M), + constraint("gcd", N)) ), rule("trivial", - headReplaced( constraint("gcd", M) ), guard( expression({ x -> x.get() == 0 }, M) ), - body( statement { } /*nothing*/ ) + headReplaced(constraint("gcd", M)), guard(expression({ x -> x.get() == 0 }, M)), + body(statement { } /*nothing*/) ), rule("step", - headKept( constraint("gcd", N) ), - headReplaced( constraint("gcd", M) ), - guard( expression({ m, n -> m.get() >= n.get()}, M, N) ), - body( statement({ m, n, tmp -> tmp.set(m.get() - n.get())}, M, N, TMP), - constraint("gcd", TMP) + headKept(constraint("gcd", N)), + headReplaced(constraint("gcd", M)), + guard(expression({ m, n -> m.get() >= n.get() }, M, N)), + body(statement({ m, n, tmp -> tmp.set(m.get() - n.get()) }, M, N, TMP), + constraint("gcd", TMP) ) ) ).session("gcd").run { @@ -149,26 +151,23 @@ class TestProgram { val (M, N) = metaLogical("M", "N") programWithRules( rule("main", - headReplaced( constraint("main") ), body( statement({ n -> n.set(10) }, N), - constraint("prime", N) ) + headReplaced(constraint("main")), body(statement({ n -> n.set(10) }, N), + constraint("prime", N)) ), rule("gen", - headKept( constraint("prime", N)), guard( expression({ n -> n.get() > 2 }, N)), - body( statement({ m, n -> m.set(n.get() - 1) }, M, N), - constraint("prime", M) ) + headKept(constraint("prime", N)), guard(expression({ n -> n.get() > 2 }, N)), + body(statement({ m, n -> m.set(n.get() - 1) }, M, N), + constraint("prime", M)) ), rule("sift", - headKept( constraint("prime", M)), - headReplaced( constraint("prime", N)), - guard( expression({ m, n -> (n.get() % m.get()) == 0 }, M, N)), - body( statement { } /* nothing */ ) + headKept(constraint("prime", M)), + headReplaced(constraint("prime", N)), + guard(expression({ m, n -> (n.get() % m.get()) == 0 }, M, N)), + body(statement { } /* nothing */) ) ).session("primes").run { assertEquals(4, allOccurrences().count()) - assertEquals(setOf(2,3,5,7), occurrences(ConstraintSymbol.symbol("prime", 1)). - flatMap { co -> co.arguments() }. - map { a -> (a as Logical).findRoot().value() }. - toSet()) + assertEquals(setOf(2, 3, 5, 7), occurrences(ConstraintSymbol.symbol("prime", 1)).flatMap { co -> co.arguments() }.map { a -> (a as Logical).findRoot().value() }.toSet()) } } diff --git a/reactor/Test/test/TestProgramBuilder.kt b/reactor/Test/test/TestProgramBuilder.kt index a46badfb..28f572f1 100644 --- a/reactor/Test/test/TestProgramBuilder.kt +++ b/reactor/Test/test/TestProgramBuilder.kt @@ -1,11 +1,8 @@ -import jetbrains.mps.logic.reactor.evaluation.AbstractSolver -import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation import jetbrains.mps.logic.reactor.program.InvalidConstraintException import jetbrains.mps.logic.reactor.program.InvalidRuleException import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import solver.MockSessionSolver /** * @author Fedor Isakov @@ -13,10 +10,8 @@ import solver.MockSessionSolver class TestProgramBuilder { - val sessionSolver = MockSessionSolver() - @Before fun beforeTest() { - programBuilder = ProgramBuilder(MockConstraintRegistry(sessionSolver)) + programBuilder = ProgramBuilder(MockConstraintRegistry()) } lateinit var programBuilder: ProgramBuilder diff --git a/reactor/Test/test/TestRuleMatcher.kt b/reactor/Test/test/TestRuleMatcher.kt index abe84bc9..91a6d1b5 100644 --- a/reactor/Test/test/TestRuleMatcher.kt +++ b/reactor/Test/test/TestRuleMatcher.kt @@ -1,10 +1,11 @@ import jetbrains.mps.logic.reactor.core.* +import jetbrains.mps.logic.reactor.core.internal.createOccurrenceMatcher +import jetbrains.mps.logic.reactor.core.internal.logical import jetbrains.mps.logic.reactor.program.ConstraintSymbol import jetbrains.mps.logic.reactor.program.ConstraintSymbol.symbol import jetbrains.mps.unification.Term import jetbrains.mps.unification.test.MockTerm.* import jetbrains.mps.unification.test.MockTermsParser.* -import org.junit.Assert.assertEquals import org.junit.Ignore import org.junit.Test import program.MockConstraint @@ -42,25 +43,25 @@ class TestRuleMatcher { val foox = MockConstraint(ConstraintSymbol("foo", 1), X) // foo("bar") = foo("bar") - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foobar, occurrence("foo", "bar")) shouldBe true // foo("bar") != foo(X) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foobar, occurrence("foo", xLogical)) shouldBe false // foo("bar") = foo(X = "bar") xLogical.set("bar") - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foobar, occurrence("foo", xLogical)) shouldBe true // foo(X) = foo(Y) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foox, occurrence("foo", yLogical)) shouldBe true // foo(X) = foo("bar") - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foox, occurrence("foo", "bar")) shouldBe true // foo(X) = foo(Y = "bar") yLogical.set("bar") - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foox, occurrence("foo", yLogical)) shouldBe true } @@ -74,25 +75,25 @@ class TestRuleMatcher { val foofx = MockConstraint(ConstraintSymbol("foo", 1), term("f", metaVar(X))) // foo(f { h }) = foo(f { h }) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foofh, occurrence("foo", parseTerm("f { h }"))) shouldBe true // foo(f { h }) != foo(f { X }) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foofh, occurrence("foo", term("f", logicalVar(xLogical)))) shouldBe false // foo(f { h }) != foo(f { X = h }) xLogical.set(term("h")) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foofh, occurrence("foo", term("f", logicalVar(xLogical)))) shouldBe true // foo(f { X }) = foo(f { h }) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foofx, occurrence("foo", parseTerm("f { h }"))) shouldBe true // foo(f { X }) = foo(f { Y }) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foofx, occurrence("foo", term("f", logicalVar(yLogical)))) shouldBe true // foo(f { X }) = foo(f { Y = h }) xLogical.set(term("h")) - OccurrenceMatcher(). + createOccurrenceMatcher(). matches(foofx, occurrence("foo", term("f", logicalVar(yLogical)))) shouldBe true } @@ -106,28 +107,28 @@ class TestRuleMatcher { val foofx = MockConstraint(ConstraintSymbol("foo", 1), term("f", metaVar(X))) // [X -> free] |- foo(f { X }) = foo(f { X }) - OccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). + createOccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). matches(foofx, occurrence("foo", term("f", logicalVar(xLogical)))) shouldBe true // [X -> free] |- foo(f { X }) != foo(f { Y }) - OccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). + createOccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). matches(foofx, occurrence("foo", term("f", logicalVar(yLogical)))) shouldBe false // [X -> free] |- foo(f { X }) != foo(f { h }) - OccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). + createOccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). matches(foofx, occurrence("foo", parseTerm("f { h }"))) shouldBe false // [X -> free] |- foo(f { X }) != foo(f { Y = h }) yLogical.set(term("h")) - OccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). + createOccurrenceMatcher(arrayOf(X to logicalVar(xLogical)).toMap()). matches(foofx, occurrence("foo", term("f", logicalVar(yLogical)))) shouldBe false // [X -> h] |- foo(f { X }) = foo(f { h }) - OccurrenceMatcher(arrayOf(X to term("h")).toMap()). + createOccurrenceMatcher(arrayOf(X to term("h")).toMap()). matches(foofx, occurrence("foo", parseTerm("f { h }"))) shouldBe true // [X -> h] |- foo(f { X }) != foo(f { Z }) - OccurrenceMatcher(arrayOf(X to term("h")).toMap()). + createOccurrenceMatcher(arrayOf(X to term("h")).toMap()). matches(foofx, occurrence("foo", term("f", logicalVar(zLogical)))) shouldBe false // [X -> h] |- foo(f { X }) = foo(f { Z = h }) zLogical.set(term("h")) - OccurrenceMatcher(arrayOf(X to term("h")).toMap()). + createOccurrenceMatcher(arrayOf(X to term("h")).toMap()). matches(foofx, occurrence("foo", term("f", logicalVar(zLogical)))) shouldBe true } @@ -784,5 +785,5 @@ class TestRuleMatcher { } } - private fun Builder.ruleMatcher() = Matcher(rules.first()) + private fun Builder.ruleMatcher() = createRuleMatcher(rules.first()) } \ No newline at end of file diff --git a/reactor/Test/test/jetbrains/mps/unification/test/SolverTests.java b/reactor/Test/test/jetbrains/mps/unification/test/SolverTests.java index e273c0a6..d76afab2 100644 --- a/reactor/Test/test/jetbrains/mps/unification/test/SolverTests.java +++ b/reactor/Test/test/jetbrains/mps/unification/test/SolverTests.java @@ -16,7 +16,9 @@ package jetbrains.mps.unification.test; -import jetbrains.mps.logic.reactor.core.LogicalImpl; +import jetbrains.mps.logic.reactor.core.LogicalObserverKt; +import jetbrains.mps.logic.reactor.core.internal.LogicalImplKt; +import jetbrains.mps.logic.reactor.logical.JoinableLogical; import jetbrains.mps.logic.reactor.logical.MetaLogical; import jetbrains.mps.unification.Substitution; import jetbrains.mps.unification.Term; @@ -27,10 +29,11 @@ import org.junit.Test; import java.util.Collection; import java.util.Collections; -import static jetbrains.mps.unification.Substitution.FailureCause.*; +import static jetbrains.mps.unification.Substitution.FailureCause.CYCLE_DETECTED; +import static jetbrains.mps.unification.Substitution.FailureCause.SYMBOL_CLASH; import static jetbrains.mps.unification.test.AssertUnification.*; import static jetbrains.mps.unification.test.MockTerm.*; -import static jetbrains.mps.unification.test.MockTermsParser.*; +import static jetbrains.mps.unification.test.MockTermsParser.parseTerm; /** * Created by fyodor on 09.06.2014. @@ -408,12 +411,32 @@ public class SolverTests { class Wrapper implements Term { Term wrapped; - Wrapper(Term term) { this.wrapped = term; } - @Override public Object symbol() { return wrapped; } - @Override public Collection arguments() { return Collections.emptyList(); } - @Override public Term get() { return this; } - @Override public boolean is(Kind kind) { return Kind.FUN == kind; } - @Override public int compareTo(@NotNull Term other) { + Wrapper(Term term) { + this.wrapped = term; + } + + @Override + public Object symbol() { + return wrapped; + } + + @Override + public Collection arguments() { + return Collections.emptyList(); + } + + @Override + public Term get() { + return this; + } + + @Override + public boolean is(Kind kind) { + return Kind.FUN == kind; + } + + @Override + public int compareTo(@NotNull Term other) { return String.valueOf(symbol()).compareTo(String.valueOf(other.symbol())); } @@ -427,7 +450,7 @@ public class SolverTests { @Override public Term unwrap(Term maybeWrapper) { - return maybeWrapper instanceof Wrapper ? ((Wrapper)maybeWrapper).wrapped : maybeWrapper; + return maybeWrapper instanceof Wrapper ? ((Wrapper) maybeWrapper).wrapped : maybeWrapper; } }; @@ -551,13 +574,13 @@ public class SolverTests { MetaLogical X = new MetaLogical<>("X", Term.class); MetaLogical Y = new MetaLogical<>("Y", Term.class); MetaLogical Z = new MetaLogical<>("Z", Term.class); - LogicalImpl xLogical = new LogicalImpl<>(X); - LogicalImpl yLogical = new LogicalImpl<>(Y); - LogicalImpl zLogical = new LogicalImpl<>(Z); + JoinableLogical xLogical = LogicalImplKt.logical(X); + JoinableLogical yLogical = LogicalImplKt.logical(Y); + JoinableLogical zLogical = LogicalImplKt.logical(Z); Term left = term("foo", term("bar", logicalVar(yLogical)), logicalVar(zLogical)); Term right = term("foo", term("bar", logicalVar(xLogical)), logicalVar(zLogical)); - + assertUnifiesWithBindings(left, right, new Substitution.Binding(logicalVar(xLogical), logicalVar(yLogical))); @@ -571,9 +594,9 @@ public class SolverTests { MetaLogical X = new MetaLogical<>("X", Term.class); MetaLogical Y = new MetaLogical<>("Y", Term.class); MetaLogical Z = new MetaLogical<>("Z", Term.class); - LogicalImpl xLogical = new LogicalImpl<>(X); - LogicalImpl yLogical = new LogicalImpl<>(Y); - LogicalImpl zLogical = new LogicalImpl<>(Z); + JoinableLogical xLogical = LogicalImplKt.logical(X); + JoinableLogical yLogical = LogicalImplKt.logical(Y); + JoinableLogical zLogical = LogicalImplKt.logical(Z); Term left = logicalVar(yLogical); Term right = term("foo", term("bar", logicalVar(xLogical)), logicalVar(zLogical));