diff options
Diffstat (limited to 'subprojects/language/src')
26 files changed, 2533 insertions, 0 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 b/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 new file mode 100644 index 00000000..21ff456e --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 | |||
@@ -0,0 +1,68 @@ | |||
1 | module tools.refinery.language.GenerateProblem | ||
2 | |||
3 | import org.eclipse.xtext.xtext.generator.* | ||
4 | import org.eclipse.xtext.xtext.generator.model.project.* | ||
5 | |||
6 | var rootPath = '..' | ||
7 | |||
8 | Workflow { | ||
9 | component = XtextGenerator { | ||
10 | configuration = { | ||
11 | project = StandardProjectConfig { | ||
12 | baseName = 'language' | ||
13 | rootPath = rootPath | ||
14 | runtimeTest = { | ||
15 | enabled = true | ||
16 | srcGen = 'src/testFixtures/xtext-gen' | ||
17 | } | ||
18 | genericIde = { | ||
19 | name = 'language-ide' | ||
20 | } | ||
21 | web = { | ||
22 | enabled = true | ||
23 | name = 'language-web' | ||
24 | } | ||
25 | mavenLayout = true | ||
26 | } | ||
27 | code = { | ||
28 | encoding = 'UTF-8' | ||
29 | lineDelimiter = '\n' | ||
30 | fileHeader = '/*\n * generated by Xtext \${version}\n */' | ||
31 | preferXtendStubs = false | ||
32 | } | ||
33 | } | ||
34 | |||
35 | language = StandardLanguage { | ||
36 | name = 'tools.refinery.language.Problem' | ||
37 | fileExtensions = 'problem' | ||
38 | referencedResource = 'platform:/resource/tools.refinery.refinery-language-model/model/problem.genmodel' | ||
39 | serializer = { | ||
40 | generateStub = false | ||
41 | } | ||
42 | formatter = { | ||
43 | generateStub = true | ||
44 | } | ||
45 | validator = { | ||
46 | generateDeprecationValidation = true | ||
47 | } | ||
48 | generator = { | ||
49 | generateStub = false | ||
50 | } | ||
51 | junitSupport = { | ||
52 | generateStub = false | ||
53 | skipXbaseTestingPackage = true | ||
54 | junitVersion = '5' | ||
55 | } | ||
56 | webSupport = { | ||
57 | // We only generate the {@code AbstractProblemWebModule}, | ||
58 | // because we write our own integration code for CodeMirror 6. | ||
59 | framework = 'codemirror' | ||
60 | generateHtmlExample = false | ||
61 | generateJettyLauncher = false | ||
62 | generateJsHighlighting = false | ||
63 | generateServlet = false | ||
64 | generateWebXml = false | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext new file mode 100644 index 00000000..c94d40ab --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext | |||
@@ -0,0 +1,205 @@ | |||
1 | grammar tools.refinery.language.Problem with org.eclipse.xtext.common.Terminals | ||
2 | |||
3 | import "http://www.eclipse.org/emf/2002/Ecore" as ecore | ||
4 | import "https://refinery.tools/emf/2021/Problem" | ||
5 | |||
6 | Problem: | ||
7 | ("problem" name=Identifier ".")? | ||
8 | statements+=Statement*; | ||
9 | |||
10 | Statement: | ||
11 | ClassDeclaration | EnumDeclaration | PredicateDefinition | RuleDefinition | Assertion | NodeValueAssertion | | ||
12 | ScopeDeclaration | | ||
13 | IndividualDeclaration; | ||
14 | |||
15 | ClassDeclaration: | ||
16 | abstract?="abstract"? "class" | ||
17 | name=Identifier | ||
18 | ("extends" superTypes+=[Relation|QualifiedName] ("," superTypes+=[Relation|QualifiedName])*)? | ||
19 | ("{" (referenceDeclarations+=ReferenceDeclaration ";"?)* "}" | "."); | ||
20 | |||
21 | EnumDeclaration: | ||
22 | "enum" | ||
23 | name=Identifier | ||
24 | ("{" (literals+=EnumLiteral ("," literals+=EnumLiteral)* ("," | ";")?)? "}" | "."); | ||
25 | |||
26 | EnumLiteral returns Node: | ||
27 | name=Identifier; | ||
28 | |||
29 | ReferenceDeclaration: | ||
30 | (containment?="contains" | "refers")? | ||
31 | referenceType=[Relation|QualifiedName] | ||
32 | ("[" multiplicity=Multiplicity "]")? | ||
33 | name=Identifier | ||
34 | ("opposite" opposite=[ReferenceDeclaration|QualifiedName])?; | ||
35 | |||
36 | enum PredicateKind: | ||
37 | DIRECT="direct"; | ||
38 | |||
39 | PredicateDefinition: | ||
40 | (error?="error" "pred"? | kind=PredicateKind? "pred") | ||
41 | name=Identifier | ||
42 | "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" | ||
43 | ("<->" bodies+=Conjunction (";" bodies+=Conjunction)*)? | ||
44 | "."; | ||
45 | |||
46 | enum RuleKind: | ||
47 | DIRECT="direct"; | ||
48 | |||
49 | RuleDefinition: | ||
50 | kind=RuleKind "rule" | ||
51 | name=Identifier | ||
52 | "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" | ||
53 | (":" bodies+=Conjunction (";" bodies+=Conjunction)* | ||
54 | "~>" action=Action)? | ||
55 | "."; | ||
56 | |||
57 | Parameter: | ||
58 | parameterType=[Relation|QualifiedName]? name=Identifier; | ||
59 | |||
60 | Conjunction: | ||
61 | literals+=Literal ("," literals+=Literal)*; | ||
62 | |||
63 | Action: | ||
64 | actionLiterals+=ActionLiteral ("," actionLiterals+=ActionLiteral)*; | ||
65 | |||
66 | Literal: | ||
67 | Atom | ValueLiteral | NegativeLiteral; | ||
68 | |||
69 | ValueLiteral: | ||
70 | atom=Atom | ||
71 | (refinement?=":" | "=") | ||
72 | values+=LogicConstant ("|" values+=LogicConstant)*; | ||
73 | |||
74 | NegativeLiteral: | ||
75 | "!" atom=Atom; | ||
76 | |||
77 | ActionLiteral: | ||
78 | ValueActionLiteral | DeleteActionLiteral | NewActionLiteral; | ||
79 | |||
80 | ValueActionLiteral: | ||
81 | atom=Atom | ||
82 | (refinement?=":" | "=") | ||
83 | value=LogicValue; | ||
84 | |||
85 | DeleteActionLiteral: | ||
86 | "delete" variableOrNode=[VariableOrNode|QualifiedName]; | ||
87 | |||
88 | NewActionLiteral: | ||
89 | "new" variable=NewVariable; | ||
90 | |||
91 | NewVariable: | ||
92 | name=Identifier; | ||
93 | |||
94 | Atom: | ||
95 | relation=[Relation|QualifiedName] | ||
96 | transitiveClosure?="+"? | ||
97 | "(" (arguments+=Argument ("," arguments+=Argument)*)? ")"; | ||
98 | |||
99 | LogicConstant: | ||
100 | value=LogicValue; | ||
101 | |||
102 | Argument: | ||
103 | VariableOrNodeArgument | ConstantArgument; | ||
104 | |||
105 | VariableOrNodeArgument: | ||
106 | variableOrNode=[VariableOrNode|QualifiedName]; | ||
107 | |||
108 | ConstantArgument: | ||
109 | constant=Constant; | ||
110 | |||
111 | Assertion: | ||
112 | default?="default"? | ||
113 | (value=ShortLogicValue? | ||
114 | relation=[Relation|QualifiedName] | ||
115 | "(" (arguments+=AssertionArgument ("," arguments+=AssertionArgument)*)? ")" | ||
116 | | relation=[Relation|QualifiedName] | ||
117 | "(" (arguments+=AssertionArgument ("," arguments+=AssertionArgument)*)? ")" | ||
118 | ":" value=LogicValue) | ||
119 | "."; | ||
120 | |||
121 | AssertionArgument: | ||
122 | NodeAssertionArgument | WildcardAssertionArgument | ConstantAssertionArgument; | ||
123 | |||
124 | NodeAssertionArgument: | ||
125 | node=[Node|QualifiedName]; | ||
126 | |||
127 | WildcardAssertionArgument: | ||
128 | {WildcardAssertionArgument} "*"; | ||
129 | |||
130 | ConstantAssertionArgument: | ||
131 | constant=Constant; | ||
132 | |||
133 | enum LogicValue: | ||
134 | TRUE="true" | FALSE="false" | UNKNOWN="unknown" | ERROR="error"; | ||
135 | |||
136 | enum ShortLogicValue returns LogicValue: | ||
137 | FALSE="!" | UNKNOWN="?"; | ||
138 | |||
139 | NodeValueAssertion: | ||
140 | node=[Node|QualifiedName] ":" value=Constant "."; | ||
141 | |||
142 | Constant: | ||
143 | RealConstant | IntConstant | StringConstant; | ||
144 | |||
145 | IntConstant: | ||
146 | intValue=Integer; | ||
147 | |||
148 | RealConstant: | ||
149 | realValue=Real; | ||
150 | |||
151 | StringConstant: | ||
152 | stringValue=STRING; | ||
153 | |||
154 | ScopeDeclaration: | ||
155 | "scope" typeScopes+=TypeScope ("," typeScopes+=TypeScope)* "."; | ||
156 | |||
157 | TypeScope: | ||
158 | targetType=[ClassDeclaration|QualifiedName] | ||
159 | (increment?="+=" | "=") | ||
160 | multiplicity=DefiniteMultiplicity; | ||
161 | |||
162 | Multiplicity: | ||
163 | UnboundedMultiplicity | DefiniteMultiplicity; | ||
164 | |||
165 | DefiniteMultiplicity returns Multiplicity: | ||
166 | RangeMultiplicity | ExactMultiplicity; | ||
167 | |||
168 | UnboundedMultiplicity: | ||
169 | {UnboundedMultiplicity}; | ||
170 | |||
171 | RangeMultiplicity: | ||
172 | lowerBound=INT ".." upperBound=UpperBound; | ||
173 | |||
174 | ExactMultiplicity: | ||
175 | exactValue=INT; | ||
176 | |||
177 | IndividualDeclaration: | ||
178 | "indiv" nodes+=EnumLiteral ("," nodes+=EnumLiteral)* "."; | ||
179 | |||
180 | UpperBound returns ecore::EInt: | ||
181 | INT | "*"; | ||
182 | |||
183 | QualifiedName hidden(): | ||
184 | Identifier ("::" Identifier)*; | ||
185 | |||
186 | Identifier: | ||
187 | ID | "true" | "false" | "unknown" | "error" | "class" | "abstract" | "extends" | "enum" | "pred" | | ||
188 | "indiv" | "problem" | "new" | "delete" | "direct" | "rule"; | ||
189 | |||
190 | Integer returns ecore::EInt hidden(): | ||
191 | "-"? INT; | ||
192 | |||
193 | Real returns ecore::EDouble: | ||
194 | "-"? (EXPONENTIAL | INT "." (INT | EXPONENTIAL)); | ||
195 | |||
196 | @Override | ||
197 | terminal ID: | ||
198 | ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*; | ||
199 | |||
200 | terminal EXPONENTIAL: | ||
201 | INT ("e" | "E") ("+" | "-")? INT; | ||
202 | |||
203 | @Override | ||
204 | terminal SL_COMMENT: | ||
205 | ('%' | '//') !('\n' | '\r')* ('\r'? '\n')?; | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java new file mode 100644 index 00000000..dd7731b4 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java | |||
@@ -0,0 +1,84 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language; | ||
5 | |||
6 | import org.eclipse.xtext.conversion.IValueConverterService; | ||
7 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
8 | import org.eclipse.xtext.resource.DerivedStateAwareResource; | ||
9 | import org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager; | ||
10 | import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; | ||
11 | import org.eclipse.xtext.resource.IDerivedStateComputer; | ||
12 | import org.eclipse.xtext.resource.ILocationInFileProvider; | ||
13 | import org.eclipse.xtext.resource.IResourceDescription; | ||
14 | import org.eclipse.xtext.resource.XtextResource; | ||
15 | import org.eclipse.xtext.scoping.IGlobalScopeProvider; | ||
16 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
17 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
18 | import org.eclipse.xtext.validation.IResourceValidator; | ||
19 | import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; | ||
20 | |||
21 | import com.google.inject.Binder; | ||
22 | import com.google.inject.name.Names; | ||
23 | |||
24 | import tools.refinery.language.conversion.ProblemValueConverterService; | ||
25 | import tools.refinery.language.naming.ProblemQualifiedNameConverter; | ||
26 | import tools.refinery.language.resource.ProblemDerivedStateComputer; | ||
27 | import tools.refinery.language.resource.ProblemLocationInFileProvider; | ||
28 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
29 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; | ||
30 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; | ||
31 | |||
32 | /** | ||
33 | * Use this class to register components to be used at runtime / without the | ||
34 | * Equinox extension registry. | ||
35 | */ | ||
36 | public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { | ||
37 | public Class<? extends IQualifiedNameConverter> bindIQualifiedNameConverter() { | ||
38 | return ProblemQualifiedNameConverter.class; | ||
39 | } | ||
40 | |||
41 | public Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() { | ||
42 | return ProblemResourceDescriptionStrategy.class; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public Class<? extends IValueConverterService> bindIValueConverterService() { | ||
47 | return ProblemValueConverterService.class; | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public Class<? extends IGlobalScopeProvider> bindIGlobalScopeProvider() { | ||
52 | return ProblemGlobalScopeProvider.class; | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public void configureIScopeProviderDelegate(Binder binder) { | ||
57 | binder.bind(IScopeProvider.class).annotatedWith(Names.named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE)) | ||
58 | .to(ProblemLocalScopeProvider.class); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public Class<? extends XtextResource> bindXtextResource() { | ||
63 | return DerivedStateAwareResource.class; | ||
64 | } | ||
65 | |||
66 | // Method name follows Xtext convention. | ||
67 | @SuppressWarnings("squid:S100") | ||
68 | public Class<? extends IResourceDescription.Manager> bindIResourceDescription$Manager() { | ||
69 | return DerivedStateAwareResourceDescriptionManager.class; | ||
70 | } | ||
71 | |||
72 | public Class<? extends IResourceValidator> bindIResourceValidator() { | ||
73 | return DerivedStateAwareResourceValidator.class; | ||
74 | } | ||
75 | |||
76 | public Class<? extends IDerivedStateComputer> bindIDerivedStateComputer() { | ||
77 | return ProblemDerivedStateComputer.class; | ||
78 | } | ||
79 | |||
80 | @Override | ||
81 | public Class<? extends ILocationInFileProvider> bindILocationInFileProvider() { | ||
82 | return ProblemLocationInFileProvider.class; | ||
83 | } | ||
84 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java new file mode 100644 index 00000000..d753a119 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language; | ||
5 | |||
6 | import org.eclipse.emf.ecore.resource.Resource; | ||
7 | import org.eclipse.xtext.resource.IResourceFactory; | ||
8 | import org.eclipse.xtext.resource.IResourceServiceProvider; | ||
9 | |||
10 | import com.google.inject.Guice; | ||
11 | import com.google.inject.Injector; | ||
12 | |||
13 | import tools.refinery.language.model.ProblemEMFSetup; | ||
14 | |||
15 | /** | ||
16 | * Initialization support for running Xtext languages without Equinox extension | ||
17 | * registry. | ||
18 | */ | ||
19 | public class ProblemStandaloneSetup extends ProblemStandaloneSetupGenerated { | ||
20 | |||
21 | public static void doSetup() { | ||
22 | new ProblemStandaloneSetup().createInjectorAndDoEMFRegistration(); | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public Injector createInjectorAndDoEMFRegistration() { | ||
27 | ProblemEMFSetup.doEMFRegistration(); | ||
28 | var xmiInjector = createXmiInjector(); | ||
29 | registerXmiInjector(xmiInjector); | ||
30 | return super.createInjectorAndDoEMFRegistration(); | ||
31 | } | ||
32 | |||
33 | protected Injector createXmiInjector() { | ||
34 | return Guice.createInjector(new ProblemXmiRuntimeModule()); | ||
35 | } | ||
36 | |||
37 | protected void registerXmiInjector(Injector injector) { | ||
38 | IResourceFactory resourceFactory = injector.getInstance(IResourceFactory.class); | ||
39 | IResourceServiceProvider serviceProvider = injector.getInstance(IResourceServiceProvider.class); | ||
40 | |||
41 | Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(ProblemEMFSetup.XMI_RESOURCE_EXTENSION, resourceFactory); | ||
42 | IResourceServiceProvider.Registry.INSTANCE.getExtensionToFactoryMap().put(ProblemEMFSetup.XMI_RESOURCE_EXTENSION, serviceProvider); | ||
43 | } | ||
44 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemXmiRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemXmiRuntimeModule.java new file mode 100644 index 00000000..03a33bee --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemXmiRuntimeModule.java | |||
@@ -0,0 +1,35 @@ | |||
1 | package tools.refinery.language; | ||
2 | |||
3 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
4 | import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; | ||
5 | import org.eclipse.xtext.resource.IResourceFactory; | ||
6 | import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule; | ||
7 | |||
8 | import tools.refinery.language.model.ProblemEMFSetup; | ||
9 | import tools.refinery.language.naming.ProblemQualifiedNameConverter; | ||
10 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
11 | import tools.refinery.language.resource.ProblemXmiResourceFactory; | ||
12 | |||
13 | public class ProblemXmiRuntimeModule extends AbstractGenericResourceRuntimeModule { | ||
14 | @Override | ||
15 | protected String getLanguageName() { | ||
16 | return "tools.refinery.language.ProblemXmi"; | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | protected String getFileExtensions() { | ||
21 | return ProblemEMFSetup.XMI_RESOURCE_EXTENSION; | ||
22 | } | ||
23 | |||
24 | public Class<? extends IResourceFactory> bindIResourceFactory() { | ||
25 | return ProblemXmiResourceFactory.class; | ||
26 | } | ||
27 | |||
28 | public Class<? extends IQualifiedNameConverter> bindIQualifiedNameConverter() { | ||
29 | return ProblemQualifiedNameConverter.class; | ||
30 | } | ||
31 | |||
32 | public Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() { | ||
33 | return ProblemResourceDescriptionStrategy.class; | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java b/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java new file mode 100644 index 00000000..508688ed --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java | |||
@@ -0,0 +1,19 @@ | |||
1 | package tools.refinery.language.conversion; | ||
2 | |||
3 | import org.eclipse.xtext.common.services.DefaultTerminalConverters; | ||
4 | import org.eclipse.xtext.conversion.IValueConverter; | ||
5 | import org.eclipse.xtext.conversion.ValueConverter; | ||
6 | |||
7 | import com.google.inject.Inject; | ||
8 | |||
9 | public class ProblemValueConverterService extends DefaultTerminalConverters { | ||
10 | @Inject | ||
11 | private UpperBoundValueConverter upperBoundValueConverter; | ||
12 | |||
13 | @ValueConverter(rule = "UpperBound") | ||
14 | // Method name follows Xtext convention. | ||
15 | @SuppressWarnings("squid:S100") | ||
16 | public IValueConverter<Integer> UpperBound() { | ||
17 | return upperBoundValueConverter; | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java b/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java new file mode 100644 index 00000000..be0d15ad --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java | |||
@@ -0,0 +1,35 @@ | |||
1 | package tools.refinery.language.conversion; | ||
2 | |||
3 | import org.eclipse.xtext.conversion.ValueConverterException; | ||
4 | import org.eclipse.xtext.conversion.impl.AbstractValueConverter; | ||
5 | import org.eclipse.xtext.conversion.impl.INTValueConverter; | ||
6 | import org.eclipse.xtext.nodemodel.INode; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.Singleton; | ||
10 | |||
11 | @Singleton | ||
12 | public class UpperBoundValueConverter extends AbstractValueConverter<Integer> { | ||
13 | public static final String INFINITY = "*"; | ||
14 | |||
15 | @Inject | ||
16 | private INTValueConverter intValueConverter; | ||
17 | |||
18 | @Override | ||
19 | public Integer toValue(String string, INode node) throws ValueConverterException { | ||
20 | if (INFINITY.equals(string)) { | ||
21 | return -1; | ||
22 | } else { | ||
23 | return intValueConverter.toValue(string, node); | ||
24 | } | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString(Integer value) throws ValueConverterException { | ||
29 | if (value < 0) { | ||
30 | return INFINITY; | ||
31 | } else { | ||
32 | return intValueConverter.toString(value); | ||
33 | } | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java new file mode 100644 index 00000000..903347f7 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java | |||
@@ -0,0 +1,183 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.26.0.M2 | ||
3 | */ | ||
4 | package tools.refinery.language.formatting2; | ||
5 | |||
6 | import org.eclipse.emf.ecore.EObject; | ||
7 | import org.eclipse.xtext.formatting2.AbstractJavaFormatter; | ||
8 | import org.eclipse.xtext.formatting2.IFormattableDocument; | ||
9 | import org.eclipse.xtext.formatting2.IHiddenRegionFormatter; | ||
10 | import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder; | ||
11 | import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion; | ||
12 | import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; | ||
13 | |||
14 | import tools.refinery.language.model.problem.Assertion; | ||
15 | import tools.refinery.language.model.problem.Atom; | ||
16 | import tools.refinery.language.model.problem.ClassDeclaration; | ||
17 | import tools.refinery.language.model.problem.Conjunction; | ||
18 | import tools.refinery.language.model.problem.IndividualDeclaration; | ||
19 | import tools.refinery.language.model.problem.NegativeLiteral; | ||
20 | import tools.refinery.language.model.problem.Parameter; | ||
21 | import tools.refinery.language.model.problem.PredicateDefinition; | ||
22 | import tools.refinery.language.model.problem.Problem; | ||
23 | import tools.refinery.language.model.problem.ProblemPackage; | ||
24 | |||
25 | public class ProblemFormatter extends AbstractJavaFormatter { | ||
26 | |||
27 | protected void format(Problem problem, IFormattableDocument doc) { | ||
28 | doc.prepend(problem, this::noSpace); | ||
29 | var region = regionFor(problem); | ||
30 | doc.append(region.keyword("problem"), this::oneSpace); | ||
31 | doc.prepend(region.keyword("."), this::noSpace); | ||
32 | appendNewLines(doc, region.keyword("."), this::twoNewLines); | ||
33 | for (var statement : problem.getStatements()) { | ||
34 | doc.format(statement); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | protected void format(Assertion assertion, IFormattableDocument doc) { | ||
39 | surroundNewLines(doc, assertion, this::singleNewLine); | ||
40 | var region = regionFor(assertion); | ||
41 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__DEFAULT), this::oneSpace); | ||
42 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__VALUE), this::noSpace); | ||
43 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__RELATION), this::noSpace); | ||
44 | formatParenthesizedList(region, doc); | ||
45 | doc.prepend(region.keyword(":"), this::noSpace); | ||
46 | doc.append(region.keyword(":"), this::oneSpace); | ||
47 | doc.prepend(region.keyword("."), this::noSpace); | ||
48 | for (var argument : assertion.getArguments()) { | ||
49 | doc.format(argument); | ||
50 | } | ||
51 | } | ||
52 | |||
53 | protected void format(ClassDeclaration classDeclaration, IFormattableDocument doc) { | ||
54 | surroundNewLines(doc, classDeclaration, this::twoNewLines); | ||
55 | var region = regionFor(classDeclaration); | ||
56 | doc.append(region.feature(ProblemPackage.Literals.CLASS_DECLARATION__ABSTRACT), this::oneSpace); | ||
57 | doc.append(region.keyword("class"), this::oneSpace); | ||
58 | doc.surround(region.keyword("extends"), this::oneSpace); | ||
59 | formatList(region, ",", doc); | ||
60 | doc.prepend(region.keyword("{"), this::oneSpace); | ||
61 | doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2)); | ||
62 | doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2)); | ||
63 | doc.prepend(region.keyword("."), this::noSpace); | ||
64 | for (var referenceDeclaration : classDeclaration.getReferenceDeclarations()) { | ||
65 | doc.format(referenceDeclaration); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | protected void format(PredicateDefinition predicateDefinition, IFormattableDocument doc) { | ||
70 | surroundNewLines(doc, predicateDefinition, this::twoNewLines); | ||
71 | var region = regionFor(predicateDefinition); | ||
72 | doc.append(region.feature(ProblemPackage.Literals.PREDICATE_DEFINITION__KIND), this::oneSpace); | ||
73 | doc.append(region.keyword("pred"), this::oneSpace); | ||
74 | doc.append(region.feature(ProblemPackage.Literals.NAMED_ELEMENT__NAME), this::noSpace); | ||
75 | formatParenthesizedList(region, doc); | ||
76 | doc.surround(region.keyword("<->"), this::oneSpace); | ||
77 | formatList(region, ";", doc); | ||
78 | doc.prepend(region.keyword("."), this::noSpace); | ||
79 | for (var parameter : predicateDefinition.getParameters()) { | ||
80 | doc.format(parameter); | ||
81 | } | ||
82 | for (var body : predicateDefinition.getBodies()) { | ||
83 | doc.format(body); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | protected void format(Parameter parameter, IFormattableDocument doc) { | ||
88 | doc.append(regionFor(parameter).feature(ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE), this::oneSpace); | ||
89 | } | ||
90 | |||
91 | protected void format(Conjunction conjunction, IFormattableDocument doc) { | ||
92 | var region = regionFor(conjunction); | ||
93 | formatList(region, ",", doc); | ||
94 | for (var literal : conjunction.getLiterals()) { | ||
95 | doc.format(literal); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | protected void format(NegativeLiteral literal, IFormattableDocument doc) { | ||
100 | var region = regionFor(literal); | ||
101 | doc.append(region.keyword("!"), this::noSpace); | ||
102 | doc.format(literal.getAtom()); | ||
103 | } | ||
104 | |||
105 | protected void format(Atom atom, IFormattableDocument doc) { | ||
106 | var region = regionFor(atom); | ||
107 | doc.append(region.feature(ProblemPackage.Literals.ATOM__RELATION), this::noSpace); | ||
108 | doc.append(region.feature(ProblemPackage.Literals.ATOM__TRANSITIVE_CLOSURE), this::noSpace); | ||
109 | formatParenthesizedList(region, doc); | ||
110 | for (var argument : atom.getArguments()) { | ||
111 | doc.format(argument); | ||
112 | } | ||
113 | } | ||
114 | |||
115 | protected void format(IndividualDeclaration individualDeclaration, IFormattableDocument doc) { | ||
116 | surroundNewLines(doc, individualDeclaration, this::singleNewLine); | ||
117 | var region = regionFor(individualDeclaration); | ||
118 | doc.append(region.keyword("indiv"), this::oneSpace); | ||
119 | formatList(region, ",", doc); | ||
120 | doc.prepend(region.keyword("."), this::noSpace); | ||
121 | } | ||
122 | |||
123 | protected void formatParenthesizedList(ISemanticRegionsFinder region, IFormattableDocument doc) { | ||
124 | doc.append(region.keyword("("), this::noSpace); | ||
125 | doc.prepend(region.keyword(")"), this::noSpace); | ||
126 | formatList(region, ",", doc); | ||
127 | } | ||
128 | |||
129 | protected void formatList(ISemanticRegionsFinder region, String separator, IFormattableDocument doc) { | ||
130 | for (var comma : region.keywords(separator)) { | ||
131 | doc.prepend(comma, this::noSpace); | ||
132 | doc.append(comma, this::oneSpace); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | protected void singleNewLine(IHiddenRegionFormatter it) { | ||
137 | it.setNewLines(1, 1, 2); | ||
138 | } | ||
139 | |||
140 | protected void twoNewLines(IHiddenRegionFormatter it) { | ||
141 | it.highPriority(); | ||
142 | it.setNewLines(2); | ||
143 | } | ||
144 | |||
145 | protected void surroundNewLines(IFormattableDocument doc, EObject eObject, | ||
146 | Procedure1<? super IHiddenRegionFormatter> init) { | ||
147 | var region = doc.getRequest().getTextRegionAccess().regionForEObject(eObject); | ||
148 | preprendNewLines(doc, region, init); | ||
149 | appendNewLines(doc, region, init); | ||
150 | } | ||
151 | |||
152 | protected void preprendNewLines(IFormattableDocument doc, ISequentialRegion region, | ||
153 | Procedure1<? super IHiddenRegionFormatter> init) { | ||
154 | if (region == null) { | ||
155 | return; | ||
156 | } | ||
157 | var previousHiddenRegion = region.getPreviousHiddenRegion(); | ||
158 | if (previousHiddenRegion == null) { | ||
159 | return; | ||
160 | } | ||
161 | if (previousHiddenRegion.getPreviousSequentialRegion() == null) { | ||
162 | doc.set(previousHiddenRegion, it -> it.setNewLines(0)); | ||
163 | } else { | ||
164 | doc.set(previousHiddenRegion, init); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | protected void appendNewLines(IFormattableDocument doc, ISequentialRegion region, | ||
169 | Procedure1<? super IHiddenRegionFormatter> init) { | ||
170 | if (region == null) { | ||
171 | return; | ||
172 | } | ||
173 | var nextHiddenRegion = region.getNextHiddenRegion(); | ||
174 | if (nextHiddenRegion == null) { | ||
175 | return; | ||
176 | } | ||
177 | if (nextHiddenRegion.getNextSequentialRegion() == null) { | ||
178 | doc.set(nextHiddenRegion, it -> it.setNewLines(1)); | ||
179 | } else { | ||
180 | doc.set(nextHiddenRegion, init); | ||
181 | } | ||
182 | } | ||
183 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java new file mode 100644 index 00000000..e959be74 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java | |||
@@ -0,0 +1,25 @@ | |||
1 | package tools.refinery.language.naming; | ||
2 | |||
3 | import java.util.regex.Pattern; | ||
4 | |||
5 | public final class NamingUtil { | ||
6 | private static final String SINGLETON_VARIABLE_PREFIX = "_"; | ||
7 | |||
8 | private static final Pattern ID_REGEX = Pattern.compile("[_a-zA-Z][_0-9a-zA-Z]*"); | ||
9 | |||
10 | private NamingUtil() { | ||
11 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
12 | } | ||
13 | |||
14 | public static boolean isNullOrEmpty(String name) { | ||
15 | return name == null || name.isEmpty(); | ||
16 | } | ||
17 | |||
18 | public static boolean isSingletonVariableName(String name) { | ||
19 | return name != null && name.startsWith(SINGLETON_VARIABLE_PREFIX); | ||
20 | } | ||
21 | |||
22 | public static boolean isValidId(String name) { | ||
23 | return name != null && ID_REGEX.matcher(name).matches(); | ||
24 | } | ||
25 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java new file mode 100644 index 00000000..5453906f --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java | |||
@@ -0,0 +1,15 @@ | |||
1 | package tools.refinery.language.naming; | ||
2 | |||
3 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
4 | |||
5 | import com.google.inject.Singleton; | ||
6 | |||
7 | @Singleton | ||
8 | public class ProblemQualifiedNameConverter extends IQualifiedNameConverter.DefaultImpl { | ||
9 | public static final String DELIMITER = "::"; | ||
10 | |||
11 | @Override | ||
12 | public String getDelimiter() { | ||
13 | return DELIMITER; | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java new file mode 100644 index 00000000..bb1226c4 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java | |||
@@ -0,0 +1,194 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.HashSet; | ||
4 | import java.util.List; | ||
5 | import java.util.Set; | ||
6 | |||
7 | import org.eclipse.xtext.linking.impl.LinkingHelper; | ||
8 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
9 | import org.eclipse.xtext.nodemodel.INode; | ||
10 | import org.eclipse.xtext.nodemodel.util.NodeModelUtils; | ||
11 | import org.eclipse.xtext.scoping.IScope; | ||
12 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
13 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
14 | |||
15 | import com.google.inject.Inject; | ||
16 | import com.google.inject.Singleton; | ||
17 | import com.google.inject.name.Named; | ||
18 | |||
19 | import tools.refinery.language.model.problem.Argument; | ||
20 | import tools.refinery.language.model.problem.Atom; | ||
21 | import tools.refinery.language.model.problem.Conjunction; | ||
22 | import tools.refinery.language.model.problem.ExistentialQuantifier; | ||
23 | import tools.refinery.language.model.problem.ImplicitVariable; | ||
24 | import tools.refinery.language.model.problem.Literal; | ||
25 | import tools.refinery.language.model.problem.NegativeLiteral; | ||
26 | import tools.refinery.language.model.problem.Parameter; | ||
27 | import tools.refinery.language.model.problem.ParametricDefinition; | ||
28 | import tools.refinery.language.model.problem.Problem; | ||
29 | import tools.refinery.language.model.problem.ProblemFactory; | ||
30 | import tools.refinery.language.model.problem.ProblemPackage; | ||
31 | import tools.refinery.language.model.problem.Statement; | ||
32 | import tools.refinery.language.model.problem.ValueLiteral; | ||
33 | import tools.refinery.language.model.problem.VariableOrNodeArgument; | ||
34 | import tools.refinery.language.naming.NamingUtil; | ||
35 | |||
36 | @Singleton | ||
37 | public class DerivedVariableComputer { | ||
38 | @Inject | ||
39 | private LinkingHelper linkingHelper; | ||
40 | |||
41 | @Inject | ||
42 | private IQualifiedNameConverter qualifiedNameConverter; | ||
43 | |||
44 | @Inject | ||
45 | @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE) | ||
46 | private IScopeProvider scopeProvider; | ||
47 | |||
48 | public void installDerivedVariables(Problem problem, Set<String> nodeNames) { | ||
49 | for (Statement statement : problem.getStatements()) { | ||
50 | if (statement instanceof ParametricDefinition definition) { | ||
51 | installDerivedParametricDefinitionState(definition, nodeNames); | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | |||
56 | protected void installDerivedParametricDefinitionState(ParametricDefinition definition, Set<String> nodeNames) { | ||
57 | Set<String> knownVariables = new HashSet<>(); | ||
58 | knownVariables.addAll(nodeNames); | ||
59 | for (Parameter parameter : definition.getParameters()) { | ||
60 | String name = parameter.getName(); | ||
61 | if (name != null) { | ||
62 | knownVariables.add(name); | ||
63 | } | ||
64 | } | ||
65 | for (Conjunction body : definition.getBodies()) { | ||
66 | installDeriveConjunctionState(body, knownVariables); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | protected void installDeriveConjunctionState(Conjunction conjunction, Set<String> knownVariables) { | ||
71 | Set<String> newVariables = new HashSet<>(); | ||
72 | for (Literal literal : conjunction.getLiterals()) { | ||
73 | if (literal instanceof Atom atom) { | ||
74 | createSigletonVariablesAndCollectVariables(atom, knownVariables, newVariables); | ||
75 | } else | ||
76 | if (literal instanceof ValueLiteral valueLiteral) { | ||
77 | createSigletonVariablesAndCollectVariables(valueLiteral.getAtom(), knownVariables, newVariables); | ||
78 | } | ||
79 | } | ||
80 | createVariables(conjunction, newVariables); | ||
81 | newVariables.addAll(knownVariables); | ||
82 | for (Literal literal : conjunction.getLiterals()) { | ||
83 | if (literal instanceof NegativeLiteral negativeLiteral) { | ||
84 | installDeriveNegativeLiteralState(negativeLiteral, newVariables); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | |||
89 | protected void installDeriveNegativeLiteralState(NegativeLiteral negativeLiteral, Set<String> knownVariables) { | ||
90 | Set<String> newVariables = new HashSet<>(); | ||
91 | createSigletonVariablesAndCollectVariables(negativeLiteral.getAtom(), knownVariables, newVariables); | ||
92 | createVariables(negativeLiteral, newVariables); | ||
93 | } | ||
94 | |||
95 | protected void createSigletonVariablesAndCollectVariables(Atom atom, Set<String> knownVariables, | ||
96 | Set<String> newVariables) { | ||
97 | for (Argument argument : atom.getArguments()) { | ||
98 | if (argument instanceof VariableOrNodeArgument variableOrNodeArgument) { | ||
99 | IScope scope = scopeProvider.getScope(variableOrNodeArgument, | ||
100 | ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE); | ||
101 | List<INode> nodes = NodeModelUtils.findNodesForFeature(variableOrNodeArgument, | ||
102 | ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE); | ||
103 | for (INode node : nodes) { | ||
104 | var variableName = linkingHelper.getCrossRefNodeAsString(node, true); | ||
105 | var created = tryCreateVariableForArgument(variableOrNodeArgument, variableName, scope, | ||
106 | knownVariables, newVariables); | ||
107 | if (created) { | ||
108 | break; | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | protected boolean tryCreateVariableForArgument(VariableOrNodeArgument variableOrNodeArgument, String variableName, | ||
116 | IScope scope, Set<String> knownVariables, Set<String> newVariables) { | ||
117 | if (!NamingUtil.isValidId(variableName)) { | ||
118 | return false; | ||
119 | } | ||
120 | var qualifiedName = qualifiedNameConverter.toQualifiedName(variableName); | ||
121 | if (scope.getSingleElement(qualifiedName) != null) { | ||
122 | return false; | ||
123 | } | ||
124 | if (NamingUtil.isSingletonVariableName(variableName)) { | ||
125 | createSingletonVariable(variableOrNodeArgument, variableName); | ||
126 | return true; | ||
127 | } | ||
128 | if (!knownVariables.contains(variableName)) { | ||
129 | newVariables.add(variableName); | ||
130 | return true; | ||
131 | } | ||
132 | return false; | ||
133 | } | ||
134 | |||
135 | protected void createVariables(ExistentialQuantifier quantifier, Set<String> newVariables) { | ||
136 | for (String variableName : newVariables) { | ||
137 | createVariable(quantifier, variableName); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | protected void createVariable(ExistentialQuantifier quantifier, String variableName) { | ||
142 | if (NamingUtil.isValidId(variableName)) { | ||
143 | ImplicitVariable variable = createNamedVariable(variableName); | ||
144 | quantifier.getImplicitVariables().add(variable); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | protected void createSingletonVariable(VariableOrNodeArgument argument, String variableName) { | ||
149 | if (NamingUtil.isValidId(variableName)) { | ||
150 | ImplicitVariable variable = createNamedVariable(variableName); | ||
151 | argument.setSingletonVariable(variable); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | protected ImplicitVariable createNamedVariable(String variableName) { | ||
156 | var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); | ||
157 | variable.setName(variableName); | ||
158 | return variable; | ||
159 | } | ||
160 | |||
161 | public void discardDerivedVariables(Problem problem) { | ||
162 | for (Statement statement : problem.getStatements()) { | ||
163 | if (statement instanceof ParametricDefinition parametricDefinition) { | ||
164 | discardParametricDefinitionState(parametricDefinition); | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | protected void discardParametricDefinitionState(ParametricDefinition definition) { | ||
170 | for (Conjunction body : definition.getBodies()) { | ||
171 | body.getImplicitVariables().clear(); | ||
172 | for (Literal literal : body.getLiterals()) { | ||
173 | if (literal instanceof Atom atom) { | ||
174 | discardDerivedAtomState(atom); | ||
175 | } | ||
176 | if (literal instanceof NegativeLiteral negativeLiteral) { | ||
177 | negativeLiteral.getImplicitVariables().clear(); | ||
178 | discardDerivedAtomState(negativeLiteral.getAtom()); | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | protected void discardDerivedAtomState(Atom atom) { | ||
185 | if (atom == null) { | ||
186 | return; | ||
187 | } | ||
188 | for (Argument argument : atom.getArguments()) { | ||
189 | if (argument instanceof VariableOrNodeArgument variableOrNodeArgument) { | ||
190 | variableOrNodeArgument.setSingletonVariable(null); | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java b/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java new file mode 100644 index 00000000..99bf9b64 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java | |||
@@ -0,0 +1,88 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.List; | ||
4 | import java.util.Set; | ||
5 | |||
6 | import org.eclipse.emf.ecore.EObject; | ||
7 | import org.eclipse.emf.ecore.EStructuralFeature; | ||
8 | import org.eclipse.xtext.linking.impl.LinkingHelper; | ||
9 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
10 | import org.eclipse.xtext.nodemodel.INode; | ||
11 | import org.eclipse.xtext.nodemodel.util.NodeModelUtils; | ||
12 | import org.eclipse.xtext.scoping.IScope; | ||
13 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
14 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
15 | |||
16 | import com.google.common.collect.ImmutableSet; | ||
17 | import com.google.inject.Inject; | ||
18 | import com.google.inject.name.Named; | ||
19 | |||
20 | import tools.refinery.language.model.problem.Assertion; | ||
21 | import tools.refinery.language.model.problem.AssertionArgument; | ||
22 | import tools.refinery.language.model.problem.NodeAssertionArgument; | ||
23 | import tools.refinery.language.model.problem.NodeValueAssertion; | ||
24 | import tools.refinery.language.model.problem.Problem; | ||
25 | import tools.refinery.language.model.problem.ProblemPackage; | ||
26 | import tools.refinery.language.model.problem.Statement; | ||
27 | import tools.refinery.language.naming.NamingUtil; | ||
28 | |||
29 | public class NodeNameCollector { | ||
30 | @Inject | ||
31 | private LinkingHelper linkingHelper; | ||
32 | |||
33 | @Inject | ||
34 | private IQualifiedNameConverter qualifiedNameConverter; | ||
35 | |||
36 | @Inject | ||
37 | @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE) | ||
38 | private IScopeProvider scopeProvider; | ||
39 | |||
40 | private final ImmutableSet.Builder<String> nodeNames = ImmutableSet.builder(); | ||
41 | |||
42 | private IScope nodeScope; | ||
43 | |||
44 | public Set<String> getNodeNames() { | ||
45 | return nodeNames.build(); | ||
46 | } | ||
47 | |||
48 | public void collectNodeNames(Problem problem) { | ||
49 | nodeScope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | ||
50 | for (Statement statement : problem.getStatements()) { | ||
51 | collectStatementNodeNames(statement); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | protected void collectStatementNodeNames(Statement statement) { | ||
56 | if (statement instanceof Assertion assertion) { | ||
57 | collectAssertionNodeNames(assertion); | ||
58 | } else if (statement instanceof NodeValueAssertion nodeValueAssertion) { | ||
59 | collectNodeValueAssertionNodeNames(nodeValueAssertion); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | protected void collectAssertionNodeNames(Assertion assertion) { | ||
64 | for (AssertionArgument argument : assertion.getArguments()) { | ||
65 | if (argument instanceof NodeAssertionArgument) { | ||
66 | collectNodeNames(argument, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | protected void collectNodeValueAssertionNodeNames(NodeValueAssertion nodeValueAssertion) { | ||
72 | collectNodeNames(nodeValueAssertion, ProblemPackage.Literals.NODE_VALUE_ASSERTION__NODE); | ||
73 | } | ||
74 | |||
75 | private void collectNodeNames(EObject eObject, EStructuralFeature feature) { | ||
76 | List<INode> nodes = NodeModelUtils.findNodesForFeature(eObject, feature); | ||
77 | for (INode node : nodes) { | ||
78 | var nodeName = linkingHelper.getCrossRefNodeAsString(node, true); | ||
79 | if (!NamingUtil.isValidId(nodeName)) { | ||
80 | continue; | ||
81 | } | ||
82 | var qualifiedName = qualifiedNameConverter.toQualifiedName(nodeName); | ||
83 | if (nodeScope.getSingleElement(qualifiedName) == null) { | ||
84 | nodeNames.add(nodeName); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | } \ No newline at end of file | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java new file mode 100644 index 00000000..275feca3 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java | |||
@@ -0,0 +1,163 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.Collection; | ||
4 | import java.util.HashMap; | ||
5 | import java.util.HashSet; | ||
6 | import java.util.List; | ||
7 | import java.util.Map; | ||
8 | import java.util.Set; | ||
9 | import java.util.function.Function; | ||
10 | |||
11 | import org.eclipse.emf.common.notify.impl.AdapterImpl; | ||
12 | import org.eclipse.emf.ecore.EObject; | ||
13 | import org.eclipse.emf.ecore.resource.Resource; | ||
14 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
15 | import org.eclipse.xtext.Constants; | ||
16 | import org.eclipse.xtext.resource.DerivedStateAwareResource; | ||
17 | import org.eclipse.xtext.resource.IDerivedStateComputer; | ||
18 | import org.eclipse.xtext.resource.XtextResource; | ||
19 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
20 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
21 | |||
22 | import com.google.inject.Inject; | ||
23 | import com.google.inject.Provider; | ||
24 | import com.google.inject.Singleton; | ||
25 | import com.google.inject.name.Named; | ||
26 | |||
27 | import tools.refinery.language.model.problem.ClassDeclaration; | ||
28 | import tools.refinery.language.model.problem.Node; | ||
29 | import tools.refinery.language.model.problem.Problem; | ||
30 | import tools.refinery.language.model.problem.ProblemFactory; | ||
31 | import tools.refinery.language.model.problem.Statement; | ||
32 | |||
33 | @Singleton | ||
34 | public class ProblemDerivedStateComputer implements IDerivedStateComputer { | ||
35 | public static final String NEW_NODE = "new"; | ||
36 | |||
37 | @Inject | ||
38 | @Named(Constants.LANGUAGE_NAME) | ||
39 | private String languageName; | ||
40 | |||
41 | @Inject | ||
42 | @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE) | ||
43 | private IScopeProvider scopeProvider; | ||
44 | |||
45 | @Inject | ||
46 | private Provider<NodeNameCollector> nodeNameCollectorProvider; | ||
47 | |||
48 | @Inject | ||
49 | private DerivedVariableComputer derivedVariableComputer; | ||
50 | |||
51 | @Override | ||
52 | public void installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) { | ||
53 | var problem = getProblem(resource); | ||
54 | if (problem != null) { | ||
55 | var adapter = getOrInstallAdapter(resource); | ||
56 | installDerivedProblemState(problem, adapter, preLinkingPhase); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | protected Problem getProblem(Resource resource) { | ||
61 | List<EObject> contents = resource.getContents(); | ||
62 | if (contents.isEmpty()) { | ||
63 | return null; | ||
64 | } | ||
65 | EObject object = contents.get(0); | ||
66 | if (object instanceof Problem problem) { | ||
67 | return problem; | ||
68 | } | ||
69 | return null; | ||
70 | } | ||
71 | |||
72 | protected void installDerivedProblemState(Problem problem, Adapter adapter, boolean preLinkingPhase) { | ||
73 | installNewNodes(problem, adapter); | ||
74 | if (preLinkingPhase) { | ||
75 | return; | ||
76 | } | ||
77 | Set<String> nodeNames = installDerivedNodes(problem); | ||
78 | derivedVariableComputer.installDerivedVariables(problem, nodeNames); | ||
79 | } | ||
80 | |||
81 | protected void installNewNodes(Problem problem, Adapter adapter) { | ||
82 | for (Statement statement : problem.getStatements()) { | ||
83 | if (statement instanceof ClassDeclaration declaration && !declaration.isAbstract() | ||
84 | && declaration.getNewNode() == null) { | ||
85 | var newNode = adapter.createNodeIfAbsent(declaration, key -> createNode(NEW_NODE)); | ||
86 | declaration.setNewNode(newNode); | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | protected Set<String> installDerivedNodes(Problem problem) { | ||
92 | var collector = nodeNameCollectorProvider.get(); | ||
93 | collector.collectNodeNames(problem); | ||
94 | Set<String> nodeNames = collector.getNodeNames(); | ||
95 | List<Node> grapNodes = problem.getNodes(); | ||
96 | for (String nodeName : nodeNames) { | ||
97 | var graphNode = createNode(nodeName); | ||
98 | grapNodes.add(graphNode); | ||
99 | } | ||
100 | return nodeNames; | ||
101 | } | ||
102 | |||
103 | protected Node createNode(String name) { | ||
104 | var node = ProblemFactory.eINSTANCE.createNode(); | ||
105 | node.setName(name); | ||
106 | return node; | ||
107 | } | ||
108 | |||
109 | @Override | ||
110 | public void discardDerivedState(DerivedStateAwareResource resource) { | ||
111 | var problem = getProblem(resource); | ||
112 | if (problem != null) { | ||
113 | var adapter = getOrInstallAdapter(resource); | ||
114 | discardDerivedProblemState(problem, adapter); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | protected void discardDerivedProblemState(Problem problem, Adapter adapter) { | ||
119 | Set<ClassDeclaration> classDeclarations = new HashSet<>(); | ||
120 | problem.getNodes().clear(); | ||
121 | for (Statement statement : problem.getStatements()) { | ||
122 | if (statement instanceof ClassDeclaration classDeclaration) { | ||
123 | classDeclaration.setNewNode(null); | ||
124 | classDeclarations.add(classDeclaration); | ||
125 | } | ||
126 | } | ||
127 | adapter.retainAll(classDeclarations); | ||
128 | derivedVariableComputer.discardDerivedVariables(problem); | ||
129 | } | ||
130 | |||
131 | protected Adapter getOrInstallAdapter(Resource resource) { | ||
132 | if (!(resource instanceof XtextResource)) { | ||
133 | return new Adapter(); | ||
134 | } | ||
135 | String resourceLanguageName = ((XtextResource) resource).getLanguageName(); | ||
136 | if (!languageName.equals(resourceLanguageName)) { | ||
137 | return new Adapter(); | ||
138 | } | ||
139 | var adapter = (Adapter) EcoreUtil.getAdapter(resource.eAdapters(), Adapter.class); | ||
140 | if (adapter == null) { | ||
141 | adapter = new Adapter(); | ||
142 | resource.eAdapters().add(adapter); | ||
143 | } | ||
144 | return adapter; | ||
145 | } | ||
146 | |||
147 | protected static class Adapter extends AdapterImpl { | ||
148 | private Map<ClassDeclaration, Node> newNodes = new HashMap<>(); | ||
149 | |||
150 | public Node createNodeIfAbsent(ClassDeclaration classDeclaration, Function<ClassDeclaration, Node> createNode) { | ||
151 | return newNodes.computeIfAbsent(classDeclaration, createNode); | ||
152 | } | ||
153 | |||
154 | public void retainAll(Collection<ClassDeclaration> classDeclarations) { | ||
155 | newNodes.keySet().retainAll(classDeclarations); | ||
156 | } | ||
157 | |||
158 | @Override | ||
159 | public boolean isAdapterForType(Object type) { | ||
160 | return Adapter.class == type; | ||
161 | } | ||
162 | } | ||
163 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java new file mode 100644 index 00000000..7aa75833 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java | |||
@@ -0,0 +1,33 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import org.eclipse.emf.ecore.EObject; | ||
4 | import org.eclipse.xtext.resource.DefaultLocationInFileProvider; | ||
5 | import org.eclipse.xtext.util.ITextRegion; | ||
6 | |||
7 | import tools.refinery.language.model.ProblemUtil; | ||
8 | import tools.refinery.language.model.problem.ImplicitVariable; | ||
9 | import tools.refinery.language.model.problem.Node; | ||
10 | |||
11 | public class ProblemLocationInFileProvider extends DefaultLocationInFileProvider { | ||
12 | @Override | ||
13 | protected ITextRegion doGetTextRegion(EObject obj, RegionDescription query) { | ||
14 | if (obj instanceof Node node) { | ||
15 | return getNodeTextRegion(node, query); | ||
16 | } | ||
17 | if (obj instanceof ImplicitVariable) { | ||
18 | return ITextRegion.EMPTY_REGION; | ||
19 | } | ||
20 | return super.doGetTextRegion(obj, query); | ||
21 | } | ||
22 | |||
23 | protected ITextRegion getNodeTextRegion(Node node, RegionDescription query) { | ||
24 | if (ProblemUtil.isIndividualNode(node)) { | ||
25 | return super.doGetTextRegion(node, query); | ||
26 | } | ||
27 | if (ProblemUtil.isNewNode(node)) { | ||
28 | EObject container = node.eContainer(); | ||
29 | return doGetTextRegion(container, query); | ||
30 | } | ||
31 | return ITextRegion.EMPTY_REGION; | ||
32 | } | ||
33 | } | ||
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 new file mode 100644 index 00000000..f86ebd38 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java | |||
@@ -0,0 +1,103 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import org.eclipse.emf.ecore.EObject; | ||
4 | import org.eclipse.xtext.EcoreUtil2; | ||
5 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
6 | import org.eclipse.xtext.naming.QualifiedName; | ||
7 | import org.eclipse.xtext.resource.EObjectDescription; | ||
8 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
9 | import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; | ||
10 | import org.eclipse.xtext.util.IAcceptor; | ||
11 | |||
12 | import com.google.inject.Inject; | ||
13 | import com.google.inject.Singleton; | ||
14 | |||
15 | import tools.refinery.language.model.ProblemUtil; | ||
16 | import tools.refinery.language.model.problem.NamedElement; | ||
17 | import tools.refinery.language.model.problem.Node; | ||
18 | import tools.refinery.language.model.problem.Problem; | ||
19 | import tools.refinery.language.model.problem.Variable; | ||
20 | import tools.refinery.language.naming.NamingUtil; | ||
21 | |||
22 | @Singleton | ||
23 | public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { | ||
24 | @Inject | ||
25 | private IQualifiedNameConverter qualifiedNameConverter; | ||
26 | |||
27 | @Override | ||
28 | public boolean createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> acceptor) { | ||
29 | if (!shouldExport(eObject)) { | ||
30 | return false; | ||
31 | } | ||
32 | var qualifiedName = getNameAsQualifiedName(eObject); | ||
33 | if (qualifiedName == null) { | ||
34 | return true; | ||
35 | } | ||
36 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); | ||
37 | var problemQualifiedName = getNameAsQualifiedName(problem); | ||
38 | boolean nameExported; | ||
39 | if (shouldExportSimpleName(eObject)) { | ||
40 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); | ||
41 | nameExported = true; | ||
42 | } else { | ||
43 | nameExported = false; | ||
44 | } | ||
45 | var parent = eObject.eContainer(); | ||
46 | while (parent != null && parent != problem) { | ||
47 | var parentQualifiedName = getNameAsQualifiedName(parent); | ||
48 | if (parentQualifiedName == null) { | ||
49 | parent = parent.eContainer(); | ||
50 | continue; | ||
51 | } | ||
52 | qualifiedName = parentQualifiedName.append(qualifiedName); | ||
53 | if (shouldExportSimpleName(parent)) { | ||
54 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); | ||
55 | nameExported = true; | ||
56 | } else { | ||
57 | nameExported = false; | ||
58 | } | ||
59 | parent = parent.eContainer(); | ||
60 | } | ||
61 | if (!nameExported) { | ||
62 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); | ||
63 | } | ||
64 | return true; | ||
65 | } | ||
66 | |||
67 | protected QualifiedName getNameAsQualifiedName(EObject eObject) { | ||
68 | if (!(eObject instanceof NamedElement)) { | ||
69 | return null; | ||
70 | } | ||
71 | var namedElement = (NamedElement) eObject; | ||
72 | var name = namedElement.getName(); | ||
73 | if (NamingUtil.isNullOrEmpty(name)) { | ||
74 | return null; | ||
75 | } | ||
76 | return qualifiedNameConverter.toQualifiedName(name); | ||
77 | } | ||
78 | |||
79 | protected boolean shouldExport(EObject eObject) { | ||
80 | if (eObject instanceof Variable) { | ||
81 | // Variables are always private to the containing predicate definition. | ||
82 | return false; | ||
83 | } | ||
84 | if (eObject instanceof Node node) { | ||
85 | // Only enum literals and new nodes are visible across problem files. | ||
86 | return ProblemUtil.isIndividualNode(node) || ProblemUtil.isNewNode(node); | ||
87 | } | ||
88 | return true; | ||
89 | } | ||
90 | |||
91 | protected boolean shouldExportSimpleName(EObject eObject) { | ||
92 | if (eObject instanceof Node node) { | ||
93 | return !ProblemUtil.isNewNode(node); | ||
94 | } | ||
95 | return true; | ||
96 | } | ||
97 | |||
98 | private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, | ||
99 | IAcceptor<IEObjectDescription> acceptor) { | ||
100 | var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); | ||
101 | acceptor.accept(EObjectDescription.create(qualifiedNameWithPrefix, eObject)); | ||
102 | } | ||
103 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java new file mode 100644 index 00000000..68aa6016 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java | |||
@@ -0,0 +1,16 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import org.eclipse.emf.common.util.URI; | ||
4 | import org.eclipse.emf.ecore.resource.Resource; | ||
5 | import org.eclipse.xtext.resource.IResourceFactory; | ||
6 | |||
7 | import tools.refinery.language.model.problem.util.ProblemResourceFactoryImpl; | ||
8 | |||
9 | public class ProblemXmiResourceFactory implements IResourceFactory { | ||
10 | private Resource.Factory problemResourceFactory = new ProblemResourceFactoryImpl(); | ||
11 | |||
12 | @Override | ||
13 | public Resource createResource(URI uri) { | ||
14 | return problemResourceFactory.createResource(uri); | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java new file mode 100644 index 00000000..7525dfc6 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java | |||
@@ -0,0 +1,51 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.Map; | ||
5 | |||
6 | import org.eclipse.emf.ecore.EObject; | ||
7 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
8 | |||
9 | import com.google.inject.Inject; | ||
10 | import com.google.inject.Singleton; | ||
11 | |||
12 | import tools.refinery.language.model.problem.Problem; | ||
13 | |||
14 | @Singleton | ||
15 | public class ReferenceCounter { | ||
16 | @Inject | ||
17 | private IResourceScopeCache cache; | ||
18 | |||
19 | public int countReferences(Problem problem, EObject eObject) { | ||
20 | var count = getReferenceCounts(problem).get(eObject); | ||
21 | if (count == null) { | ||
22 | return 0; | ||
23 | } | ||
24 | return count; | ||
25 | } | ||
26 | |||
27 | protected Map<EObject, Integer> getReferenceCounts(Problem problem) { | ||
28 | var resource = problem.eResource(); | ||
29 | if (resource == null) { | ||
30 | return doGetReferenceCounts(problem); | ||
31 | } | ||
32 | return cache.get(problem, resource, () -> doGetReferenceCounts(problem)); | ||
33 | } | ||
34 | |||
35 | protected Map<EObject, Integer> doGetReferenceCounts(Problem problem) { | ||
36 | var map = new HashMap<EObject, Integer>(); | ||
37 | countCrossReferences(problem, map); | ||
38 | var iterator = problem.eAllContents(); | ||
39 | while (iterator.hasNext()) { | ||
40 | var eObject = iterator.next(); | ||
41 | countCrossReferences(eObject, map); | ||
42 | } | ||
43 | return map; | ||
44 | } | ||
45 | |||
46 | protected void countCrossReferences(EObject eObject, Map<EObject, Integer> map) { | ||
47 | for (var referencedObject : eObject.eCrossReferences()) { | ||
48 | map.compute(referencedObject, (key, currentValue) -> currentValue == null ? 1 : currentValue + 1); | ||
49 | } | ||
50 | } | ||
51 | } | ||
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 new file mode 100644 index 00000000..b582d16b --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java | |||
@@ -0,0 +1,18 @@ | |||
1 | package tools.refinery.language.scoping; | ||
2 | |||
3 | import java.util.LinkedHashSet; | ||
4 | |||
5 | import org.eclipse.emf.common.util.URI; | ||
6 | import org.eclipse.emf.ecore.resource.Resource; | ||
7 | import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; | ||
8 | |||
9 | import tools.refinery.language.model.ProblemUtil; | ||
10 | |||
11 | public class ProblemGlobalScopeProvider extends ImportUriGlobalScopeProvider { | ||
12 | @Override | ||
13 | protected LinkedHashSet<URI> getImportedUris(Resource resource) { | ||
14 | LinkedHashSet<URI> importedUris = new LinkedHashSet<>(); | ||
15 | importedUris.add(ProblemUtil.BUILTIN_LIBRARY_URI); | ||
16 | return importedUris; | ||
17 | } | ||
18 | } | ||
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 new file mode 100644 index 00000000..85797025 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java | |||
@@ -0,0 +1,42 @@ | |||
1 | package tools.refinery.language.scoping; | ||
2 | |||
3 | import java.util.List; | ||
4 | |||
5 | import org.eclipse.emf.ecore.EObject; | ||
6 | import org.eclipse.emf.ecore.resource.Resource; | ||
7 | import org.eclipse.xtext.naming.QualifiedName; | ||
8 | import org.eclipse.xtext.resource.IResourceDescriptions; | ||
9 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | ||
10 | import org.eclipse.xtext.resource.ISelectable; | ||
11 | import org.eclipse.xtext.scoping.impl.ImportNormalizer; | ||
12 | import org.eclipse.xtext.scoping.impl.ImportedNamespaceAwareLocalScopeProvider; | ||
13 | |||
14 | import com.google.inject.Inject; | ||
15 | |||
16 | import tools.refinery.language.model.ProblemUtil; | ||
17 | |||
18 | public class ProblemLocalScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { | ||
19 | private static final QualifiedName BUILTIN_LIBRARY_QUALIFIED_NAME = QualifiedName | ||
20 | .create(ProblemUtil.BUILTIN_LIBRARY_NAME); | ||
21 | |||
22 | @Inject | ||
23 | private IResourceDescriptionsProvider resourceDescriptionsProvider; | ||
24 | |||
25 | @Override | ||
26 | protected List<ImportNormalizer> getImplicitImports(boolean ignoreCase) { | ||
27 | return List.of(doCreateImportNormalizer(BUILTIN_LIBRARY_QUALIFIED_NAME, true, ignoreCase)); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | protected List<ImportNormalizer> getImportedNamespaceResolvers(EObject context, boolean ignoreCase) { | ||
32 | return List.of(); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | protected ISelectable internalGetAllDescriptions(Resource resource) { | ||
37 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. | ||
38 | IResourceDescriptions resourceDescriptions = resourceDescriptionsProvider | ||
39 | .getResourceDescriptions(resource.getResourceSet()); | ||
40 | return resourceDescriptions.getResourceDescription(resource.getURI()); | ||
41 | } | ||
42 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java new file mode 100644 index 00000000..d31a5308 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java | |||
@@ -0,0 +1,104 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.scoping; | ||
5 | |||
6 | import java.util.ArrayList; | ||
7 | import java.util.List; | ||
8 | |||
9 | import org.eclipse.emf.ecore.EObject; | ||
10 | import org.eclipse.emf.ecore.EReference; | ||
11 | import org.eclipse.xtext.EcoreUtil2; | ||
12 | import org.eclipse.xtext.scoping.IScope; | ||
13 | import org.eclipse.xtext.scoping.Scopes; | ||
14 | |||
15 | import tools.refinery.language.model.ProblemUtil; | ||
16 | import tools.refinery.language.model.problem.ClassDeclaration; | ||
17 | import tools.refinery.language.model.problem.ExistentialQuantifier; | ||
18 | import tools.refinery.language.model.problem.NewActionLiteral; | ||
19 | import tools.refinery.language.model.problem.ParametricDefinition; | ||
20 | import tools.refinery.language.model.problem.Action; | ||
21 | import tools.refinery.language.model.problem.Problem; | ||
22 | import tools.refinery.language.model.problem.ProblemPackage; | ||
23 | import tools.refinery.language.model.problem.ReferenceDeclaration; | ||
24 | import tools.refinery.language.model.problem.Variable; | ||
25 | import tools.refinery.language.model.problem.VariableOrNodeArgument; | ||
26 | |||
27 | /** | ||
28 | * This class contains custom scoping description. | ||
29 | * | ||
30 | * See | ||
31 | * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#scoping | ||
32 | * on how and when to use it. | ||
33 | */ | ||
34 | public class ProblemScopeProvider extends AbstractProblemScopeProvider { | ||
35 | |||
36 | @Override | ||
37 | public IScope getScope(EObject context, EReference reference) { | ||
38 | var scope = super.getScope(context, reference); | ||
39 | if (reference == ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE | ||
40 | || reference == ProblemPackage.Literals.NODE_VALUE_ASSERTION__NODE) { | ||
41 | return getNodesScope(context, scope); | ||
42 | } | ||
43 | if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE | ||
44 | || reference == ProblemPackage.Literals.DELETE_ACTION_LITERAL__VARIABLE_OR_NODE) { | ||
45 | return getVariableScope(context, scope); | ||
46 | } | ||
47 | if (reference == ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE) { | ||
48 | return getOppositeScope(context, scope); | ||
49 | } | ||
50 | return scope; | ||
51 | } | ||
52 | |||
53 | protected IScope getNodesScope(EObject context, IScope delegateScope) { | ||
54 | var problem = EcoreUtil2.getContainerOfType(context, Problem.class); | ||
55 | if (problem == null) { | ||
56 | return delegateScope; | ||
57 | } | ||
58 | return Scopes.scopeFor(problem.getNodes(), delegateScope); | ||
59 | } | ||
60 | |||
61 | protected IScope getVariableScope(EObject context, IScope delegateScope) { | ||
62 | List<Variable> variables = new ArrayList<>(); | ||
63 | EObject currentContext = context; | ||
64 | if (context instanceof VariableOrNodeArgument argument) { | ||
65 | Variable singletonVariable = argument.getSingletonVariable(); | ||
66 | if (singletonVariable != null) { | ||
67 | variables.add(singletonVariable); | ||
68 | } | ||
69 | } | ||
70 | while (currentContext != null && !(currentContext instanceof ParametricDefinition)) { | ||
71 | if (currentContext instanceof ExistentialQuantifier quantifier) { | ||
72 | variables.addAll(quantifier.getImplicitVariables()); | ||
73 | } else | ||
74 | if(currentContext instanceof Action action) { | ||
75 | for (var literal : action.getActionLiterals()) { | ||
76 | if(literal instanceof NewActionLiteral newActionLiteral && newActionLiteral.getVariable() != null) { | ||
77 | variables.add(newActionLiteral.getVariable()); | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | currentContext = currentContext.eContainer(); | ||
82 | } | ||
83 | IScope parentScope = getNodesScope(context, delegateScope); | ||
84 | if (currentContext != null) { | ||
85 | ParametricDefinition definition = (ParametricDefinition) currentContext; | ||
86 | parentScope = Scopes.scopeFor(definition.getParameters(),parentScope); | ||
87 | } | ||
88 | return Scopes.scopeFor(variables,parentScope); | ||
89 | } | ||
90 | |||
91 | protected IScope getOppositeScope(EObject context, IScope delegateScope) { | ||
92 | var referenceDeclaration = EcoreUtil2.getContainerOfType(context, ReferenceDeclaration.class); | ||
93 | if (referenceDeclaration == null) { | ||
94 | return delegateScope; | ||
95 | } | ||
96 | var relation = referenceDeclaration.getReferenceType(); | ||
97 | if (!(relation instanceof ClassDeclaration)) { | ||
98 | return delegateScope; | ||
99 | } | ||
100 | var classDeclaration = (ClassDeclaration) relation; | ||
101 | var referenceDeclarations = ProblemUtil.getAllReferenceDeclarations(classDeclaration); | ||
102 | return Scopes.scopeFor(referenceDeclarations, delegateScope); | ||
103 | } | ||
104 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java new file mode 100644 index 00000000..975fdca2 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.validation; | ||
5 | |||
6 | import org.eclipse.xtext.EcoreUtil2; | ||
7 | import org.eclipse.xtext.validation.Check; | ||
8 | |||
9 | import com.google.inject.Inject; | ||
10 | |||
11 | import tools.refinery.language.model.ProblemUtil; | ||
12 | import tools.refinery.language.model.problem.Node; | ||
13 | import tools.refinery.language.model.problem.Problem; | ||
14 | import tools.refinery.language.model.problem.ProblemPackage; | ||
15 | import tools.refinery.language.model.problem.Variable; | ||
16 | import tools.refinery.language.model.problem.VariableOrNodeArgument; | ||
17 | import tools.refinery.language.resource.ReferenceCounter; | ||
18 | |||
19 | /** | ||
20 | * This class contains custom validation rules. | ||
21 | * | ||
22 | * See | ||
23 | * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation | ||
24 | */ | ||
25 | public class ProblemValidator extends AbstractProblemValidator { | ||
26 | private static final String ISSUE_PREFIX = "tools.refinery.language.validation.ProblemValidator."; | ||
27 | |||
28 | public static final String SINGLETON_VARIABLE_ISSUE = ISSUE_PREFIX + "SINGLETON_VARIABLE"; | ||
29 | |||
30 | public static final String NON_INDIVIDUAL_NODE_ISSUE = ISSUE_PREFIX + "NON_INDIVIDUAL_NODE"; | ||
31 | |||
32 | @Inject | ||
33 | private ReferenceCounter referenceCounter; | ||
34 | |||
35 | @Check | ||
36 | public void checkUniqueVariable(VariableOrNodeArgument argument) { | ||
37 | var variableOrNode = argument.getVariableOrNode(); | ||
38 | if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) | ||
39 | && !ProblemUtil.isSingletonVariable(variable)) { | ||
40 | var problem = EcoreUtil2.getContainerOfType(variable, Problem.class); | ||
41 | if (problem != null && referenceCounter.countReferences(problem, variable) <= 1) { | ||
42 | var name = variable.getName(); | ||
43 | var message = "Variable '%s' has only a single reference. Add another reference or mark is as a singleton variable: '_%s'" | ||
44 | .formatted(name, name); | ||
45 | warning(message, argument, ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE, | ||
46 | INSIGNIFICANT_INDEX, SINGLETON_VARIABLE_ISSUE); | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | @Check | ||
52 | public void checkNonUniqueNode(VariableOrNodeArgument argument) { | ||
53 | var variableOrNode = argument.getVariableOrNode(); | ||
54 | if (variableOrNode instanceof Node node && !ProblemUtil.isIndividualNode(node)) { | ||
55 | var name = node.getName(); | ||
56 | var message = "Only individual nodes can be referenced in predicates. Mark '%s' as individual with the declaration 'indiv %s.'" | ||
57 | .formatted(name, name); | ||
58 | error(message, argument, ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE, | ||
59 | INSIGNIFICANT_INDEX, NON_INDIVIDUAL_NODE_ISSUE); | ||
60 | } | ||
61 | } | ||
62 | } | ||
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.xtend b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.xtend new file mode 100644 index 00000000..53d31a6c --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.xtend | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.26.0.M1 | ||
3 | */ | ||
4 | package tools.refinery.language.tests | ||
5 | |||
6 | import com.google.inject.Inject | ||
7 | import org.eclipse.xtext.testing.InjectWith | ||
8 | import org.eclipse.xtext.testing.extensions.InjectionExtension | ||
9 | import org.eclipse.xtext.testing.util.ParseHelper | ||
10 | import org.junit.jupiter.api.Test | ||
11 | import org.junit.jupiter.api.^extension.ExtendWith | ||
12 | import tools.refinery.language.model.problem.Problem | ||
13 | import tools.refinery.language.model.tests.ProblemTestUtil | ||
14 | |||
15 | import static org.hamcrest.MatcherAssert.assertThat | ||
16 | import static org.hamcrest.Matchers.* | ||
17 | |||
18 | @ExtendWith(InjectionExtension) | ||
19 | @InjectWith(ProblemInjectorProvider) | ||
20 | class ProblemParsingTest { | ||
21 | @Inject | ||
22 | ParseHelper<Problem> parseHelper | ||
23 | |||
24 | @Inject | ||
25 | extension ProblemTestUtil | ||
26 | |||
27 | @Test | ||
28 | def void exampleTest() { | ||
29 | val it = parseHelper.parse(''' | ||
30 | class Family { | ||
31 | contains Person[] members | ||
32 | } | ||
33 | |||
34 | class Person { | ||
35 | Person[0..*] children opposite parent | ||
36 | Person[0..1] parent opposite children | ||
37 | int age | ||
38 | TaxStatus taxStatus | ||
39 | } | ||
40 | |||
41 | enum TaxStatus { | ||
42 | child, student, adult, retired | ||
43 | } | ||
44 | |||
45 | % A child cannot have any dependents. | ||
46 | error invalidTaxStatus(Person p) <-> | ||
47 | taxStatus(p, child), children(p, _q). | ||
48 | |||
49 | indiv family. | ||
50 | Family(family). | ||
51 | members(family, anne): true. | ||
52 | members(family, bob). | ||
53 | members(family, ciri). | ||
54 | children(anne, ciri). | ||
55 | ?children(bob, ciri). | ||
56 | taxStatus(anne, adult). | ||
57 | age(anne, 35). | ||
58 | bobAge: 27. | ||
59 | age(bob, bobAge). | ||
60 | !age(ciri, bobAge). | ||
61 | ''') | ||
62 | assertThat(errors, empty) | ||
63 | } | ||
64 | } | ||
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java new file mode 100644 index 00000000..41ad2d31 --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java | |||
@@ -0,0 +1,235 @@ | |||
1 | package tools.refinery.language.tests.formatting2; | ||
2 | |||
3 | import static org.hamcrest.MatcherAssert.assertThat; | ||
4 | import static org.hamcrest.Matchers.equalTo; | ||
5 | |||
6 | import java.util.List; | ||
7 | |||
8 | import org.eclipse.xtext.formatting2.FormatterRequest; | ||
9 | import org.eclipse.xtext.formatting2.IFormatter2; | ||
10 | import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; | ||
11 | import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement; | ||
12 | import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder; | ||
13 | import org.eclipse.xtext.resource.XtextResource; | ||
14 | import org.eclipse.xtext.testing.InjectWith; | ||
15 | import org.eclipse.xtext.testing.extensions.InjectionExtension; | ||
16 | import org.eclipse.xtext.testing.util.ParseHelper; | ||
17 | import org.junit.jupiter.api.Test; | ||
18 | import org.junit.jupiter.api.extension.ExtendWith; | ||
19 | |||
20 | import com.google.inject.Inject; | ||
21 | import com.google.inject.Provider; | ||
22 | |||
23 | import tools.refinery.language.model.problem.Problem; | ||
24 | import tools.refinery.language.tests.ProblemInjectorProvider; | ||
25 | |||
26 | @ExtendWith(InjectionExtension.class) | ||
27 | @InjectWith(ProblemInjectorProvider.class) | ||
28 | class ProblemFormatterTest { | ||
29 | @Inject | ||
30 | private ParseHelper<Problem> parseHelper; | ||
31 | |||
32 | @Inject | ||
33 | private Provider<FormatterRequest> formatterRequestProvider; | ||
34 | |||
35 | @Inject | ||
36 | private TextRegionAccessBuilder regionBuilder; | ||
37 | |||
38 | @Inject | ||
39 | private IFormatter2 formatter2; | ||
40 | |||
41 | @Test | ||
42 | void problemNameTest() { | ||
43 | testFormatter(" problem problem . ", "problem problem.\n"); | ||
44 | } | ||
45 | |||
46 | @Test | ||
47 | void assertionTest() { | ||
48 | testFormatter(" equals ( a , b , * ) : true . ", "equals(a, b, *): true.\n"); | ||
49 | } | ||
50 | |||
51 | @Test | ||
52 | void defaultAssertionTest() { | ||
53 | testFormatter(" default equals ( a , b , * ) : true . ", "default equals(a, b, *): true.\n"); | ||
54 | } | ||
55 | |||
56 | @Test | ||
57 | void assertionShortTrueTest() { | ||
58 | testFormatter(" equals ( a , b , * ) . ", "equals(a, b, *).\n"); | ||
59 | } | ||
60 | |||
61 | @Test | ||
62 | void defaultAssertionShortTrueTest() { | ||
63 | testFormatter(" default equals ( a , b , * ) . ", "default equals(a, b, *).\n"); | ||
64 | } | ||
65 | |||
66 | @Test | ||
67 | void assertionShortFalseTest() { | ||
68 | testFormatter(" ! equals ( a , b , * ) . ", "!equals(a, b, *).\n"); | ||
69 | } | ||
70 | |||
71 | @Test | ||
72 | void defaultAssertionShortFalseTest() { | ||
73 | testFormatter(" default ! equals ( a , b , * ) . ", "default !equals(a, b, *).\n"); | ||
74 | } | ||
75 | |||
76 | @Test | ||
77 | void assertionShortUnknownTest() { | ||
78 | testFormatter(" ? equals ( a , b , * ) . ", "?equals(a, b, *).\n"); | ||
79 | } | ||
80 | |||
81 | @Test | ||
82 | void defaultAssertionShortUnknownTest() { | ||
83 | testFormatter(" default ? equals ( a , b , * ) . ", "default ?equals(a, b, *).\n"); | ||
84 | } | ||
85 | |||
86 | @Test | ||
87 | void multipleAssertionsTest() { | ||
88 | testFormatter(" exists ( a ) . ? equals ( a , a ).", """ | ||
89 | exists(a). | ||
90 | ?equals(a, a). | ||
91 | """); | ||
92 | } | ||
93 | |||
94 | @Test | ||
95 | void multipleAssertionsNamedProblemTest() { | ||
96 | testFormatter(" problem foo . exists ( a ) . ? equals ( a , a ).", """ | ||
97 | problem foo. | ||
98 | |||
99 | exists(a). | ||
100 | ?equals(a, a). | ||
101 | """); | ||
102 | } | ||
103 | |||
104 | @Test | ||
105 | void classWithoutBodyTest() { | ||
106 | testFormatter(" class Foo . ", "class Foo.\n"); | ||
107 | } | ||
108 | |||
109 | @Test | ||
110 | void abstractClassWithoutBodyTest() { | ||
111 | testFormatter(" abstract class Foo . ", "abstract class Foo.\n"); | ||
112 | } | ||
113 | |||
114 | @Test | ||
115 | void classExtendsWithoutBodyTest() { | ||
116 | testFormatter(" class Foo. class Bar . class Quux extends Foo , Bar . ", """ | ||
117 | class Foo. | ||
118 | |||
119 | class Bar. | ||
120 | |||
121 | class Quux extends Foo, Bar. | ||
122 | """); | ||
123 | } | ||
124 | |||
125 | @Test | ||
126 | void classWithEmptyBodyTest() { | ||
127 | testFormatter(" class Foo { } ", """ | ||
128 | class Foo { | ||
129 | } | ||
130 | """); | ||
131 | } | ||
132 | |||
133 | @Test | ||
134 | void classExtendsWithBodyTest() { | ||
135 | testFormatter(" class Foo. class Bar . class Quux extends Foo , Bar { } ", """ | ||
136 | class Foo. | ||
137 | |||
138 | class Bar. | ||
139 | |||
140 | class Quux extends Foo, Bar { | ||
141 | } | ||
142 | """); | ||
143 | } | ||
144 | |||
145 | @Test | ||
146 | void predicateWithoutBodyTest() { | ||
147 | testFormatter(" pred foo ( node a , b ) . ", "pred foo(node a, b).\n"); | ||
148 | } | ||
149 | |||
150 | @Test | ||
151 | void predicateWithBodyTest() { | ||
152 | testFormatter( | ||
153 | " pred foo ( node a , b ) <-> equal (a , _c ) , ! equal ( a , b ) ; equal+( a , b ) . ", | ||
154 | "pred foo(node a, b) <-> equal(a, _c), !equal(a, b); equal+(a, b).\n"); | ||
155 | } | ||
156 | |||
157 | @Test | ||
158 | void predicatesWithoutBodyTest() { | ||
159 | testFormatter(" pred foo ( node a , b ) . pred bar ( node c ) . ", """ | ||
160 | pred foo(node a, b). | ||
161 | |||
162 | pred bar(node c). | ||
163 | """); | ||
164 | } | ||
165 | |||
166 | @Test | ||
167 | void predicateCommentsTest() { | ||
168 | testFormatter(""" | ||
169 | % Some foo | ||
170 | pred foo ( node a , b ) . | ||
171 | % Some bar | ||
172 | pred bar ( node c ) . | ||
173 | """, """ | ||
174 | % Some foo | ||
175 | pred foo(node a, b). | ||
176 | |||
177 | % Some bar | ||
178 | pred bar(node c). | ||
179 | """); | ||
180 | } | ||
181 | |||
182 | @Test | ||
183 | void individualDeclarationTest() { | ||
184 | testFormatter(" indiv a , b . ", "indiv a, b.\n"); | ||
185 | } | ||
186 | |||
187 | @Test | ||
188 | void mixedDeclarationsTest() { | ||
189 | testFormatter(""" | ||
190 | problem test. | ||
191 | pred foo(node a). | ||
192 | class Foo. | ||
193 | foo(n1, n2). | ||
194 | indiv i1. | ||
195 | !foo(i1, n1). | ||
196 | pred bar(node a, node b). | ||
197 | pred quux(). | ||
198 | default !bar(*, *). | ||
199 | """, """ | ||
200 | problem test. | ||
201 | |||
202 | pred foo(node a). | ||
203 | |||
204 | class Foo. | ||
205 | |||
206 | foo(n1, n2). | ||
207 | indiv i1. | ||
208 | !foo(i1, n1). | ||
209 | |||
210 | pred bar(node a, node b). | ||
211 | |||
212 | pred quux(). | ||
213 | |||
214 | default !bar(*, *). | ||
215 | """); | ||
216 | } | ||
217 | |||
218 | private void testFormatter(String toFormat, String expected) { | ||
219 | Problem problem; | ||
220 | try { | ||
221 | problem = parseHelper.parse(toFormat); | ||
222 | } catch (Exception e) { | ||
223 | throw new RuntimeException("Failed to parse document", e); | ||
224 | } | ||
225 | var resource = (XtextResource) problem.eResource(); | ||
226 | FormatterRequest request = formatterRequestProvider.get(); | ||
227 | request.setAllowIdentityEdits(false); | ||
228 | request.setFormatUndefinedHiddenRegionsOnly(false); | ||
229 | ITextRegionAccess regionAccess = regionBuilder.forNodeModel(resource).create(); | ||
230 | request.setTextRegionAccess(regionAccess); | ||
231 | List<ITextReplacement> replacements = formatter2.format(request); | ||
232 | var formattedString = regionAccess.getRewriter().renderToString(replacements); | ||
233 | assertThat(formattedString, equalTo(expected)); | ||
234 | } | ||
235 | } | ||
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/rules/DirectRuleParsingTest.xtend b/subprojects/language/src/test/java/tools/refinery/language/tests/rules/DirectRuleParsingTest.xtend new file mode 100644 index 00000000..d60651a0 --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/rules/DirectRuleParsingTest.xtend | |||
@@ -0,0 +1,96 @@ | |||
1 | package tools.refinery.language.tests.rules | ||
2 | |||
3 | import com.google.inject.Inject | ||
4 | import org.eclipse.xtext.testing.InjectWith | ||
5 | import org.eclipse.xtext.testing.extensions.InjectionExtension | ||
6 | import org.eclipse.xtext.testing.util.ParseHelper | ||
7 | import org.junit.jupiter.api.Test | ||
8 | import org.junit.jupiter.api.^extension.ExtendWith | ||
9 | import tools.refinery.language.model.problem.Problem | ||
10 | import tools.refinery.language.tests.ProblemInjectorProvider | ||
11 | import tools.refinery.language.model.tests.ProblemTestUtil | ||
12 | |||
13 | import static org.hamcrest.MatcherAssert.assertThat | ||
14 | import static org.hamcrest.Matchers.* | ||
15 | |||
16 | @ExtendWith(InjectionExtension) | ||
17 | @InjectWith(ProblemInjectorProvider) | ||
18 | class DirectRuleParsingTest { | ||
19 | @Inject | ||
20 | ParseHelper<Problem> parseHelper | ||
21 | |||
22 | @Inject | ||
23 | extension ProblemTestUtil | ||
24 | |||
25 | @Test | ||
26 | def void relationValueRewriteTest() { | ||
27 | val it = parseHelper.parse(''' | ||
28 | pred Person(p). | ||
29 | direct rule r(p1): Person(p1) = true ~> Person(p1) = false. | ||
30 | ''') | ||
31 | assertThat(errors, empty) | ||
32 | } | ||
33 | |||
34 | @Test | ||
35 | def void relationValueMergeTest() { | ||
36 | val it = parseHelper.parse(''' | ||
37 | pred Person(p). | ||
38 | direct rule r(p1): Person(p1): true ~> Person(p1): false. | ||
39 | ''') | ||
40 | assertThat(errors, empty) | ||
41 | } | ||
42 | |||
43 | @Test | ||
44 | def void newNodeTest() { | ||
45 | val it = parseHelper.parse(''' | ||
46 | pred Person(p). | ||
47 | direct rule r(p1): Person(p1) = true ~> new p2, Person(p2) = unknown. | ||
48 | ''') | ||
49 | assertThat(errors, empty) | ||
50 | assertThat(rule("r").param(0), equalTo(rule("r").conj(0).lit(0).valueAtom.arg(0).variable)) | ||
51 | assertThat(rule("r").actionLit(0).newVar, | ||
52 | equalTo(rule("r").actionLit(1).valueAtom.arg(0).variable) | ||
53 | ) | ||
54 | } | ||
55 | |||
56 | @Test | ||
57 | def void differentScopeTest() { | ||
58 | val it = parseHelper.parse(''' | ||
59 | pred Friend(a, b). | ||
60 | direct rule r(p1): Friend(p1, p2) = false ~> new p2, Friend(p1, p2) = true. | ||
61 | ''') | ||
62 | assertThat(errors, empty) | ||
63 | assertThat(rule("r").conj(0).lit(0).valueAtom.arg(1).variable, | ||
64 | not(equalTo(rule("r").actionLit(1).valueAtom.arg(1).variable))) | ||
65 | } | ||
66 | |||
67 | @Test | ||
68 | def void parameterShadowingTest() { | ||
69 | val it = parseHelper.parse(''' | ||
70 | pred Friend(a, b). | ||
71 | direct rule r(p1, p2): Friend(p1, p2) = false ~> new p2, Friend(p1, p2) = true. | ||
72 | ''') | ||
73 | assertThat(errors, empty) | ||
74 | assertThat(rule("r").param(1), | ||
75 | not(equalTo(rule("r").actionLit(1).valueAtom.arg(1).variable))) | ||
76 | } | ||
77 | |||
78 | @Test | ||
79 | def void deleteParameterNodeTest() { | ||
80 | val it = parseHelper.parse(''' | ||
81 | pred Person(p). | ||
82 | direct rule r(p1): Person(p1): false ~> delete p1. | ||
83 | ''') | ||
84 | assertThat(errors, empty) | ||
85 | } | ||
86 | |||
87 | @Test | ||
88 | def void deleteDifferentScopeNodeTest() { | ||
89 | val it = parseHelper.parse(''' | ||
90 | pred Friend(p). | ||
91 | direct rule r(p1): Friend(p1, p2) = true ~> delete p2. | ||
92 | ''') | ||
93 | assertThat(errors, not(empty)) | ||
94 | } | ||
95 | |||
96 | } | ||
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.xtend b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.xtend new file mode 100644 index 00000000..3a046341 --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.xtend | |||
@@ -0,0 +1,322 @@ | |||
1 | package tools.refinery.language.tests.scoping | ||
2 | |||
3 | import com.google.inject.Inject | ||
4 | import java.util.stream.Stream | ||
5 | import org.eclipse.xtext.testing.InjectWith | ||
6 | import org.eclipse.xtext.testing.extensions.InjectionExtension | ||
7 | import org.eclipse.xtext.testing.util.ParseHelper | ||
8 | import org.junit.jupiter.api.Test | ||
9 | import org.junit.jupiter.api.^extension.ExtendWith | ||
10 | import org.junit.jupiter.params.ParameterizedTest | ||
11 | import org.junit.jupiter.params.provider.Arguments | ||
12 | import org.junit.jupiter.params.provider.MethodSource | ||
13 | import org.junit.jupiter.params.provider.ValueSource | ||
14 | import tools.refinery.language.model.problem.Problem | ||
15 | import tools.refinery.language.model.tests.ProblemTestUtil | ||
16 | import tools.refinery.language.tests.ProblemInjectorProvider | ||
17 | |||
18 | import static org.hamcrest.MatcherAssert.assertThat | ||
19 | import static org.hamcrest.Matchers.* | ||
20 | |||
21 | @ExtendWith(InjectionExtension) | ||
22 | @InjectWith(ProblemInjectorProvider) | ||
23 | class NodeScopingTest { | ||
24 | @Inject | ||
25 | ParseHelper<Problem> parseHelper | ||
26 | |||
27 | @Inject | ||
28 | extension ProblemTestUtil | ||
29 | |||
30 | @ParameterizedTest | ||
31 | @ValueSource(strings=#["", "builtin::"]) | ||
32 | def void builtInArgumentTypeTest(String prefix) { | ||
33 | val it = parseHelper.parse(''' | ||
34 | pred predicate(«prefix»node a, «prefix»data b, «prefix»int c). | ||
35 | ''') | ||
36 | assertThat(errors, empty) | ||
37 | assertThat(pred('predicate').param(0).parameterType, equalTo(builtin.findClass('node'))) | ||
38 | assertThat(pred('predicate').param(1).parameterType, equalTo(builtin.findClass('data'))) | ||
39 | assertThat(pred('predicate').param(2).parameterType, equalTo(builtin.findClass('int'))) | ||
40 | } | ||
41 | |||
42 | @Test | ||
43 | def void implicitNodeInAssertionTest() { | ||
44 | val it = parseHelper.parse(''' | ||
45 | pred predicate(node x, node y) <-> node(x). | ||
46 | predicate(a, a). | ||
47 | ?predicate(a, b). | ||
48 | ''') | ||
49 | assertThat(errors, empty) | ||
50 | assertThat(nodeNames, hasItems('a', 'b')) | ||
51 | assertThat(assertion(0).arg(0).node, equalTo(node('a'))) | ||
52 | assertThat(assertion(0).arg(1).node, equalTo(node('a'))) | ||
53 | assertThat(assertion(1).arg(0).node, equalTo(node('a'))) | ||
54 | assertThat(assertion(1).arg(1).node, equalTo(node('b'))) | ||
55 | } | ||
56 | |||
57 | @Test | ||
58 | def void implicitNodeInNodeValueAssertionTest() { | ||
59 | val it = parseHelper.parse(''' | ||
60 | a: 16. | ||
61 | ''') | ||
62 | assertThat(errors, empty) | ||
63 | assertThat(nodeNames, hasItems('a')) | ||
64 | assertThat(nodeValueAssertion(0).node, equalTo(node('a'))) | ||
65 | } | ||
66 | |||
67 | @Test | ||
68 | def void implicitNodeInPredicateTest() { | ||
69 | val it = parseHelper.parse(''' | ||
70 | pred predicate(node a) <-> node(b). | ||
71 | predicate(b). | ||
72 | ''') | ||
73 | assertThat(errors, empty) | ||
74 | assertThat(nodeNames, hasItem("b")) | ||
75 | assertThat(pred("predicate").conj(0).lit(0).arg(0).node, equalTo(node("b"))) | ||
76 | assertThat(assertion(0).arg(0).node, equalTo(node("b"))) | ||
77 | } | ||
78 | |||
79 | @ParameterizedTest | ||
80 | @MethodSource("individualNodeReferenceSource") | ||
81 | def void individualNodeInAssertionTest(String qualifiedNamePrefix, boolean namedProblem) { | ||
82 | val it = parseHelper.parse(''' | ||
83 | «IF namedProblem»problem test.«ENDIF» | ||
84 | indiv a, b. | ||
85 | pred predicate(node x, node y) <-> node(x). | ||
86 | predicate(«qualifiedNamePrefix»a, «qualifiedNamePrefix»a). | ||
87 | ?predicate(«qualifiedNamePrefix»a, «qualifiedNamePrefix»b). | ||
88 | ''') | ||
89 | assertThat(errors, empty) | ||
90 | assertThat(nodeNames, empty) | ||
91 | assertThat(assertion(0).arg(0).node, equalTo(individualNode('a'))) | ||
92 | assertThat(assertion(0).arg(1).node, equalTo(individualNode('a'))) | ||
93 | assertThat(assertion(1).arg(0).node, equalTo(individualNode('a'))) | ||
94 | assertThat(assertion(1).arg(1).node, equalTo(individualNode('b'))) | ||
95 | } | ||
96 | |||
97 | @ParameterizedTest | ||
98 | @MethodSource("individualNodeReferenceSource") | ||
99 | def void individualNodeInNodeValueAssertionTest(String qualifiedNamePrefix, boolean namedProblem) { | ||
100 | val it = parseHelper.parse(''' | ||
101 | «IF namedProblem»problem test.«ENDIF» | ||
102 | indiv a. | ||
103 | «qualifiedNamePrefix»a: 16. | ||
104 | ''') | ||
105 | assertThat(errors, empty) | ||
106 | assertThat(nodeNames, empty) | ||
107 | assertThat(nodeValueAssertion(0).node, equalTo(individualNode('a'))) | ||
108 | } | ||
109 | |||
110 | @ParameterizedTest | ||
111 | @MethodSource("individualNodeReferenceSource") | ||
112 | def void individualNodeInPredicateTest(String qualifiedNamePrefix, boolean namedProblem) { | ||
113 | val it = parseHelper.parse(''' | ||
114 | «IF namedProblem»problem test.«ENDIF» | ||
115 | indiv b. | ||
116 | pred predicate(node a) <-> node(«qualifiedNamePrefix»b). | ||
117 | ''') | ||
118 | assertThat(errors, empty) | ||
119 | assertThat(nodeNames, empty) | ||
120 | assertThat(pred("predicate").conj(0).lit(0).arg(0).node, equalTo(individualNode("b"))) | ||
121 | } | ||
122 | |||
123 | static def individualNodeReferenceSource() { | ||
124 | Stream.of( | ||
125 | Arguments.of("", false), | ||
126 | Arguments.of("", true), | ||
127 | Arguments.of("test::", true) | ||
128 | ) | ||
129 | } | ||
130 | |||
131 | @ParameterizedTest | ||
132 | @MethodSource("builtInNodeReferencesSource") | ||
133 | def void builtInNodeTest(String qualifiedName) { | ||
134 | val it = parseHelper.parse(''' | ||
135 | pred predicate(node x) <-> node(x). | ||
136 | predicate(«qualifiedName»). | ||
137 | ''') | ||
138 | assertThat(errors, empty) | ||
139 | assertThat(nodes, empty) | ||
140 | assertThat(assertion(0).arg(0).node, equalTo(builtin.findClass('int').newNode)) | ||
141 | } | ||
142 | |||
143 | @ParameterizedTest | ||
144 | @MethodSource("builtInNodeReferencesSource") | ||
145 | def void builtInNodeInNodeValueAssertionTest(String qualifiedName) { | ||
146 | val it = parseHelper.parse(''' | ||
147 | «qualifiedName»: 16. | ||
148 | ''') | ||
149 | assertThat(errors, empty) | ||
150 | assertThat(nodes, empty) | ||
151 | assertThat(nodeValueAssertion(0).node, equalTo(builtin.findClass('int').newNode)) | ||
152 | } | ||
153 | |||
154 | @ParameterizedTest | ||
155 | @MethodSource("builtInNodeReferencesSource") | ||
156 | def void builtInNodeInPredicateTest(String qualifiedName) { | ||
157 | val it = parseHelper.parse(''' | ||
158 | pred predicate(node x) <-> node(«qualifiedName»). | ||
159 | ''') | ||
160 | assertThat(errors, empty) | ||
161 | assertThat(pred("predicate").conj(0).lit(0).arg(0).node, equalTo(builtin.findClass('int').newNode)) | ||
162 | } | ||
163 | |||
164 | static def builtInNodeReferencesSource() { | ||
165 | Stream.of( | ||
166 | Arguments.of("int::new"), | ||
167 | Arguments.of("builtin::int::new") | ||
168 | ) | ||
169 | } | ||
170 | |||
171 | @ParameterizedTest(name="{0}, namedProblem={1}") | ||
172 | @MethodSource("classNewNodeReferencesSource") | ||
173 | def void classNewNodeTest(String qualifiedName, boolean namedProblem) { | ||
174 | val it = parseHelper.parse(''' | ||
175 | «IF namedProblem»problem test.«ENDIF» | ||
176 | class Foo. | ||
177 | pred predicate(node x) <-> node(x). | ||
178 | predicate(«qualifiedName»). | ||
179 | ''') | ||
180 | assertThat(errors, empty) | ||
181 | assertThat(nodes, empty) | ||
182 | assertThat(assertion(0).arg(0).node, equalTo(findClass('Foo').newNode)) | ||
183 | } | ||
184 | |||
185 | @ParameterizedTest(name="{0}, namedProblem={1}") | ||
186 | @MethodSource("classNewNodeReferencesSource") | ||
187 | def void classNewNodeInNodeValueAssertionTest(String qualifiedName, boolean namedProblem) { | ||
188 | val it = parseHelper.parse(''' | ||
189 | «IF namedProblem»problem test.«ENDIF» | ||
190 | class Foo. | ||
191 | «qualifiedName»: 16. | ||
192 | ''') | ||
193 | assertThat(errors, empty) | ||
194 | assertThat(nodes, empty) | ||
195 | assertThat(nodeValueAssertion(0).node, equalTo(findClass('Foo').newNode)) | ||
196 | } | ||
197 | |||
198 | @ParameterizedTest(name="{0}, namedProblem={1}") | ||
199 | @MethodSource("classNewNodeReferencesSource") | ||
200 | def void classNewNodeInPredicateTest(String qualifiedName, boolean namedProblem) { | ||
201 | val it = parseHelper.parse(''' | ||
202 | «IF namedProblem»problem test.«ENDIF» | ||
203 | class Foo. | ||
204 | pred predicate(node x) <-> node(«qualifiedName»). | ||
205 | ''') | ||
206 | assertThat(errors, empty) | ||
207 | assertThat(pred("predicate").conj(0).lit(0).arg(0).node, equalTo(findClass('Foo').newNode)) | ||
208 | } | ||
209 | |||
210 | static def classNewNodeReferencesSource() { | ||
211 | Stream.of( | ||
212 | Arguments.of("Foo::new", false), | ||
213 | Arguments.of("Foo::new", true), | ||
214 | Arguments.of("test::Foo::new", true) | ||
215 | ) | ||
216 | } | ||
217 | |||
218 | @Test | ||
219 | def void newNodeIsNotSpecial() { | ||
220 | val it = parseHelper.parse(''' | ||
221 | class Foo. | ||
222 | pred predicate(node x) <-> node(x). | ||
223 | predicate(new). | ||
224 | ''') | ||
225 | assertThat(errors, empty) | ||
226 | assertThat(nodeNames, hasItem('new')) | ||
227 | assertThat(assertion(0).arg(0).node, not(equalTo(findClass('Foo').newNode))) | ||
228 | } | ||
229 | |||
230 | @ParameterizedTest(name="{0}, namedProblem={1}") | ||
231 | @MethodSource("enumLiteralReferencesSource") | ||
232 | def void enumLiteralTest(String qualifiedName, boolean namedProblem) { | ||
233 | val it = parseHelper.parse(''' | ||
234 | «IF namedProblem»problem test.«ENDIF» | ||
235 | enum Foo { alpha, beta } | ||
236 | pred predicate(Foo a) <-> node(a). | ||
237 | predicate(«qualifiedName»). | ||
238 | ''') | ||
239 | assertThat(errors, empty) | ||
240 | assertThat(nodes, empty) | ||
241 | assertThat(assertion(0).arg(0).node, equalTo(findEnum("Foo").literal("alpha"))) | ||
242 | } | ||
243 | |||
244 | @ParameterizedTest(name="{0}, namedProblem={1}") | ||
245 | @MethodSource("enumLiteralReferencesSource") | ||
246 | def void enumLiteralInNodeValueAssertionTest(String qualifiedName, boolean namedProblem) { | ||
247 | val it = parseHelper.parse(''' | ||
248 | «IF namedProblem»problem test.«ENDIF» | ||
249 | enum Foo { alpha, beta } | ||
250 | «qualifiedName»: 16. | ||
251 | ''') | ||
252 | assertThat(errors, empty) | ||
253 | assertThat(nodes, empty) | ||
254 | assertThat(nodeValueAssertion(0).node, equalTo(findEnum("Foo").literal("alpha"))) | ||
255 | } | ||
256 | |||
257 | @ParameterizedTest(name="{0}, namedProblem={1}") | ||
258 | @MethodSource("enumLiteralReferencesSource") | ||
259 | def void enumLiteralInPredicateTest(String qualifiedName, boolean namedProblem) { | ||
260 | val it = parseHelper.parse(''' | ||
261 | «IF namedProblem»problem test.«ENDIF» | ||
262 | enum Foo { alpha, beta } | ||
263 | pred predicate(Foo a) <-> node(«qualifiedName»). | ||
264 | ''') | ||
265 | assertThat(errors, empty) | ||
266 | assertThat(nodes, empty) | ||
267 | assertThat(pred("predicate").conj(0).lit(0).arg(0).node, equalTo(findEnum("Foo").literal("alpha"))) | ||
268 | } | ||
269 | |||
270 | static def enumLiteralReferencesSource() { | ||
271 | Stream.of( | ||
272 | Arguments.of("alpha", false), | ||
273 | Arguments.of("alpha", true), | ||
274 | Arguments.of("Foo::alpha", false), | ||
275 | Arguments.of("Foo::alpha", true), | ||
276 | Arguments.of("test::alpha", true), | ||
277 | Arguments.of("test::Foo::alpha", true) | ||
278 | ) | ||
279 | } | ||
280 | |||
281 | @ParameterizedTest | ||
282 | @MethodSource("builtInEnumLiteralReferencesSource") | ||
283 | def void builtInEnumLiteralTest(String qualifiedName) { | ||
284 | val it = parseHelper.parse(''' | ||
285 | pred predicate(node a) <-> node(a). | ||
286 | predicate(«qualifiedName»). | ||
287 | ''') | ||
288 | assertThat(errors, empty) | ||
289 | assertThat(nodes, empty) | ||
290 | assertThat(assertion(0).arg(0).node, equalTo(builtin.findEnum("bool").literal("true"))) | ||
291 | } | ||
292 | |||
293 | @ParameterizedTest | ||
294 | @MethodSource("builtInEnumLiteralReferencesSource") | ||
295 | def void builtInEnumLiteralInNodeValueAssertionTest(String qualifiedName) { | ||
296 | val it = parseHelper.parse(''' | ||
297 | «qualifiedName»: 16. | ||
298 | ''') | ||
299 | assertThat(errors, empty) | ||
300 | assertThat(nodes, empty) | ||
301 | assertThat(nodeValueAssertion(0).node, equalTo(builtin.findEnum("bool").literal("true"))) | ||
302 | } | ||
303 | |||
304 | @ParameterizedTest | ||
305 | @MethodSource("builtInEnumLiteralReferencesSource") | ||
306 | def void bultInEnumLiteralInPredicateTest(String qualifiedName) { | ||
307 | val it = parseHelper.parse(''' | ||
308 | pred predicate() <-> node(«qualifiedName»). | ||
309 | ''') | ||
310 | assertThat(errors, empty) | ||
311 | assertThat(pred("predicate").conj(0).lit(0).arg(0).node, equalTo(builtin.findEnum("bool").literal("true"))) | ||
312 | } | ||
313 | |||
314 | static def builtInEnumLiteralReferencesSource() { | ||
315 | Stream.of( | ||
316 | Arguments.of("true"), | ||
317 | Arguments.of("bool::true"), | ||
318 | Arguments.of("builtin::true"), | ||
319 | Arguments.of("builtin::bool::true") | ||
320 | ) | ||
321 | } | ||
322 | } | ||
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java new file mode 100644 index 00000000..ba3aaeb7 --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java | |||
@@ -0,0 +1,229 @@ | |||
1 | package tools.refinery.language.tests.serializer; | ||
2 | |||
3 | import static org.hamcrest.MatcherAssert.assertThat; | ||
4 | import static org.hamcrest.Matchers.equalTo; | ||
5 | |||
6 | import java.io.ByteArrayOutputStream; | ||
7 | import java.io.IOException; | ||
8 | import java.util.Map; | ||
9 | import java.util.stream.Stream; | ||
10 | |||
11 | import org.eclipse.emf.common.util.URI; | ||
12 | import org.eclipse.emf.ecore.resource.Resource; | ||
13 | import org.eclipse.emf.ecore.resource.ResourceSet; | ||
14 | import org.eclipse.xtext.testing.InjectWith; | ||
15 | import org.eclipse.xtext.testing.extensions.InjectionExtension; | ||
16 | import org.junit.jupiter.api.BeforeEach; | ||
17 | import org.junit.jupiter.api.Test; | ||
18 | import org.junit.jupiter.api.extension.ExtendWith; | ||
19 | import org.junit.jupiter.params.ParameterizedTest; | ||
20 | import org.junit.jupiter.params.provider.Arguments; | ||
21 | import org.junit.jupiter.params.provider.MethodSource; | ||
22 | |||
23 | import com.google.inject.Inject; | ||
24 | |||
25 | import tools.refinery.language.model.ProblemUtil; | ||
26 | import tools.refinery.language.model.problem.Atom; | ||
27 | import tools.refinery.language.model.problem.LogicValue; | ||
28 | import tools.refinery.language.model.problem.Node; | ||
29 | import tools.refinery.language.model.problem.PredicateDefinition; | ||
30 | import tools.refinery.language.model.problem.Problem; | ||
31 | import tools.refinery.language.model.problem.ProblemFactory; | ||
32 | import tools.refinery.language.model.problem.Relation; | ||
33 | import tools.refinery.language.model.problem.VariableOrNode; | ||
34 | import tools.refinery.language.model.tests.ProblemTestUtil; | ||
35 | import tools.refinery.language.tests.ProblemInjectorProvider; | ||
36 | |||
37 | @ExtendWith(InjectionExtension.class) | ||
38 | @InjectWith(ProblemInjectorProvider.class) | ||
39 | class ProblemSerializerTest { | ||
40 | @Inject | ||
41 | private ResourceSet resourceSet; | ||
42 | |||
43 | @Inject | ||
44 | private ProblemTestUtil testUtil; | ||
45 | |||
46 | private Resource resource; | ||
47 | |||
48 | private Problem problem; | ||
49 | |||
50 | private Problem builtin; | ||
51 | |||
52 | @BeforeEach | ||
53 | void beforeEach() { | ||
54 | problem = ProblemFactory.eINSTANCE.createProblem(); | ||
55 | resource = resourceSet.createResource(URI.createFileURI("test.problem")); | ||
56 | resource.getContents().add(problem); | ||
57 | builtin = ProblemUtil.getBuiltInLibrary(problem).get(); | ||
58 | } | ||
59 | |||
60 | @ParameterizedTest | ||
61 | @MethodSource | ||
62 | void assertionTest(LogicValue value, String serializedAssertion) { | ||
63 | var pred = createPred(); | ||
64 | var node = ProblemFactory.eINSTANCE.createNode(); | ||
65 | node.setName("a"); | ||
66 | var individualDeclaration = ProblemFactory.eINSTANCE.createIndividualDeclaration(); | ||
67 | individualDeclaration.getNodes().add(node); | ||
68 | problem.getStatements().add(individualDeclaration); | ||
69 | createAssertion(pred, node, value); | ||
70 | |||
71 | assertSerializedResult(""" | ||
72 | pred foo(node p). | ||
73 | |||
74 | indiv a. | ||
75 | """ + serializedAssertion + "\n"); | ||
76 | } | ||
77 | |||
78 | static Stream<Arguments> assertionTest() { | ||
79 | return Stream.of(Arguments.of(LogicValue.TRUE, "foo(a)."), Arguments.of(LogicValue.FALSE, "!foo(a)."), | ||
80 | Arguments.of(LogicValue.UNKNOWN, "?foo(a)."), Arguments.of(LogicValue.ERROR, "foo(a): error.")); | ||
81 | } | ||
82 | |||
83 | @Test | ||
84 | void implicitNodeTest() { | ||
85 | var pred = createPred(); | ||
86 | var node = ProblemFactory.eINSTANCE.createNode(); | ||
87 | node.setName("a"); | ||
88 | problem.getNodes().add(node); | ||
89 | createAssertion(pred, node); | ||
90 | |||
91 | assertSerializedResult(""" | ||
92 | pred foo(node p). | ||
93 | |||
94 | foo(a). | ||
95 | """); | ||
96 | } | ||
97 | |||
98 | private PredicateDefinition createPred() { | ||
99 | var pred = ProblemFactory.eINSTANCE.createPredicateDefinition(); | ||
100 | pred.setName("foo"); | ||
101 | var parameter = ProblemFactory.eINSTANCE.createParameter(); | ||
102 | var nodeType = testUtil.findClass(builtin, "node"); | ||
103 | parameter.setParameterType(nodeType); | ||
104 | parameter.setName("p"); | ||
105 | pred.getParameters().add(parameter); | ||
106 | problem.getStatements().add(pred); | ||
107 | return pred; | ||
108 | } | ||
109 | |||
110 | @Test | ||
111 | void newNodeTest() { | ||
112 | var classDeclaration = ProblemFactory.eINSTANCE.createClassDeclaration(); | ||
113 | classDeclaration.setName("Foo"); | ||
114 | var newNode = ProblemFactory.eINSTANCE.createNode(); | ||
115 | newNode.setName("new"); | ||
116 | classDeclaration.setNewNode(newNode); | ||
117 | problem.getStatements().add(classDeclaration); | ||
118 | createAssertion(classDeclaration, newNode); | ||
119 | |||
120 | assertSerializedResult(""" | ||
121 | class Foo. | ||
122 | |||
123 | Foo(Foo::new). | ||
124 | """); | ||
125 | } | ||
126 | |||
127 | private void createAssertion(Relation relation, Node node) { | ||
128 | createAssertion(relation, node, LogicValue.TRUE); | ||
129 | } | ||
130 | |||
131 | private void createAssertion(Relation relation, Node node, LogicValue value) { | ||
132 | var assertion = ProblemFactory.eINSTANCE.createAssertion(); | ||
133 | assertion.setRelation(relation); | ||
134 | var argument = ProblemFactory.eINSTANCE.createNodeAssertionArgument(); | ||
135 | argument.setNode(node); | ||
136 | assertion.getArguments().add(argument); | ||
137 | assertion.setValue(value); | ||
138 | problem.getStatements().add(assertion); | ||
139 | } | ||
140 | |||
141 | @Test | ||
142 | void implicitVariableTest() { | ||
143 | var pred = ProblemFactory.eINSTANCE.createPredicateDefinition(); | ||
144 | pred.setName("foo"); | ||
145 | var nodeType = testUtil.findClass(builtin, "node"); | ||
146 | var parameter1 = ProblemFactory.eINSTANCE.createParameter(); | ||
147 | parameter1.setParameterType(nodeType); | ||
148 | parameter1.setName("p1"); | ||
149 | pred.getParameters().add(parameter1); | ||
150 | var parameter2 = ProblemFactory.eINSTANCE.createParameter(); | ||
151 | parameter2.setParameterType(nodeType); | ||
152 | parameter2.setName("p2"); | ||
153 | pred.getParameters().add(parameter2); | ||
154 | var conjunction = ProblemFactory.eINSTANCE.createConjunction(); | ||
155 | var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); | ||
156 | variable.setName("q"); | ||
157 | conjunction.getImplicitVariables().add(variable); | ||
158 | var equals = testUtil.reference(nodeType, "equals"); | ||
159 | conjunction.getLiterals().add(createAtom(equals, parameter1, variable)); | ||
160 | conjunction.getLiterals().add(createAtom(equals, variable, parameter2)); | ||
161 | pred.getBodies().add(conjunction); | ||
162 | problem.getStatements().add(pred); | ||
163 | |||
164 | assertSerializedResult(""" | ||
165 | pred foo(node p1, node p2) <-> equals(p1, q), equals(q, p2). | ||
166 | """); | ||
167 | } | ||
168 | |||
169 | private Atom createAtom(Relation relation, VariableOrNode variable1, VariableOrNode variable2) { | ||
170 | var atom = ProblemFactory.eINSTANCE.createAtom(); | ||
171 | atom.setRelation(relation); | ||
172 | var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); | ||
173 | arg1.setVariableOrNode(variable1); | ||
174 | atom.getArguments().add(arg1); | ||
175 | var arg2 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); | ||
176 | arg2.setVariableOrNode(variable2); | ||
177 | atom.getArguments().add(arg2); | ||
178 | return atom; | ||
179 | } | ||
180 | |||
181 | @Test | ||
182 | void singletonVariableTest() { | ||
183 | var pred = ProblemFactory.eINSTANCE.createPredicateDefinition(); | ||
184 | pred.setName("foo"); | ||
185 | var nodeType = testUtil.findClass(builtin, "node"); | ||
186 | var parameter = ProblemFactory.eINSTANCE.createParameter(); | ||
187 | parameter.setParameterType(nodeType); | ||
188 | parameter.setName("p"); | ||
189 | pred.getParameters().add(parameter); | ||
190 | var conjunction = ProblemFactory.eINSTANCE.createConjunction(); | ||
191 | var atom = ProblemFactory.eINSTANCE.createAtom(); | ||
192 | var equals = testUtil.reference(nodeType, "equals"); | ||
193 | atom.setRelation(equals); | ||
194 | var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); | ||
195 | arg1.setVariableOrNode(parameter); | ||
196 | atom.getArguments().add(arg1); | ||
197 | var arg2 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); | ||
198 | var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); | ||
199 | variable.setName("_q"); | ||
200 | arg2.setSingletonVariable(variable); | ||
201 | arg2.setVariableOrNode(variable); | ||
202 | atom.getArguments().add(arg2); | ||
203 | conjunction.getLiterals().add(atom); | ||
204 | pred.getBodies().add(conjunction); | ||
205 | problem.getStatements().add(pred); | ||
206 | |||
207 | assertSerializedResult(""" | ||
208 | pred foo(node p) <-> equals(p, _q). | ||
209 | """); | ||
210 | } | ||
211 | |||
212 | private void assertSerializedResult(String expected) { | ||
213 | var outputStream = new ByteArrayOutputStream(); | ||
214 | try { | ||
215 | resource.save(outputStream, Map.of()); | ||
216 | } catch (IOException e) { | ||
217 | throw new AssertionError("Failed to serialize problem", e); | ||
218 | } finally { | ||
219 | try { | ||
220 | outputStream.close(); | ||
221 | } catch (IOException e) { | ||
222 | // Nothing to handle in a test. | ||
223 | } | ||
224 | } | ||
225 | var problemString = outputStream.toString(); | ||
226 | |||
227 | assertThat(problemString, equalTo(expected)); | ||
228 | } | ||
229 | } | ||