+11
-148
src/main/java/com/ezylang/evalex/Expression.java
+11
-148
src/main/java/com/ezylang/evalex/Expression.java
···
18
18
import com.ezylang.evalex.config.ExpressionConfiguration;
19
19
import com.ezylang.evalex.data.DataAccessorIfc;
20
20
import com.ezylang.evalex.data.EvaluationValue;
21
-
import com.ezylang.evalex.data.IndexedAccessor;
22
-
import com.ezylang.evalex.data.types.ExpressionNodeValue;
23
21
import com.ezylang.evalex.data.types.NumberValue;
24
-
import com.ezylang.evalex.functions.FunctionIfc;
25
-
import com.ezylang.evalex.operators.OperatorIfc;
26
22
import com.ezylang.evalex.parser.*;
27
23
import java.math.BigDecimal;
28
-
import java.util.*;
29
24
import java.util.function.UnaryOperator;
30
25
import lombok.Getter;
31
26
import org.jetbrains.annotations.Nullable;
···
41
36
private final ExpressionConfiguration configuration;
42
37
private final String expressionString;
43
38
private final @Nullable DataAccessorIfc dataAccessor;
44
-
private final ASTNode abstractSyntaxTree;
39
+
private final Solvable solvable;
45
40
46
41
/**
47
42
* Creates a new expression with a custom configuration. The expression is not parsed until it is
···
50
45
* @param expressionString A string holding an expression.
51
46
*/
52
47
public Expression(
53
-
String expressionString, ASTNode abstractSyntaxTree, ExpressionConfiguration configuration) {
48
+
String expressionString, Solvable solvable, ExpressionConfiguration configuration) {
54
49
this.expressionString = expressionString;
55
-
this.abstractSyntaxTree = abstractSyntaxTree;
50
+
this.solvable = solvable;
56
51
this.configuration = configuration;
57
52
this.dataAccessor = configuration.getDataAccessorSupplier().get();
58
53
}
···
69
64
* @throws EvaluationException If there were problems while evaluating the expression.
70
65
*/
71
66
public EvaluationValue evaluate(EvaluationContext context) throws EvaluationException {
72
-
EvaluationValue result = evaluateSubtree(getAbstractSyntaxTree(), context);
67
+
EvaluationValue result = evaluateSubtree(this.getSolvable(), context);
73
68
if (result.isNumberValue()) {
74
69
BigDecimal bigDecimal = result.getNumberValue();
75
70
if (configuration.getDecimalPlacesResult()
···
88
83
}
89
84
90
85
public EvaluationValue evaluateSubtree(
91
-
ASTNode startNode, UnaryOperator<EvaluationContext.EvaluationContextBuilder> builder)
86
+
Solvable solvable, UnaryOperator<EvaluationContext.EvaluationContextBuilder> builder)
92
87
throws EvaluationException {
93
-
return this.evaluateSubtree(startNode, builder.apply(EvaluationContext.builder(this)).build());
88
+
return this.evaluateSubtree(solvable, builder.apply(EvaluationContext.builder(this)).build());
94
89
}
95
90
96
91
/**
97
92
* Evaluates only a subtree of the abstract syntax tree.
98
93
*
99
-
* @param startNode The {@link ASTNode} to start evaluation from.
94
+
* @param solvable The {@link Solvable} to start evaluation from.
100
95
* @return The evaluation result value.
101
96
* @throws EvaluationException If there were problems while evaluating the expression.
102
97
*/
103
-
public EvaluationValue evaluateSubtree(ASTNode startNode, EvaluationContext context)
98
+
public EvaluationValue evaluateSubtree(Solvable solvable, EvaluationContext context)
104
99
throws EvaluationException {
105
-
if (startNode instanceof InlinedASTNode)
106
-
return tryRoundValue(((InlinedASTNode) startNode).value()); // All primitives go here.
107
-
108
-
Token token = startNode.getToken();
109
-
return tryRoundValue(
110
-
switch (token.getType()) {
111
-
case VARIABLE_OR_CONSTANT -> {
112
-
var result = getVariableOrConstant(token, context);
113
-
if (result.isExpressionNode()) {
114
-
yield evaluateSubtree(result.getExpressionNode(), context);
115
-
}
116
-
yield result;
117
-
}
118
-
case PREFIX_OPERATOR, POSTFIX_OPERATOR -> token
119
-
.getOperatorDefinition()
120
-
.evaluate(context, token, evaluateSubtree(startNode.getParameters()[0], context));
121
-
case INFIX_OPERATOR -> evaluateInfixOperator(startNode, token, context);
122
-
case ARRAY_INDEX -> evaluateArrayIndex(startNode, context);
123
-
case STRUCTURE_SEPARATOR -> evaluateStructureSeparator(startNode, context);
124
-
case FUNCTION -> evaluateFunction(startNode, token, context);
125
-
default -> throw new EvaluationException(token, "Unexpected evaluation token: " + token);
126
-
});
100
+
return solvable.solve(context);
127
101
}
128
102
129
103
public EvaluationValue tryRoundValue(EvaluationValue value) {
···
136
110
return value;
137
111
}
138
112
139
-
private EvaluationValue getVariableOrConstant(Token token, EvaluationContext context)
113
+
public EvaluationValue getVariableOrConstant(Token token, EvaluationContext context)
140
114
throws EvaluationException {
141
115
EvaluationValue result = context.parameters().get(token.getValue());
142
116
if (result == null) {
···
152
126
return result;
153
127
}
154
128
155
-
private EvaluationValue evaluateFunction(
156
-
ASTNode startNode, Token token, EvaluationContext context) throws EvaluationException {
157
-
EvaluationValue[] parameters;
158
-
159
-
if (startNode.getParameters().length == 0) {
160
-
parameters = EvaluationValue.EMPTY;
161
-
} else {
162
-
parameters = new EvaluationValue[startNode.getParameters().length];
163
-
for (int i = 0; i < startNode.getParameters().length; i++) {
164
-
if (token.getFunctionDefinition().isParameterLazy(i)) {
165
-
parameters[i] = ExpressionNodeValue.of(startNode.getParameters()[i]);
166
-
} else {
167
-
parameters[i] = evaluateSubtree(startNode.getParameters()[i], context);
168
-
}
169
-
}
170
-
}
171
-
172
-
FunctionIfc function = token.getFunctionDefinition();
173
-
function.validatePreEvaluation(token, parameters);
174
-
return function.evaluate(context, token, parameters);
175
-
}
176
-
177
-
private EvaluationValue evaluateArrayIndex(ASTNode startNode, EvaluationContext context)
178
-
throws EvaluationException {
179
-
EvaluationValue array = evaluateSubtree(startNode.getParameters()[0], context);
180
-
EvaluationValue index = evaluateSubtree(startNode.getParameters()[1], context);
181
-
182
-
if (array instanceof IndexedAccessor accessor && index.isNumberValue()) {
183
-
var result = accessor.getIndexedData(index.getNumberValue(), startNode.getToken(), context);
184
-
if (result == null)
185
-
throw new EvaluationException(
186
-
startNode.getToken(),
187
-
String.format(
188
-
"Index %s out of bounds for %s %s",
189
-
index.getNumberValue(), array.getClass().getSimpleName(), array.getValue()));
190
-
return result;
191
-
}
192
-
throw EvaluationException.ofUnsupportedDataTypeInOperation(startNode.getToken());
193
-
}
194
-
195
-
private EvaluationValue evaluateStructureSeparator(ASTNode startNode, EvaluationContext context)
196
-
throws EvaluationException {
197
-
EvaluationValue structure = evaluateSubtree(startNode.getParameters()[0], context);
198
-
Token nameToken = startNode.getParameters()[1].getToken();
199
-
String name = nameToken.getValue();
200
-
201
-
if (structure instanceof DataAccessorIfc accessor) {
202
-
var result = accessor.getVariableData(name, nameToken, context);
203
-
if (result == null)
204
-
throw new EvaluationException(
205
-
nameToken,
206
-
String.format(
207
-
"Field '%s' not found in %s", name, structure.getClass().getSimpleName()));
208
-
return result;
209
-
}
210
-
throw EvaluationException.ofUnsupportedDataTypeInOperation(startNode.getToken());
211
-
}
212
-
213
-
private EvaluationValue evaluateInfixOperator(
214
-
ASTNode startNode, Token token, EvaluationContext context) throws EvaluationException {
215
-
EvaluationValue left;
216
-
EvaluationValue right;
217
-
218
-
OperatorIfc op = token.getOperatorDefinition();
219
-
if (op.isOperandLazy()) {
220
-
left = ExpressionNodeValue.of(startNode.getParameters()[0]);
221
-
right = ExpressionNodeValue.of(startNode.getParameters()[1]);
222
-
} else {
223
-
left = evaluateSubtree(startNode.getParameters()[0], context);
224
-
right = evaluateSubtree(startNode.getParameters()[1], context);
225
-
}
226
-
return op.evaluate(context, token, left, right);
227
-
}
228
-
229
129
/**
230
130
* Rounds the given value.
231
131
*
···
244
144
* @return The copied Expression instance.
245
145
*/
246
146
public Expression copy() {
247
-
return new Expression(getExpressionString(), getAbstractSyntaxTree(), getConfiguration());
147
+
return new Expression(getExpressionString(), getSolvable(), getConfiguration());
248
148
}
249
149
250
150
/**
···
267
167
*/
268
168
public EvaluationValue convertValue(Object value) {
269
169
return EvaluationValue.of(value, configuration);
270
-
}
271
-
272
-
/**
273
-
* Returns the list of all nodes of the abstract syntax tree.
274
-
*
275
-
* @return The list of all nodes in the parsed expression.
276
-
*/
277
-
public List<ASTNode> getAllASTNodes() {
278
-
return getAllASTNodesForNode(getAbstractSyntaxTree());
279
-
}
280
-
281
-
private List<ASTNode> getAllASTNodesForNode(ASTNode node) {
282
-
List<ASTNode> nodes = new ArrayList<>();
283
-
nodes.add(node);
284
-
for (ASTNode child : node.getParameters()) {
285
-
nodes.addAll(getAllASTNodesForNode(child));
286
-
}
287
-
return nodes;
288
-
}
289
-
290
-
/**
291
-
* Returns all variables that are used i the expression, excluding the constants like e.g. <code>
292
-
* PI</code> or <code>TRUE</code> and <code>FALSE</code>.
293
-
*
294
-
* @return All used variables excluding constants.
295
-
*/
296
-
public Set<String> getUsedVariables() {
297
-
Set<String> variables = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
298
-
299
-
for (ASTNode node : getAllASTNodes()) {
300
-
if (node.getToken().getType() == Token.TokenType.VARIABLE_OR_CONSTANT
301
-
&& !configuration.getConstants().containsKey(node.getToken().getValue())) {
302
-
variables.add(node.getToken().getValue());
303
-
}
304
-
}
305
-
306
-
return variables;
307
170
}
308
171
}
+1
-4
src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java
+1
-4
src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java
···
15
15
*/
16
16
package com.ezylang.evalex.config;
17
17
18
-
import com.ezylang.evalex.Expression;
19
18
import com.ezylang.evalex.data.DataAccessorIfc;
20
19
import com.ezylang.evalex.data.EvaluationValue;
21
20
import com.ezylang.evalex.data.conversion.DefaultEvaluationValueConverter;
···
32
31
import com.ezylang.evalex.operators.OperatorIfc;
33
32
import com.ezylang.evalex.operators.arithmetic.*;
34
33
import com.ezylang.evalex.operators.booleans.*;
35
-
import com.ezylang.evalex.parser.ASTNode;
36
34
import com.ezylang.evalex.parser.ExpressionParser;
37
35
import java.math.BigDecimal;
38
36
import java.math.MathContext;
···
123
121
/**
124
122
* Default constants will be added automatically to each expression and can be used in expression
125
123
* evaluation. <br>
126
-
* It is assumed that constant will <b>never</b> change. {@link
127
-
* ExpressionParser#inlineASTNode(Expression, ASTNode)} relies on this assumption!
124
+
* It is assumed that constant will <b>never</b> change.
128
125
*/
129
126
@Builder.Default
130
127
private final Map<String, EvaluationValue> constants =
+6
-10
src/main/java/com/ezylang/evalex/data/EvaluationValue.java
+6
-10
src/main/java/com/ezylang/evalex/data/EvaluationValue.java
···
16
16
package com.ezylang.evalex.data;
17
17
18
18
import com.ezylang.evalex.config.ExpressionConfiguration;
19
-
import com.ezylang.evalex.data.types.ExpressionNodeValue;
20
-
import com.ezylang.evalex.parser.ASTNode;
19
+
import com.ezylang.evalex.data.types.SolvableValue;
20
+
import com.ezylang.evalex.parser.Solvable;
21
21
import java.math.BigDecimal;
22
22
import java.time.Duration;
23
23
import java.time.Instant;
···
77
77
return false;
78
78
}
79
79
80
-
default boolean isExpressionNode() {
80
+
default boolean isSolvable() {
81
81
return false;
82
82
}
83
83
···
186
186
return Collections.emptyMap();
187
187
}
188
188
189
-
default DataAccessorIfc getDataAccessorValue() {
190
-
return null;
191
-
}
192
-
193
189
/**
194
-
* Gets the expression node, if this value is of type {@link ExpressionNodeValue}.
190
+
* Gets the {@link Solvable}, if this value is of type {@link SolvableValue}.
195
191
*
196
-
* @return The expression node, or null for any other data type.
192
+
* @return The solvable, or null for any other data type.
197
193
*/
198
-
default ASTNode getExpressionNode() {
194
+
default Solvable getSolvable() {
199
195
return null;
200
196
}
201
197
+4
-7
src/main/java/com/ezylang/evalex/data/conversion/DefaultEvaluationValueConverter.java
+4
-7
src/main/java/com/ezylang/evalex/data/conversion/DefaultEvaluationValueConverter.java
···
18
18
import com.ezylang.evalex.config.ExpressionConfiguration;
19
19
import com.ezylang.evalex.data.EvaluationValue;
20
20
import com.ezylang.evalex.data.types.NullValue;
21
-
import com.ezylang.evalex.parser.ASTNode;
22
-
import com.ezylang.evalex.parser.InlinedASTNode;
21
+
import com.ezylang.evalex.parser.Solvable;
23
22
import java.math.BigDecimal;
24
23
import java.time.Duration;
25
24
import java.time.Instant;
···
65
64
public static final BooleanConverter BOOLEAN_CONVERTER = new BooleanConverter();
66
65
public static final DateTimeConverter DATE_TIME_CONVERTER = new DateTimeConverter();
67
66
public static final DurationConverter DURATION_CONVERTER = new DurationConverter();
68
-
public static final ExpressionNodeConverter EXPRESSION_NODE_CONVERTER =
69
-
new ExpressionNodeConverter();
67
+
public static final SolvableConverter SOLVABLE_CONVERTER = new SolvableConverter();
70
68
public static final ArrayConverter ARRAY_CONVERTER = new ArrayConverter();
71
69
public static final StructureConverter STRUCTURE_CONVERTER = new StructureConverter();
72
70
···
77
75
BOOLEAN_CONVERTER,
78
76
DATE_TIME_CONVERTER,
79
77
DURATION_CONVERTER,
80
-
EXPRESSION_NODE_CONVERTER,
78
+
SOLVABLE_CONVERTER,
81
79
ARRAY_CONVERTER,
82
80
STRUCTURE_CONVERTER);
83
81
···
101
99
102
100
fastPath.put(Duration.class, DURATION_CONVERTER);
103
101
104
-
fastPath.put(ASTNode.class, EXPRESSION_NODE_CONVERTER);
105
-
fastPath.put(InlinedASTNode.class, EXPRESSION_NODE_CONVERTER);
102
+
fastPath.put(Solvable.class, SOLVABLE_CONVERTER);
106
103
107
104
fastPath.put(List.class, ARRAY_CONVERTER);
108
105
fastPath.put(Object[].class, ARRAY_CONVERTER);
+6
-6
src/main/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverter.java
src/main/java/com/ezylang/evalex/data/conversion/SolvableConverter.java
+6
-6
src/main/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverter.java
src/main/java/com/ezylang/evalex/data/conversion/SolvableConverter.java
···
17
17
18
18
import com.ezylang.evalex.config.ExpressionConfiguration;
19
19
import com.ezylang.evalex.data.EvaluationValue;
20
-
import com.ezylang.evalex.data.types.ExpressionNodeValue;
21
-
import com.ezylang.evalex.parser.ASTNode;
20
+
import com.ezylang.evalex.data.types.SolvableValue;
21
+
import com.ezylang.evalex.parser.Solvable;
22
22
23
-
/** Converter to convert to the EXPRESSION_NODE data type. */
24
-
public class ExpressionNodeConverter implements ConverterIfc {
23
+
/** Converter to convert to the {@link SolvableValue} data type. */
24
+
public class SolvableConverter implements ConverterIfc {
25
25
@Override
26
26
public EvaluationValue convert(Object object, ExpressionConfiguration configuration) {
27
-
return ExpressionNodeValue.of((ASTNode) object);
27
+
return SolvableValue.of((Solvable) object);
28
28
}
29
29
30
30
@Override
31
31
public boolean canConvert(Object object) {
32
-
return object instanceof ASTNode;
32
+
return object instanceof Solvable;
33
33
}
34
34
}
+7
-7
src/main/java/com/ezylang/evalex/data/types/ExpressionNodeValue.java
src/main/java/com/ezylang/evalex/data/types/SolvableValue.java
+7
-7
src/main/java/com/ezylang/evalex/data/types/ExpressionNodeValue.java
src/main/java/com/ezylang/evalex/data/types/SolvableValue.java
···
16
16
package com.ezylang.evalex.data.types;
17
17
18
18
import com.ezylang.evalex.data.EvaluationValue;
19
-
import com.ezylang.evalex.parser.ASTNode;
19
+
import com.ezylang.evalex.parser.Solvable;
20
20
import lombok.*;
21
21
22
22
@ToString()
23
23
@EqualsAndHashCode(callSuper = false)
24
24
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
25
-
public final class ExpressionNodeValue implements EvaluationValue {
25
+
public class SolvableValue implements EvaluationValue {
26
26
27
-
private final ASTNode value;
27
+
private final Solvable value;
28
28
29
-
public static ExpressionNodeValue of(@NonNull ASTNode node) {
30
-
return new ExpressionNodeValue(node);
29
+
public static SolvableValue of(@NonNull Solvable node) {
30
+
return new SolvableValue(node);
31
31
}
32
32
33
33
@Override
···
36
36
}
37
37
38
38
@Override
39
-
public boolean isExpressionNode() {
39
+
public boolean isSolvable() {
40
40
return true;
41
41
}
42
42
43
43
@Override
44
-
public ASTNode getExpressionNode() {
44
+
public Solvable getSolvable() {
45
45
return value;
46
46
}
47
47
}
-30
src/main/java/com/ezylang/evalex/functions/FunctionIfc.java
-30
src/main/java/com/ezylang/evalex/functions/FunctionIfc.java
···
17
17
18
18
import com.ezylang.evalex.EvaluationContext;
19
19
import com.ezylang.evalex.EvaluationException;
20
-
import com.ezylang.evalex.Expression;
21
20
import com.ezylang.evalex.data.EvaluationValue;
22
-
import com.ezylang.evalex.data.types.ExpressionNodeValue;
23
-
import com.ezylang.evalex.parser.ASTNode;
24
-
import com.ezylang.evalex.parser.InlinedASTNode;
25
21
import com.ezylang.evalex.parser.Token;
26
22
import java.util.List;
27
-
import org.jetbrains.annotations.Nullable;
28
23
29
24
/**
30
25
* Interface that is required for all functions in a function dictionary for evaluation of
···
92
87
default int getCountOfNonVarArgParameters() {
93
88
int numOfParameters = getFunctionParameterDefinitions().size();
94
89
return hasVarArgs() ? numOfParameters - 1 : numOfParameters;
95
-
}
96
-
97
-
default boolean forceInline() {
98
-
return false;
99
-
}
100
-
101
-
default @Nullable EvaluationValue inlineFunction(
102
-
Expression expression, Token token, ASTNode... parameters) throws EvaluationException {
103
-
104
-
EvaluationValue[] parsed;
105
-
if (parameters.length == 0) {
106
-
parsed = EvaluationValue.EMPTY;
107
-
} else {
108
-
parsed = new EvaluationValue[parameters.length];
109
-
for (int i = 0; i < parameters.length; i++) {
110
-
if (token.getFunctionDefinition().isParameterLazy(i)) {
111
-
parsed[i] = ExpressionNodeValue.of(parameters[i]);
112
-
} else {
113
-
parsed[i] = ((InlinedASTNode) parameters[i]).value();
114
-
}
115
-
}
116
-
}
117
-
118
-
this.validatePreEvaluation(token, parsed);
119
-
return this.evaluate(EvaluationContext.builder(expression).build(), token, parsed);
120
90
}
121
91
}
+2
-2
src/main/java/com/ezylang/evalex/functions/basic/IfFunction.java
+2
-2
src/main/java/com/ezylang/evalex/functions/basic/IfFunction.java
···
37
37
EvaluationContext context, Token functionToken, EvaluationValue... parameterValues)
38
38
throws EvaluationException {
39
39
if (Boolean.TRUE.equals(parameterValues[0].getBooleanValue())) {
40
-
return context.expression().evaluateSubtree(parameterValues[1].getExpressionNode(), context);
40
+
return context.expression().evaluateSubtree(parameterValues[1].getSolvable(), context);
41
41
} else {
42
-
return context.expression().evaluateSubtree(parameterValues[2].getExpressionNode(), context);
42
+
return context.expression().evaluateSubtree(parameterValues[2].getSolvable(), context);
43
43
}
44
44
}
45
45
}
-9
src/main/java/com/ezylang/evalex/functions/basic/RandomFunction.java
-9
src/main/java/com/ezylang/evalex/functions/basic/RandomFunction.java
···
16
16
package com.ezylang.evalex.functions.basic;
17
17
18
18
import com.ezylang.evalex.EvaluationContext;
19
-
import com.ezylang.evalex.Expression;
20
19
import com.ezylang.evalex.data.EvaluationValue;
21
20
import com.ezylang.evalex.functions.AbstractFunction;
22
-
import com.ezylang.evalex.parser.ASTNode;
23
21
import com.ezylang.evalex.parser.Token;
24
22
import java.security.SecureRandom;
25
-
import org.jetbrains.annotations.Nullable;
26
23
27
24
/** Random function produces a random value between 0 and 1. */
28
25
public class RandomFunction extends AbstractFunction {
···
34
31
SecureRandom secureRandom = new SecureRandom();
35
32
36
33
return context.expression().convertDoubleValue(secureRandom.nextDouble());
37
-
}
38
-
39
-
@Override
40
-
public @Nullable EvaluationValue inlineFunction(
41
-
Expression expression, Token token, ASTNode... parameters) {
42
-
return null;
43
34
}
44
35
}
+2
-2
src/main/java/com/ezylang/evalex/functions/basic/SwitchFunction.java
+2
-2
src/main/java/com/ezylang/evalex/functions/basic/SwitchFunction.java
···
98
98
private EvaluationValue evaluateParameter(
99
99
Expression expression, EvaluationValue parameter, EvaluationContext context)
100
100
throws EvaluationException {
101
-
return parameter.isExpressionNode()
102
-
? expression.evaluateSubtree(parameter.getExpressionNode(), context)
101
+
return parameter.isSolvable()
102
+
? expression.evaluateSubtree(parameter.getSolvable(), context)
103
103
: parameter;
104
104
}
105
105
}
-9
src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java
-9
src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java
···
16
16
package com.ezylang.evalex.functions.datetime;
17
17
18
18
import com.ezylang.evalex.EvaluationContext;
19
-
import com.ezylang.evalex.Expression;
20
19
import com.ezylang.evalex.data.EvaluationValue;
21
20
import com.ezylang.evalex.data.types.DateTimeValue;
22
21
import com.ezylang.evalex.functions.AbstractFunction;
23
-
import com.ezylang.evalex.parser.ASTNode;
24
22
import com.ezylang.evalex.parser.Token;
25
23
import java.time.Instant;
26
-
import org.jetbrains.annotations.Nullable;
27
24
28
25
/**
29
26
* Produces a new DATE_TIME that represents the current date and time.
···
45
42
public EvaluationValue evaluate(
46
43
EvaluationContext context, Token functionToken, EvaluationValue... parameterValues) {
47
44
return DateTimeValue.of(Instant.now());
48
-
}
49
-
50
-
@Override
51
-
public @Nullable EvaluationValue inlineFunction(
52
-
Expression expression, Token token, ASTNode... parameters) {
53
-
return null;
54
45
}
55
46
}
-8
src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java
-8
src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java
···
23
23
import com.ezylang.evalex.data.types.DateTimeValue;
24
24
import com.ezylang.evalex.functions.AbstractFunction;
25
25
import com.ezylang.evalex.functions.FunctionParameter;
26
-
import com.ezylang.evalex.parser.ASTNode;
27
26
import com.ezylang.evalex.parser.Token;
28
27
import java.time.Instant;
29
28
import java.time.LocalDate;
30
29
import java.time.ZoneId;
31
-
import org.jetbrains.annotations.Nullable;
32
30
33
31
/**
34
32
* Produces a new DATE_TIME that represents the current date, at midnight (00:00).
···
65
63
return ZoneIdConverter.convert(functionToken, parameterValues[0].getStringValue());
66
64
}
67
65
return expression.getConfiguration().getZoneId();
68
-
}
69
-
70
-
@Override
71
-
public @Nullable EvaluationValue inlineFunction(
72
-
Expression expression, Token token, ASTNode... parameters) {
73
-
return null;
74
66
}
75
67
}
-26
src/main/java/com/ezylang/evalex/operators/OperatorIfc.java
-26
src/main/java/com/ezylang/evalex/operators/OperatorIfc.java
···
17
17
18
18
import com.ezylang.evalex.EvaluationContext;
19
19
import com.ezylang.evalex.EvaluationException;
20
-
import com.ezylang.evalex.Expression;
21
20
import com.ezylang.evalex.config.ExpressionConfiguration;
22
21
import com.ezylang.evalex.data.EvaluationValue;
23
-
import com.ezylang.evalex.data.types.ExpressionNodeValue;
24
-
import com.ezylang.evalex.parser.ASTNode;
25
-
import com.ezylang.evalex.parser.InlinedASTNode;
26
22
import com.ezylang.evalex.parser.Token;
27
-
import org.jetbrains.annotations.Nullable;
28
23
29
24
/**
30
25
* Interface that is required for all operators in an operator dictionary for evaluation of
···
134
129
EvaluationValue evaluate(
135
130
EvaluationContext context, Token operatorToken, EvaluationValue... operands)
136
131
throws EvaluationException;
137
-
138
-
default boolean forceInline() {
139
-
return false;
140
-
}
141
-
142
-
default @Nullable EvaluationValue inlineOperator(
143
-
Expression expression, Token token, ASTNode... parameters) throws EvaluationException {
144
-
EvaluationValue left =
145
-
this.isOperandLazy()
146
-
? ExpressionNodeValue.of(parameters[0])
147
-
: ((InlinedASTNode) parameters[0]).value();
148
-
if (isPostfix() || isPrefix()) {
149
-
return this.evaluate(EvaluationContext.builder(expression).build(), token, left);
150
-
} else {
151
-
EvaluationValue right =
152
-
this.isOperandLazy()
153
-
? ExpressionNodeValue.of(parameters[1])
154
-
: ((InlinedASTNode) parameters[1]).value();
155
-
return this.evaluate(EvaluationContext.builder(expression).build(), token, left, right);
156
-
}
157
-
}
158
132
}
+2
-5
src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java
+2
-5
src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java
···
34
34
EvaluationContext context, Token operatorToken, EvaluationValue... operands)
35
35
throws EvaluationException {
36
36
return BooleanValue.of(
37
-
context
38
-
.expression()
39
-
.evaluateSubtree(operands[0].getExpressionNode(), context)
40
-
.getBooleanValue()
37
+
context.expression().evaluateSubtree(operands[0].getSolvable(), context).getBooleanValue()
41
38
&& context
42
39
.expression()
43
-
.evaluateSubtree(operands[1].getExpressionNode(), context)
40
+
.evaluateSubtree(operands[1].getSolvable(), context)
44
41
.getBooleanValue());
45
42
}
46
43
}
+2
-5
src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java
+2
-5
src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java
···
34
34
EvaluationContext context, Token operatorToken, EvaluationValue... operands)
35
35
throws EvaluationException {
36
36
return BooleanValue.of(
37
-
context
38
-
.expression()
39
-
.evaluateSubtree(operands[0].getExpressionNode(), context)
40
-
.getBooleanValue()
37
+
context.expression().evaluateSubtree(operands[0].getSolvable(), context).getBooleanValue()
41
38
|| context
42
39
.expression()
43
-
.evaluateSubtree(operands[1].getExpressionNode(), context)
40
+
.evaluateSubtree(operands[1].getSolvable(), context)
44
41
.getBooleanValue());
45
42
}
46
43
}
+107
-67
src/main/java/com/ezylang/evalex/parser/ExpressionParser.java
+107
-67
src/main/java/com/ezylang/evalex/parser/ExpressionParser.java
···
18
18
import com.ezylang.evalex.EvaluationException;
19
19
import com.ezylang.evalex.Expression;
20
20
import com.ezylang.evalex.config.ExpressionConfiguration;
21
+
import com.ezylang.evalex.data.DataAccessorIfc;
21
22
import com.ezylang.evalex.data.EvaluationValue;
23
+
import com.ezylang.evalex.data.IndexedAccessor;
24
+
import com.ezylang.evalex.data.types.SolvableValue;
22
25
import com.ezylang.evalex.functions.FunctionIfc;
23
26
import com.ezylang.evalex.operators.OperatorIfc;
24
-
import java.util.Arrays;
25
27
import lombok.Getter;
26
-
import org.jetbrains.annotations.NotNull;
27
28
28
29
@Getter
29
30
public final class ExpressionParser {
···
41
42
public Expression parse(String expression) throws ParseException {
42
43
return new Expression(
43
44
expression,
44
-
converter.toAbstractSyntaxTree(tokenizer.parse(expression), expression),
45
+
this.toSolvable(converter.toAbstractSyntaxTree(tokenizer.parse(expression), expression)),
45
46
configuration);
46
47
}
47
48
48
-
public Expression parseAndInline(String expression) throws ParseException, EvaluationException {
49
-
Expression result = this.parse(expression);
50
-
return new Expression(
51
-
expression, this.inlineASTNode(result, result.getAbstractSyntaxTree()), configuration);
49
+
public Solvable toSolvable(ASTNode node) {
50
+
if (node instanceof InlinedASTNode inlined)
51
+
return context -> context.expression().tryRoundValue(inlined.value());
52
+
53
+
Token token = node.getToken();
54
+
Solvable value =
55
+
switch (token.getType()) {
56
+
case VARIABLE_OR_CONSTANT -> context -> {
57
+
var result = context.expression().getVariableOrConstant(token, context);
58
+
if (result.isSolvable()) return result.getSolvable().solve(context);
59
+
return result;
60
+
};
61
+
case PREFIX_OPERATOR, POSTFIX_OPERATOR -> {
62
+
OperatorIfc operator = token.getOperatorDefinition();
63
+
Solvable solvable = toSolvable(node.getParameters()[0]);
64
+
yield context -> operator.evaluate(context, token, solvable.solve(context));
65
+
}
66
+
case INFIX_OPERATOR -> infixOperatorToSolvable(node);
67
+
case ARRAY_INDEX -> arrayIndexToSolvable(node);
68
+
case STRUCTURE_SEPARATOR -> structureSeparatorToSolvable(node);
69
+
case FUNCTION -> functionToSolvable(node);
70
+
default -> throw new IllegalStateException("Unexpected evaluation token: " + token);
71
+
};
72
+
return context -> context.expression().tryRoundValue(value.solve(context));
52
73
}
53
74
54
-
public Expression inlineExpression(Expression expression) throws EvaluationException {
55
-
return new Expression(
56
-
expression.getExpressionString(),
57
-
this.inlineASTNode(expression, expression.getAbstractSyntaxTree()),
58
-
expression.getConfiguration());
75
+
private Solvable infixOperatorToSolvable(ASTNode node) {
76
+
Token token = node.getToken();
77
+
OperatorIfc operator = token.getOperatorDefinition();
78
+
79
+
Solvable left;
80
+
Solvable right;
81
+
if (operator.isOperandLazy()) {
82
+
var first = SolvableValue.of(toSolvable(node.getParameters()[0]));
83
+
var second = SolvableValue.of(toSolvable(node.getParameters()[1]));
84
+
left = context -> first;
85
+
right = context -> second;
86
+
} else {
87
+
left = toSolvable(node.getParameters()[0]);
88
+
right = toSolvable(node.getParameters()[1]);
89
+
}
90
+
return context -> operator.evaluate(context, token, left.solve(context), right.solve(context));
59
91
}
60
92
61
-
/**
62
-
* Optional operation which attempts to inline nodes with constant results.<br>
63
-
* This method attempts to inline constant variables, functions and operators.
64
-
*
65
-
* <p>If an operator cannot be inlined, it must implement {@link
66
-
* OperatorIfc#inlineOperator(Expression, Token, ASTNode...)} and return null. Same with
67
-
* functions, but {@link FunctionIfc#inlineFunction(Expression, Token, ASTNode...)}.
68
-
*
69
-
* @return New {@link InlinedASTNode} or {@link ASTNode} if inlining was unsuccessful.
70
-
*/
71
-
public @NotNull ASTNode inlineASTNode(Expression owner, ASTNode node) {
72
-
if (node instanceof InlinedASTNode) return node;
73
-
var token = node.getToken();
93
+
private Solvable arrayIndexToSolvable(ASTNode node) {
94
+
Token token = node.getToken();
74
95
75
-
if (node.getParameters().length == 0) {
76
-
if (token.getType() == Token.TokenType.VARIABLE_OR_CONSTANT) {
77
-
if (!owner.getConfiguration().isAllowOverwriteConstants()) {
78
-
EvaluationValue constant = owner.getConfiguration().getConstants().get(token.getValue());
79
-
if (constant != null) return InlinedASTNode.of(token, owner.tryRoundValue(constant));
80
-
}
81
-
} else if (token.getType() == Token.TokenType.FUNCTION
82
-
&& token.getFunctionDefinition().forceInline()) {
83
-
try {
84
-
EvaluationValue function = token.getFunctionDefinition().inlineFunction(owner, token);
85
-
if (function != null) return InlinedASTNode.of(token, owner.tryRoundValue(function));
86
-
} catch (Exception e) {
87
-
return node;
88
-
}
96
+
Solvable solvableArray = toSolvable(node.getParameters()[0]);
97
+
Solvable solvableIndex = toSolvable(node.getParameters()[1]);
98
+
99
+
return context -> {
100
+
var array = solvableArray.solve(context);
101
+
var index = solvableIndex.solve(context);
102
+
103
+
if (array instanceof IndexedAccessor accessor && index.isNumberValue()) {
104
+
var result = accessor.getIndexedData(index.getNumberValue(), token, context);
105
+
if (result == null)
106
+
throw new EvaluationException(
107
+
token,
108
+
String.format(
109
+
"Index %s out of bounds for %s %s",
110
+
index.getNumberValue(), array.getClass().getSimpleName(), array.getValue()));
111
+
return result;
89
112
}
90
-
return node;
91
-
}
113
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(token);
114
+
};
115
+
}
92
116
93
-
ASTNode[] parameters = node.getParameters();
94
-
for (int i = 0; i < node.getParameters().length; i++) {
95
-
parameters[i] = inlineASTNode(owner, parameters[i]);
96
-
}
97
-
boolean allMatch = Arrays.stream(parameters).allMatch(node1 -> node1 instanceof InlinedASTNode);
117
+
private Solvable structureSeparatorToSolvable(ASTNode startNode) {
118
+
Solvable solvableStructure = toSolvable(startNode.getParameters()[0]);
119
+
Token nameToken = startNode.getParameters()[1].getToken();
120
+
String name = nameToken.getValue();
98
121
99
-
switch (token.getType()) {
100
-
case POSTFIX_OPERATOR, PREFIX_OPERATOR, INFIX_OPERATOR -> {
101
-
var operator = token.getOperatorDefinition();
102
-
if (!allMatch && !operator.forceInline()) return node;
103
-
try {
104
-
var result = operator.inlineOperator(owner, token, parameters.clone());
105
-
if (result != null)
106
-
return InlinedASTNode.of(token, owner.tryRoundValue(result), parameters);
107
-
} catch (Exception e) {
108
-
return node;
109
-
}
122
+
return context -> {
123
+
EvaluationValue structure = solvableStructure.solve(context);
124
+
if (structure instanceof DataAccessorIfc accessor) {
125
+
var result = accessor.getVariableData(name, nameToken, context);
126
+
if (result == null)
127
+
throw new EvaluationException(
128
+
nameToken,
129
+
String.format(
130
+
"Field '%s' not found in %s", name, structure.getClass().getSimpleName()));
131
+
return result;
110
132
}
111
-
case FUNCTION -> {
112
-
var function = token.getFunctionDefinition();
113
-
if (!allMatch && !function.forceInline()) return node;
114
-
try {
115
-
var result = function.inlineFunction(owner, token, parameters.clone());
116
-
if (result != null)
117
-
return InlinedASTNode.of(token, owner.tryRoundValue(result), parameters);
118
-
} catch (Exception e) {
119
-
return node;
133
+
throw EvaluationException.ofUnsupportedDataTypeInOperation(startNode.getToken());
134
+
};
135
+
}
136
+
137
+
private Solvable functionToSolvable(ASTNode node) {
138
+
Token token = node.getToken();
139
+
FunctionIfc function = token.getFunctionDefinition();
140
+
Solvable[] solvables;
141
+
142
+
if (node.getParameters().length == 0) {
143
+
solvables = new Solvable[0];
144
+
} else {
145
+
solvables = new Solvable[node.getParameters().length];
146
+
for (int i = 0; i < node.getParameters().length; i++) {
147
+
if (function.isParameterLazy(i)) {
148
+
var unwrapped = SolvableValue.of(toSolvable(node.getParameters()[i]));
149
+
solvables[i] = context -> unwrapped;
150
+
} else {
151
+
solvables[i] = toSolvable(node.getParameters()[i]);
120
152
}
121
153
}
122
154
}
123
-
return node;
155
+
156
+
return context -> {
157
+
EvaluationValue[] parameters = new EvaluationValue[solvables.length];
158
+
for (int i = 0; i < solvables.length; i++) {
159
+
parameters[i] = solvables[i].solve(context);
160
+
}
161
+
function.validatePreEvaluation(token, parameters);
162
+
return function.evaluate(context, token, parameters);
163
+
};
124
164
}
125
165
}
+24
src/main/java/com/ezylang/evalex/parser/Solvable.java
+24
src/main/java/com/ezylang/evalex/parser/Solvable.java
···
1
+
/*
2
+
Copyright 2012-2024 Udo Klimaschewski
3
+
4
+
Licensed under the Apache License, Version 2.0 (the "License");
5
+
you may not use this file except in compliance with the License.
6
+
You may obtain a copy of the License at
7
+
8
+
http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+
Unless required by applicable law or agreed to in writing, software
11
+
distributed under the License is distributed on an "AS IS" BASIS,
12
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
See the License for the specific language governing permissions and
14
+
limitations under the License.
15
+
*/
16
+
package com.ezylang.evalex.parser;
17
+
18
+
import com.ezylang.evalex.EvaluationContext;
19
+
import com.ezylang.evalex.EvaluationException;
20
+
import com.ezylang.evalex.data.EvaluationValue;
21
+
22
+
public interface Solvable {
23
+
EvaluationValue solve(EvaluationContext context) throws EvaluationException;
24
+
}
+2
-3
src/test/java/com/ezylang/evalex/BaseEvaluationTest.java
+2
-3
src/test/java/com/ezylang/evalex/BaseEvaluationTest.java
···
40
40
protected EvaluationValue evaluate(String expressionString)
41
41
throws EvaluationException, ParseException {
42
42
Expression expression =
43
-
TestConfigurationProvider.StandardParserWithAdditionalTestOperators.parseAndInline(
44
-
expressionString);
43
+
TestConfigurationProvider.StandardParserWithAdditionalTestOperators.parse(expressionString);
45
44
return expression.evaluate(EvaluationContext.builder(expression).build());
46
45
}
47
46
48
47
private EvaluationValue evaluate(String expressionString, ExpressionConfiguration configuration)
49
48
throws EvaluationException, ParseException {
50
49
ExpressionParser parser = new ExpressionParser(configuration);
51
-
Expression expression = parser.parseAndInline(expressionString);
50
+
Expression expression = parser.parse(expressionString);
52
51
53
52
return expression.evaluate(EvaluationContext.builder(expression).build());
54
53
}
+2
-3
src/test/java/com/ezylang/evalex/ExpressionEvaluationExceptionsTest.java
+2
-3
src/test/java/com/ezylang/evalex/ExpressionEvaluationExceptionsTest.java
···
22
22
import com.ezylang.evalex.parser.ParseException;
23
23
import com.ezylang.evalex.parser.Token;
24
24
import com.ezylang.evalex.parser.Token.TokenType;
25
-
import java.util.function.UnaryOperator;
26
25
import org.junit.jupiter.api.Test;
27
26
28
27
class ExpressionEvaluationExceptionsTest {
···
34
33
assertThatThrownBy(
35
34
() -> {
36
35
ASTNode node = ASTNode.of(new Token(1, "(", TokenType.BRACE_OPEN));
37
-
expression.evaluateSubtree(node, UnaryOperator.identity());
36
+
ExpressionConfiguration.defaultExpressionParser().toSolvable(node);
38
37
})
39
-
.isInstanceOf(EvaluationException.class)
38
+
.isInstanceOf(IllegalStateException.class)
40
39
.hasMessage(
41
40
"Unexpected evaluation token: Token(startPosition=1, value=(, type=BRACE_OPEN)");
42
41
}
-87
src/test/java/com/ezylang/evalex/ExpressionInlineTest.java
-87
src/test/java/com/ezylang/evalex/ExpressionInlineTest.java
···
1
-
/*
2
-
Copyright 2012-2024 Udo Klimaschewski
3
-
4
-
Licensed under the Apache License, Version 2.0 (the "License");
5
-
you may not use this file except in compliance with the License.
6
-
You may obtain a copy of the License at
7
-
8
-
http://www.apache.org/licenses/LICENSE-2.0
9
-
10
-
Unless required by applicable law or agreed to in writing, software
11
-
distributed under the License is distributed on an "AS IS" BASIS,
12
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
-
See the License for the specific language governing permissions and
14
-
limitations under the License.
15
-
*/
16
-
package com.ezylang.evalex;
17
-
18
-
import static org.assertj.core.api.Assertions.assertThat;
19
-
20
-
import com.ezylang.evalex.parser.InlinedASTNode;
21
-
import com.ezylang.evalex.parser.ParseException;
22
-
import java.math.BigDecimal;
23
-
import java.util.function.UnaryOperator;
24
-
import org.junit.jupiter.api.Test;
25
-
26
-
public class ExpressionInlineTest extends BaseExpressionEvaluatorTest {
27
-
28
-
@Test
29
-
public void testInlinedExpression() throws ParseException, EvaluationException {
30
-
Expression expression = parser.inlineExpression(createExpression("2 + 2"));
31
-
32
-
assertThat(expression.evaluate(UnaryOperator.identity()).getStringValue())
33
-
.isEqualTo(BigDecimal.valueOf(4).toPlainString());
34
-
assertThat(expression.getAbstractSyntaxTree()).isInstanceOf(InlinedASTNode.class);
35
-
}
36
-
37
-
@Test
38
-
public void testInlinedConstant() throws ParseException, EvaluationException {
39
-
Expression expression = parser.inlineExpression(createExpression("2 + PI"));
40
-
41
-
assertThat(expression.evaluate(UnaryOperator.identity()).getStringValue())
42
-
.isEqualTo(
43
-
BigDecimal.valueOf(2)
44
-
.add(
45
-
new BigDecimal(
46
-
"3.1415926535897932384626433832795028841971693993751058209749445923078"))
47
-
.toPlainString());
48
-
assertThat(expression.getAbstractSyntaxTree()).isInstanceOf(InlinedASTNode.class);
49
-
}
50
-
51
-
@Test
52
-
public void testNotInlinedParameter() throws ParseException, EvaluationException {
53
-
Expression expression = parser.inlineExpression(createExpression("cheese / 2"));
54
-
55
-
assertThat(expression.evaluate(builder -> builder.parameter("cheese", 22)).getStringValue())
56
-
.isEqualTo(
57
-
BigDecimal.valueOf(22)
58
-
.divide(BigDecimal.valueOf(2), configuration.getMathContext())
59
-
.toPlainString());
60
-
assertThat(expression.getAbstractSyntaxTree()).isNotInstanceOf(InlinedASTNode.class);
61
-
}
62
-
63
-
@Test
64
-
public void testFunctionInlined() throws ParseException, EvaluationException {
65
-
Expression expression = parser.inlineExpression(createExpression("SUM(2, 4, 6, 7)"));
66
-
67
-
assertThat(expression.evaluate(UnaryOperator.identity()).getStringValue())
68
-
.isEqualTo(BigDecimal.valueOf(2 + 4 + 6 + 7).toPlainString());
69
-
assertThat(expression.getAbstractSyntaxTree()).isInstanceOf(InlinedASTNode.class);
70
-
}
71
-
72
-
@Test
73
-
public void testFunctionNotInlined() throws ParseException, EvaluationException {
74
-
Expression expression = parser.inlineExpression(createExpression("SUM(2, a, 6, 7)"));
75
-
76
-
assertThat(expression.evaluate(builder -> builder.parameter("a", 5)).getStringValue())
77
-
.isEqualTo(BigDecimal.valueOf(2 + 5 + 6 + 7).toPlainString());
78
-
assertThat(expression.getAbstractSyntaxTree()).isNotInstanceOf(InlinedASTNode.class);
79
-
}
80
-
81
-
@Test
82
-
public void testRandomNotInlined() throws ParseException, EvaluationException {
83
-
Expression expression = parser.inlineExpression(createExpression("RANDOM()"));
84
-
85
-
assertThat(expression.getAbstractSyntaxTree()).isNotInstanceOf(InlinedASTNode.class);
86
-
}
87
-
}
+3
-47
src/test/java/com/ezylang/evalex/ExpressionTest.java
+3
-47
src/test/java/com/ezylang/evalex/ExpressionTest.java
···
22
22
import com.ezylang.evalex.data.DataAccessorIfc;
23
23
import com.ezylang.evalex.data.EvaluationValue;
24
24
import com.ezylang.evalex.data.types.StringValue;
25
-
import com.ezylang.evalex.parser.ASTNode;
26
-
import com.ezylang.evalex.parser.ExpressionParser;
27
-
import com.ezylang.evalex.parser.ParseException;
28
-
import com.ezylang.evalex.parser.Token;
25
+
import com.ezylang.evalex.parser.*;
29
26
import java.math.MathContext;
30
27
import java.util.HashMap;
31
-
import java.util.List;
32
28
import java.util.Map;
33
29
import java.util.function.Supplier;
34
30
import org.junit.jupiter.api.Test;
···
66
62
@Test
67
63
void testExpressionNode() throws ParseException, EvaluationException {
68
64
Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("a*b");
69
-
ASTNode subExpression =
70
-
ExpressionConfiguration.defaultExpressionParser()
71
-
.parseAndInline("4+3")
72
-
.getAbstractSyntaxTree();
65
+
Solvable subExpression =
66
+
ExpressionConfiguration.defaultExpressionParser().parse("4+3").getSolvable();
73
67
74
68
EvaluationValue result =
75
69
expression.evaluate(builder -> builder.parameter("a", 2).parameter("b", subExpression));
···
176
170
.parse("1");
177
171
assertThat(limitedMathContextExpression.convertDoubleValue(1.6789).getNumberValue())
178
172
.isEqualByComparingTo("1.68");
179
-
}
180
-
181
-
@Test
182
-
void testGetAllASTNodes() throws ParseException {
183
-
Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("1+2/3");
184
-
List<ASTNode> nodes = expression.getAllASTNodes();
185
-
assertThat(nodes.get(0).getToken().getValue()).isEqualTo("+");
186
-
assertThat(nodes.get(1).getToken().getValue()).isEqualTo("1");
187
-
assertThat(nodes.get(2).getToken().getValue()).isEqualTo("/");
188
-
assertThat(nodes.get(3).getToken().getValue()).isEqualTo("2");
189
-
assertThat(nodes.get(4).getToken().getValue()).isEqualTo("3");
190
-
}
191
-
192
-
@Test
193
-
void testGetUsedVariables() throws ParseException {
194
-
Expression expression =
195
-
ExpressionConfiguration.defaultExpressionParser().parse("a/2*PI+MIN(e,b)");
196
-
assertThat(expression.getUsedVariables()).containsExactlyInAnyOrder("a", "b");
197
-
}
198
-
199
-
@Test
200
-
void testGetUsedVariablesLongNames() throws ParseException {
201
-
Expression expression =
202
-
ExpressionConfiguration.defaultExpressionParser().parse("var1/2*PI+MIN(var2,var3)");
203
-
assertThat(expression.getUsedVariables()).containsExactlyInAnyOrder("var1", "var2", "var3");
204
-
}
205
-
206
-
@Test
207
-
void testGetUsedVariablesNoVariables() throws ParseException {
208
-
Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("1/2");
209
-
assertThat(expression.getUsedVariables()).isEmpty();
210
-
}
211
-
212
-
@Test
213
-
void testGetUsedVariablesCaseSensitivity() throws ParseException {
214
-
Expression expression =
215
-
ExpressionConfiguration.defaultExpressionParser().parse("a+B*b-A/PI*(1/2)*pi+e-E+a");
216
-
assertThat(expression.getUsedVariables()).containsExactlyInAnyOrder("a", "b");
217
173
}
218
174
219
175
@Test
+13
-19
src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
+13
-19
src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
···
16
16
package com.ezylang.evalex.data;
17
17
18
18
import static com.ezylang.evalex.config.ExpressionConfiguration.defaultConfiguration;
19
+
import static com.ezylang.evalex.config.ExpressionConfiguration.defaultExpressionParser;
19
20
import static org.assertj.core.api.Assertions.assertThat;
20
21
21
22
import com.ezylang.evalex.EvaluationException;
···
26
27
import com.ezylang.evalex.data.types.NumberValue;
27
28
import com.ezylang.evalex.parser.ASTNode;
28
29
import com.ezylang.evalex.parser.ParseException;
30
+
import com.ezylang.evalex.parser.Solvable;
29
31
import com.ezylang.evalex.parser.Token;
30
32
import com.ezylang.evalex.parser.Token.TokenType;
31
33
import java.math.BigDecimal;
···
45
47
assertThat(value.isBooleanValue()).isFalse();
46
48
assertThat(value.isStructureValue()).isFalse();
47
49
assertThat(value.isArrayValue()).isFalse();
48
-
assertThat(value.isExpressionNode()).isFalse();
50
+
assertThat(value.isSolvable()).isFalse();
49
51
assertThat(value.isNullValue()).isFalse();
50
52
assertDataIsCorrect(
51
53
value, "Hello World", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, String.class);
···
85
87
assertThat(value.isStringValue()).isFalse();
86
88
assertThat(value.isStructureValue()).isFalse();
87
89
assertThat(value.isArrayValue()).isFalse();
88
-
assertThat(value.isExpressionNode()).isFalse();
90
+
assertThat(value.isSolvable()).isFalse();
89
91
assertThat(value.isNullValue()).isFalse();
90
92
assertDataIsCorrect(
91
93
value, "true", BigDecimal.ONE, true, Instant.EPOCH, Duration.ZERO, Boolean.class);
···
399
401
assertThat(value.isBooleanValue()).isFalse();
400
402
assertThat(value.isStructureValue()).isFalse();
401
403
assertThat(value.isStringValue()).isFalse();
402
-
assertThat(value.isExpressionNode()).isFalse();
404
+
assertThat(value.isSolvable()).isFalse();
403
405
assertThat(value.isNullValue()).isFalse();
404
406
405
407
assertThat(value.getArrayValue()).hasSize(2);
···
435
437
assertThat(value.isBooleanValue()).isFalse();
436
438
assertThat(value.isStringValue()).isFalse();
437
439
assertThat(value.isArrayValue()).isFalse();
438
-
assertThat(value.isExpressionNode()).isFalse();
440
+
assertThat(value.isSolvable()).isFalse();
439
441
assertThat(value.isNullValue()).isFalse();
440
442
441
443
assertThat(value.getStructureValue()).hasSize(2);
···
461
463
@Test
462
464
void testExpressionNode() {
463
465
ASTNode node = ASTNode.of(new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT));
464
-
EvaluationValue value = EvaluationValue.of(node, defaultConfiguration());
466
+
EvaluationValue value =
467
+
EvaluationValue.of(defaultExpressionParser().toSolvable(node), defaultConfiguration());
465
468
466
-
assertThat(value.isExpressionNode()).isTrue();
469
+
assertThat(value.isSolvable()).isTrue();
467
470
assertThat(value.isNumberValue()).isFalse();
468
471
assertThat(value.isBooleanValue()).isFalse();
469
472
assertThat(value.isStructureValue()).isFalse();
···
472
475
assertThat(value.isNullValue()).isFalse();
473
476
474
477
assertDataIsCorrect(
475
-
value,
476
-
"ASTNode(parameters=[], token=Token(startPosition=1, value=a, type=VARIABLE_OR_CONSTANT))",
477
-
BigDecimal.ZERO,
478
-
false,
479
-
Instant.EPOCH,
480
-
Duration.ZERO,
481
-
ASTNode.class);
478
+
value, null, BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, Solvable.class);
482
479
}
483
480
484
481
@Test
485
482
void testExpressionNodeReturnsNull() {
486
483
EvaluationValue value = EvaluationValue.of(false, defaultConfiguration());
487
-
assertThat(value.getExpressionNode()).isNull();
484
+
assertThat(value.getSolvable()).isNull();
488
485
}
489
486
490
487
@Test
···
529
526
assertThat(value.isBooleanValue()).isFalse();
530
527
assertThat(value.isStructureValue()).isFalse();
531
528
assertThat(value.isArrayValue()).isFalse();
532
-
assertThat(value.isExpressionNode()).isFalse();
529
+
assertThat(value.isSolvable()).isFalse();
533
530
assertThat(value.isNullValue()).isTrue();
534
531
assertDataIsCorrect(value, null, null, null);
535
532
}
···
561
558
562
559
private void assertDataIsCorrect(
563
560
EvaluationValue value, String stringValue, BigDecimal numberValue, Boolean booleanValue) {
564
-
assertThat(value.getStringValue()).isEqualTo(stringValue);
561
+
if (stringValue != null) assertThat(value.getStringValue()).isEqualTo(stringValue);
565
562
assertThat(value.getNumberValue()).isEqualTo(numberValue);
566
563
assertThat(value.getBooleanValue()).isEqualTo(booleanValue);
567
564
}
···
575
572
Duration durationValue,
576
573
Class<?> valueInstance) {
577
574
assertDataIsCorrect(value, stringValue, numberValue, booleanValue);
578
-
assertThat(value.getStringValue()).isEqualTo(stringValue);
579
-
assertThat(value.getNumberValue()).isEqualTo(numberValue);
580
-
assertThat(value.getBooleanValue()).isEqualTo(booleanValue);
581
575
assertThat(value.getDateTimeValue()).isEqualTo(dateTimeValue);
582
576
assertThat(value.getDurationValue()).isEqualTo(durationValue);
583
577
assertThat(value.getValue()).isInstanceOf(valueInstance);
+8
-10
src/test/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverterTest.java
src/test/java/com/ezylang/evalex/data/conversion/SolvableConverterTest.java
+8
-10
src/test/java/com/ezylang/evalex/data/conversion/ExpressionNodeConverterTest.java
src/test/java/com/ezylang/evalex/data/conversion/SolvableConverterTest.java
···
19
19
20
20
import com.ezylang.evalex.config.ExpressionConfiguration;
21
21
import com.ezylang.evalex.data.EvaluationValue;
22
-
import com.ezylang.evalex.data.types.ExpressionNodeValue;
23
-
import com.ezylang.evalex.parser.ASTNode;
24
-
import com.ezylang.evalex.parser.Token;
22
+
import com.ezylang.evalex.data.types.SolvableValue;
23
+
import com.ezylang.evalex.data.types.StringValue;
24
+
import com.ezylang.evalex.parser.Solvable;
25
25
import java.math.BigDecimal;
26
26
import org.junit.jupiter.api.Test;
27
27
28
-
class ExpressionNodeConverterTest {
28
+
class SolvableConverterTest {
29
29
30
30
private final ExpressionConfiguration defaultConfiguration =
31
31
ExpressionConfiguration.defaultConfiguration();
32
32
33
-
private final ExpressionNodeConverter converter = new ExpressionNodeConverter();
33
+
private final SolvableConverter converter = new SolvableConverter();
34
34
35
-
private final ASTNode testNode =
36
-
ASTNode.of(new Token(1, "a", Token.TokenType.VARIABLE_OR_CONSTANT));
35
+
private final Solvable testNode = context -> StringValue.of("Hello!");
37
36
38
37
@Test
39
38
void testDuration() {
40
-
41
39
EvaluationValue converted = converter.convert(testNode, defaultConfiguration);
42
40
43
-
assertThat(converted).isInstanceOf(ExpressionNodeValue.class);
44
-
assertThat(converted.getExpressionNode().toJSON()).isEqualTo(testNode.toJSON());
41
+
assertThat(converted).isInstanceOf(SolvableValue.class);
42
+
assertThat(converted.getSolvable()).isEqualTo(testNode);
45
43
}
46
44
47
45
@Test