+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/reactor/API/pom.xml b/reactor/API/pom.xml
new file mode 100644
index 00000000..18807e50
--- /dev/null
+++ b/reactor/API/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+
+ jetbrains.mps.logic.conreactor
+ conreactor-main
+ 0.9
+ ..
+
+
+ conreactor-api
+ jar
+
+
+ ${basedir}/src/
+
+
+
\ No newline at end of file
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java
new file mode 100644
index 00000000..cb89b852
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java
@@ -0,0 +1,28 @@
+/*
+ * 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.evaluation;
+
+/**
+ * Not very useful now.
+ * Serves as an abstract superclass for implementations, as a mixin of Instructible and Queryable.
+ *
+ * @author Fedor Isakov
+ */
+public abstract class AbstractSolver implements Instructible, Queryable {
+
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java
new file mode 100644
index 00000000..cf96f48b
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java
@@ -0,0 +1,38 @@
+/*
+ * 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.evaluation;
+
+
+import jetbrains.mps.logic.reactor.logical.LogicalContext;
+import jetbrains.mps.logic.reactor.program.Constraint;
+
+import java.util.List;
+
+/**
+ * Represents a run-time object corresponding to an actual constraint.
+ *
+ * @author Fedor Isakov
+ */
+public interface ConstraintOccurrence {
+
+ Constraint constraint();
+
+ List> arguments();
+
+ LogicalContext logicalContext();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailureException.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailureException.java
new file mode 100644
index 00000000..0743133e
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailureException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.evaluation;
+
+
+/**
+ * Thrown in case evaluation fails.
+ *
+ * @author Fedor Isakov
+ */
+public class EvaluationFailureException extends RuntimeException {
+
+ public EvaluationFailureException(String message) {
+ super(message);
+ }
+
+ public EvaluationFailureException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java
new file mode 100644
index 00000000..35b5345c
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java
@@ -0,0 +1,97 @@
+/*
+ * 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.evaluation;
+
+
+import jetbrains.mps.logic.reactor.logical.LogicalContext;
+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;
+
+/**
+ * The starting point to evaluate a program.
+ *
+ * The session is started with {@code newSession}, provided the backend has been initialized.
+ *
+ * The backend is supposed to be provided at startup.
+ *
+ * @author Fedor Isakov
+ */
+public abstract class EvaluationSession {
+
+ private static EvaluationSession.Backend ourBackend;
+
+ public static EvaluationSession current() {
+ if (ourBackend == null) {
+ throw new IllegalStateException("no backend");
+ }
+ return ourBackend.current();
+ }
+
+ public static EvaluationSession.Config newSession(Program program) {
+ if (ourBackend == null) {
+ throw new IllegalStateException("no backend");
+ }
+ return ourBackend.createConfig(program);
+ }
+
+ 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) {
+ if (ourBackend != backend) {
+ throw new IllegalStateException("illegal access");
+ }
+ ourBackend = null;
+ }
+
+ public abstract SessionSolver sessionSolver();
+
+ public abstract StoreView storeView();
+
+ public abstract PredicateInvocation invocation(Predicate predicate, LogicalContext logicalContext);
+
+ public abstract ConstraintOccurrence occurrence(Constraint constraint, LogicalContext logicalContext);
+
+ protected interface Backend {
+
+ EvaluationSession current();
+
+ EvaluationSession.Config createConfig(Program program);
+
+ }
+
+ public static abstract class Config {
+
+ public abstract EvaluationSession.Config withPredicates(PredicateSymbol... predicateSymbols);
+
+ public abstract EvaluationSession.Config withTrace(EvaluationTrace computingTracer);
+
+ public abstract EvaluationSession.Config withStoreView(StoreView storeView);
+
+ public abstract EvaluationSession.Config withParam(String key, Object param);
+
+ public abstract EvaluationSession start(SessionSolver sessionSolver);
+
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java
new file mode 100644
index 00000000..218381ca
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java
@@ -0,0 +1,96 @@
+/*
+ * 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.evaluation;
+
+
+/**
+ * An interface to be implemented by clients wishing to be notified of the events during evaluation.
+ *
+ * @author Fedor Isakov
+ */
+public interface EvaluationTrace {
+
+ EvaluationTrace NULL = new EvaluationTrace() {
+
+ public void activate(ConstraintOccurrence occurrence) {
+ }
+
+ public void reactivate(ConstraintOccurrence occurrence) {
+ }
+
+ public void suspend(ConstraintOccurrence occurrence) {
+ }
+
+ public void discard(ConstraintOccurrence occurrence) {
+ }
+
+ public void trying(MatchRule matchRule) {
+ }
+
+ public void reject(MatchRule matchRule) {
+ }
+
+ public void trigger(MatchRule matchRule) {
+ }
+
+ public void retry(MatchRule matchRule) {
+ }
+
+ public void finish(MatchRule matchRule) {
+ }
+
+ public void tell(PredicateInvocation invocation) {
+ }
+
+ public void ask(boolean result, PredicateInvocation invocation) {
+ }
+
+ public void failure(EvaluationFailureException fail) {
+ }
+
+ public void reportFailure(String message) {
+ }
+
+ };
+
+ void activate(ConstraintOccurrence occurrence);
+
+ void reactivate(ConstraintOccurrence occurrence);
+
+ void suspend(ConstraintOccurrence occurrence);
+
+ void discard(ConstraintOccurrence occurrence);
+
+ void trying(MatchRule matchRule);
+
+ void reject(MatchRule matchRule);
+
+ void trigger(MatchRule matchRule);
+
+ void retry(MatchRule matchRule);
+
+ void finish(MatchRule matchRule);
+
+ void tell(PredicateInvocation invocation);
+
+ void ask(boolean result, PredicateInvocation invocation);
+
+ void failure(EvaluationFailureException fail);
+
+ @Deprecated
+ void reportFailure(String message);
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Instructible.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Instructible.java
new file mode 100644
index 00000000..d2d4ad66
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Instructible.java
@@ -0,0 +1,29 @@
+/*
+ * 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.evaluation;
+
+
+/**
+ * Abstract interface to a predicate.
+ *
+ * @author Fedor Isakov
+ */
+public interface Instructible {
+
+ void tell(PredicateInvocation invocation);
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java
new file mode 100644
index 00000000..deacffb1
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java
@@ -0,0 +1,35 @@
+/*
+ * 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.evaluation;
+
+
+import jetbrains.mps.logic.reactor.program.Rule;
+
+/**
+ * A binding of a rule and the constraints that matched its head.
+ *
+ * @author Fedor Isakov
+ */
+public interface MatchRule {
+
+ Rule rule();
+
+ Iterable matchHeadKept();
+
+ Iterable matchHeadReplaced();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java
new file mode 100644
index 00000000..1b0def4b
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java
@@ -0,0 +1,38 @@
+/*
+ * 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.evaluation;
+
+
+import jetbrains.mps.logic.reactor.logical.LogicalContext;
+import jetbrains.mps.logic.reactor.program.Predicate;
+
+import java.util.List;
+
+/**
+ * A run-time object representing invocation of a predicate.
+ *
+ * @author Fedor Isakov
+ */
+public interface PredicateInvocation {
+
+ Predicate predicate();
+
+ List> arguments();
+
+ LogicalContext logicalContext();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Queryable.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Queryable.java
new file mode 100644
index 00000000..85482933
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Queryable.java
@@ -0,0 +1,29 @@
+/*
+ * 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.evaluation;
+
+
+/**
+ * Abstract interface to a predicate.
+ *
+ * @author Fedor Isakov
+ */
+public interface Queryable extends Instructible {
+
+ boolean ask(PredicateInvocation invocation);
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java
new file mode 100644
index 00000000..ba0ed3d7
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java
@@ -0,0 +1,95 @@
+/*
+ * 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.evaluation;
+
+
+import jetbrains.mps.logic.reactor.logical.LogicalContext;
+import jetbrains.mps.logic.reactor.program.AndItem;
+import jetbrains.mps.logic.reactor.program.Predicate;
+import jetbrains.mps.logic.reactor.program.PredicateSymbol;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Is used to provide an interface for handlers and solvers working together in a single session.
+ *
+ * @author Fedor Isakov
+ */
+public abstract class SessionSolver implements Queryable, Instructible {
+
+ private Map solvers = new HashMap();
+ private EvaluationTrace tracer = EvaluationTrace.NULL;
+
+ public void init(PredicateSymbol... predicateSymbols) {
+ registerSymbols(predicateSymbols);
+ }
+
+ public void init(EvaluationTrace evaluationTrace, PredicateSymbol... predicateSymbols) {
+ tracer = evaluationTrace;
+ init(predicateSymbols);
+ }
+
+ @Override
+ public boolean ask(PredicateInvocation invocation) {
+ AbstractSolver solver = solver(invocation.predicate().symbol());
+ boolean result = solver.ask(invocation);
+ tracer.ask(result, invocation);
+ return result;
+ }
+
+ @Override
+ public void tell(PredicateInvocation invocation) {
+ AbstractSolver handler = solver(invocation.predicate().symbol());
+ tracer.tell(invocation);
+ handler.tell(invocation);
+ }
+
+ public boolean ask(Predicate predicate, LogicalContext logicalContext) {
+ return ask(EvaluationSession.current().invocation(predicate, logicalContext));
+ }
+
+ public void tell(AndItem item, LogicalContext logicalContext) {
+ if (item instanceof Predicate) {
+ tell(EvaluationSession.current().invocation((Predicate) item, logicalContext));
+
+ } else {
+ // FIXME: implement me
+ throw new UnsupportedOperationException("not implemented");
+ }
+ }
+
+ protected abstract void registerSymbol(PredicateSymbol predicateSymbol, EvaluationTrace computingTracer);
+
+ protected void registerSolver(PredicateSymbol constraint, AbstractSolver solver) {
+ solvers.put(constraint, solver);
+ }
+
+ private AbstractSolver solver(PredicateSymbol predicateSymbol) {
+ if (!(solvers.containsKey(predicateSymbol))) {
+ throw new IllegalStateException("no handler: " + predicateSymbol);
+ }
+ return solvers.get(predicateSymbol);
+ }
+
+ private void registerSymbols(PredicateSymbol... predicateSymbols) {
+ for (PredicateSymbol symbol : predicateSymbols) {
+ registerSymbol(symbol, tracer);
+ }
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/StoreView.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/StoreView.java
new file mode 100644
index 00000000..701758f8
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/StoreView.java
@@ -0,0 +1,34 @@
+/*
+ * 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.evaluation;
+
+import jetbrains.mps.logic.reactor.program.ConstraintSymbol;
+
+/**
+ * A read-only view on the constraints store.
+ *
+ * @author Fedor Isakov
+ */
+public interface StoreView {
+
+ Iterable constraintSymbols();
+
+ Iterable allOccurrences();
+
+ Iterable occurrences(ConstraintSymbol symbol);
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java
new file mode 100644
index 00000000..3db39328
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java
@@ -0,0 +1,58 @@
+/*
+ * 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.logical;
+
+
+/**
+ * A logical variable that can be joined with another variable to produce a union.
+ *
+ * @param the value type
+ *
+ * @author Fedor Isakov
+ */
+public interface JoinableLogical extends Logical {
+
+ /**
+ * Covariant override.
+ */
+ JoinableLogical findRoot();
+
+ /**
+ * Unions two equivalence classes of logicals.
+ * Both the receiver and the {@code other} parameter are expected to be representatives.
+ * The one with the highest rank becomes the representative for the new class.
+ */
+ void union(JoinableLogical other, JoinableLogical.ValueReconciler reconciler);
+
+ /**
+ * Calls {@link JoinableLogical#union(JoinableLogical , JoinableLogical.ValueReconciler) } with the default value reconciler.
+ * The default reconciler throws {@link java.lang.IllegalArgumentException } if the two values are not equal.
+ */
+ void union(JoinableLogical other);
+
+ /**
+ * Should only be called on a representative.
+ */
+ void setValue(T newValue);
+
+ interface ValueReconciler {
+
+ void reconcile(T valueA, T valueB);
+
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/Logical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/Logical.java
new file mode 100644
index 00000000..524910a5
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/Logical.java
@@ -0,0 +1,56 @@
+/*
+ * 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.logical;
+
+
+/**
+ * A run-time representation of a logical variable.
+ *
+ * @param the value type
+ *
+ * @author Fedor Isakov
+ */
+public interface Logical {
+
+ String name();
+
+ /**
+ * Returns the representative logical instance.
+ *
+ * TODO: rename to 'find()'
+ */
+ Logical findRoot();
+
+ /**
+ * Returns the value associated with this logical instance.
+ * Can be null.
+ * Only the logical instance that is a representative can have value that is not null.
+ *
+ * FIXME: must return the representative's value
+ */
+ T value();
+
+ /**
+ * True iff the representative has a non-null value.
+ */
+ boolean isBound();
+
+ boolean isWildcard();
+
+ MetaLogical metaLogical();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalContext.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalContext.java
new file mode 100644
index 00000000..a6dbb094
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalContext.java
@@ -0,0 +1,30 @@
+/*
+ * 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.logical;
+
+
+/**
+ * Substitution of logicals for meta-logicals.
+ *
+ * @author Fedor Isakov
+ */
+public interface LogicalContext {
+
+
+ Logical variable(MetaLogical metaLogical);
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalOwner.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalOwner.java
new file mode 100644
index 00000000..793b0cdc
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalOwner.java
@@ -0,0 +1,29 @@
+/*
+ * 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.logical;
+
+
+/**
+ * An abstract wrapper for a {@link Logical}.
+ *
+ * @author Fedor Isakov
+ */
+public interface LogicalOwner {
+
+ Logical> logical();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/MetaLogical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MetaLogical.java
new file mode 100644
index 00000000..899087b8
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MetaLogical.java
@@ -0,0 +1,62 @@
+/*
+ * 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.logical;
+
+
+/**
+ * A declaration of a logical variable.
+ *
+ * @param the value type
+ *
+ * @author Fedor Isakov
+ */
+public class MetaLogical {
+
+ private static final String WILDCARD = "_";
+ private String name;
+ private Class type;
+ private boolean wildcard = false;
+
+ public MetaLogical(String name, Class type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ public MetaLogical(Class type) {
+ this.name = MetaLogical.WILDCARD + System.identityHashCode(this);
+ this.type = type;
+ this.wildcard = true;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public boolean isWildcard() {
+ return wildcard;
+ }
+
+ public Class type() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/MultiMetaLogical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MultiMetaLogical.java
new file mode 100644
index 00000000..0bb6f919
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MultiMetaLogical.java
@@ -0,0 +1,59 @@
+/*
+ * 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.logical;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A declaration of a logical variable with index.
+ *
+ * @param the value type
+ *
+ * @author Fedor Isakov
+ */
+public class MultiMetaLogical extends MetaLogical {
+
+ private int cardinality;
+ private List> metaLogicals = new ArrayList>();
+
+ public MultiMetaLogical(String name, Class type, int cardinality) {
+ super(name, type);
+ this.cardinality = cardinality;
+ init();
+ }
+
+ public int cardinality() {
+ return cardinality;
+ }
+
+ public MetaLogical logicalAt(int idx) {
+ return metaLogicals.get(idx);
+ }
+
+ public MetaLogical[] toArray() {
+ MetaLogical[] array = new MetaLogical[cardinality];
+ return metaLogicals.toArray(array);
+ }
+
+ private void init() {
+ for (int i = 0; i < cardinality; i++) {
+ metaLogicals.add(new MetaLogical(name() + "_" + (i + 1), type()));
+ }
+ }
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/AndItem.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/AndItem.java
new file mode 100644
index 00000000..f29483fc
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/AndItem.java
@@ -0,0 +1,33 @@
+/*
+ * 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.program;
+
+
+import java.util.List;
+
+/**
+ * An abstract conjunct.
+ *
+ * @author Fedor Isakov
+ */
+public interface AndItem {
+
+ Symbol symbol();
+
+ List> arguments();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Constraint.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Constraint.java
new file mode 100644
index 00000000..53baa70e
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Constraint.java
@@ -0,0 +1,41 @@
+/*
+ * 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.program;
+
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A constraint provided by a handler.
+ *
+ * @author Fedor Isakov
+ */
+public interface Constraint extends AndItem {
+
+ ConstraintSymbol symbol();
+
+ List> argumentTypes();
+
+ /**
+ * Returns the collection of predicates that need to be applied after a successful match of this collection by a
+ * rule's head.
+ * This method only returns meaningful results for constraints that serve as patterns in a rule's head.
+ */
+ Collection extends Predicate> patternPredicates(Collection> occurrenceArgs);
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/ConstraintSymbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/ConstraintSymbol.java
new file mode 100644
index 00000000..c4f84849
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/ConstraintSymbol.java
@@ -0,0 +1,40 @@
+/*
+ * 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.program;
+
+
+/**
+ * A constraint symbol.
+ *
+ * @author Fedor Isakov
+ */
+public class ConstraintSymbol extends Symbol {
+
+ public ConstraintSymbol(String id, int arity) {
+ super(id, arity);
+ }
+
+ public static ConstraintSymbol symbol(String id, int arity) {
+ return new ConstraintSymbol(id, arity);
+ }
+
+ @Override
+ public String toString() {
+ return id() + "/" + arity();
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java
new file mode 100644
index 00000000..7dfffa25
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java
@@ -0,0 +1,35 @@
+/*
+ * 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.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();
+
+ public abstract Iterable primarySymbols();
+
+ public abstract Iterable rules();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidConstraintException.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidConstraintException.java
new file mode 100644
index 00000000..cd5a78b4
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidConstraintException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.program;
+
+
+
+public class InvalidConstraintException extends Exception {
+
+ public InvalidConstraintException(String message) {
+ super(message);
+ }
+
+ public InvalidConstraintException(String message, Throwable ex) {
+ super(message, ex);
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidRuleException.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidRuleException.java
new file mode 100644
index 00000000..06eb9870
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidRuleException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.program;
+
+
+public class InvalidRuleException extends Exception {
+
+ public InvalidRuleException(String message) {
+ super(message);
+ }
+
+ public InvalidRuleException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/JavaPredicateSymbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/JavaPredicateSymbol.java
new file mode 100644
index 00000000..8fcb3e8d
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/JavaPredicateSymbol.java
@@ -0,0 +1,47 @@
+/*
+ * 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.program;
+
+
+/**
+ * A predicate symbol for an anonymous expression.
+ *
+ * @author Fedor Isakov
+ */
+public class JavaPredicateSymbol extends PredicateSymbol {
+
+ public static final JavaPredicateSymbol EXPRESSION0 = new JavaPredicateSymbol(1);
+
+ public static final JavaPredicateSymbol EXPRESSION1 = new JavaPredicateSymbol(2);
+
+ public static final JavaPredicateSymbol EXPRESSION2 = new JavaPredicateSymbol(3);
+
+ public static final JavaPredicateSymbol EXPRESSION3 = new JavaPredicateSymbol(4);
+
+ private static final String EXPRESSION = "expression";
+
+ private static JavaPredicateSymbol[] KNOWN_SYMBOLS = {EXPRESSION0, EXPRESSION1, EXPRESSION2, EXPRESSION3};
+
+ private JavaPredicateSymbol(int arity) {
+ super(EXPRESSION, arity);
+ }
+
+ public static JavaPredicateSymbol withArity(int arity) {
+ return KNOWN_SYMBOLS[arity];
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Predicate.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Predicate.java
new file mode 100644
index 00000000..0a665538
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Predicate.java
@@ -0,0 +1,29 @@
+/*
+ * 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.program;
+
+
+/**
+ * A predicate that is provided by a solver. Can be either told or asked.
+ *
+ * @author Fedor Isakov
+ */
+public interface Predicate extends AndItem {
+
+ PredicateSymbol symbol();
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/PredicateSymbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/PredicateSymbol.java
new file mode 100644
index 00000000..1bf26b9c
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/PredicateSymbol.java
@@ -0,0 +1,36 @@
+/*
+ * 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.program;
+
+
+/**
+ * A predicate symbol.
+ *
+ * @author Fedor Isakov
+ */
+public class PredicateSymbol extends Symbol {
+
+ public PredicateSymbol(String id, int arity) {
+ super(id, arity);
+ }
+
+ @Override
+ public String toString() {
+ return id() + "()/" + arity();
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Program.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Program.java
new file mode 100644
index 00000000..61c4dd88
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Program.java
@@ -0,0 +1,43 @@
+/*
+ * 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.program;
+
+
+import jetbrains.mps.logic.reactor.logical.LogicalContext;
+
+import java.util.List;
+
+/**
+ * A collection of handlers that constitute a constraint rules program.
+ *
+ * @author Fedor Isakov
+ */
+public abstract class Program {
+
+ public abstract String name();
+
+ public abstract Iterable constraintSymbols();
+
+ public abstract List> constraintArgumentTypes(ConstraintSymbol constraintSymbol);
+
+ public abstract Iterable predicateSymbols();
+
+ public abstract Iterable handlers();
+
+ public abstract List> instantiateArguments(List> arguments, LogicalContext logicalContext);
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java
new file mode 100644
index 00000000..e00405ae
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java
@@ -0,0 +1,50 @@
+/*
+ * 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.program;
+
+
+/**
+ * A constraint rule description.
+ *
+ * @author Fedor Isakov
+ */
+public abstract class Rule {
+
+ public abstract Rule.Kind kind();
+
+ public abstract String tag();
+
+ public abstract Iterable headKept();
+
+ public abstract Iterable headReplaced();
+
+ public abstract Iterable guard();
+
+ @Deprecated
+ public abstract Iterable body();
+
+ public abstract Iterable extends Iterable> bodyAlternation();
+
+ public abstract Iterable all();
+
+ public enum Kind {
+ SIMPLIFICATION(),
+ PROPAGATION(),
+ SIMPAGATION()
+ }
+
+}
diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Symbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Symbol.java
new file mode 100644
index 00000000..ed9f1a24
--- /dev/null
+++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Symbol.java
@@ -0,0 +1,70 @@
+/*
+ * 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.program;
+
+
+/**
+ * A symbol used by both constraints and predicates. Is uniquely identified by id and arity.
+ *
+ * @author Fedor Isakov
+ */
+public abstract class Symbol {
+
+ private String id;
+ private int arity;
+
+ protected Symbol(String id, int arity) {
+ this.id = id;
+ this.arity = arity;
+ }
+
+ public String id() {
+ return id;
+ }
+
+ public int arity() {
+ return arity;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 43 * result + ((id != null ? String.valueOf(id).hashCode() : 0));
+ result = 31 * result + 37 * arity;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || this.getClass() != o.getClass()) {
+ return false;
+ }
+
+ Symbol that = (Symbol) o;
+ if ((id != null ? !(id.equals(that.id())) : that.id != null)) {
+ return false;
+ }
+ if (arity != that.arity) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/reactor/Core/Core.iml b/reactor/Core/Core.iml
new file mode 100644
index 00000000..27448936
--- /dev/null
+++ b/reactor/Core/Core.iml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/reactor/Core/pom.xml b/reactor/Core/pom.xml
new file mode 100644
index 00000000..281db4a7
--- /dev/null
+++ b/reactor/Core/pom.xml
@@ -0,0 +1,50 @@
+
+
+ 4.0.0
+
+
+ jetbrains.mps.logic.conreactor
+ conreactor-main
+ 0.9
+ ..
+
+
+ conreactor-core
+ jar
+
+
+
+ jetbrains.mps.logic.conreactor
+ conreactor-api
+ ${project.parent.version}
+
+
+
+
+ ${basedir}/src/
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ ${basedir}/src
+
+
+
+
+
+
+
+
+
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt
new file mode 100644
index 00000000..5c2b7a99
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.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.program.Rule
+import jetbrains.mps.logic.reactor.util.Profiler
+import jetbrains.mps.logic.reactor.util.profile
+import com.github.andrewoma.dexx.collection.Map as PersMap
+
+
+class Controller(
+ val program: Program,
+ val trace: EvaluationTrace = EvaluationTrace.NULL,
+ val profiler: Profiler? = null,
+ val storeView: StoreView? = null)
+{
+ private val session: EvaluationSession = EvaluationSession.current()
+
+ private val ruleIndex: RuleIndex = RuleIndex(program.handlers())
+
+ private val frameStack = FrameStack(storeView)
+
+ private val matcher = Matcher(ruleIndex, profiler)
+
+ // persistent (functional) object. reassigned on update
+ private var propHistory = PropagationHistory()
+
+ internal fun currentFrame(): Frame = frameStack.current
+
+ fun storeView(): StoreView = frameStack.current.store.view()
+
+ fun activate(constraint: Constraint) {
+ try {
+ process(session.occurrence(constraint, noLogicalContext)) // FIXME noLogicalContext
+ }
+ catch (t: Throwable) {
+ throw t
+ }
+ }
+
+ fun reactivate(occurrence: ConstraintOccurrence) {
+ try {
+ process(occurrence)
+ }
+ catch (t: Throwable) {
+ throw t
+ }
+ }
+
+ /** For tests only */
+ fun evaluate(occurrence: ConstraintOccurrence): StoreView {
+ process(occurrence)
+ return storeView()
+ }
+
+ private fun process(active: ConstraintOccurrence) {
+ assert(active.isAlive())
+
+ profiler.profile("process_${active.constraint().symbol()}") {
+
+ if (!active.isStored()) {
+ frameStack.current.store.store(active)
+ trace.activate(active)
+ } else {
+ trace.reactivate(active)
+ }
+
+ for (match in matcher.matches(active, frameStack.current.store)) {
+ // TODO: paranoid check. should be isAlive() instead
+ if (!active.isStored()) break
+ if (!match.successful) continue
+ // FIXME: move this check elsewhere
+ if ((match.keptOccurrences + match.discardedOccurrences).any { co -> !co.isStored() }) continue
+ if (propHistory.isRecorded(match)) continue
+
+ trace.trying(match)
+
+ for (prd in match.patternPredicates) {
+ tellPredicate(prd, match.logicalContext, trace)
+ }
+
+ if (!match.rule.checkGuard(match.logicalContext, trace)) {
+ trace.reject(match)
+ continue
+ }
+
+ trace.trigger(match)
+
+ for (occ in match.discardedOccurrences) {
+ frameStack.current.store.discard(occ)
+ trace.discard(occ)
+ }
+
+ var failure: EvaluationFailureException? = null
+
+ for (body in match.rule.bodyAlternation()) {
+ if (failure != null) {
+ trace.failure(failure)
+ trace.retry(match)
+ failure = null
+ }
+
+ // propHistory is now functional (persistent)
+ // we must reassign the field on every rule triggering
+ // and store on the stack the last value before rule activation in order to undo in case of failure
+ val savedPropHistory = propHistory
+ this.propHistory = propHistory.record(match)
+
+ val savedFrame = frameStack.current
+ frameStack.push()
+
+ try {
+ for (item in body) {
+ when (item) {
+ is Constraint -> process(session.occurrence(item, match.logicalContext))
+ is Predicate -> tellPredicate(item, match.logicalContext, trace)
+ else -> throw IllegalArgumentException("unknown item ${item}")
+ }
+ }
+ }
+ catch (ex: EvaluationFailureException) {
+ failure = ex
+
+ // abrupt termination: restore the state
+ this.propHistory = savedPropHistory
+
+ frameStack.reset(savedFrame)
+ }
+
+ if (failure == null) {
+ // normal termination: skip the other alternatives
+ break
+ }
+ }
+
+ if (failure != null) {
+ throw failure
+ }
+
+ trace.finish(match)
+ }
+
+ // TODO: should be isAlive()
+ if (active.isStored()) {
+ trace.suspend(active)
+ }
+
+ }
+ }
+
+ private fun Rule.checkGuard(logicalContext: LogicalContext, trace: EvaluationTrace): Boolean =
+ profiler.profile("checkGuard") {
+
+ return guard().all { prd -> askPredicate(prd, logicalContext, trace) }
+
+ }
+
+ private fun askPredicate(predicate: Predicate, logicalContext: LogicalContext, trace: EvaluationTrace): Boolean =
+ profiler.profile("ask_${predicate.symbol()}", {
+
+ session.sessionSolver().ask(session.invocation(predicate, logicalContext))
+
+ })
+
+ private fun tellPredicate(predicate: Predicate, logicalContext: LogicalContext, trace: EvaluationTrace) =
+ profiler.profile("tell_${predicate.symbol()}") {
+
+ session.sessionSolver().tell(session.invocation(predicate, logicalContext))
+
+ }
+
+}
+
+private val noLogicalContext: LogicalContext = object: LogicalContext {
+ override fun variable(metaLogical: MetaLogical): Logical = TODO()
+}
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt
new file mode 100644
index 00000000..098e1c0c
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.evaluation.*
+import jetbrains.mps.logic.reactor.logical.LogicalContext
+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 jetbrains.mps.logic.reactor.util.Profiler
+import java.util.*
+import com.github.andrewoma.dexx.collection.LinkedList as PLinkedList
+import com.github.andrewoma.dexx.collection.List as PList
+
+/**
+ * @author Fedor Isakov
+ */
+
+interface SessionObjects {
+
+ fun handler(): Controller
+
+}
+
+class EvaluationSessionImpl private constructor (
+ val program: Program,
+ val sessionSolver: SessionSolver,
+ val trace: EvaluationTrace) : EvaluationSession(), SessionObjects
+{
+
+ lateinit var controller: Controller
+
+ private fun launch(main: Constraint, profiler: Profiler?, storeView: StoreView?) {
+ this.controller = Controller(program, trace, profiler, storeView)
+ 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
+
+ override fun withPredicates(vararg predicateSymbols: PredicateSymbol): EvaluationSession.Config {
+ this.predicateSymbols.addAll(Arrays.asList(* predicateSymbols))
+ return this
+ }
+
+ override fun withTrace(computingTracer: EvaluationTrace): EvaluationSession.Config {
+ this.evaluationTrace = computingTracer
+ return this
+ }
+
+ override fun withStoreView(storeView: StoreView): EvaluationSession.Config {
+ this.storeView = storeView
+ return this
+ }
+
+ override fun withParam(key: String, param: Any): EvaluationSession.Config {
+ this.parameters.put(key, param)
+ return this
+ }
+
+ override fun start(sessionSolver: SessionSolver): EvaluationSession {
+ var session = ourBackend.ourSession.get()
+ if (session != null) throw IllegalStateException("session already active")
+
+ sessionSolver.init(evaluationTrace, * predicateSymbols.toArray(arrayOfNulls(predicateSymbols.size)))
+
+ @Suppress("UNCHECKED_CAST")
+ val durations =
+ parameters.get("profiling.data") as MutableMap?
+ val profiler = durations?.let { Profiler() }
+
+ session = EvaluationSessionImpl(program, sessionSolver, evaluationTrace)
+ ourBackend.ourSession.set(session)
+ try {
+ session.launch(parameters["main"] as Constraint, profiler, storeView)
+ }
+ finally {
+ try {
+ profiler?.run {
+ formattedData().entries.forEach { e -> durations?.put(e.key, e.value) }
+ clear()
+ }
+ }
+ catch (t: Throwable) {
+ // avoid nested failure
+ }
+ ourBackend.ourSession.set(null)
+ }
+
+ return session
+ }
+ }
+
+ override fun handler() = controller
+
+ override fun sessionSolver(): SessionSolver = sessionSolver
+
+ override fun storeView(): StoreView =
+ controller.storeView()
+
+ override fun invocation(predicate: Predicate, logicalContext: LogicalContext): PredicateInvocation =
+ predicate.invocation(program.instantiateArguments(predicate.arguments(), logicalContext), logicalContext)
+
+ override fun occurrence(constraint: Constraint, logicalContext: LogicalContext): ConstraintOccurrence =
+ constraint.occurrence(controller, program.instantiateArguments(constraint.arguments(), logicalContext), logicalContext)
+
+ private class Backend : EvaluationSession.Backend {
+
+ val ourSession = ThreadLocal()
+
+ override fun current(): EvaluationSession = ourSession.get() ?: throw IllegalStateException("no session")
+
+ override fun createConfig(program: Program): EvaluationSession.Config = Config(program)
+ }
+
+ companion object {
+ private val ourBackend = Backend()
+
+ fun init() {
+ setBackend(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/Frame.kt
new file mode 100644
index 00000000..28d71442
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Frame.kt
@@ -0,0 +1,135 @@
+/*
+ * 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 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.evaluation.StoreView
+import jetbrains.mps.logic.reactor.logical.Logical
+import jetbrains.mps.logic.reactor.util.IdWrapper
+import jetbrains.mps.logic.reactor.util.cons
+import jetbrains.mps.logic.reactor.util.remove
+import java.util.*
+
+internal class Frame: LogicalObserver, StoreKeeper {
+
+ val stack: FrameStack
+
+ val store: Store
+
+ private var observers: Map>, ConsList<(StoreKeeper) -> LogicalObserver>>
+
+ constructor(stack: FrameStack) {
+ this.stack = stack
+ this.store = Store() { stack.current }
+ this.observers = Maps.of()
+ }
+
+ constructor(stack: FrameStack, prev: Frame) {
+ this.stack = stack
+ this.store = Store(prev.store) { stack.current }
+ this.observers = prev.observers
+ }
+
+ constructor(stack: FrameStack, storeView: StoreView) {
+ this.stack = stack
+ this.store = Store(storeView) { stack.current }
+ this.observers = Maps.of()
+ }
+
+ override fun store() = store
+
+ override fun addObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) {
+ val logicalId = IdWrapper(logical)
+ if (!observers.containsKey(logicalId)) {
+ stack.addObserver(logical)
+ }
+ this.observers = observers.put(logicalId,
+ observers[logicalId]?.prepend(obs) ?: cons(obs))
+ }
+
+ override fun removeObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) {
+ val logicalId = IdWrapper(logical)
+ observers[logicalId].remove(obs)?.let { newList ->
+ this.observers = observers.put(logicalId, newList)
+ if (newList.isEmpty) {
+ stack.removeObserver(logical)
+ }
+ }
+ }
+
+ override fun valueUpdated(logical: Logical<*>) {
+ observers[IdWrapper(logical)]?.let { list ->
+ for (obs in list) {
+ obs(this).valueUpdated(logical)
+ }
+ }
+ }
+
+ override fun parentUpdated(logical: Logical<*>) {
+ observers[IdWrapper(logical)]?.let { list ->
+ for (obs in list) {
+ obs(this).parentUpdated(logical)
+ }
+ }
+ }
+
+}
+
+internal class FrameStack(storeView: StoreView?) : LogicalObserver {
+
+ var current: Frame
+
+ val observing = HashSet>>()
+
+ init {
+ this.current = if (storeView != null) Frame(this, storeView) else Frame(this)
+ }
+
+ fun push(): Frame {
+ val frame = Frame(this, current)
+ this.current = frame
+ return frame
+ }
+
+ fun reset(frame: Frame): Unit {
+ this.current = frame
+ }
+
+ fun addObserver(logical: Logical<*>) {
+ val token = IdWrapper(logical)
+ if (!observing.contains(token)) {
+ logical.addObserver(this)
+ observing.add(token)
+ }
+ }
+
+ fun removeObserver(logical: Logical<*>) {
+ // NOP
+ // yes, keep listening, the updates are still filtered down the stream
+ }
+
+ override fun valueUpdated(logical: Logical<*>) {
+ current.valueUpdated(logical)
+ }
+
+ override fun parentUpdated(logical: Logical<*>) {
+ current.parentUpdated(logical)
+ }
+
+}
\ 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
new file mode 100644
index 00000000..ca11984a
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.evaluation.PredicateInvocation
+import jetbrains.mps.logic.reactor.logical.LogicalContext
+import jetbrains.mps.logic.reactor.program.Predicate
+
+/**
+ * @author Fedor Isakov
+ */
+
+fun Predicate.invocation(arguments: List<*>, logicalContext: LogicalContext): PredicateInvocation =
+ Invocation(this, arguments, logicalContext)
+
+private data class Invocation(val predicate: Predicate,
+ val invocationArguments: List<*>,
+ val logicalContext: LogicalContext) : PredicateInvocation {
+
+ override fun predicate(): Predicate = predicate
+
+ override fun arguments(): List<*> = invocationArguments
+
+ override fun logicalContext(): LogicalContext = logicalContext
+}
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt
new file mode 100644
index 00000000..97f49e44
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.logical.Logical
+import jetbrains.mps.logic.reactor.logical.MetaLogical
+import jetbrains.mps.logic.reactor.logical.JoinableLogical
+import java.util.*
+
+/**
+ * @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 {
+
+ companion object {
+ var lastIdx = 0
+ }
+
+ val name: String
+
+ val metaLogical: MetaLogical
+
+ var _parent: LogicalImpl? = null
+
+ var _value: T? = null
+
+ var rank = 0
+
+ internal val valueObservers = ArrayList, LogicalObserver>>()
+
+ internal val parentObservers = ArrayList, LogicalObserver>>()
+
+ constructor(value: T) {
+ this.name = "$${++lastIdx}"
+ this.metaLogical = DefaultMetaLogical(name)
+ this._value = value
+ }
+
+ constructor(name: String) {
+ this.name = "${name}_${++lastIdx}"
+ this.metaLogical = DefaultMetaLogical(name)
+ }
+
+ constructor(name: String, value: T) {
+ this.name = "${name}_${++lastIdx}"
+ this.metaLogical = DefaultMetaLogical(name)
+ this._value = value
+ }
+
+ constructor(metaLogical: MetaLogical) {
+ this.metaLogical = metaLogical
+ this.name = "${metaLogical.name()}_${++lastIdx}"
+ }
+
+ override fun name(): String = name
+
+ override fun value(): T? = _value
+
+ override fun isBound(): Boolean = find()._value != null
+
+ override fun isWildcard(): Boolean = metaLogical.isWildcard
+
+ override fun metaLogical(): MetaLogical = metaLogical
+
+ override fun findRoot(): JoinableLogical = find()
+
+ override fun setValue(newValue: T) {
+ if (_value !== newValue) {
+ this._value = newValue
+ notifyValueUpdated()
+ }
+ }
+
+ override fun union(other: JoinableLogical, reconciler: JoinableLogical.ValueReconciler) {
+ val thisRepr = this.find()
+ val otherRepr = (other as LogicalImpl).find()
+
+ // invariant: thisRepr.rank > otherRepr.rank
+ if (thisRepr.rank() < otherRepr.rank()) {
+ otherRepr.union(thisRepr, reconciler);
+ return;
+
+ } else if (thisRepr.rank() == otherRepr.rank()) {
+ thisRepr.incRank();
+ }
+
+ val thisVal = thisRepr.value();
+ val otherVal = otherRepr.value();
+
+ // first copy the value
+ if (thisVal == null && otherVal != null) {
+ // var ground
+ thisRepr.setValue(otherVal);
+ // TODO: clear the value in the "other" logical after union
+
+ } else if (thisVal != null && otherVal == null) {
+ // ground var
+ // TODO: no need to copy the value
+ otherRepr.setValue(thisVal);
+ }
+
+ // then set parent
+ otherRepr.setParent(thisRepr)
+ thisRepr.mergeParentObservers(otherRepr)
+
+ // last, reconcile the values/merge observers
+ if (thisVal == null && otherVal == null) {
+ // var var
+ thisRepr.mergeValueObservers(otherRepr);
+
+ } else if (thisVal != null && otherVal != null) {
+ // ground ground
+ reconciler.reconcile(thisVal, otherVal);
+ }
+ }
+
+ override fun union(other: JoinableLogical) {
+ union(other, { a, b -> if (a != b) throw IllegalStateException("$a does not equal to $b")})
+ }
+
+ private fun find(): LogicalImpl {
+ val tmp = _parent
+ if (tmp == null) return this
+ else {
+ val root = tmp.find()
+ this._parent = root
+ return root
+ }
+ }
+
+ private fun rank(): Int = rank
+
+ private fun incRank() { rank++ }
+
+ private fun setParent(parent: LogicalImpl) {
+ this._parent = parent
+ notifyParentUpdated()
+ }
+
+ private fun mergeValueObservers(mergeFrom: JoinableLogical) {
+ val other = mergeFrom as LogicalImpl
+ valueObservers.addAll(other.valueObservers)
+ other.valueObservers.clear()
+ }
+
+ private fun mergeParentObservers(mergeFrom: JoinableLogical) {
+ val other = mergeFrom as LogicalImpl
+ parentObservers.addAll(other.parentObservers)
+ other.parentObservers.clear()
+ }
+
+ private fun notifyValueUpdated() {
+ val obs = ArrayList(valueObservers)
+ this.valueObservers.clear()
+ for (p in obs) {
+ p.second.valueUpdated(p.first)
+ }
+ }
+
+ private fun notifyParentUpdated() {
+ val obs = ArrayList(parentObservers)
+ for (p in obs) {
+ p.second.parentUpdated(p.first)
+ }
+ }
+
+ override fun toString(): String =
+ if (_parent != null) "${name}(^${_parent.toString()})"
+ else name
+
+}
+
+class DefaultMetaLogical (val name: String) : MetaLogical(name, Object::class.java as Class) {}
\ No newline at end of file
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Matcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Matcher.kt
new file mode 100644
index 00000000..2a19661e
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Matcher.kt
@@ -0,0 +1,194 @@
+/*
+ * 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.evaluation.ConstraintOccurrence
+import jetbrains.mps.logic.reactor.evaluation.MatchRule
+import jetbrains.mps.logic.reactor.logical.Logical
+import jetbrains.mps.logic.reactor.logical.LogicalContext
+import jetbrains.mps.logic.reactor.logical.LogicalOwner
+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.Rule
+import jetbrains.mps.logic.reactor.util.LazyIterable
+import jetbrains.mps.logic.reactor.util.Profiler
+import jetbrains.mps.unification.Substitution
+import jetbrains.mps.unification.Term
+import jetbrains.mps.unification.TermWrapper
+import com.github.andrewoma.dexx.collection.List as PersList
+
+/**
+ * @author Fedor Isakov
+ */
+
+class Matcher(val ruleIndex: RuleIndex,
+ val profiler: Profiler? = null)
+{
+
+ private val rule2matchTrieSet = HashMap()
+
+ fun matches(activeOcc: ConstraintOccurrence, aux: OccurrenceIndex) : Iterable = object : Iterable {
+
+ override fun iterator(): Iterator {
+
+ val relevantRules = ruleIndex.forOccurrence(activeOcc).toList()
+
+ val tries = LazyIterable(relevantRules) { rule ->
+ rule2matchTrieSet[rule] ?: MatchTrieSet(rule, profiler).apply {
+ rule2matchTrieSet[rule] = this
+ }
+ }.asSequence()
+
+ return tries.flatMap { matchTrieSet ->
+ matchTrieSet.matches(activeOcc, aux)
+ }.map { partialMatch ->
+ partialMatch.complete(profiler)
+ }.filter { match ->
+ match.successful
+ }.iterator()
+ }
+
+ }
+
+}
+
+class Match(val rule: Rule,
+ val substitution: Substitution,
+ val keptOccurrences: List,
+ val discardedOccurrences: List) : MatchRule
+{
+
+ val logicalContext: LogicalContext by lazy(LazyThreadSafetyMode.NONE) {
+ object : LogicalContext {
+
+ // invariant: the variables in substitution bindings can only be instances of MetaLogical
+ val meta2subs = substitution.bindings().map { b ->
+ (b.`var`().symbol() as MetaLogical).to(b.term().toValue())
+ }.toMap(HashMap, Any?>())
+
+ val meta2logical = HashMap, Logical<*>>()
+
+ override fun variable(meta: MetaLogical): Logical =
+ (meta2logical[meta] ?: meta2subs[meta]?.let { value ->
+ when (value) {
+ is Logical<*> -> value
+ is LogicalOwner -> value.logical()
+ else -> LogicalImpl(value)
+ }
+ } ?: meta.logical().also {
+ logical -> meta2logical[meta] = logical }) as Logical
+
+ }
+ }
+
+ val patternPredicates: List by lazy(LazyThreadSafetyMode.NONE) {
+ (rule.headKept() + rule.headReplaced()).zip(keptOccurrences + discardedOccurrences).flatMap { p ->
+ p.first.patternPredicates(p.second.arguments()) }.toList()
+ }
+
+ val successful: Boolean
+ get() = substitution.isSuccessful
+
+ val isPropagation: Boolean
+ get() = !keptOccurrences.isEmpty() && discardedOccurrences.isEmpty()
+
+ override fun rule(): Rule = rule
+
+ override fun matchHeadKept(): Iterable = keptOccurrences
+
+ override fun matchHeadReplaced(): Iterable = discardedOccurrences
+
+}
+
+/** Function term with arguments == constraints converted to terms. May contain variables. */
+class RuleTerm(rule: Rule) :
+ Function(
+ rule.tag(),
+ (rule.headKept() + rule.headReplaced()).map { c -> ConstraintPatternTerm(c) }) {}
+
+/** Function term with arguments == constraint arguments converted to terms.
+ * MetaLogical arguments are term variables.
+ * Everything else is either a term or a constant wrapping the value. */
+class ConstraintPatternTerm(constraint: Constraint) :
+ Function(
+ constraint.symbol(),
+ constraint.arguments().mapIndexed { idx: Int, arg: Any? -> arg.asTerm() }) {}
+
+/** Function term with arguments == terms corresponding to constraint occurrences. Never contains variables. */
+class MatchTerm(rule: Rule, kept: List, discarded: List) :
+ Function(rule.tag(), (kept + discarded).map { co -> ConstraintOccurrenceTerm(co) }) {}
+
+/** Function term with arguments == constraint occurrence arguments converted to terms.
+ * Logical arguments are constants wrapping the logical itself.
+ * Everything else is either a term or a constant wrapping the value.
+ * Never contains variable terms. */
+class ConstraintOccurrenceTerm(occurrence: ConstraintOccurrence) :
+ Function(
+ occurrence.constraint().symbol(),
+ occurrence.arguments().mapIndexed { idx: Int, arg: Any? -> arg.asTerm() }) {}
+
+
+/** Wraps the terms for the internal representation of the unification algorithm.
+ * When unifying constraint and occurrence terms we are interested in bindings of the meta logicals,
+ * so these are represented as variables.
+ * In addition, the logicals are either represented as constants (unbound ones) or their values.
+ * When unwrapping, the reverse transformation takes place, so that the unification result binds meta logicals
+ * to logicals, and not to their values. */
+class MatchTermWrapper() : TermWrapper {
+
+ override fun wrap(orig: Term): Term =
+ if (orig.`is`(Term.Kind.VAR)) {
+ val symbol = orig.symbol()
+ when (symbol) {
+ is MetaLogical<*> -> orig
+ is Logical<*> -> symbol.let { logical ->
+ if (logical.isBound && logical.findRoot().value() is Term) {
+ WrapGroundLogical(logical as Logical)
+
+ } else {
+ WrapFreeLogical(logical)
+ }
+ }
+ else -> WrapConstant(orig)
+ }
+
+ } else {
+ orig
+ }
+
+ override fun unwrap(wrapper: Term): Term = when (wrapper) {
+ is WrapConstant -> wrapper.orig
+ is WrapFreeLogical -> Constant(wrapper.logical)
+ is WrapGroundLogical -> Constant(wrapper.logical)
+ else -> wrapper
+ }
+
+}
+
+fun Any?.asTerm(): Term = when (this) {
+ is MetaLogical<*> -> Variable(this)
+ is Logical<*> -> Variable(this)
+// is Logical<*> -> if (this.isBound) this.findRoot().value().asTerm() else Constant(this.findRoot())
+ is Term -> this // wrapped before unification
+ is Any -> Constant(this)
+ else -> throw NullPointerException()
+}
+
+// FIXME: "unpacking" the logicals
+fun Term.toValue(): Any? = if (this is Constant) this.symbol() else this
+
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt
new file mode 100644
index 00000000..f3029d23
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.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.program.Constraint
+
+/**
+ * @author Fedor Isakov
+ */
+
+fun Constraint.occurrence(controller: Controller, arguments: List<*>, logicalContext: LogicalContext): ConstraintOccurrence =
+ Occurrence({ controller.currentFrame() }, this, arguments, logicalContext)
+
+private data class Occurrence (val currentFrame: () -> Frame,
+ val constraint: Constraint,
+ val arguments: List<*>,
+ val logicalContext: LogicalContext) :
+ ConstraintOccurrence,
+ LogicalObserver,
+ StoreItem
+{
+ override var alive = true
+ override var stored = false
+
+ init {
+ for (a in arguments) {
+ if (a is Logical<*>) {
+ currentFrame().addObserver(a) { this }
+ }
+ }
+ }
+
+ override fun constraint(): Constraint = constraint
+
+ override fun arguments(): List<*> = arguments
+
+ override fun logicalContext(): LogicalContext = logicalContext
+
+ override fun valueUpdated(logical: Logical<*>) {
+ if (alive) {
+ (EvaluationSession.current() as SessionObjects).handler().reactivate(this)
+ }
+ }
+
+ override fun parentUpdated(logical: Logical<*>) {
+ if (alive) {
+ (EvaluationSession.current() as SessionObjects).handler().reactivate(this)
+ }
+ }
+
+ override fun terminate() {
+ for (a in arguments) {
+ if (a is Logical<*>) {
+ currentFrame().removeObserver(a) { this }
+ }
+ }
+ alive = false
+ }
+
+ override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})"
+
+}
+
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatch.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatch.kt
new file mode 100644
index 00000000..851fe0b7
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatch.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.evaluation.ConstraintOccurrence
+import jetbrains.mps.logic.reactor.program.Rule
+import jetbrains.mps.logic.reactor.util.Profiler
+import jetbrains.mps.logic.reactor.util.profile
+import jetbrains.mps.unification.Substitution
+import jetbrains.mps.unification.Unification
+
+internal class PartialMatch(val rule: Rule) {
+
+ internal lateinit var keptOccurrences: Array
+ private set
+
+ internal lateinit var discardedOccurrences: Array
+ private set
+
+ private lateinit var substitution: Substitution
+
+ constructor(rule: Rule, keptSize: Int, discardedSize: Int) :
+ this(rule)
+ {
+ this.keptOccurrences = arrayOfNulls(keptSize)
+ this.discardedOccurrences = arrayOfNulls(discardedSize)
+ }
+
+
+ fun complete(profiler: Profiler? = null): Match {
+ return profiler.profile("matchesRule_${rule.tag()}") {
+
+ val kept = keptOccurrences.asList() as List
+ val discarded = discardedOccurrences.asList() as List
+
+ this.substitution = Unification.unify(MatchTerm(rule, kept, discarded), RuleTerm(rule), MatchTermWrapper())
+
+ if (substitution.isSuccessful) {
+ Match(rule, substitution, kept, discarded)
+ }
+ else
+ Match(rule, substitution, emptyList(), emptyList())
+
+ }
+ }
+
+ internal fun keep(occ: ConstraintOccurrence, idx: Int): PartialMatch {
+ val match = PartialMatch(rule)
+ match.keptOccurrences = this.keptOccurrences.copyOf()
+ match.keptOccurrences[idx] = occ
+ match.discardedOccurrences = this.discardedOccurrences
+ return match
+ }
+
+ internal fun discard(occ: ConstraintOccurrence, idx: Int): PartialMatch {
+ val match = PartialMatch(rule)
+ match.keptOccurrences = this.keptOccurrences
+ match.discardedOccurrences = this.discardedOccurrences.copyOf()
+ match.discardedOccurrences[idx] = occ
+ return match
+ }
+
+ override fun toString(): String = "${keptOccurrences.toList()} \\ ${discardedOccurrences.toList()}"
+}
\ No newline at end of file
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatchTrie.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatchTrie.kt
new file mode 100644
index 00000000..f54a1fac
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatchTrie.kt
@@ -0,0 +1,447 @@
+/*
+ * 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.evaluation.ConstraintOccurrence
+import jetbrains.mps.logic.reactor.logical.Logical
+import jetbrains.mps.logic.reactor.logical.MetaLogical
+import jetbrains.mps.logic.reactor.program.Constraint
+import jetbrains.mps.logic.reactor.program.Rule
+import jetbrains.mps.logic.reactor.util.Profiler
+import jetbrains.mps.logic.reactor.util.profile
+import jetbrains.mps.unification.Term
+import jetbrains.mps.unification.Unification
+import java.util.*
+import kotlin.collections.ArrayList
+import com.github.andrewoma.dexx.collection.Map as PersMap
+
+/**
+ * @author Fedor Isakov
+ */
+internal class MatchTrieSet(val rule: Rule, val profiler: Profiler?) {
+
+ private val keptConstraints = rule.headKept().toCollection(ArrayList(4))
+
+ private val discardedConstraints = rule.headReplaced().toCollection(ArrayList(4))
+
+ // for each slot keep a set of "related" slots, that is the slots with matching (meta-)logicals
+ // this map is built only once per rule
+ private val relatedSlots: List =
+ HashMap, BitSet>().let { meta2slots ->
+
+ (keptConstraints + discardedConstraints).forEachIndexed { slot, cst ->
+ cst.arguments().forEach { arg ->
+ if (arg is MetaLogical<*>) {
+ val bs = meta2slots[arg] ?: BitSet().apply { meta2slots[arg] = this }
+ bs.set(slot)
+ }
+ }
+ }
+
+ ArrayList().apply {
+ (keptConstraints + discardedConstraints).forEachIndexed { slot, cst ->
+ val bs = BitSet()
+ cst.arguments().forEach { arg ->
+ if (arg is MetaLogical<*>) {
+ bs.or(meta2slots[arg])
+ }
+ }
+ bs.clear(slot)
+ add(bs)
+ }
+ }
+
+ }
+
+ val tries = ArrayList()
+
+ fun matches(activeOcc: ConstraintOccurrence, occIndex: OccurrenceIndex): Sequence {
+ val constraints = keptConstraints + discardedConstraints
+
+ val relevantTries = ArrayList()
+ val copyTries = ArrayList()
+ for (t in tries) {
+ if (!t.vacant.isEmpty) {
+ copyTries.add(t)
+ for (idx in t.vacant.stream()) {
+ if (constraints[idx].probablyMatches(activeOcc)) {
+ relevantTries.add(PartialMatchTrie(activeOcc, occIndex, t))
+ copyTries.removeAt(copyTries.size - 1)
+ break
+ }
+ }
+ }
+ }
+ if (relevantTries.isEmpty()) {
+ relevantTries.add(PartialMatchTrie(activeOcc, occIndex, null))
+ }
+
+ this.tries.clear()
+ this.tries.addAll(copyTries)
+ this.tries.addAll(relevantTries)
+
+ return relevantTries.asSequence().flatMap { t -> t.matches() }
+ }
+
+ inner class PartialMatchTrie(val activeOcc: ConstraintOccurrence,
+ val occIndex: OccurrenceIndex,
+ prototype: PartialMatchTrie?)
+ {
+ private val root: RootMatchTrieNode = RootMatchTrieNode(prototype?.root)
+
+ val vacant = BitSet()
+
+ fun matches(): Sequence = object : Sequence {
+
+ override fun iterator() = object : Iterator {
+
+ val firstRoot: MatchTrieNode? = root.firstChild
+
+ var next: PartialMatch? = null
+
+ override fun hasNext(): Boolean {
+ if (next == null) {
+ this.next = calcNext()
+ }
+ return (next != null)
+ }
+
+ override fun next(): PartialMatch {
+ if (next == null) {
+ this.next = calcNext()
+ }
+ if (next != null) {
+ val tmp: PartialMatch = next!!
+ this.next = calcNext()
+ return tmp
+ }
+ throw NoSuchElementException()
+ }
+
+ private fun calcNext(): PartialMatch? {
+ return profiler.profile("partialMatch_calcNext") {
+ // the search always starts at the first root
+ if (firstRoot == null) {
+ return null
+ }
+ var mtn: MatchTrieNode? = firstRoot
+
+ while (mtn != null) {
+ if (mtn.isSeen) {
+ if (mtn.nextSibling == null) {
+ mtn.parent?.setSeen()
+ }
+ mtn = mtn.nextSibling ?: mtn.parent
+
+ } else if (mtn.isLeaf) {
+ mtn.setSeen()
+ vacant.clear()
+ return mtn.match
+
+ } else if (mtn.firstChild == null) {
+ mtn.setSeen()
+ vacant.or(mtn.vacantSlots)
+ vacant.andNot(mtn.relatedFirstVacantSlots)
+
+ } else {
+ mtn = mtn.firstChild
+ }
+ }
+
+ return null
+ }
+ }
+ }
+
+ }
+
+ private abstract inner class BaseMatchTrieNode() {
+
+ var _firstChild: MatchTrieNode? = null
+
+ protected fun copyChildren(copyFrom: BaseMatchTrieNode, parent: MatchTrieNode?) {
+ copyFrom._firstChild?.firstAlive()?.let { copyFirst ->
+
+ val first = MatchTrieNode(parent, copyFirst)
+ var prev = first
+ var copyNext = copyFirst.nextSibling?.firstAlive()
+
+ while (copyNext != null) {
+ val next = MatchTrieNode(parent, copyNext)
+ prev._nextSibling = next
+ prev = next
+ copyNext = copyNext.nextSibling?.firstAlive()
+ }
+
+ this._firstChild = first
+ }
+ }
+
+ protected fun buildNode(siblings: Iterable) : MatchTrieNode? {
+ var first: MatchTrieNode? = null
+ var prev: MatchTrieNode? = null
+ for (next in siblings) {
+ if (first == null) {
+ first = next
+ }
+ prev?._nextSibling = next
+ prev = next
+ }
+
+ return first
+ }
+
+ }
+
+ private inner class RootMatchTrieNode(copyFrom: RootMatchTrieNode? = null) : BaseMatchTrieNode() {
+
+ init {
+ if (copyFrom != null) { copyChildren(copyFrom, null) }
+ }
+
+ val firstChild: MatchTrieNode?
+ get() {
+ if (_firstChild == null) {
+ this._firstChild = buildNode(calcRoots())
+ }
+ return _firstChild
+ }
+
+ private fun calcRoots(): Iterable {
+ return profiler.profile>("partialMatch_calcRoots") {
+
+ keptConstraints.withIndex().filter {(_, cst) ->
+ cst.probablyMatches(activeOcc)}.map {(idx, cst) ->
+ MatchTrieNode(null, true, cst, activeOcc, idx)
+ } +
+ discardedConstraints.withIndex().filter {(_, cst) ->
+ cst.probablyMatches(activeOcc)}.map {(idx, cst) ->
+ MatchTrieNode(null, false, cst, activeOcc, idx)
+ }
+
+ }
+ }
+
+ }
+
+ private inner class MatchTrieNode(val parent: MatchTrieNode?,
+ val keepConstraint: Boolean,
+ val constraint: Constraint,
+ val occurrence: ConstraintOccurrence,
+ val index: Int) : BaseMatchTrieNode()
+ {
+
+ constructor(parent: MatchTrieNode?, copyFrom: MatchTrieNode) :
+ this(parent, copyFrom.keepConstraint, copyFrom.constraint, copyFrom.occurrence, copyFrom.index)
+ {
+ copyChildren(copyFrom, this)
+ }
+
+ val firstChild: MatchTrieNode?
+ get() {
+ if (_firstChild == null) {
+ this._firstChild = buildNode(calcChildren())
+ }
+ return _firstChild
+ }
+
+ var _nextSibling: MatchTrieNode? = null
+
+ val nextSibling: MatchTrieNode?
+ get() = _nextSibling
+
+ private var _isSeen: Boolean = false
+
+ val isSeen: Boolean
+ get() = _isSeen
+
+ val vacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) {
+ val result = BitSet()
+ result.set(0, keptConstraints.size + discardedConstraints.size)
+ foldPath(result) { bits, mtn ->
+ val slot = if (mtn.keepConstraint) mtn.index else mtn.index + keptConstraints.size
+ bits.clear(slot)
+ bits
+ }
+ result
+ }
+
+ val relatedFirstVacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) {
+ val result = vacantSlots.clone() as BitSet
+ result.and(relatedSlots[if (keepConstraint) index else index + keptConstraints.size])
+ result
+ }
+
+ val isLeaf: Boolean
+ get() = vacantSlots.isEmpty
+
+ val match: PartialMatch by lazy(LazyThreadSafetyMode.NONE) {
+ val parentMatch = parent?.match ?: PartialMatch(rule, keptConstraints.size, discardedConstraints.size)
+ if (keepConstraint) parentMatch.keep(occurrence, index)
+ else parentMatch.discard(occurrence, index)
+ }
+
+ fun setSeen() {
+ this._isSeen = true
+ }
+
+ private fun calcChildren(): Iterable {
+ val nextSlots = if (!relatedFirstVacantSlots.isEmpty) relatedFirstVacantSlots else vacantSlots
+ val nextSlot = nextSlots.nextSetBit(0)
+ if (nextSlot < 0) {
+ return emptyList()
+ }
+
+ val keep = nextSlot < keptConstraints.size
+ val nextIdx = if (keep) nextSlot else nextSlot - keptConstraints.size
+ val nextCst = if (keep) keptConstraints[nextSlot] else discardedConstraints[nextIdx]
+
+ return if (nextCst.probablyMatches(activeOcc) && !hasOccurrence(activeOcc)) {
+ arrayListOf(MatchTrieNode(this, keep, nextCst, activeOcc, nextIdx))
+
+ }
+ else {
+ val (result, auxOccurrences) = lookupAuxOccurrences(nextCst)
+ when (result) {
+ // FIXME: this optimization is causing major problems, drop it?
+ Result.DEFINITIVE -> null /*parent*/
+ Result.NONE -> this
+ Result.INCONCLUSIVE -> null
+ }?.forEachInPath { mtn -> mtn.setSeen() }
+
+ auxOccurrences.map { auxOcc -> MatchTrieNode(this, keep, nextCst, auxOcc, nextIdx) }
+ }
+ }
+
+ // the first component indicates the quality of the result:
+ // DEFINITIVE indicates the only possible matches were returned
+ // INCONCLUSIVE may yield some (ir-)relevant results
+ // NONE means no results could have been found at all, so it's useless to attempt again with the same input
+ private fun lookupAuxOccurrences(cst: Constraint): Pair> {
+ return profiler.profile>> ("lookupAux_${cst.symbol()}") {
+
+ val fromArgs = ArrayList(4)
+ for (arg in metaInstances(cst.arguments()) + cst.arguments().filter { arg -> !(arg is MetaLogical<*>) }) {
+ when (arg) {
+ is Logical<*> -> fromArgs.addAll(occIndex.forLogicalAndConstraint(arg, cst))
+
+ is Term -> fromArgs.addAll(occIndex.forTermAndConstraint(arg, cst))
+
+ is Any -> fromArgs.addAll(occIndex.forValue(arg))
+ }
+ }
+ if (fromArgs.isNotEmpty()) {
+ Result.DEFINITIVE.to(fromArgs.filter { occ -> cst.probablyMatches(occ) && !hasOccurrence(occ) })
+
+ } else {
+ return profiler.profile>> ("lookupAux_default_${cst.symbol()}") {
+
+ val hasRelated = relatedFirstVacantSlots == vacantSlots
+ val allArgsMetaLogicals = cst.arguments().all { a -> a is MetaLogical<*> }
+ val noArgs = cst.arguments().isEmpty()
+
+ if (noArgs || (allArgsMetaLogicals && !hasRelated)) {
+ Result.INCONCLUSIVE.to(occIndex.forSymbol(cst.symbol()).filter { occ -> cst.probablyMatches(occ) && !hasOccurrence(occ) })
+
+ }
+ else {
+ // all candidate occurrences should have been found by this time; if not, then there's none
+ Result.NONE.to(emptyList())
+ }
+ }
+ }
+ }
+ }
+
+ fun firstAlive(): MatchTrieNode? {
+ var fca: MatchTrieNode? = this
+ while (!(fca?.occurrence?.isAlive() ?: true)) {
+ fca = fca?.nextSibling
+ }
+ return fca
+ }
+
+
+ private fun collectMetaInstances(meta: Collection<*>, list: ArrayList): ArrayList {
+ constraint.arguments().zip(occurrence.arguments()).forEach { p ->
+ if (p.first is MetaLogical<*> && meta.contains(p.first) && !list.contains(p.second)) {
+ list.add(p.second!!)
+ }
+ }
+ return list
+ }
+
+ private fun metaInstances(meta: Collection<*>): List =
+ foldPath(ArrayList(4)) { list, mtn -> mtn.collectMetaInstances(meta, list) }
+
+ private fun hasOccurrence(occ: ConstraintOccurrence): Boolean =
+ anyInPath { mtn -> mtn.occurrence === occ } // referential equality!
+
+ private inline fun anyInPath(predicate: (MatchTrieNode) -> Boolean): Boolean =
+ foldPath(false) { b, mtn -> b || predicate(mtn) }
+
+ private inline fun forEachInPath(proc: (MatchTrieNode) -> Unit) =
+ foldPath(Unit) { b, mtn -> proc(mtn) }
+
+ private inline fun foldPath(initVal: T, step: (T, MatchTrieNode) -> T): T {
+ var mtn: MatchTrieNode? = this
+ var currVal = initVal
+ while(mtn != null) {
+ currVal = step(currVal, mtn)
+ mtn = mtn.parent
+ }
+ return currVal
+ }
+
+ }
+
+ }
+
+ fun Constraint.probablyMatches(occ: ConstraintOccurrence): Boolean {
+ if (this.symbol() != occ.constraint().symbol()) { return false }
+
+ val cstArgIt = this.arguments().iterator()
+ val occArgIt = occ.arguments().iterator()
+ while (cstArgIt.hasNext() && occArgIt.hasNext()) {
+ val cstArg = cstArgIt.next()
+ val occArg = occArgIt.next()
+
+ if (cstArg is Term && occArg is Term) {
+ if (!Unification.unify(cstArg, occArg).isSuccessful) { return false }
+ }
+
+ if (cstArg !is MetaLogical<*> && occArg !is Logical<*>) {
+ if (!(cstArg?.equals(occArg) ?: (occArg === null))) { return false }
+ }
+ }
+
+ return true
+ }
+
+ enum class Result {
+ DEFINITIVE,
+ INCONCLUSIVE,
+ NONE
+ }
+
+}
+
+
+
+
+
+
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/PropagationHistory.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PropagationHistory.kt
new file mode 100644
index 00000000..2eb0dac9
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PropagationHistory.kt
@@ -0,0 +1,59 @@
+/*
+ * 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 com.github.andrewoma.dexx.collection.ConsList
+import com.github.andrewoma.dexx.collection.Maps
+import com.github.andrewoma.dexx.collection.Map as PersMap
+import com.github.andrewoma.dexx.collection.List as PersList
+import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
+import jetbrains.mps.logic.reactor.program.Rule
+import jetbrains.mps.logic.reactor.util.IdWrapper
+
+internal class PropagationHistory {
+
+ val recordedPropagation : PersMap>>>
+
+ constructor() {
+ this.recordedPropagation = Maps.of>>> ()
+ }
+
+ private constructor(recorded : PersMap>>>) {
+ this.recordedPropagation = recorded
+ }
+
+ fun isRecorded(m: Match): Boolean {
+ if (!m.isPropagation) return false
+ val test = m.keptOccurrences.map { occ -> IdWrapper(occ) }.sortedBy { idOcc -> idOcc.idHash }.toList()
+
+ return recordedPropagation.get(m.rule)?.let { hist ->
+ hist.any { recorded -> recorded == test } // use the reference equality via IdWrapper
+ } ?: false
+ }
+
+ fun record(pm: Match): PropagationHistory {
+ if (pm.isPropagation) {
+ val idOccs = pm.keptOccurrences.map { occ -> IdWrapper(occ) }.sortedBy { id -> id.idHash }.toList()
+
+ val hist = recordedPropagation.get(pm.rule) ?: ConsList.empty>>()
+
+ return PropagationHistory(recordedPropagation.put(pm.rule, hist.prepend(idOccs)))
+ }
+ return this
+ }
+
+}
\ 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
new file mode 100644
index 00000000..cdb01fbf
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.evaluation.ConstraintOccurrence
+import jetbrains.mps.logic.reactor.logical.Logical
+import jetbrains.mps.logic.reactor.logical.MetaLogical
+import jetbrains.mps.logic.reactor.program.Constraint
+import jetbrains.mps.logic.reactor.program.ConstraintSymbol
+import jetbrains.mps.logic.reactor.program.Handler
+import jetbrains.mps.logic.reactor.program.Rule
+import jetbrains.mps.unification.Term
+import java.util.*
+
+/**
+ * @author Fedor Isakov
+ */
+
+class RuleIndex : Iterable {
+
+ private val primarySymbol2valueIndex = HashMap()
+
+ private val auxSymbol2valueIndex = HashMap()
+
+ private val tag2rule = LinkedHashMap()
+
+ constructor(handlers: Iterable) {
+ for (h in handlers) {
+ for (r in h.rules()) {
+ tag2rule[r.tag()] = r
+ }
+ }
+ buildIndex(handlers)
+ }
+
+ fun byTag(tag: String): Rule? = tag2rule[tag]
+
+ fun forOccurrence(occ: ConstraintOccurrence): Iterable =
+ primarySymbol2valueIndex.get(occ.constraint().symbol())?.select(occ) ?:
+ (auxSymbol2valueIndex.get(occ.constraint().symbol())?.select(occ) ?: emptyList())
+
+ override fun iterator(): Iterator = tag2rule.values.iterator()
+
+ private fun buildIndex(handlers: Iterable) {
+ // first, init the primary symbols value index
+ handlers
+ .flatMap { h -> h.primarySymbols() }
+ .forEach { symbol -> primarySymbol2valueIndex.getOrPut(symbol) { ValueIndex(symbol) } }
+
+ for (h in handlers) {
+ val primSymbols = h.primarySymbols().toSet()
+ for (r in h.rules()) {
+ for (c in (r.headKept() + r.headReplaced())) {
+ val symbol = c.symbol()
+ if (symbol in primSymbols) {
+ primarySymbol2valueIndex[symbol]?.update(r, c)
+
+ } else {
+ auxSymbol2valueIndex.getOrPut(symbol) { ValueIndex(symbol) }.update(r, c)
+ }
+ }
+ }
+ }
+ }
+
+}
+
+/**
+ * Represents a list of rules with at least one constraint matching the 'symbol'.
+ * The list is indexed by all values extracted from the constraint arguments.
+ * The list can be selected based on the argument(s) of a constraint occurrence.
+ */
+private class ValueIndex(val symbol: ConstraintSymbol) {
+
+ val rules = ArrayList()
+
+ val anySelectors = ArrayList>()
+
+ val termSelectors = ArrayList>()
+
+ val wildcardSelectors = ArrayList()
+
+ init {
+ for (idx in 1..symbol.arity()) {
+ anySelectors.add(HashMap())
+ termSelectors.add(TermTrie())
+ wildcardSelectors.add(BitSet())
+ }
+ }
+
+ fun update(rule: Rule, cst: Constraint) {
+ var bit = rules.indexOf(rule)
+ if (bit < 0) {
+ bit = rules.size
+ rules.add(rule)
+ }
+
+ for ((idx, arg) in cst.arguments().withIndex()) {
+ val value2indices = anySelectors[idx]
+ when (arg) {
+ is MetaLogical<*> -> {
+ // all values should be accepted by a meta logical
+ wildcardSelectors[idx].set(bit)
+ }
+ is Term -> {
+ termSelectors.set(idx, termSelectors[idx].put(arg, bit))
+ }
+ is Any -> {
+ val bitset = value2indices.getOrPut(arg) { BitSet() }
+ bitset.set(bit)
+ }
+ else -> {
+ /* never happens */
+ throw NullPointerException()
+ }
+ }
+ }
+ }
+
+ fun select(occ: ConstraintOccurrence): Iterable {
+ if (occ.constraint().symbol() != symbol) throw IllegalArgumentException()
+
+ // initially select all rules
+ val upToBit = rules.size
+ val ruleIndices = BitSet(rules.size)
+ ruleIndices.set(0, upToBit)
+
+ for ((idx, arg) in occ.arguments().withIndex()) {
+ val value2indices = anySelectors[idx]
+ val termIndices = termSelectors[idx]
+ val wildcardIndices = wildcardSelectors[idx]
+ if (arg is Logical<*> && !arg.isBound) {
+ // ALL values must be selected for a free logical
+ continue
+ }
+
+ val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg
+ when (argVal) {
+ is Term -> {
+ val bits = wildcardIndices.clone() as BitSet
+ for (i in termIndices.lookupValues(argVal)) {
+ bits.set(i)
+ }
+ ruleIndices.and(bits)
+ }
+ is Any -> {
+ if (value2indices.containsKey(argVal)) {
+ // ensure only rules with either matching values or wildcard arguments get selected
+ val bits = wildcardIndices.clone() as BitSet
+ bits.or(value2indices[argVal])
+ ruleIndices.and(bits)
+
+ } else {
+ ruleIndices.and(wildcardIndices)
+ }
+ }
+ else -> {
+ /* never happens */
+ throw NullPointerException()
+ }
+ }
+
+ if (ruleIndices.isEmpty) {
+ break
+ }
+ }
+
+ val result = ArrayList(ruleIndices.cardinality())
+ var next = ruleIndices.nextSetBit(0)
+ while (next >= 0) {
+ result.add(rules.get(next))
+ next = ruleIndices.nextSetBit(next+1)
+ }
+ return result
+ }
+
+}
\ No newline at end of file
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt
new file mode 100644
index 00000000..63af9e79
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt
@@ -0,0 +1,333 @@
+/*
+ * 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 com.github.andrewoma.dexx.collection.DerivedKeyHashMap
+import com.github.andrewoma.dexx.collection.KeyFunction
+import com.github.andrewoma.dexx.collection.Maps
+import com.github.andrewoma.dexx.collection.internal.base.AbstractSet
+import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
+import jetbrains.mps.logic.reactor.evaluation.EvaluationSession
+import jetbrains.mps.logic.reactor.evaluation.StoreView
+import jetbrains.mps.logic.reactor.logical.Logical
+import jetbrains.mps.logic.reactor.logical.LogicalContext
+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
+
+/**
+ * @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(): Unit
+
+}
+
+interface StoreKeeper {
+
+ fun store(): Store
+
+ fun addObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver)
+
+ fun removeObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver)
+
+}
+
+interface OccurrenceIndex {
+
+ fun forSymbol(symbol: ConstraintSymbol): Iterable
+
+ fun forLogical(logical: Logical<*>): Iterable
+
+ fun forLogicalAndConstraint(logical: Logical<*>, cst:Constraint): Iterable
+
+ fun forTerm(term: Term): Iterable
+
+ fun forTermAndConstraint(term: Term, cst: Constraint): Iterable
+
+ fun forValue(value: Any): Iterable
+
+}
+
+/**
+ * TODO: make this class persistent.
+ */
+class Store : LogicalObserver, OccurrenceIndex {
+
+ val currentFrame: () -> StoreKeeper
+
+ var symbol2occurrences: PersMap>
+
+ var logical2occurrences: PersMap>, IdHashSet>
+
+ var term2occurrences: TermTrie
+
+ var value2occurrences: PersMap>
+
+ constructor(copyFrom: Store, currentFrame: () -> StoreKeeper) {
+ this.currentFrame = currentFrame
+ this.symbol2occurrences = copyFrom.symbol2occurrences
+ this.logical2occurrences = copyFrom.logical2occurrences
+ this.term2occurrences = copyFrom.term2occurrences
+ this.value2occurrences = copyFrom.value2occurrences
+ }
+
+ constructor(copyFrom: StoreView, currentFrame: () -> StoreKeeper) {
+ 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 t2o = TermTrie()
+ var v2o = Maps.of>()
+
+ copyFrom.allOccurrences().forEach { occ ->
+ occ.arguments().forEach { arg ->
+ when (arg) {
+ is Logical<*> -> l2o = l2o.put(IdWrapper(arg.findRoot()), l2o[IdWrapper(arg.findRoot())]?.add(occ) ?: singletonSet(occ))
+ is Term -> t2o = t2o.put(arg.withConstraint(occ.constraint()), occ)
+ is Any -> v2o = v2o.put(arg, v2o[arg]?.add(occ) ?: singletonSet(occ))
+ }
+ }
+
+ }
+
+ this.logical2occurrences = l2o
+ this.term2occurrences = t2o
+ this.value2occurrences = v2o
+ }
+
+ constructor(currentFrame: () -> StoreKeeper) {
+ this.currentFrame = currentFrame
+ this.symbol2occurrences = Maps.of()
+ this.logical2occurrences = Maps.of()
+ this.term2occurrences = TermTrie()
+ this.value2occurrences = Maps.of()
+ }
+
+ override fun valueUpdated(logical: Logical<*>) {
+ logical2occurrences[IdWrapper(logical.findRoot())]?.let { toMerge ->
+ val value = logical.findRoot().value()
+ when (value) {
+ is Term -> {
+ for (occ in toMerge) {
+ this.term2occurrences = term2occurrences.put(value.withConstraint(occ.constraint()), occ)
+ }
+ }
+ is Any -> {
+ var newSet = value2occurrences[value] ?: emptySet()
+ for (occ in toMerge) {
+ newSet = newSet.add(occ)
+ }
+ this.value2occurrences = value2occurrences.put(value, newSet)
+ }
+ else -> {
+ // never happens
+ throw NullPointerException()
+ }
+ }
+ }
+ }
+
+ override fun parentUpdated(logical: Logical<*>) {
+ // TODO: should we care about the order in which occurrences are stored?
+ val logicalId = IdWrapper(logical)
+ logical2occurrences[logicalId]?.let { toMerge ->
+ val rootId = IdWrapper(logical.findRoot())
+ var newSet = logical2occurrences[rootId] ?: emptySet()
+ for (log in toMerge) {
+ newSet = newSet.add(log)
+ }
+ this.logical2occurrences = logical2occurrences.remove(logicalId).put(rootId, newSet)
+ assert(logical2occurrences.containsKey(rootId))
+ assert(!logical2occurrences.containsKey(logicalId))
+ }
+ }
+
+ fun store(occ: ConstraintOccurrence): Unit {
+ val symbol = occ.constraint().symbol()
+
+ this.symbol2occurrences = symbol2occurrences.put(symbol,
+ symbol2occurrences[symbol]?.add(occ) ?: singletonSet(occ))
+
+ for (arg in occ.arguments()) {
+ val value = if (arg is Logical<*> && arg.isBound) arg.findRoot().value() else arg
+ when (value) {
+ is Logical<*> -> {
+ // free logical
+ val argId = IdWrapper(value.findRoot())
+ this.logical2occurrences = logical2occurrences.put(argId,
+ logical2occurrences[argId]?.add(occ) ?: singletonSet(occ))
+ currentFrame().addObserver(value) { frame -> frame.store() }
+ }
+ is Term -> {
+ this.term2occurrences = term2occurrences.put(value.withConstraint(occ.constraint()), occ)
+ }
+ is Any -> {
+ this.value2occurrences = value2occurrences.put(value,
+ value2occurrences[value]?.add(occ) ?: singletonSet(occ))
+ }
+ else -> {
+ // never happens
+ throw NullPointerException()
+ }
+ }
+ }
+
+ // TODO: superfluous cast
+ (occ as StoreItem).stored = true
+ }
+
+ fun discard(occ: ConstraintOccurrence, profiler: Profiler? = null, tag: String? = null): Unit {
+ val symbol = occ.constraint().symbol()
+
+ symbol2occurrences[symbol]?.remove(occ)?.let { newList ->
+ this.symbol2occurrences = symbol2occurrences.put(symbol, newList)
+ }
+
+ for (arg in occ.arguments()) {
+ when (arg) {
+ is Logical<*> -> {
+ val argId = IdWrapper(arg.findRoot())
+ logical2occurrences[argId]?.remove(occ)?.let { newList ->
+ this.logical2occurrences = if (newList.isEmpty) {
+ logical2occurrences.remove(argId)
+
+ } else {
+ logical2occurrences.put(argId, newList)
+ }
+ }
+ }
+ is Term -> {
+ // removing occurrences from the index is *very* expensive,
+ // so let's simply use the 'alive' flag to filter out terminated occurrences
+// this.term2occurrences = term2occurrences.remove(arg, occ)
+ }
+ is Any -> {
+ value2occurrences[arg]?.remove(occ)?. let { newList ->
+ this.value2occurrences = value2occurrences.put(arg, newList)
+ }
+ }
+ }
+ }
+
+ // TODO: superfluous cast
+ (occ as StoreItem).stored = false
+ occ.terminate()
+ }
+
+ fun allOccurrences(): Sequence {
+ return symbol2occurrences.values().flatten().filter { co -> co.isStored() }.asSequence()
+ }
+
+ fun view(): StoreView = StoreViewImpl(allOccurrences())
+
+ override fun forSymbol(symbol: ConstraintSymbol): Iterable {
+ return (symbol2occurrences[symbol] ?: emptySet()).filter { co -> co.isStored() }
+ }
+
+ override fun forLogicalAndConstraint(logical: Logical<*>, cst: Constraint): Iterable {
+ return if (logical.isBound) {
+ val value = logical.findRoot().value()
+ when (value) {
+ is Term -> forTermAndConstraint(value, cst)
+ is Any -> forValue(value)
+ else -> throw NullPointerException()
+ }
+
+ } else {
+ (logical2occurrences[IdWrapper(logical.findRoot())] ?: emptySet()).filter { co -> co.isStored() }
+ }
+ }
+
+ override fun forLogical(logical: Logical<*>): Iterable {
+ return if (logical.isBound) {
+ val value = logical.findRoot().value()
+ when (value) {
+ is Term -> forTerm(value)
+ is Any -> forValue(value)
+ else -> throw NullPointerException()
+ }
+
+ } else {
+ (logical2occurrences[IdWrapper(logical.findRoot())] ?: emptySet()).filter { co -> co.isStored() }
+ }
+ }
+
+ override fun forTerm(term: Term): Iterable {
+ return term2occurrences.lookupValues(term.withAny()).filter { it.isStored() }
+ }
+
+ override fun forTermAndConstraint(term: Term, cst: Constraint): Iterable {
+ return term2occurrences.lookupValues(term.withConstraint(cst)).filter { it.isStored() }
+ }
+
+ override fun forValue(value: Any): Iterable {
+ return (value2occurrences[value] ?: emptySet()).filter { co -> co.isStored() }
+ }
+
+ private fun Term.withConstraint(cst: Constraint): Term = Function(CONSTRAINT, listOf(Function(cst.symbol(), emptyList()), this))
+
+ private fun Term.withAny(): Term = Function(CONSTRAINT, listOf(Variable(ANY), this))
+
+ private companion object {
+
+ private val CONSTRAINT = object : Any() {
+ override fun toString(): String = "CONSTRAINT"
+ }
+
+ private val ANY = object : Any() {
+ override fun toString(): String = "ANY"
+ }
+
+ }
+
+}
+
+private class StoreViewImpl(occurrences: Sequence) : StoreView {
+
+ val allOccurrences = occurrences.toSet()
+
+ val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet()
+
+ override fun constraintSymbols(): Iterable = allSymbols
+
+ override fun allOccurrences(): Iterable = allOccurrences
+
+ override fun occurrences(symbol: ConstraintSymbol): Iterable =
+ allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet()
+
+}
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermImpl.kt
new file mode 100644
index 00000000..dc7be155
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermImpl.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.logical.Logical
+import jetbrains.mps.unification.Term
+
+abstract class TermImpl(val symbol: Any) : Term {
+
+ override fun symbol() = symbol
+
+ override fun compareTo(other: Term): Int {
+ return if (this.javaClass === other.javaClass)
+ symbol.toString().compareTo(symbol.toString())
+ else
+ this.javaClass.toString().compareTo(other.javaClass.toString())
+ }
+}
+
+open class Function(symbol: Any, val arguments: List) : TermImpl(symbol) {
+
+ override fun arguments(): Collection = arguments
+
+ override fun `is`(kind: Term.Kind?): Boolean = (kind === Term.Kind.FUN)
+
+ override fun get(): Term = this
+}
+
+class Constant(symbol: Any) : Function(symbol, emptyList()) {}
+
+class WrapConstant(val orig: Term) : Function(orig, emptyList()) {}
+
+class WrapFreeLogical(val logical: Logical<*>) : Function(logical.findRoot(), emptyList()) {}
+
+class WrapGroundLogical(val logical: Logical) :
+ Function(logical.findRoot().value().symbol(),
+ logical.findRoot().value().arguments().toList()) {}
+
+class Variable(symbol: Any) : TermImpl(symbol) {
+
+ override fun arguments(): Collection = emptyList()
+
+ override fun `is`(kind: Term.Kind): Boolean = (kind === Term.Kind.VAR)
+
+ override fun get(): Term = TODO()
+}
\ No newline at end of file
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermTrie.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermTrie.kt
new file mode 100644
index 00000000..6780fdaf
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermTrie.kt
@@ -0,0 +1,269 @@
+/*
+ * 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 com.github.andrewoma.dexx.collection.ConsList
+import com.github.andrewoma.dexx.collection.Map as PersMap
+import com.github.andrewoma.dexx.collection.Maps
+import jetbrains.mps.logic.reactor.util.*
+import jetbrains.mps.unification.Term
+import java.util.*
+
+/**
+ * @author Fedor Isakov
+ */
+
+/**
+ * A persistent map-like structure to keep multiple values associated with a single term.
+ *
+ * The method `put` returns a new instance updated to contain the new association. Likewise, the method
+ * `remove` returns the new instance updated to remove the association.
+ *
+ * Term variables are supported: a variable matches any term, including other variable. All variables are treated
+ * as "wildcards", so the subsequent unification of the query term and the key does not necessarily succeeds.
+ *
+ * To access the stored values, the method `lookupValues` returns all values associated with terms.
+ * The method `allValues` returns all stored values. Neither method makes any guarantees about the cardinality or
+ * the order of the values returned.
+ *
+ * The implementation is based on the structure called "discrimination tree" as described in the chapter 26 of [1].
+ *
+ * [1] Alan Robinson and Andrei Voronkov (Eds.). 2001. Handbook of Automated Reasoning.
+ * Elsevier Sci. Pub. B. V., Amsterdam, The Netherlands, The Netherlands.
+ *
+ */
+class TermTrie() {
+
+ private companion object {
+
+ private val WILDCARD = object : Any() {
+ override fun toString() = "WILDCARD"
+ }
+
+ }
+
+ private var root: PathNode
+
+ init {
+ root = PathNode(WILDCARD, 1)
+ }
+
+ private constructor(setRoot: PathNode) : this() {
+ this.root = setRoot
+ }
+
+ fun put(term: Term, value: T): TermTrie = TermTrie(putValue(term, value))
+
+ fun remove(term: Term, value: T): TermTrie = TermTrie(removeValue(term, value))
+
+ fun lookupValues(term: Term): Iterable {
+ val result = ArrayList()
+ visitMatching(term) { value -> result.add(value) }
+ return result
+ }
+
+ fun allValues(): Iterable {
+ val result = ArrayList()
+ visitAll(root, { value -> result.add(value) })
+ return result
+ }
+
+ private fun putValue(matchTerm: Term, value: T): PathNode {
+ val seen = IdentityHashMap()
+ var nodeStack: ConsList> = cons(root)
+ val termList = arrayListOf(matchTerm)
+
+ while (!termList.isEmpty()) {
+ val node = nodeStack.first() !!
+ val term = termList.removeAt(termList.size - 1)
+
+ // dereferece the term only if it hasn't been dereferenced before
+ val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } }
+ val arguments = deref.arguments().toList()
+ for(i in 1..arguments.size) {
+ termList.add(arguments[arguments.size - i])
+ }
+
+ val nextNode = node.nextOrDefault(symbolOrWildcard(deref)) { sym -> PathNode(sym, arguments.size) }
+ nodeStack = nodeStack.prepend(nextNode)
+ }
+
+ val head = nodeStack.first() !!
+ return nodeStack.tail().fold(head.addValue(value)) { nextNode, node -> node.putNext(nextNode) }
+ }
+
+ private fun removeValue(matchTerm: Term, value: T): PathNode {
+ val seen = IdentityHashMap()
+ var nodeStack: ConsList> = cons(root)
+ val termList = arrayListOf(matchTerm)
+
+ while (!termList.isEmpty()) {
+ val node = nodeStack.first() !!
+ val term = termList.removeAt(termList.size - 1)
+
+ // dereferece the term only if it hasn't been dereferenced before
+ val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } }
+ val arguments = deref.arguments().toList()
+ for(i in 1..arguments.size) {
+ termList.add(arguments[arguments.size - i])
+ }
+
+ val nextNode = node.next(symbolOrWildcard(deref))
+ if (nextNode == null) {
+ // term not found
+ return root
+ }
+ nodeStack = nodeStack.prepend(nextNode)
+ }
+
+ val head = nodeStack.first() !!
+ val newHead = head.removeValue(value)
+ return if (head !== newHead) {
+ nodeStack.tail().fold(newHead) { nextNode, node -> node.putNext(nextNode) }
+ } else {
+ // value not found
+ root
+ }
+ }
+
+ private fun visitMatching(matchTerm: Term, visitor: (T) -> Unit) {
+ val seen = IdentityHashMap()
+ val visitList = arrayListOf(root.to(cons(matchTerm)))
+
+ while (!visitList.isEmpty()) {
+ val (node, termList) = visitList.removeAt(visitList.size - 1)
+ if (!termList.isEmpty) {
+ val term = termList.first() !!
+ val termTail = termList.tail()
+
+ // dereferece the term only if it hasn't been dereferenced before
+ val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } }
+
+ val sym = symbolOrWildcard(deref)
+ if (sym == WILDCARD) {
+ if (!termTail.isEmpty) {
+ node.skipAllNext().forEach { n -> visitList.add(n.to(termTail)) }
+
+ } else {
+ node.allNext().forEach { visitAll(it, visitor) }
+ }
+
+ } else {
+ node.next(WILDCARD)?.let { nn ->
+ nn.values().forEach(visitor)
+ if (!termTail.isEmpty) {
+ visitList.add(nn.to(termTail))
+ }
+ }
+ node.next(sym)?.let { nn ->
+ nn.values().forEach(visitor)
+
+ var newTail = termTail
+ val arguments = deref.arguments().toList()
+ for(i in 1..arguments.size) {
+ newTail = newTail.prepend(arguments[arguments.size - i])
+ }
+
+ if (!newTail.isEmpty) {
+ visitList.add(nn.to(newTail))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun visitAll(node: PathNode, visitor: (T) -> Unit) {
+ node.values().forEach(visitor)
+ node.allNext().forEach { visitAll(it, visitor) }
+ }
+
+ private fun deref(term: Term): Term {
+ var deref = term
+ while (deref.`is`(Term.Kind.REF)) {
+ deref = deref.get()
+ }
+ return deref
+ }
+
+ private fun symbolOrWildcard(term: Term): Any {
+ return if (term.`is`(Term.Kind.VAR)) {
+ WILDCARD
+
+ } else {
+ term.symbol()
+ }
+ }
+
+ private class PathNode(val symbol: Any,
+ val arity: Int,
+ val next: PersMap>,
+ val values: IdHashSet)
+ {
+
+ constructor(symbol: Any, arity: Int) :
+ this(symbol, arity, Maps.of(), emptySet())
+
+ constructor(copyFrom: PathNode, setValues: IdHashSet) :
+ this(copyFrom.symbol, copyFrom.arity, copyFrom.next, setValues)
+
+ constructor(copyFrom: PathNode, setNext: PersMap>) :
+ this(copyFrom.symbol, copyFrom.arity, setNext, copyFrom.values)
+
+ fun values(): Iterable = values
+
+ fun hasValues(): Boolean = !values.isEmpty
+
+ fun hasValue(value: T): Boolean = values.contains(value)
+
+ fun next(symbol: Any): PathNode? = next[symbol]
+
+ fun hasNext(): Boolean = !next.isEmpty
+
+ fun allNext(): Iterable> = next.values()
+
+ fun skipAllNext(): Iterable> = allNext(0)
+
+ private fun allNext(skip: Int): Iterable> = allNext().flatMap { nn ->
+ val newSkip = skip + nn.arity
+ if (newSkip == 0) {
+ listOf(nn)
+
+ } else {
+ nn.allNext(newSkip - 1)
+ }
+ }
+
+ fun putNext(node: PathNode): PathNode = PathNode(this, next.put(node.symbol, node))
+
+ fun removeNext(node: PathNode): PathNode = PathNode(this, next.remove(node.symbol))
+
+ fun addValue(value: T): PathNode = PathNode(this, values.add(value))
+
+ fun removeValue(value: T): PathNode =
+ if (values.contains(value)) PathNode(this, values.remove(value)) else this
+
+ inline fun nextOrDefault(symbol: Any,
+ default: (sym: Any) -> PathNode): PathNode
+ {
+ return next[symbol] ?: default(symbol)
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt
new file mode 100644
index 00000000..0671c89f
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.util
+
+import com.github.andrewoma.dexx.collection.ConsList
+
+/**
+ * @author Fedor Isakov
+ */
+
+
+fun emptyConsList(): ConsList = ConsList.empty()
+
+fun cons(e: E): ConsList = emptyConsList().append(e)
+
+fun consListOf(vararg args: E): ConsList {
+ val builder = ConsList.factory().newBuilder()
+ for (e in args) {
+ builder.add(e)
+ }
+ return builder.build()
+}
+
+fun ConsList.removeAt(idx: Int): ConsList {
+ if (idx < 0) throw IllegalArgumentException("index < 0")
+ val left = this.take(idx)
+ var right = this.drop(idx + 1)
+ for (e in left.reversed()) {
+ right = right.prepend(e)
+ }
+ return right
+}
+
+fun ConsList?.remove(e: E): ConsList? {
+ return this?.run {
+ val idx = indexOf(e)
+ if (idx >= 0) removeAt(idx) else this
+ }
+}
\ No newline at end of file
diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdHashSet.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdHashSet.kt
new file mode 100644
index 00000000..aa972176
--- /dev/null
+++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdHashSet.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.util
+
+import com.github.andrewoma.dexx.collection.DerivedKeyHashMap
+import com.github.andrewoma.dexx.collection.KeyFunction
+import com.github.andrewoma.dexx.collection.internal.base.AbstractSet
+
+class IdHashSet : AbstractSet {
+
+ companion object {
+
+ val EMPTY_SET = IdHashSet()
+
+ private fun idkey(value: E) = System.identityHashCode(value)
+
+ }
+
+ var storage: DerivedKeyHashMap
+
+ constructor() {
+ this.storage = emptyStorage()
+ }
+
+ constructor(storage: DerivedKeyHashMap) {
+ this.storage = storage
+ }
+
+ constructor(copyFrom: Iterable) {
+ this.storage = copyFrom.fold(emptyStorage()) { s, e -> s.put(idkey(e), e) }
+ }
+
+ override fun add(value: E): IdHashSet