aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-12 17:48:47 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-12 17:48:47 +0100
commitfc7e9312d00e60171ed77c477ed91231d3dbfff9 (patch)
treecc185dd088b5fa6e9357aab3c9062a70626d1953 /subprojects/language
parentbuild: refactor java-application conventions (diff)
downloadrefinery-fc7e9312d00e60171ed77c477ed91231d3dbfff9.tar.gz
refinery-fc7e9312d00e60171ed77c477ed91231d3dbfff9.tar.zst
refinery-fc7e9312d00e60171ed77c477ed91231d3dbfff9.zip
build: move modules into subproject directory
Diffstat (limited to 'subprojects/language')
-rw-r--r--subprojects/language/build.gradle66
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe268
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/Problem.xtext205
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java84
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java44
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemXmiRuntimeModule.java35
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java19
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java35
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java183
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java25
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java15
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java194
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java88
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java163
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java33
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java103
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java16
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java51
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java18
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java42
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java104
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java62
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.xtend64
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java235
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/rules/DirectRuleParsingTest.xtend96
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.xtend322
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java229
27 files changed, 2599 insertions, 0 deletions
diff --git a/subprojects/language/build.gradle b/subprojects/language/build.gradle
new file mode 100644
index 00000000..f7574ecc
--- /dev/null
+++ b/subprojects/language/build.gradle
@@ -0,0 +1,66 @@
1plugins {
2 id 'java-test-fixtures'
3 id 'refinery-java-library'
4 id 'refinery-mwe2'
5 id 'refinery-sonarqube'
6 id 'refinery-xtend'
7 id 'refinery-xtext-conventions'
8}
9
10dependencies {
11 api platform(libs.xtext.bom)
12 api libs.ecore
13 api libs.xtext.core
14 api libs.xtext.xbase
15 api project(':refinery-language-model')
16 testFixturesApi libs.xtext.testing
17 testFixturesApi testFixtures(project(':refinery-language-model'))
18 mwe2 libs.xtext.generator
19 mwe2 libs.xtext.generator.antlr
20}
21
22sourceSets {
23 testFixtures {
24 java.srcDirs += ['src/testFixtures/xtext-gen']
25 resources.srcDirs += ['src/testFixtures/xtext-gen']
26 }
27}
28
29tasks.named('jar') {
30 from(sourceSets.main.allSource) {
31 include '**/*.xtext'
32 }
33}
34
35def generateXtextLanguage = tasks.register('generateXtextLanguage', JavaExec) {
36 mainClass = 'org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher'
37 classpath = configurations.mwe2
38 inputs.file 'src/main/java/tools/refinery/language/GenerateProblem.mwe2'
39 inputs.file 'src/main/java/tools/refinery/language/Problem.xtext'
40 outputs.dir 'src/main/xtext-gen'
41 outputs.dir 'src/testFixtures/xtext-gen'
42 outputs.dir '../language-ide/src/main/xtext-gen'
43 outputs.dir '../language-web/src/main/xtext-gen'
44 args += 'src/main/java/tools/refinery/language/GenerateProblem.mwe2'
45 args += '-p'
46 args += "rootPath=/${projectDir}/.."
47}
48
49for (taskName in ['compileJava', 'processResources', 'generateXtext', 'generateEclipseSourceFolders']) {
50 tasks.named(taskName) {
51 dependsOn generateXtextLanguage
52 }
53}
54
55tasks.named('clean') {
56 delete 'src/main/xtext-gen'
57 delete 'src/testFixtures/xtext-gen'
58 delete '../language-ide/src/main/xtext-gen'
59 delete '../language-web/src/main/xtext-gen'
60}
61
62sonarqube.properties {
63 properties['sonar.exclusions'] += [
64 'src/testFixtures/xtext-gen/**',
65 ]
66}
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 @@
1module tools.refinery.language.GenerateProblem
2
3import org.eclipse.xtext.xtext.generator.*
4import org.eclipse.xtext.xtext.generator.model.project.*
5
6var rootPath = '..'
7
8Workflow {
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 @@
1grammar tools.refinery.language.Problem with org.eclipse.xtext.common.Terminals
2
3import "http://www.eclipse.org/emf/2002/Ecore" as ecore
4import "https://refinery.tools/emf/2021/Problem"
5
6Problem:
7 ("problem" name=Identifier ".")?
8 statements+=Statement*;
9
10Statement:
11 ClassDeclaration | EnumDeclaration | PredicateDefinition | RuleDefinition | Assertion | NodeValueAssertion |
12 ScopeDeclaration |
13 IndividualDeclaration;
14
15ClassDeclaration:
16 abstract?="abstract"? "class"
17 name=Identifier
18 ("extends" superTypes+=[Relation|QualifiedName] ("," superTypes+=[Relation|QualifiedName])*)?
19 ("{" (referenceDeclarations+=ReferenceDeclaration ";"?)* "}" | ".");
20
21EnumDeclaration:
22 "enum"
23 name=Identifier
24 ("{" (literals+=EnumLiteral ("," literals+=EnumLiteral)* ("," | ";")?)? "}" | ".");
25
26EnumLiteral returns Node:
27 name=Identifier;
28
29ReferenceDeclaration:
30 (containment?="contains" | "refers")?
31 referenceType=[Relation|QualifiedName]
32 ("[" multiplicity=Multiplicity "]")?
33 name=Identifier
34 ("opposite" opposite=[ReferenceDeclaration|QualifiedName])?;
35
36enum PredicateKind:
37 DIRECT="direct";
38
39PredicateDefinition:
40 (error?="error" "pred"? | kind=PredicateKind? "pred")
41 name=Identifier
42 "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")"
43 ("<->" bodies+=Conjunction (";" bodies+=Conjunction)*)?
44 ".";
45
46enum RuleKind:
47 DIRECT="direct";
48
49RuleDefinition:
50 kind=RuleKind "rule"
51 name=Identifier
52 "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")"
53 (":" bodies+=Conjunction (";" bodies+=Conjunction)*
54 "~>" action=Action)?
55 ".";
56
57Parameter:
58 parameterType=[Relation|QualifiedName]? name=Identifier;
59
60Conjunction:
61 literals+=Literal ("," literals+=Literal)*;
62
63Action:
64 actionLiterals+=ActionLiteral ("," actionLiterals+=ActionLiteral)*;
65
66Literal:
67 Atom | ValueLiteral | NegativeLiteral;
68
69ValueLiteral:
70 atom=Atom
71 (refinement?=":" | "=")
72 values+=LogicConstant ("|" values+=LogicConstant)*;
73
74NegativeLiteral:
75 "!" atom=Atom;
76
77ActionLiteral:
78 ValueActionLiteral | DeleteActionLiteral | NewActionLiteral;
79
80ValueActionLiteral:
81 atom=Atom
82 (refinement?=":" | "=")
83 value=LogicValue;
84
85DeleteActionLiteral:
86 "delete" variableOrNode=[VariableOrNode|QualifiedName];
87
88NewActionLiteral:
89 "new" variable=NewVariable;
90
91NewVariable:
92 name=Identifier;
93
94Atom:
95 relation=[Relation|QualifiedName]
96 transitiveClosure?="+"?
97 "(" (arguments+=Argument ("," arguments+=Argument)*)? ")";
98
99LogicConstant:
100 value=LogicValue;
101
102Argument:
103 VariableOrNodeArgument | ConstantArgument;
104
105VariableOrNodeArgument:
106 variableOrNode=[VariableOrNode|QualifiedName];
107
108ConstantArgument:
109 constant=Constant;
110
111Assertion:
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
121AssertionArgument:
122 NodeAssertionArgument | WildcardAssertionArgument | ConstantAssertionArgument;
123
124NodeAssertionArgument:
125 node=[Node|QualifiedName];
126
127WildcardAssertionArgument:
128 {WildcardAssertionArgument} "*";
129
130ConstantAssertionArgument:
131 constant=Constant;
132
133enum LogicValue:
134 TRUE="true" | FALSE="false" | UNKNOWN="unknown" | ERROR="error";
135
136enum ShortLogicValue returns LogicValue:
137 FALSE="!" | UNKNOWN="?";
138
139NodeValueAssertion:
140 node=[Node|QualifiedName] ":" value=Constant ".";
141
142Constant:
143 RealConstant | IntConstant | StringConstant;
144
145IntConstant:
146 intValue=Integer;
147
148RealConstant:
149 realValue=Real;
150
151StringConstant:
152 stringValue=STRING;
153
154ScopeDeclaration:
155 "scope" typeScopes+=TypeScope ("," typeScopes+=TypeScope)* ".";
156
157TypeScope:
158 targetType=[ClassDeclaration|QualifiedName]
159 (increment?="+=" | "=")
160 multiplicity=DefiniteMultiplicity;
161
162Multiplicity:
163 UnboundedMultiplicity | DefiniteMultiplicity;
164
165DefiniteMultiplicity returns Multiplicity:
166 RangeMultiplicity | ExactMultiplicity;
167
168UnboundedMultiplicity:
169 {UnboundedMultiplicity};
170
171RangeMultiplicity:
172 lowerBound=INT ".." upperBound=UpperBound;
173
174ExactMultiplicity:
175 exactValue=INT;
176
177IndividualDeclaration:
178 "indiv" nodes+=EnumLiteral ("," nodes+=EnumLiteral)* ".";
179
180UpperBound returns ecore::EInt:
181 INT | "*";
182
183QualifiedName hidden():
184 Identifier ("::" Identifier)*;
185
186Identifier:
187 ID | "true" | "false" | "unknown" | "error" | "class" | "abstract" | "extends" | "enum" | "pred" |
188 "indiv" | "problem" | "new" | "delete" | "direct" | "rule";
189
190Integer returns ecore::EInt hidden():
191 "-"? INT;
192
193Real returns ecore::EDouble:
194 "-"? (EXPONENTIAL | INT "." (INT | EXPONENTIAL));
195
196@Override
197terminal ID:
198 ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
199
200terminal EXPONENTIAL:
201 INT ("e" | "E") ("+" | "-")? INT;
202
203@Override
204terminal 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 */
4package tools.refinery.language;
5
6import org.eclipse.xtext.conversion.IValueConverterService;
7import org.eclipse.xtext.naming.IQualifiedNameConverter;
8import org.eclipse.xtext.resource.DerivedStateAwareResource;
9import org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager;
10import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy;
11import org.eclipse.xtext.resource.IDerivedStateComputer;
12import org.eclipse.xtext.resource.ILocationInFileProvider;
13import org.eclipse.xtext.resource.IResourceDescription;
14import org.eclipse.xtext.resource.XtextResource;
15import org.eclipse.xtext.scoping.IGlobalScopeProvider;
16import org.eclipse.xtext.scoping.IScopeProvider;
17import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
18import org.eclipse.xtext.validation.IResourceValidator;
19import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator;
20
21import com.google.inject.Binder;
22import com.google.inject.name.Names;
23
24import tools.refinery.language.conversion.ProblemValueConverterService;
25import tools.refinery.language.naming.ProblemQualifiedNameConverter;
26import tools.refinery.language.resource.ProblemDerivedStateComputer;
27import tools.refinery.language.resource.ProblemLocationInFileProvider;
28import tools.refinery.language.resource.ProblemResourceDescriptionStrategy;
29import tools.refinery.language.scoping.ProblemGlobalScopeProvider;
30import 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 */
36public 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 */
4package tools.refinery.language;
5
6import org.eclipse.emf.ecore.resource.Resource;
7import org.eclipse.xtext.resource.IResourceFactory;
8import org.eclipse.xtext.resource.IResourceServiceProvider;
9
10import com.google.inject.Guice;
11import com.google.inject.Injector;
12
13import tools.refinery.language.model.ProblemEMFSetup;
14
15/**
16 * Initialization support for running Xtext languages without Equinox extension
17 * registry.
18 */
19public 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 @@
1package tools.refinery.language;
2
3import org.eclipse.xtext.naming.IQualifiedNameConverter;
4import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy;
5import org.eclipse.xtext.resource.IResourceFactory;
6import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule;
7
8import tools.refinery.language.model.ProblemEMFSetup;
9import tools.refinery.language.naming.ProblemQualifiedNameConverter;
10import tools.refinery.language.resource.ProblemResourceDescriptionStrategy;
11import tools.refinery.language.resource.ProblemXmiResourceFactory;
12
13public 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 @@
1package tools.refinery.language.conversion;
2
3import org.eclipse.xtext.common.services.DefaultTerminalConverters;
4import org.eclipse.xtext.conversion.IValueConverter;
5import org.eclipse.xtext.conversion.ValueConverter;
6
7import com.google.inject.Inject;
8
9public 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 @@
1package tools.refinery.language.conversion;
2
3import org.eclipse.xtext.conversion.ValueConverterException;
4import org.eclipse.xtext.conversion.impl.AbstractValueConverter;
5import org.eclipse.xtext.conversion.impl.INTValueConverter;
6import org.eclipse.xtext.nodemodel.INode;
7
8import com.google.inject.Inject;
9import com.google.inject.Singleton;
10
11@Singleton
12public 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 */
4package tools.refinery.language.formatting2;
5
6import org.eclipse.emf.ecore.EObject;
7import org.eclipse.xtext.formatting2.AbstractJavaFormatter;
8import org.eclipse.xtext.formatting2.IFormattableDocument;
9import org.eclipse.xtext.formatting2.IHiddenRegionFormatter;
10import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder;
11import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion;
12import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
13
14import tools.refinery.language.model.problem.Assertion;
15import tools.refinery.language.model.problem.Atom;
16import tools.refinery.language.model.problem.ClassDeclaration;
17import tools.refinery.language.model.problem.Conjunction;
18import tools.refinery.language.model.problem.IndividualDeclaration;
19import tools.refinery.language.model.problem.NegativeLiteral;
20import tools.refinery.language.model.problem.Parameter;
21import tools.refinery.language.model.problem.PredicateDefinition;
22import tools.refinery.language.model.problem.Problem;
23import tools.refinery.language.model.problem.ProblemPackage;
24
25public 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 @@
1package tools.refinery.language.naming;
2
3import java.util.regex.Pattern;
4
5public 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 @@
1package tools.refinery.language.naming;
2
3import org.eclipse.xtext.naming.IQualifiedNameConverter;
4
5import com.google.inject.Singleton;
6
7@Singleton
8public 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 @@
1package tools.refinery.language.resource;
2
3import java.util.HashSet;
4import java.util.List;
5import java.util.Set;
6
7import org.eclipse.xtext.linking.impl.LinkingHelper;
8import org.eclipse.xtext.naming.IQualifiedNameConverter;
9import org.eclipse.xtext.nodemodel.INode;
10import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
11import org.eclipse.xtext.scoping.IScope;
12import org.eclipse.xtext.scoping.IScopeProvider;
13import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
14
15import com.google.inject.Inject;
16import com.google.inject.Singleton;
17import com.google.inject.name.Named;
18
19import tools.refinery.language.model.problem.Argument;
20import tools.refinery.language.model.problem.Atom;
21import tools.refinery.language.model.problem.Conjunction;
22import tools.refinery.language.model.problem.ExistentialQuantifier;
23import tools.refinery.language.model.problem.ImplicitVariable;
24import tools.refinery.language.model.problem.Literal;
25import tools.refinery.language.model.problem.NegativeLiteral;
26import tools.refinery.language.model.problem.Parameter;
27import tools.refinery.language.model.problem.ParametricDefinition;
28import tools.refinery.language.model.problem.Problem;
29import tools.refinery.language.model.problem.ProblemFactory;
30import tools.refinery.language.model.problem.ProblemPackage;
31import tools.refinery.language.model.problem.Statement;
32import tools.refinery.language.model.problem.ValueLiteral;
33import tools.refinery.language.model.problem.VariableOrNodeArgument;
34import tools.refinery.language.naming.NamingUtil;
35
36@Singleton
37public 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 @@
1package tools.refinery.language.resource;
2
3import java.util.List;
4import java.util.Set;
5
6import org.eclipse.emf.ecore.EObject;
7import org.eclipse.emf.ecore.EStructuralFeature;
8import org.eclipse.xtext.linking.impl.LinkingHelper;
9import org.eclipse.xtext.naming.IQualifiedNameConverter;
10import org.eclipse.xtext.nodemodel.INode;
11import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
12import org.eclipse.xtext.scoping.IScope;
13import org.eclipse.xtext.scoping.IScopeProvider;
14import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
15
16import com.google.common.collect.ImmutableSet;
17import com.google.inject.Inject;
18import com.google.inject.name.Named;
19
20import tools.refinery.language.model.problem.Assertion;
21import tools.refinery.language.model.problem.AssertionArgument;
22import tools.refinery.language.model.problem.NodeAssertionArgument;
23import tools.refinery.language.model.problem.NodeValueAssertion;
24import tools.refinery.language.model.problem.Problem;
25import tools.refinery.language.model.problem.ProblemPackage;
26import tools.refinery.language.model.problem.Statement;
27import tools.refinery.language.naming.NamingUtil;
28
29public 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 @@
1package tools.refinery.language.resource;
2
3import java.util.Collection;
4import java.util.HashMap;
5import java.util.HashSet;
6import java.util.List;
7import java.util.Map;
8import java.util.Set;
9import java.util.function.Function;
10
11import org.eclipse.emf.common.notify.impl.AdapterImpl;
12import org.eclipse.emf.ecore.EObject;
13import org.eclipse.emf.ecore.resource.Resource;
14import org.eclipse.emf.ecore.util.EcoreUtil;
15import org.eclipse.xtext.Constants;
16import org.eclipse.xtext.resource.DerivedStateAwareResource;
17import org.eclipse.xtext.resource.IDerivedStateComputer;
18import org.eclipse.xtext.resource.XtextResource;
19import org.eclipse.xtext.scoping.IScopeProvider;
20import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
21
22import com.google.inject.Inject;
23import com.google.inject.Provider;
24import com.google.inject.Singleton;
25import com.google.inject.name.Named;
26
27import tools.refinery.language.model.problem.ClassDeclaration;
28import tools.refinery.language.model.problem.Node;
29import tools.refinery.language.model.problem.Problem;
30import tools.refinery.language.model.problem.ProblemFactory;
31import tools.refinery.language.model.problem.Statement;
32
33@Singleton
34public 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 @@
1package tools.refinery.language.resource;
2
3import org.eclipse.emf.ecore.EObject;
4import org.eclipse.xtext.resource.DefaultLocationInFileProvider;
5import org.eclipse.xtext.util.ITextRegion;
6
7import tools.refinery.language.model.ProblemUtil;
8import tools.refinery.language.model.problem.ImplicitVariable;
9import tools.refinery.language.model.problem.Node;
10
11public 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 @@
1package tools.refinery.language.resource;
2
3import org.eclipse.emf.ecore.EObject;
4import org.eclipse.xtext.EcoreUtil2;
5import org.eclipse.xtext.naming.IQualifiedNameConverter;
6import org.eclipse.xtext.naming.QualifiedName;
7import org.eclipse.xtext.resource.EObjectDescription;
8import org.eclipse.xtext.resource.IEObjectDescription;
9import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy;
10import org.eclipse.xtext.util.IAcceptor;
11
12import com.google.inject.Inject;
13import com.google.inject.Singleton;
14
15import tools.refinery.language.model.ProblemUtil;
16import tools.refinery.language.model.problem.NamedElement;
17import tools.refinery.language.model.problem.Node;
18import tools.refinery.language.model.problem.Problem;
19import tools.refinery.language.model.problem.Variable;
20import tools.refinery.language.naming.NamingUtil;
21
22@Singleton
23public 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 @@
1package tools.refinery.language.resource;
2
3import org.eclipse.emf.common.util.URI;
4import org.eclipse.emf.ecore.resource.Resource;
5import org.eclipse.xtext.resource.IResourceFactory;
6
7import tools.refinery.language.model.problem.util.ProblemResourceFactoryImpl;
8
9public 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 @@
1package tools.refinery.language.resource;
2
3import java.util.HashMap;
4import java.util.Map;
5
6import org.eclipse.emf.ecore.EObject;
7import org.eclipse.xtext.util.IResourceScopeCache;
8
9import com.google.inject.Inject;
10import com.google.inject.Singleton;
11
12import tools.refinery.language.model.problem.Problem;
13
14@Singleton
15public 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 @@
1package tools.refinery.language.scoping;
2
3import java.util.LinkedHashSet;
4
5import org.eclipse.emf.common.util.URI;
6import org.eclipse.emf.ecore.resource.Resource;
7import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider;
8
9import tools.refinery.language.model.ProblemUtil;
10
11public 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 @@
1package tools.refinery.language.scoping;
2
3import java.util.List;
4
5import org.eclipse.emf.ecore.EObject;
6import org.eclipse.emf.ecore.resource.Resource;
7import org.eclipse.xtext.naming.QualifiedName;
8import org.eclipse.xtext.resource.IResourceDescriptions;
9import org.eclipse.xtext.resource.IResourceDescriptionsProvider;
10import org.eclipse.xtext.resource.ISelectable;
11import org.eclipse.xtext.scoping.impl.ImportNormalizer;
12import org.eclipse.xtext.scoping.impl.ImportedNamespaceAwareLocalScopeProvider;
13
14import com.google.inject.Inject;
15
16import tools.refinery.language.model.ProblemUtil;
17
18public 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 */
4package tools.refinery.language.scoping;
5
6import java.util.ArrayList;
7import java.util.List;
8
9import org.eclipse.emf.ecore.EObject;
10import org.eclipse.emf.ecore.EReference;
11import org.eclipse.xtext.EcoreUtil2;
12import org.eclipse.xtext.scoping.IScope;
13import org.eclipse.xtext.scoping.Scopes;
14
15import tools.refinery.language.model.ProblemUtil;
16import tools.refinery.language.model.problem.ClassDeclaration;
17import tools.refinery.language.model.problem.ExistentialQuantifier;
18import tools.refinery.language.model.problem.NewActionLiteral;
19import tools.refinery.language.model.problem.ParametricDefinition;
20import tools.refinery.language.model.problem.Action;
21import tools.refinery.language.model.problem.Problem;
22import tools.refinery.language.model.problem.ProblemPackage;
23import tools.refinery.language.model.problem.ReferenceDeclaration;
24import tools.refinery.language.model.problem.Variable;
25import 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 */
34public 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 */
4package tools.refinery.language.validation;
5
6import org.eclipse.xtext.EcoreUtil2;
7import org.eclipse.xtext.validation.Check;
8
9import com.google.inject.Inject;
10
11import tools.refinery.language.model.ProblemUtil;
12import tools.refinery.language.model.problem.Node;
13import tools.refinery.language.model.problem.Problem;
14import tools.refinery.language.model.problem.ProblemPackage;
15import tools.refinery.language.model.problem.Variable;
16import tools.refinery.language.model.problem.VariableOrNodeArgument;
17import 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 */
25public 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 */
4package tools.refinery.language.tests
5
6import com.google.inject.Inject
7import org.eclipse.xtext.testing.InjectWith
8import org.eclipse.xtext.testing.extensions.InjectionExtension
9import org.eclipse.xtext.testing.util.ParseHelper
10import org.junit.jupiter.api.Test
11import org.junit.jupiter.api.^extension.ExtendWith
12import tools.refinery.language.model.problem.Problem
13import tools.refinery.language.model.tests.ProblemTestUtil
14
15import static org.hamcrest.MatcherAssert.assertThat
16import static org.hamcrest.Matchers.*
17
18@ExtendWith(InjectionExtension)
19@InjectWith(ProblemInjectorProvider)
20class 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 @@
1package tools.refinery.language.tests.formatting2;
2
3import static org.hamcrest.MatcherAssert.assertThat;
4import static org.hamcrest.Matchers.equalTo;
5
6import java.util.List;
7
8import org.eclipse.xtext.formatting2.FormatterRequest;
9import org.eclipse.xtext.formatting2.IFormatter2;
10import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
11import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement;
12import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder;
13import org.eclipse.xtext.resource.XtextResource;
14import org.eclipse.xtext.testing.InjectWith;
15import org.eclipse.xtext.testing.extensions.InjectionExtension;
16import org.eclipse.xtext.testing.util.ParseHelper;
17import org.junit.jupiter.api.Test;
18import org.junit.jupiter.api.extension.ExtendWith;
19
20import com.google.inject.Inject;
21import com.google.inject.Provider;
22
23import tools.refinery.language.model.problem.Problem;
24import tools.refinery.language.tests.ProblemInjectorProvider;
25
26@ExtendWith(InjectionExtension.class)
27@InjectWith(ProblemInjectorProvider.class)
28class 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 @@
1package tools.refinery.language.tests.rules
2
3import com.google.inject.Inject
4import org.eclipse.xtext.testing.InjectWith
5import org.eclipse.xtext.testing.extensions.InjectionExtension
6import org.eclipse.xtext.testing.util.ParseHelper
7import org.junit.jupiter.api.Test
8import org.junit.jupiter.api.^extension.ExtendWith
9import tools.refinery.language.model.problem.Problem
10import tools.refinery.language.tests.ProblemInjectorProvider
11import tools.refinery.language.model.tests.ProblemTestUtil
12
13import static org.hamcrest.MatcherAssert.assertThat
14import static org.hamcrest.Matchers.*
15
16@ExtendWith(InjectionExtension)
17@InjectWith(ProblemInjectorProvider)
18class 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 @@
1package tools.refinery.language.tests.scoping
2
3import com.google.inject.Inject
4import java.util.stream.Stream
5import org.eclipse.xtext.testing.InjectWith
6import org.eclipse.xtext.testing.extensions.InjectionExtension
7import org.eclipse.xtext.testing.util.ParseHelper
8import org.junit.jupiter.api.Test
9import org.junit.jupiter.api.^extension.ExtendWith
10import org.junit.jupiter.params.ParameterizedTest
11import org.junit.jupiter.params.provider.Arguments
12import org.junit.jupiter.params.provider.MethodSource
13import org.junit.jupiter.params.provider.ValueSource
14import tools.refinery.language.model.problem.Problem
15import tools.refinery.language.model.tests.ProblemTestUtil
16import tools.refinery.language.tests.ProblemInjectorProvider
17
18import static org.hamcrest.MatcherAssert.assertThat
19import static org.hamcrest.Matchers.*
20
21@ExtendWith(InjectionExtension)
22@InjectWith(ProblemInjectorProvider)
23class 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 @@
1package tools.refinery.language.tests.serializer;
2
3import static org.hamcrest.MatcherAssert.assertThat;
4import static org.hamcrest.Matchers.equalTo;
5
6import java.io.ByteArrayOutputStream;
7import java.io.IOException;
8import java.util.Map;
9import java.util.stream.Stream;
10
11import org.eclipse.emf.common.util.URI;
12import org.eclipse.emf.ecore.resource.Resource;
13import org.eclipse.emf.ecore.resource.ResourceSet;
14import org.eclipse.xtext.testing.InjectWith;
15import org.eclipse.xtext.testing.extensions.InjectionExtension;
16import org.junit.jupiter.api.BeforeEach;
17import org.junit.jupiter.api.Test;
18import org.junit.jupiter.api.extension.ExtendWith;
19import org.junit.jupiter.params.ParameterizedTest;
20import org.junit.jupiter.params.provider.Arguments;
21import org.junit.jupiter.params.provider.MethodSource;
22
23import com.google.inject.Inject;
24
25import tools.refinery.language.model.ProblemUtil;
26import tools.refinery.language.model.problem.Atom;
27import tools.refinery.language.model.problem.LogicValue;
28import tools.refinery.language.model.problem.Node;
29import tools.refinery.language.model.problem.PredicateDefinition;
30import tools.refinery.language.model.problem.Problem;
31import tools.refinery.language.model.problem.ProblemFactory;
32import tools.refinery.language.model.problem.Relation;
33import tools.refinery.language.model.problem.VariableOrNode;
34import tools.refinery.language.model.tests.ProblemTestUtil;
35import tools.refinery.language.tests.ProblemInjectorProvider;
36
37@ExtendWith(InjectionExtension.class)
38@InjectWith(ProblemInjectorProvider.class)
39class 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}