diff options
author | Kristóf Marussy <kristof@marussy.com> | 2024-02-03 01:39:11 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2024-02-03 03:38:59 +0100 |
commit | d5654f1ae03bec95c08e69a19a116c9825a27098 (patch) | |
tree | a12b59bbf54352cc51d1ae9bafef0eb10b8d28b4 /subprojects/language | |
parent | refactor(language): name disambiguation (diff) | |
download | refinery-d5654f1ae03bec95c08e69a19a116c9825a27098.tar.gz refinery-d5654f1ae03bec95c08e69a19a116c9825a27098.tar.zst refinery-d5654f1ae03bec95c08e69a19a116c9825a27098.zip |
feat(language): import resolution
Diffstat (limited to 'subprojects/language')
23 files changed, 641 insertions, 78 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java index 19816da4..a846e265 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java | |||
@@ -32,6 +32,7 @@ import tools.refinery.language.naming.ProblemQualifiedNameConverter; | |||
32 | import tools.refinery.language.naming.ProblemQualifiedNameProvider; | 32 | import tools.refinery.language.naming.ProblemQualifiedNameProvider; |
33 | import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser; | 33 | import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser; |
34 | import tools.refinery.language.resource.*; | 34 | import tools.refinery.language.resource.*; |
35 | import tools.refinery.language.resource.state.ProblemDerivedStateComputer; | ||
35 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; | 36 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; |
36 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; | 37 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; |
37 | import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; | 38 | import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java b/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java new file mode 100644 index 00000000..0fb4cd2c --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/library/BuiltinLibrary.java | |||
@@ -0,0 +1,41 @@ | |||
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.library; | ||
7 | |||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | import org.eclipse.xtext.naming.QualifiedName; | ||
10 | |||
11 | import java.util.List; | ||
12 | import java.util.Optional; | ||
13 | |||
14 | public class BuiltinLibrary implements RefineryLibrary { | ||
15 | public static final QualifiedName BUILTIN_LIBRARY_NAME = QualifiedName.create("builtin"); | ||
16 | public static final URI BUILTIN_LIBRARY_URI = getLibraryUri(BUILTIN_LIBRARY_NAME).orElseThrow( | ||
17 | () -> new IllegalStateException("Builtin library was not found")); | ||
18 | |||
19 | @Override | ||
20 | public List<QualifiedName> getAutomaticImports() { | ||
21 | return List.of(BUILTIN_LIBRARY_NAME); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public Optional<URI> resolveQualifiedName(QualifiedName qualifiedName) { | ||
26 | if (qualifiedName.startsWith(BUILTIN_LIBRARY_NAME)) { | ||
27 | return getLibraryUri(qualifiedName); | ||
28 | } | ||
29 | return Optional.empty(); | ||
30 | } | ||
31 | |||
32 | private static Optional<URI> getLibraryUri(QualifiedName qualifiedName) { | ||
33 | var libraryPath = String.join("/", qualifiedName.getSegments()); | ||
34 | var libraryResource = BuiltinLibrary.class.getClassLoader() | ||
35 | .getResource("tools/refinery/language/library/%s.refinery".formatted(libraryPath)); | ||
36 | if (libraryResource == null) { | ||
37 | return Optional.empty(); | ||
38 | } | ||
39 | return Optional.of(URI.createURI(libraryResource.toString())); | ||
40 | } | ||
41 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java new file mode 100644 index 00000000..0efca199 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibraries.java | |||
@@ -0,0 +1,54 @@ | |||
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.library; | ||
7 | |||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | import org.eclipse.xtext.naming.QualifiedName; | ||
10 | import tools.refinery.language.scoping.imports.NamedImport; | ||
11 | |||
12 | import java.util.LinkedHashMap; | ||
13 | import java.util.List; | ||
14 | import java.util.Optional; | ||
15 | import java.util.ServiceLoader; | ||
16 | |||
17 | public final class RefineryLibraries { | ||
18 | private static final ServiceLoader<RefineryLibrary> SERVICE_LOADER = ServiceLoader.load(RefineryLibrary.class); | ||
19 | private static final List<NamedImport> AUTOMATIC_IMPORTS; | ||
20 | |||
21 | static { | ||
22 | var imports = new LinkedHashMap<QualifiedName, URI>(); | ||
23 | for (var service : SERVICE_LOADER) { | ||
24 | for (var qualifiedName : service.getAutomaticImports()) { | ||
25 | var uri = service.resolveQualifiedName(qualifiedName).orElseThrow( | ||
26 | () -> new IllegalStateException("Automatic import %s was not found".formatted(qualifiedName))); | ||
27 | if (imports.put(qualifiedName, uri) != null) { | ||
28 | throw new IllegalStateException("Duplicate automatic import " + qualifiedName); | ||
29 | } | ||
30 | } | ||
31 | } | ||
32 | AUTOMATIC_IMPORTS = imports.entrySet().stream() | ||
33 | .map(entry -> NamedImport.implicit(entry.getValue(), entry.getKey())) | ||
34 | .toList(); | ||
35 | } | ||
36 | |||
37 | private RefineryLibraries() { | ||
38 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
39 | } | ||
40 | |||
41 | public static List<NamedImport> getAutomaticImports() { | ||
42 | return AUTOMATIC_IMPORTS; | ||
43 | } | ||
44 | |||
45 | public static Optional<URI> resolveQualifiedName(QualifiedName qualifiedName) { | ||
46 | for (var service : SERVICE_LOADER) { | ||
47 | var result = service.resolveQualifiedName(qualifiedName); | ||
48 | if (result.isPresent()) { | ||
49 | return result; | ||
50 | } | ||
51 | } | ||
52 | return Optional.empty(); | ||
53 | } | ||
54 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java new file mode 100644 index 00000000..9db2900e --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/library/RefineryLibrary.java | |||
@@ -0,0 +1,20 @@ | |||
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.library; | ||
7 | |||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | import org.eclipse.xtext.naming.QualifiedName; | ||
10 | |||
11 | import java.util.List; | ||
12 | import java.util.Optional; | ||
13 | |||
14 | public interface RefineryLibrary { | ||
15 | default List<QualifiedName> getAutomaticImports() { | ||
16 | return List.of(); | ||
17 | } | ||
18 | |||
19 | Optional<URI> resolveQualifiedName(QualifiedName qualifiedName); | ||
20 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/LoadOnDemandResourceDescriptionProvider.java b/subprojects/language/src/main/java/tools/refinery/language/resource/LoadOnDemandResourceDescriptionProvider.java new file mode 100644 index 00000000..373a32f2 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/LoadOnDemandResourceDescriptionProvider.java | |||
@@ -0,0 +1,49 @@ | |||
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.resource; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import org.eclipse.emf.common.util.URI; | ||
10 | import org.eclipse.emf.ecore.resource.Resource; | ||
11 | import org.eclipse.xtext.EcoreUtil2; | ||
12 | import org.eclipse.xtext.resource.IResourceDescription; | ||
13 | import org.eclipse.xtext.resource.IResourceDescriptions; | ||
14 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | ||
15 | import org.eclipse.xtext.scoping.impl.GlobalResourceDescriptionProvider; | ||
16 | |||
17 | public class LoadOnDemandResourceDescriptionProvider { | ||
18 | @Inject | ||
19 | private IResourceDescriptionsProvider resourceDescriptionsProvider; | ||
20 | |||
21 | @Inject | ||
22 | private GlobalResourceDescriptionProvider globalResourceDescriptionProvider; | ||
23 | |||
24 | private Resource context; | ||
25 | private IResourceDescriptions resourceDescriptions; | ||
26 | |||
27 | public void setContext(Resource context) { | ||
28 | if (this.context != null) { | ||
29 | throw new IllegalStateException("Context was already set"); | ||
30 | } | ||
31 | this.context = context; | ||
32 | resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(context.getResourceSet()); | ||
33 | } | ||
34 | |||
35 | public IResourceDescription getResourceDescription(URI uri) { | ||
36 | if (this.context == null) { | ||
37 | throw new IllegalStateException("Context was not set"); | ||
38 | } | ||
39 | var resourceDescription = resourceDescriptions.getResourceDescription(uri); | ||
40 | if (resourceDescription != null) { | ||
41 | return resourceDescription; | ||
42 | } | ||
43 | var importedResource = EcoreUtil2.getResource(context, uri.toString()); | ||
44 | if (importedResource == null) { | ||
45 | return null; | ||
46 | } | ||
47 | return globalResourceDescriptionProvider.getResourceDescription(importedResource); | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java index 76fd5852..a2ec7ed0 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java | |||
@@ -16,11 +16,13 @@ import org.eclipse.xtext.resource.EObjectDescription; | |||
16 | import org.eclipse.xtext.resource.IEObjectDescription; | 16 | import org.eclipse.xtext.resource.IEObjectDescription; |
17 | import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; | 17 | import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; |
18 | import org.eclipse.xtext.util.IAcceptor; | 18 | import org.eclipse.xtext.util.IAcceptor; |
19 | import tools.refinery.language.scoping.imports.ImportCollector; | ||
19 | import tools.refinery.language.model.problem.*; | 20 | import tools.refinery.language.model.problem.*; |
20 | import tools.refinery.language.naming.NamingUtil; | 21 | import tools.refinery.language.naming.NamingUtil; |
21 | import tools.refinery.language.utils.ProblemUtil; | 22 | import tools.refinery.language.utils.ProblemUtil; |
22 | 23 | ||
23 | import java.util.Map; | 24 | import java.util.Map; |
25 | import java.util.stream.Collectors; | ||
24 | 26 | ||
25 | @Singleton | 27 | @Singleton |
26 | public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { | 28 | public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { |
@@ -35,12 +37,17 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
35 | public static final String SHADOWING_KEY_RELATION = "relation"; | 37 | public static final String SHADOWING_KEY_RELATION = "relation"; |
36 | public static final String PREFERRED_NAME = DATA_PREFIX + "PREFERRED_NAME"; | 38 | public static final String PREFERRED_NAME = DATA_PREFIX + "PREFERRED_NAME"; |
37 | public static final String PREFERRED_NAME_TRUE = "true"; | 39 | public static final String PREFERRED_NAME_TRUE = "true"; |
40 | public static final String IMPORTS = DATA_PREFIX + "IMPORTS"; | ||
41 | public static final String IMPORTS_SEPARATOR = "|"; | ||
38 | public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; | 42 | public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; |
39 | public static final String COLOR_RELATION_TRUE = "true"; | 43 | public static final String COLOR_RELATION_TRUE = "true"; |
40 | 44 | ||
41 | @Inject | 45 | @Inject |
42 | private IQualifiedNameConverter qualifiedNameConverter; | 46 | private IQualifiedNameConverter qualifiedNameConverter; |
43 | 47 | ||
48 | @Inject | ||
49 | private ImportCollector importCollector; | ||
50 | |||
44 | @Override | 51 | @Override |
45 | public boolean createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> acceptor) { | 52 | public boolean createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> acceptor) { |
46 | if (!shouldExport(eObject)) { | 53 | if (!shouldExport(eObject)) { |
@@ -115,6 +122,11 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
115 | var builder = ImmutableMap.<String, String>builder(); | 122 | var builder = ImmutableMap.<String, String>builder(); |
116 | if (eObject instanceof Problem) { | 123 | if (eObject instanceof Problem) { |
117 | builder.put(SHADOWING_KEY, SHADOWING_KEY_PROBLEM); | 124 | builder.put(SHADOWING_KEY, SHADOWING_KEY_PROBLEM); |
125 | var explicitImports = importCollector.getDirectImports(eObject.eResource()); | ||
126 | var importsString = explicitImports.toList().stream() | ||
127 | .map(importEntry -> importEntry.uri().toString()) | ||
128 | .collect(Collectors.joining(IMPORTS_SEPARATOR)); | ||
129 | builder.put(IMPORTS, importsString); | ||
118 | } else if (eObject instanceof Node) { | 130 | } else if (eObject instanceof Node) { |
119 | builder.put(SHADOWING_KEY, SHADOWING_KEY_NODE); | 131 | builder.put(SHADOWING_KEY, SHADOWING_KEY_NODE); |
120 | } else if (eObject instanceof Relation relation) { | 132 | } else if (eObject instanceof Relation relation) { |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/DerivedVariableComputer.java index 07c5da41..f0baf35f 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/DerivedVariableComputer.java | |||
@@ -3,7 +3,7 @@ | |||
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource.state; |
7 | 7 | ||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import com.google.inject.Singleton; | 9 | import com.google.inject.Singleton; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ImplicitVariableScope.java index e97c8287..e25887ad 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ImplicitVariableScope.java | |||
@@ -3,7 +3,7 @@ | |||
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource.state; |
7 | 7 | ||
8 | import org.eclipse.emf.ecore.EObject; | 8 | import org.eclipse.emf.ecore.EObject; |
9 | import org.eclipse.xtext.linking.impl.LinkingHelper; | 9 | import org.eclipse.xtext.linking.impl.LinkingHelper; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/NodeNameCollector.java index e5deca4d..de4a607c 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/NodeNameCollector.java | |||
@@ -3,7 +3,7 @@ | |||
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource.state; |
7 | 7 | ||
8 | import com.google.common.collect.ImmutableSet; | 8 | import com.google.common.collect.ImmutableSet; |
9 | import com.google.inject.Inject; | 9 | import com.google.inject.Inject; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java index 31eb55a6..d905aa9a 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/state/ProblemDerivedStateComputer.java | |||
@@ -3,7 +3,7 @@ | |||
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.language.resource; | 6 | package tools.refinery.language.resource.state; |
7 | 7 | ||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import com.google.inject.Provider; | 9 | import com.google.inject.Provider; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/CompositeSelectable.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/CompositeSelectable.java new file mode 100644 index 00000000..0fddcaf9 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/CompositeSelectable.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.collect.Iterables; | ||
9 | import org.eclipse.emf.ecore.EClass; | ||
10 | import org.eclipse.emf.ecore.EObject; | ||
11 | import org.eclipse.xtext.naming.QualifiedName; | ||
12 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
13 | import org.eclipse.xtext.resource.ISelectable; | ||
14 | |||
15 | import java.util.Collection; | ||
16 | import java.util.List; | ||
17 | |||
18 | class CompositeSelectable implements ISelectable { | ||
19 | private static final CompositeSelectable EMPTY = new CompositeSelectable(List.of()); | ||
20 | |||
21 | private final List<? extends ISelectable> children; | ||
22 | |||
23 | private CompositeSelectable(List<? extends ISelectable> children) { | ||
24 | |||
25 | this.children = children; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public boolean isEmpty() { | ||
30 | return children.isEmpty(); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public Iterable<IEObjectDescription> getExportedObjects() { | ||
35 | return Iterables.concat(Iterables.transform(children, ISelectable::getExportedObjects)); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) { | ||
40 | return Iterables.concat(Iterables.transform(children, child -> child.getExportedObjects(type, name, | ||
41 | ignoreCase))); | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) { | ||
46 | return Iterables.concat(Iterables.transform(children, child -> child.getExportedObjectsByType(type))); | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) { | ||
51 | return Iterables.concat(Iterables.transform(children, child -> child.getExportedObjectsByObject(object))); | ||
52 | } | ||
53 | |||
54 | public static ISelectable of(Collection<? extends ISelectable> children) { | ||
55 | var filteredChildren = children.stream().filter(selectable -> !selectable.isEmpty()).toList(); | ||
56 | return switch (filteredChildren.size()) { | ||
57 | case 0 -> EMPTY; | ||
58 | case 1 -> filteredChildren.getFirst(); | ||
59 | default -> new CompositeSelectable(filteredChildren); | ||
60 | }; | ||
61 | } | ||
62 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java index 0c7828d8..09fe4716 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java | |||
@@ -12,18 +12,21 @@ import org.eclipse.xtext.naming.QualifiedName; | |||
12 | import org.eclipse.xtext.resource.IEObjectDescription; | 12 | import org.eclipse.xtext.resource.IEObjectDescription; |
13 | import org.eclipse.xtext.resource.ISelectable; | 13 | import org.eclipse.xtext.resource.ISelectable; |
14 | import org.eclipse.xtext.resource.impl.AliasedEObjectDescription; | 14 | import org.eclipse.xtext.resource.impl.AliasedEObjectDescription; |
15 | import org.jetbrains.annotations.NotNull; | ||
16 | 15 | ||
17 | import java.util.Iterator; | 16 | import java.util.Iterator; |
17 | import java.util.List; | ||
18 | import java.util.NoSuchElementException; | 18 | import java.util.NoSuchElementException; |
19 | 19 | ||
20 | public class NormalizedSelectable implements ISelectable { | 20 | class NormalizedSelectable implements ISelectable { |
21 | private final ISelectable delegateSelectable; | 21 | private final ISelectable delegateSelectable; |
22 | private final QualifiedName originalPrefix; | 22 | private final QualifiedName originalPrefix; |
23 | private final QualifiedName normalizedPrefix; | 23 | private final QualifiedName normalizedPrefix; |
24 | 24 | ||
25 | private NormalizedSelectable(ISelectable delegateSelectable, QualifiedName originalPrefix, | 25 | public NormalizedSelectable(ISelectable delegateSelectable, QualifiedName originalPrefix, |
26 | QualifiedName normalizedPrefix) { | 26 | QualifiedName normalizedPrefix) { |
27 | if (originalPrefix.equals(QualifiedName.EMPTY)) { | ||
28 | throw new IllegalArgumentException("Cannot normalize empty qualified name prefix"); | ||
29 | } | ||
27 | this.delegateSelectable = delegateSelectable; | 30 | this.delegateSelectable = delegateSelectable; |
28 | this.originalPrefix = originalPrefix; | 31 | this.originalPrefix = originalPrefix; |
29 | this.normalizedPrefix = normalizedPrefix; | 32 | this.normalizedPrefix = normalizedPrefix; |
@@ -37,41 +40,36 @@ public class NormalizedSelectable implements ISelectable { | |||
37 | @Override | 40 | @Override |
38 | public Iterable<IEObjectDescription> getExportedObjects() { | 41 | public Iterable<IEObjectDescription> getExportedObjects() { |
39 | var delegateIterable = delegateSelectable.getExportedObjects(); | 42 | var delegateIterable = delegateSelectable.getExportedObjects(); |
40 | var aliasedIterable = getAliasedElements(delegateIterable); | 43 | return getAliasedElements(delegateIterable); |
41 | return Iterables.concat(delegateIterable, aliasedIterable); | ||
42 | } | 44 | } |
43 | 45 | ||
44 | @Override | 46 | @Override |
45 | public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) { | 47 | public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) { |
46 | var delegateIterable = delegateSelectable.getExportedObjects(type, name, ignoreCase); | ||
47 | boolean startsWith = ignoreCase ? name.startsWithIgnoreCase(normalizedPrefix) : | 48 | boolean startsWith = ignoreCase ? name.startsWithIgnoreCase(normalizedPrefix) : |
48 | name.startsWith(normalizedPrefix); | 49 | name.startsWith(normalizedPrefix); |
49 | if (startsWith && name.getSegmentCount() > normalizedPrefix.getSegmentCount()) { | 50 | if (startsWith && name.getSegmentCount() > normalizedPrefix.getSegmentCount()) { |
50 | var originalName = originalPrefix.append(name.skipFirst(normalizedPrefix.getSegmentCount())); | 51 | var originalName = originalPrefix.append(name.skipFirst(normalizedPrefix.getSegmentCount())); |
51 | var originalIterable = Iterables.transform( | 52 | return Iterables.transform( |
52 | delegateSelectable.getExportedObjects(type, originalName, ignoreCase), | 53 | delegateSelectable.getExportedObjects(type, originalName, ignoreCase), |
53 | description -> { | 54 | description -> { |
54 | var normalizedName = normalizedPrefix.append( | 55 | var normalizedName = normalizedPrefix.append( |
55 | description.getName().skipFirst(originalPrefix.getSegmentCount())); | 56 | description.getName().skipFirst(originalPrefix.getSegmentCount())); |
56 | return new AliasedEObjectDescription(normalizedName, description); | 57 | return new AliasedEObjectDescription(normalizedName, description); |
57 | }); | 58 | }); |
58 | return Iterables.concat(originalIterable, delegateIterable); | ||
59 | } | 59 | } |
60 | return delegateIterable; | 60 | return List.of(); |
61 | } | 61 | } |
62 | 62 | ||
63 | @Override | 63 | @Override |
64 | public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) { | 64 | public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) { |
65 | var delegateIterable = delegateSelectable.getExportedObjectsByType(type); | 65 | var delegateIterable = delegateSelectable.getExportedObjectsByType(type); |
66 | var aliasedIterable = getAliasedElements(delegateIterable); | 66 | return getAliasedElements(delegateIterable); |
67 | return Iterables.concat(delegateIterable, aliasedIterable); | ||
68 | } | 67 | } |
69 | 68 | ||
70 | @Override | 69 | @Override |
71 | public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) { | 70 | public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) { |
72 | var delegateIterable = delegateSelectable.getExportedObjectsByObject(object); | 71 | var delegateIterable = delegateSelectable.getExportedObjectsByObject(object); |
73 | var aliasedIterable = getAliasedElements(delegateIterable); | 72 | return getAliasedElements(delegateIterable); |
74 | return Iterables.concat(delegateIterable, aliasedIterable); | ||
75 | } | 73 | } |
76 | 74 | ||
77 | private Iterable<IEObjectDescription> getAliasedElements(Iterable<IEObjectDescription> delegateIterable) { | 75 | private Iterable<IEObjectDescription> getAliasedElements(Iterable<IEObjectDescription> delegateIterable) { |
@@ -108,15 +106,4 @@ public class NormalizedSelectable implements ISelectable { | |||
108 | } | 106 | } |
109 | }; | 107 | }; |
110 | } | 108 | } |
111 | |||
112 | public static ISelectable of(@NotNull ISelectable delegateSelectable, @NotNull QualifiedName originalPrefix, | ||
113 | @NotNull QualifiedName normalizedPrefix) { | ||
114 | if (originalPrefix.equals(normalizedPrefix)) { | ||
115 | return delegateSelectable; | ||
116 | } | ||
117 | if (originalPrefix.equals(QualifiedName.EMPTY)) { | ||
118 | throw new IllegalArgumentException("Cannot normalize empty qualified name prefix"); | ||
119 | } | ||
120 | return new NormalizedSelectable(delegateSelectable, originalPrefix, normalizedPrefix); | ||
121 | } | ||
122 | } | 109 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java index 37a67c0c..dad4e5d0 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java | |||
@@ -6,35 +6,85 @@ | |||
6 | package tools.refinery.language.scoping; | 6 | package tools.refinery.language.scoping; |
7 | 7 | ||
8 | import com.google.common.base.Predicate; | 8 | import com.google.common.base.Predicate; |
9 | import org.eclipse.emf.common.util.URI; | 9 | import com.google.inject.Inject; |
10 | import com.google.inject.Provider; | ||
10 | import org.eclipse.emf.ecore.EClass; | 11 | import org.eclipse.emf.ecore.EClass; |
11 | import org.eclipse.emf.ecore.resource.Resource; | 12 | import org.eclipse.emf.ecore.resource.Resource; |
12 | import org.eclipse.xtext.naming.QualifiedName; | 13 | import org.eclipse.xtext.naming.QualifiedName; |
13 | import org.eclipse.xtext.resource.IEObjectDescription; | 14 | import org.eclipse.xtext.resource.IEObjectDescription; |
14 | import org.eclipse.xtext.resource.IResourceDescriptions; | ||
15 | import org.eclipse.xtext.resource.ISelectable; | 15 | import org.eclipse.xtext.resource.ISelectable; |
16 | import org.eclipse.xtext.scoping.IScope; | 16 | import org.eclipse.xtext.scoping.IScope; |
17 | import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; | 17 | import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeProvider; |
18 | import org.eclipse.xtext.scoping.impl.SelectableBasedScope; | 18 | import org.eclipse.xtext.scoping.impl.SelectableBasedScope; |
19 | import tools.refinery.language.utils.ProblemUtil; | 19 | import org.eclipse.xtext.util.IResourceScopeCache; |
20 | import tools.refinery.language.scoping.imports.ImportCollector; | ||
21 | import tools.refinery.language.resource.LoadOnDemandResourceDescriptionProvider; | ||
22 | import tools.refinery.language.scoping.imports.NamedImport; | ||
20 | 23 | ||
21 | import java.util.LinkedHashSet; | 24 | import java.util.ArrayList; |
25 | import java.util.Collection; | ||
26 | import java.util.List; | ||
22 | 27 | ||
23 | public class ProblemGlobalScopeProvider extends ImportUriGlobalScopeProvider { | 28 | public class ProblemGlobalScopeProvider extends AbstractGlobalScopeProvider { |
29 | private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemGlobalScopeProvider.CACHE_KEY"; | ||
30 | |||
31 | @Inject | ||
32 | private ImportCollector importCollector; | ||
33 | |||
34 | @Inject | ||
35 | private Provider<LoadOnDemandResourceDescriptionProvider> loadOnDemandProvider; | ||
36 | |||
37 | @Inject | ||
38 | private IResourceScopeCache cache; | ||
39 | |||
40 | // {@link com.google.common.base.Predicate} required by Xtext API. | ||
41 | @SuppressWarnings("squid:S4738") | ||
24 | @Override | 42 | @Override |
25 | protected LinkedHashSet<URI> getImportedUris(Resource resource) { | 43 | protected IScope getScope(Resource resource, boolean ignoreCase, EClass type, |
26 | LinkedHashSet<URI> importedUris = new LinkedHashSet<>(); | 44 | Predicate<IEObjectDescription> filter) { |
27 | importedUris.add(ProblemUtil.BUILTIN_LIBRARY_URI); | 45 | var loadedImports = cache.get(CACHE_KEY, resource, () -> computeLoadedImports(resource)); |
28 | return importedUris; | 46 | var qualifiedScope = createScope(IScope.NULLSCOPE, loadedImports.qualifiedImports(), type, filter, ignoreCase); |
47 | var implicitScope = createScope(qualifiedScope, loadedImports.implicitImports(), type, filter, ignoreCase); | ||
48 | return createScope(implicitScope, loadedImports.explicitImports(), type, filter, ignoreCase); | ||
29 | } | 49 | } |
30 | 50 | ||
31 | @Override | 51 | protected LoadedImports computeLoadedImports(Resource resource) { |
32 | protected IScope createLazyResourceScope(IScope parent, URI uri, IResourceDescriptions descriptions, EClass type, | 52 | var imports = importCollector.getAllImports(resource); |
33 | Predicate<IEObjectDescription> filter, boolean ignoreCase) { | 53 | var loadOnDemand = loadOnDemandProvider.get(); |
34 | ISelectable description = descriptions.getResourceDescription(uri); | 54 | loadOnDemand.setContext(resource); |
35 | if (description != null && ProblemUtil.BUILTIN_LIBRARY_URI.equals(uri)) { | 55 | var qualifiedImports = new ArrayList<ISelectable>(); |
36 | description = NormalizedSelectable.of(description, QualifiedName.create("builtin"), QualifiedName.EMPTY); | 56 | var implicitImports = new ArrayList<ISelectable>(); |
57 | var explicitImports = new ArrayList<ISelectable>(); | ||
58 | for (var importEntry : imports.toList()) { | ||
59 | var uri = importEntry.uri(); | ||
60 | var resourceDescription = loadOnDemand.getResourceDescription(uri); | ||
61 | if (resourceDescription == null) { | ||
62 | continue; | ||
63 | } | ||
64 | qualifiedImports.add(resourceDescription); | ||
65 | if (importEntry instanceof NamedImport namedImport) { | ||
66 | var qualifiedName = namedImport.qualifiedName(); | ||
67 | if (namedImport.alsoImplicit()) { | ||
68 | implicitImports.add(new NormalizedSelectable(resourceDescription, qualifiedName, | ||
69 | QualifiedName.EMPTY)); | ||
70 | } | ||
71 | for (var alias : namedImport.aliases()) { | ||
72 | explicitImports.add(new NormalizedSelectable(resourceDescription, qualifiedName, alias)); | ||
73 | } | ||
74 | } | ||
37 | } | 75 | } |
38 | return SelectableBasedScope.createScope(parent, description, filter, type, ignoreCase); | 76 | return new LoadedImports(qualifiedImports, implicitImports, explicitImports); |
77 | } | ||
78 | |||
79 | // {@link com.google.common.base.Predicate} required by Xtext API. | ||
80 | @SuppressWarnings("squid:S4738") | ||
81 | protected IScope createScope(IScope parent, Collection<? extends ISelectable> children, EClass type, | ||
82 | Predicate<IEObjectDescription> filter, boolean ignoreCase) { | ||
83 | var selectable = CompositeSelectable.of(children); | ||
84 | return SelectableBasedScope.createScope(parent, selectable, filter, type, ignoreCase); | ||
85 | } | ||
86 | |||
87 | protected record LoadedImports(List<ISelectable> qualifiedImports, List<ISelectable> implicitImports, | ||
88 | List<ISelectable> explicitImports) { | ||
39 | } | 89 | } |
40 | } | 90 | } |
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 9be32636..3e00b87e 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 | |||
@@ -6,37 +6,72 @@ | |||
6 | package tools.refinery.language.scoping; | 6 | package tools.refinery.language.scoping; |
7 | 7 | ||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import org.eclipse.emf.ecore.EObject; | ||
10 | import org.eclipse.emf.ecore.EReference; | ||
9 | import org.eclipse.emf.ecore.resource.Resource; | 11 | import org.eclipse.emf.ecore.resource.Resource; |
10 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 12 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
11 | import org.eclipse.xtext.naming.QualifiedName; | 13 | import org.eclipse.xtext.naming.QualifiedName; |
14 | import org.eclipse.xtext.resource.IResourceDescription; | ||
12 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | 15 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; |
13 | import org.eclipse.xtext.resource.ISelectable; | 16 | import org.eclipse.xtext.resource.ISelectable; |
14 | import org.eclipse.xtext.scoping.impl.SimpleLocalScopeProvider; | 17 | import org.eclipse.xtext.scoping.IScope; |
18 | import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeDelegatingScopeProvider; | ||
19 | import org.eclipse.xtext.scoping.impl.SelectableBasedScope; | ||
20 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
15 | import tools.refinery.language.naming.NamingUtil; | 21 | import tools.refinery.language.naming.NamingUtil; |
16 | 22 | ||
17 | public class ProblemLocalScopeProvider extends SimpleLocalScopeProvider { | 23 | public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScopeProvider { |
24 | private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemLocalScopeProvider.CACHE_KEY"; | ||
25 | |||
18 | @Inject | 26 | @Inject |
19 | private IQualifiedNameProvider qualifiedNameProvider; | 27 | private IQualifiedNameProvider qualifiedNameProvider; |
20 | 28 | ||
21 | @Inject | 29 | @Inject |
22 | private IResourceDescriptionsProvider resourceDescriptionsProvider; | 30 | private IResourceDescriptionsProvider resourceDescriptionsProvider; |
23 | 31 | ||
32 | @Inject | ||
33 | private IResourceScopeCache cache; | ||
34 | |||
24 | @Override | 35 | @Override |
25 | protected ISelectable getAllDescriptions(Resource resource) { | 36 | public IScope getScope(EObject context, EReference reference) { |
37 | var resource = context.eResource(); | ||
38 | if (resource == null) { | ||
39 | return IScope.NULLSCOPE; | ||
40 | } | ||
41 | var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource)); | ||
42 | if (localImports.resourceDescription() == null) { | ||
43 | return IScope.NULLSCOPE; | ||
44 | } | ||
45 | var globalScope = getGlobalScope(resource, reference); | ||
46 | var type = reference.getEReferenceType(); | ||
47 | boolean ignoreCase = isIgnoreCase(reference); | ||
48 | var scope = SelectableBasedScope.createScope(globalScope, localImports.resourceDescription(), type, | ||
49 | ignoreCase); | ||
50 | if (localImports.normalizedSelectable() == null) { | ||
51 | return scope; | ||
52 | } | ||
53 | return SelectableBasedScope.createScope(scope, localImports.normalizedSelectable(), type, ignoreCase); | ||
54 | } | ||
55 | |||
56 | protected LocalImports computeLocalImports(Resource resource) { | ||
26 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. | 57 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. |
27 | var resourceDescriptions = resourceDescriptionsProvider | 58 | var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet()); |
28 | .getResourceDescriptions(resource.getResourceSet()); | ||
29 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); | 59 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); |
30 | if (resourceDescription != null && !resource.getContents().isEmpty()) { | 60 | if (resourceDescription == null) { |
31 | var rootElement = resource.getContents().getFirst(); | 61 | return new LocalImports(null, null); |
32 | if (rootElement != null) { | 62 | } |
33 | var rootName = NamingUtil.stripRootPrefix(qualifiedNameProvider.getFullyQualifiedName(rootElement)); | 63 | var rootElement = resource.getContents().getFirst(); |
34 | if (rootName == null) { | 64 | if (rootElement == null) { |
35 | return resourceDescription; | 65 | return new LocalImports(resourceDescription, null); |
36 | } | ||
37 | return NormalizedSelectable.of(resourceDescription, rootName, QualifiedName.EMPTY); | ||
38 | } | ||
39 | } | 66 | } |
40 | return resourceDescription; | 67 | var rootName = NamingUtil.stripRootPrefix(qualifiedNameProvider.getFullyQualifiedName(rootElement)); |
68 | if (rootName == null) { | ||
69 | return new LocalImports(resourceDescription, null); | ||
70 | } | ||
71 | var normalizedSelectable = new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY); | ||
72 | return new LocalImports(resourceDescription, normalizedSelectable); | ||
73 | } | ||
74 | |||
75 | protected record LocalImports(IResourceDescription resourceDescription, ISelectable normalizedSelectable) { | ||
41 | } | 76 | } |
42 | } | 77 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/Import.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/Import.java new file mode 100644 index 00000000..e2f0d3d8 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/Import.java | |||
@@ -0,0 +1,12 @@ | |||
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.imports; | ||
7 | |||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | |||
10 | public sealed interface Import permits NamedImport, TransitiveImport { | ||
11 | URI uri(); | ||
12 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollection.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollection.java new file mode 100644 index 00000000..63171138 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollection.java | |||
@@ -0,0 +1,76 @@ | |||
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.imports; | ||
7 | |||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | import org.jetbrains.annotations.NotNull; | ||
10 | import org.jetbrains.annotations.Nullable; | ||
11 | |||
12 | import java.util.*; | ||
13 | |||
14 | public class ImportCollection { | ||
15 | public static ImportCollection EMPTY = new ImportCollection() { | ||
16 | @Override | ||
17 | public void add(Import importEntry) { | ||
18 | throw new UnsupportedOperationException("Read-only collection"); | ||
19 | } | ||
20 | |||
21 | @Override | ||
22 | public void remove(URI uri) { | ||
23 | throw new UnsupportedOperationException("Read-only collection"); | ||
24 | } | ||
25 | }; | ||
26 | |||
27 | private final Map<URI, Import> importMap = new HashMap<>(); | ||
28 | |||
29 | public void add(Import importEntry) { | ||
30 | importMap.compute(importEntry.uri(), (ignored, originalEntry) -> merge(originalEntry, importEntry)); | ||
31 | } | ||
32 | |||
33 | public void addAll(Iterable<? extends Import> imports) { | ||
34 | imports.forEach(this::add); | ||
35 | } | ||
36 | |||
37 | public void remove(URI uri) { | ||
38 | importMap.remove(uri); | ||
39 | } | ||
40 | |||
41 | public List<Import> toList() { | ||
42 | return List.copyOf(importMap.values()); | ||
43 | } | ||
44 | |||
45 | public Set<URI> toUriSet() { | ||
46 | return new LinkedHashSet<>(importMap.keySet()); | ||
47 | } | ||
48 | |||
49 | @NotNull | ||
50 | private static Import merge(@Nullable Import left, @NotNull Import right) { | ||
51 | if (left == null) { | ||
52 | return right; | ||
53 | } | ||
54 | if (!left.uri().equals(right.uri())) { | ||
55 | throw new IllegalArgumentException("Expected URIs '%s' and '%s' to be equal".formatted( | ||
56 | left.uri(), right.uri())); | ||
57 | } | ||
58 | return switch (left) { | ||
59 | case TransitiveImport transitiveLeft -> | ||
60 | right instanceof TransitiveImport ? left : merge(right, transitiveLeft); | ||
61 | case NamedImport namedLeft -> switch (right) { | ||
62 | case TransitiveImport ignored -> namedLeft; | ||
63 | case NamedImport namedRight -> { | ||
64 | if (!namedLeft.qualifiedName().equals(namedRight.qualifiedName())) { | ||
65 | throw new IllegalArgumentException("Expected qualified names '%s' and '%s' to be equal" | ||
66 | .formatted(namedLeft.qualifiedName(), namedRight.qualifiedName())); | ||
67 | } | ||
68 | var mergedAliases = new LinkedHashSet<>(namedLeft.aliases()); | ||
69 | mergedAliases.addAll(namedRight.aliases()); | ||
70 | yield new NamedImport(namedLeft.uri(), namedLeft.qualifiedName(), | ||
71 | List.copyOf(mergedAliases), namedLeft.alsoImplicit() || namedRight.alsoImplicit()); | ||
72 | } | ||
73 | }; | ||
74 | }; | ||
75 | } | ||
76 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java new file mode 100644 index 00000000..cea99f0a --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java | |||
@@ -0,0 +1,138 @@ | |||
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.imports; | ||
7 | |||
8 | import com.google.common.base.Splitter; | ||
9 | import com.google.common.base.Strings; | ||
10 | import com.google.inject.Inject; | ||
11 | import com.google.inject.Provider; | ||
12 | import com.google.inject.Singleton; | ||
13 | import org.eclipse.emf.common.util.URI; | ||
14 | import org.eclipse.emf.ecore.resource.Resource; | ||
15 | import org.eclipse.xtext.linking.impl.LinkingHelper; | ||
16 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
17 | import org.eclipse.xtext.naming.QualifiedName; | ||
18 | import org.eclipse.xtext.nodemodel.util.NodeModelUtils; | ||
19 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
20 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
21 | import tools.refinery.language.library.RefineryLibraries; | ||
22 | import tools.refinery.language.model.problem.ImportStatement; | ||
23 | import tools.refinery.language.model.problem.Problem; | ||
24 | import tools.refinery.language.model.problem.ProblemPackage; | ||
25 | import tools.refinery.language.naming.NamingUtil; | ||
26 | import tools.refinery.language.resource.LoadOnDemandResourceDescriptionProvider; | ||
27 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
28 | |||
29 | import java.util.*; | ||
30 | |||
31 | @Singleton | ||
32 | public class ImportCollector { | ||
33 | private static final String PREFIX = "tools.refinery.language.imports."; | ||
34 | private static final String DIRECT_IMPORTS_KEY = PREFIX + "DIRECT_IMPORTS"; | ||
35 | private static final String ALL_IMPORTS_KEY = PREFIX + "ALL_IMPORTS"; | ||
36 | |||
37 | @Inject | ||
38 | private IResourceScopeCache cache; | ||
39 | |||
40 | @Inject | ||
41 | private LinkingHelper linkingHelper; | ||
42 | |||
43 | @Inject | ||
44 | private IQualifiedNameConverter qualifiedNameConverter; | ||
45 | |||
46 | @Inject | ||
47 | private Provider<LoadOnDemandResourceDescriptionProvider> loadOnDemandProvider; | ||
48 | |||
49 | public ImportCollection getDirectImports(Resource resource) { | ||
50 | return cache.get(DIRECT_IMPORTS_KEY, resource, () -> this.computeDirectImports(resource)); | ||
51 | } | ||
52 | |||
53 | protected ImportCollection computeDirectImports(Resource resource) { | ||
54 | if (resource.getContents().isEmpty() || !(resource.getContents().getFirst() instanceof Problem problem)) { | ||
55 | return ImportCollection.EMPTY; | ||
56 | } | ||
57 | Map<QualifiedName, Set<QualifiedName>> aliasesMap = new LinkedHashMap<>(); | ||
58 | for (var statement : problem.getStatements()) { | ||
59 | if (statement instanceof ImportStatement importStatement) { | ||
60 | collectImportStatement(importStatement, aliasesMap); | ||
61 | } | ||
62 | } | ||
63 | var collection = new ImportCollection(); | ||
64 | collection.addAll(RefineryLibraries.getAutomaticImports()); | ||
65 | for (var entry : aliasesMap.entrySet()) { | ||
66 | var qualifiedName = entry.getKey(); | ||
67 | RefineryLibraries.resolveQualifiedName(qualifiedName).ifPresent(uri -> { | ||
68 | if (!uri.equals(resource.getURI())) { | ||
69 | var aliases = entry.getValue(); | ||
70 | collection.add(NamedImport.explicit(uri, qualifiedName, List.copyOf(aliases))); | ||
71 | } | ||
72 | }); | ||
73 | } | ||
74 | collection.remove(resource.getURI()); | ||
75 | return collection; | ||
76 | } | ||
77 | |||
78 | private void collectImportStatement(ImportStatement importStatement, Map<QualifiedName, Set<QualifiedName>> aliasesMap) { | ||
79 | var nodes = NodeModelUtils.findNodesForFeature(importStatement, | ||
80 | ProblemPackage.Literals.IMPORT_STATEMENT__IMPORTED_MODULE); | ||
81 | var aliasString = importStatement.getAlias(); | ||
82 | var alias = Strings.isNullOrEmpty(aliasString) ? QualifiedName.EMPTY : | ||
83 | NamingUtil.stripRootPrefix(qualifiedNameConverter.toQualifiedName(aliasString)); | ||
84 | for (var node : nodes) { | ||
85 | var qualifiedNameString = linkingHelper.getCrossRefNodeAsString(node, true); | ||
86 | if (Strings.isNullOrEmpty(qualifiedNameString)) { | ||
87 | continue; | ||
88 | } | ||
89 | var qualifiedName = NamingUtil.stripRootPrefix( | ||
90 | qualifiedNameConverter.toQualifiedName(qualifiedNameString)); | ||
91 | var aliases = aliasesMap.computeIfAbsent(qualifiedName, ignored -> new LinkedHashSet<>()); | ||
92 | aliases.add(alias); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | public ImportCollection getAllImports(Resource resource) { | ||
97 | return cache.get(ALL_IMPORTS_KEY, resource, () -> this.computeAllImports(resource)); | ||
98 | } | ||
99 | |||
100 | protected ImportCollection computeAllImports(Resource resource) { | ||
101 | var collection = new ImportCollection(); | ||
102 | collection.addAll(getDirectImports(resource).toList()); | ||
103 | var loadOnDemand = loadOnDemandProvider.get(); | ||
104 | loadOnDemand.setContext(resource); | ||
105 | var seen = new HashSet<URI>(); | ||
106 | seen.add(resource.getURI()); | ||
107 | var queue = new ArrayDeque<>(collection.toUriSet()); | ||
108 | while (!queue.isEmpty()) { | ||
109 | var uri = queue.removeFirst(); | ||
110 | seen.add(uri); | ||
111 | collection.add(new TransitiveImport(uri)); | ||
112 | var resourceDescription = loadOnDemand.getResourceDescription(uri); | ||
113 | if (resourceDescription == null) { | ||
114 | continue; | ||
115 | } | ||
116 | var problemDescriptions = resourceDescription.getExportedObjectsByType(ProblemPackage.Literals.PROBLEM); | ||
117 | for (var eObjectDescription : problemDescriptions) { | ||
118 | for (var importedUri : getImports(eObjectDescription)) { | ||
119 | if (!seen.contains(importedUri)) { | ||
120 | queue.addLast(importedUri); | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | } | ||
125 | collection.remove(resource.getURI()); | ||
126 | return collection; | ||
127 | } | ||
128 | |||
129 | protected List<URI> getImports(IEObjectDescription eObjectDescription) { | ||
130 | var importString = eObjectDescription.getUserData(ProblemResourceDescriptionStrategy.IMPORTS); | ||
131 | if (importString == null) { | ||
132 | return List.of(); | ||
133 | } | ||
134 | return Splitter.on(ProblemResourceDescriptionStrategy.IMPORTS_SEPARATOR).splitToStream(importString) | ||
135 | .map(URI::createURI) | ||
136 | .toList(); | ||
137 | } | ||
138 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/NamedImport.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/NamedImport.java new file mode 100644 index 00000000..f5e89605 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/NamedImport.java | |||
@@ -0,0 +1,22 @@ | |||
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.imports; | ||
7 | |||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | import org.eclipse.xtext.naming.QualifiedName; | ||
10 | |||
11 | import java.util.List; | ||
12 | |||
13 | public record NamedImport(URI uri, QualifiedName qualifiedName, List<QualifiedName> aliases, | ||
14 | boolean alsoImplicit) implements Import { | ||
15 | public static NamedImport implicit(URI uri, QualifiedName qualifiedName) { | ||
16 | return new NamedImport(uri, qualifiedName, List.of(), true); | ||
17 | } | ||
18 | |||
19 | public static NamedImport explicit(URI uri, QualifiedName qualifiedName, List<QualifiedName> aliases) { | ||
20 | return new NamedImport(uri, qualifiedName, aliases, false); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/TransitiveImport.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/TransitiveImport.java new file mode 100644 index 00000000..6f5ceaa6 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/TransitiveImport.java | |||
@@ -0,0 +1,11 @@ | |||
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.imports; | ||
7 | |||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | |||
10 | public record TransitiveImport(URI uri) implements Import { | ||
11 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java index 59e26561..0bd1e50b 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.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 | */ |
@@ -11,6 +11,7 @@ import org.eclipse.emf.ecore.EObject; | |||
11 | import org.eclipse.emf.ecore.resource.Resource; | 11 | import org.eclipse.emf.ecore.resource.Resource; |
12 | import org.eclipse.xtext.util.IResourceScopeCache; | 12 | import org.eclipse.xtext.util.IResourceScopeCache; |
13 | import org.eclipse.xtext.util.Tuples; | 13 | import org.eclipse.xtext.util.Tuples; |
14 | import tools.refinery.language.library.BuiltinLibrary; | ||
14 | import tools.refinery.language.model.problem.*; | 15 | import tools.refinery.language.model.problem.*; |
15 | 16 | ||
16 | import java.util.*; | 17 | import java.util.*; |
@@ -27,8 +28,8 @@ public class ProblemDesugarer { | |||
27 | 28 | ||
28 | private Optional<Problem> doGetBuiltinProblem(Resource resource) { | 29 | private Optional<Problem> doGetBuiltinProblem(Resource resource) { |
29 | return Optional.ofNullable(resource).map(Resource::getResourceSet) | 30 | return Optional.ofNullable(resource).map(Resource::getResourceSet) |
30 | .map(resourceSet -> resourceSet.getResource(ProblemUtil.BUILTIN_LIBRARY_URI, true)) | 31 | .map(resourceSet -> resourceSet.getResource(BuiltinLibrary.BUILTIN_LIBRARY_URI, true)) |
31 | .map(Resource::getContents).filter(contents -> !contents.isEmpty()).map(contents -> contents.get(0)) | 32 | .map(Resource::getContents).filter(contents -> !contents.isEmpty()).map(List::getFirst) |
32 | .filter(Problem.class::isInstance).map(Problem.class::cast); | 33 | .filter(Problem.class::isInstance).map(Problem.class::cast); |
33 | } | 34 | } |
34 | 35 | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java index 0f87c04b..23ff55e7 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java | |||
@@ -5,16 +5,13 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.language.utils; | 6 | package tools.refinery.language.utils; |
7 | 7 | ||
8 | import org.eclipse.emf.common.util.URI; | ||
9 | import org.eclipse.emf.ecore.EObject; | 8 | import org.eclipse.emf.ecore.EObject; |
10 | import org.eclipse.emf.ecore.util.EcoreUtil; | 9 | import org.eclipse.emf.ecore.util.EcoreUtil; |
11 | import org.eclipse.xtext.EcoreUtil2; | 10 | import org.eclipse.xtext.EcoreUtil2; |
11 | import tools.refinery.language.library.BuiltinLibrary; | ||
12 | import tools.refinery.language.model.problem.*; | 12 | import tools.refinery.language.model.problem.*; |
13 | 13 | ||
14 | public final class ProblemUtil { | 14 | public final class ProblemUtil { |
15 | public static final String BUILTIN_LIBRARY_NAME = "builtin"; | ||
16 | public static final URI BUILTIN_LIBRARY_URI = getLibraryUri(); | ||
17 | |||
18 | private ProblemUtil() { | 15 | private ProblemUtil() { |
19 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | 16 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); |
20 | } | 17 | } |
@@ -23,7 +20,7 @@ public final class ProblemUtil { | |||
23 | if (eObject != null) { | 20 | if (eObject != null) { |
24 | var eResource = eObject.eResource(); | 21 | var eResource = eObject.eResource(); |
25 | if (eResource != null) { | 22 | if (eResource != null) { |
26 | return ProblemUtil.BUILTIN_LIBRARY_URI.equals(eResource.getURI()); | 23 | return BuiltinLibrary.BUILTIN_LIBRARY_URI.equals(eResource.getURI()); |
27 | } | 24 | } |
28 | } | 25 | } |
29 | return false; | 26 | return false; |
@@ -131,13 +128,4 @@ public final class ProblemUtil { | |||
131 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); | 128 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); |
132 | return problem != null && problem.getKind() == ModuleKind.MODULE; | 129 | return problem != null && problem.getKind() == ModuleKind.MODULE; |
133 | } | 130 | } |
134 | |||
135 | private static URI getLibraryUri() { | ||
136 | var libraryResource = ProblemUtil.class.getClassLoader() | ||
137 | .getResource("tools/refinery/language/%s.problem".formatted(BUILTIN_LIBRARY_NAME)); | ||
138 | if (libraryResource == null) { | ||
139 | throw new AssertionError("Library '%s' was not found".formatted(BUILTIN_LIBRARY_NAME)); | ||
140 | } | ||
141 | return URI.createURI(libraryResource.toString()); | ||
142 | } | ||
143 | } | 131 | } |
diff --git a/subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary b/subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary new file mode 100644 index 00000000..bb7e369d --- /dev/null +++ b/subprojects/language/src/main/resources/META-INF/services/tools.refinery.language.library.RefineryLibrary | |||
@@ -0,0 +1,4 @@ | |||
1 | # SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
2 | # | ||
3 | # SPDX-License-Identifier: EPL-2.0 | ||
4 | tools.refinery.language.library.BuiltinLibrary | ||
diff --git a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem b/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery index 022c3167..022c3167 100644 --- a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem +++ b/subprojects/language/src/main/resources/tools/refinery/language/library/builtin.refinery | |||