aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/generator
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-12-24 19:42:21 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-12-25 00:04:37 +0100
commit1f9b4652b1f85fc9f2cefcd46b34431ecea5c381 (patch)
tree84aba7f8ce90ff5aea38c912b3999ec7810f6f44 /subprojects/generator
parentfeat: command line model generator (diff)
downloadrefinery-1f9b4652b1f85fc9f2cefcd46b34431ecea5c381.tar.gz
refinery-1f9b4652b1f85fc9f2cefcd46b34431ecea5c381.tar.zst
refinery-1f9b4652b1f85fc9f2cefcd46b34431ecea5c381.zip
refactor(generator): scope overrides
Diffstat (limited to 'subprojects/generator')
-rw-r--r--subprojects/generator/build.gradle.kts1
-rw-r--r--subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java61
-rw-r--r--subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java129
3 files changed, 191 insertions, 0 deletions
diff --git a/subprojects/generator/build.gradle.kts b/subprojects/generator/build.gradle.kts
index d87ce6de..f78fee4d 100644
--- a/subprojects/generator/build.gradle.kts
+++ b/subprojects/generator/build.gradle.kts
@@ -12,4 +12,5 @@ dependencies {
12 api(project(":refinery-language-semantics")) 12 api(project(":refinery-language-semantics"))
13 api(libs.eclipseCollections.api) 13 api(libs.eclipseCollections.api)
14 implementation(project(":refinery-store-query-interpreter")) 14 implementation(project(":refinery-store-query-interpreter"))
15 testImplementation(testFixtures(project(":refinery-language")))
15} 16}
diff --git a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
index abfbe7e6..20ea8132 100644
--- a/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
+++ b/subprojects/generator/src/main/java/tools/refinery/generator/ProblemLoader.java
@@ -17,11 +17,18 @@ import org.eclipse.xtext.util.LazyStringInputStream;
17import org.eclipse.xtext.validation.CheckMode; 17import org.eclipse.xtext.validation.CheckMode;
18import org.eclipse.xtext.validation.IResourceValidator; 18import org.eclipse.xtext.validation.IResourceValidator;
19import tools.refinery.language.model.problem.Problem; 19import tools.refinery.language.model.problem.Problem;
20import tools.refinery.language.model.problem.Relation;
21import tools.refinery.language.model.problem.ScopeDeclaration;
20import tools.refinery.store.util.CancellationToken; 22import tools.refinery.store.util.CancellationToken;
21 23
24import java.io.ByteArrayOutputStream;
22import java.io.File; 25import java.io.File;
23import java.io.IOException; 26import java.io.IOException;
24import java.io.InputStream; 27import java.io.InputStream;
28import java.nio.charset.StandardCharsets;
29import java.util.ArrayList;
30import java.util.HashSet;
31import java.util.List;
25import java.util.Map; 32import java.util.Map;
26 33
27public class ProblemLoader { 34public class ProblemLoader {
@@ -104,4 +111,58 @@ public class ProblemLoader {
104 } 111 }
105 return problem; 112 return problem;
106 } 113 }
114
115 public Problem loadScopeConstraints(Problem problem, List<String> extraScopes,
116 List<String> overrideScopes) throws IOException {
117 var allScopes = new ArrayList<>(extraScopes);
118 allScopes.addAll(overrideScopes);
119 if (allScopes.isEmpty()) {
120 return problem;
121 }
122 int originalStatementCount = problem.getStatements().size();
123 var builder = new StringBuilder();
124 var problemResource = problem.eResource();
125 try (var outputStream = new ByteArrayOutputStream()) {
126 problemResource.save(outputStream, Map.of());
127 builder.append(outputStream.toString(StandardCharsets.UTF_8));
128 }
129 builder.append('\n');
130 for (var scope : allScopes) {
131 builder.append("scope ").append(scope).append(".\n");
132 }
133 var modifiedProblem = loadString(builder.toString(), problemResource.getURI());
134 var modifiedStatements = modifiedProblem.getStatements();
135 int modifiedStatementCount = modifiedStatements.size();
136 if (modifiedStatementCount != originalStatementCount + allScopes.size()) {
137 throw new IllegalArgumentException("Failed to parse scope constraints");
138 }
139 // Override scopes remove any scope constraint from the original problem with the same target type.
140 var overriddenScopes = new HashSet<Relation>();
141 for (int i = modifiedStatementCount - overrideScopes.size(); i < modifiedStatementCount; i++) {
142 var statement = modifiedStatements.get(i);
143 if (!(statement instanceof ScopeDeclaration scopeDeclaration)) {
144 throw new IllegalStateException("Invalid scope constraint: " + statement);
145 }
146 for (var typeScope : scopeDeclaration.getTypeScopes()) {
147 overriddenScopes.add(typeScope.getTargetType());
148 }
149 }
150 int statementIndex = 0;
151 var iterator = modifiedStatements.iterator();
152 // Scope overrides only affect type scopes from the original problem and leave type scopes added on the
153 // command line intact.
154 while (statementIndex < originalStatementCount && iterator.hasNext()) {
155 var statement = iterator.next();
156 if (statement instanceof ScopeDeclaration scopeDeclaration) {
157 var typeScopes = scopeDeclaration.getTypeScopes();
158 typeScopes.removeIf(typeScope -> overriddenScopes.contains(typeScope.getTargetType()));
159 // Scope declarations with no type scopes are invalid, so we have to remove them.
160 if (typeScopes.isEmpty()) {
161 iterator.remove();
162 }
163 }
164 statementIndex++;
165 }
166 return modifiedProblem;
167 }
107} 168}
diff --git a/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java b/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java
new file mode 100644
index 00000000..0c0db105
--- /dev/null
+++ b/subprojects/generator/src/test/java/tools/refinery/generator/ProblemLoaderTest.java
@@ -0,0 +1,129 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.generator;
7
8
9import com.google.inject.Inject;
10import org.eclipse.xtext.testing.InjectWith;
11import org.eclipse.xtext.testing.extensions.InjectionExtension;
12import org.junit.jupiter.api.extension.ExtendWith;
13import org.junit.jupiter.params.ParameterizedTest;
14import org.junit.jupiter.params.provider.Arguments;
15import org.junit.jupiter.params.provider.MethodSource;
16import tools.refinery.language.tests.ProblemInjectorProvider;
17
18import java.io.ByteArrayOutputStream;
19import java.io.IOException;
20import java.nio.charset.StandardCharsets;
21import java.util.List;
22import java.util.Map;
23import java.util.stream.Stream;
24
25import static org.hamcrest.MatcherAssert.assertThat;
26import static org.hamcrest.Matchers.is;
27
28@ExtendWith(InjectionExtension.class)
29@InjectWith(ProblemInjectorProvider.class)
30class ProblemLoaderTest {
31 private static final String PREFIX = """
32 class Foo.
33 class Bar.
34 """;
35
36 @Inject
37 private ProblemLoader loader;
38
39 @ParameterizedTest
40 @MethodSource
41 void loadScopeConstraintsTest(String originalScopes, List<String> scopes, List<String> overrideScopes,
42 String expectedScopes) throws IOException {
43 var problem = loader.loadString(PREFIX + originalScopes);
44 var modifiedProblem = loader.loadScopeConstraints(problem, scopes, overrideScopes);
45 String serializedProblem;
46 try (var outputStream = new ByteArrayOutputStream()) {
47 modifiedProblem.eResource().save(outputStream, Map.of());
48 serializedProblem = outputStream.toString(StandardCharsets.UTF_8);
49 }
50 assertThat(serializedProblem, is(PREFIX + expectedScopes));
51 }
52
53 static Stream<Arguments> loadScopeConstraintsTest() {
54 return Stream.of(Arguments.of("",
55 List.of(),
56 List.of(),
57 ""), Arguments.of("",
58 List.of("node=5..10"),
59 List.of(), """
60
61 scope node=5..10.
62 """), Arguments.of("",
63 List.of("Foo=2", "Bar=3"),
64 List.of(), """
65
66 scope Foo=2.
67 scope Bar=3.
68 """), Arguments.of("""
69 scope Foo = 1, Bar = 1.
70 """,
71 List.of("node=5..10"),
72 List.of(), """
73 scope Foo = 1, Bar = 1.
74
75 scope node=5..10.
76 """), Arguments.of("""
77 scope Foo = 0..10, Bar = 1.
78 """,
79 List.of("Foo = 5"),
80 List.of(), """
81 scope Foo = 0..10, Bar = 1.
82
83 scope Foo = 5.
84 """), Arguments.of("""
85 scope Foo = 1, Bar = 1.
86 """,
87 List.of(),
88 List.of("node=5..10"), """
89 scope Foo = 1, Bar = 1.
90
91 scope node=5..10.
92 """), Arguments.of("""
93 scope Foo = 1, Bar = 1.
94 """,
95 List.of(),
96 List.of("Foo=3..4"), """
97 scope Bar = 1.
98
99 scope Foo=3..4.
100 """), Arguments.of("""
101 scope Foo = 1, Bar = 1.
102 """,
103 List.of("Foo=2"),
104 List.of("Foo=3..4"), """
105 scope Bar = 1.
106
107 scope Foo=2.
108 scope Foo=3..4.
109 """), Arguments.of("""
110 scope Foo = 1.
111 scope Bar = 1.
112 """,
113 List.of(),
114 List.of("Bar=3..4"), """
115 scope Foo = 1.
116
117
118 scope Bar=3..4.
119 """), Arguments.of("""
120 scope Foo = 1, Bar = 1.
121 """,
122 List.of(),
123 List.of("Foo=3..4", "Bar=4..5"), """
124
125 scope Foo=3..4.
126 scope Bar=4..5.
127 """));
128 }
129}