diff options
Diffstat (limited to 'subprojects/language/src/main/java/tools')
21 files changed, 1587 insertions, 0 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 b/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 new file mode 100644 index 00000000..21ff456e --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/GenerateProblem.mwe2 | |||
@@ -0,0 +1,68 @@ | |||
1 | module tools.refinery.language.GenerateProblem | ||
2 | |||
3 | import org.eclipse.xtext.xtext.generator.* | ||
4 | import org.eclipse.xtext.xtext.generator.model.project.* | ||
5 | |||
6 | var rootPath = '..' | ||
7 | |||
8 | Workflow { | ||
9 | component = XtextGenerator { | ||
10 | configuration = { | ||
11 | project = StandardProjectConfig { | ||
12 | baseName = 'language' | ||
13 | rootPath = rootPath | ||
14 | runtimeTest = { | ||
15 | enabled = true | ||
16 | srcGen = 'src/testFixtures/xtext-gen' | ||
17 | } | ||
18 | genericIde = { | ||
19 | name = 'language-ide' | ||
20 | } | ||
21 | web = { | ||
22 | enabled = true | ||
23 | name = 'language-web' | ||
24 | } | ||
25 | mavenLayout = true | ||
26 | } | ||
27 | code = { | ||
28 | encoding = 'UTF-8' | ||
29 | lineDelimiter = '\n' | ||
30 | fileHeader = '/*\n * generated by Xtext \${version}\n */' | ||
31 | preferXtendStubs = false | ||
32 | } | ||
33 | } | ||
34 | |||
35 | language = StandardLanguage { | ||
36 | name = 'tools.refinery.language.Problem' | ||
37 | fileExtensions = 'problem' | ||
38 | referencedResource = 'platform:/resource/tools.refinery.refinery-language-model/model/problem.genmodel' | ||
39 | serializer = { | ||
40 | generateStub = false | ||
41 | } | ||
42 | formatter = { | ||
43 | generateStub = true | ||
44 | } | ||
45 | validator = { | ||
46 | generateDeprecationValidation = true | ||
47 | } | ||
48 | generator = { | ||
49 | generateStub = false | ||
50 | } | ||
51 | junitSupport = { | ||
52 | generateStub = false | ||
53 | skipXbaseTestingPackage = true | ||
54 | junitVersion = '5' | ||
55 | } | ||
56 | webSupport = { | ||
57 | // We only generate the {@code AbstractProblemWebModule}, | ||
58 | // because we write our own integration code for CodeMirror 6. | ||
59 | framework = 'codemirror' | ||
60 | generateHtmlExample = false | ||
61 | generateJettyLauncher = false | ||
62 | generateJsHighlighting = false | ||
63 | generateServlet = false | ||
64 | generateWebXml = false | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext new file mode 100644 index 00000000..c94d40ab --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext | |||
@@ -0,0 +1,205 @@ | |||
1 | grammar tools.refinery.language.Problem with org.eclipse.xtext.common.Terminals | ||
2 | |||
3 | import "http://www.eclipse.org/emf/2002/Ecore" as ecore | ||
4 | import "https://refinery.tools/emf/2021/Problem" | ||
5 | |||
6 | Problem: | ||
7 | ("problem" name=Identifier ".")? | ||
8 | statements+=Statement*; | ||
9 | |||
10 | Statement: | ||
11 | ClassDeclaration | EnumDeclaration | PredicateDefinition | RuleDefinition | Assertion | NodeValueAssertion | | ||
12 | ScopeDeclaration | | ||
13 | IndividualDeclaration; | ||
14 | |||
15 | ClassDeclaration: | ||
16 | abstract?="abstract"? "class" | ||
17 | name=Identifier | ||
18 | ("extends" superTypes+=[Relation|QualifiedName] ("," superTypes+=[Relation|QualifiedName])*)? | ||
19 | ("{" (referenceDeclarations+=ReferenceDeclaration ";"?)* "}" | "."); | ||
20 | |||
21 | EnumDeclaration: | ||
22 | "enum" | ||
23 | name=Identifier | ||
24 | ("{" (literals+=EnumLiteral ("," literals+=EnumLiteral)* ("," | ";")?)? "}" | "."); | ||
25 | |||
26 | EnumLiteral returns Node: | ||
27 | name=Identifier; | ||
28 | |||
29 | ReferenceDeclaration: | ||
30 | (containment?="contains" | "refers")? | ||
31 | referenceType=[Relation|QualifiedName] | ||
32 | ("[" multiplicity=Multiplicity "]")? | ||
33 | name=Identifier | ||
34 | ("opposite" opposite=[ReferenceDeclaration|QualifiedName])?; | ||
35 | |||
36 | enum PredicateKind: | ||
37 | DIRECT="direct"; | ||
38 | |||
39 | PredicateDefinition: | ||
40 | (error?="error" "pred"? | kind=PredicateKind? "pred") | ||
41 | name=Identifier | ||
42 | "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" | ||
43 | ("<->" bodies+=Conjunction (";" bodies+=Conjunction)*)? | ||
44 | "."; | ||
45 | |||
46 | enum RuleKind: | ||
47 | DIRECT="direct"; | ||
48 | |||
49 | RuleDefinition: | ||
50 | kind=RuleKind "rule" | ||
51 | name=Identifier | ||
52 | "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" | ||
53 | (":" bodies+=Conjunction (";" bodies+=Conjunction)* | ||
54 | "~>" action=Action)? | ||
55 | "."; | ||
56 | |||
57 | Parameter: | ||
58 | parameterType=[Relation|QualifiedName]? name=Identifier; | ||
59 | |||
60 | Conjunction: | ||
61 | literals+=Literal ("," literals+=Literal)*; | ||
62 | |||
63 | Action: | ||
64 | actionLiterals+=ActionLiteral ("," actionLiterals+=ActionLiteral)*; | ||
65 | |||
66 | Literal: | ||
67 | Atom | ValueLiteral | NegativeLiteral; | ||
68 | |||
69 | ValueLiteral: | ||
70 | atom=Atom | ||
71 | (refinement?=":" | "=") | ||
72 | values+=LogicConstant ("|" values+=LogicConstant)*; | ||
73 | |||
74 | NegativeLiteral: | ||
75 | "!" atom=Atom; | ||
76 | |||
77 | ActionLiteral: | ||
78 | ValueActionLiteral | DeleteActionLiteral | NewActionLiteral; | ||
79 | |||
80 | ValueActionLiteral: | ||
81 | atom=Atom | ||
82 | (refinement?=":" | "=") | ||
83 | value=LogicValue; | ||
84 | |||
85 | DeleteActionLiteral: | ||
86 | "delete" variableOrNode=[VariableOrNode|QualifiedName]; | ||
87 | |||
88 | NewActionLiteral: | ||
89 | "new" variable=NewVariable; | ||
90 | |||
91 | NewVariable: | ||
92 | name=Identifier; | ||
93 | |||
94 | Atom: | ||
95 | relation=[Relation|QualifiedName] | ||
96 | transitiveClosure?="+"? | ||
97 | "(" (arguments+=Argument ("," arguments+=Argument)*)? ")"; | ||
98 | |||
99 | LogicConstant: | ||
100 | value=LogicValue; | ||
101 | |||
102 | Argument: | ||
103 | VariableOrNodeArgument | ConstantArgument; | ||
104 | |||
105 | VariableOrNodeArgument: | ||
106 | variableOrNode=[VariableOrNode|QualifiedName]; | ||
107 | |||
108 | ConstantArgument: | ||
109 | constant=Constant; | ||
110 | |||
111 | Assertion: | ||
112 | default?="default"? | ||
113 | (value=ShortLogicValue? | ||
114 | relation=[Relation|QualifiedName] | ||
115 | "(" (arguments+=AssertionArgument ("," arguments+=AssertionArgument)*)? ")" | ||
116 | | relation=[Relation|QualifiedName] | ||
117 | "(" (arguments+=AssertionArgument ("," arguments+=AssertionArgument)*)? ")" | ||
118 | ":" value=LogicValue) | ||
119 | "."; | ||
120 | |||
121 | AssertionArgument: | ||
122 | NodeAssertionArgument | WildcardAssertionArgument | ConstantAssertionArgument; | ||
123 | |||
124 | NodeAssertionArgument: | ||
125 | node=[Node|QualifiedName]; | ||
126 | |||
127 | WildcardAssertionArgument: | ||
128 | {WildcardAssertionArgument} "*"; | ||
129 | |||
130 | ConstantAssertionArgument: | ||
131 | constant=Constant; | ||
132 | |||
133 | enum LogicValue: | ||
134 | TRUE="true" | FALSE="false" | UNKNOWN="unknown" | ERROR="error"; | ||
135 | |||
136 | enum ShortLogicValue returns LogicValue: | ||
137 | FALSE="!" | UNKNOWN="?"; | ||
138 | |||
139 | NodeValueAssertion: | ||
140 | node=[Node|QualifiedName] ":" value=Constant "."; | ||
141 | |||
142 | Constant: | ||
143 | RealConstant | IntConstant | StringConstant; | ||
144 | |||
145 | IntConstant: | ||
146 | intValue=Integer; | ||
147 | |||
148 | RealConstant: | ||
149 | realValue=Real; | ||
150 | |||
151 | StringConstant: | ||
152 | stringValue=STRING; | ||
153 | |||
154 | ScopeDeclaration: | ||
155 | "scope" typeScopes+=TypeScope ("," typeScopes+=TypeScope)* "."; | ||
156 | |||
157 | TypeScope: | ||
158 | targetType=[ClassDeclaration|QualifiedName] | ||
159 | (increment?="+=" | "=") | ||
160 | multiplicity=DefiniteMultiplicity; | ||
161 | |||
162 | Multiplicity: | ||
163 | UnboundedMultiplicity | DefiniteMultiplicity; | ||
164 | |||
165 | DefiniteMultiplicity returns Multiplicity: | ||
166 | RangeMultiplicity | ExactMultiplicity; | ||
167 | |||
168 | UnboundedMultiplicity: | ||
169 | {UnboundedMultiplicity}; | ||
170 | |||
171 | RangeMultiplicity: | ||
172 | lowerBound=INT ".." upperBound=UpperBound; | ||
173 | |||
174 | ExactMultiplicity: | ||
175 | exactValue=INT; | ||
176 | |||
177 | IndividualDeclaration: | ||
178 | "indiv" nodes+=EnumLiteral ("," nodes+=EnumLiteral)* "."; | ||
179 | |||
180 | UpperBound returns ecore::EInt: | ||
181 | INT | "*"; | ||
182 | |||
183 | QualifiedName hidden(): | ||
184 | Identifier ("::" Identifier)*; | ||
185 | |||
186 | Identifier: | ||
187 | ID | "true" | "false" | "unknown" | "error" | "class" | "abstract" | "extends" | "enum" | "pred" | | ||
188 | "indiv" | "problem" | "new" | "delete" | "direct" | "rule"; | ||
189 | |||
190 | Integer returns ecore::EInt hidden(): | ||
191 | "-"? INT; | ||
192 | |||
193 | Real returns ecore::EDouble: | ||
194 | "-"? (EXPONENTIAL | INT "." (INT | EXPONENTIAL)); | ||
195 | |||
196 | @Override | ||
197 | terminal ID: | ||
198 | ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*; | ||
199 | |||
200 | terminal EXPONENTIAL: | ||
201 | INT ("e" | "E") ("+" | "-")? INT; | ||
202 | |||
203 | @Override | ||
204 | terminal SL_COMMENT: | ||
205 | ('%' | '//') !('\n' | '\r')* ('\r'? '\n')?; | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java new file mode 100644 index 00000000..dd7731b4 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java | |||
@@ -0,0 +1,84 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language; | ||
5 | |||
6 | import org.eclipse.xtext.conversion.IValueConverterService; | ||
7 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
8 | import org.eclipse.xtext.resource.DerivedStateAwareResource; | ||
9 | import org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager; | ||
10 | import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; | ||
11 | import org.eclipse.xtext.resource.IDerivedStateComputer; | ||
12 | import org.eclipse.xtext.resource.ILocationInFileProvider; | ||
13 | import org.eclipse.xtext.resource.IResourceDescription; | ||
14 | import org.eclipse.xtext.resource.XtextResource; | ||
15 | import org.eclipse.xtext.scoping.IGlobalScopeProvider; | ||
16 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
17 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
18 | import org.eclipse.xtext.validation.IResourceValidator; | ||
19 | import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; | ||
20 | |||
21 | import com.google.inject.Binder; | ||
22 | import com.google.inject.name.Names; | ||
23 | |||
24 | import tools.refinery.language.conversion.ProblemValueConverterService; | ||
25 | import tools.refinery.language.naming.ProblemQualifiedNameConverter; | ||
26 | import tools.refinery.language.resource.ProblemDerivedStateComputer; | ||
27 | import tools.refinery.language.resource.ProblemLocationInFileProvider; | ||
28 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
29 | import tools.refinery.language.scoping.ProblemGlobalScopeProvider; | ||
30 | import tools.refinery.language.scoping.ProblemLocalScopeProvider; | ||
31 | |||
32 | /** | ||
33 | * Use this class to register components to be used at runtime / without the | ||
34 | * Equinox extension registry. | ||
35 | */ | ||
36 | public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { | ||
37 | public Class<? extends IQualifiedNameConverter> bindIQualifiedNameConverter() { | ||
38 | return ProblemQualifiedNameConverter.class; | ||
39 | } | ||
40 | |||
41 | public Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() { | ||
42 | return ProblemResourceDescriptionStrategy.class; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public Class<? extends IValueConverterService> bindIValueConverterService() { | ||
47 | return ProblemValueConverterService.class; | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public Class<? extends IGlobalScopeProvider> bindIGlobalScopeProvider() { | ||
52 | return ProblemGlobalScopeProvider.class; | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public void configureIScopeProviderDelegate(Binder binder) { | ||
57 | binder.bind(IScopeProvider.class).annotatedWith(Names.named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE)) | ||
58 | .to(ProblemLocalScopeProvider.class); | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public Class<? extends XtextResource> bindXtextResource() { | ||
63 | return DerivedStateAwareResource.class; | ||
64 | } | ||
65 | |||
66 | // Method name follows Xtext convention. | ||
67 | @SuppressWarnings("squid:S100") | ||
68 | public Class<? extends IResourceDescription.Manager> bindIResourceDescription$Manager() { | ||
69 | return DerivedStateAwareResourceDescriptionManager.class; | ||
70 | } | ||
71 | |||
72 | public Class<? extends IResourceValidator> bindIResourceValidator() { | ||
73 | return DerivedStateAwareResourceValidator.class; | ||
74 | } | ||
75 | |||
76 | public Class<? extends IDerivedStateComputer> bindIDerivedStateComputer() { | ||
77 | return ProblemDerivedStateComputer.class; | ||
78 | } | ||
79 | |||
80 | @Override | ||
81 | public Class<? extends ILocationInFileProvider> bindILocationInFileProvider() { | ||
82 | return ProblemLocationInFileProvider.class; | ||
83 | } | ||
84 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java new file mode 100644 index 00000000..d753a119 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemStandaloneSetup.java | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language; | ||
5 | |||
6 | import org.eclipse.emf.ecore.resource.Resource; | ||
7 | import org.eclipse.xtext.resource.IResourceFactory; | ||
8 | import org.eclipse.xtext.resource.IResourceServiceProvider; | ||
9 | |||
10 | import com.google.inject.Guice; | ||
11 | import com.google.inject.Injector; | ||
12 | |||
13 | import tools.refinery.language.model.ProblemEMFSetup; | ||
14 | |||
15 | /** | ||
16 | * Initialization support for running Xtext languages without Equinox extension | ||
17 | * registry. | ||
18 | */ | ||
19 | public class ProblemStandaloneSetup extends ProblemStandaloneSetupGenerated { | ||
20 | |||
21 | public static void doSetup() { | ||
22 | new ProblemStandaloneSetup().createInjectorAndDoEMFRegistration(); | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public Injector createInjectorAndDoEMFRegistration() { | ||
27 | ProblemEMFSetup.doEMFRegistration(); | ||
28 | var xmiInjector = createXmiInjector(); | ||
29 | registerXmiInjector(xmiInjector); | ||
30 | return super.createInjectorAndDoEMFRegistration(); | ||
31 | } | ||
32 | |||
33 | protected Injector createXmiInjector() { | ||
34 | return Guice.createInjector(new ProblemXmiRuntimeModule()); | ||
35 | } | ||
36 | |||
37 | protected void registerXmiInjector(Injector injector) { | ||
38 | IResourceFactory resourceFactory = injector.getInstance(IResourceFactory.class); | ||
39 | IResourceServiceProvider serviceProvider = injector.getInstance(IResourceServiceProvider.class); | ||
40 | |||
41 | Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(ProblemEMFSetup.XMI_RESOURCE_EXTENSION, resourceFactory); | ||
42 | IResourceServiceProvider.Registry.INSTANCE.getExtensionToFactoryMap().put(ProblemEMFSetup.XMI_RESOURCE_EXTENSION, serviceProvider); | ||
43 | } | ||
44 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemXmiRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemXmiRuntimeModule.java new file mode 100644 index 00000000..03a33bee --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemXmiRuntimeModule.java | |||
@@ -0,0 +1,35 @@ | |||
1 | package tools.refinery.language; | ||
2 | |||
3 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
4 | import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; | ||
5 | import org.eclipse.xtext.resource.IResourceFactory; | ||
6 | import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule; | ||
7 | |||
8 | import tools.refinery.language.model.ProblemEMFSetup; | ||
9 | import tools.refinery.language.naming.ProblemQualifiedNameConverter; | ||
10 | import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; | ||
11 | import tools.refinery.language.resource.ProblemXmiResourceFactory; | ||
12 | |||
13 | public class ProblemXmiRuntimeModule extends AbstractGenericResourceRuntimeModule { | ||
14 | @Override | ||
15 | protected String getLanguageName() { | ||
16 | return "tools.refinery.language.ProblemXmi"; | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | protected String getFileExtensions() { | ||
21 | return ProblemEMFSetup.XMI_RESOURCE_EXTENSION; | ||
22 | } | ||
23 | |||
24 | public Class<? extends IResourceFactory> bindIResourceFactory() { | ||
25 | return ProblemXmiResourceFactory.class; | ||
26 | } | ||
27 | |||
28 | public Class<? extends IQualifiedNameConverter> bindIQualifiedNameConverter() { | ||
29 | return ProblemQualifiedNameConverter.class; | ||
30 | } | ||
31 | |||
32 | public Class<? extends IDefaultResourceDescriptionStrategy> bindIDefaultResourceDescriptionStrategy() { | ||
33 | return ProblemResourceDescriptionStrategy.class; | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java b/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java new file mode 100644 index 00000000..508688ed --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/conversion/ProblemValueConverterService.java | |||
@@ -0,0 +1,19 @@ | |||
1 | package tools.refinery.language.conversion; | ||
2 | |||
3 | import org.eclipse.xtext.common.services.DefaultTerminalConverters; | ||
4 | import org.eclipse.xtext.conversion.IValueConverter; | ||
5 | import org.eclipse.xtext.conversion.ValueConverter; | ||
6 | |||
7 | import com.google.inject.Inject; | ||
8 | |||
9 | public class ProblemValueConverterService extends DefaultTerminalConverters { | ||
10 | @Inject | ||
11 | private UpperBoundValueConverter upperBoundValueConverter; | ||
12 | |||
13 | @ValueConverter(rule = "UpperBound") | ||
14 | // Method name follows Xtext convention. | ||
15 | @SuppressWarnings("squid:S100") | ||
16 | public IValueConverter<Integer> UpperBound() { | ||
17 | return upperBoundValueConverter; | ||
18 | } | ||
19 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java b/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java new file mode 100644 index 00000000..be0d15ad --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/conversion/UpperBoundValueConverter.java | |||
@@ -0,0 +1,35 @@ | |||
1 | package tools.refinery.language.conversion; | ||
2 | |||
3 | import org.eclipse.xtext.conversion.ValueConverterException; | ||
4 | import org.eclipse.xtext.conversion.impl.AbstractValueConverter; | ||
5 | import org.eclipse.xtext.conversion.impl.INTValueConverter; | ||
6 | import org.eclipse.xtext.nodemodel.INode; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.Singleton; | ||
10 | |||
11 | @Singleton | ||
12 | public class UpperBoundValueConverter extends AbstractValueConverter<Integer> { | ||
13 | public static final String INFINITY = "*"; | ||
14 | |||
15 | @Inject | ||
16 | private INTValueConverter intValueConverter; | ||
17 | |||
18 | @Override | ||
19 | public Integer toValue(String string, INode node) throws ValueConverterException { | ||
20 | if (INFINITY.equals(string)) { | ||
21 | return -1; | ||
22 | } else { | ||
23 | return intValueConverter.toValue(string, node); | ||
24 | } | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public String toString(Integer value) throws ValueConverterException { | ||
29 | if (value < 0) { | ||
30 | return INFINITY; | ||
31 | } else { | ||
32 | return intValueConverter.toString(value); | ||
33 | } | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java new file mode 100644 index 00000000..903347f7 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java | |||
@@ -0,0 +1,183 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.26.0.M2 | ||
3 | */ | ||
4 | package tools.refinery.language.formatting2; | ||
5 | |||
6 | import org.eclipse.emf.ecore.EObject; | ||
7 | import org.eclipse.xtext.formatting2.AbstractJavaFormatter; | ||
8 | import org.eclipse.xtext.formatting2.IFormattableDocument; | ||
9 | import org.eclipse.xtext.formatting2.IHiddenRegionFormatter; | ||
10 | import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder; | ||
11 | import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion; | ||
12 | import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; | ||
13 | |||
14 | import tools.refinery.language.model.problem.Assertion; | ||
15 | import tools.refinery.language.model.problem.Atom; | ||
16 | import tools.refinery.language.model.problem.ClassDeclaration; | ||
17 | import tools.refinery.language.model.problem.Conjunction; | ||
18 | import tools.refinery.language.model.problem.IndividualDeclaration; | ||
19 | import tools.refinery.language.model.problem.NegativeLiteral; | ||
20 | import tools.refinery.language.model.problem.Parameter; | ||
21 | import tools.refinery.language.model.problem.PredicateDefinition; | ||
22 | import tools.refinery.language.model.problem.Problem; | ||
23 | import tools.refinery.language.model.problem.ProblemPackage; | ||
24 | |||
25 | public class ProblemFormatter extends AbstractJavaFormatter { | ||
26 | |||
27 | protected void format(Problem problem, IFormattableDocument doc) { | ||
28 | doc.prepend(problem, this::noSpace); | ||
29 | var region = regionFor(problem); | ||
30 | doc.append(region.keyword("problem"), this::oneSpace); | ||
31 | doc.prepend(region.keyword("."), this::noSpace); | ||
32 | appendNewLines(doc, region.keyword("."), this::twoNewLines); | ||
33 | for (var statement : problem.getStatements()) { | ||
34 | doc.format(statement); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | protected void format(Assertion assertion, IFormattableDocument doc) { | ||
39 | surroundNewLines(doc, assertion, this::singleNewLine); | ||
40 | var region = regionFor(assertion); | ||
41 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__DEFAULT), this::oneSpace); | ||
42 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__VALUE), this::noSpace); | ||
43 | doc.append(region.feature(ProblemPackage.Literals.ASSERTION__RELATION), this::noSpace); | ||
44 | formatParenthesizedList(region, doc); | ||
45 | doc.prepend(region.keyword(":"), this::noSpace); | ||
46 | doc.append(region.keyword(":"), this::oneSpace); | ||
47 | doc.prepend(region.keyword("."), this::noSpace); | ||
48 | for (var argument : assertion.getArguments()) { | ||
49 | doc.format(argument); | ||
50 | } | ||
51 | } | ||
52 | |||
53 | protected void format(ClassDeclaration classDeclaration, IFormattableDocument doc) { | ||
54 | surroundNewLines(doc, classDeclaration, this::twoNewLines); | ||
55 | var region = regionFor(classDeclaration); | ||
56 | doc.append(region.feature(ProblemPackage.Literals.CLASS_DECLARATION__ABSTRACT), this::oneSpace); | ||
57 | doc.append(region.keyword("class"), this::oneSpace); | ||
58 | doc.surround(region.keyword("extends"), this::oneSpace); | ||
59 | formatList(region, ",", doc); | ||
60 | doc.prepend(region.keyword("{"), this::oneSpace); | ||
61 | doc.append(region.keyword("{"), it -> it.setNewLines(1, 1, 2)); | ||
62 | doc.prepend(region.keyword("}"), it -> it.setNewLines(1, 1, 2)); | ||
63 | doc.prepend(region.keyword("."), this::noSpace); | ||
64 | for (var referenceDeclaration : classDeclaration.getReferenceDeclarations()) { | ||
65 | doc.format(referenceDeclaration); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | protected void format(PredicateDefinition predicateDefinition, IFormattableDocument doc) { | ||
70 | surroundNewLines(doc, predicateDefinition, this::twoNewLines); | ||
71 | var region = regionFor(predicateDefinition); | ||
72 | doc.append(region.feature(ProblemPackage.Literals.PREDICATE_DEFINITION__KIND), this::oneSpace); | ||
73 | doc.append(region.keyword("pred"), this::oneSpace); | ||
74 | doc.append(region.feature(ProblemPackage.Literals.NAMED_ELEMENT__NAME), this::noSpace); | ||
75 | formatParenthesizedList(region, doc); | ||
76 | doc.surround(region.keyword("<->"), this::oneSpace); | ||
77 | formatList(region, ";", doc); | ||
78 | doc.prepend(region.keyword("."), this::noSpace); | ||
79 | for (var parameter : predicateDefinition.getParameters()) { | ||
80 | doc.format(parameter); | ||
81 | } | ||
82 | for (var body : predicateDefinition.getBodies()) { | ||
83 | doc.format(body); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | protected void format(Parameter parameter, IFormattableDocument doc) { | ||
88 | doc.append(regionFor(parameter).feature(ProblemPackage.Literals.PARAMETER__PARAMETER_TYPE), this::oneSpace); | ||
89 | } | ||
90 | |||
91 | protected void format(Conjunction conjunction, IFormattableDocument doc) { | ||
92 | var region = regionFor(conjunction); | ||
93 | formatList(region, ",", doc); | ||
94 | for (var literal : conjunction.getLiterals()) { | ||
95 | doc.format(literal); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | protected void format(NegativeLiteral literal, IFormattableDocument doc) { | ||
100 | var region = regionFor(literal); | ||
101 | doc.append(region.keyword("!"), this::noSpace); | ||
102 | doc.format(literal.getAtom()); | ||
103 | } | ||
104 | |||
105 | protected void format(Atom atom, IFormattableDocument doc) { | ||
106 | var region = regionFor(atom); | ||
107 | doc.append(region.feature(ProblemPackage.Literals.ATOM__RELATION), this::noSpace); | ||
108 | doc.append(region.feature(ProblemPackage.Literals.ATOM__TRANSITIVE_CLOSURE), this::noSpace); | ||
109 | formatParenthesizedList(region, doc); | ||
110 | for (var argument : atom.getArguments()) { | ||
111 | doc.format(argument); | ||
112 | } | ||
113 | } | ||
114 | |||
115 | protected void format(IndividualDeclaration individualDeclaration, IFormattableDocument doc) { | ||
116 | surroundNewLines(doc, individualDeclaration, this::singleNewLine); | ||
117 | var region = regionFor(individualDeclaration); | ||
118 | doc.append(region.keyword("indiv"), this::oneSpace); | ||
119 | formatList(region, ",", doc); | ||
120 | doc.prepend(region.keyword("."), this::noSpace); | ||
121 | } | ||
122 | |||
123 | protected void formatParenthesizedList(ISemanticRegionsFinder region, IFormattableDocument doc) { | ||
124 | doc.append(region.keyword("("), this::noSpace); | ||
125 | doc.prepend(region.keyword(")"), this::noSpace); | ||
126 | formatList(region, ",", doc); | ||
127 | } | ||
128 | |||
129 | protected void formatList(ISemanticRegionsFinder region, String separator, IFormattableDocument doc) { | ||
130 | for (var comma : region.keywords(separator)) { | ||
131 | doc.prepend(comma, this::noSpace); | ||
132 | doc.append(comma, this::oneSpace); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | protected void singleNewLine(IHiddenRegionFormatter it) { | ||
137 | it.setNewLines(1, 1, 2); | ||
138 | } | ||
139 | |||
140 | protected void twoNewLines(IHiddenRegionFormatter it) { | ||
141 | it.highPriority(); | ||
142 | it.setNewLines(2); | ||
143 | } | ||
144 | |||
145 | protected void surroundNewLines(IFormattableDocument doc, EObject eObject, | ||
146 | Procedure1<? super IHiddenRegionFormatter> init) { | ||
147 | var region = doc.getRequest().getTextRegionAccess().regionForEObject(eObject); | ||
148 | preprendNewLines(doc, region, init); | ||
149 | appendNewLines(doc, region, init); | ||
150 | } | ||
151 | |||
152 | protected void preprendNewLines(IFormattableDocument doc, ISequentialRegion region, | ||
153 | Procedure1<? super IHiddenRegionFormatter> init) { | ||
154 | if (region == null) { | ||
155 | return; | ||
156 | } | ||
157 | var previousHiddenRegion = region.getPreviousHiddenRegion(); | ||
158 | if (previousHiddenRegion == null) { | ||
159 | return; | ||
160 | } | ||
161 | if (previousHiddenRegion.getPreviousSequentialRegion() == null) { | ||
162 | doc.set(previousHiddenRegion, it -> it.setNewLines(0)); | ||
163 | } else { | ||
164 | doc.set(previousHiddenRegion, init); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | protected void appendNewLines(IFormattableDocument doc, ISequentialRegion region, | ||
169 | Procedure1<? super IHiddenRegionFormatter> init) { | ||
170 | if (region == null) { | ||
171 | return; | ||
172 | } | ||
173 | var nextHiddenRegion = region.getNextHiddenRegion(); | ||
174 | if (nextHiddenRegion == null) { | ||
175 | return; | ||
176 | } | ||
177 | if (nextHiddenRegion.getNextSequentialRegion() == null) { | ||
178 | doc.set(nextHiddenRegion, it -> it.setNewLines(1)); | ||
179 | } else { | ||
180 | doc.set(nextHiddenRegion, init); | ||
181 | } | ||
182 | } | ||
183 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java new file mode 100644 index 00000000..e959be74 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/NamingUtil.java | |||
@@ -0,0 +1,25 @@ | |||
1 | package tools.refinery.language.naming; | ||
2 | |||
3 | import java.util.regex.Pattern; | ||
4 | |||
5 | public final class NamingUtil { | ||
6 | private static final String SINGLETON_VARIABLE_PREFIX = "_"; | ||
7 | |||
8 | private static final Pattern ID_REGEX = Pattern.compile("[_a-zA-Z][_0-9a-zA-Z]*"); | ||
9 | |||
10 | private NamingUtil() { | ||
11 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
12 | } | ||
13 | |||
14 | public static boolean isNullOrEmpty(String name) { | ||
15 | return name == null || name.isEmpty(); | ||
16 | } | ||
17 | |||
18 | public static boolean isSingletonVariableName(String name) { | ||
19 | return name != null && name.startsWith(SINGLETON_VARIABLE_PREFIX); | ||
20 | } | ||
21 | |||
22 | public static boolean isValidId(String name) { | ||
23 | return name != null && ID_REGEX.matcher(name).matches(); | ||
24 | } | ||
25 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java new file mode 100644 index 00000000..5453906f --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameConverter.java | |||
@@ -0,0 +1,15 @@ | |||
1 | package tools.refinery.language.naming; | ||
2 | |||
3 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
4 | |||
5 | import com.google.inject.Singleton; | ||
6 | |||
7 | @Singleton | ||
8 | public class ProblemQualifiedNameConverter extends IQualifiedNameConverter.DefaultImpl { | ||
9 | public static final String DELIMITER = "::"; | ||
10 | |||
11 | @Override | ||
12 | public String getDelimiter() { | ||
13 | return DELIMITER; | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java new file mode 100644 index 00000000..bb1226c4 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java | |||
@@ -0,0 +1,194 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.HashSet; | ||
4 | import java.util.List; | ||
5 | import java.util.Set; | ||
6 | |||
7 | import org.eclipse.xtext.linking.impl.LinkingHelper; | ||
8 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
9 | import org.eclipse.xtext.nodemodel.INode; | ||
10 | import org.eclipse.xtext.nodemodel.util.NodeModelUtils; | ||
11 | import org.eclipse.xtext.scoping.IScope; | ||
12 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
13 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
14 | |||
15 | import com.google.inject.Inject; | ||
16 | import com.google.inject.Singleton; | ||
17 | import com.google.inject.name.Named; | ||
18 | |||
19 | import tools.refinery.language.model.problem.Argument; | ||
20 | import tools.refinery.language.model.problem.Atom; | ||
21 | import tools.refinery.language.model.problem.Conjunction; | ||
22 | import tools.refinery.language.model.problem.ExistentialQuantifier; | ||
23 | import tools.refinery.language.model.problem.ImplicitVariable; | ||
24 | import tools.refinery.language.model.problem.Literal; | ||
25 | import tools.refinery.language.model.problem.NegativeLiteral; | ||
26 | import tools.refinery.language.model.problem.Parameter; | ||
27 | import tools.refinery.language.model.problem.ParametricDefinition; | ||
28 | import tools.refinery.language.model.problem.Problem; | ||
29 | import tools.refinery.language.model.problem.ProblemFactory; | ||
30 | import tools.refinery.language.model.problem.ProblemPackage; | ||
31 | import tools.refinery.language.model.problem.Statement; | ||
32 | import tools.refinery.language.model.problem.ValueLiteral; | ||
33 | import tools.refinery.language.model.problem.VariableOrNodeArgument; | ||
34 | import tools.refinery.language.naming.NamingUtil; | ||
35 | |||
36 | @Singleton | ||
37 | public class DerivedVariableComputer { | ||
38 | @Inject | ||
39 | private LinkingHelper linkingHelper; | ||
40 | |||
41 | @Inject | ||
42 | private IQualifiedNameConverter qualifiedNameConverter; | ||
43 | |||
44 | @Inject | ||
45 | @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE) | ||
46 | private IScopeProvider scopeProvider; | ||
47 | |||
48 | public void installDerivedVariables(Problem problem, Set<String> nodeNames) { | ||
49 | for (Statement statement : problem.getStatements()) { | ||
50 | if (statement instanceof ParametricDefinition definition) { | ||
51 | installDerivedParametricDefinitionState(definition, nodeNames); | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | |||
56 | protected void installDerivedParametricDefinitionState(ParametricDefinition definition, Set<String> nodeNames) { | ||
57 | Set<String> knownVariables = new HashSet<>(); | ||
58 | knownVariables.addAll(nodeNames); | ||
59 | for (Parameter parameter : definition.getParameters()) { | ||
60 | String name = parameter.getName(); | ||
61 | if (name != null) { | ||
62 | knownVariables.add(name); | ||
63 | } | ||
64 | } | ||
65 | for (Conjunction body : definition.getBodies()) { | ||
66 | installDeriveConjunctionState(body, knownVariables); | ||
67 | } | ||
68 | } | ||
69 | |||
70 | protected void installDeriveConjunctionState(Conjunction conjunction, Set<String> knownVariables) { | ||
71 | Set<String> newVariables = new HashSet<>(); | ||
72 | for (Literal literal : conjunction.getLiterals()) { | ||
73 | if (literal instanceof Atom atom) { | ||
74 | createSigletonVariablesAndCollectVariables(atom, knownVariables, newVariables); | ||
75 | } else | ||
76 | if (literal instanceof ValueLiteral valueLiteral) { | ||
77 | createSigletonVariablesAndCollectVariables(valueLiteral.getAtom(), knownVariables, newVariables); | ||
78 | } | ||
79 | } | ||
80 | createVariables(conjunction, newVariables); | ||
81 | newVariables.addAll(knownVariables); | ||
82 | for (Literal literal : conjunction.getLiterals()) { | ||
83 | if (literal instanceof NegativeLiteral negativeLiteral) { | ||
84 | installDeriveNegativeLiteralState(negativeLiteral, newVariables); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | |||
89 | protected void installDeriveNegativeLiteralState(NegativeLiteral negativeLiteral, Set<String> knownVariables) { | ||
90 | Set<String> newVariables = new HashSet<>(); | ||
91 | createSigletonVariablesAndCollectVariables(negativeLiteral.getAtom(), knownVariables, newVariables); | ||
92 | createVariables(negativeLiteral, newVariables); | ||
93 | } | ||
94 | |||
95 | protected void createSigletonVariablesAndCollectVariables(Atom atom, Set<String> knownVariables, | ||
96 | Set<String> newVariables) { | ||
97 | for (Argument argument : atom.getArguments()) { | ||
98 | if (argument instanceof VariableOrNodeArgument variableOrNodeArgument) { | ||
99 | IScope scope = scopeProvider.getScope(variableOrNodeArgument, | ||
100 | ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE); | ||
101 | List<INode> nodes = NodeModelUtils.findNodesForFeature(variableOrNodeArgument, | ||
102 | ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE); | ||
103 | for (INode node : nodes) { | ||
104 | var variableName = linkingHelper.getCrossRefNodeAsString(node, true); | ||
105 | var created = tryCreateVariableForArgument(variableOrNodeArgument, variableName, scope, | ||
106 | knownVariables, newVariables); | ||
107 | if (created) { | ||
108 | break; | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | protected boolean tryCreateVariableForArgument(VariableOrNodeArgument variableOrNodeArgument, String variableName, | ||
116 | IScope scope, Set<String> knownVariables, Set<String> newVariables) { | ||
117 | if (!NamingUtil.isValidId(variableName)) { | ||
118 | return false; | ||
119 | } | ||
120 | var qualifiedName = qualifiedNameConverter.toQualifiedName(variableName); | ||
121 | if (scope.getSingleElement(qualifiedName) != null) { | ||
122 | return false; | ||
123 | } | ||
124 | if (NamingUtil.isSingletonVariableName(variableName)) { | ||
125 | createSingletonVariable(variableOrNodeArgument, variableName); | ||
126 | return true; | ||
127 | } | ||
128 | if (!knownVariables.contains(variableName)) { | ||
129 | newVariables.add(variableName); | ||
130 | return true; | ||
131 | } | ||
132 | return false; | ||
133 | } | ||
134 | |||
135 | protected void createVariables(ExistentialQuantifier quantifier, Set<String> newVariables) { | ||
136 | for (String variableName : newVariables) { | ||
137 | createVariable(quantifier, variableName); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | protected void createVariable(ExistentialQuantifier quantifier, String variableName) { | ||
142 | if (NamingUtil.isValidId(variableName)) { | ||
143 | ImplicitVariable variable = createNamedVariable(variableName); | ||
144 | quantifier.getImplicitVariables().add(variable); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | protected void createSingletonVariable(VariableOrNodeArgument argument, String variableName) { | ||
149 | if (NamingUtil.isValidId(variableName)) { | ||
150 | ImplicitVariable variable = createNamedVariable(variableName); | ||
151 | argument.setSingletonVariable(variable); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | protected ImplicitVariable createNamedVariable(String variableName) { | ||
156 | var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); | ||
157 | variable.setName(variableName); | ||
158 | return variable; | ||
159 | } | ||
160 | |||
161 | public void discardDerivedVariables(Problem problem) { | ||
162 | for (Statement statement : problem.getStatements()) { | ||
163 | if (statement instanceof ParametricDefinition parametricDefinition) { | ||
164 | discardParametricDefinitionState(parametricDefinition); | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | protected void discardParametricDefinitionState(ParametricDefinition definition) { | ||
170 | for (Conjunction body : definition.getBodies()) { | ||
171 | body.getImplicitVariables().clear(); | ||
172 | for (Literal literal : body.getLiterals()) { | ||
173 | if (literal instanceof Atom atom) { | ||
174 | discardDerivedAtomState(atom); | ||
175 | } | ||
176 | if (literal instanceof NegativeLiteral negativeLiteral) { | ||
177 | negativeLiteral.getImplicitVariables().clear(); | ||
178 | discardDerivedAtomState(negativeLiteral.getAtom()); | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | protected void discardDerivedAtomState(Atom atom) { | ||
185 | if (atom == null) { | ||
186 | return; | ||
187 | } | ||
188 | for (Argument argument : atom.getArguments()) { | ||
189 | if (argument instanceof VariableOrNodeArgument variableOrNodeArgument) { | ||
190 | variableOrNodeArgument.setSingletonVariable(null); | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java b/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java new file mode 100644 index 00000000..99bf9b64 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/NodeNameCollector.java | |||
@@ -0,0 +1,88 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.List; | ||
4 | import java.util.Set; | ||
5 | |||
6 | import org.eclipse.emf.ecore.EObject; | ||
7 | import org.eclipse.emf.ecore.EStructuralFeature; | ||
8 | import org.eclipse.xtext.linking.impl.LinkingHelper; | ||
9 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
10 | import org.eclipse.xtext.nodemodel.INode; | ||
11 | import org.eclipse.xtext.nodemodel.util.NodeModelUtils; | ||
12 | import org.eclipse.xtext.scoping.IScope; | ||
13 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
14 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
15 | |||
16 | import com.google.common.collect.ImmutableSet; | ||
17 | import com.google.inject.Inject; | ||
18 | import com.google.inject.name.Named; | ||
19 | |||
20 | import tools.refinery.language.model.problem.Assertion; | ||
21 | import tools.refinery.language.model.problem.AssertionArgument; | ||
22 | import tools.refinery.language.model.problem.NodeAssertionArgument; | ||
23 | import tools.refinery.language.model.problem.NodeValueAssertion; | ||
24 | import tools.refinery.language.model.problem.Problem; | ||
25 | import tools.refinery.language.model.problem.ProblemPackage; | ||
26 | import tools.refinery.language.model.problem.Statement; | ||
27 | import tools.refinery.language.naming.NamingUtil; | ||
28 | |||
29 | public class NodeNameCollector { | ||
30 | @Inject | ||
31 | private LinkingHelper linkingHelper; | ||
32 | |||
33 | @Inject | ||
34 | private IQualifiedNameConverter qualifiedNameConverter; | ||
35 | |||
36 | @Inject | ||
37 | @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE) | ||
38 | private IScopeProvider scopeProvider; | ||
39 | |||
40 | private final ImmutableSet.Builder<String> nodeNames = ImmutableSet.builder(); | ||
41 | |||
42 | private IScope nodeScope; | ||
43 | |||
44 | public Set<String> getNodeNames() { | ||
45 | return nodeNames.build(); | ||
46 | } | ||
47 | |||
48 | public void collectNodeNames(Problem problem) { | ||
49 | nodeScope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | ||
50 | for (Statement statement : problem.getStatements()) { | ||
51 | collectStatementNodeNames(statement); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | protected void collectStatementNodeNames(Statement statement) { | ||
56 | if (statement instanceof Assertion assertion) { | ||
57 | collectAssertionNodeNames(assertion); | ||
58 | } else if (statement instanceof NodeValueAssertion nodeValueAssertion) { | ||
59 | collectNodeValueAssertionNodeNames(nodeValueAssertion); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | protected void collectAssertionNodeNames(Assertion assertion) { | ||
64 | for (AssertionArgument argument : assertion.getArguments()) { | ||
65 | if (argument instanceof NodeAssertionArgument) { | ||
66 | collectNodeNames(argument, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | protected void collectNodeValueAssertionNodeNames(NodeValueAssertion nodeValueAssertion) { | ||
72 | collectNodeNames(nodeValueAssertion, ProblemPackage.Literals.NODE_VALUE_ASSERTION__NODE); | ||
73 | } | ||
74 | |||
75 | private void collectNodeNames(EObject eObject, EStructuralFeature feature) { | ||
76 | List<INode> nodes = NodeModelUtils.findNodesForFeature(eObject, feature); | ||
77 | for (INode node : nodes) { | ||
78 | var nodeName = linkingHelper.getCrossRefNodeAsString(node, true); | ||
79 | if (!NamingUtil.isValidId(nodeName)) { | ||
80 | continue; | ||
81 | } | ||
82 | var qualifiedName = qualifiedNameConverter.toQualifiedName(nodeName); | ||
83 | if (nodeScope.getSingleElement(qualifiedName) == null) { | ||
84 | nodeNames.add(nodeName); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | } \ No newline at end of file | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java new file mode 100644 index 00000000..275feca3 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java | |||
@@ -0,0 +1,163 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.Collection; | ||
4 | import java.util.HashMap; | ||
5 | import java.util.HashSet; | ||
6 | import java.util.List; | ||
7 | import java.util.Map; | ||
8 | import java.util.Set; | ||
9 | import java.util.function.Function; | ||
10 | |||
11 | import org.eclipse.emf.common.notify.impl.AdapterImpl; | ||
12 | import org.eclipse.emf.ecore.EObject; | ||
13 | import org.eclipse.emf.ecore.resource.Resource; | ||
14 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
15 | import org.eclipse.xtext.Constants; | ||
16 | import org.eclipse.xtext.resource.DerivedStateAwareResource; | ||
17 | import org.eclipse.xtext.resource.IDerivedStateComputer; | ||
18 | import org.eclipse.xtext.resource.XtextResource; | ||
19 | import org.eclipse.xtext.scoping.IScopeProvider; | ||
20 | import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; | ||
21 | |||
22 | import com.google.inject.Inject; | ||
23 | import com.google.inject.Provider; | ||
24 | import com.google.inject.Singleton; | ||
25 | import com.google.inject.name.Named; | ||
26 | |||
27 | import tools.refinery.language.model.problem.ClassDeclaration; | ||
28 | import tools.refinery.language.model.problem.Node; | ||
29 | import tools.refinery.language.model.problem.Problem; | ||
30 | import tools.refinery.language.model.problem.ProblemFactory; | ||
31 | import tools.refinery.language.model.problem.Statement; | ||
32 | |||
33 | @Singleton | ||
34 | public class ProblemDerivedStateComputer implements IDerivedStateComputer { | ||
35 | public static final String NEW_NODE = "new"; | ||
36 | |||
37 | @Inject | ||
38 | @Named(Constants.LANGUAGE_NAME) | ||
39 | private String languageName; | ||
40 | |||
41 | @Inject | ||
42 | @Named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE) | ||
43 | private IScopeProvider scopeProvider; | ||
44 | |||
45 | @Inject | ||
46 | private Provider<NodeNameCollector> nodeNameCollectorProvider; | ||
47 | |||
48 | @Inject | ||
49 | private DerivedVariableComputer derivedVariableComputer; | ||
50 | |||
51 | @Override | ||
52 | public void installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) { | ||
53 | var problem = getProblem(resource); | ||
54 | if (problem != null) { | ||
55 | var adapter = getOrInstallAdapter(resource); | ||
56 | installDerivedProblemState(problem, adapter, preLinkingPhase); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | protected Problem getProblem(Resource resource) { | ||
61 | List<EObject> contents = resource.getContents(); | ||
62 | if (contents.isEmpty()) { | ||
63 | return null; | ||
64 | } | ||
65 | EObject object = contents.get(0); | ||
66 | if (object instanceof Problem problem) { | ||
67 | return problem; | ||
68 | } | ||
69 | return null; | ||
70 | } | ||
71 | |||
72 | protected void installDerivedProblemState(Problem problem, Adapter adapter, boolean preLinkingPhase) { | ||
73 | installNewNodes(problem, adapter); | ||
74 | if (preLinkingPhase) { | ||
75 | return; | ||
76 | } | ||
77 | Set<String> nodeNames = installDerivedNodes(problem); | ||
78 | derivedVariableComputer.installDerivedVariables(problem, nodeNames); | ||
79 | } | ||
80 | |||
81 | protected void installNewNodes(Problem problem, Adapter adapter) { | ||
82 | for (Statement statement : problem.getStatements()) { | ||
83 | if (statement instanceof ClassDeclaration declaration && !declaration.isAbstract() | ||
84 | && declaration.getNewNode() == null) { | ||
85 | var newNode = adapter.createNodeIfAbsent(declaration, key -> createNode(NEW_NODE)); | ||
86 | declaration.setNewNode(newNode); | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | protected Set<String> installDerivedNodes(Problem problem) { | ||
92 | var collector = nodeNameCollectorProvider.get(); | ||
93 | collector.collectNodeNames(problem); | ||
94 | Set<String> nodeNames = collector.getNodeNames(); | ||
95 | List<Node> grapNodes = problem.getNodes(); | ||
96 | for (String nodeName : nodeNames) { | ||
97 | var graphNode = createNode(nodeName); | ||
98 | grapNodes.add(graphNode); | ||
99 | } | ||
100 | return nodeNames; | ||
101 | } | ||
102 | |||
103 | protected Node createNode(String name) { | ||
104 | var node = ProblemFactory.eINSTANCE.createNode(); | ||
105 | node.setName(name); | ||
106 | return node; | ||
107 | } | ||
108 | |||
109 | @Override | ||
110 | public void discardDerivedState(DerivedStateAwareResource resource) { | ||
111 | var problem = getProblem(resource); | ||
112 | if (problem != null) { | ||
113 | var adapter = getOrInstallAdapter(resource); | ||
114 | discardDerivedProblemState(problem, adapter); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | protected void discardDerivedProblemState(Problem problem, Adapter adapter) { | ||
119 | Set<ClassDeclaration> classDeclarations = new HashSet<>(); | ||
120 | problem.getNodes().clear(); | ||
121 | for (Statement statement : problem.getStatements()) { | ||
122 | if (statement instanceof ClassDeclaration classDeclaration) { | ||
123 | classDeclaration.setNewNode(null); | ||
124 | classDeclarations.add(classDeclaration); | ||
125 | } | ||
126 | } | ||
127 | adapter.retainAll(classDeclarations); | ||
128 | derivedVariableComputer.discardDerivedVariables(problem); | ||
129 | } | ||
130 | |||
131 | protected Adapter getOrInstallAdapter(Resource resource) { | ||
132 | if (!(resource instanceof XtextResource)) { | ||
133 | return new Adapter(); | ||
134 | } | ||
135 | String resourceLanguageName = ((XtextResource) resource).getLanguageName(); | ||
136 | if (!languageName.equals(resourceLanguageName)) { | ||
137 | return new Adapter(); | ||
138 | } | ||
139 | var adapter = (Adapter) EcoreUtil.getAdapter(resource.eAdapters(), Adapter.class); | ||
140 | if (adapter == null) { | ||
141 | adapter = new Adapter(); | ||
142 | resource.eAdapters().add(adapter); | ||
143 | } | ||
144 | return adapter; | ||
145 | } | ||
146 | |||
147 | protected static class Adapter extends AdapterImpl { | ||
148 | private Map<ClassDeclaration, Node> newNodes = new HashMap<>(); | ||
149 | |||
150 | public Node createNodeIfAbsent(ClassDeclaration classDeclaration, Function<ClassDeclaration, Node> createNode) { | ||
151 | return newNodes.computeIfAbsent(classDeclaration, createNode); | ||
152 | } | ||
153 | |||
154 | public void retainAll(Collection<ClassDeclaration> classDeclarations) { | ||
155 | newNodes.keySet().retainAll(classDeclarations); | ||
156 | } | ||
157 | |||
158 | @Override | ||
159 | public boolean isAdapterForType(Object type) { | ||
160 | return Adapter.class == type; | ||
161 | } | ||
162 | } | ||
163 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java new file mode 100644 index 00000000..7aa75833 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java | |||
@@ -0,0 +1,33 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import org.eclipse.emf.ecore.EObject; | ||
4 | import org.eclipse.xtext.resource.DefaultLocationInFileProvider; | ||
5 | import org.eclipse.xtext.util.ITextRegion; | ||
6 | |||
7 | import tools.refinery.language.model.ProblemUtil; | ||
8 | import tools.refinery.language.model.problem.ImplicitVariable; | ||
9 | import tools.refinery.language.model.problem.Node; | ||
10 | |||
11 | public class ProblemLocationInFileProvider extends DefaultLocationInFileProvider { | ||
12 | @Override | ||
13 | protected ITextRegion doGetTextRegion(EObject obj, RegionDescription query) { | ||
14 | if (obj instanceof Node node) { | ||
15 | return getNodeTextRegion(node, query); | ||
16 | } | ||
17 | if (obj instanceof ImplicitVariable) { | ||
18 | return ITextRegion.EMPTY_REGION; | ||
19 | } | ||
20 | return super.doGetTextRegion(obj, query); | ||
21 | } | ||
22 | |||
23 | protected ITextRegion getNodeTextRegion(Node node, RegionDescription query) { | ||
24 | if (ProblemUtil.isIndividualNode(node)) { | ||
25 | return super.doGetTextRegion(node, query); | ||
26 | } | ||
27 | if (ProblemUtil.isNewNode(node)) { | ||
28 | EObject container = node.eContainer(); | ||
29 | return doGetTextRegion(container, query); | ||
30 | } | ||
31 | return ITextRegion.EMPTY_REGION; | ||
32 | } | ||
33 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java new file mode 100644 index 00000000..f86ebd38 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java | |||
@@ -0,0 +1,103 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import org.eclipse.emf.ecore.EObject; | ||
4 | import org.eclipse.xtext.EcoreUtil2; | ||
5 | import org.eclipse.xtext.naming.IQualifiedNameConverter; | ||
6 | import org.eclipse.xtext.naming.QualifiedName; | ||
7 | import org.eclipse.xtext.resource.EObjectDescription; | ||
8 | import org.eclipse.xtext.resource.IEObjectDescription; | ||
9 | import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; | ||
10 | import org.eclipse.xtext.util.IAcceptor; | ||
11 | |||
12 | import com.google.inject.Inject; | ||
13 | import com.google.inject.Singleton; | ||
14 | |||
15 | import tools.refinery.language.model.ProblemUtil; | ||
16 | import tools.refinery.language.model.problem.NamedElement; | ||
17 | import tools.refinery.language.model.problem.Node; | ||
18 | import tools.refinery.language.model.problem.Problem; | ||
19 | import tools.refinery.language.model.problem.Variable; | ||
20 | import tools.refinery.language.naming.NamingUtil; | ||
21 | |||
22 | @Singleton | ||
23 | public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { | ||
24 | @Inject | ||
25 | private IQualifiedNameConverter qualifiedNameConverter; | ||
26 | |||
27 | @Override | ||
28 | public boolean createEObjectDescriptions(EObject eObject, IAcceptor<IEObjectDescription> acceptor) { | ||
29 | if (!shouldExport(eObject)) { | ||
30 | return false; | ||
31 | } | ||
32 | var qualifiedName = getNameAsQualifiedName(eObject); | ||
33 | if (qualifiedName == null) { | ||
34 | return true; | ||
35 | } | ||
36 | var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); | ||
37 | var problemQualifiedName = getNameAsQualifiedName(problem); | ||
38 | boolean nameExported; | ||
39 | if (shouldExportSimpleName(eObject)) { | ||
40 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); | ||
41 | nameExported = true; | ||
42 | } else { | ||
43 | nameExported = false; | ||
44 | } | ||
45 | var parent = eObject.eContainer(); | ||
46 | while (parent != null && parent != problem) { | ||
47 | var parentQualifiedName = getNameAsQualifiedName(parent); | ||
48 | if (parentQualifiedName == null) { | ||
49 | parent = parent.eContainer(); | ||
50 | continue; | ||
51 | } | ||
52 | qualifiedName = parentQualifiedName.append(qualifiedName); | ||
53 | if (shouldExportSimpleName(parent)) { | ||
54 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); | ||
55 | nameExported = true; | ||
56 | } else { | ||
57 | nameExported = false; | ||
58 | } | ||
59 | parent = parent.eContainer(); | ||
60 | } | ||
61 | if (!nameExported) { | ||
62 | acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); | ||
63 | } | ||
64 | return true; | ||
65 | } | ||
66 | |||
67 | protected QualifiedName getNameAsQualifiedName(EObject eObject) { | ||
68 | if (!(eObject instanceof NamedElement)) { | ||
69 | return null; | ||
70 | } | ||
71 | var namedElement = (NamedElement) eObject; | ||
72 | var name = namedElement.getName(); | ||
73 | if (NamingUtil.isNullOrEmpty(name)) { | ||
74 | return null; | ||
75 | } | ||
76 | return qualifiedNameConverter.toQualifiedName(name); | ||
77 | } | ||
78 | |||
79 | protected boolean shouldExport(EObject eObject) { | ||
80 | if (eObject instanceof Variable) { | ||
81 | // Variables are always private to the containing predicate definition. | ||
82 | return false; | ||
83 | } | ||
84 | if (eObject instanceof Node node) { | ||
85 | // Only enum literals and new nodes are visible across problem files. | ||
86 | return ProblemUtil.isIndividualNode(node) || ProblemUtil.isNewNode(node); | ||
87 | } | ||
88 | return true; | ||
89 | } | ||
90 | |||
91 | protected boolean shouldExportSimpleName(EObject eObject) { | ||
92 | if (eObject instanceof Node node) { | ||
93 | return !ProblemUtil.isNewNode(node); | ||
94 | } | ||
95 | return true; | ||
96 | } | ||
97 | |||
98 | private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, | ||
99 | IAcceptor<IEObjectDescription> acceptor) { | ||
100 | var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); | ||
101 | acceptor.accept(EObjectDescription.create(qualifiedNameWithPrefix, eObject)); | ||
102 | } | ||
103 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java new file mode 100644 index 00000000..68aa6016 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java | |||
@@ -0,0 +1,16 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import org.eclipse.emf.common.util.URI; | ||
4 | import org.eclipse.emf.ecore.resource.Resource; | ||
5 | import org.eclipse.xtext.resource.IResourceFactory; | ||
6 | |||
7 | import tools.refinery.language.model.problem.util.ProblemResourceFactoryImpl; | ||
8 | |||
9 | public class ProblemXmiResourceFactory implements IResourceFactory { | ||
10 | private Resource.Factory problemResourceFactory = new ProblemResourceFactoryImpl(); | ||
11 | |||
12 | @Override | ||
13 | public Resource createResource(URI uri) { | ||
14 | return problemResourceFactory.createResource(uri); | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java new file mode 100644 index 00000000..7525dfc6 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ReferenceCounter.java | |||
@@ -0,0 +1,51 @@ | |||
1 | package tools.refinery.language.resource; | ||
2 | |||
3 | import java.util.HashMap; | ||
4 | import java.util.Map; | ||
5 | |||
6 | import org.eclipse.emf.ecore.EObject; | ||
7 | import org.eclipse.xtext.util.IResourceScopeCache; | ||
8 | |||
9 | import com.google.inject.Inject; | ||
10 | import com.google.inject.Singleton; | ||
11 | |||
12 | import tools.refinery.language.model.problem.Problem; | ||
13 | |||
14 | @Singleton | ||
15 | public class ReferenceCounter { | ||
16 | @Inject | ||
17 | private IResourceScopeCache cache; | ||
18 | |||
19 | public int countReferences(Problem problem, EObject eObject) { | ||
20 | var count = getReferenceCounts(problem).get(eObject); | ||
21 | if (count == null) { | ||
22 | return 0; | ||
23 | } | ||
24 | return count; | ||
25 | } | ||
26 | |||
27 | protected Map<EObject, Integer> getReferenceCounts(Problem problem) { | ||
28 | var resource = problem.eResource(); | ||
29 | if (resource == null) { | ||
30 | return doGetReferenceCounts(problem); | ||
31 | } | ||
32 | return cache.get(problem, resource, () -> doGetReferenceCounts(problem)); | ||
33 | } | ||
34 | |||
35 | protected Map<EObject, Integer> doGetReferenceCounts(Problem problem) { | ||
36 | var map = new HashMap<EObject, Integer>(); | ||
37 | countCrossReferences(problem, map); | ||
38 | var iterator = problem.eAllContents(); | ||
39 | while (iterator.hasNext()) { | ||
40 | var eObject = iterator.next(); | ||
41 | countCrossReferences(eObject, map); | ||
42 | } | ||
43 | return map; | ||
44 | } | ||
45 | |||
46 | protected void countCrossReferences(EObject eObject, Map<EObject, Integer> map) { | ||
47 | for (var referencedObject : eObject.eCrossReferences()) { | ||
48 | map.compute(referencedObject, (key, currentValue) -> currentValue == null ? 1 : currentValue + 1); | ||
49 | } | ||
50 | } | ||
51 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java new file mode 100644 index 00000000..b582d16b --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemGlobalScopeProvider.java | |||
@@ -0,0 +1,18 @@ | |||
1 | package tools.refinery.language.scoping; | ||
2 | |||
3 | import java.util.LinkedHashSet; | ||
4 | |||
5 | import org.eclipse.emf.common.util.URI; | ||
6 | import org.eclipse.emf.ecore.resource.Resource; | ||
7 | import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; | ||
8 | |||
9 | import tools.refinery.language.model.ProblemUtil; | ||
10 | |||
11 | public class ProblemGlobalScopeProvider extends ImportUriGlobalScopeProvider { | ||
12 | @Override | ||
13 | protected LinkedHashSet<URI> getImportedUris(Resource resource) { | ||
14 | LinkedHashSet<URI> importedUris = new LinkedHashSet<>(); | ||
15 | importedUris.add(ProblemUtil.BUILTIN_LIBRARY_URI); | ||
16 | return importedUris; | ||
17 | } | ||
18 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java new file mode 100644 index 00000000..85797025 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java | |||
@@ -0,0 +1,42 @@ | |||
1 | package tools.refinery.language.scoping; | ||
2 | |||
3 | import java.util.List; | ||
4 | |||
5 | import org.eclipse.emf.ecore.EObject; | ||
6 | import org.eclipse.emf.ecore.resource.Resource; | ||
7 | import org.eclipse.xtext.naming.QualifiedName; | ||
8 | import org.eclipse.xtext.resource.IResourceDescriptions; | ||
9 | import org.eclipse.xtext.resource.IResourceDescriptionsProvider; | ||
10 | import org.eclipse.xtext.resource.ISelectable; | ||
11 | import org.eclipse.xtext.scoping.impl.ImportNormalizer; | ||
12 | import org.eclipse.xtext.scoping.impl.ImportedNamespaceAwareLocalScopeProvider; | ||
13 | |||
14 | import com.google.inject.Inject; | ||
15 | |||
16 | import tools.refinery.language.model.ProblemUtil; | ||
17 | |||
18 | public class ProblemLocalScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { | ||
19 | private static final QualifiedName BUILTIN_LIBRARY_QUALIFIED_NAME = QualifiedName | ||
20 | .create(ProblemUtil.BUILTIN_LIBRARY_NAME); | ||
21 | |||
22 | @Inject | ||
23 | private IResourceDescriptionsProvider resourceDescriptionsProvider; | ||
24 | |||
25 | @Override | ||
26 | protected List<ImportNormalizer> getImplicitImports(boolean ignoreCase) { | ||
27 | return List.of(doCreateImportNormalizer(BUILTIN_LIBRARY_QUALIFIED_NAME, true, ignoreCase)); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | protected List<ImportNormalizer> getImportedNamespaceResolvers(EObject context, boolean ignoreCase) { | ||
32 | return List.of(); | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | protected ISelectable internalGetAllDescriptions(Resource resource) { | ||
37 | // Force the use of ProblemResourceDescriptionStrategy to include all QualifiedNames of objects. | ||
38 | IResourceDescriptions resourceDescriptions = resourceDescriptionsProvider | ||
39 | .getResourceDescriptions(resource.getResourceSet()); | ||
40 | return resourceDescriptions.getResourceDescription(resource.getURI()); | ||
41 | } | ||
42 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java new file mode 100644 index 00000000..d31a5308 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java | |||
@@ -0,0 +1,104 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.scoping; | ||
5 | |||
6 | import java.util.ArrayList; | ||
7 | import java.util.List; | ||
8 | |||
9 | import org.eclipse.emf.ecore.EObject; | ||
10 | import org.eclipse.emf.ecore.EReference; | ||
11 | import org.eclipse.xtext.EcoreUtil2; | ||
12 | import org.eclipse.xtext.scoping.IScope; | ||
13 | import org.eclipse.xtext.scoping.Scopes; | ||
14 | |||
15 | import tools.refinery.language.model.ProblemUtil; | ||
16 | import tools.refinery.language.model.problem.ClassDeclaration; | ||
17 | import tools.refinery.language.model.problem.ExistentialQuantifier; | ||
18 | import tools.refinery.language.model.problem.NewActionLiteral; | ||
19 | import tools.refinery.language.model.problem.ParametricDefinition; | ||
20 | import tools.refinery.language.model.problem.Action; | ||
21 | import tools.refinery.language.model.problem.Problem; | ||
22 | import tools.refinery.language.model.problem.ProblemPackage; | ||
23 | import tools.refinery.language.model.problem.ReferenceDeclaration; | ||
24 | import tools.refinery.language.model.problem.Variable; | ||
25 | import tools.refinery.language.model.problem.VariableOrNodeArgument; | ||
26 | |||
27 | /** | ||
28 | * This class contains custom scoping description. | ||
29 | * | ||
30 | * See | ||
31 | * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#scoping | ||
32 | * on how and when to use it. | ||
33 | */ | ||
34 | public class ProblemScopeProvider extends AbstractProblemScopeProvider { | ||
35 | |||
36 | @Override | ||
37 | public IScope getScope(EObject context, EReference reference) { | ||
38 | var scope = super.getScope(context, reference); | ||
39 | if (reference == ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE | ||
40 | || reference == ProblemPackage.Literals.NODE_VALUE_ASSERTION__NODE) { | ||
41 | return getNodesScope(context, scope); | ||
42 | } | ||
43 | if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE | ||
44 | || reference == ProblemPackage.Literals.DELETE_ACTION_LITERAL__VARIABLE_OR_NODE) { | ||
45 | return getVariableScope(context, scope); | ||
46 | } | ||
47 | if (reference == ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE) { | ||
48 | return getOppositeScope(context, scope); | ||
49 | } | ||
50 | return scope; | ||
51 | } | ||
52 | |||
53 | protected IScope getNodesScope(EObject context, IScope delegateScope) { | ||
54 | var problem = EcoreUtil2.getContainerOfType(context, Problem.class); | ||
55 | if (problem == null) { | ||
56 | return delegateScope; | ||
57 | } | ||
58 | return Scopes.scopeFor(problem.getNodes(), delegateScope); | ||
59 | } | ||
60 | |||
61 | protected IScope getVariableScope(EObject context, IScope delegateScope) { | ||
62 | List<Variable> variables = new ArrayList<>(); | ||
63 | EObject currentContext = context; | ||
64 | if (context instanceof VariableOrNodeArgument argument) { | ||
65 | Variable singletonVariable = argument.getSingletonVariable(); | ||
66 | if (singletonVariable != null) { | ||
67 | variables.add(singletonVariable); | ||
68 | } | ||
69 | } | ||
70 | while (currentContext != null && !(currentContext instanceof ParametricDefinition)) { | ||
71 | if (currentContext instanceof ExistentialQuantifier quantifier) { | ||
72 | variables.addAll(quantifier.getImplicitVariables()); | ||
73 | } else | ||
74 | if(currentContext instanceof Action action) { | ||
75 | for (var literal : action.getActionLiterals()) { | ||
76 | if(literal instanceof NewActionLiteral newActionLiteral && newActionLiteral.getVariable() != null) { | ||
77 | variables.add(newActionLiteral.getVariable()); | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | currentContext = currentContext.eContainer(); | ||
82 | } | ||
83 | IScope parentScope = getNodesScope(context, delegateScope); | ||
84 | if (currentContext != null) { | ||
85 | ParametricDefinition definition = (ParametricDefinition) currentContext; | ||
86 | parentScope = Scopes.scopeFor(definition.getParameters(),parentScope); | ||
87 | } | ||
88 | return Scopes.scopeFor(variables,parentScope); | ||
89 | } | ||
90 | |||
91 | protected IScope getOppositeScope(EObject context, IScope delegateScope) { | ||
92 | var referenceDeclaration = EcoreUtil2.getContainerOfType(context, ReferenceDeclaration.class); | ||
93 | if (referenceDeclaration == null) { | ||
94 | return delegateScope; | ||
95 | } | ||
96 | var relation = referenceDeclaration.getReferenceType(); | ||
97 | if (!(relation instanceof ClassDeclaration)) { | ||
98 | return delegateScope; | ||
99 | } | ||
100 | var classDeclaration = (ClassDeclaration) relation; | ||
101 | var referenceDeclarations = ProblemUtil.getAllReferenceDeclarations(classDeclaration); | ||
102 | return Scopes.scopeFor(referenceDeclarations, delegateScope); | ||
103 | } | ||
104 | } | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java new file mode 100644 index 00000000..975fdca2 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.validation; | ||
5 | |||
6 | import org.eclipse.xtext.EcoreUtil2; | ||
7 | import org.eclipse.xtext.validation.Check; | ||
8 | |||
9 | import com.google.inject.Inject; | ||
10 | |||
11 | import tools.refinery.language.model.ProblemUtil; | ||
12 | import tools.refinery.language.model.problem.Node; | ||
13 | import tools.refinery.language.model.problem.Problem; | ||
14 | import tools.refinery.language.model.problem.ProblemPackage; | ||
15 | import tools.refinery.language.model.problem.Variable; | ||
16 | import tools.refinery.language.model.problem.VariableOrNodeArgument; | ||
17 | import tools.refinery.language.resource.ReferenceCounter; | ||
18 | |||
19 | /** | ||
20 | * This class contains custom validation rules. | ||
21 | * | ||
22 | * See | ||
23 | * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation | ||
24 | */ | ||
25 | public class ProblemValidator extends AbstractProblemValidator { | ||
26 | private static final String ISSUE_PREFIX = "tools.refinery.language.validation.ProblemValidator."; | ||
27 | |||
28 | public static final String SINGLETON_VARIABLE_ISSUE = ISSUE_PREFIX + "SINGLETON_VARIABLE"; | ||
29 | |||
30 | public static final String NON_INDIVIDUAL_NODE_ISSUE = ISSUE_PREFIX + "NON_INDIVIDUAL_NODE"; | ||
31 | |||
32 | @Inject | ||
33 | private ReferenceCounter referenceCounter; | ||
34 | |||
35 | @Check | ||
36 | public void checkUniqueVariable(VariableOrNodeArgument argument) { | ||
37 | var variableOrNode = argument.getVariableOrNode(); | ||
38 | if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) | ||
39 | && !ProblemUtil.isSingletonVariable(variable)) { | ||
40 | var problem = EcoreUtil2.getContainerOfType(variable, Problem.class); | ||
41 | if (problem != null && referenceCounter.countReferences(problem, variable) <= 1) { | ||
42 | var name = variable.getName(); | ||
43 | var message = "Variable '%s' has only a single reference. Add another reference or mark is as a singleton variable: '_%s'" | ||
44 | .formatted(name, name); | ||
45 | warning(message, argument, ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE, | ||
46 | INSIGNIFICANT_INDEX, SINGLETON_VARIABLE_ISSUE); | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | @Check | ||
52 | public void checkNonUniqueNode(VariableOrNodeArgument argument) { | ||
53 | var variableOrNode = argument.getVariableOrNode(); | ||
54 | if (variableOrNode instanceof Node node && !ProblemUtil.isIndividualNode(node)) { | ||
55 | var name = node.getName(); | ||
56 | var message = "Only individual nodes can be referenced in predicates. Mark '%s' as individual with the declaration 'indiv %s.'" | ||
57 | .formatted(name, name); | ||
58 | error(message, argument, ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE, | ||
59 | INSIGNIFICANT_INDEX, NON_INDIVIDUAL_NODE_ISSUE); | ||
60 | } | ||
61 | } | ||
62 | } | ||