diff options
6 files changed, 202 insertions, 131 deletions
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java index 110295b2..9c40e6df 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SemanticsUtils.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -8,11 +8,14 @@ package tools.refinery.language.semantics; | |||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import com.google.inject.Singleton; | 9 | import com.google.inject.Singleton; |
10 | import com.google.inject.name.Named; | 10 | import com.google.inject.name.Named; |
11 | import org.eclipse.emf.ecore.EClass; | ||
11 | import org.eclipse.emf.ecore.EObject; | 12 | import org.eclipse.emf.ecore.EObject; |
12 | import org.eclipse.emf.ecore.util.EcoreUtil; | 13 | import org.eclipse.emf.ecore.util.EcoreUtil; |
13 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | 14 | import org.eclipse.xtext.naming.IQualifiedNameConverter; |
14 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 15 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
15 | import org.eclipse.xtext.naming.QualifiedName; | 16 | import org.eclipse.xtext.naming.QualifiedName; |
17 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
18 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | ||
16 | import org.eclipse.xtext.scoping.IScope; | 19 | import org.eclipse.xtext.scoping.IScope; |
17 | import org.jetbrains.annotations.NotNull; | 20 | import org.jetbrains.annotations.NotNull; |
18 | import org.jetbrains.annotations.Nullable; | 21 | import org.jetbrains.annotations.Nullable; |
@@ -33,6 +36,9 @@ public class SemanticsUtils { | |||
33 | @Inject | 36 | @Inject |
34 | private IQualifiedNameConverter qualifiedNameConverter; | 37 | private IQualifiedNameConverter qualifiedNameConverter; |
35 | 38 | ||
39 | @Inject | ||
40 | private IResourceDescriptionsProvider resourceDescriptionsProvider; | ||
41 | |||
36 | public Optional<String> getNameWithoutRootPrefix(EObject eObject) { | 42 | public Optional<String> getNameWithoutRootPrefix(EObject eObject) { |
37 | var qualifiedName = delegateQualifiedNameProvider.getFullyQualifiedName(eObject); | 43 | var qualifiedName = delegateQualifiedNameProvider.getFullyQualifiedName(eObject); |
38 | if (qualifiedName == null) { | 44 | if (qualifiedName == null) { |
@@ -42,11 +48,31 @@ public class SemanticsUtils { | |||
42 | } | 48 | } |
43 | 49 | ||
44 | @Nullable | 50 | @Nullable |
51 | public <T> T maybeGetLocalElement(Problem problem, QualifiedName qualifiedName, Class<T> type, EClass eClass) { | ||
52 | var resource = problem.eResource(); | ||
53 | var resourceSet = resource.getResourceSet(); | ||
54 | var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resourceSet); | ||
55 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); | ||
56 | if (resourceDescription == null) { | ||
57 | return null; | ||
58 | } | ||
59 | var eObjectDescriptions = resourceDescription.getExportedObjects(eClass, qualifiedName, false); | ||
60 | return maybeGet(problem, eObjectDescriptions, qualifiedName, type); | ||
61 | } | ||
62 | |||
63 | @Nullable | ||
45 | public <T> T maybeGetElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { | 64 | public <T> T maybeGetElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { |
46 | if (qualifiedName == null) { | 65 | if (qualifiedName == null) { |
47 | throw new IllegalArgumentException("Element name must not be null"); | 66 | throw new IllegalArgumentException("Element name must not be null"); |
48 | } | 67 | } |
49 | var iterator = scope.getElements(qualifiedName).iterator(); | 68 | var eObjectDescriptions = scope.getElements(qualifiedName); |
69 | return maybeGet(problem, eObjectDescriptions, qualifiedName, type); | ||
70 | } | ||
71 | |||
72 | @Nullable | ||
73 | private <T> T maybeGet(Problem problem, Iterable<IEObjectDescription> eObjectDescriptions, | ||
74 | QualifiedName qualifiedName, Class<T> type) { | ||
75 | var iterator = eObjectDescriptions.iterator(); | ||
50 | if (!iterator.hasNext()) { | 76 | if (!iterator.hasNext()) { |
51 | return null; | 77 | return null; |
52 | } | 78 | } |
@@ -66,8 +92,19 @@ public class SemanticsUtils { | |||
66 | } | 92 | } |
67 | 93 | ||
68 | @NotNull | 94 | @NotNull |
95 | public <T> T getLocalElement(Problem problem, QualifiedName qualifiedName, Class<T> type, EClass eClass) { | ||
96 | var element = maybeGetLocalElement(problem, qualifiedName, type, eClass); | ||
97 | return getOrThrow(element, qualifiedName, type); | ||
98 | } | ||
99 | |||
100 | @NotNull | ||
69 | public <T> T getElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { | 101 | public <T> T getElement(Problem problem, IScope scope, QualifiedName qualifiedName, Class<T> type) { |
70 | var element = maybeGetElement(problem, scope, qualifiedName, type); | 102 | var element = maybeGetElement(problem, scope, qualifiedName, type); |
103 | return getOrThrow(element, qualifiedName, type); | ||
104 | } | ||
105 | |||
106 | @NotNull | ||
107 | private <T> T getOrThrow(@Nullable T element, QualifiedName qualifiedName, Class<T> type) { | ||
71 | if (element == null) { | 108 | if (element == null) { |
72 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); | 109 | var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName); |
73 | throw new IllegalArgumentException("No such %s: %s" | 110 | throw new IllegalArgumentException("No such %s: %s" |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java index 2fb0a49d..377a66f3 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java | |||
@@ -10,6 +10,8 @@ import com.google.inject.Provider; | |||
10 | import org.eclipse.collections.api.factory.primitive.IntObjectMaps; | 10 | import org.eclipse.collections.api.factory.primitive.IntObjectMaps; |
11 | import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; | 11 | import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; |
12 | import org.eclipse.emf.common.util.URI; | 12 | import org.eclipse.emf.common.util.URI; |
13 | import org.eclipse.emf.ecore.EObject; | ||
14 | import org.eclipse.emf.ecore.resource.Resource; | ||
13 | import org.eclipse.emf.ecore.util.EcoreUtil; | 15 | import org.eclipse.emf.ecore.util.EcoreUtil; |
14 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 16 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
15 | import org.eclipse.xtext.naming.QualifiedName; | 17 | import org.eclipse.xtext.naming.QualifiedName; |
@@ -18,7 +20,7 @@ import org.eclipse.xtext.resource.XtextResource; | |||
18 | import org.eclipse.xtext.resource.XtextResourceSet; | 20 | import org.eclipse.xtext.resource.XtextResourceSet; |
19 | import org.eclipse.xtext.scoping.IScopeProvider; | 21 | import org.eclipse.xtext.scoping.IScopeProvider; |
20 | import tools.refinery.language.model.problem.*; | 22 | import tools.refinery.language.model.problem.*; |
21 | import tools.refinery.language.scoping.imports.ImportAdapter; | 23 | import tools.refinery.language.naming.NamingUtil; |
22 | import tools.refinery.language.utils.ProblemDesugarer; | 24 | import tools.refinery.language.utils.ProblemDesugarer; |
23 | import tools.refinery.language.utils.ProblemUtil; | 25 | import tools.refinery.language.utils.ProblemUtil; |
24 | import tools.refinery.store.model.Model; | 26 | import tools.refinery.store.model.Model; |
@@ -34,9 +36,7 @@ import tools.refinery.store.tuple.Tuple; | |||
34 | import java.io.ByteArrayInputStream; | 36 | import java.io.ByteArrayInputStream; |
35 | import java.io.ByteArrayOutputStream; | 37 | import java.io.ByteArrayOutputStream; |
36 | import java.io.IOException; | 38 | import java.io.IOException; |
37 | import java.util.Map; | 39 | import java.util.*; |
38 | import java.util.TreeMap; | ||
39 | import java.util.TreeSet; | ||
40 | import java.util.function.Function; | 40 | import java.util.function.Function; |
41 | import java.util.stream.Collectors; | 41 | import java.util.stream.Collectors; |
42 | 42 | ||
@@ -66,13 +66,17 @@ public class SolutionSerializer { | |||
66 | private Model model; | 66 | private Model model; |
67 | private ReasoningAdapter reasoningAdapter; | 67 | private ReasoningAdapter reasoningAdapter; |
68 | private PartialInterpretation<TruthValue, Boolean> existsInterpretation; | 68 | private PartialInterpretation<TruthValue, Boolean> existsInterpretation; |
69 | private Resource originalResource; | ||
69 | private Problem originalProblem; | 70 | private Problem originalProblem; |
71 | private QualifiedName originalProblemName; | ||
70 | private Problem problem; | 72 | private Problem problem; |
73 | private QualifiedName newProblemName; | ||
71 | private NodeDeclaration nodeDeclaration; | 74 | private NodeDeclaration nodeDeclaration; |
72 | private final MutableIntObjectMap<Node> nodes = IntObjectMaps.mutable.empty(); | 75 | private final MutableIntObjectMap<Node> nodes = IntObjectMaps.mutable.empty(); |
73 | 76 | ||
74 | public Problem serializeSolution(ProblemTrace trace, Model model) { | 77 | public Problem serializeSolution(ProblemTrace trace, Model model) { |
75 | var uri = URI.createURI("__synthetic." + ProblemUtil.MODULE_EXTENSION); | 78 | var uri = URI.createURI("__solution_%s.%s".formatted(UUID.randomUUID().toString().replace('-', '_'), |
79 | ProblemUtil.MODULE_EXTENSION)); | ||
76 | return serializeSolution(trace, model, uri); | 80 | return serializeSolution(trace, model, uri); |
77 | } | 81 | } |
78 | 82 | ||
@@ -83,10 +87,14 @@ public class SolutionSerializer { | |||
83 | existsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, | 87 | existsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, |
84 | ReasoningAdapter.EXISTS_SYMBOL); | 88 | ReasoningAdapter.EXISTS_SYMBOL); |
85 | originalProblem = trace.getProblem(); | 89 | originalProblem = trace.getProblem(); |
90 | originalProblemName = qualifiedNameProvider.getFullyQualifiedName(originalProblem); | ||
86 | problem = copyProblem(originalProblem, uri); | 91 | problem = copyProblem(originalProblem, uri); |
87 | problem.setKind(ModuleKind.MODULE); | 92 | problem.setKind(ProblemUtil.getDefaultModuleKind(uri)); |
93 | problem.setExplicitKind(false); | ||
94 | problem.setName(null); | ||
95 | newProblemName = qualifiedNameProvider.getFullyQualifiedName(originalProblem); | ||
88 | problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement); | 96 | problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement); |
89 | problem.getNodes().removeIf(this::shouldRemoveNode); | 97 | removeNonExistentImplicitNodes(); |
90 | nodeDeclaration = ProblemFactory.eINSTANCE.createNodeDeclaration(); | 98 | nodeDeclaration = ProblemFactory.eINSTANCE.createNodeDeclaration(); |
91 | nodeDeclaration.setKind(NodeKind.NODE); | 99 | nodeDeclaration.setKind(NodeKind.NODE); |
92 | nodeDeclaration.getNodes().addAll(problem.getNodes()); | 100 | nodeDeclaration.getNodes().addAll(problem.getNodes()); |
@@ -105,15 +113,28 @@ public class SolutionSerializer { | |||
105 | return statement instanceof Assertion || statement instanceof ScopeDeclaration; | 113 | return statement instanceof Assertion || statement instanceof ScopeDeclaration; |
106 | } | 114 | } |
107 | 115 | ||
108 | private boolean shouldRemoveNode(Node newNode) { | 116 | private void removeNonExistentImplicitNodes() { |
109 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(newNode); | 117 | var originalImplicitNodes = originalProblem.getNodes(); |
110 | var scope = scopeProvider.getScope(originalProblem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | 118 | var newImplicitNodes = problem.getNodes(); |
111 | var originalNode = semanticsUtils.maybeGetElement(originalProblem, scope, qualifiedName, Node.class); | 119 | if (newImplicitNodes.size() != originalImplicitNodes.size()) { |
112 | if (originalNode == null) { | 120 | throw new IllegalStateException("Expected %d implicit nodes in problem, but got %d after copying" |
113 | return false; | 121 | .formatted(originalImplicitNodes.size(), newImplicitNodes.size())); |
122 | } | ||
123 | var iterator = newImplicitNodes.iterator(); | ||
124 | for (var originalNode : originalImplicitNodes) { | ||
125 | if (!iterator.hasNext()) { | ||
126 | throw new AssertionError("Unexpected end of copied implicit node list"); | ||
127 | } | ||
128 | var newNode = iterator.next(); | ||
129 | if (!Objects.equals(originalNode.getName(), newNode.getName())) { | ||
130 | throw new IllegalStateException("Expected copy of '%s' to have the same name, got '%s' instead" | ||
131 | .formatted(originalNode.getName(), newNode.getName())); | ||
132 | } | ||
133 | int nodeId = trace.getNodeId(originalNode); | ||
134 | if (!isExistingNode(nodeId)) { | ||
135 | iterator.remove(); | ||
136 | } | ||
114 | } | 137 | } |
115 | int nodeId = trace.getNodeId(originalNode); | ||
116 | return !isExistingNode(nodeId); | ||
117 | } | 138 | } |
118 | 139 | ||
119 | private boolean isExistingNode(int nodeId) { | 140 | private boolean isExistingNode(int nodeId) { |
@@ -125,14 +146,10 @@ public class SolutionSerializer { | |||
125 | } | 146 | } |
126 | 147 | ||
127 | private Problem copyProblem(Problem originalProblem, URI uri) { | 148 | private Problem copyProblem(Problem originalProblem, URI uri) { |
128 | var newResourceSet = resourceSetProvider.get(); | 149 | originalResource = originalProblem.eResource(); |
129 | ImportAdapter.copySettings(originalProblem, newResourceSet); | 150 | var resourceSet = originalResource.getResourceSet(); |
130 | if (!ProblemUtil.MODULE_EXTENSION.equals(uri.fileExtension())) { | ||
131 | uri = uri.appendFileExtension(ProblemUtil.MODULE_EXTENSION); | ||
132 | } | ||
133 | var newResource = resourceFactory.createResource(uri); | 151 | var newResource = resourceFactory.createResource(uri); |
134 | newResourceSet.getResources().add(newResource); | 152 | resourceSet.getResources().add(newResource); |
135 | var originalResource = originalProblem.eResource(); | ||
136 | if (originalResource instanceof XtextResource) { | 153 | if (originalResource instanceof XtextResource) { |
137 | byte[] bytes; | 154 | byte[] bytes; |
138 | try { | 155 | try { |
@@ -147,7 +164,7 @@ public class SolutionSerializer { | |||
147 | throw new IllegalStateException("Failed to copy problem", e); | 164 | throw new IllegalStateException("Failed to copy problem", e); |
148 | } | 165 | } |
149 | var contents = newResource.getContents(); | 166 | var contents = newResource.getContents(); |
150 | EcoreUtil.resolveAll(newResourceSet); | 167 | EcoreUtil.resolveAll(newResource); |
151 | if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) { | 168 | if (!contents.isEmpty() && contents.getFirst() instanceof Problem newProblem) { |
152 | return newProblem; | 169 | return newProblem; |
153 | } | 170 | } |
@@ -159,10 +176,24 @@ public class SolutionSerializer { | |||
159 | } | 176 | } |
160 | } | 177 | } |
161 | 178 | ||
179 | private QualifiedName getConvertedName(EObject original) { | ||
180 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(original); | ||
181 | if (originalProblemName != null && qualifiedName.startsWith(originalProblemName)) { | ||
182 | qualifiedName = qualifiedName.skipFirst(originalProblemName.getSegmentCount()); | ||
183 | } | ||
184 | if (newProblemName != null) { | ||
185 | qualifiedName = newProblemName.append(qualifiedName); | ||
186 | } | ||
187 | return NamingUtil.addRootPrefix(qualifiedName); | ||
188 | } | ||
189 | |||
162 | private Relation findRelation(Relation originalRelation) { | 190 | private Relation findRelation(Relation originalRelation) { |
163 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalRelation); | 191 | if (originalRelation.eResource() != originalResource) { |
164 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); | 192 | return originalRelation; |
165 | return semanticsUtils.getElement(problem, scope, qualifiedName, Relation.class); | 193 | } |
194 | var qualifiedName = getConvertedName(originalRelation); | ||
195 | return semanticsUtils.getLocalElement(problem, qualifiedName, Relation.class, | ||
196 | ProblemPackage.Literals.RELATION); | ||
166 | } | 197 | } |
167 | 198 | ||
168 | private Relation findPartialRelation(PartialRelation partialRelation) { | 199 | private Relation findPartialRelation(PartialRelation partialRelation) { |
@@ -170,13 +201,11 @@ public class SolutionSerializer { | |||
170 | } | 201 | } |
171 | 202 | ||
172 | private Node findNode(Node originalNode) { | 203 | private Node findNode(Node originalNode) { |
173 | var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(originalNode); | 204 | if (originalNode.eResource() != originalResource) { |
174 | return findNode(qualifiedName); | 205 | return originalNode; |
175 | } | 206 | } |
176 | 207 | var qualifiedName = getConvertedName(originalNode); | |
177 | private Node findNode(QualifiedName qualifiedName) { | 208 | return semanticsUtils.maybeGetLocalElement(problem, qualifiedName, Node.class, ProblemPackage.Literals.NODE); |
178 | var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | ||
179 | return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class); | ||
180 | } | 209 | } |
181 | 210 | ||
182 | private void addAssertion(Relation relation, LogicValue value, Node... arguments) { | 211 | private void addAssertion(Relation relation, LogicValue value, Node... arguments) { |
@@ -194,8 +223,8 @@ public class SolutionSerializer { | |||
194 | } | 223 | } |
195 | 224 | ||
196 | private void addExistsAssertions() { | 225 | private void addExistsAssertions() { |
197 | var builtinSymbols = desugarer.getBuiltinSymbols(problem) | 226 | var builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalStateException("No " + |
198 | .orElseThrow(() -> new IllegalStateException("No builtin library in copied problem")); | 227 | "builtin library in copied problem")); |
199 | // Make sure to output exists assertions in a deterministic order. | 228 | // Make sure to output exists assertions in a deterministic order. |
200 | var sortedNewNodes = new TreeMap<Integer, Node>(); | 229 | var sortedNewNodes = new TreeMap<Integer, Node>(); |
201 | for (var pair : trace.getNodeTrace().keyValuesView()) { | 230 | for (var pair : trace.getNodeTrace().keyValuesView()) { |
@@ -204,8 +233,7 @@ public class SolutionSerializer { | |||
204 | var newNode = findNode(originalNode); | 233 | var newNode = findNode(originalNode); |
205 | // Since all implicit nodes that do not exist has already been removed in serializeSolution, | 234 | // Since all implicit nodes that do not exist has already been removed in serializeSolution, |
206 | // we only need to add !exists assertions to ::new nodes and explicitly declared nodes that do not exist. | 235 | // we only need to add !exists assertions to ::new nodes and explicitly declared nodes that do not exist. |
207 | if (ProblemUtil.isMultiNode(originalNode) || | 236 | if (ProblemUtil.isMultiNode(originalNode) || (ProblemUtil.isDeclaredNode(originalNode) && !isExistingNode(nodeId))) { |
208 | (ProblemUtil.isDeclaredNode(originalNode) && !isExistingNode(nodeId))) { | ||
209 | sortedNewNodes.put(nodeId, newNode); | 237 | sortedNewNodes.put(nodeId, newNode); |
210 | } else { | 238 | } else { |
211 | nodes.put(nodeId, newNode); | 239 | nodes.put(nodeId, newNode); |
@@ -218,8 +246,8 @@ public class SolutionSerializer { | |||
218 | } | 246 | } |
219 | 247 | ||
220 | private void addClassAssertions() { | 248 | private void addClassAssertions() { |
221 | var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream() | 249 | var types = |
222 | .collect(Collectors.toMap(Function.identity(), this::findPartialRelation)); | 250 | trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream().collect(Collectors.toMap(Function.identity(), this::findPartialRelation)); |
223 | var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll(); | 251 | var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll(); |
224 | while (cursor.move()) { | 252 | while (cursor.move()) { |
225 | var key = cursor.getKey(); | 253 | var key = cursor.getKey(); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java new file mode 100644 index 00000000..f9405fc1 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.scoping; | ||
7 | |||
8 | import com.google.common.base.Predicate; | ||
9 | import com.google.common.collect.Iterables; | ||
10 | import org.eclipse.emf.ecore.EClass; | ||
11 | import org.eclipse.emf.ecore.EObject; | ||
12 | import org.eclipse.xtext.naming.QualifiedName; | ||
13 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
14 | import org.eclipse.xtext.resource.ISelectable; | ||
15 | import tools.refinery.language.naming.NamingUtil; | ||
16 | |||
17 | import java.util.List; | ||
18 | |||
19 | public class NoFullyQualifiedNamesSelectable implements ISelectable { | ||
20 | private final ISelectable delegateSelectable; | ||
21 | |||
22 | // {@link com.google.common.base.Predicate} required by Xtext API. | ||
23 | @SuppressWarnings("squid:S4738") | ||
24 | private final Predicate<IEObjectDescription> filter = | ||
25 | eObjectDescription -> !NamingUtil.isFullyQualified(eObjectDescription.getName()); | ||
26 | |||
27 | public NoFullyQualifiedNamesSelectable(ISelectable delegateSelectable) { | ||
28 | this.delegateSelectable = delegateSelectable; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public boolean isEmpty() { | ||
33 | return delegateSelectable.isEmpty(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public Iterable<IEObjectDescription> getExportedObjects() { | ||
38 | return filter(delegateSelectable.getExportedObjects()); | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) { | ||
43 | if (NamingUtil.isFullyQualified(name)) { | ||
44 | return List.of(); | ||
45 | } | ||
46 | return delegateSelectable.getExportedObjects(type, name, ignoreCase); | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) { | ||
51 | return filter(delegateSelectable.getExportedObjectsByType(type)); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) { | ||
56 | return filter(delegateSelectable.getExportedObjectsByObject(object)); | ||
57 | } | ||
58 | |||
59 | private Iterable<IEObjectDescription> filter(Iterable<IEObjectDescription> eObjectDescriptions) { | ||
60 | return Iterables.filter(eObjectDescriptions, filter); | ||
61 | } | ||
62 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java index 1c0c1d86..0067bf94 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java | |||
@@ -12,7 +12,6 @@ import org.eclipse.emf.ecore.EReference; | |||
12 | import org.eclipse.emf.ecore.resource.Resource; | 12 | import org.eclipse.emf.ecore.resource.Resource; |
13 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 13 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
14 | import org.eclipse.xtext.naming.QualifiedName; | 14 | import org.eclipse.xtext.naming.QualifiedName; |
15 | import org.eclipse.xtext.resource.IResourceDescription; | ||
16 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | 15 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; |
17 | import org.eclipse.xtext.resource.ISelectable; | 16 | import org.eclipse.xtext.resource.ISelectable; |
18 | import org.eclipse.xtext.scoping.IScope; | 17 | import org.eclipse.xtext.scoping.IScope; |
@@ -39,41 +38,31 @@ public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScop | |||
39 | if (resource == null) { | 38 | if (resource == null) { |
40 | return IScope.NULLSCOPE; | 39 | return IScope.NULLSCOPE; |
41 | } | 40 | } |
41 | var globalScope = getGlobalScope(resource, reference); | ||
42 | var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource)); | 42 | var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource)); |
43 | if (localImports.resourceDescription() == null) { | 43 | if (localImports == null) { |
44 | return IScope.NULLSCOPE; | 44 | return globalScope; |
45 | } | 45 | } |
46 | var globalScope = getGlobalScope(resource, reference); | ||
47 | var type = reference.getEReferenceType(); | 46 | var type = reference.getEReferenceType(); |
48 | boolean ignoreCase = isIgnoreCase(reference); | 47 | boolean ignoreCase = isIgnoreCase(reference); |
49 | var scope = ShadowingKeyAwareSelectableBasedScope.createScope(globalScope, localImports.resourceDescription(), | 48 | return ShadowingKeyAwareSelectableBasedScope.createScope(globalScope, localImports, type, ignoreCase); |
50 | type, ignoreCase); | ||
51 | if (localImports.normalizedSelectable() == null) { | ||
52 | return scope; | ||
53 | } | ||
54 | return ShadowingKeyAwareSelectableBasedScope.createScope(scope, localImports.normalizedSelectable(), type, | ||
55 | ignoreCase); | ||
56 | } | 49 | } |
57 | 50 | ||
58 | protected LocalImports computeLocalImports(Resource resource) { | 51 | protected ISelectable computeLocalImports(Resource resource) { |
59 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. | 52 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. |
60 | var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet()); | 53 | var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet()); |
61 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); | 54 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); |
62 | if (resourceDescription == null) { | 55 | if (resourceDescription == null) { |
63 | return new LocalImports(null, null); | 56 | return null; |
64 | } | 57 | } |
65 | var rootElement = resource.getContents().getFirst(); | 58 | var rootElement = resource.getContents().getFirst(); |
66 | if (rootElement == null) { | 59 | if (rootElement == null) { |
67 | return new LocalImports(resourceDescription, null); | 60 | return new NoFullyQualifiedNamesSelectable(resourceDescription); |
68 | } | 61 | } |
69 | var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement); | 62 | var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement); |
70 | if (rootName == null) { | 63 | if (rootName == null) { |
71 | return new LocalImports(resourceDescription, null); | 64 | return new NoFullyQualifiedNamesSelectable(resourceDescription); |
72 | } | 65 | } |
73 | var normalizedSelectable = new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY); | 66 | return new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY); |
74 | return new LocalImports(resourceDescription, normalizedSelectable); | ||
75 | } | ||
76 | |||
77 | protected record LocalImports(IResourceDescription resourceDescription, ISelectable normalizedSelectable) { | ||
78 | } | 67 | } |
79 | } | 68 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java index 5a8f7fd7..d7a5304f 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java | |||
@@ -12,7 +12,6 @@ import org.apache.log4j.Logger; | |||
12 | import org.eclipse.emf.common.notify.Notification; | 12 | import org.eclipse.emf.common.notify.Notification; |
13 | import org.eclipse.emf.common.notify.impl.AdapterImpl; | 13 | import org.eclipse.emf.common.notify.impl.AdapterImpl; |
14 | import org.eclipse.emf.common.util.URI; | 14 | import org.eclipse.emf.common.util.URI; |
15 | import org.eclipse.emf.ecore.EObject; | ||
16 | import org.eclipse.emf.ecore.resource.Resource; | 15 | import org.eclipse.emf.ecore.resource.Resource; |
17 | import org.eclipse.emf.ecore.resource.ResourceSet; | 16 | import org.eclipse.emf.ecore.resource.ResourceSet; |
18 | import org.eclipse.emf.ecore.util.EcoreUtil; | 17 | import org.eclipse.emf.ecore.util.EcoreUtil; |
@@ -203,28 +202,4 @@ public class ImportAdapter extends AdapterImpl { | |||
203 | private static ImportAdapter getAdapter(ResourceSet resourceSet) { | 202 | private static ImportAdapter getAdapter(ResourceSet resourceSet) { |
204 | return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class); | 203 | return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class); |
205 | } | 204 | } |
206 | |||
207 | public static void copySettings(EObject context, ResourceSet newResourceSet) { | ||
208 | var resource = context.eResource(); | ||
209 | if (resource == null) { | ||
210 | return; | ||
211 | } | ||
212 | var originalResourceSet = resource.getResourceSet(); | ||
213 | if (originalResourceSet == null) { | ||
214 | return; | ||
215 | } | ||
216 | copySettings(originalResourceSet, newResourceSet); | ||
217 | } | ||
218 | |||
219 | public static void copySettings(ResourceSet originalResourceSet, ResourceSet newResourceSet) { | ||
220 | var originalAdapter = getAdapter(originalResourceSet); | ||
221 | if (originalAdapter == null) { | ||
222 | return; | ||
223 | } | ||
224 | var newAdapter = getOrInstall(newResourceSet); | ||
225 | newAdapter.libraries.clear(); | ||
226 | newAdapter.libraries.addAll(originalAdapter.libraries); | ||
227 | newAdapter.libraryPaths.clear(); | ||
228 | newAdapter.libraryPaths.addAll(originalAdapter.libraryPaths); | ||
229 | } | ||
230 | } | 205 | } |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java index bc0320a6..0704e026 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -68,15 +68,14 @@ class NodeScopingTest { | |||
68 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.node("b"))); | 68 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.node("b"))); |
69 | } | 69 | } |
70 | 70 | ||
71 | @ParameterizedTest | 71 | @Test |
72 | @MethodSource("atomNodeReferenceSource") | 72 | void atomNodeInAssertionTest() { |
73 | void atomNodeInAssertionTest(String qualifiedNamePrefix, boolean namedProblem) { | ||
74 | var problem = parse(""" | 73 | var problem = parse(""" |
75 | atom a, b. | 74 | atom a, b. |
76 | pred predicate(node x, node y) <-> node(x). | 75 | pred predicate(node x, node y) <-> node(x). |
77 | predicate({PARAM}a, {PARAM}a). | 76 | predicate(a, a). |
78 | ?predicate({PARAM}a, {PARAM}b). | 77 | ?predicate(a, b). |
79 | """, qualifiedNamePrefix, namedProblem); | 78 | """); |
80 | assertThat(problem.getResourceErrors(), empty()); | 79 | assertThat(problem.getResourceErrors(), empty()); |
81 | assertThat(problem.nodeNames(), empty()); | 80 | assertThat(problem.nodeNames(), empty()); |
82 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.atomNode("a"))); | 81 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.atomNode("a"))); |
@@ -85,22 +84,17 @@ class NodeScopingTest { | |||
85 | assertThat(problem.assertion(1).arg(1).node(), equalTo(problem.atomNode("b"))); | 84 | assertThat(problem.assertion(1).arg(1).node(), equalTo(problem.atomNode("b"))); |
86 | } | 85 | } |
87 | 86 | ||
88 | @ParameterizedTest | 87 | @Test |
89 | @MethodSource("atomNodeReferenceSource") | 88 | void atomNodeInPredicateTest() { |
90 | void atomNodeInPredicateTest(String qualifiedNamePrefix, boolean namedProblem) { | ||
91 | var problem = parse(""" | 89 | var problem = parse(""" |
92 | atom b. | 90 | atom b. |
93 | pred predicate(node a) <-> node({PARAM}b). | 91 | pred predicate(node a) <-> node(b). |
94 | """, qualifiedNamePrefix, namedProblem); | 92 | """); |
95 | assertThat(problem.getResourceErrors(), empty()); | 93 | assertThat(problem.getResourceErrors(), empty()); |
96 | assertThat(problem.nodeNames(), empty()); | 94 | assertThat(problem.nodeNames(), empty()); |
97 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), equalTo(problem.atomNode("b"))); | 95 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), equalTo(problem.atomNode("b"))); |
98 | } | 96 | } |
99 | 97 | ||
100 | static Stream<Arguments> atomNodeReferenceSource() { | ||
101 | return Stream.of(Arguments.of("", false), Arguments.of("", true), Arguments.of("test::", true)); | ||
102 | } | ||
103 | |||
104 | @Disabled("No nodes are present in builtin.problem currently") | 98 | @Disabled("No nodes are present in builtin.problem currently") |
105 | @ParameterizedTest | 99 | @ParameterizedTest |
106 | @MethodSource("builtInNodeReferencesSource") | 100 | @MethodSource("builtInNodeReferencesSource") |
@@ -131,37 +125,30 @@ class NodeScopingTest { | |||
131 | return Stream.of(Arguments.of("int::new"), Arguments.of("builtin::int::new")); | 125 | return Stream.of(Arguments.of("int::new"), Arguments.of("builtin::int::new")); |
132 | } | 126 | } |
133 | 127 | ||
134 | @ParameterizedTest | 128 | @Test |
135 | @MethodSource("classNewNodeReferencesSource") | 129 | void classNewNodeTest() { |
136 | void classNewNodeTest(String qualifiedName, boolean namedProblem) { | ||
137 | var problem = parse(""" | 130 | var problem = parse(""" |
138 | class Foo. | 131 | class Foo. |
139 | pred predicate(node x) <-> node(x). | 132 | pred predicate(node x) <-> node(x). |
140 | predicate({PARAM}). | 133 | predicate(Foo::new). |
141 | """, qualifiedName, namedProblem); | 134 | """); |
142 | assertThat(problem.getResourceErrors(), empty()); | 135 | assertThat(problem.getResourceErrors(), empty()); |
143 | assertThat(problem.nodeNames(), empty()); | 136 | assertThat(problem.nodeNames(), empty()); |
144 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findClass("Foo").get().getNewNode())); | 137 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findClass("Foo").get().getNewNode())); |
145 | } | 138 | } |
146 | 139 | ||
147 | @ParameterizedTest | 140 | @Test |
148 | @MethodSource("classNewNodeReferencesSource") | 141 | void classNewNodeInPredicateTest() { |
149 | void classNewNodeInPredicateTest(String qualifiedName, boolean namedProblem) { | ||
150 | var problem = parse(""" | 142 | var problem = parse(""" |
151 | class Foo. | 143 | class Foo. |
152 | pred predicate(node x) <-> node({PARAM}). | 144 | pred predicate(node x) <-> node(Foo::new). |
153 | """, qualifiedName, namedProblem); | 145 | """); |
154 | assertThat(problem.getResourceErrors(), empty()); | 146 | assertThat(problem.getResourceErrors(), empty()); |
155 | assertThat(problem.nodeNames(), empty()); | 147 | assertThat(problem.nodeNames(), empty()); |
156 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), | 148 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), |
157 | equalTo(problem.findClass("Foo").get().getNewNode())); | 149 | equalTo(problem.findClass("Foo").get().getNewNode())); |
158 | } | 150 | } |
159 | 151 | ||
160 | static Stream<Arguments> classNewNodeReferencesSource() { | ||
161 | return Stream.of(Arguments.of("Foo::new", false), Arguments.of("Foo::new", true), | ||
162 | Arguments.of("test::Foo::new", true)); | ||
163 | } | ||
164 | |||
165 | @Test | 152 | @Test |
166 | void newNodeIsNotSpecial() { | 153 | void newNodeIsNotSpecial() { |
167 | var problem = parse(""" | 154 | var problem = parse(""" |
@@ -176,12 +163,12 @@ class NodeScopingTest { | |||
176 | 163 | ||
177 | @ParameterizedTest | 164 | @ParameterizedTest |
178 | @MethodSource("enumLiteralReferencesSource") | 165 | @MethodSource("enumLiteralReferencesSource") |
179 | void enumLiteralTest(String qualifiedName, boolean namedProblem) { | 166 | void enumLiteralTest(String qualifiedName) { |
180 | var problem = parse(""" | 167 | var problem = parse(""" |
181 | enum Foo { alpha, beta } | 168 | enum Foo { alpha, beta } |
182 | pred predicate(Foo a) <-> node(a). | 169 | pred predicate(Foo a) <-> node(a). |
183 | predicate({PARAM}). | 170 | predicate({PARAM}). |
184 | """, qualifiedName, namedProblem); | 171 | """, qualifiedName); |
185 | assertThat(problem.getResourceErrors(), empty()); | 172 | assertThat(problem.getResourceErrors(), empty()); |
186 | assertThat(problem.nodeNames(), empty()); | 173 | assertThat(problem.nodeNames(), empty()); |
187 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findEnum("Foo").literal("alpha"))); | 174 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findEnum("Foo").literal("alpha"))); |
@@ -189,11 +176,11 @@ class NodeScopingTest { | |||
189 | 176 | ||
190 | @ParameterizedTest | 177 | @ParameterizedTest |
191 | @MethodSource("enumLiteralReferencesSource") | 178 | @MethodSource("enumLiteralReferencesSource") |
192 | void enumLiteralInPredicateTest(String qualifiedName, boolean namedProblem) { | 179 | void enumLiteralInPredicateTest(String qualifiedName) { |
193 | var problem = parse(""" | 180 | var problem = parse(""" |
194 | enum Foo { alpha, beta } | 181 | enum Foo { alpha, beta } |
195 | pred predicate(Foo a) <-> node({PARAM}). | 182 | pred predicate(Foo a) <-> node({PARAM}). |
196 | """, qualifiedName, namedProblem); | 183 | """, qualifiedName); |
197 | assertThat(problem.getResourceErrors(), empty()); | 184 | assertThat(problem.getResourceErrors(), empty()); |
198 | assertThat(problem.nodeNames(), empty()); | 185 | assertThat(problem.nodeNames(), empty()); |
199 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), | 186 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), |
@@ -201,9 +188,7 @@ class NodeScopingTest { | |||
201 | } | 188 | } |
202 | 189 | ||
203 | static Stream<Arguments> enumLiteralReferencesSource() { | 190 | static Stream<Arguments> enumLiteralReferencesSource() { |
204 | return Stream.of(Arguments.of("alpha", false), Arguments.of("alpha", true), Arguments.of("Foo::alpha", false), | 191 | return Stream.of(Arguments.of("alpha"), Arguments.of("Foo::alpha")); |
205 | Arguments.of("Foo::alpha", true), Arguments.of("test::alpha", true), | ||
206 | Arguments.of("test::Foo::alpha", true)); | ||
207 | } | 192 | } |
208 | 193 | ||
209 | @Disabled("No enum literals are present in builtin.problem currently") | 194 | @Disabled("No enum literals are present in builtin.problem currently") |
@@ -222,7 +207,7 @@ class NodeScopingTest { | |||
222 | @Disabled("No enum literals are present in builtin.problem currently") | 207 | @Disabled("No enum literals are present in builtin.problem currently") |
223 | @ParameterizedTest | 208 | @ParameterizedTest |
224 | @MethodSource("builtInEnumLiteralReferencesSource") | 209 | @MethodSource("builtInEnumLiteralReferencesSource") |
225 | void bultInEnumLiteralInPredicateTest(String qualifiedName) { | 210 | void builtInEnumLiteralInPredicateTest(String qualifiedName) { |
226 | var problem = parse(""" | 211 | var problem = parse(""" |
227 | pred predicate() <-> node({PARAM}). | 212 | pred predicate() <-> node({PARAM}). |
228 | """, qualifiedName); | 213 | """, qualifiedName); |
@@ -237,13 +222,8 @@ class NodeScopingTest { | |||
237 | Arguments.of("builtin::bool::true")); | 222 | Arguments.of("builtin::bool::true")); |
238 | } | 223 | } |
239 | 224 | ||
240 | private WrappedProblem parse(String text, String parameter, boolean namedProblem) { | ||
241 | var problemName = namedProblem ? "problem test.\n" : ""; | ||
242 | return parseHelper.parse(problemName + text.replace("{PARAM}", parameter)); | ||
243 | } | ||
244 | |||
245 | private WrappedProblem parse(String text, String parameter) { | 225 | private WrappedProblem parse(String text, String parameter) { |
246 | return parse(text, parameter, false); | 226 | return parseHelper.parse(text.replace("{PARAM}", parameter)); |
247 | } | 227 | } |
248 | 228 | ||
249 | private WrappedProblem parse(String text) { | 229 | private WrappedProblem parse(String text) { |