aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language/src/main/java/tools
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/language/src/main/java/tools')
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/Problem.xtext56
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java9
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/expressions/AbstractTermInterpreter.java131
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/expressions/BuiltinTermInterpreter.java54
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/expressions/CompositeTermInterpreter.java99
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/expressions/TermInterpreter.java29
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java44
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.java56
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java20
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java7
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java71
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapterProvider.java68
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java5
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/AggregatorName.java19
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/DataExprType.java19
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java16
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java18
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/InvalidType.java16
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/LiteralType.java16
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/MutableType.java32
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/NodeType.java16
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/ProblemTypeAnalyzer.java32
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/Signature.java11
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/SignatureProvider.java107
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java568
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java57
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java98
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java15
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java96
30 files changed, 1508 insertions, 282 deletions
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext
index 43351d3e..08f7a585 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext
+++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext
@@ -17,7 +17,8 @@ enum ModuleKind:
17 17
18Statement: 18Statement:
19 ImportStatement | Assertion | ClassDeclaration | EnumDeclaration | 19 ImportStatement | Assertion | ClassDeclaration | EnumDeclaration |
20 DatatypeDeclaration | PredicateDefinition | /* FunctionDefinition | RuleDefinition | */ 20 DatatypeDeclaration | AggregatorDeclaration | PredicateDefinition |
21 /* FunctionDefinition | RuleDefinition | */
21 ScopeDeclaration | NodeDeclaration; 22 ScopeDeclaration | NodeDeclaration;
22 23
23ImportStatement: 24ImportStatement:
@@ -40,6 +41,9 @@ EnumLiteral returns Node:
40DatatypeDeclaration: 41DatatypeDeclaration:
41 "extern" "datatype" name=Identifier "."; 42 "extern" "datatype" name=Identifier ".";
42 43
44AggregatorDeclaration:
45 "extern" "aggregator" name=Identifier ".";
46
43enum ReferenceKind: 47enum ReferenceKind:
44 REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container"; 48 REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container";
45 49
@@ -71,7 +75,6 @@ Conjunction:
71// 75//
72//Case: 76//Case:
73// Conjunction ({Match.condition=current} "->" value=Expr)?; 77// Conjunction ({Match.condition=current} "->" value=Expr)?;
74
75//RuleDefinition: 78//RuleDefinition:
76// "rule" 79// "rule"
77// name=Identifier 80// name=Identifier
@@ -106,11 +109,18 @@ Expr:
106 AssignmentExpr; 109 AssignmentExpr;
107 110
108AssignmentExpr returns Expr: 111AssignmentExpr returns Expr:
109 ComparisonExpr ({AssignmentExpr.left=current}"is" right=ComparisonExpr)*; 112 BooleanExpr ({AssignmentExpr.left=current} "is" right=BooleanExpr)*;
113
114enum BooleanBinaryOp returns BinaryOp:
115 AND="&&" | OR="||" | XOR="^^";
116
117BooleanExpr returns Expr:
118 ComparisonExpr ({ArithmeticBinaryExpr.left=current}
119 op=BooleanBinaryOp right=ComparisonExpr)*;
110 120
111enum ComparisonOp: 121enum ComparisonOp:
112 LESS="<" | LESS_EQ="<=" | GREATER=">" | GREATER_EQ=">=" | EQ="==" | NOT_EQ="!=" | 122 LESS="<" | LESS_EQ="<=" | GREATER=">" | GREATER_EQ=">=" | EQ="===" | NOT_EQ="!==" |
113 IN="in" | SUBSUMES=":>" | SUBSUMED_BY="<:" | ABS_EQ="===" | ABS_NOT_EQ="!=="; 123 IN="in" | NODE_EQ="==" | NODE_NOT_EQ="!=";
114 124
115ComparisonExpr returns Expr: 125ComparisonExpr returns Expr:
116 LatticeExpr ({ComparisonExpr.left=current} 126 LatticeExpr ({ComparisonExpr.left=current}
@@ -120,12 +130,8 @@ enum LatticeBinaryOp:
120 MEET="/\\" | JOIN="\\/"; 130 MEET="/\\" | JOIN="\\/";
121 131
122LatticeExpr returns Expr: 132LatticeExpr returns Expr:
123 RangeExpr ({LatticeBinaryExpr.left=current} 133 AdditiveExpr ({LatticeBinaryExpr.left=current}
124 op=LatticeBinaryOp right=RangeExpr)*; 134 op=LatticeBinaryOp right=AdditiveExpr)*;
125
126RangeExpr returns Expr:
127 AdditiveExpr ({RangeExpr.left=current} ".." ("*" | right=AdditiveExpr))* |
128 {RangeExpr} "*" ".." ("*" | right=AdditiveExpr);
129 135
130enum AdditiveOp returns BinaryOp: 136enum AdditiveOp returns BinaryOp:
131 ADD="+" | SUB="-"; 137 ADD="+" | SUB="-";
@@ -145,11 +151,14 @@ enum ExponentialOp returns BinaryOp:
145 POW="**"; 151 POW="**";
146 152
147ExponentialExpr returns Expr: 153ExponentialExpr returns Expr:
148 UnaryExpr ({ArithmeticBinaryExpr.left=current} 154 RangeExpr ({ArithmeticBinaryExpr.left=current}
149 op=ExponentialOp right=ExponentialExpr)?; 155 op=ExponentialOp right=ExponentialExpr)?;
150 156
157RangeExpr returns Expr:
158 UnaryExpr ({RangeExpr.left=current} ".." right=UnaryExpr)*;
159
151UnaryExpr returns Expr: 160UnaryExpr returns Expr:
152 ArithmeticUnaryExpr | ModalExpr | NegationExpr | 161 ArithmeticUnaryExpr | NegationExpr |
153 CountExpr | AggregationExpr | CastExpr; 162 CountExpr | AggregationExpr | CastExpr;
154 163
155enum UnaryOp: 164enum UnaryOp:
@@ -158,23 +167,15 @@ enum UnaryOp:
158ArithmeticUnaryExpr: 167ArithmeticUnaryExpr:
159 op=UnaryOp body=UnaryExpr; 168 op=UnaryOp body=UnaryExpr;
160 169
161enum Modality:
162 MAY="may" | MUST="must" | CURRENT="current";
163
164ModalExpr:
165 modality=Modality body=UnaryExpr;
166
167NegationExpr: 170NegationExpr:
168 "!" body=UnaryExpr; 171 "!" body=UnaryExpr;
169 172
170CountExpr: 173CountExpr:
171 "count" body=UnaryExpr; 174 "count" body=UnaryExpr;
172 175
173enum AggregationOp:
174 SUM="sum" | PROD="prod" | MIN="min" | MAX="max";
175
176AggregationExpr: 176AggregationExpr:
177 op=AggregationOp "{" value=Expr "|" condition=ComparisonExpr "}"; 177 aggregator=[AggregatorDeclaration|QualifiedName]
178 "{" value=Expr "|" condition=ComparisonExpr "}";
178 179
179CastExpr returns Expr: 180CastExpr returns Expr:
180 CastExprBody ({CastExpr.body=current} "as" targetType=[Relation|QualifiedName])?; 181 CastExprBody ({CastExpr.body=current} "as" targetType=[Relation|QualifiedName])?;
@@ -191,7 +192,7 @@ VariableOrNodeExpr:
191 variableOrNode=[VariableOrNode|QualifiedName]; 192 variableOrNode=[VariableOrNode|QualifiedName];
192 193
193Constant: 194Constant:
194 RealConstant | IntConstant | StringConstant | LogicConstant; 195 RealConstant | IntConstant | StringConstant | InfiniteConstant | LogicConstant;
195 196
196IntConstant: 197IntConstant:
197 intValue=INT; 198 intValue=INT;
@@ -202,6 +203,9 @@ RealConstant:
202StringConstant: 203StringConstant:
203 stringValue=STRING; 204 stringValue=STRING;
204 205
206InfiniteConstant:
207 {InfiniteConstant} "*";
208
205enum LogicValue: 209enum LogicValue:
206 TRUE="true" | FALSE="false" | UNKNOWN="unknown" | ERROR="error"; 210 TRUE="true" | FALSE="false" | UNKNOWN="unknown" | ERROR="error";
207 211
@@ -276,8 +280,8 @@ Identifier:
276 NonContainmentIdentifier | "contains" | "container"; 280 NonContainmentIdentifier | "contains" | "container";
277 281
278NonContainmentIdentifier: 282NonContainmentIdentifier:
279 ID | "atom" | "multi" | "contained" | 283 ID | "atom" | "multi" | "contained" | "problem" | "module" |
280 "sum" | "prod" | "min" | "max" | "problem" | "module"; 284 "datatype" | "aggregator";
281 285
282Real returns ecore::EDouble: 286Real returns ecore::EDouble:
283 EXPONENTIAL | INT "." (INT | EXPONENTIAL); 287 EXPONENTIAL | INT "." (INT | EXPONENTIAL);
diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
index f7039027..955a89f9 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java
@@ -30,9 +30,8 @@ import org.eclipse.xtext.validation.IResourceValidator;
30import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator; 30import org.eclipse.xtext.xbase.annotations.validation.DerivedStateAwareResourceValidator;
31import tools.refinery.language.conversion.ProblemValueConverterService; 31import tools.refinery.language.conversion.ProblemValueConverterService;
32import tools.refinery.language.linking.ProblemLinkingService; 32import tools.refinery.language.linking.ProblemLinkingService;
33import tools.refinery.language.naming.ProblemDelegateQualifiedNameProvider;
34import tools.refinery.language.naming.ProblemQualifiedNameConverter;
35import tools.refinery.language.naming.ProblemQualifiedNameProvider; 33import tools.refinery.language.naming.ProblemQualifiedNameProvider;
34import tools.refinery.language.naming.ProblemQualifiedNameConverter;
36import tools.refinery.language.parser.ProblemEcoreElementFactory; 35import tools.refinery.language.parser.ProblemEcoreElementFactory;
37import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser; 36import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser;
38import tools.refinery.language.resource.ProblemLocationInFileProvider; 37import tools.refinery.language.resource.ProblemLocationInFileProvider;
@@ -68,12 +67,6 @@ public class ProblemRuntimeModule extends AbstractProblemRuntimeModule {
68 return ProblemQualifiedNameConverter.class; 67 return ProblemQualifiedNameConverter.class;
69 } 68 }
70 69
71 public void configureIQualifiedNameProviderDelegate(Binder binder) {
72 binder.bind(IQualifiedNameProvider.class)
73 .annotatedWith(Names.named(ProblemQualifiedNameProvider.NAMED_DELEGATE))
74 .to(ProblemDelegateQualifiedNameProvider.class);
75 }
76
77 @Override 70 @Override
78 public Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() { 71 public Class<? extends IQualifiedNameProvider> bindIQualifiedNameProvider() {
79 return ProblemQualifiedNameProvider.class; 72 return ProblemQualifiedNameProvider.class;
diff --git a/subprojects/language/src/main/java/tools/refinery/language/expressions/AbstractTermInterpreter.java b/subprojects/language/src/main/java/tools/refinery/language/expressions/AbstractTermInterpreter.java
new file mode 100644
index 00000000..2530b707
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/expressions/AbstractTermInterpreter.java
@@ -0,0 +1,131 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.expressions;
7
8import tools.refinery.language.model.problem.BinaryOp;
9import tools.refinery.language.model.problem.UnaryOp;
10import tools.refinery.language.typesystem.AggregatorName;
11import tools.refinery.language.typesystem.DataExprType;
12
13import java.util.*;
14
15// This class is used to configure term interpreters by clients with various arguments.
16@SuppressWarnings("SameParameterValue")
17public abstract class AbstractTermInterpreter implements TermInterpreter {
18 private final Map<DataExprType, DataExprType> negations = new HashMap<>();
19 private final Map<UnaryKey, DataExprType> unaryOperators = new HashMap<>();
20 private final Set<DataExprType> comparisons = new HashSet<>();
21 private final Set<DataExprType> ranges = new HashSet<>();
22 private final Map<BinaryKey, DataExprType> binaryOperators = new HashMap<>();
23 private final Set<CastKey> casts = new HashSet<>();
24 private final Map<AggregatorKey, DataExprType> aggregators = new HashMap<>();
25
26 protected AbstractTermInterpreter() {
27 }
28
29 protected void addNegation(DataExprType type, DataExprType result) {
30 negations.put(type, result);
31 }
32
33 protected void addNegation(DataExprType type) {
34 addNegation(type, type);
35 }
36
37 protected void addUnaryOperator(UnaryOp op, DataExprType type, DataExprType result) {
38 unaryOperators.put(new UnaryKey(op, type), result);
39 }
40
41 protected void addUnaryOperator(UnaryOp op, DataExprType type) {
42 addUnaryOperator(op, type, type);
43 }
44
45 protected void addComparison(DataExprType type) {
46 comparisons.add(type);
47 }
48
49 protected void addRange(DataExprType type) {
50 ranges.add(type);
51 }
52
53 protected void addBinaryOperator(BinaryOp op, DataExprType leftType, DataExprType rightType, DataExprType result) {
54 binaryOperators.put(new BinaryKey(op, leftType, rightType), result);
55 }
56
57 protected void addBinaryOperator(BinaryOp op, DataExprType type) {
58 addBinaryOperator(op, type, type, type);
59 }
60
61 protected void addCast(DataExprType fromType, DataExprType toType) {
62 if (fromType.equals(toType)) {
63 throw new IllegalArgumentException("The fromType and toType of a cast operator must be different");
64 }
65 casts.add(new CastKey(fromType, toType));
66 }
67
68 protected void addAggregator(AggregatorName aggregator, DataExprType type, DataExprType result) {
69 aggregators.put(new AggregatorKey(aggregator, type), result);
70 }
71
72 protected void addAggregator(AggregatorName aggregator, DataExprType type) {
73 addAggregator(aggregator, type, type);
74 }
75
76 @Override
77 public Optional<DataExprType> getNegationType(DataExprType type) {
78 return Optional.ofNullable(negations.get(type));
79 }
80
81 @Override
82 public Optional<DataExprType> getUnaryOperationType(UnaryOp op, DataExprType type) {
83 if (unaryOperators.isEmpty()) {
84 return Optional.empty();
85 }
86 return Optional.ofNullable(unaryOperators.get(new UnaryKey(op, type)));
87 }
88
89 @Override
90 public boolean isComparisonSupported(DataExprType type) {
91 return comparisons.contains(type);
92 }
93
94 @Override
95 public boolean isRangeSupported(DataExprType type) {
96 return ranges.contains(type);
97 }
98
99 @Override
100 public Optional<DataExprType> getBinaryOperationType(BinaryOp op, DataExprType leftType, DataExprType rightType) {
101 if (binaryOperators.isEmpty()) {
102 return Optional.empty();
103 }
104 return Optional.ofNullable(binaryOperators.get(new BinaryKey(op, leftType, rightType)));
105 }
106
107 @Override
108 public Optional<DataExprType> getAggregationType(AggregatorName aggregator, DataExprType type) {
109 if (aggregators.isEmpty()) {
110 return Optional.empty();
111 }
112 return Optional.ofNullable(aggregators.get(new AggregatorKey(aggregator, type)));
113 }
114
115 @Override
116 public boolean isCastSupported(DataExprType fromType, DataExprType toType) {
117 return casts.contains(new CastKey(fromType, toType));
118 }
119
120 private record UnaryKey(UnaryOp op, DataExprType type) {
121 }
122
123 private record BinaryKey(BinaryOp op, DataExprType leftType, DataExprType rightType) {
124 }
125
126 private record CastKey(DataExprType fromType, DataExprType toType) {
127 }
128
129 private record AggregatorKey(AggregatorName aggregator, DataExprType type) {
130 }
131}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/expressions/BuiltinTermInterpreter.java b/subprojects/language/src/main/java/tools/refinery/language/expressions/BuiltinTermInterpreter.java
new file mode 100644
index 00000000..412ed8ba
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/expressions/BuiltinTermInterpreter.java
@@ -0,0 +1,54 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.expressions;
7
8import tools.refinery.language.library.BuiltinLibrary;
9import tools.refinery.language.model.problem.BinaryOp;
10import tools.refinery.language.model.problem.UnaryOp;
11import tools.refinery.language.typesystem.AggregatorName;
12import tools.refinery.language.typesystem.DataExprType;
13
14public final class BuiltinTermInterpreter extends AbstractTermInterpreter {
15 public static final DataExprType BOOLEAN_TYPE = new DataExprType(BuiltinLibrary.BUILTIN_LIBRARY_NAME, "boolean");
16 public static final DataExprType INT_TYPE = new DataExprType(BuiltinLibrary.BUILTIN_LIBRARY_NAME, "int");
17 public static final DataExprType REAL_TYPE = new DataExprType(BuiltinLibrary.BUILTIN_LIBRARY_NAME, "real");
18 public static final DataExprType STRING_TYPE = new DataExprType(BuiltinLibrary.BUILTIN_LIBRARY_NAME, "string");
19 public static final AggregatorName MIN_AGGREGATOR = new AggregatorName(BuiltinLibrary.BUILTIN_LIBRARY_NAME, "min");
20 public static final AggregatorName MAX_AGGREGATOR = new AggregatorName(BuiltinLibrary.BUILTIN_LIBRARY_NAME, "max");
21 public static final AggregatorName SUM_AGGREGATOR = new AggregatorName(BuiltinLibrary.BUILTIN_LIBRARY_NAME, "sum");
22
23 public BuiltinTermInterpreter() {
24 addNegation(BOOLEAN_TYPE);
25 addBinaryOperator(BinaryOp.AND, BOOLEAN_TYPE);
26 addBinaryOperator(BinaryOp.OR, BOOLEAN_TYPE);
27 addBinaryOperator(BinaryOp.XOR, BOOLEAN_TYPE);
28
29 addUnaryOperator(UnaryOp.PLUS, INT_TYPE);
30 addUnaryOperator(UnaryOp.MINUS, INT_TYPE);
31 addComparison(INT_TYPE);
32 addRange(INT_TYPE);
33 addBinaryOperator(BinaryOp.ADD, INT_TYPE);
34 addBinaryOperator(BinaryOp.SUB, INT_TYPE);
35 addBinaryOperator(BinaryOp.MUL, INT_TYPE);
36 addAggregator(MIN_AGGREGATOR, INT_TYPE);
37 addAggregator(MAX_AGGREGATOR, INT_TYPE);
38 addAggregator(SUM_AGGREGATOR, INT_TYPE);
39
40 addUnaryOperator(UnaryOp.PLUS, REAL_TYPE);
41 addUnaryOperator(UnaryOp.MINUS, REAL_TYPE);
42 addCast(INT_TYPE, REAL_TYPE);
43 addComparison(REAL_TYPE);
44 addRange(REAL_TYPE);
45 addBinaryOperator(BinaryOp.ADD, REAL_TYPE);
46 addBinaryOperator(BinaryOp.SUB, REAL_TYPE);
47 addBinaryOperator(BinaryOp.MUL, REAL_TYPE);
48 addBinaryOperator(BinaryOp.DIV, REAL_TYPE);
49 addBinaryOperator(BinaryOp.POW, REAL_TYPE);
50 addAggregator(MIN_AGGREGATOR, REAL_TYPE);
51 addAggregator(MAX_AGGREGATOR, REAL_TYPE);
52 addAggregator(SUM_AGGREGATOR, REAL_TYPE);
53 }
54}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/expressions/CompositeTermInterpreter.java b/subprojects/language/src/main/java/tools/refinery/language/expressions/CompositeTermInterpreter.java
new file mode 100644
index 00000000..b337e5dd
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/expressions/CompositeTermInterpreter.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.expressions;
7
8import tools.refinery.language.model.problem.BinaryOp;
9import tools.refinery.language.model.problem.UnaryOp;
10import tools.refinery.language.typesystem.AggregatorName;
11import tools.refinery.language.typesystem.DataExprType;
12
13import java.util.List;
14import java.util.Optional;
15
16public class CompositeTermInterpreter implements TermInterpreter {
17 private final List<TermInterpreter> interpreters;
18
19 public CompositeTermInterpreter(List<TermInterpreter> interpreters) {
20 this.interpreters = interpreters;
21 }
22
23 @Override
24 public Optional<DataExprType> getNegationType(DataExprType type) {
25 for (var interpreter : interpreters) {
26 var result = interpreter.getNegationType(type);
27 if (result.isPresent()) {
28 return result;
29 }
30 }
31 return Optional.empty();
32 }
33
34 @Override
35 public Optional<DataExprType> getUnaryOperationType(UnaryOp op, DataExprType type) {
36 for (var interpreter : interpreters) {
37 var result = interpreter.getUnaryOperationType(op, type);
38 if (result.isPresent()) {
39 return result;
40 }
41 }
42 return Optional.empty();
43 }
44
45 @Override
46 public boolean isComparisonSupported(DataExprType type) {
47 for (var interpreter : interpreters) {
48 var result = interpreter.isComparisonSupported(type);
49 if (result) {
50 return true;
51 }
52 }
53 return false;
54 }
55
56 @Override
57 public boolean isRangeSupported(DataExprType type) {
58 for (var interpreter : interpreters) {
59 var result = interpreter.isRangeSupported(type);
60 if (result) {
61 return true;
62 }
63 }
64 return false;
65 }
66
67 @Override
68 public Optional<DataExprType> getBinaryOperationType(BinaryOp op, DataExprType leftType, DataExprType rightType) {
69 for (var interpreter : interpreters) {
70 var result = interpreter.getBinaryOperationType(op, leftType, rightType);
71 if (result.isPresent()) {
72 return result;
73 }
74 }
75 return Optional.empty();
76 }
77
78 @Override
79 public boolean isCastSupported(DataExprType fromType, DataExprType toType) {
80 for (var interpreter : interpreters) {
81 var result = interpreter.isCastSupported(fromType, toType);
82 if (result) {
83 return true;
84 }
85 }
86 return false;
87 }
88
89 @Override
90 public Optional<DataExprType> getAggregationType(AggregatorName aggregator, DataExprType type) {
91 for (var interpreter : interpreters) {
92 var result = interpreter.getAggregationType(aggregator, type);
93 if (result.isPresent()) {
94 return result;
95 }
96 }
97 return Optional.empty();
98 }
99}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/expressions/TermInterpreter.java b/subprojects/language/src/main/java/tools/refinery/language/expressions/TermInterpreter.java
new file mode 100644
index 00000000..122785f2
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/expressions/TermInterpreter.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.expressions;
7
8import tools.refinery.language.model.problem.BinaryOp;
9import tools.refinery.language.model.problem.UnaryOp;
10import tools.refinery.language.typesystem.AggregatorName;
11import tools.refinery.language.typesystem.DataExprType;
12
13import java.util.Optional;
14
15public interface TermInterpreter {
16 Optional<DataExprType> getNegationType(DataExprType type);
17
18 Optional<DataExprType> getUnaryOperationType(UnaryOp op, DataExprType type);
19
20 boolean isComparisonSupported(DataExprType type);
21
22 boolean isRangeSupported(DataExprType type);
23
24 Optional<DataExprType> getBinaryOperationType(BinaryOp op, DataExprType leftType, DataExprType rightType);
25
26 boolean isCastSupported(DataExprType fromType, DataExprType toType);
27
28 Optional<DataExprType> getAggregationType(AggregatorName aggregator, DataExprType type);
29}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java
deleted file mode 100644
index b3931401..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemDelegateQualifiedNameProvider.java
+++ /dev/null
@@ -1,44 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.naming;
7
8import com.google.inject.Inject;
9import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider;
10import org.eclipse.xtext.naming.IQualifiedNameConverter;
11import org.eclipse.xtext.naming.QualifiedName;
12import tools.refinery.language.model.problem.Problem;
13import tools.refinery.language.scoping.imports.ImportAdapter;
14import tools.refinery.language.utils.ProblemUtil;
15
16public class ProblemDelegateQualifiedNameProvider extends DefaultDeclarativeQualifiedNameProvider {
17 @Inject
18 private IQualifiedNameConverter qualifiedNameConverter;
19
20 protected QualifiedName qualifiedName(Problem problem) {
21 var qualifiedNameString = problem.getName();
22 if (qualifiedNameString != null) {
23 return NamingUtil.stripRootPrefix(qualifiedNameConverter.toQualifiedName(qualifiedNameString));
24 }
25 if (!ProblemUtil.isModule(problem)) {
26 return null;
27 }
28 var resource = problem.eResource();
29 if (resource == null) {
30 return null;
31 }
32 var resourceUri = resource.getURI();
33 if (resourceUri == null) {
34 return null;
35 }
36 var resourceSet = resource.getResourceSet();
37 if (resourceSet == null) {
38 return null;
39 }
40 var adapter = ImportAdapter.getOrInstall(resourceSet);
41 // If a module has no explicitly specified name, return the qualified name it was resolved under.
42 return adapter.getQualifiedName(resourceUri);
43 }
44}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.java b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.java
index 5b682058..2a4df4d0 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/naming/ProblemQualifiedNameProvider.java
@@ -6,36 +6,44 @@
6package tools.refinery.language.naming; 6package tools.refinery.language.naming;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.name.Named; 9import com.google.inject.Singleton;
10import org.eclipse.emf.ecore.EObject; 10import org.eclipse.xtext.naming.DefaultDeclarativeQualifiedNameProvider;
11import org.eclipse.xtext.naming.IQualifiedNameProvider; 11import org.eclipse.xtext.naming.IQualifiedNameConverter;
12import org.eclipse.xtext.naming.QualifiedName; 12import org.eclipse.xtext.naming.QualifiedName;
13import org.eclipse.xtext.util.IResourceScopeCache; 13import tools.refinery.language.model.problem.Problem;
14import org.eclipse.xtext.util.Tuples; 14import tools.refinery.language.scoping.imports.ImportAdapterProvider;
15import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; 15import tools.refinery.language.utils.ProblemUtil;
16
17public class ProblemQualifiedNameProvider extends IQualifiedNameProvider.AbstractImpl {
18 private static final String PREFIX = "tools.refinery.language.naming.ProblemQualifiedNameProvider.";
19 public static final String NAMED_DELEGATE = PREFIX + "NAMED_DELEGATE";
20 public static final String CACHE_KEY = PREFIX + "CACHE_KEY";
21 16
17@Singleton
18public class ProblemQualifiedNameProvider extends DefaultDeclarativeQualifiedNameProvider {
22 @Inject 19 @Inject
23 @Named(NAMED_DELEGATE) 20 private IQualifiedNameConverter qualifiedNameConverter;
24 private IQualifiedNameProvider delegate;
25 21
26 @Inject 22 @Inject
27 private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; 23 private ImportAdapterProvider importAdapterProvider;
28
29 @Override
30 public QualifiedName getFullyQualifiedName(EObject obj) {
31 return cache.get(Tuples.pair(obj, CACHE_KEY), obj.eResource(), () -> computeFullyQualifiedName(obj));
32 }
33 24
34 public QualifiedName computeFullyQualifiedName(EObject obj) { 25 protected QualifiedName qualifiedName(Problem problem) {
35 var qualifiedName = delegate.getFullyQualifiedName(obj); 26 var qualifiedNameString = problem.getName();
36 if (qualifiedName != null && ProblemResourceDescriptionStrategy.shouldExport(obj)) { 27 if (qualifiedNameString != null) {
37 return NamingUtil.addRootPrefix(qualifiedName); 28 return NamingUtil.stripRootPrefix(qualifiedNameConverter.toQualifiedName(qualifiedNameString));
29 }
30 if (!ProblemUtil.isModule(problem)) {
31 return null;
32 }
33 var resource = problem.eResource();
34 if (resource == null) {
35 return null;
36 }
37 var resourceUri = resource.getURI();
38 if (resourceUri == null) {
39 return null;
40 }
41 var resourceSet = resource.getResourceSet();
42 if (resourceSet == null) {
43 return null;
38 } 44 }
39 return qualifiedName; 45 var adapter = importAdapterProvider.getOrInstall(resourceSet);
46 // If a module has no explicitly specified name, return the qualified name it was resolved under.
47 return adapter.getQualifiedName(resourceUri);
40 } 48 }
41} 49}
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
index 3dcf6b1f..8fd60364 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
@@ -8,7 +8,6 @@ package tools.refinery.language.resource;
8import com.google.common.collect.ImmutableMap; 8import com.google.common.collect.ImmutableMap;
9import com.google.inject.Inject; 9import com.google.inject.Inject;
10import com.google.inject.Singleton; 10import com.google.inject.Singleton;
11import com.google.inject.name.Named;
12import org.eclipse.emf.ecore.EObject; 11import org.eclipse.emf.ecore.EObject;
13import org.eclipse.xtext.EcoreUtil2; 12import org.eclipse.xtext.EcoreUtil2;
14import org.eclipse.xtext.naming.IQualifiedNameConverter; 13import org.eclipse.xtext.naming.IQualifiedNameConverter;
@@ -19,10 +18,9 @@ import org.eclipse.xtext.resource.IEObjectDescription;
19import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; 18import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy;
20import org.eclipse.xtext.util.IAcceptor; 19import org.eclipse.xtext.util.IAcceptor;
21import tools.refinery.language.documentation.DocumentationCommentParser; 20import tools.refinery.language.documentation.DocumentationCommentParser;
22import tools.refinery.language.naming.ProblemQualifiedNameProvider;
23import tools.refinery.language.scoping.imports.ImportCollector;
24import tools.refinery.language.model.problem.*; 21import tools.refinery.language.model.problem.*;
25import tools.refinery.language.naming.NamingUtil; 22import tools.refinery.language.naming.NamingUtil;
23import tools.refinery.language.scoping.imports.ImportCollector;
26import tools.refinery.language.utils.ProblemUtil; 24import tools.refinery.language.utils.ProblemUtil;
27 25
28import java.util.Map; 26import java.util.Map;
@@ -32,13 +30,15 @@ import java.util.stream.Collectors;
32public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { 30public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy {
33 private static final String DATA_PREFIX = "tools.refinery.language.resource.ProblemResourceDescriptionStrategy."; 31 private static final String DATA_PREFIX = "tools.refinery.language.resource.ProblemResourceDescriptionStrategy.";
34 32
35 public static final String ARITY = DATA_PREFIX + "ARITY"; 33 public static final String TYPE_LIKE = DATA_PREFIX + "ARITY";
34 public static final String TYPE_LIKE_TRUE = "true";
36 public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE"; 35 public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE";
37 public static final String ERROR_PREDICATE_TRUE = "true"; 36 public static final String ERROR_PREDICATE_TRUE = "true";
38 public static final String SHADOWING_KEY = DATA_PREFIX + "SHADOWING_KEY"; 37 public static final String SHADOWING_KEY = DATA_PREFIX + "SHADOWING_KEY";
39 public static final String SHADOWING_KEY_PROBLEM = "problem"; 38 public static final String SHADOWING_KEY_PROBLEM = "problem";
40 public static final String SHADOWING_KEY_NODE = "node"; 39 public static final String SHADOWING_KEY_NODE = "node";
41 public static final String SHADOWING_KEY_RELATION = "relation"; 40 public static final String SHADOWING_KEY_RELATION = "relation";
41 public static final String SHADOWING_KEY_AGGREGATOR = "aggregator";
42 public static final String PREFERRED_NAME = DATA_PREFIX + "PREFERRED_NAME"; 42 public static final String PREFERRED_NAME = DATA_PREFIX + "PREFERRED_NAME";
43 public static final String PREFERRED_NAME_TRUE = "true"; 43 public static final String PREFERRED_NAME_TRUE = "true";
44 public static final String IMPORTS = DATA_PREFIX + "IMPORTS"; 44 public static final String IMPORTS = DATA_PREFIX + "IMPORTS";
@@ -51,8 +51,7 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
51 private IQualifiedNameConverter qualifiedNameConverter; 51 private IQualifiedNameConverter qualifiedNameConverter;
52 52
53 @Inject 53 @Inject
54 @Named(ProblemQualifiedNameProvider.NAMED_DELEGATE) 54 private IQualifiedNameProvider qualifiedNameProvider;
55 private IQualifiedNameProvider delegateQualifiedNameProvider;
56 55
57 @Inject 56 @Inject
58 private ImportCollector importCollector; 57 private ImportCollector importCollector;
@@ -123,7 +122,7 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
123 if (problem == null) { 122 if (problem == null) {
124 return QualifiedName.EMPTY; 123 return QualifiedName.EMPTY;
125 } 124 }
126 var qualifiedName = delegateQualifiedNameProvider.getFullyQualifiedName(problem); 125 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(problem);
127 return qualifiedName == null ? QualifiedName.EMPTY : qualifiedName; 126 return qualifiedName == null ? QualifiedName.EMPTY : qualifiedName;
128 } 127 }
129 128
@@ -152,8 +151,11 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
152 builder.put(SHADOWING_KEY, SHADOWING_KEY_NODE); 151 builder.put(SHADOWING_KEY, SHADOWING_KEY_NODE);
153 } else if (eObject instanceof Relation relation) { 152 } else if (eObject instanceof Relation relation) {
154 builder.put(SHADOWING_KEY, SHADOWING_KEY_RELATION); 153 builder.put(SHADOWING_KEY, SHADOWING_KEY_RELATION);
155 int arity = ProblemUtil.getArity(relation); 154 if (ProblemUtil.isTypeLike(relation)) {
156 builder.put(ARITY, Integer.toString(arity)); 155 builder.put(TYPE_LIKE, TYPE_LIKE_TRUE);
156 }
157 } else if (eObject instanceof AggregatorDeclaration) {
158 builder.put(SHADOWING_KEY, SHADOWING_KEY_AGGREGATOR);
157 } 159 }
158 if (eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isError()) { 160 if (eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isError()) {
159 builder.put(ERROR_PREDICATE, ERROR_PREDICATE_TRUE); 161 builder.put(ERROR_PREDICATE, ERROR_PREDICATE_TRUE);
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
index 0067bf94..3b94423a 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemLocalScopeProvider.java
@@ -6,7 +6,6 @@
6package tools.refinery.language.scoping; 6package tools.refinery.language.scoping;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.name.Named;
10import org.eclipse.emf.ecore.EObject; 9import org.eclipse.emf.ecore.EObject;
11import org.eclipse.emf.ecore.EReference; 10import org.eclipse.emf.ecore.EReference;
12import org.eclipse.emf.ecore.resource.Resource; 11import org.eclipse.emf.ecore.resource.Resource;
@@ -17,14 +16,12 @@ import org.eclipse.xtext.resource.ISelectable;
17import org.eclipse.xtext.scoping.IScope; 16import org.eclipse.xtext.scoping.IScope;
18import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeDelegatingScopeProvider; 17import org.eclipse.xtext.scoping.impl.AbstractGlobalScopeDelegatingScopeProvider;
19import org.eclipse.xtext.util.IResourceScopeCache; 18import org.eclipse.xtext.util.IResourceScopeCache;
20import tools.refinery.language.naming.ProblemQualifiedNameProvider;
21 19
22public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScopeProvider { 20public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScopeProvider {
23 private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemLocalScopeProvider.CACHE_KEY"; 21 private static final String CACHE_KEY = "tools.refinery.language.scoping.ProblemLocalScopeProvider.CACHE_KEY";
24 22
25 @Inject 23 @Inject
26 @Named(ProblemQualifiedNameProvider.NAMED_DELEGATE) 24 private IQualifiedNameProvider qualifiedNameProvider;
27 private IQualifiedNameProvider delegateQualifiedNameProvider;
28 25
29 @Inject 26 @Inject
30 private IResourceDescriptionsProvider resourceDescriptionsProvider; 27 private IResourceDescriptionsProvider resourceDescriptionsProvider;
@@ -59,7 +56,7 @@ public class ProblemLocalScopeProvider extends AbstractGlobalScopeDelegatingScop
59 if (rootElement == null) { 56 if (rootElement == null) {
60 return new NoFullyQualifiedNamesSelectable(resourceDescription); 57 return new NoFullyQualifiedNamesSelectable(resourceDescription);
61 } 58 }
62 var rootName = delegateQualifiedNameProvider.getFullyQualifiedName(rootElement); 59 var rootName = qualifiedNameProvider.getFullyQualifiedName(rootElement);
63 if (rootName == null) { 60 if (rootName == null) {
64 return new NoFullyQualifiedNamesSelectable(resourceDescription); 61 return new NoFullyQualifiedNamesSelectable(resourceDescription);
65 } 62 }
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
index f83a7ebd..d94c9a13 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java
@@ -9,14 +9,12 @@
9 */ 9 */
10package tools.refinery.language.scoping; 10package tools.refinery.language.scoping;
11 11
12import com.google.inject.Inject;
13import org.eclipse.emf.ecore.EObject; 12import org.eclipse.emf.ecore.EObject;
14import org.eclipse.emf.ecore.EReference; 13import org.eclipse.emf.ecore.EReference;
15import org.eclipse.xtext.EcoreUtil2; 14import org.eclipse.xtext.EcoreUtil2;
16import org.eclipse.xtext.scoping.IScope; 15import org.eclipse.xtext.scoping.IScope;
17import org.eclipse.xtext.scoping.Scopes; 16import org.eclipse.xtext.scoping.Scopes;
18import tools.refinery.language.model.problem.*; 17import tools.refinery.language.model.problem.*;
19import tools.refinery.language.utils.ProblemDesugarer;
20 18
21import java.util.Collection; 19import java.util.Collection;
22import java.util.LinkedHashSet; 20import java.util.LinkedHashSet;
@@ -29,9 +27,6 @@ import java.util.LinkedHashSet;
29 * on how and when to use it. 27 * on how and when to use it.
30 */ 28 */
31public class ProblemScopeProvider extends AbstractProblemScopeProvider { 29public class ProblemScopeProvider extends AbstractProblemScopeProvider {
32 @Inject
33 private ProblemDesugarer desugarer;
34
35 @Override 30 @Override
36 public IScope getScope(EObject context, EReference reference) { 31 public IScope getScope(EObject context, EReference reference) {
37 var scope = super.getScope(context, reference); 32 var scope = super.getScope(context, reference);
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
index d7a5304f..753c8565 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapter.java
@@ -16,7 +16,12 @@ import org.eclipse.emf.ecore.resource.Resource;
16import org.eclipse.emf.ecore.resource.ResourceSet; 16import org.eclipse.emf.ecore.resource.ResourceSet;
17import org.eclipse.emf.ecore.util.EcoreUtil; 17import org.eclipse.emf.ecore.util.EcoreUtil;
18import org.eclipse.xtext.naming.QualifiedName; 18import org.eclipse.xtext.naming.QualifiedName;
19import tools.refinery.language.expressions.CompositeTermInterpreter;
20import tools.refinery.language.expressions.TermInterpreter;
21import tools.refinery.language.library.BuiltinLibrary;
19import tools.refinery.language.library.RefineryLibrary; 22import tools.refinery.language.library.RefineryLibrary;
23import tools.refinery.language.model.problem.Problem;
24import tools.refinery.language.utils.BuiltinSymbols;
20 25
21import java.io.File; 26import java.io.File;
22import java.nio.file.Path; 27import java.nio.file.Path;
@@ -25,15 +30,12 @@ import java.util.*;
25public class ImportAdapter extends AdapterImpl { 30public class ImportAdapter extends AdapterImpl {
26 private static final Logger LOG = Logger.getLogger(ImportAdapter.class); 31 private static final Logger LOG = Logger.getLogger(ImportAdapter.class);
27 private static final List<RefineryLibrary> DEFAULT_LIBRARIES; 32 private static final List<RefineryLibrary> DEFAULT_LIBRARIES;
33 private static final List<TermInterpreter> DEFAULT_TERM_INTERPRETERS;
28 private static final List<Path> DEFAULT_PATHS; 34 private static final List<Path> DEFAULT_PATHS;
29 35
30 static { 36 static {
31 var serviceLoader = ServiceLoader.load(RefineryLibrary.class); 37 DEFAULT_LIBRARIES = loadServices(RefineryLibrary.class);
32 var defaultLibraries = new ArrayList<RefineryLibrary>(); 38 DEFAULT_TERM_INTERPRETERS = loadServices(TermInterpreter.class);
33 for (var service : serviceLoader) {
34 defaultLibraries.add(service);
35 }
36 DEFAULT_LIBRARIES = List.copyOf(defaultLibraries);
37 var pathEnv = System.getenv("REFINERY_LIBRARY_PATH"); 39 var pathEnv = System.getenv("REFINERY_LIBRARY_PATH");
38 if (pathEnv == null) { 40 if (pathEnv == null) {
39 DEFAULT_PATHS = List.of(); 41 DEFAULT_PATHS = List.of();
@@ -45,16 +47,29 @@ public class ImportAdapter extends AdapterImpl {
45 } 47 }
46 } 48 }
47 49
48 private final List<RefineryLibrary> libraries; 50 private static <T> List<T> loadServices(Class<T> serviceClass) {
49 private final List<Path> libraryPaths; 51 var serviceLoader = ServiceLoader.load(serviceClass);
52 var services = new ArrayList<T>();
53 for (var service : serviceLoader) {
54 services.add(service);
55 }
56 return List.copyOf(services);
57 }
58
59 private ResourceSet resourceSet;
60 private final List<RefineryLibrary> libraries = new ArrayList<>(DEFAULT_LIBRARIES);
61 private final List<TermInterpreter> termInterpreters = new ArrayList<>(DEFAULT_TERM_INTERPRETERS);
62 private final TermInterpreter termInterpreter = new CompositeTermInterpreter(termInterpreters);
63 private final List<Path> libraryPaths = new ArrayList<>(DEFAULT_PATHS);
50 private final Cache<QualifiedName, QualifiedName> failedResolutions = 64 private final Cache<QualifiedName, QualifiedName> failedResolutions =
51 CacheBuilder.newBuilder().maximumSize(100).build(); 65 CacheBuilder.newBuilder().maximumSize(100).build();
52 private final Map<QualifiedName, URI> qualifiedNameToUriMap = new LinkedHashMap<>(); 66 private final Map<QualifiedName, URI> qualifiedNameToUriMap = new LinkedHashMap<>();
53 private final Map<URI, QualifiedName> uriToQualifiedNameMap = new LinkedHashMap<>(); 67 private final Map<URI, QualifiedName> uriToQualifiedNameMap = new LinkedHashMap<>();
68 private Problem builtinProblem;
69 private BuiltinSymbols builtinSymbols;
54 70
55 private ImportAdapter(ResourceSet resourceSet) { 71 void setResourceSet(ResourceSet resourceSet) {
56 libraries = new ArrayList<>(DEFAULT_LIBRARIES); 72 this.resourceSet = resourceSet;
57 libraryPaths = new ArrayList<>(DEFAULT_PATHS);
58 for (var resource : resourceSet.getResources()) { 73 for (var resource : resourceSet.getResources()) {
59 resourceAdded(resource); 74 resourceAdded(resource);
60 } 75 }
@@ -69,6 +84,14 @@ public class ImportAdapter extends AdapterImpl {
69 return libraries; 84 return libraries;
70 } 85 }
71 86
87 public List<TermInterpreter> getTermInterpreters() {
88 return termInterpreters;
89 }
90
91 public TermInterpreter getTermInterpreter() {
92 return termInterpreter;
93 }
94
72 public List<Path> getLibraryPaths() { 95 public List<Path> getLibraryPaths() {
73 return libraryPaths; 96 return libraryPaths;
74 } 97 }
@@ -190,16 +213,26 @@ public class ImportAdapter extends AdapterImpl {
190 } 213 }
191 } 214 }
192 215
193 public static ImportAdapter getOrInstall(ResourceSet resourceSet) { 216 public Problem getBuiltinProblem() {
194 var adapter = getAdapter(resourceSet); 217 if (builtinProblem == null) {
195 if (adapter == null) { 218 var builtinResource = resourceSet.getResource(BuiltinLibrary.BUILTIN_LIBRARY_URI, true);
196 adapter = new ImportAdapter(resourceSet); 219 if (builtinResource == null) {
197 resourceSet.eAdapters().add(adapter); 220 throw new IllegalStateException("Failed to load builtin resource");
221 }
222 var contents = builtinResource.getContents();
223 if (contents.isEmpty()) {
224 throw new IllegalStateException("builtin resource is empty");
225 }
226 builtinProblem = (Problem) contents.getFirst();
227 EcoreUtil.resolveAll(builtinResource);
198 } 228 }
199 return adapter; 229 return builtinProblem;
200 } 230 }
201 231
202 private static ImportAdapter getAdapter(ResourceSet resourceSet) { 232 public BuiltinSymbols getBuiltinSymbols() {
203 return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class); 233 if (builtinSymbols == null) {
234 builtinSymbols = new BuiltinSymbols(getBuiltinProblem());
235 }
236 return builtinSymbols;
204 } 237 }
205} 238}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapterProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapterProvider.java
new file mode 100644
index 00000000..5ab3a851
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportAdapterProvider.java
@@ -0,0 +1,68 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.scoping.imports;
7
8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import com.google.inject.Singleton;
11import org.eclipse.emf.ecore.EObject;
12import org.eclipse.emf.ecore.resource.Resource;
13import org.eclipse.emf.ecore.resource.ResourceSet;
14import org.eclipse.emf.ecore.util.EcoreUtil;
15import org.jetbrains.annotations.NotNull;
16import tools.refinery.language.expressions.TermInterpreter;
17import tools.refinery.language.utils.BuiltinSymbols;
18
19@Singleton
20public class ImportAdapterProvider {
21 @Inject
22 private Provider<ImportAdapter> delegateProvider;
23
24 public BuiltinSymbols getBuiltinSymbols(@NotNull EObject context) {
25 var adapter = getOrInstall(context);
26 return adapter.getBuiltinSymbols();
27 }
28
29 public BuiltinSymbols getBuiltinSymbols(@NotNull Resource context) {
30 var adapter = getOrInstall(context);
31 return adapter.getBuiltinSymbols();
32 }
33
34 public TermInterpreter getTermInterpreter(@NotNull EObject context) {
35 var adapter = getOrInstall(context);
36 return adapter.getTermInterpreter();
37 }
38
39 public ImportAdapter getOrInstall(@NotNull EObject context) {
40 var resource = context.eResource();
41 if (resource == null) {
42 throw new IllegalArgumentException("context is not in a resource");
43 }
44 return getOrInstall(resource);
45 }
46
47 public ImportAdapter getOrInstall(@NotNull Resource context) {
48 var resourceSet = context.getResourceSet();
49 if (resourceSet == null) {
50 throw new IllegalArgumentException("context is not in a resource set");
51 }
52 return getOrInstall(resourceSet);
53 }
54
55 public ImportAdapter getOrInstall(@NotNull ResourceSet resourceSet) {
56 var adapter = getAdapter(resourceSet);
57 if (adapter == null) {
58 adapter = delegateProvider.get();
59 adapter.setResourceSet(resourceSet);
60 resourceSet.eAdapters().add(adapter);
61 }
62 return adapter;
63 }
64
65 public static ImportAdapter getAdapter(@NotNull ResourceSet resourceSet) {
66 return (ImportAdapter) EcoreUtil.getAdapter(resourceSet.eAdapters(), ImportAdapter.class);
67 }
68}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java
index ac5a92ba..f3ab54ae 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/imports/ImportCollector.java
@@ -46,6 +46,9 @@ public class ImportCollector {
46 @Inject 46 @Inject
47 private Provider<LoadOnDemandResourceDescriptionProvider> loadOnDemandProvider; 47 private Provider<LoadOnDemandResourceDescriptionProvider> loadOnDemandProvider;
48 48
49 @Inject
50 private ImportAdapterProvider importAdapterProvider;
51
49 public ImportCollection getDirectImports(Resource resource) { 52 public ImportCollection getDirectImports(Resource resource) {
50 return cache.get(DIRECT_IMPORTS_KEY, resource, () -> this.computeDirectImports(resource)); 53 return cache.get(DIRECT_IMPORTS_KEY, resource, () -> this.computeDirectImports(resource));
51 } 54 }
@@ -58,7 +61,7 @@ public class ImportCollector {
58 if (resourceSet == null) { 61 if (resourceSet == null) {
59 return ImportCollection.EMPTY; 62 return ImportCollection.EMPTY;
60 } 63 }
61 var adapter = ImportAdapter.getOrInstall(resourceSet); 64 var adapter = importAdapterProvider.getOrInstall(resourceSet);
62 var collection = new ImportCollection(); 65 var collection = new ImportCollection();
63 collectAutomaticImports(collection, adapter); 66 collectAutomaticImports(collection, adapter);
64 collectExplicitImports(problem, collection, adapter); 67 collectExplicitImports(problem, collection, adapter);
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/AggregatorName.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/AggregatorName.java
new file mode 100644
index 00000000..5939865a
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/AggregatorName.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8import org.eclipse.xtext.naming.QualifiedName;
9
10public record AggregatorName(QualifiedName qualifiedName) {
11 public AggregatorName(QualifiedName prefix, String name) {
12 this(prefix.append(name));
13 }
14
15 @Override
16 public String toString() {
17 return qualifiedName.isEmpty() ? "" : qualifiedName.getLastSegment();
18 }
19}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/DataExprType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/DataExprType.java
new file mode 100644
index 00000000..9bc3e6aa
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/DataExprType.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8import org.eclipse.xtext.naming.QualifiedName;
9
10public record DataExprType(QualifiedName qualifiedName) implements FixedType {
11 public DataExprType(QualifiedName prefix, String name) {
12 this(prefix.append(name));
13 }
14
15 @Override
16 public String toString() {
17 return qualifiedName.isEmpty() ? "" : qualifiedName.getLastSegment();
18 }
19}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java
new file mode 100644
index 00000000..9e44063d
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ExprType.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8public sealed interface ExprType permits FixedType, MutableType {
9 NodeType NODE = new NodeType();
10 LiteralType LITERAL = new LiteralType();
11 InvalidType INVALID = new InvalidType();
12
13 FixedType getActualType();
14
15 ExprType unwrapIfSet();
16}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java
new file mode 100644
index 00000000..1b2ded48
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/FixedType.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8public sealed interface FixedType extends ExprType permits NodeType, LiteralType, InvalidType, DataExprType {
9 @Override
10 default FixedType getActualType() {
11 return this;
12 }
13
14 @Override
15 default FixedType unwrapIfSet() {
16 return this;
17 }
18}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/InvalidType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/InvalidType.java
new file mode 100644
index 00000000..c18612bc
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/InvalidType.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8public final class InvalidType implements FixedType {
9 InvalidType() {
10 }
11
12 @Override
13 public String toString() {
14 return "invalid";
15 }
16}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/LiteralType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/LiteralType.java
new file mode 100644
index 00000000..7fd33553
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/LiteralType.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8public final class LiteralType implements FixedType {
9 LiteralType() {
10 }
11
12 @Override
13 public String toString() {
14 return "constraint";
15 }
16}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/MutableType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/MutableType.java
new file mode 100644
index 00000000..78fdf884
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/MutableType.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8public final class MutableType implements ExprType {
9 private DataExprType actualType;
10
11 @Override
12 public FixedType getActualType() {
13 return actualType == null ? INVALID : actualType;
14 }
15
16 public void setActualType(DataExprType actualType) {
17 if (this.actualType != null) {
18 throw new IllegalStateException("Actual type was already set");
19 }
20 this.actualType = actualType;
21 }
22
23 @Override
24 public ExprType unwrapIfSet() {
25 return actualType == null ? this : actualType;
26 }
27
28 @Override
29 public String toString() {
30 return getActualType().toString();
31 }
32}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/NodeType.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/NodeType.java
new file mode 100644
index 00000000..1baff212
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/NodeType.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8public final class NodeType implements FixedType {
9 NodeType() {
10 }
11
12 @Override
13 public String toString() {
14 return "node";
15 }
16}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/ProblemTypeAnalyzer.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ProblemTypeAnalyzer.java
new file mode 100644
index 00000000..fcf99ad8
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/ProblemTypeAnalyzer.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import com.google.inject.Singleton;
11import org.eclipse.xtext.util.IResourceScopeCache;
12import tools.refinery.language.model.problem.Problem;
13
14@Singleton
15public class ProblemTypeAnalyzer {
16 private static final String CACHE_KEY = "tools.refinery.language.typesystem.ProblemTypeAnalyzer.CACHE_KEY";
17
18 @Inject
19 private IResourceScopeCache resourceScopeCache;
20
21 @Inject
22 private Provider<TypedModule> typedModuleProvider;
23
24 public TypedModule getOrComputeTypes(Problem problem) {
25 var resource = problem.eResource();
26 return resourceScopeCache.get(CACHE_KEY, resource, () -> {
27 var typedModule = typedModuleProvider.get();
28 typedModule.setProblem(problem);
29 return typedModule;
30 });
31 }
32}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/Signature.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/Signature.java
new file mode 100644
index 00000000..8e72c185
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/Signature.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8import java.util.List;
9
10public record Signature(List<FixedType> parameterTypes, FixedType resultType) {
11}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/SignatureProvider.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/SignatureProvider.java
new file mode 100644
index 00000000..3e25a0f5
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/SignatureProvider.java
@@ -0,0 +1,107 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8import com.google.inject.Inject;
9import com.google.inject.Singleton;
10import org.eclipse.xtext.naming.IQualifiedNameProvider;
11import org.eclipse.xtext.util.IResourceScopeCache;
12import tools.refinery.language.model.problem.*;
13
14import java.util.ArrayList;
15import java.util.HashMap;
16import java.util.List;
17
18@Singleton
19public class SignatureProvider {
20 private static final String PREFIX = "tools.refinery.language.typesystem.SignatureProvider.";
21 private static final String SIGNATURE_CACHE = PREFIX + "SIGNATURE_CACHE";
22 private static final String DATATYPE_CACHE = PREFIX + "DATATYPE_CACHE";
23 private static final String AGGREGATOR_CACHE = PREFIX + "AGGREGATOR_CACHE";
24
25 @Inject
26 private IQualifiedNameProvider qualifiedNameProvider;
27
28 @Inject
29 private IResourceScopeCache cache;
30
31 public Signature getSignature(Relation relation) {
32 var signatures = cache.get(SIGNATURE_CACHE, relation.eResource(), () -> new HashMap<Relation, Signature>());
33 return signatures.computeIfAbsent(relation, this::computeSignature);
34 }
35
36 public int getArity(Relation relation) {
37 return getSignature(relation).parameterTypes().size();
38 }
39
40 private Signature computeSignature(Relation relation) {
41 return new Signature(getParameterTypes(relation), getResultType(relation));
42 }
43
44 private List<FixedType> getParameterTypes(Relation relation) {
45 return switch (relation) {
46 case ClassDeclaration ignored -> List.of(ExprType.NODE);
47 case EnumDeclaration ignored -> List.of(ExprType.NODE);
48 case DatatypeDeclaration datatypeDeclaration -> List.of(getDataType(datatypeDeclaration));
49 case ReferenceDeclaration referenceDeclaration -> {
50 if (referenceDeclaration.getReferenceType() instanceof DatatypeDeclaration) {
51 yield List.of(ExprType.NODE);
52 }
53 yield List.of(ExprType.NODE, ExprType.NODE);
54 }
55 case ParametricDefinition parametricDefinition -> {
56 var parameters = parametricDefinition.getParameters();
57 var exprTypes = new ArrayList<FixedType>(parameters.size());
58 for (var parameter : parameters) {
59 if (parameter.getParameterType() instanceof DatatypeDeclaration datatypeDeclaration) {
60 exprTypes.add(getDataType(datatypeDeclaration));
61 } else {
62 exprTypes.add(ExprType.NODE);
63 }
64 }
65 yield List.copyOf(exprTypes);
66 }
67 default -> throw new IllegalArgumentException("Unknown Relation: " + relation);
68 };
69 }
70
71 private FixedType getResultType(Relation relation) {
72 if (relation instanceof ReferenceDeclaration referenceDeclaration &&
73 referenceDeclaration.getReferenceType() instanceof DatatypeDeclaration datatypeDeclaration) {
74 return getDataType(datatypeDeclaration);
75 }
76 return ExprType.LITERAL;
77 }
78
79 public DataExprType getDataType(DatatypeDeclaration datatypeDeclaration) {
80 var dataTypes = cache.get(DATATYPE_CACHE, datatypeDeclaration.eResource(),
81 () -> new HashMap<DatatypeDeclaration, DataExprType>());
82 return dataTypes.computeIfAbsent(datatypeDeclaration, this::computeDataType);
83 }
84
85 private DataExprType computeDataType(DatatypeDeclaration datatypeDeclaration) {
86 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(datatypeDeclaration);
87 if (qualifiedName == null) {
88 throw new IllegalArgumentException("Datatype declaration has no qualified name: " + datatypeDeclaration);
89 }
90 return new DataExprType(qualifiedName);
91 }
92
93 public AggregatorName getAggregatorName(AggregatorDeclaration aggregatorDeclaration) {
94 var dataTypes = cache.get(AGGREGATOR_CACHE, aggregatorDeclaration.eResource(),
95 () -> new HashMap<AggregatorDeclaration, AggregatorName>());
96 return dataTypes.computeIfAbsent(aggregatorDeclaration, this::computeAggregatorName);
97 }
98
99 private AggregatorName computeAggregatorName(AggregatorDeclaration aggregatorDeclaration) {
100 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(aggregatorDeclaration);
101 if (qualifiedName == null) {
102 throw new IllegalArgumentException(
103 "Aggregator declaration has no qualified name: " + aggregatorDeclaration);
104 }
105 return new AggregatorName(qualifiedName);
106 }
107}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java b/subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java
new file mode 100644
index 00000000..de923e0d
--- /dev/null
+++ b/subprojects/language/src/main/java/tools/refinery/language/typesystem/TypedModule.java
@@ -0,0 +1,568 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.typesystem;
7
8import com.google.inject.Inject;
9import org.eclipse.emf.common.util.Diagnostic;
10import org.eclipse.emf.ecore.EObject;
11import org.eclipse.emf.ecore.EStructuralFeature;
12import org.eclipse.xtext.validation.CheckType;
13import org.eclipse.xtext.validation.FeatureBasedDiagnostic;
14import tools.refinery.language.expressions.BuiltinTermInterpreter;
15import tools.refinery.language.expressions.TermInterpreter;
16import tools.refinery.language.model.problem.*;
17import tools.refinery.language.scoping.imports.ImportAdapterProvider;
18import tools.refinery.language.validation.ProblemValidator;
19
20import java.util.*;
21
22public class TypedModule {
23 private static final String OPERAND_TYPE_ERROR_MESSAGE = "Cannot determine operand type.";
24
25 @Inject
26 private SignatureProvider signatureProvider;
27
28 @Inject
29 private ImportAdapterProvider importAdapterProvider;
30
31 private TermInterpreter interpreter;
32 private final Map<Variable, List<AssignmentExpr>> assignments = new LinkedHashMap<>();
33 private final Map<Variable, FixedType> variableTypes = new HashMap<>();
34 private final Map<Expr, ExprType> expressionTypes = new HashMap<>();
35 private final Set<Variable> variablesToProcess = new LinkedHashSet<>();
36 private final List<FeatureBasedDiagnostic> diagnostics = new ArrayList<>();
37
38 void setProblem(Problem problem) {
39 interpreter = importAdapterProvider.getTermInterpreter(problem);
40 gatherAssignments(problem);
41 checkTypes(problem);
42 }
43
44 private void gatherAssignments(Problem problem) {
45 var iterator = problem.eAllContents();
46 while (iterator.hasNext()) {
47 var eObject = iterator.next();
48 if (!(eObject instanceof AssignmentExpr assignmentExpr)) {
49 continue;
50 }
51 if (assignmentExpr.getLeft() instanceof VariableOrNodeExpr variableOrNodeExpr &&
52 variableOrNodeExpr.getVariableOrNode() instanceof Variable variable) {
53 var assignmentList = assignments.computeIfAbsent(variable, ignored -> new ArrayList<>(1));
54 assignmentList.add(assignmentExpr);
55 }
56 iterator.prune();
57 }
58 }
59
60 private void checkTypes(Problem problem) {
61 for (var statement : problem.getStatements()) {
62 switch (statement) {
63 case PredicateDefinition predicateDefinition -> checkTypes(predicateDefinition);
64 case Assertion assertion -> checkTypes(assertion);
65 default -> {
66 // Nothing to type check.
67 }
68 }
69 }
70 }
71
72 private void checkTypes(PredicateDefinition predicateDefinition) {
73 for (var conjunction : predicateDefinition.getBodies()) {
74 for (var literal : conjunction.getLiterals()) {
75 coerceIntoLiteral(literal);
76 }
77 }
78 }
79
80 private void checkTypes(Assertion assertion) {
81 var relation = assertion.getRelation();
82 var value = assertion.getValue();
83 if (relation == null) {
84 return;
85 }
86 var type = signatureProvider.getSignature(relation).resultType();
87 if (type == ExprType.LITERAL) {
88 if (value == null) {
89 return;
90 }
91 expectType(value, BuiltinTermInterpreter.BOOLEAN_TYPE);
92 return;
93 }
94 if (value == null) {
95 var message = "Assertion value of type %s is required.".formatted(type);
96 error(message, assertion, ProblemPackage.Literals.ASSERTION__RELATION, 0, ProblemValidator.TYPE_ERROR);
97 }
98 expectType(value, type);
99 }
100
101 public List<FeatureBasedDiagnostic> getDiagnostics() {
102 return diagnostics;
103 }
104
105 public FixedType getVariableType(Variable variable) {
106 // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant
107 // way, which would cause a ConcurrentModificationException with computeIfAbsent.
108 @SuppressWarnings("squid:S3824")
109 var type = variableTypes.get(variable);
110 //noinspection Java8MapApi
111 if (type == null) {
112 type = computeVariableType(variable);
113 variableTypes.put(variable, type);
114 }
115 return type;
116 }
117
118 private FixedType computeVariableType(Variable variable) {
119 if (variable instanceof Parameter) {
120 return computeUnassignedVariableType(variable);
121 }
122 var assignmnentList = assignments.get(variable);
123 if (assignmnentList == null || assignmnentList.isEmpty()) {
124 return computeUnassignedVariableType(variable);
125 }
126 if (variablesToProcess.contains(variable)) {
127 throw new IllegalStateException("Circular reference to variable: " + variable.getName());
128 }
129 if (assignmnentList.size() > 1) {
130 var message = "Multiple assignments for variable '%s'.".formatted(variable.getName());
131 for (var assignment : assignmnentList) {
132 error(message, assignment, ProblemPackage.Literals.BINARY_EXPR__LEFT, 0,
133 ProblemValidator.INVALID_ASSIGNMENT_ISSUE);
134 }
135 return ExprType.INVALID;
136 }
137 var assignment = assignmnentList.getFirst();
138 variablesToProcess.add(variable);
139 try {
140 var assignedType = getExpressionType(assignment.getRight());
141 if (assignedType instanceof MutableType) {
142 var message = "Cannot determine type of variable '%s'.".formatted(variable.getName());
143 error(message, assignment, ProblemPackage.Literals.BINARY_EXPR__RIGHT, 0, ProblemValidator.TYPE_ERROR);
144 return ExprType.INVALID;
145 }
146 if (assignedType instanceof DataExprType dataExprType) {
147 return dataExprType;
148 }
149 if (assignedType != ExprType.INVALID) {
150 var message = "Expected data expression for variable '%s', got %s instead."
151 .formatted(variable.getName(), assignedType);
152 error(message, assignment, ProblemPackage.Literals.BINARY_EXPR__RIGHT, 0, ProblemValidator.TYPE_ERROR);
153 }
154 return ExprType.INVALID;
155 } finally {
156 variablesToProcess.remove(variable);
157 }
158 }
159
160 private FixedType computeUnassignedVariableType(Variable variable) {
161 if (variable instanceof Parameter parameter &&
162 parameter.getParameterType() instanceof DatatypeDeclaration datatypeDeclaration) {
163 return signatureProvider.getDataType(datatypeDeclaration);
164 }
165 // Parameters without an explicit datatype annotation are node variables.
166 return ExprType.NODE;
167 }
168
169 public ExprType getExpressionType(Expr expr) {
170 // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant
171 // way, which would cause a ConcurrentModificationException with computeIfAbsent.
172 @SuppressWarnings("squid:S3824")
173 var type = expressionTypes.get(expr);
174 //noinspection Java8MapApi
175 if (type == null) {
176 type = computeExpressionType(expr);
177 expressionTypes.put(expr, type);
178 }
179 return type.unwrapIfSet();
180 }
181
182 private ExprType computeExpressionType(Expr expr) {
183 return switch (expr) {
184 case LogicConstant logicConstant -> computeExpressionType(logicConstant);
185 case IntConstant ignored -> BuiltinTermInterpreter.INT_TYPE;
186 case RealConstant ignored -> BuiltinTermInterpreter.REAL_TYPE;
187 case StringConstant ignored -> BuiltinTermInterpreter.STRING_TYPE;
188 case InfiniteConstant ignored -> new MutableType();
189 case VariableOrNodeExpr variableOrNodeExpr -> computeExpressionType(variableOrNodeExpr);
190 case AssignmentExpr assignmentExpr -> computeExpressionType(assignmentExpr);
191 case Atom atom -> computeExpressionType(atom);
192 case NegationExpr negationExpr -> computeExpressionType(negationExpr);
193 case ArithmeticUnaryExpr arithmeticUnaryExpr -> computeExpressionType(arithmeticUnaryExpr);
194 case CountExpr countExpr -> computeExpressionType(countExpr);
195 case AggregationExpr aggregationExpr -> computeExpressionType(aggregationExpr);
196 case ComparisonExpr comparisonExpr -> computeExpressionType(comparisonExpr);
197 case LatticeBinaryExpr latticeBinaryExpr -> computeExpressionType(latticeBinaryExpr);
198 case RangeExpr rangeExpr -> computeExpressionType(rangeExpr);
199 case ArithmeticBinaryExpr arithmeticBinaryExpr -> computeExpressionType(arithmeticBinaryExpr);
200 case CastExpr castExpr -> computeExpressionType(castExpr);
201 default -> {
202 error("Unknown expression: " + expr.getClass().getSimpleName(), expr, null, 0,
203 ProblemValidator.UNKNOWN_EXPRESSION_ISSUE);
204 yield ExprType.INVALID;
205 }
206 };
207 }
208
209 private ExprType computeExpressionType(LogicConstant expr) {
210 return switch (expr.getLogicValue()) {
211 case TRUE, FALSE -> BuiltinTermInterpreter.BOOLEAN_TYPE;
212 case UNKNOWN, ERROR -> new MutableType();
213 case null -> ExprType.INVALID;
214 };
215 }
216
217 private ExprType computeExpressionType(VariableOrNodeExpr expr) {
218 var target = expr.getVariableOrNode();
219 if (target == null || target.eIsProxy()) {
220 return ExprType.INVALID;
221 }
222 return switch (target) {
223 case Node ignored -> ExprType.NODE;
224 case Variable variable -> {
225 if (variablesToProcess.contains(variable)) {
226 var message = "Circular reference to variable '%s'.".formatted(variable.getName());
227 error(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, 0,
228 ProblemValidator.INVALID_ASSIGNMENT_ISSUE);
229 yield ExprType.INVALID;
230 }
231 yield getVariableType(variable);
232 }
233 default -> {
234 error("Unknown variable: " + target.getName(), expr,
235 ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, 0,
236 ProblemValidator.UNKNOWN_EXPRESSION_ISSUE);
237 yield ExprType.INVALID;
238 }
239 };
240 }
241
242 private ExprType computeExpressionType(AssignmentExpr expr) {
243 // Force the left side to type check. Since the left side is a variable, it will force the right side to also
244 // type check in order to infer the variable type.
245 return getExpressionType(expr.getLeft()) == ExprType.INVALID ? ExprType.INVALID : ExprType.LITERAL;
246 }
247
248 private ExprType computeExpressionType(Atom atom) {
249 var relation = atom.getRelation();
250 if (relation == null || relation.eIsProxy()) {
251 return ExprType.INVALID;
252 }
253 if (relation instanceof DatatypeDeclaration) {
254 var message = "Invalid call to data type. Use 'as %s' for casting.".formatted(
255 relation.getName());
256 error(message, atom, ProblemPackage.Literals.ATOM__RELATION, 0, ProblemValidator.TYPE_ERROR);
257 }
258 var signature = signatureProvider.getSignature(relation);
259 var parameterTypes = signature.parameterTypes();
260 var arguments = atom.getArguments();
261 int size = Math.min(parameterTypes.size(), arguments.size());
262 boolean ok = parameterTypes.size() == arguments.size();
263 for (int i = 0; i < size; i++) {
264 var parameterType = parameterTypes.get(i);
265 var argument = arguments.get(i);
266 if (!expectType(argument, parameterType)) {
267 // Avoid short-circuiting to let us type check all arguments.
268 ok = false;
269 }
270 }
271 return ok ? signature.resultType() : ExprType.INVALID;
272 }
273
274 private ExprType computeExpressionType(NegationExpr negationExpr) {
275 var body = negationExpr.getBody();
276 if (body == null) {
277 return ExprType.INVALID;
278 }
279 var actualType = getExpressionType(body);
280 if (actualType == ExprType.LITERAL) {
281 // Negation of literals yields another (non-enumerable) literal.
282 return ExprType.LITERAL;
283 }
284 if (actualType == DataExprType.INVALID) {
285 return ExprType.INVALID;
286 }
287 if (actualType instanceof MutableType) {
288 error(OPERAND_TYPE_ERROR_MESSAGE, body, null, 0, ProblemValidator.TYPE_ERROR);
289 return ExprType.INVALID;
290 }
291 if (actualType instanceof DataExprType dataExprType) {
292 var result = interpreter.getNegationType(dataExprType);
293 if (result.isPresent()) {
294 return result.get();
295 }
296 }
297 var message = "Data type %s does not support negation.".formatted(actualType);
298 error(message, negationExpr, null, 0, ProblemValidator.TYPE_ERROR);
299 return ExprType.INVALID;
300 }
301
302 private ExprType computeExpressionType(ArithmeticUnaryExpr expr) {
303 var op = expr.getOp();
304 var body = expr.getBody();
305 if (op == null || body == null) {
306 return ExprType.INVALID;
307 }
308 var actualType = getExpressionType(body);
309 if (actualType == DataExprType.INVALID) {
310 return ExprType.INVALID;
311 }
312 if (actualType instanceof MutableType) {
313 error(OPERAND_TYPE_ERROR_MESSAGE, body, null, 0, ProblemValidator.TYPE_ERROR);
314 return ExprType.INVALID;
315 }
316 if (actualType instanceof DataExprType dataExprType) {
317 var result = interpreter.getUnaryOperationType(op, dataExprType);
318 if (result.isPresent()) {
319 return result.get();
320 }
321 }
322 var message = "Unsupported operator for data type %s.".formatted(actualType);
323 error(message, expr, null, 0, ProblemValidator.TYPE_ERROR);
324 return ExprType.INVALID;
325 }
326
327 private ExprType computeExpressionType(CountExpr countExpr) {
328 return coerceIntoLiteral(countExpr.getBody()) ? BuiltinTermInterpreter.INT_TYPE : ExprType.INVALID;
329 }
330
331 private ExprType computeExpressionType(AggregationExpr expr) {
332 var aggregator = expr.getAggregator();
333 if (aggregator == null || aggregator.eIsProxy()) {
334 return null;
335 }
336 // Avoid short-circuiting to let us type check both the value and the condition.
337 boolean ok = coerceIntoLiteral(expr.getCondition());
338 var value = expr.getValue();
339 var actualType = getExpressionType(value);
340 if (actualType == ExprType.INVALID) {
341 return ExprType.INVALID;
342 }
343 if (actualType instanceof MutableType) {
344 error(OPERAND_TYPE_ERROR_MESSAGE, value, null, 0, ProblemValidator.TYPE_ERROR);
345 return ExprType.INVALID;
346 }
347 if (actualType instanceof DataExprType dataExprType) {
348 var aggregatorName = signatureProvider.getAggregatorName(aggregator);
349 var result = interpreter.getAggregationType(aggregatorName, dataExprType);
350 if (result.isPresent()) {
351 return ok ? result.get() : ExprType.INVALID;
352 }
353 }
354 var message = "Unsupported aggregator for type %s.".formatted(actualType);
355 error(message, expr, ProblemPackage.Literals.AGGREGATION_EXPR__AGGREGATOR, 0, ProblemValidator.TYPE_ERROR);
356 return ExprType.INVALID;
357 }
358
359 private ExprType computeExpressionType(ComparisonExpr expr) {
360 var left = expr.getLeft();
361 var right = expr.getRight();
362 var op = expr.getOp();
363 if (op == ComparisonOp.NODE_EQ || op == ComparisonOp.NODE_NOT_EQ) {
364 // Avoid short-circuiting to let us type check both arguments.
365 boolean leftOk = expectType(left, ExprType.NODE);
366 boolean rightOk = expectType(right, ExprType.NODE);
367 return leftOk && rightOk ? ExprType.LITERAL : ExprType.INVALID;
368 }
369 if (!(getCommonDataType(expr) instanceof DataExprType commonType)) {
370 return ExprType.INVALID;
371 }
372 // Data equality and inequality are always supported for data types.
373 if (op != ComparisonOp.EQ && op != ComparisonOp.NOT_EQ && !interpreter.isComparisonSupported(commonType)) {
374 var message = "Data type %s does not support comparison.".formatted(commonType);
375 error(message, expr, null, 0, ProblemValidator.TYPE_ERROR);
376 return ExprType.INVALID;
377 }
378 return BuiltinTermInterpreter.BOOLEAN_TYPE;
379 }
380
381 private ExprType computeExpressionType(LatticeBinaryExpr expr) {
382 // Lattice operations are always supported for data types.
383 return getCommonDataType(expr);
384 }
385
386 private ExprType computeExpressionType(RangeExpr expr) {
387 var left = expr.getLeft();
388 var right = expr.getRight();
389 if (left instanceof InfiniteConstant && right instanceof InfiniteConstant) {
390 // `*..*` is equivalent to `unknown` if neither subexpression have been typed yet.
391 var mutableType = new MutableType();
392 if (expressionTypes.putIfAbsent(left, mutableType) == null &&
393 expressionTypes.put(right, mutableType) == null) {
394 return mutableType;
395 }
396 }
397 if (!(getCommonDataType(expr) instanceof DataExprType commonType)) {
398 return ExprType.INVALID;
399 }
400 if (!interpreter.isRangeSupported(commonType)) {
401 var message = "Data type %s does not support ranges.".formatted(commonType);
402 error(message, expr, null, 0, ProblemValidator.TYPE_ERROR);
403 return ExprType.INVALID;
404 }
405 return commonType;
406 }
407
408 private ExprType computeExpressionType(ArithmeticBinaryExpr expr) {
409 var op = expr.getOp();
410 var left = expr.getLeft();
411 var right = expr.getRight();
412 if (op == null || left == null || right == null) {
413 return ExprType.INVALID;
414 }
415 // Avoid short-circuiting to let us type check both arguments.
416 var leftType = getExpressionType(left);
417 var rightType = getExpressionType(right);
418 if (leftType == ExprType.INVALID || rightType == ExprType.INVALID) {
419 return ExprType.INVALID;
420 }
421 if (rightType instanceof MutableType rightMutableType) {
422 if (leftType instanceof DataExprType leftExprType) {
423 rightMutableType.setActualType(leftExprType);
424 rightType = leftExprType;
425 } else {
426 error(OPERAND_TYPE_ERROR_MESSAGE, right, null, 0, ProblemValidator.TYPE_ERROR);
427 return ExprType.INVALID;
428 }
429 }
430 if (leftType instanceof MutableType leftMutableType) {
431 if (rightType instanceof DataExprType rightExprType) {
432 leftMutableType.setActualType(rightExprType);
433 leftType = rightExprType;
434 } else {
435 error(OPERAND_TYPE_ERROR_MESSAGE, left, null, 0, ProblemValidator.TYPE_ERROR);
436 return ExprType.INVALID;
437 }
438 }
439 if (leftType instanceof DataExprType leftExprType && rightType instanceof DataExprType rightExprType) {
440 var result = interpreter.getBinaryOperationType(op, leftExprType, rightExprType);
441 if (result.isPresent()) {
442 return result.get();
443 }
444 }
445 var messageBuilder = new StringBuilder("Unsupported operator for ");
446 if (leftType.equals(rightType)) {
447 messageBuilder.append("data type ").append(leftType);
448 } else {
449 messageBuilder.append("data types ").append(leftType).append(" and ").append(rightType);
450 }
451 messageBuilder.append(".");
452 error(messageBuilder.toString(), expr, null, 0, ProblemValidator.TYPE_ERROR);
453 return ExprType.INVALID;
454 }
455
456 private ExprType computeExpressionType(CastExpr expr) {
457 var body = expr.getBody();
458 var targetRelation = expr.getTargetType();
459 if (body == null || !(targetRelation instanceof DatatypeDeclaration targetDeclaration)) {
460 return null;
461 }
462 var actualType = getExpressionType(body);
463 if (actualType == ExprType.INVALID) {
464 return ExprType.INVALID;
465 }
466 var targetType = signatureProvider.getDataType(targetDeclaration);
467 if (actualType instanceof MutableType mutableType) {
468 // Type ascription for polymorphic literal (e.g., `unknown as int` for the set of all integers).
469 mutableType.setActualType(targetType);
470 return targetType;
471 }
472 if (actualType.equals(targetType)) {
473 return targetType;
474 }
475 if (actualType instanceof DataExprType dataExprType && interpreter.isCastSupported(dataExprType, targetType)) {
476 return targetType;
477 }
478 var message = "Casting from %s to %s is not supported.".formatted(actualType, targetType);
479 error(message, expr, null, 0, ProblemValidator.TYPE_ERROR);
480 return ExprType.INVALID;
481 }
482
483 private FixedType getCommonDataType(BinaryExpr expr) {
484 var commonType = getCommonType(expr);
485 if (!(commonType instanceof DataExprType) && commonType != ExprType.INVALID) {
486 var message = "Expected data expression, got %s instead.".formatted(commonType);
487 error(message, expr, null, 0, ProblemValidator.TYPE_ERROR);
488 return ExprType.INVALID;
489 }
490 return commonType;
491 }
492
493 private FixedType getCommonType(BinaryExpr expr) {
494 var left = expr.getLeft();
495 var right = expr.getRight();
496 if (left == null || right == null) {
497 return ExprType.INVALID;
498 }
499 var leftType = getExpressionType(left);
500 if (leftType instanceof FixedType fixedLeftType) {
501 return expectType(right, fixedLeftType) ? fixedLeftType : ExprType.INVALID;
502 } else {
503 var rightType = getExpressionType(right);
504 if (rightType instanceof FixedType fixedRightType) {
505 return expectType(left, leftType, fixedRightType) ? fixedRightType : ExprType.INVALID;
506 } else {
507 error(OPERAND_TYPE_ERROR_MESSAGE, left, null, 0, ProblemValidator.TYPE_ERROR);
508 error(OPERAND_TYPE_ERROR_MESSAGE, right, null, 0, ProblemValidator.TYPE_ERROR);
509 return ExprType.INVALID;
510 }
511 }
512 }
513
514 private boolean coerceIntoLiteral(Expr expr) {
515 if (expr == null) {
516 return false;
517 }
518 var actualType = getExpressionType(expr);
519 if (actualType == ExprType.LITERAL) {
520 return true;
521 }
522 return expectType(expr, actualType, BuiltinTermInterpreter.BOOLEAN_TYPE);
523 }
524
525 private boolean expectType(Expr expr, FixedType expectedType) {
526 if (expr == null) {
527 return false;
528 }
529 var actualType = getExpressionType(expr);
530 return expectType(expr, actualType, expectedType);
531 }
532
533 private boolean expectType(Expr expr, ExprType actualType, FixedType expectedType) {
534 if (expectedType == ExprType.INVALID) {
535 // Silence any further errors is the expected type failed to compute.
536 return false;
537 }
538 if (actualType.equals(expectedType)) {
539 return true;
540 }
541 if (actualType == ExprType.INVALID) {
542 // We have already emitted an error previously.
543 return false;
544 }
545 if (actualType instanceof MutableType mutableType && expectedType instanceof DataExprType dataExprType) {
546 mutableType.setActualType(dataExprType);
547 return true;
548 }
549 var builder = new StringBuilder()
550 .append("Expected ")
551 .append(expectedType)
552 .append(" expression");
553 if (!(actualType instanceof MutableType)) {
554 builder.append(", got ")
555 .append(actualType)
556 .append(" instead");
557 }
558 builder.append(".");
559 error(builder.toString(), expr, null, 0, ProblemValidator.TYPE_ERROR);
560 return false;
561 }
562
563 private void error(String message, EObject object, EStructuralFeature feature, int index, String code,
564 String... issueData) {
565 diagnostics.add(new FeatureBasedDiagnostic(Diagnostic.ERROR, message, object, feature, index,
566 CheckType.NORMAL, code, issueData));
567 }
568}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java
index c87fa044..72f23e85 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -7,7 +7,56 @@ package tools.refinery.language.utils;
7 7
8import tools.refinery.language.model.problem.*; 8import tools.refinery.language.model.problem.*;
9 9
10public record BuiltinSymbols(Problem problem, ClassDeclaration node, PredicateDefinition equals, 10public final class BuiltinSymbols {
11 PredicateDefinition exists, ClassDeclaration contained, PredicateDefinition contains, 11 private final Problem problem;
12 PredicateDefinition invalidContainer) { 12 private final ClassDeclaration node;
13 private final PredicateDefinition equals;
14 private final PredicateDefinition exists;
15 private final ClassDeclaration contained;
16 private final PredicateDefinition contains;
17 private final PredicateDefinition invalidContainer;
18
19 public BuiltinSymbols(Problem problem) {
20 this.problem = problem;
21 node = getDeclaration(ClassDeclaration.class, "node");
22 equals = getDeclaration(PredicateDefinition.class, "equals");
23 exists = getDeclaration(PredicateDefinition.class, "exists");
24 contained = getDeclaration(ClassDeclaration.class, "contained");
25 contains = getDeclaration(PredicateDefinition.class, "contains");
26 invalidContainer = getDeclaration(PredicateDefinition.class, "invalidContainer");
27 }
28
29 public Problem problem() {
30 return problem;
31 }
32
33 public ClassDeclaration node() {
34 return node;
35 }
36
37 public PredicateDefinition equals() {
38 return equals;
39 }
40
41 public PredicateDefinition exists() {
42 return exists;
43 }
44
45 public ClassDeclaration contained() {
46 return contained;
47 }
48
49 public PredicateDefinition contains() {
50 return contains;
51 }
52
53 public PredicateDefinition invalidContainer() {
54 return invalidContainer;
55 }
56
57 private <T extends Statement & NamedElement> T getDeclaration(Class<T> type, String name) {
58 return problem.getStatements().stream().filter(type::isInstance).map(type::cast)
59 .filter(declaration -> name.equals(declaration.getName())).findFirst()
60 .orElseThrow(() -> new IllegalArgumentException("Built-in declaration " + name + " was not found"));
61 }
13} 62}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
deleted file mode 100644
index d45c8083..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
+++ /dev/null
@@ -1,98 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.utils;
7
8import com.google.inject.Inject;
9import com.google.inject.Singleton;
10import org.eclipse.emf.ecore.EObject;
11import org.eclipse.emf.ecore.resource.Resource;
12import org.eclipse.xtext.util.IResourceScopeCache;
13import org.eclipse.xtext.util.Tuples;
14import tools.refinery.language.library.BuiltinLibrary;
15import tools.refinery.language.model.problem.*;
16
17import java.util.*;
18
19@Singleton
20public class ProblemDesugarer {
21 @Inject
22 private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE;
23
24 public Optional<Problem> getBuiltinProblem(EObject context) {
25 return Optional.ofNullable(context).map(EObject::eResource).flatMap(resource ->
26 cache.get("builtinProblem", resource, () -> doGetBuiltinProblem(resource)));
27 }
28
29 private Optional<Problem> doGetBuiltinProblem(Resource resource) {
30 return Optional.ofNullable(resource).map(Resource::getResourceSet)
31 .map(resourceSet -> resourceSet.getResource(BuiltinLibrary.BUILTIN_LIBRARY_URI, true))
32 .map(Resource::getContents).filter(contents -> !contents.isEmpty()).map(List::getFirst)
33 .filter(Problem.class::isInstance).map(Problem.class::cast);
34 }
35
36 public Optional<BuiltinSymbols> getBuiltinSymbols(EObject context) {
37 return getBuiltinProblem(context).map(builtin ->
38 cache.get("builtinSymbols", builtin.eResource(), () -> doGetBuiltinSymbols(builtin)));
39 }
40
41 private BuiltinSymbols doGetBuiltinSymbols(Problem builtin) {
42 var node = doGetDeclaration(builtin, ClassDeclaration.class, "node");
43 var equals = doGetDeclaration(builtin, PredicateDefinition.class, "equals");
44 var exists = doGetDeclaration(builtin, PredicateDefinition.class, "exists");
45 var contained = doGetDeclaration(builtin, ClassDeclaration.class, "contained");
46 var contains = doGetDeclaration(builtin, PredicateDefinition.class, "contains");
47 var invalidContainer = doGetDeclaration(builtin, PredicateDefinition.class, "invalidContainer");
48 return new BuiltinSymbols(builtin, node, equals, exists, contained, contains, invalidContainer);
49 }
50
51 private <T extends Statement & NamedElement> T doGetDeclaration(Problem builtin, Class<T> type, String name) {
52 return builtin.getStatements().stream().filter(type::isInstance).map(type::cast)
53 .filter(declaration -> name.equals(declaration.getName())).findFirst()
54 .orElseThrow(() -> new IllegalArgumentException("Built-in declaration " + name + " was not found"));
55 }
56
57 public Collection<ClassDeclaration> getSuperclassesAndSelf(ClassDeclaration classDeclaration) {
58 return cache.get(Tuples.create(classDeclaration, "superclassesAndSelf"), classDeclaration.eResource(),
59 () -> doGetSuperclassesAndSelf(classDeclaration));
60 }
61
62 private Collection<ClassDeclaration> doGetSuperclassesAndSelf(ClassDeclaration classDeclaration) {
63 var builtinSymbols = getBuiltinSymbols(classDeclaration);
64 Set<ClassDeclaration> found = new HashSet<>();
65 builtinSymbols.ifPresent(symbols -> found.add(symbols.node()));
66 Deque<ClassDeclaration> queue = new ArrayDeque<>();
67 queue.addLast(classDeclaration);
68 while (!queue.isEmpty()) {
69 ClassDeclaration current = queue.removeFirst();
70 if (!found.contains(current)) {
71 found.add(current);
72 for (Relation superType : current.getSuperTypes()) {
73 if (superType instanceof ClassDeclaration superDeclaration) {
74 queue.addLast(superDeclaration);
75 }
76 }
77 }
78 }
79 return found;
80 }
81
82 public Collection<ReferenceDeclaration> getAllReferenceDeclarations(ClassDeclaration classDeclaration) {
83 return cache.get(Tuples.create(classDeclaration, "allReferenceDeclarations"), classDeclaration.eResource(),
84 () -> doGetAllReferenceDeclarations(classDeclaration));
85 }
86
87 private Collection<ReferenceDeclaration> doGetAllReferenceDeclarations(ClassDeclaration classDeclaration) {
88 Set<ReferenceDeclaration> referenceDeclarations = new HashSet<>();
89 for (ClassDeclaration superclass : getSuperclassesAndSelf(classDeclaration)) {
90 referenceDeclarations.addAll(superclass.getFeatureDeclarations());
91 }
92 return referenceDeclarations;
93 }
94
95 public boolean isContainmentReference(ReferenceDeclaration referenceDeclaration) {
96 return referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT;
97 }
98}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
index 6d6d65da..9daa8f61 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
@@ -98,18 +98,19 @@ public final class ProblemUtil {
98 return true; 98 return true;
99 } 99 }
100 100
101 public static int getArity(Relation relation) { 101 public static boolean isTypeLike(Relation relation) {
102 if (relation instanceof ClassDeclaration || relation instanceof EnumDeclaration || 102 if (relation instanceof ClassDeclaration || relation instanceof EnumDeclaration ||
103 relation instanceof DatatypeDeclaration) { 103 relation instanceof DatatypeDeclaration) {
104 return 1; 104 return true;
105 }
106 if (relation instanceof ReferenceDeclaration) {
107 return 2;
108 } 105 }
109 if (relation instanceof PredicateDefinition predicateDefinition) { 106 if (relation instanceof PredicateDefinition predicateDefinition) {
110 return predicateDefinition.getParameters().size(); 107 return predicateDefinition.getParameters().size() == 1;
111 } 108 }
112 throw new IllegalArgumentException("Unknown Relation: " + relation); 109 return false;
110 }
111
112 public static boolean isContainmentReference(ReferenceDeclaration referenceDeclaration) {
113 return referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT;
113 } 114 }
114 115
115 public static boolean isContainerReference(ReferenceDeclaration referenceDeclaration) { 116 public static boolean isContainerReference(ReferenceDeclaration referenceDeclaration) {
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
index 7f45bc20..745e2d2b 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java
@@ -10,6 +10,7 @@
10package tools.refinery.language.validation; 10package tools.refinery.language.validation;
11 11
12import com.google.inject.Inject; 12import com.google.inject.Inject;
13import org.eclipse.emf.common.util.Diagnostic;
13import org.eclipse.emf.ecore.EObject; 14import org.eclipse.emf.ecore.EObject;
14import org.eclipse.emf.ecore.EReference; 15import org.eclipse.emf.ecore.EReference;
15import org.eclipse.xtext.EcoreUtil2; 16import org.eclipse.xtext.EcoreUtil2;
@@ -18,11 +19,15 @@ import org.eclipse.xtext.validation.Check;
18import org.jetbrains.annotations.Nullable; 19import org.jetbrains.annotations.Nullable;
19import tools.refinery.language.model.problem.*; 20import tools.refinery.language.model.problem.*;
20import tools.refinery.language.naming.NamingUtil; 21import tools.refinery.language.naming.NamingUtil;
21import tools.refinery.language.scoping.imports.ImportAdapter; 22import tools.refinery.language.scoping.imports.ImportAdapterProvider;
22import tools.refinery.language.utils.ProblemDesugarer; 23import tools.refinery.language.typesystem.ProblemTypeAnalyzer;
24import tools.refinery.language.typesystem.SignatureProvider;
23import tools.refinery.language.utils.ProblemUtil; 25import tools.refinery.language.utils.ProblemUtil;
24 26
25import java.util.*; 27import java.util.ArrayList;
28import java.util.LinkedHashMap;
29import java.util.LinkedHashSet;
30import java.util.Set;
26 31
27/** 32/**
28 * This class contains custom validation rules. 33 * This class contains custom validation rules.
@@ -32,47 +37,39 @@ import java.util.*;
32 */ 37 */
33public class ProblemValidator extends AbstractProblemValidator { 38public class ProblemValidator extends AbstractProblemValidator {
34 private static final String ISSUE_PREFIX = "tools.refinery.language.validation.ProblemValidator."; 39 private static final String ISSUE_PREFIX = "tools.refinery.language.validation.ProblemValidator.";
35
36 public static final String UNEXPECTED_MODULE_NAME_ISSUE = ISSUE_PREFIX + "UNEXPECTED_MODULE_NAME"; 40 public static final String UNEXPECTED_MODULE_NAME_ISSUE = ISSUE_PREFIX + "UNEXPECTED_MODULE_NAME";
37
38 public static final String INVALID_IMPORT_ISSUE = ISSUE_PREFIX + "INVALID_IMPORT"; 41 public static final String INVALID_IMPORT_ISSUE = ISSUE_PREFIX + "INVALID_IMPORT";
39
40 public static final String SINGLETON_VARIABLE_ISSUE = ISSUE_PREFIX + "SINGLETON_VARIABLE"; 42 public static final String SINGLETON_VARIABLE_ISSUE = ISSUE_PREFIX + "SINGLETON_VARIABLE";
41
42 public static final String NODE_CONSTANT_ISSUE = ISSUE_PREFIX + "NODE_CONSTANT_ISSUE"; 43 public static final String NODE_CONSTANT_ISSUE = ISSUE_PREFIX + "NODE_CONSTANT_ISSUE";
43
44 public static final String DUPLICATE_NAME_ISSUE = ISSUE_PREFIX + "DUPLICATE_NAME"; 44 public static final String DUPLICATE_NAME_ISSUE = ISSUE_PREFIX + "DUPLICATE_NAME";
45
46 public static final String INVALID_MULTIPLICITY_ISSUE = ISSUE_PREFIX + "INVALID_MULTIPLICITY"; 45 public static final String INVALID_MULTIPLICITY_ISSUE = ISSUE_PREFIX + "INVALID_MULTIPLICITY";
47
48 public static final String ZERO_MULTIPLICITY_ISSUE = ISSUE_PREFIX + "ZERO_MULTIPLICITY"; 46 public static final String ZERO_MULTIPLICITY_ISSUE = ISSUE_PREFIX + "ZERO_MULTIPLICITY";
49
50 public static final String MISSING_OPPOSITE_ISSUE = ISSUE_PREFIX + "MISSING_OPPOSITE"; 47 public static final String MISSING_OPPOSITE_ISSUE = ISSUE_PREFIX + "MISSING_OPPOSITE";
51
52 public static final String INVALID_OPPOSITE_ISSUE = ISSUE_PREFIX + "INVALID_OPPOSITE"; 48 public static final String INVALID_OPPOSITE_ISSUE = ISSUE_PREFIX + "INVALID_OPPOSITE";
53
54 public static final String INVALID_SUPERTYPE_ISSUE = ISSUE_PREFIX + "INVALID_SUPERTYPE"; 49 public static final String INVALID_SUPERTYPE_ISSUE = ISSUE_PREFIX + "INVALID_SUPERTYPE";
55
56 public static final String INVALID_REFERENCE_TYPE_ISSUE = ISSUE_PREFIX + "INVALID_REFERENCE_TYPE"; 50 public static final String INVALID_REFERENCE_TYPE_ISSUE = ISSUE_PREFIX + "INVALID_REFERENCE_TYPE";
57
58 public static final String INVALID_ARITY_ISSUE = ISSUE_PREFIX + "INVALID_ARITY"; 51 public static final String INVALID_ARITY_ISSUE = ISSUE_PREFIX + "INVALID_ARITY";
59
60 public static final String INVALID_TRANSITIVE_CLOSURE_ISSUE = ISSUE_PREFIX + "INVALID_TRANSITIVE_CLOSURE"; 52 public static final String INVALID_TRANSITIVE_CLOSURE_ISSUE = ISSUE_PREFIX + "INVALID_TRANSITIVE_CLOSURE";
61
62 public static final String INVALID_VALUE_ISSUE = ISSUE_PREFIX + "INVALID_VALUE"; 53 public static final String INVALID_VALUE_ISSUE = ISSUE_PREFIX + "INVALID_VALUE";
63
64 public static final String UNSUPPORTED_ASSERTION_ISSUE = ISSUE_PREFIX + "UNSUPPORTED_ASSERTION"; 54 public static final String UNSUPPORTED_ASSERTION_ISSUE = ISSUE_PREFIX + "UNSUPPORTED_ASSERTION";
65 55 public static final String UNKNOWN_EXPRESSION_ISSUE = ISSUE_PREFIX + "UNKNOWN_EXPRESSION";
66 public static final String INVALID_ASSIGNMENT_ISSUE = ISSUE_PREFIX + "INVALID_ASSIGNMENT"; 56 public static final String INVALID_ASSIGNMENT_ISSUE = ISSUE_PREFIX + "INVALID_ASSIGNMENT";
57 public static final String TYPE_ERROR = ISSUE_PREFIX + "TYPE_ERROR";
67 58
68 @Inject 59 @Inject
69 private ReferenceCounter referenceCounter; 60 private ReferenceCounter referenceCounter;
70 61
71 @Inject 62 @Inject
72 private ProblemDesugarer desugarer; 63 private IQualifiedNameConverter qualifiedNameConverter;
73 64
74 @Inject 65 @Inject
75 private IQualifiedNameConverter qualifiedNameConverter; 66 private ImportAdapterProvider importAdapterProvider;
67
68 @Inject
69 private SignatureProvider signatureProvider;
70
71 @Inject
72 private ProblemTypeAnalyzer typeAnalyzer;
76 73
77 @Check 74 @Check
78 public void checkModuleName(Problem problem) { 75 public void checkModuleName(Problem problem) {
@@ -88,7 +85,7 @@ public class ProblemValidator extends AbstractProblemValidator {
88 if (resourceSet == null) { 85 if (resourceSet == null) {
89 return; 86 return;
90 } 87 }
91 var adapter = ImportAdapter.getOrInstall(resourceSet); 88 var adapter = importAdapterProvider.getOrInstall(resourceSet);
92 var expectedName = adapter.getQualifiedName(resource.getURI()); 89 var expectedName = adapter.getQualifiedName(resource.getURI());
93 if (expectedName == null) { 90 if (expectedName == null) {
94 return; 91 return;
@@ -158,15 +155,19 @@ public class ProblemValidator extends AbstractProblemValidator {
158 public void checkUniqueDeclarations(Problem problem) { 155 public void checkUniqueDeclarations(Problem problem) {
159 var relations = new ArrayList<Relation>(); 156 var relations = new ArrayList<Relation>();
160 var nodes = new ArrayList<Node>(); 157 var nodes = new ArrayList<Node>();
158 var aggregators = new ArrayList<AggregatorDeclaration>();
161 for (var statement : problem.getStatements()) { 159 for (var statement : problem.getStatements()) {
162 if (statement instanceof Relation relation) { 160 if (statement instanceof Relation relation) {
163 relations.add(relation); 161 relations.add(relation);
164 } else if (statement instanceof NodeDeclaration nodeDeclaration) { 162 } else if (statement instanceof NodeDeclaration nodeDeclaration) {
165 nodes.addAll(nodeDeclaration.getNodes()); 163 nodes.addAll(nodeDeclaration.getNodes());
164 } else if (statement instanceof AggregatorDeclaration aggregatorDeclaration) {
165 aggregators.add(aggregatorDeclaration);
166 } 166 }
167 } 167 }
168 checkUniqueSimpleNames(relations); 168 checkUniqueSimpleNames(relations);
169 checkUniqueSimpleNames(nodes); 169 checkUniqueSimpleNames(nodes);
170 checkUniqueSimpleNames(aggregators);
170 } 171 }
171 172
172 @Check 173 @Check
@@ -353,11 +354,14 @@ public class ProblemValidator extends AbstractProblemValidator {
353 354
354 @Check 355 @Check
355 public void checkAssertion(Assertion assertion) { 356 public void checkAssertion(Assertion assertion) {
356 int argumentCount = assertion.getArguments().size(); 357 var relation = assertion.getRelation();
357 if (!(assertion.getValue() instanceof LogicConstant)) { 358 if (relation instanceof DatatypeDeclaration) {
358 var message = "Assertion value must be one of 'true', 'false', 'unknown', or 'error'."; 359 var message = "Assertions for data types are not supported.";
359 acceptError(message, assertion, ProblemPackage.Literals.ASSERTION__VALUE, 0, INVALID_VALUE_ISSUE); 360 acceptError(message, assertion, ProblemPackage.Literals.ASSERTION__RELATION, 0,
361 UNSUPPORTED_ASSERTION_ISSUE);
362 return;
360 } 363 }
364 int argumentCount = assertion.getArguments().size();
361 checkArity(assertion, ProblemPackage.Literals.ASSERTION__RELATION, argumentCount); 365 checkArity(assertion, ProblemPackage.Literals.ASSERTION__RELATION, argumentCount);
362 } 366 }
363 367
@@ -376,7 +380,7 @@ public class ProblemValidator extends AbstractProblemValidator {
376 // Feature does not point to a {@link Relation}, we are probably already emitting another error. 380 // Feature does not point to a {@link Relation}, we are probably already emitting another error.
377 return; 381 return;
378 } 382 }
379 int arity = ProblemUtil.getArity(relation); 383 int arity = signatureProvider.getArity(relation);
380 if (arity == expectedArity) { 384 if (arity == expectedArity) {
381 return; 385 return;
382 } 386 }
@@ -387,11 +391,7 @@ public class ProblemValidator extends AbstractProblemValidator {
387 391
388 @Check 392 @Check
389 public void checkMultiObjectAssertion(Assertion assertion) { 393 public void checkMultiObjectAssertion(Assertion assertion) {
390 var builtinSymbolsOption = desugarer.getBuiltinSymbols(assertion); 394 var builtinSymbols = importAdapterProvider.getBuiltinSymbols(assertion);
391 if (builtinSymbolsOption.isEmpty()) {
392 return;
393 }
394 var builtinSymbols = builtinSymbolsOption.get();
395 var relation = assertion.getRelation(); 395 var relation = assertion.getRelation();
396 boolean isExists = builtinSymbols.exists().equals(relation); 396 boolean isExists = builtinSymbols.exists().equals(relation);
397 boolean isEquals = builtinSymbols.equals().equals(relation); 397 boolean isEquals = builtinSymbols.equals().equals(relation);
@@ -524,5 +524,37 @@ public class ProblemValidator extends AbstractProblemValidator {
524 acceptError(message, variableOrNodeExpr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, 524 acceptError(message, variableOrNodeExpr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE,
525 0, INVALID_ASSIGNMENT_ISSUE); 525 0, INVALID_ASSIGNMENT_ISSUE);
526 } 526 }
527 if (!(assignmentExpr.eContainer() instanceof Conjunction)) {
528 var message = "Assignments may only appear as top-level expressions.";
529 acceptError(message, assignmentExpr, null, 0, INVALID_ASSIGNMENT_ISSUE);
530 }
531 }
532
533 @Check
534 private void checkInfiniteConstant(InfiniteConstant infiniteConstant) {
535 if (!(infiniteConstant.eContainer() instanceof RangeExpr)) {
536 var message = "Negative and positive infinity literals may only appear in '..' range expressions.";
537 acceptError(message, infiniteConstant, null, 0, TYPE_ERROR);
538 }
539 }
540
541 @Check
542 private void checkTypes(Problem problem) {
543 var diagnostics = typeAnalyzer.getOrComputeTypes(problem).getDiagnostics();
544 for (var diagnostic : diagnostics) {
545 switch (diagnostic.getSeverity()) {
546 case Diagnostic.INFO -> info(diagnostic.getMessage(), diagnostic.getSourceEObject(),
547 diagnostic.getFeature(), diagnostic.getIndex(), diagnostic.getIssueCode(),
548 diagnostic.getIssueData());
549 case Diagnostic.WARNING -> warning(diagnostic.getMessage(), diagnostic.getSourceEObject(),
550 diagnostic.getFeature(), diagnostic.getIndex(), diagnostic.getIssueCode(),
551 diagnostic.getIssueData());
552 case Diagnostic.ERROR -> error(diagnostic.getMessage(), diagnostic.getSourceEObject(),
553 diagnostic.getFeature(), diagnostic.getIndex(), diagnostic.getIssueCode(),
554 diagnostic.getIssueData());
555 default -> throw new IllegalStateException("Unknown severity %s of %s"
556 .formatted(diagnostic.getSeverity(), diagnostic));
557 }
558 }
527 } 559 }
528} 560}