a fork of EvalEx by ezylang with a handful of breaking changes

Remove inlining and replace ASTNode with Solvable in expressions.

+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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