diff options
Diffstat (limited to 'subprojects/language/src')
4 files changed, 96 insertions, 90 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java new file mode 100644 index 00000000..f9405fc1 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/NoFullyQualifiedNamesSelectable.java | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.language.scoping; | ||
7 | |||
8 | import com.google.common.base.Predicate; | ||
9 | import com.google.common.collect.Iterables; | ||
10 | import org.eclipse.emf.ecore.EClass; | ||
11 | import org.eclipse.emf.ecore.EObject; | ||
12 | import org.eclipse.xtext.naming.QualifiedName; | ||
13 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
14 | import org.eclipse.xtext.resource.ISelectable; | ||
15 | import tools.refinery.language.naming.NamingUtil; | ||
16 | |||
17 | import java.util.List; | ||
18 | |||
19 | public class NoFullyQualifiedNamesSelectable implements ISelectable { | ||
20 | private final ISelectable delegateSelectable; | ||
21 | |||
22 | // {@link com.google.common.base.Predicate} required by Xtext API. | ||
23 | @SuppressWarnings("squid:S4738") | ||
24 | private final Predicate<IEObjectDescription> filter = | ||
25 | eObjectDescription -> !NamingUtil.isFullyQualified(eObjectDescription.getName()); | ||
26 | |||
27 | public NoFullyQualifiedNamesSelectable(ISelectable delegateSelectable) { | ||
28 | this.delegateSelectable = delegateSelectable; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public boolean isEmpty() { | ||
33 | return delegateSelectable.isEmpty(); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public Iterable<IEObjectDescription> getExportedObjects() { | ||
38 | return filter(delegateSelectable.getExportedObjects()); | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public Iterable<IEObjectDescription> getExportedObjects(EClass type, QualifiedName name, boolean ignoreCase) { | ||
43 | if (NamingUtil.isFullyQualified(name)) { | ||
44 | return List.of(); | ||
45 | } | ||
46 | return delegateSelectable.getExportedObjects(type, name, ignoreCase); | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public Iterable<IEObjectDescription> getExportedObjectsByType(EClass type) { | ||
51 | return filter(delegateSelectable.getExportedObjectsByType(type)); | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public Iterable<IEObjectDescription> getExportedObjectsByObject(EObject object) { | ||
56 | return filter(delegateSelectable.getExportedObjectsByObject(object)); | ||
57 | } | ||
58 | |||
59 | private Iterable<IEObjectDescription> filter(Iterable<IEObjectDescription> eObjectDescriptions) { | ||
60 | return Iterables.filter(eObjectDescriptions, filter); | ||
61 | } | ||
62 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java index 1c0c1d86..0067bf94 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java | |||
@@ -12,7 +12,6 @@ import org.eclipse.emf.ecore.EReference; | |||
12 | import org.eclipse.emf.ecore.resource.Resource; | 12 | import org.eclipse.emf.ecore.resource.Resource; |
13 | import org.eclipse.xtext.naming.IQualifiedNameProvider; | 13 | import org.eclipse.xtext.naming.IQualifiedNameProvider; |
14 | import org.eclipse.xtext.naming.QualifiedName; | 14 | import org.eclipse.xtext.naming.QualifiedName; |
15 | import org.eclipse.xtext.resource.IResourceDescription; | ||
16 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | 15 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; |
17 | import org.eclipse.xtext.resource.ISelectable; | 16 | import org.eclipse.xtext.resource.ISelectable; |
18 | import org.eclipse.xtext.scoping.IScope; | 17 | import org.eclipse.xtext.scoping.IScope; |
@@ -39,41 +38,31 @@ public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScop | |||
39 | if (resource == null) { | 38 | if (resource == null) { |
40 | return IScope.NULLSCOPE; | 39 | return IScope.NULLSCOPE; |
41 | } | 40 | } |
41 | var globalScope = getGlobalScope(resource, reference); | ||
42 | var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource)); | 42 | var localImports = cache.get(CACHE_KEY, resource, () -> computeLocalImports(resource)); |
43 | if (localImports.resourceDescription() == null) { | 43 | if (localImports == null) { |
44 | return IScope.NULLSCOPE; | 44 | return globalScope; |
45 | } | 45 | } |
46 | var globalScope = getGlobalScope(resource, reference); | ||
47 | var type = reference.getEReferenceType(); | 46 | var type = reference.getEReferenceType(); |
48 | boolean ignoreCase = isIgnoreCase(reference); | 47 | boolean ignoreCase = isIgnoreCase(reference); |
49 | var scope = ShadowingKeyAwareSelectableBasedScope.createScope(globalScope, localImports.resourceDescription(), | 48 | return ShadowingKeyAwareSelectableBasedScope.createScope(globalScope, localImports, type, ignoreCase); |
50 | type, ignoreCase); | ||
51 | if (localImports.normalizedSelectable() == null) { | ||
52 | return scope; | ||
53 | } | ||
54 | return ShadowingKeyAwareSelectableBasedScope.createScope(scope, localImports.normalizedSelectable(), type, | ||
55 | ignoreCase); | ||
56 | } | 49 | } |
57 | 50 | ||
58 | protected LocalImports computeLocalImports(Resource resource) { | 51 | protected ISelectable computeLocalImports(Resource resource) { |
59 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. | 52 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. |
60 | var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet()); | 53 | var resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(resource.getResourceSet()); |
61 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); | 54 | var resourceDescription = resourceDescriptions.getResourceDescription(resource.getURI()); |
62 | if (resourceDescription == null) { | 55 | if (resourceDescription == null) { |
63 | return new LocalImports(null, null); | 56 | return null; |
64 | } | 57 | } |
65 | var rootElement = resource.getContents().getFirst(); | 58 | var rootElement = resource.getContents().getFirst(); |
66 | if (rootElement == null) { | 59 | if (rootElement == null) { |
67 | return new LocalImports(resourceDescription, null); | 60 | return new NoFullyQualifiedNamesSelectable(resourceDescription); |
68 | } | 61 | } |
69 | var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement); | 62 | var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement); |
70 | if (rootName == null) { | 63 | if (rootName == null) { |
71 | return new LocalImports(resourceDescription, null); | 64 | return new NoFullyQualifiedNamesSelectable(resourceDescription); |
72 | } | 65 | } |
73 | var normalizedSelectable = new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY); | 66 | return new NormalizedSelectable(resourceDescription, rootName, QualifiedName.EMPTY); |
74 | return new LocalImports(resourceDescription, normalizedSelectable); | ||
75 | } | ||
76 | |||
77 | protected record LocalImports(IResourceDescription resourceDescription, ISelectable normalizedSelectable) { | ||
78 | } | 67 | } |
79 | } | 68 | } |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java index 5a8f7fd7..d7a5304f 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java | |||
@@ -12,7 +12,6 @@ import org.apache.log4j.Logger; | |||
12 | import org.eclipse.emf.common.notify.Notification; | 12 | import org.eclipse.emf.common.notify.Notification; |
13 | import org.eclipse.emf.common.notify.impl.AdapterImpl; | 13 | import org.eclipse.emf.common.notify.impl.AdapterImpl; |
14 | import org.eclipse.emf.common.util.URI; | 14 | import org.eclipse.emf.common.util.URI; |
15 | import org.eclipse.emf.ecore.EObject; | ||
16 | import org.eclipse.emf.ecore.resource.Resource; | 15 | import org.eclipse.emf.ecore.resource.Resource; |
17 | import org.eclipse.emf.ecore.resource.ResourceSet; | 16 | import org.eclipse.emf.ecore.resource.ResourceSet; |
18 | import org.eclipse.emf.ecore.util.EcoreUtil; | 17 | import org.eclipse.emf.ecore.util.EcoreUtil; |
@@ -203,28 +202,4 @@ public class ImportAdapter extends AdapterImpl { | |||
203 | private static ImportAdapter getAdapter(ResourceSet resourceSet) { | 202 | private static ImportAdapter getAdapter(ResourceSet resourceSet) { |
204 | return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class); | 203 | return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class); |
205 | } | 204 | } |
206 | |||
207 | public static void copySettings(EObject context, ResourceSet newResourceSet) { | ||
208 | var resource = context.eResource(); | ||
209 | if (resource == null) { | ||
210 | return; | ||
211 | } | ||
212 | var originalResourceSet = resource.getResourceSet(); | ||
213 | if (originalResourceSet == null) { | ||
214 | return; | ||
215 | } | ||
216 | copySettings(originalResourceSet, newResourceSet); | ||
217 | } | ||
218 | |||
219 | public static void copySettings(ResourceSet originalResourceSet, ResourceSet newResourceSet) { | ||
220 | var originalAdapter = getAdapter(originalResourceSet); | ||
221 | if (originalAdapter == null) { | ||
222 | return; | ||
223 | } | ||
224 | var newAdapter = getOrInstall(newResourceSet); | ||
225 | newAdapter.libraries.clear(); | ||
226 | newAdapter.libraries.addAll(originalAdapter.libraries); | ||
227 | newAdapter.libraryPaths.clear(); | ||
228 | newAdapter.libraryPaths.addAll(originalAdapter.libraryPaths); | ||
229 | } | ||
230 | } | 205 | } |
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java index bc0320a6..0704e026 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -68,15 +68,14 @@ class NodeScopingTest { | |||
68 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.node("b"))); | 68 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.node("b"))); |
69 | } | 69 | } |
70 | 70 | ||
71 | @ParameterizedTest | 71 | @Test |
72 | @MethodSource("atomNodeReferenceSource") | 72 | void atomNodeInAssertionTest() { |
73 | void atomNodeInAssertionTest(String qualifiedNamePrefix, boolean namedProblem) { | ||
74 | var problem = parse(""" | 73 | var problem = parse(""" |
75 | atom a, b. | 74 | atom a, b. |
76 | pred predicate(node x, node y) <-> node(x). | 75 | pred predicate(node x, node y) <-> node(x). |
77 | predicate({PARAM}a, {PARAM}a). | 76 | predicate(a, a). |
78 | ?predicate({PARAM}a, {PARAM}b). | 77 | ?predicate(a, b). |
79 | """, qualifiedNamePrefix, namedProblem); | 78 | """); |
80 | assertThat(problem.getResourceErrors(), empty()); | 79 | assertThat(problem.getResourceErrors(), empty()); |
81 | assertThat(problem.nodeNames(), empty()); | 80 | assertThat(problem.nodeNames(), empty()); |
82 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.atomNode("a"))); | 81 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.atomNode("a"))); |
@@ -85,22 +84,17 @@ class NodeScopingTest { | |||
85 | assertThat(problem.assertion(1).arg(1).node(), equalTo(problem.atomNode("b"))); | 84 | assertThat(problem.assertion(1).arg(1).node(), equalTo(problem.atomNode("b"))); |
86 | } | 85 | } |
87 | 86 | ||
88 | @ParameterizedTest | 87 | @Test |
89 | @MethodSource("atomNodeReferenceSource") | 88 | void atomNodeInPredicateTest() { |
90 | void atomNodeInPredicateTest(String qualifiedNamePrefix, boolean namedProblem) { | ||
91 | var problem = parse(""" | 89 | var problem = parse(""" |
92 | atom b. | 90 | atom b. |
93 | pred predicate(node a) <-> node({PARAM}b). | 91 | pred predicate(node a) <-> node(b). |
94 | """, qualifiedNamePrefix, namedProblem); | 92 | """); |
95 | assertThat(problem.getResourceErrors(), empty()); | 93 | assertThat(problem.getResourceErrors(), empty()); |
96 | assertThat(problem.nodeNames(), empty()); | 94 | assertThat(problem.nodeNames(), empty()); |
97 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), equalTo(problem.atomNode("b"))); | 95 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), equalTo(problem.atomNode("b"))); |
98 | } | 96 | } |
99 | 97 | ||
100 | static Stream<Arguments> atomNodeReferenceSource() { | ||
101 | return Stream.of(Arguments.of("", false), Arguments.of("", true), Arguments.of("test::", true)); | ||
102 | } | ||
103 | |||
104 | @Disabled("No nodes are present in builtin.problem currently") | 98 | @Disabled("No nodes are present in builtin.problem currently") |
105 | @ParameterizedTest | 99 | @ParameterizedTest |
106 | @MethodSource("builtInNodeReferencesSource") | 100 | @MethodSource("builtInNodeReferencesSource") |
@@ -131,37 +125,30 @@ class NodeScopingTest { | |||
131 | return Stream.of(Arguments.of("int::new"), Arguments.of("builtin::int::new")); | 125 | return Stream.of(Arguments.of("int::new"), Arguments.of("builtin::int::new")); |
132 | } | 126 | } |
133 | 127 | ||
134 | @ParameterizedTest | 128 | @Test |
135 | @MethodSource("classNewNodeReferencesSource") | 129 | void classNewNodeTest() { |
136 | void classNewNodeTest(String qualifiedName, boolean namedProblem) { | ||
137 | var problem = parse(""" | 130 | var problem = parse(""" |
138 | class Foo. | 131 | class Foo. |
139 | pred predicate(node x) <-> node(x). | 132 | pred predicate(node x) <-> node(x). |
140 | predicate({PARAM}). | 133 | predicate(Foo::new). |
141 | """, qualifiedName, namedProblem); | 134 | """); |
142 | assertThat(problem.getResourceErrors(), empty()); | 135 | assertThat(problem.getResourceErrors(), empty()); |
143 | assertThat(problem.nodeNames(), empty()); | 136 | assertThat(problem.nodeNames(), empty()); |
144 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findClass("Foo").get().getNewNode())); | 137 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findClass("Foo").get().getNewNode())); |
145 | } | 138 | } |
146 | 139 | ||
147 | @ParameterizedTest | 140 | @Test |
148 | @MethodSource("classNewNodeReferencesSource") | 141 | void classNewNodeInPredicateTest() { |
149 | void classNewNodeInPredicateTest(String qualifiedName, boolean namedProblem) { | ||
150 | var problem = parse(""" | 142 | var problem = parse(""" |
151 | class Foo. | 143 | class Foo. |
152 | pred predicate(node x) <-> node({PARAM}). | 144 | pred predicate(node x) <-> node(Foo::new). |
153 | """, qualifiedName, namedProblem); | 145 | """); |
154 | assertThat(problem.getResourceErrors(), empty()); | 146 | assertThat(problem.getResourceErrors(), empty()); |
155 | assertThat(problem.nodeNames(), empty()); | 147 | assertThat(problem.nodeNames(), empty()); |
156 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), | 148 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), |
157 | equalTo(problem.findClass("Foo").get().getNewNode())); | 149 | equalTo(problem.findClass("Foo").get().getNewNode())); |
158 | } | 150 | } |
159 | 151 | ||
160 | static Stream<Arguments> classNewNodeReferencesSource() { | ||
161 | return Stream.of(Arguments.of("Foo::new", false), Arguments.of("Foo::new", true), | ||
162 | Arguments.of("test::Foo::new", true)); | ||
163 | } | ||
164 | |||
165 | @Test | 152 | @Test |
166 | void newNodeIsNotSpecial() { | 153 | void newNodeIsNotSpecial() { |
167 | var problem = parse(""" | 154 | var problem = parse(""" |
@@ -176,12 +163,12 @@ class NodeScopingTest { | |||
176 | 163 | ||
177 | @ParameterizedTest | 164 | @ParameterizedTest |
178 | @MethodSource("enumLiteralReferencesSource") | 165 | @MethodSource("enumLiteralReferencesSource") |
179 | void enumLiteralTest(String qualifiedName, boolean namedProblem) { | 166 | void enumLiteralTest(String qualifiedName) { |
180 | var problem = parse(""" | 167 | var problem = parse(""" |
181 | enum Foo { alpha, beta } | 168 | enum Foo { alpha, beta } |
182 | pred predicate(Foo a) <-> node(a). | 169 | pred predicate(Foo a) <-> node(a). |
183 | predicate({PARAM}). | 170 | predicate({PARAM}). |
184 | """, qualifiedName, namedProblem); | 171 | """, qualifiedName); |
185 | assertThat(problem.getResourceErrors(), empty()); | 172 | assertThat(problem.getResourceErrors(), empty()); |
186 | assertThat(problem.nodeNames(), empty()); | 173 | assertThat(problem.nodeNames(), empty()); |
187 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findEnum("Foo").literal("alpha"))); | 174 | assertThat(problem.assertion(0).arg(0).node(), equalTo(problem.findEnum("Foo").literal("alpha"))); |
@@ -189,11 +176,11 @@ class NodeScopingTest { | |||
189 | 176 | ||
190 | @ParameterizedTest | 177 | @ParameterizedTest |
191 | @MethodSource("enumLiteralReferencesSource") | 178 | @MethodSource("enumLiteralReferencesSource") |
192 | void enumLiteralInPredicateTest(String qualifiedName, boolean namedProblem) { | 179 | void enumLiteralInPredicateTest(String qualifiedName) { |
193 | var problem = parse(""" | 180 | var problem = parse(""" |
194 | enum Foo { alpha, beta } | 181 | enum Foo { alpha, beta } |
195 | pred predicate(Foo a) <-> node({PARAM}). | 182 | pred predicate(Foo a) <-> node({PARAM}). |
196 | """, qualifiedName, namedProblem); | 183 | """, qualifiedName); |
197 | assertThat(problem.getResourceErrors(), empty()); | 184 | assertThat(problem.getResourceErrors(), empty()); |
198 | assertThat(problem.nodeNames(), empty()); | 185 | assertThat(problem.nodeNames(), empty()); |
199 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), | 186 | assertThat(problem.pred("predicate").conj(0).lit(0).arg(0).node(), |
@@ -201,9 +188,7 @@ class NodeScopingTest { | |||
201 | } | 188 | } |
202 | 189 | ||
203 | static Stream<Arguments> enumLiteralReferencesSource() { | 190 | static Stream<Arguments> enumLiteralReferencesSource() { |
204 | return Stream.of(Arguments.of("alpha", false), Arguments.of("alpha", true), Arguments.of("Foo::alpha", false), | 191 | return Stream.of(Arguments.of("alpha"), Arguments.of("Foo::alpha")); |
205 | Arguments.of("Foo::alpha", true), Arguments.of("test::alpha", true), | ||
206 | Arguments.of("test::Foo::alpha", true)); | ||
207 | } | 192 | } |
208 | 193 | ||
209 | @Disabled("No enum literals are present in builtin.problem currently") | 194 | @Disabled("No enum literals are present in builtin.problem currently") |
@@ -222,7 +207,7 @@ class NodeScopingTest { | |||
222 | @Disabled("No enum literals are present in builtin.problem currently") | 207 | @Disabled("No enum literals are present in builtin.problem currently") |
223 | @ParameterizedTest | 208 | @ParameterizedTest |
224 | @MethodSource("builtInEnumLiteralReferencesSource") | 209 | @MethodSource("builtInEnumLiteralReferencesSource") |
225 | void bultInEnumLiteralInPredicateTest(String qualifiedName) { | 210 | void builtInEnumLiteralInPredicateTest(String qualifiedName) { |
226 | var problem = parse(""" | 211 | var problem = parse(""" |
227 | pred predicate() <-> node({PARAM}). | 212 | pred predicate() <-> node({PARAM}). |
228 | """, qualifiedName); | 213 | """, qualifiedName); |
@@ -237,13 +222,8 @@ class NodeScopingTest { | |||
237 | Arguments.of("builtin::bool::true")); | 222 | Arguments.of("builtin::bool::true")); |
238 | } | 223 | } |
239 | 224 | ||
240 | private WrappedProblem parse(String text, String parameter, boolean namedProblem) { | ||
241 | var problemName = namedProblem ? "problem test.\n" : ""; | ||
242 | return parseHelper.parse(problemName + text.replace("{PARAM}", parameter)); | ||
243 | } | ||
244 | |||
245 | private WrappedProblem parse(String text, String parameter) { | 225 | private WrappedProblem parse(String text, String parameter) { |
246 | return parse(text, parameter, false); | 226 | return parseHelper.parse(text.replace("{PARAM}", parameter)); |
247 | } | 227 | } |
248 | 228 | ||
249 | private WrappedProblem parse(String text) { | 229 | private WrappedProblem parse(String text) { |