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 | |
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')
24 files changed, 779 insertions, 170 deletions
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java index 7c649232..c2bca2a5 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java | |||
@@ -6,7 +6,9 @@ | |||
6 | package tools.refinery.language.semantics; | 6 | package tools.refinery.language.semantics; |
7 | 7 | ||
8 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
9 | import tools.refinery.language.library.BuiltinLibrary; | ||
9 | import tools.refinery.language.model.problem.*; | 10 | import tools.refinery.language.model.problem.*; |
11 | import tools.refinery.language.scoping.imports.ImportCollector; | ||
10 | import tools.refinery.language.semantics.internal.MutableSeed; | 12 | import tools.refinery.language.semantics.internal.MutableSeed; |
11 | import tools.refinery.language.utils.BuiltinSymbols; | 13 | import tools.refinery.language.utils.BuiltinSymbols; |
12 | import tools.refinery.language.utils.ProblemDesugarer; | 14 | import tools.refinery.language.utils.ProblemDesugarer; |
@@ -51,8 +53,13 @@ public class ModelInitializer { | |||
51 | @Inject | 53 | @Inject |
52 | private ProblemTraceImpl problemTrace; | 54 | private ProblemTraceImpl problemTrace; |
53 | 55 | ||
56 | @Inject | ||
57 | private ImportCollector importCollector; | ||
58 | |||
54 | private Problem problem; | 59 | private Problem problem; |
55 | 60 | ||
61 | private final Set<Problem> importedProblems = new HashSet<>(); | ||
62 | |||
56 | private BuiltinSymbols builtinSymbols; | 63 | private BuiltinSymbols builtinSymbols; |
57 | 64 | ||
58 | private PartialRelation nodeRelation; | 65 | private PartialRelation nodeRelation; |
@@ -82,6 +89,8 @@ public class ModelInitializer { | |||
82 | throw new IllegalArgumentException("Problem was already set"); | 89 | throw new IllegalArgumentException("Problem was already set"); |
83 | } | 90 | } |
84 | this.problem = problem; | 91 | this.problem = problem; |
92 | loadImportedProblems(); | ||
93 | importedProblems.add(problem); | ||
85 | problemTrace.setProblem(problem); | 94 | problemTrace.setProblem(problem); |
86 | try { | 95 | try { |
87 | builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalArgumentException( | 96 | builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalArgumentException( |
@@ -128,6 +137,29 @@ public class ModelInitializer { | |||
128 | } | 137 | } |
129 | } | 138 | } |
130 | 139 | ||
140 | private void loadImportedProblems() { | ||
141 | var resource = problem.eResource(); | ||
142 | if (resource == null) { | ||
143 | return; | ||
144 | } | ||
145 | var resourceSet = resource.getResourceSet(); | ||
146 | if (resourceSet == null) { | ||
147 | return; | ||
148 | } | ||
149 | var importedUris = importCollector.getAllImports(resource).toUriSet(); | ||
150 | for (var uri : importedUris) { | ||
151 | if (BuiltinLibrary.BUILTIN_LIBRARY_URI.equals(uri)) { | ||
152 | // We hard-code the behavior of the builtin library. | ||
153 | continue; | ||
154 | } | ||
155 | var importedResource = resourceSet.getResource(uri, false); | ||
156 | if (importedResource != null && !importedResource.getContents().isEmpty() && | ||
157 | importedResource.getContents().getFirst() instanceof Problem importedProblem) { | ||
158 | importedProblems.add(importedProblem); | ||
159 | } | ||
160 | } | ||
161 | } | ||
162 | |||
131 | public void configureStoreBuilder(ModelStoreBuilder storeBuilder) { | 163 | public void configureStoreBuilder(ModelStoreBuilder storeBuilder) { |
132 | checkProblem(); | 164 | checkProblem(); |
133 | try { | 165 | try { |
@@ -168,19 +200,21 @@ public class ModelInitializer { | |||
168 | } | 200 | } |
169 | 201 | ||
170 | private void collectNodes() { | 202 | private void collectNodes() { |
171 | for (var statement : problem.getStatements()) { | 203 | for (var importedProblem : importedProblems) { |
172 | if (statement instanceof NodeDeclaration nodeDeclaration) { | 204 | for (var statement : importedProblem.getStatements()) { |
173 | for (var node : nodeDeclaration.getNodes()) { | 205 | if (statement instanceof NodeDeclaration nodeDeclaration) { |
174 | collectNode(node); | 206 | for (var node : nodeDeclaration.getNodes()) { |
175 | } | 207 | collectNode(node); |
176 | } else if (statement instanceof ClassDeclaration classDeclaration) { | 208 | } |
177 | var newNode = classDeclaration.getNewNode(); | 209 | } else if (statement instanceof ClassDeclaration classDeclaration) { |
178 | if (newNode != null) { | 210 | var newNode = classDeclaration.getNewNode(); |
179 | collectNode(newNode); | 211 | if (newNode != null) { |
180 | } | 212 | collectNode(newNode); |
181 | } else if (statement instanceof EnumDeclaration enumDeclaration) { | 213 | } |
182 | for (var literal : enumDeclaration.getLiterals()) { | 214 | } else if (statement instanceof EnumDeclaration enumDeclaration) { |
183 | collectNode(literal); | 215 | for (var literal : enumDeclaration.getLiterals()) { |
216 | collectNode(literal); | ||
217 | } | ||
184 | } | 218 | } |
185 | } | 219 | } |
186 | } | 220 | } |
@@ -194,13 +228,15 @@ public class ModelInitializer { | |||
194 | } | 228 | } |
195 | 229 | ||
196 | private void collectPartialSymbols() { | 230 | private void collectPartialSymbols() { |
197 | for (var statement : problem.getStatements()) { | 231 | for (var importedProblem : importedProblems) { |
198 | if (statement instanceof ClassDeclaration classDeclaration) { | 232 | for (var statement : importedProblem.getStatements()) { |
199 | collectClassDeclarationSymbols(classDeclaration); | 233 | if (statement instanceof ClassDeclaration classDeclaration) { |
200 | } else if (statement instanceof EnumDeclaration enumDeclaration) { | 234 | collectClassDeclarationSymbols(classDeclaration); |
201 | collectPartialRelation(enumDeclaration, 1, TruthValue.FALSE, TruthValue.FALSE); | 235 | } else if (statement instanceof EnumDeclaration enumDeclaration) { |
202 | } else if (statement instanceof PredicateDefinition predicateDefinition) { | 236 | collectPartialRelation(enumDeclaration, 1, TruthValue.FALSE, TruthValue.FALSE); |
203 | collectPredicateDefinitionSymbol(predicateDefinition); | 237 | } else if (statement instanceof PredicateDefinition predicateDefinition) { |
238 | collectPredicateDefinitionSymbol(predicateDefinition); | ||
239 | } | ||
204 | } | 240 | } |
205 | } | 241 | } |
206 | } | 242 | } |
@@ -251,11 +287,13 @@ public class ModelInitializer { | |||
251 | } | 287 | } |
252 | 288 | ||
253 | private void collectMetamodel() { | 289 | private void collectMetamodel() { |
254 | for (var statement : problem.getStatements()) { | 290 | for (var importedProblem : importedProblems) { |
255 | if (statement instanceof ClassDeclaration classDeclaration) { | 291 | for (var statement : importedProblem.getStatements()) { |
256 | collectClassDeclarationMetamodel(classDeclaration); | 292 | if (statement instanceof ClassDeclaration classDeclaration) { |
257 | } else if (statement instanceof EnumDeclaration enumDeclaration) { | 293 | collectClassDeclarationMetamodel(classDeclaration); |
258 | collectEnumMetamodel(enumDeclaration); | 294 | } else if (statement instanceof EnumDeclaration enumDeclaration) { |
295 | collectEnumMetamodel(enumDeclaration); | ||
296 | } | ||
259 | } | 297 | } |
260 | } | 298 | } |
261 | } | 299 | } |
@@ -350,15 +388,17 @@ public class ModelInitializer { | |||
350 | } | 388 | } |
351 | 389 | ||
352 | private void collectAssertions() { | 390 | private void collectAssertions() { |
353 | for (var statement : problem.getStatements()) { | 391 | for (var importedProblem : importedProblems) { |
354 | if (statement instanceof ClassDeclaration classDeclaration) { | 392 | for (var statement : importedProblem.getStatements()) { |
355 | collectClassDeclarationAssertions(classDeclaration); | 393 | if (statement instanceof ClassDeclaration classDeclaration) { |
356 | } else if (statement instanceof EnumDeclaration enumDeclaration) { | 394 | collectClassDeclarationAssertions(classDeclaration); |
357 | collectEnumAssertions(enumDeclaration); | 395 | } else if (statement instanceof EnumDeclaration enumDeclaration) { |
358 | } else if (statement instanceof NodeDeclaration nodeDeclaration) { | 396 | collectEnumAssertions(enumDeclaration); |
359 | collectNodeDeclarationAssertions(nodeDeclaration); | 397 | } else if (statement instanceof NodeDeclaration nodeDeclaration) { |
360 | } else if (statement instanceof Assertion assertion) { | 398 | collectNodeDeclarationAssertions(nodeDeclaration); |
361 | collectAssertion(assertion); | 399 | } else if (statement instanceof Assertion assertion) { |
400 | collectAssertion(assertion); | ||
401 | } | ||
362 | } | 402 | } |
363 | } | 403 | } |
364 | } | 404 | } |
@@ -429,9 +469,11 @@ public class ModelInitializer { | |||
429 | } | 469 | } |
430 | 470 | ||
431 | private void fixClassDeclarationAssertions() { | 471 | private void fixClassDeclarationAssertions() { |
432 | for (var statement : problem.getStatements()) { | 472 | for (var importedProblem : importedProblems) { |
433 | if (statement instanceof ClassDeclaration classDeclaration) { | 473 | for (var statement : importedProblem.getStatements()) { |
434 | fixClassDeclarationAssertions(classDeclaration); | 474 | if (statement instanceof ClassDeclaration classDeclaration) { |
475 | fixClassDeclarationAssertions(classDeclaration); | ||
476 | } | ||
435 | } | 477 | } |
436 | } | 478 | } |
437 | } | 479 | } |
@@ -499,9 +541,11 @@ public class ModelInitializer { | |||
499 | } | 541 | } |
500 | 542 | ||
501 | private void collectPredicates(ModelStoreBuilder storeBuilder) { | 543 | private void collectPredicates(ModelStoreBuilder storeBuilder) { |
502 | for (var statement : problem.getStatements()) { | 544 | for (var importedProblem : importedProblems) { |
503 | if (statement instanceof PredicateDefinition predicateDefinition) { | 545 | for (var statement : importedProblem.getStatements()) { |
504 | collectPredicateDefinitionTraced(predicateDefinition, storeBuilder); | 546 | if (statement instanceof PredicateDefinition predicateDefinition) { |
547 | collectPredicateDefinitionTraced(predicateDefinition, storeBuilder); | ||
548 | } | ||
505 | } | 549 | } |
506 | } | 550 | } |
507 | } | 551 | } |
@@ -601,51 +645,51 @@ public class ModelInitializer { | |||
601 | 645 | ||
602 | private void toLiterals(Expr expr, Map<tools.refinery.language.model.problem.Variable, Variable> localScope, | 646 | private void toLiterals(Expr expr, Map<tools.refinery.language.model.problem.Variable, Variable> localScope, |
603 | List<Literal> literals) { | 647 | List<Literal> literals) { |
604 | switch (expr) { | 648 | switch (expr) { |
605 | case LogicConstant logicConstant -> { | 649 | case LogicConstant logicConstant -> { |
606 | switch (logicConstant.getLogicValue()) { | 650 | switch (logicConstant.getLogicValue()) { |
607 | case TRUE -> literals.add(BooleanLiteral.TRUE); | 651 | case TRUE -> literals.add(BooleanLiteral.TRUE); |
608 | case FALSE -> literals.add(BooleanLiteral.FALSE); | 652 | case FALSE -> literals.add(BooleanLiteral.FALSE); |
609 | default -> throw new TracedException(logicConstant, "Unsupported literal"); | 653 | default -> throw new TracedException(logicConstant, "Unsupported literal"); |
610 | } | 654 | } |
611 | } | 655 | } |
612 | case Atom atom -> { | 656 | case Atom atom -> { |
613 | var target = getPartialRelation(atom.getRelation()); | 657 | var target = getPartialRelation(atom.getRelation()); |
614 | var polarity = atom.isTransitiveClosure() ? CallPolarity.TRANSITIVE : CallPolarity.POSITIVE; | 658 | var polarity = atom.isTransitiveClosure() ? CallPolarity.TRANSITIVE : CallPolarity.POSITIVE; |
615 | var argumentList = toArgumentList(atom.getArguments(), localScope, literals); | 659 | var argumentList = toArgumentList(atom.getArguments(), localScope, literals); |
616 | literals.add(target.call(polarity, argumentList)); | 660 | literals.add(target.call(polarity, argumentList)); |
617 | } | 661 | } |
618 | case NegationExpr negationExpr -> { | 662 | case NegationExpr negationExpr -> { |
619 | var body = negationExpr.getBody(); | 663 | var body = negationExpr.getBody(); |
620 | if (!(body instanceof Atom atom)) { | 664 | if (!(body instanceof Atom atom)) { |
621 | throw new TracedException(body, "Cannot negate literal"); | 665 | throw new TracedException(body, "Cannot negate literal"); |
622 | } | 666 | } |
623 | var target = getPartialRelation(atom.getRelation()); | 667 | var target = getPartialRelation(atom.getRelation()); |
624 | Constraint constraint; | 668 | Constraint constraint; |
625 | if (atom.isTransitiveClosure()) { | 669 | if (atom.isTransitiveClosure()) { |
626 | constraint = Query.of(target.name() + "#transitive", (builder, p1, p2) -> builder.clause( | 670 | constraint = Query.of(target.name() + "#transitive", (builder, p1, p2) -> builder.clause( |
627 | target.callTransitive(p1, p2) | 671 | target.callTransitive(p1, p2) |
628 | )).getDnf(); | 672 | )).getDnf(); |
629 | } else { | 673 | } else { |
630 | constraint = target; | 674 | constraint = target; |
631 | } | 675 | } |
632 | var negatedScope = extendScope(localScope, negationExpr.getImplicitVariables()); | 676 | var negatedScope = extendScope(localScope, negationExpr.getImplicitVariables()); |
633 | var argumentList = toArgumentList(atom.getArguments(), negatedScope, literals); | 677 | var argumentList = toArgumentList(atom.getArguments(), negatedScope, literals); |
634 | literals.add(constraint.call(CallPolarity.NEGATIVE, argumentList)); | 678 | literals.add(constraint.call(CallPolarity.NEGATIVE, argumentList)); |
635 | } | 679 | } |
636 | case ComparisonExpr comparisonExpr -> { | 680 | case ComparisonExpr comparisonExpr -> { |
637 | var argumentList = toArgumentList(List.of(comparisonExpr.getLeft(), comparisonExpr.getRight()), | 681 | var argumentList = toArgumentList(List.of(comparisonExpr.getLeft(), comparisonExpr.getRight()), |
638 | localScope, literals); | 682 | localScope, literals); |
639 | boolean positive = switch (comparisonExpr.getOp()) { | 683 | boolean positive = switch (comparisonExpr.getOp()) { |
640 | case EQ -> true; | 684 | case EQ -> true; |
641 | case NOT_EQ -> false; | 685 | case NOT_EQ -> false; |
642 | default -> throw new TracedException( | 686 | default -> throw new TracedException( |
643 | comparisonExpr, "Unsupported operator"); | 687 | comparisonExpr, "Unsupported operator"); |
644 | }; | 688 | }; |
645 | literals.add(new EquivalenceLiteral(positive, argumentList.get(0), argumentList.get(1))); | 689 | literals.add(new EquivalenceLiteral(positive, argumentList.get(0), argumentList.get(1))); |
646 | } | 690 | } |
647 | default -> throw new TracedException(expr, "Unsupported literal"); | 691 | default -> throw new TracedException(expr, "Unsupported literal"); |
648 | } | 692 | } |
649 | } | 693 | } |
650 | 694 | ||
651 | private List<Variable> toArgumentList( | 695 | private List<Variable> toArgumentList( |
@@ -680,13 +724,15 @@ public class ModelInitializer { | |||
680 | } | 724 | } |
681 | 725 | ||
682 | private void collectScopes() { | 726 | private void collectScopes() { |
683 | for (var statement : problem.getStatements()) { | 727 | for (var importedProblem : importedProblems) { |
684 | if (statement instanceof ScopeDeclaration scopeDeclaration) { | 728 | for (var statement : importedProblem.getStatements()) { |
685 | for (var typeScope : scopeDeclaration.getTypeScopes()) { | 729 | if (statement instanceof ScopeDeclaration scopeDeclaration) { |
686 | if (typeScope.isIncrement()) { | 730 | for (var typeScope : scopeDeclaration.getTypeScopes()) { |
687 | collectTypeScopeIncrement(typeScope); | 731 | if (typeScope.isIncrement()) { |
688 | } else { | 732 | collectTypeScopeIncrement(typeScope); |
689 | collectTypeScope(typeScope); | 733 | } else { |
734 | collectTypeScope(typeScope); | ||
735 | } | ||
690 | } | 736 | } |
691 | } | 737 | } |
692 | } | 738 | } |
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 | |||