diff options
author | 2024-02-02 16:28:19 +0100 | |
---|---|---|
committer | 2024-02-02 17:36:24 +0100 | |
commit | 2dfcb286216419976368ad926f8ac7f018aa2bf9 (patch) | |
tree | b9d235ebf2049e42e58126e743c782333d64681a /subprojects/language/src | |
parent | refactor: serialize solutions as modules (diff) | |
download | refinery-2dfcb286216419976368ad926f8ac7f018aa2bf9.tar.gz refinery-2dfcb286216419976368ad926f8ac7f018aa2bf9.tar.zst refinery-2dfcb286216419976368ad926f8ac7f018aa2bf9.zip |
refactor(language): name disambiguation
* Use fully qualified names starting with :: (as in C++) to unambiguously refer
to an element.
* Name shadowing within modules.
Diffstat (limited to 'subprojects/language/src')
14 files changed, 488 insertions, 87 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext index 4ce3fae1..f0d6c38c 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext | |||
@@ -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 | */ |
@@ -16,10 +16,13 @@ enum ModuleKind: | |||
16 | PROBLEM="problem" | MODULE="module"; | 16 | PROBLEM="problem" | MODULE="module"; |
17 | 17 | ||
18 | Statement: | 18 | Statement: |
19 | Assertion | ClassDeclaration | EnumDeclaration | | 19 | ImportStatement | Assertion | ClassDeclaration | EnumDeclaration | |
20 | PredicateDefinition | /* FunctionDefinition | RuleDefinition | */ | 20 | PredicateDefinition | /* FunctionDefinition | RuleDefinition | */ |
21 | ScopeDeclaration | NodeDeclaration; | 21 | ScopeDeclaration | NodeDeclaration; |
22 | 22 | ||
23 | ImportStatement: | ||
24 | "import" importedModule=[Problem|QualifiedName] ("as" alias=ID)? "."; | ||
25 | |||
23 | ClassDeclaration: | 26 | ClassDeclaration: |
24 | abstract?="abstract"? "class" | 27 | abstract?="abstract"? "class" |
25 | name=Identifier | 28 | name=Identifier |
@@ -266,10 +269,10 @@ UpperBound returns ecore::EInt: | |||
266 | INT | "*"; | 269 | INT | "*"; |
267 | 270 | ||
268 | QualifiedName hidden(): | 271 | QualifiedName hidden(): |
269 | Identifier ("::" Identifier)*; | 272 | "::"? Identifier (QUALIFIED_NAME_SEPARATOR Identifier)*; |
270 | 273 | ||
271 | NonContainmentQualifiedName hidden(): | 274 | NonContainmentQualifiedName hidden(): |
272 | NonContainmentIdentifier ("::" Identifier)*; | 275 | (NonContainmentIdentifier | "::" Identifier) (QUALIFIED_NAME_SEPARATOR Identifier)*; |
273 | 276 | ||
274 | Identifier: | 277 | Identifier: |
275 | NonContainmentIdentifier | "contains" | "container"; | 278 | NonContainmentIdentifier | "contains" | "container"; |
@@ -284,6 +287,9 @@ Real returns ecore::EDouble: | |||
284 | terminal TRANSITIVE_CLOSURE: | 287 | terminal TRANSITIVE_CLOSURE: |
285 | "synthetic:TRANSITIVE_CLOSURE"; | 288 | "synthetic:TRANSITIVE_CLOSURE"; |
286 | 289 | ||
290 | terminal QUALIFIED_NAME_SEPARATOR: | ||
291 | "synthetic::QUALIFIED_NAME_SEPARATOR"; | ||
292 | |||
287 | @Override | 293 | @Override |
288 | terminal ID: | 294 | terminal ID: |
289 | ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*; | 295 | ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*; |
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 00dd3de3..19816da4 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.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 | */ |
@@ -14,6 +14,7 @@ import com.google.inject.name.Names; | |||
14 | import org.eclipse.xtext.conversion.IValueConverterService; | 14 | import org.eclipse.xtext.conversion.IValueConverterService; |
15 | import org.eclipse.xtext.linking.ILinkingService; | 15 | import org.eclipse.xtext.linking.ILinkingService; |
16 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | 16 | import org.eclipse.xtext.naming.IQualifiedNameConverter; |
17 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | ||
17 | import org.eclipse.xtext.parser.IParser; | 18 | import org.eclipse.xtext.parser.IParser; |
18 | import org.eclipse.xtext.resource.*; | 19 | import org.eclipse.xtext.resource.*; |
19 | import org.eclipse.xtext.scoping.IGlobalScopeProvider; | 20 | import org.eclipse.xtext.scoping.IGlobalScopeProvider; |
@@ -26,12 +27,11 @@ import org.eclipse.xtext.validation.IResourceValidator; | |||
26 | import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; | 27 | import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; |
27 | import tools.refinery.language.conversion.ProblemValueConverterService; | 28 | import tools.refinery.language.conversion.ProblemValueConverterService; |
28 | import tools.refinery.language.linking.ProblemLinkingService; | 29 | import tools.refinery.language.linking.ProblemLinkingService; |
30 | import tools.refinery.language.naming.ProblemDelegateQualifiedNameProvider; | ||
29 | import tools.refinery.language.naming.ProblemQualifiedNameConverter; | 31 | import tools.refinery.language.naming.ProblemQualifiedNameConverter; |
32 | import tools.refinery.language.naming.ProblemQualifiedNameProvider; | ||
30 | import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser; | 33 | import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser; |
31 | import tools.refinery.language.resource.ProblemDerivedStateComputer; | 34 | import tools.refinery.language.resource.*; |
32 | import tools.refinery.language.resource.ProblemLocationInFileProvider; | ||
33 | import tools.refinery.language.resource.ProblemResource; | ||
34 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
35 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; | 35 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; |
36 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; | 36 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; |
37 | import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; | 37 | import tools.refinery.language.serializer.PreferShortAssertionsProblemSemanticSequencer; |
@@ -54,6 +54,17 @@ public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { | |||
54 | return ProblemQualifiedNameConverter.class; | 54 | return ProblemQualifiedNameConverter.class; |
55 | } | 55 | } |
56 | 56 | ||
57 | public void configureIQualifiedNameProviderDelegate(Binder binder) { | ||
58 | binder.bind(IQualifiedNameProvider.class) | ||
59 | .annotatedWith(Names.named(ProblemQualifiedNameProvider.NAMED_DELEGATE)) | ||
60 | .to(ProblemDelegateQualifiedNameProvider.class); | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() { | ||
65 | return ProblemQualifiedNameProvider.class; | ||
66 | } | ||
67 | |||
57 | public Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() { | 68 | public Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() { |
58 | return ProblemResourceDescriptionStrategy.class; | 69 | return ProblemResourceDescriptionStrategy.class; |
59 | } | 70 | } |
@@ -87,7 +98,7 @@ public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { | |||
87 | // Method name follows Xtext convention. | 98 | // Method name follows Xtext convention. |
88 | @SuppressWarnings("squid:S100") | 99 | @SuppressWarnings("squid:S100") |
89 | public Class<? extends IResourceDescription.Manager> bindIResourceDescription$Manager() { | 100 | public Class<? extends IResourceDescription.Manager> bindIResourceDescription$Manager() { |
90 | return DerivedStateAwareResourceDescriptionManager.class; | 101 | return ProblemResourceDescriptionManager.class; |
91 | } | 102 | } |
92 | 103 | ||
93 | public Class<? extends IResourceValidator> bindIResourceValidator() { | 104 | public Class<? extends IResourceValidator> bindIResourceValidator() { |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java index 1647d4e7..feae5ebb 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java | |||
@@ -1,21 +1,24 @@ | |||
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 | */ |
6 | package tools.refinery.language.naming; | 6 | package tools.refinery.language.naming; |
7 | 7 | ||
8 | import org.eclipse.xtext.naming.QualifiedName; | ||
9 | |||
8 | import java.util.regex.Pattern; | 10 | import java.util.regex.Pattern; |
9 | 11 | ||
10 | public final class NamingUtil { | 12 | public final class NamingUtil { |
11 | private static final String SINGLETON_VARIABLE_PREFIX = "_"; | 13 | private static final String SINGLETON_VARIABLE_PREFIX = "_"; |
12 | 14 | public static final QualifiedName ROOT_NAME = QualifiedName.create(""); | |
13 | private static final Pattern ID_REGEX = Pattern.compile("[_a-zA-Z][_0-9a-zA-Z]*"); | 15 | |
16 | private static final Pattern ID_REGEX = Pattern.compile("[_a-zA-Z]\\w*"); | ||
14 | 17 | ||
15 | private NamingUtil() { | 18 | private NamingUtil() { |
16 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | 19 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); |
17 | } | 20 | } |
18 | 21 | ||
19 | public static boolean isNullOrEmpty(String name) { | 22 | public static boolean isNullOrEmpty(String name) { |
20 | return name == null || name.isEmpty(); | 23 | return name == null || name.isEmpty(); |
21 | } | 24 | } |
@@ -23,8 +26,28 @@ public final class NamingUtil { | |||
23 | public static boolean isSingletonVariableName(String name) { | 26 | public static boolean isSingletonVariableName(String name) { |
24 | return name != null && name.startsWith(SINGLETON_VARIABLE_PREFIX); | 27 | return name != null && name.startsWith(SINGLETON_VARIABLE_PREFIX); |
25 | } | 28 | } |
26 | 29 | ||
30 | // This method name only makes sense if it checks for the positive case. | ||
31 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") | ||
27 | public static boolean isValidId(String name) { | 32 | public static boolean isValidId(String name) { |
28 | return name != null && ID_REGEX.matcher(name).matches(); | 33 | return name != null && ID_REGEX.matcher(name).matches(); |
29 | } | 34 | } |
35 | |||
36 | public static boolean isFullyQualified(QualifiedName name) { | ||
37 | return name.startsWith(ROOT_NAME); | ||
38 | } | ||
39 | |||
40 | public static QualifiedName stripRootPrefix(QualifiedName name) { | ||
41 | if (name == null) { | ||
42 | return null; | ||
43 | } | ||
44 | return isFullyQualified(name) ? name.skipFirst(ROOT_NAME.getSegmentCount()) : name; | ||
45 | } | ||
46 | |||
47 | public static QualifiedName addRootPrefix(QualifiedName name) { | ||
48 | if (name == null) { | ||
49 | return null; | ||
50 | } | ||
51 | return isFullyQualified(name) ? name : ROOT_NAME.append(name); | ||
52 | } | ||
30 | } | 53 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java new file mode 100644 index 00000000..1e78cee1 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java | |||
@@ -0,0 +1,18 @@ | |||
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.naming; | ||
7 | |||
8 | import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider; | ||
9 | import org.eclipse.xtext.naming.QualifiedName; | ||
10 | import tools.refinery.language.model.problem.Problem; | ||
11 | |||
12 | public class ProblemDelegateQualifiedNameProvider extends DefaultDeclarativeQualifiedNameProvider { | ||
13 | protected QualifiedName qualifiedName(Problem ele) { | ||
14 | var qualifiedName = computeFullyQualifiedNameFromNameAttribute(ele); | ||
15 | // Strip the root prefix even if explicitly provided. | ||
16 | return NamingUtil.stripRootPrefix(qualifiedName); | ||
17 | } | ||
18 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java index 74b4e208..88a0fe9a 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java | |||
@@ -12,7 +12,7 @@ import com.google.inject.Singleton; | |||
12 | @Singleton | 12 | @Singleton |
13 | public class ProblemQualifiedNameConverter extends IQualifiedNameConverter.DefaultImpl { | 13 | public class ProblemQualifiedNameConverter extends IQualifiedNameConverter.DefaultImpl { |
14 | public static final String DELIMITER = "::"; | 14 | public static final String DELIMITER = "::"; |
15 | 15 | ||
16 | @Override | 16 | @Override |
17 | public String getDelimiter() { | 17 | public String getDelimiter() { |
18 | return DELIMITER; | 18 | return DELIMITER; |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.java new file mode 100644 index 00000000..5b682058 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.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.naming; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.name.Named; | ||
10 | import org.eclipse.emf.ecore.EObject; | ||
11 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | ||
12 | import org.eclipse.xtext.naming.QualifiedName; | ||
13 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
14 | import org.eclipse.xtext.util.Tuples; | ||
15 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
16 | |||
17 | public class ProblemQualifiedNameProvider extends IQualifiedNameProvider.AbstractImpl { | ||
18 | private static final String PREFIX = "tools.refinery.language.naming.ProblemQualifiedNameProvider."; | ||
19 | public static final String NAMED_DELEGATE = PREFIX + "NAMED_DELEGATE"; | ||
20 | public static final String CACHE_KEY = PREFIX + "CACHE_KEY"; | ||
21 | |||
22 | @Inject | ||
23 | @Named(NAMED_DELEGATE) | ||
24 | private IQualifiedNameProvider delegate; | ||
25 | |||
26 | @Inject | ||
27 | private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; | ||
28 | |||
29 | @Override | ||
30 | public QualifiedName getFullyQualifiedName(EObject obj) { | ||
31 | return cache.get(Tuples.pair(obj, CACHE_KEY), obj.eResource(), () -> computeFullyQualifiedName(obj)); | ||
32 | } | ||
33 | |||
34 | public QualifiedName computeFullyQualifiedName(EObject obj) { | ||
35 | var qualifiedName = delegate.getFullyQualifiedName(obj); | ||
36 | if (qualifiedName != null && ProblemResourceDescriptionStrategy.shouldExport(obj)) { | ||
37 | return NamingUtil.addRootPrefix(qualifiedName); | ||
38 | } | ||
39 | return qualifiedName; | ||
40 | } | ||
41 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java index 306a86fc..0e19357f 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.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 | */ |
@@ -59,7 +59,7 @@ public class IdentifierTokenProvider { | |||
59 | 59 | ||
60 | private void createValueToTokenIdMap() { | 60 | private void createValueToTokenIdMap() { |
61 | var tokenIdToValueMap = tokenDefProvider.getTokenDefMap(); | 61 | var tokenIdToValueMap = tokenDefProvider.getTokenDefMap(); |
62 | valueToTokenIdMap = new HashMap<>(tokenIdToValueMap.size()); | 62 | valueToTokenIdMap = HashMap.newHashMap(tokenIdToValueMap.size()); |
63 | for (var entry : tokenIdToValueMap.entrySet()) { | 63 | for (var entry : tokenIdToValueMap.entrySet()) { |
64 | valueToTokenIdMap.put(entry.getValue(), entry.getKey()); | 64 | valueToTokenIdMap.put(entry.getValue(), entry.getKey()); |
65 | } | 65 | } |
@@ -74,17 +74,16 @@ public class IdentifierTokenProvider { | |||
74 | } | 74 | } |
75 | 75 | ||
76 | private void collectIdentifierTokensFromElement(AbstractElement element) { | 76 | private void collectIdentifierTokensFromElement(AbstractElement element) { |
77 | if (element instanceof Alternatives alternatives) { | 77 | switch (element) { |
78 | for (var alternative : alternatives.getElements()) { | 78 | case Alternatives alternatives -> { |
79 | collectIdentifierTokensFromElement(alternative); | 79 | for (var alternative : alternatives.getElements()) { |
80 | } | 80 | collectIdentifierTokensFromElement(alternative); |
81 | } else if (element instanceof RuleCall ruleCall) { | 81 | } |
82 | collectIdentifierTokensFromRule(ruleCall.getRule()); | 82 | } |
83 | } else if (element instanceof Keyword keyword) { | 83 | case RuleCall ruleCall -> collectIdentifierTokensFromRule(ruleCall.getRule()); |
84 | collectToken("'" + keyword.getValue() + "'"); | 84 | case Keyword keyword -> collectToken("'" + keyword.getValue() + "'"); |
85 | } else { | 85 | default -> throw new IllegalArgumentException("Unknown Xtext grammar element: " + element); |
86 | throw new IllegalArgumentException("Unknown Xtext grammar element: " + element); | 86 | } |
87 | } | ||
88 | } | 87 | } |
89 | 88 | ||
90 | private void collectToken(String value) { | 89 | private void collectToken(String value) { |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java index 5b91a6cc..487e4ceb 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.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 | */ |
@@ -28,6 +28,8 @@ public class ProblemTokenSource implements TokenSource { | |||
28 | 28 | ||
29 | private boolean seenId; | 29 | private boolean seenId; |
30 | 30 | ||
31 | private boolean lastVisible; | ||
32 | |||
31 | public ProblemTokenSource(TokenSource delegate) { | 33 | public ProblemTokenSource(TokenSource delegate) { |
32 | this.delegate = delegate; | 34 | this.delegate = delegate; |
33 | } | 35 | } |
@@ -47,18 +49,18 @@ public class ProblemTokenSource implements TokenSource { | |||
47 | 49 | ||
48 | @Override | 50 | @Override |
49 | public Token nextToken() { | 51 | public Token nextToken() { |
50 | if (!buffer.isEmpty()) { | 52 | boolean fromStream = buffer.isEmpty(); |
51 | return buffer.removeFirst(); | 53 | var token = fromStream ? delegate.nextToken() : buffer.removeFirst(); |
52 | } | 54 | if (seenId) { |
53 | var token = delegate.nextToken(); | 55 | if (fromStream && isPlusOrTransitiveClosure(token) && peekForTransitiveClosure()) { |
54 | if (isIdentifier(token)) { | ||
55 | seenId = true; | ||
56 | } else if (seenId && isPlusOrTransitiveClosure(token)) { | ||
57 | if (peekForTransitiveClosure()) { | ||
58 | token.setType(InternalProblemParser.RULE_TRANSITIVE_CLOSURE); | 56 | token.setType(InternalProblemParser.RULE_TRANSITIVE_CLOSURE); |
57 | } else if (lastVisible && isQualifiedNameSeparator(token)) { | ||
58 | token.setType(InternalProblemParser.RULE_QUALIFIED_NAME_SEPARATOR); | ||
59 | } | 59 | } |
60 | } else if (isVisibleToken(token)) { | 60 | } |
61 | seenId = false; | 61 | lastVisible = isVisibleToken(token); |
62 | if (lastVisible) { | ||
63 | seenId = isIdentifier(token); | ||
62 | } | 64 | } |
63 | return token; | 65 | return token; |
64 | } | 66 | } |
@@ -76,6 +78,10 @@ public class ProblemTokenSource implements TokenSource { | |||
76 | return token.getType() == InternalProblemParser.PlusSign; | 78 | return token.getType() == InternalProblemParser.PlusSign; |
77 | } | 79 | } |
78 | 80 | ||
81 | protected boolean isQualifiedNameSeparator(Token token) { | ||
82 | return token.getType() == InternalProblemParser.ColonColon; | ||
83 | } | ||
84 | |||
79 | protected boolean isVisibleToken(Token token) { | 85 | protected boolean isVisibleToken(Token token) { |
80 | int tokenId = token.getType(); | 86 | int tokenId = token.getType(); |
81 | return tokenId != InternalProblemParser.RULE_WS && tokenId != InternalProblemParser.RULE_SL_COMMENT && | 87 | return tokenId != InternalProblemParser.RULE_WS && tokenId != InternalProblemParser.RULE_SL_COMMENT && |
@@ -87,11 +93,16 @@ public class ProblemTokenSource implements TokenSource { | |||
87 | if (token.getType() != InternalProblemParser.LeftParenthesis) { | 93 | if (token.getType() != InternalProblemParser.LeftParenthesis) { |
88 | return false; | 94 | return false; |
89 | } | 95 | } |
96 | boolean allowFullyQualifiedName = true; | ||
90 | while (true) { | 97 | while (true) { |
91 | token = peekWithSkipWhitespace(); | 98 | token = peekWithSkipWhitespace(); |
99 | if (allowFullyQualifiedName && token.getType() == InternalProblemParser.ColonColon) { | ||
100 | token = peekWithSkipWhitespace(); | ||
101 | } | ||
92 | if (!isIdentifier(token)) { | 102 | if (!isIdentifier(token)) { |
93 | return false; | 103 | return false; |
94 | } | 104 | } |
105 | allowFullyQualifiedName = false; | ||
95 | token = peekWithSkipWhitespace(); | 106 | token = peekWithSkipWhitespace(); |
96 | switch (token.getType()) { | 107 | switch (token.getType()) { |
97 | case InternalProblemParser.Comma: | 108 | case InternalProblemParser.Comma: |
@@ -112,11 +123,6 @@ public class ProblemTokenSource implements TokenSource { | |||
112 | 123 | ||
113 | protected Token peekToken() { | 124 | protected Token peekToken() { |
114 | var token = delegate.nextToken(); | 125 | var token = delegate.nextToken(); |
115 | if (isIdentifier(token)) { | ||
116 | seenId = true; | ||
117 | } else if (isVisibleToken(token)) { | ||
118 | seenId = false; | ||
119 | } | ||
120 | buffer.addLast(token); | 126 | buffer.addLast(token); |
121 | return token; | 127 | return token; |
122 | } | 128 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescription.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescription.java new file mode 100644 index 00000000..498a7c57 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescription.java | |||
@@ -0,0 +1,106 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2009, 2011 itemis AG (http://www.itemis.eu) and others. | ||
3 | * Copyright (c) 2024 The Refinery Authors <https://refinery.tools/> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-2.0. | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.language.resource; | ||
10 | |||
11 | import org.apache.log4j.Logger; | ||
12 | import org.eclipse.emf.common.util.TreeIterator; | ||
13 | import org.eclipse.emf.ecore.EObject; | ||
14 | import org.eclipse.emf.ecore.resource.Resource; | ||
15 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
16 | import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; | ||
17 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
18 | import org.eclipse.xtext.resource.impl.DefaultResourceDescription; | ||
19 | import org.eclipse.xtext.resource.impl.EObjectDescriptionLookUp; | ||
20 | import org.eclipse.xtext.util.IAcceptor; | ||
21 | import tools.refinery.language.naming.NamingUtil; | ||
22 | |||
23 | import java.io.IOException; | ||
24 | import java.util.*; | ||
25 | |||
26 | /** | ||
27 | * A resource description that takes {@link ProblemResourceDescriptionStrategy#SHADOWING_KEY} into account when | ||
28 | * describing EObjects. | ||
29 | * <p> | ||
30 | * Based on {@link DefaultResourceDescription}. | ||
31 | */ | ||
32 | public class ProblemResourceDescription extends DefaultResourceDescription { | ||
33 | private static final Logger log = Logger.getLogger(ProblemResourceDescription.class); | ||
34 | |||
35 | private final IDefaultResourceDescriptionStrategy strategy; | ||
36 | |||
37 | public ProblemResourceDescription(Resource resource, IDefaultResourceDescriptionStrategy strategy) { | ||
38 | super(resource, strategy); | ||
39 | this.strategy = strategy; | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Based on {@link DefaultResourceDescription#computeExportedObjects()}. | ||
44 | * | ||
45 | * @return The computed exported objects, taking shadowing into account. | ||
46 | */ | ||
47 | @Override | ||
48 | protected List<IEObjectDescription> computeExportedObjects() { | ||
49 | if (!getResource().isLoaded()) { | ||
50 | try { | ||
51 | getResource().load(null); | ||
52 | } catch (IOException e) { | ||
53 | log.error(e.getMessage(), e); | ||
54 | return Collections.emptyList(); | ||
55 | } | ||
56 | } | ||
57 | final Map<ProblemResourceDescriptionStrategy.ShadowingKey, List<IEObjectDescription>> nameToDescriptionsMap = | ||
58 | new LinkedHashMap<>(); | ||
59 | IAcceptor<IEObjectDescription> acceptor = eObjectDescription -> { | ||
60 | var key = ProblemResourceDescriptionStrategy.getShadowingKey(eObjectDescription); | ||
61 | var descriptions = nameToDescriptionsMap.computeIfAbsent(key, ignored -> new ArrayList<>()); | ||
62 | descriptions.add(eObjectDescription); | ||
63 | }; | ||
64 | TreeIterator<EObject> allProperContents = EcoreUtil.getAllProperContents(getResource(), false); | ||
65 | while (allProperContents.hasNext()) { | ||
66 | EObject content = allProperContents.next(); | ||
67 | if (!strategy.createEObjectDescriptions(content, acceptor)) { | ||
68 | allProperContents.prune(); | ||
69 | } | ||
70 | } | ||
71 | return omitShadowedNames(nameToDescriptionsMap); | ||
72 | } | ||
73 | |||
74 | private static List<IEObjectDescription> omitShadowedNames( | ||
75 | Map<ProblemResourceDescriptionStrategy.ShadowingKey, List<IEObjectDescription>> nameToDescriptionsMap) { | ||
76 | final List<IEObjectDescription> exportedEObjects = new ArrayList<>(); | ||
77 | for (var entry : nameToDescriptionsMap.entrySet()) { | ||
78 | var descriptions = entry.getValue(); | ||
79 | if (NamingUtil.isFullyQualified(entry.getKey().name())) { | ||
80 | exportedEObjects.addAll(descriptions); | ||
81 | } else { | ||
82 | boolean foundPreferred = false; | ||
83 | for (var description : descriptions) { | ||
84 | if (ProblemResourceDescriptionStrategy.PREFERRED_NAME_TRUE.equals( | ||
85 | description.getUserData(ProblemResourceDescriptionStrategy.PREFERRED_NAME))) { | ||
86 | exportedEObjects.add(description); | ||
87 | foundPreferred = true; | ||
88 | } | ||
89 | } | ||
90 | if (!foundPreferred) { | ||
91 | exportedEObjects.addAll(descriptions); | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | return exportedEObjects; | ||
96 | } | ||
97 | |||
98 | // Based on {@code DerivedStateAwareResourceDescriptionManager#createResourceDescription}. | ||
99 | @Override | ||
100 | protected EObjectDescriptionLookUp getLookUp() { | ||
101 | if (lookup == null) { | ||
102 | lookup = new EObjectDescriptionLookUp(computeExportedObjects()); | ||
103 | } | ||
104 | return lookup; | ||
105 | } | ||
106 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionManager.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionManager.java new file mode 100644 index 00000000..23ca139a --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionManager.java | |||
@@ -0,0 +1,19 @@ | |||
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 org.eclipse.emf.ecore.resource.Resource; | ||
9 | import org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager; | ||
10 | import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; | ||
11 | import org.eclipse.xtext.resource.IResourceDescription; | ||
12 | |||
13 | public class ProblemResourceDescriptionManager extends DerivedStateAwareResourceDescriptionManager { | ||
14 | @Override | ||
15 | protected IResourceDescription createResourceDescription(Resource resource, | ||
16 | IDefaultResourceDescriptionStrategy strategy) { | ||
17 | return new ProblemResourceDescription(resource, strategy); | ||
18 | } | ||
19 | } | ||
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 79dad6e7..76fd5852 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 | |||
@@ -25,9 +25,16 @@ import java.util.Map; | |||
25 | @Singleton | 25 | @Singleton |
26 | public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { | 26 | public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { |
27 | private static final String DATA_PREFIX = "tools.refinery.language.resource.ProblemResourceDescriptionStrategy."; | 27 | private static final String DATA_PREFIX = "tools.refinery.language.resource.ProblemResourceDescriptionStrategy."; |
28 | |||
28 | public static final String ARITY = DATA_PREFIX + "ARITY"; | 29 | public static final String ARITY = DATA_PREFIX + "ARITY"; |
29 | public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE"; | 30 | public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE"; |
30 | public static final String ERROR_PREDICATE_TRUE = "true"; | 31 | public static final String ERROR_PREDICATE_TRUE = "true"; |
32 | public static final String SHADOWING_KEY = DATA_PREFIX + "SHADOWING_KEY"; | ||
33 | public static final String SHADOWING_KEY_PROBLEM = "problem"; | ||
34 | public static final String SHADOWING_KEY_NODE = "node"; | ||
35 | public static final String SHADOWING_KEY_RELATION = "relation"; | ||
36 | public static final String PREFERRED_NAME = DATA_PREFIX + "PREFERRED_NAME"; | ||
37 | public static final String PREFERRED_NAME_TRUE = "true"; | ||
31 | public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; | 38 | public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION"; |
32 | public static final String COLOR_RELATION_TRUE = "true"; | 39 | public static final String COLOR_RELATION_TRUE = "true"; |
33 | 40 | ||
@@ -44,8 +51,12 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
44 | return true; | 51 | return true; |
45 | } | 52 | } |
46 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); | 53 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); |
47 | var problemQualifiedName = getNameAsQualifiedName(problem); | ||
48 | var userData = getUserData(eObject); | 54 | var userData = getUserData(eObject); |
55 | if (eObject.equals(problem)) { | ||
56 | acceptEObjectDescription(eObject, qualifiedName, QualifiedName.EMPTY, userData, true, acceptor); | ||
57 | return true; | ||
58 | } | ||
59 | var problemQualifiedName = getNameAsQualifiedName(problem); | ||
49 | QualifiedName lastQualifiedNameToExport = null; | 60 | QualifiedName lastQualifiedNameToExport = null; |
50 | if (shouldExportSimpleName(eObject)) { | 61 | if (shouldExportSimpleName(eObject)) { |
51 | lastQualifiedNameToExport = qualifiedName; | 62 | lastQualifiedNameToExport = qualifiedName; |
@@ -82,10 +93,14 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
82 | if (NamingUtil.isNullOrEmpty(name)) { | 93 | if (NamingUtil.isNullOrEmpty(name)) { |
83 | return null; | 94 | return null; |
84 | } | 95 | } |
85 | return qualifiedNameConverter.toQualifiedName(name); | 96 | var qualifiedName = qualifiedNameConverter.toQualifiedName(name); |
97 | if (eObject instanceof Problem) { | ||
98 | return NamingUtil.stripRootPrefix(qualifiedName); | ||
99 | } | ||
100 | return qualifiedName; | ||
86 | } | 101 | } |
87 | 102 | ||
88 | protected boolean shouldExport(EObject eObject) { | 103 | public static boolean shouldExport(EObject eObject) { |
89 | if (eObject instanceof Variable) { | 104 | if (eObject instanceof Variable) { |
90 | // Variables are always private to the containing predicate definition. | 105 | // Variables are always private to the containing predicate definition. |
91 | return false; | 106 | return false; |
@@ -98,7 +113,12 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
98 | 113 | ||
99 | protected Map<String, String> getUserData(EObject eObject) { | 114 | protected Map<String, String> getUserData(EObject eObject) { |
100 | var builder = ImmutableMap.<String, String>builder(); | 115 | var builder = ImmutableMap.<String, String>builder(); |
101 | if (eObject instanceof Relation relation) { | 116 | if (eObject instanceof Problem) { |
117 | builder.put(SHADOWING_KEY, SHADOWING_KEY_PROBLEM); | ||
118 | } else if (eObject instanceof Node) { | ||
119 | builder.put(SHADOWING_KEY, SHADOWING_KEY_NODE); | ||
120 | } else if (eObject instanceof Relation relation) { | ||
121 | builder.put(SHADOWING_KEY, SHADOWING_KEY_RELATION); | ||
102 | int arity = ProblemUtil.getArity(relation); | 122 | int arity = ProblemUtil.getArity(relation); |
103 | builder.put(ARITY, Integer.toString(arity)); | 123 | builder.put(ARITY, Integer.toString(arity)); |
104 | } | 124 | } |
@@ -124,20 +144,31 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
124 | } | 144 | } |
125 | 145 | ||
126 | private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, | 146 | private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, |
127 | Map<String, String> userData, boolean fullyQualified, | 147 | Map<String, String> userData, boolean preferredName, |
128 | IAcceptor<IEObjectDescription> acceptor) { | 148 | IAcceptor<IEObjectDescription> acceptor) { |
129 | var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); | 149 | var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); |
130 | Map<String, String> userDataWithFullyQualified; | 150 | var userDataWithPreference = userData; |
131 | if (fullyQualified && shouldColorRelation(eObject)) { | 151 | if (preferredName) { |
132 | userDataWithFullyQualified = ImmutableMap.<String, String>builder() | 152 | userDataWithPreference = ImmutableMap.<String, String>builder() |
133 | .putAll(userData) | 153 | .putAll(userData) |
134 | .put(COLOR_RELATION, COLOR_RELATION_TRUE) | 154 | .put(PREFERRED_NAME, PREFERRED_NAME_TRUE) |
135 | .build(); | 155 | .build(); |
136 | } else { | ||
137 | userDataWithFullyQualified = userData; | ||
138 | } | 156 | } |
139 | var description = EObjectDescription.create(qualifiedNameWithPrefix, eObject, userDataWithFullyQualified); | 157 | var description = EObjectDescription.create(qualifiedNameWithPrefix, eObject, userDataWithPreference); |
140 | acceptor.accept(description); | 158 | acceptor.accept(description); |
159 | if (!preferredName) { | ||
160 | return; | ||
161 | } | ||
162 | var userDataWithFullyQualified = userDataWithPreference; | ||
163 | if (shouldColorRelation(eObject)) { | ||
164 | userDataWithFullyQualified = ImmutableMap.<String, String>builder() | ||
165 | .putAll(userDataWithPreference) | ||
166 | .put(COLOR_RELATION, COLOR_RELATION_TRUE) | ||
167 | .build(); | ||
168 | } | ||
169 | var rootQualifiedName = NamingUtil.addRootPrefix(qualifiedNameWithPrefix); | ||
170 | var rootDescription = EObjectDescription.create(rootQualifiedName, eObject, userDataWithFullyQualified); | ||
171 | acceptor.accept(rootDescription); | ||
141 | } | 172 | } |
142 | 173 | ||
143 | private boolean shouldColorRelation(EObject eObject) { | 174 | private boolean shouldColorRelation(EObject eObject) { |
@@ -146,4 +177,11 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti | |||
146 | } | 177 | } |
147 | return eObject instanceof ClassDeclaration || eObject instanceof EnumDeclaration; | 178 | return eObject instanceof ClassDeclaration || eObject instanceof EnumDeclaration; |
148 | } | 179 | } |
180 | |||
181 | public static ShadowingKey getShadowingKey(IEObjectDescription description) { | ||
182 | return new ShadowingKey(description.getName(), description.getUserData(SHADOWING_KEY)); | ||
183 | } | ||
184 | |||
185 | public record ShadowingKey(QualifiedName name, String shadowingKey) { | ||
186 | } | ||
149 | } | 187 | } |
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 new file mode 100644 index 00000000..0c7828d8 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/NormalizedSelectable.java | |||
@@ -0,0 +1,122 @@ | |||
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 | import org.eclipse.xtext.resource.impl.AliasedEObjectDescription; | ||
15 | import org.jetbrains.annotations.NotNull; | ||
16 | |||
17 | import java.util.Iterator; | ||
18 | import java.util.NoSuchElementException; | ||
19 | |||
20 | public class NormalizedSelectable implements ISelectable { | ||
21 | private final ISelectable delegateSelectable; | ||
22 | private final QualifiedName originalPrefix; | ||
23 | private final QualifiedName normalizedPrefix; | ||
24 | |||
25 | private NormalizedSelectable(ISelectable delegateSelectable, QualifiedName originalPrefix, | ||
26 | QualifiedName normalizedPrefix) { | ||
27 | this.delegateSelectable = delegateSelectable; | ||
28 | this.originalPrefix = originalPrefix; | ||
29 | this.normalizedPrefix = normalizedPrefix; | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public boolean isEmpty() { | ||
34 | return delegateSelectable.isEmpty(); | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public Iterable<IEObjectDescription> getExportedObjects() { | ||
39 | var delegateIterable = delegateSelectable.getExportedObjects(); | ||
40 | var aliasedIterable = getAliasedElements(delegateIterable); | ||
41 | return Iterables.concat(delegateIterable, aliasedIterable); | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | 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 | name.startsWith(normalizedPrefix); | ||
49 | if (startsWith && name.getSegmentCount() > normalizedPrefix.getSegmentCount()) { | ||
50 | var originalName = originalPrefix.append(name.skipFirst(normalizedPrefix.getSegmentCount())); | ||
51 | var originalIterable = Iterables.transform( | ||
52 | delegateSelectable.getExportedObjects(type, originalName, ignoreCase), | ||
53 | description -> { | ||
54 | var normalizedName = normalizedPrefix.append( | ||
55 | description.getName().skipFirst(originalPrefix.getSegmentCount())); | ||
56 | return new AliasedEObjectDescription(normalizedName, description); | ||
57 | }); | ||
58 | return Iterables.concat(originalIterable, delegateIterable); | ||
59 | } | ||
60 | return delegateIterable; | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) { | ||
65 | var delegateIterable = delegateSelectable.getExportedObjectsByType(type); | ||
66 | var aliasedIterable = getAliasedElements(delegateIterable); | ||
67 | return Iterables.concat(delegateIterable, aliasedIterable); | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) { | ||
72 | var delegateIterable = delegateSelectable.getExportedObjectsByObject(object); | ||
73 | var aliasedIterable = getAliasedElements(delegateIterable); | ||
74 | return Iterables.concat(delegateIterable, aliasedIterable); | ||
75 | } | ||
76 | |||
77 | private Iterable<IEObjectDescription> getAliasedElements(Iterable<IEObjectDescription> delegateIterable) { | ||
78 | return () -> new Iterator<>() { | ||
79 | private final Iterator<IEObjectDescription> delegateIterator = delegateIterable.iterator(); | ||
80 | private IEObjectDescription next = computeNext(); | ||
81 | |||
82 | @Override | ||
83 | public boolean hasNext() { | ||
84 | return next != null; | ||
85 | } | ||
86 | |||
87 | @Override | ||
88 | public IEObjectDescription next() { | ||
89 | if (!hasNext()) { | ||
90 | throw new NoSuchElementException(); | ||
91 | } | ||
92 | var current = next; | ||
93 | next = computeNext(); | ||
94 | return current; | ||
95 | } | ||
96 | |||
97 | private IEObjectDescription computeNext() { | ||
98 | while (delegateIterator.hasNext()) { | ||
99 | var description = delegateIterator.next(); | ||
100 | var qualifiedName = description.getName(); | ||
101 | if (qualifiedName.startsWith(originalPrefix) && | ||
102 | qualifiedName.getSegmentCount() > originalPrefix.getSegmentCount()) { | ||
103 | var alias = normalizedPrefix.append(qualifiedName.skipFirst(originalPrefix.getSegmentCount())); | ||
104 | return new AliasedEObjectDescription(alias, description); | ||
105 | } | ||
106 | } | ||
107 | return null; | ||
108 | } | ||
109 | }; | ||
110 | } | ||
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 | } | ||
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 4d2dd772..37a67c0c 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 | |||
@@ -5,14 +5,21 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.language.scoping; | 6 | package tools.refinery.language.scoping; |
7 | 7 | ||
8 | import java.util.LinkedHashSet; | 8 | import com.google.common.base.Predicate; |
9 | |||
10 | import org.eclipse.emf.common.util.URI; | 9 | import org.eclipse.emf.common.util.URI; |
10 | import org.eclipse.emf.ecore.EClass; | ||
11 | import org.eclipse.emf.ecore.resource.Resource; | 11 | import org.eclipse.emf.ecore.resource.Resource; |
12 | import org.eclipse.xtext.naming.QualifiedName; | ||
13 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
14 | import org.eclipse.xtext.resource.IResourceDescriptions; | ||
15 | import org.eclipse.xtext.resource.ISelectable; | ||
16 | import org.eclipse.xtext.scoping.IScope; | ||
12 | import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; | 17 | import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; |
13 | 18 | import org.eclipse.xtext.scoping.impl.SelectableBasedScope; | |
14 | import tools.refinery.language.utils.ProblemUtil; | 19 | import tools.refinery.language.utils.ProblemUtil; |
15 | 20 | ||
21 | import java.util.LinkedHashSet; | ||
22 | |||
16 | public class ProblemGlobalScopeProvider extends ImportUriGlobalScopeProvider { | 23 | public class ProblemGlobalScopeProvider extends ImportUriGlobalScopeProvider { |
17 | @Override | 24 | @Override |
18 | protected LinkedHashSet<URI> getImportedUris(Resource resource) { | 25 | protected LinkedHashSet<URI> getImportedUris(Resource resource) { |
@@ -20,4 +27,14 @@ public class ProblemGlobalScopeProvider extends ImportUriGlobalScopeProvider { | |||
20 | importedUris.add(ProblemUtil.BUILTIN_LIBRARY_URI); | 27 | importedUris.add(ProblemUtil.BUILTIN_LIBRARY_URI); |
21 | return importedUris; | 28 | return importedUris; |
22 | } | 29 | } |
30 | |||
31 | @Override | ||
32 | protected IScope createLazyResourceScope(IScope parent, URI uri, IResourceDescriptions descriptions, EClass type, | ||
33 | Predicate<IEObjectDescription> filter, boolean ignoreCase) { | ||
34 | ISelectable description = descriptions.getResourceDescription(uri); | ||
35 | if (description != null && ProblemUtil.BUILTIN_LIBRARY_URI.equals(uri)) { | ||
36 | description = NormalizedSelectable.of(description, QualifiedName.create("builtin"), QualifiedName.EMPTY); | ||
37 | } | ||
38 | return SelectableBasedScope.createScope(parent, description, filter, type, ignoreCase); | ||
39 | } | ||
23 | } | 40 | } |
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 229960a0..9be32636 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 | |||
@@ -1,47 +1,42 @@ | |||
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 | */ |
6 | package tools.refinery.language.scoping; | 6 | package tools.refinery.language.scoping; |
7 | 7 | ||
8 | import java.util.List; | 8 | import com.google.inject.Inject; |
9 | |||
10 | import org.eclipse.emf.ecore.EObject; | ||
11 | import org.eclipse.emf.ecore.resource.Resource; | 9 | import org.eclipse.emf.ecore.resource.Resource; |
10 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | ||
12 | import org.eclipse.xtext.naming.QualifiedName; | 11 | import org.eclipse.xtext.naming.QualifiedName; |
13 | import org.eclipse.xtext.resource.IResourceDescriptions; | ||
14 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | 12 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; |
15 | import org.eclipse.xtext.resource.ISelectable; | 13 | import org.eclipse.xtext.resource.ISelectable; |
16 | import org.eclipse.xtext.scoping.impl.ImportNormalizer; | 14 | import org.eclipse.xtext.scoping.impl.SimpleLocalScopeProvider; |
17 | import org.eclipse.xtext.scoping.impl.ImportedNamespaceAwareLocalScopeProvider; | 15 | import tools.refinery.language.naming.NamingUtil; |
18 | |||
19 | import com.google.inject.Inject; | ||
20 | |||
21 | import tools.refinery.language.utils.ProblemUtil; | ||
22 | 16 | ||
23 | public class ProblemLocalScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { | 17 | public class ProblemLocalScopeProvider extends SimpleLocalScopeProvider { |
24 | private static final QualifiedName BUILTIN_LIBRARY_QUALIFIED_NAME = QualifiedName | 18 | @Inject |
25 | .create(ProblemUtil.BUILTIN_LIBRARY_NAME); | 19 | private IQualifiedNameProvider qualifiedNameProvider; |
26 | 20 | ||
27 | @Inject | 21 | @Inject |
28 | private IResourceDescriptionsProvider resourceDescriptionsProvider; | 22 | private IResourceDescriptionsProvider resourceDescriptionsProvider; |
29 | 23 | ||
30 | @Override | 24 | @Override |
31 | protected List<ImportNormalizer> getImplicitImports(boolean ignoreCase) { | 25 | protected ISelectable getAllDescriptions(Resource resource) { |
32 | return List.of(doCreateImportNormalizer(BUILTIN_LIBRARY_QUALIFIED_NAME, true, ignoreCase)); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | protected List<ImportNormalizer> getImportedNamespaceResolvers(EObject context, boolean ignoreCase) { | ||
37 | return List.of(); | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | protected ISelectable internalGetAllDescriptions(Resource resource) { | ||
42 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. | 26 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. |
43 | IResourceDescriptions resourceDescriptions = resourceDescriptionsProvider | 27 | var resourceDescriptions = resourceDescriptionsProvider |
44 | .getResourceDescriptions(resource.getResourceSet()); | 28 | .getResourceDescriptions(resource.getResourceSet()); |
45 | return resourceDescriptions.getResourceDescription(resource.getURI()); | 29 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); |
30 | if (resourceDescription != null && !resource.getContents().isEmpty()) { | ||
31 | var rootElement = resource.getContents().getFirst(); | ||
32 | if (rootElement != null) { | ||
33 | var rootName = NamingUtil.stripRootPrefix(qualifiedNameProvider.getFullyQualifiedName(rootElement)); | ||
34 | if (rootName == null) { | ||
35 | return resourceDescription; | ||
36 | } | ||
37 | return NormalizedSelectable.of(resourceDescription, rootName, QualifiedName.EMPTY); | ||
38 | } | ||
39 | } | ||
40 | return resourceDescription; | ||
46 | } | 41 | } |
47 | } | 42 | } |