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

Configure Feed

Select the types of activity you want to include in your feed.

Remove inlining and replace ASTNode with Solvable in expressions.

+202 -514
+11 -148
src/main/java/com/ezylang/evalex/Expression.java
··· 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 import com.ezylang.evalex.data.DataAccessorIfc; 20 import com.ezylang.evalex.data.EvaluationValue; 21 - import com.ezylang.evalex.data.IndexedAccessor; 22 - import com.ezylang.evalex.data.types.ExpressionNodeValue; 23 import com.ezylang.evalex.data.types.NumberValue; 24 - import com.ezylang.evalex.functions.FunctionIfc; 25 - import com.ezylang.evalex.operators.OperatorIfc; 26 import com.ezylang.evalex.parser.*; 27 import java.math.BigDecimal; 28 - import java.util.*; 29 import java.util.function.UnaryOperator; 30 import lombok.Getter; 31 import org.jetbrains.annotations.Nullable; ··· 41 private final ExpressionConfiguration configuration; 42 private final String expressionString; 43 private final @Nullable DataAccessorIfc dataAccessor; 44 - private final ASTNode abstractSyntaxTree; 45 46 /** 47 * Creates a new expression with a custom configuration. The expression is not parsed until it is ··· 50 * @param expressionString A string holding an expression. 51 */ 52 public Expression( 53 - String expressionString, ASTNode abstractSyntaxTree, ExpressionConfiguration configuration) { 54 this.expressionString = expressionString; 55 - this.abstractSyntaxTree = abstractSyntaxTree; 56 this.configuration = configuration; 57 this.dataAccessor = configuration.getDataAccessorSupplier().get(); 58 } ··· 69 * @throws EvaluationException If there were problems while evaluating the expression. 70 */ 71 public EvaluationValue evaluate(EvaluationContext context) throws EvaluationException { 72 - EvaluationValue result = evaluateSubtree(getAbstractSyntaxTree(), context); 73 if (result.isNumberValue()) { 74 BigDecimal bigDecimal = result.getNumberValue(); 75 if (configuration.getDecimalPlacesResult() ··· 88 } 89 90 public EvaluationValue evaluateSubtree( 91 - ASTNode startNode, UnaryOperator<EvaluationContext.EvaluationContextBuilder> builder) 92 throws EvaluationException { 93 - return this.evaluateSubtree(startNode, builder.apply(EvaluationContext.builder(this)).build()); 94 } 95 96 /** 97 * Evaluates only a subtree of the abstract syntax tree. 98 * 99 - * @param startNode The {@link ASTNode} to start evaluation from. 100 * @return The evaluation result value. 101 * @throws EvaluationException If there were problems while evaluating the expression. 102 */ 103 - public EvaluationValue evaluateSubtree(ASTNode startNode, EvaluationContext context) 104 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 - }); 127 } 128 129 public EvaluationValue tryRoundValue(EvaluationValue value) { ··· 136 return value; 137 } 138 139 - private EvaluationValue getVariableOrConstant(Token token, EvaluationContext context) 140 throws EvaluationException { 141 EvaluationValue result = context.parameters().get(token.getValue()); 142 if (result == null) { ··· 152 return result; 153 } 154 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 /** 230 * Rounds the given value. 231 * ··· 244 * @return The copied Expression instance. 245 */ 246 public Expression copy() { 247 - return new Expression(getExpressionString(), getAbstractSyntaxTree(), getConfiguration()); 248 } 249 250 /** ··· 267 */ 268 public EvaluationValue convertValue(Object value) { 269 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 } 308 }
··· 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 import com.ezylang.evalex.data.DataAccessorIfc; 20 import com.ezylang.evalex.data.EvaluationValue; 21 import com.ezylang.evalex.data.types.NumberValue; 22 import com.ezylang.evalex.parser.*; 23 import java.math.BigDecimal; 24 import java.util.function.UnaryOperator; 25 import lombok.Getter; 26 import org.jetbrains.annotations.Nullable; ··· 36 private final ExpressionConfiguration configuration; 37 private final String expressionString; 38 private final @Nullable DataAccessorIfc dataAccessor; 39 + private final Solvable solvable; 40 41 /** 42 * Creates a new expression with a custom configuration. The expression is not parsed until it is ··· 45 * @param expressionString A string holding an expression. 46 */ 47 public Expression( 48 + String expressionString, Solvable solvable, ExpressionConfiguration configuration) { 49 this.expressionString = expressionString; 50 + this.solvable = solvable; 51 this.configuration = configuration; 52 this.dataAccessor = configuration.getDataAccessorSupplier().get(); 53 } ··· 64 * @throws EvaluationException If there were problems while evaluating the expression. 65 */ 66 public EvaluationValue evaluate(EvaluationContext context) throws EvaluationException { 67 + EvaluationValue result = evaluateSubtree(this.getSolvable(), context); 68 if (result.isNumberValue()) { 69 BigDecimal bigDecimal = result.getNumberValue(); 70 if (configuration.getDecimalPlacesResult() ··· 83 } 84 85 public EvaluationValue evaluateSubtree( 86 + Solvable solvable, UnaryOperator<EvaluationContext.EvaluationContextBuilder> builder) 87 throws EvaluationException { 88 + return this.evaluateSubtree(solvable, builder.apply(EvaluationContext.builder(this)).build()); 89 } 90 91 /** 92 * Evaluates only a subtree of the abstract syntax tree. 93 * 94 + * @param solvable The {@link Solvable} to start evaluation from. 95 * @return The evaluation result value. 96 * @throws EvaluationException If there were problems while evaluating the expression. 97 */ 98 + public EvaluationValue evaluateSubtree(Solvable solvable, EvaluationContext context) 99 throws EvaluationException { 100 + return solvable.solve(context); 101 } 102 103 public EvaluationValue tryRoundValue(EvaluationValue value) { ··· 110 return value; 111 } 112 113 + public EvaluationValue getVariableOrConstant(Token token, EvaluationContext context) 114 throws EvaluationException { 115 EvaluationValue result = context.parameters().get(token.getValue()); 116 if (result == null) { ··· 126 return result; 127 } 128 129 /** 130 * Rounds the given value. 131 * ··· 144 * @return The copied Expression instance. 145 */ 146 public Expression copy() { 147 + return new Expression(getExpressionString(), getSolvable(), getConfiguration()); 148 } 149 150 /** ··· 167 */ 168 public EvaluationValue convertValue(Object value) { 169 return EvaluationValue.of(value, configuration); 170 } 171 }
+1 -4
src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java
··· 15 */ 16 package com.ezylang.evalex.config; 17 18 - import com.ezylang.evalex.Expression; 19 import com.ezylang.evalex.data.DataAccessorIfc; 20 import com.ezylang.evalex.data.EvaluationValue; 21 import com.ezylang.evalex.data.conversion.DefaultEvaluationValueConverter; ··· 32 import com.ezylang.evalex.operators.OperatorIfc; 33 import com.ezylang.evalex.operators.arithmetic.*; 34 import com.ezylang.evalex.operators.booleans.*; 35 - import com.ezylang.evalex.parser.ASTNode; 36 import com.ezylang.evalex.parser.ExpressionParser; 37 import java.math.BigDecimal; 38 import java.math.MathContext; ··· 123 /** 124 * Default constants will be added automatically to each expression and can be used in expression 125 * evaluation. <br> 126 - * It is assumed that constant will <b>never</b> change. {@link 127 - * ExpressionParser#inlineASTNode(Expression, ASTNode)} relies on this assumption! 128 */ 129 @Builder.Default 130 private final Map<String, EvaluationValue> constants =
··· 15 */ 16 package com.ezylang.evalex.config; 17 18 import com.ezylang.evalex.data.DataAccessorIfc; 19 import com.ezylang.evalex.data.EvaluationValue; 20 import com.ezylang.evalex.data.conversion.DefaultEvaluationValueConverter; ··· 31 import com.ezylang.evalex.operators.OperatorIfc; 32 import com.ezylang.evalex.operators.arithmetic.*; 33 import com.ezylang.evalex.operators.booleans.*; 34 import com.ezylang.evalex.parser.ExpressionParser; 35 import java.math.BigDecimal; 36 import java.math.MathContext; ··· 121 /** 122 * Default constants will be added automatically to each expression and can be used in expression 123 * evaluation. <br> 124 + * It is assumed that constant will <b>never</b> change. 125 */ 126 @Builder.Default 127 private final Map<String, EvaluationValue> constants =
+6 -10
src/main/java/com/ezylang/evalex/data/EvaluationValue.java
··· 16 package com.ezylang.evalex.data; 17 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 - import com.ezylang.evalex.data.types.ExpressionNodeValue; 20 - import com.ezylang.evalex.parser.ASTNode; 21 import java.math.BigDecimal; 22 import java.time.Duration; 23 import java.time.Instant; ··· 77 return false; 78 } 79 80 - default boolean isExpressionNode() { 81 return false; 82 } 83 ··· 186 return Collections.emptyMap(); 187 } 188 189 - default DataAccessorIfc getDataAccessorValue() { 190 - return null; 191 - } 192 - 193 /** 194 - * Gets the expression node, if this value is of type {@link ExpressionNodeValue}. 195 * 196 - * @return The expression node, or null for any other data type. 197 */ 198 - default ASTNode getExpressionNode() { 199 return null; 200 } 201
··· 16 package com.ezylang.evalex.data; 17 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 + import com.ezylang.evalex.data.types.SolvableValue; 20 + import com.ezylang.evalex.parser.Solvable; 21 import java.math.BigDecimal; 22 import java.time.Duration; 23 import java.time.Instant; ··· 77 return false; 78 } 79 80 + default boolean isSolvable() { 81 return false; 82 } 83 ··· 186 return Collections.emptyMap(); 187 } 188 189 /** 190 + * Gets the {@link Solvable}, if this value is of type {@link SolvableValue}. 191 * 192 + * @return The solvable, or null for any other data type. 193 */ 194 + default Solvable getSolvable() { 195 return null; 196 } 197
+4 -7
src/main/java/com/ezylang/evalex/data/conversion/DefaultEvaluationValueConverter.java
··· 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 import com.ezylang.evalex.data.EvaluationValue; 20 import com.ezylang.evalex.data.types.NullValue; 21 - import com.ezylang.evalex.parser.ASTNode; 22 - import com.ezylang.evalex.parser.InlinedASTNode; 23 import java.math.BigDecimal; 24 import java.time.Duration; 25 import java.time.Instant; ··· 65 public static final BooleanConverter BOOLEAN_CONVERTER = new BooleanConverter(); 66 public static final DateTimeConverter DATE_TIME_CONVERTER = new DateTimeConverter(); 67 public static final DurationConverter DURATION_CONVERTER = new DurationConverter(); 68 - public static final ExpressionNodeConverter EXPRESSION_NODE_CONVERTER = 69 - new ExpressionNodeConverter(); 70 public static final ArrayConverter ARRAY_CONVERTER = new ArrayConverter(); 71 public static final StructureConverter STRUCTURE_CONVERTER = new StructureConverter(); 72 ··· 77 BOOLEAN_CONVERTER, 78 DATE_TIME_CONVERTER, 79 DURATION_CONVERTER, 80 - EXPRESSION_NODE_CONVERTER, 81 ARRAY_CONVERTER, 82 STRUCTURE_CONVERTER); 83 ··· 101 102 fastPath.put(Duration.class, DURATION_CONVERTER); 103 104 - fastPath.put(ASTNode.class, EXPRESSION_NODE_CONVERTER); 105 - fastPath.put(InlinedASTNode.class, EXPRESSION_NODE_CONVERTER); 106 107 fastPath.put(List.class, ARRAY_CONVERTER); 108 fastPath.put(Object[].class, ARRAY_CONVERTER);
··· 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 import com.ezylang.evalex.data.EvaluationValue; 20 import com.ezylang.evalex.data.types.NullValue; 21 + import com.ezylang.evalex.parser.Solvable; 22 import java.math.BigDecimal; 23 import java.time.Duration; 24 import java.time.Instant; ··· 64 public static final BooleanConverter BOOLEAN_CONVERTER = new BooleanConverter(); 65 public static final DateTimeConverter DATE_TIME_CONVERTER = new DateTimeConverter(); 66 public static final DurationConverter DURATION_CONVERTER = new DurationConverter(); 67 + public static final SolvableConverter SOLVABLE_CONVERTER = new SolvableConverter(); 68 public static final ArrayConverter ARRAY_CONVERTER = new ArrayConverter(); 69 public static final StructureConverter STRUCTURE_CONVERTER = new StructureConverter(); 70 ··· 75 BOOLEAN_CONVERTER, 76 DATE_TIME_CONVERTER, 77 DURATION_CONVERTER, 78 + SOLVABLE_CONVERTER, 79 ARRAY_CONVERTER, 80 STRUCTURE_CONVERTER); 81 ··· 99 100 fastPath.put(Duration.class, DURATION_CONVERTER); 101 102 + fastPath.put(Solvable.class, SOLVABLE_CONVERTER); 103 104 fastPath.put(List.class, ARRAY_CONVERTER); 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 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 import com.ezylang.evalex.data.EvaluationValue; 20 - import com.ezylang.evalex.data.types.ExpressionNodeValue; 21 - import com.ezylang.evalex.parser.ASTNode; 22 23 - /** Converter to convert to the EXPRESSION_NODE data type. */ 24 - public class ExpressionNodeConverter implements ConverterIfc { 25 @Override 26 public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { 27 - return ExpressionNodeValue.of((ASTNode) object); 28 } 29 30 @Override 31 public boolean canConvert(Object object) { 32 - return object instanceof ASTNode; 33 } 34 }
··· 17 18 import com.ezylang.evalex.config.ExpressionConfiguration; 19 import com.ezylang.evalex.data.EvaluationValue; 20 + import com.ezylang.evalex.data.types.SolvableValue; 21 + import com.ezylang.evalex.parser.Solvable; 22 23 + /** Converter to convert to the {@link SolvableValue} data type. */ 24 + public class SolvableConverter implements ConverterIfc { 25 @Override 26 public EvaluationValue convert(Object object, ExpressionConfiguration configuration) { 27 + return SolvableValue.of((Solvable) object); 28 } 29 30 @Override 31 public boolean canConvert(Object object) { 32 + return object instanceof Solvable; 33 } 34 }
+7 -7
src/main/java/com/ezylang/evalex/data/types/ExpressionNodeValue.java src/main/java/com/ezylang/evalex/data/types/SolvableValue.java
··· 16 package com.ezylang.evalex.data.types; 17 18 import com.ezylang.evalex.data.EvaluationValue; 19 - import com.ezylang.evalex.parser.ASTNode; 20 import lombok.*; 21 22 @ToString() 23 @EqualsAndHashCode(callSuper = false) 24 @RequiredArgsConstructor(access = AccessLevel.PRIVATE) 25 - public final class ExpressionNodeValue implements EvaluationValue { 26 27 - private final ASTNode value; 28 29 - public static ExpressionNodeValue of(@NonNull ASTNode node) { 30 - return new ExpressionNodeValue(node); 31 } 32 33 @Override ··· 36 } 37 38 @Override 39 - public boolean isExpressionNode() { 40 return true; 41 } 42 43 @Override 44 - public ASTNode getExpressionNode() { 45 return value; 46 } 47 }
··· 16 package com.ezylang.evalex.data.types; 17 18 import com.ezylang.evalex.data.EvaluationValue; 19 + import com.ezylang.evalex.parser.Solvable; 20 import lombok.*; 21 22 @ToString() 23 @EqualsAndHashCode(callSuper = false) 24 @RequiredArgsConstructor(access = AccessLevel.PRIVATE) 25 + public class SolvableValue implements EvaluationValue { 26 27 + private final Solvable value; 28 29 + public static SolvableValue of(@NonNull Solvable node) { 30 + return new SolvableValue(node); 31 } 32 33 @Override ··· 36 } 37 38 @Override 39 + public boolean isSolvable() { 40 return true; 41 } 42 43 @Override 44 + public Solvable getSolvable() { 45 return value; 46 } 47 }
-30
src/main/java/com/ezylang/evalex/functions/FunctionIfc.java
··· 17 18 import com.ezylang.evalex.EvaluationContext; 19 import com.ezylang.evalex.EvaluationException; 20 - import com.ezylang.evalex.Expression; 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.InlinedASTNode; 25 import com.ezylang.evalex.parser.Token; 26 import java.util.List; 27 - import org.jetbrains.annotations.Nullable; 28 29 /** 30 * Interface that is required for all functions in a function dictionary for evaluation of ··· 92 default int getCountOfNonVarArgParameters() { 93 int numOfParameters = getFunctionParameterDefinitions().size(); 94 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 } 121 }
··· 17 18 import com.ezylang.evalex.EvaluationContext; 19 import com.ezylang.evalex.EvaluationException; 20 import com.ezylang.evalex.data.EvaluationValue; 21 import com.ezylang.evalex.parser.Token; 22 import java.util.List; 23 24 /** 25 * Interface that is required for all functions in a function dictionary for evaluation of ··· 87 default int getCountOfNonVarArgParameters() { 88 int numOfParameters = getFunctionParameterDefinitions().size(); 89 return hasVarArgs() ? numOfParameters - 1 : numOfParameters; 90 } 91 }
+2 -2
src/main/java/com/ezylang/evalex/functions/basic/IfFunction.java
··· 37 EvaluationContext context, Token functionToken, EvaluationValue... parameterValues) 38 throws EvaluationException { 39 if (Boolean.TRUE.equals(parameterValues[0].getBooleanValue())) { 40 - return context.expression().evaluateSubtree(parameterValues[1].getExpressionNode(), context); 41 } else { 42 - return context.expression().evaluateSubtree(parameterValues[2].getExpressionNode(), context); 43 } 44 } 45 }
··· 37 EvaluationContext context, Token functionToken, EvaluationValue... parameterValues) 38 throws EvaluationException { 39 if (Boolean.TRUE.equals(parameterValues[0].getBooleanValue())) { 40 + return context.expression().evaluateSubtree(parameterValues[1].getSolvable(), context); 41 } else { 42 + return context.expression().evaluateSubtree(parameterValues[2].getSolvable(), context); 43 } 44 } 45 }
-9
src/main/java/com/ezylang/evalex/functions/basic/RandomFunction.java
··· 16 package com.ezylang.evalex.functions.basic; 17 18 import com.ezylang.evalex.EvaluationContext; 19 - import com.ezylang.evalex.Expression; 20 import com.ezylang.evalex.data.EvaluationValue; 21 import com.ezylang.evalex.functions.AbstractFunction; 22 - import com.ezylang.evalex.parser.ASTNode; 23 import com.ezylang.evalex.parser.Token; 24 import java.security.SecureRandom; 25 - import org.jetbrains.annotations.Nullable; 26 27 /** Random function produces a random value between 0 and 1. */ 28 public class RandomFunction extends AbstractFunction { ··· 34 SecureRandom secureRandom = new SecureRandom(); 35 36 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 } 44 }
··· 16 package com.ezylang.evalex.functions.basic; 17 18 import com.ezylang.evalex.EvaluationContext; 19 import com.ezylang.evalex.data.EvaluationValue; 20 import com.ezylang.evalex.functions.AbstractFunction; 21 import com.ezylang.evalex.parser.Token; 22 import java.security.SecureRandom; 23 24 /** Random function produces a random value between 0 and 1. */ 25 public class RandomFunction extends AbstractFunction { ··· 31 SecureRandom secureRandom = new SecureRandom(); 32 33 return context.expression().convertDoubleValue(secureRandom.nextDouble()); 34 } 35 }
+2 -2
src/main/java/com/ezylang/evalex/functions/basic/SwitchFunction.java
··· 98 private EvaluationValue evaluateParameter( 99 Expression expression, EvaluationValue parameter, EvaluationContext context) 100 throws EvaluationException { 101 - return parameter.isExpressionNode() 102 - ? expression.evaluateSubtree(parameter.getExpressionNode(), context) 103 : parameter; 104 } 105 }
··· 98 private EvaluationValue evaluateParameter( 99 Expression expression, EvaluationValue parameter, EvaluationContext context) 100 throws EvaluationException { 101 + return parameter.isSolvable() 102 + ? expression.evaluateSubtree(parameter.getSolvable(), context) 103 : parameter; 104 } 105 }
-9
src/main/java/com/ezylang/evalex/functions/datetime/DateTimeNowFunction.java
··· 16 package com.ezylang.evalex.functions.datetime; 17 18 import com.ezylang.evalex.EvaluationContext; 19 - import com.ezylang.evalex.Expression; 20 import com.ezylang.evalex.data.EvaluationValue; 21 import com.ezylang.evalex.data.types.DateTimeValue; 22 import com.ezylang.evalex.functions.AbstractFunction; 23 - import com.ezylang.evalex.parser.ASTNode; 24 import com.ezylang.evalex.parser.Token; 25 import java.time.Instant; 26 - import org.jetbrains.annotations.Nullable; 27 28 /** 29 * Produces a new DATE_TIME that represents the current date and time. ··· 45 public EvaluationValue evaluate( 46 EvaluationContext context, Token functionToken, EvaluationValue... parameterValues) { 47 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 } 55 }
··· 16 package com.ezylang.evalex.functions.datetime; 17 18 import com.ezylang.evalex.EvaluationContext; 19 import com.ezylang.evalex.data.EvaluationValue; 20 import com.ezylang.evalex.data.types.DateTimeValue; 21 import com.ezylang.evalex.functions.AbstractFunction; 22 import com.ezylang.evalex.parser.Token; 23 import java.time.Instant; 24 25 /** 26 * Produces a new DATE_TIME that represents the current date and time. ··· 42 public EvaluationValue evaluate( 43 EvaluationContext context, Token functionToken, EvaluationValue... parameterValues) { 44 return DateTimeValue.of(Instant.now()); 45 } 46 }
-8
src/main/java/com/ezylang/evalex/functions/datetime/DateTimeTodayFunction.java
··· 23 import com.ezylang.evalex.data.types.DateTimeValue; 24 import com.ezylang.evalex.functions.AbstractFunction; 25 import com.ezylang.evalex.functions.FunctionParameter; 26 - import com.ezylang.evalex.parser.ASTNode; 27 import com.ezylang.evalex.parser.Token; 28 import java.time.Instant; 29 import java.time.LocalDate; 30 import java.time.ZoneId; 31 - import org.jetbrains.annotations.Nullable; 32 33 /** 34 * Produces a new DATE_TIME that represents the current date, at midnight (00:00). ··· 65 return ZoneIdConverter.convert(functionToken, parameterValues[0].getStringValue()); 66 } 67 return expression.getConfiguration().getZoneId(); 68 - } 69 - 70 - @Override 71 - public @Nullable EvaluationValue inlineFunction( 72 - Expression expression, Token token, ASTNode... parameters) { 73 - return null; 74 } 75 }
··· 23 import com.ezylang.evalex.data.types.DateTimeValue; 24 import com.ezylang.evalex.functions.AbstractFunction; 25 import com.ezylang.evalex.functions.FunctionParameter; 26 import com.ezylang.evalex.parser.Token; 27 import java.time.Instant; 28 import java.time.LocalDate; 29 import java.time.ZoneId; 30 31 /** 32 * Produces a new DATE_TIME that represents the current date, at midnight (00:00). ··· 63 return ZoneIdConverter.convert(functionToken, parameterValues[0].getStringValue()); 64 } 65 return expression.getConfiguration().getZoneId(); 66 } 67 }
-26
src/main/java/com/ezylang/evalex/operators/OperatorIfc.java
··· 17 18 import com.ezylang.evalex.EvaluationContext; 19 import com.ezylang.evalex.EvaluationException; 20 - import com.ezylang.evalex.Expression; 21 import com.ezylang.evalex.config.ExpressionConfiguration; 22 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 import com.ezylang.evalex.parser.Token; 27 - import org.jetbrains.annotations.Nullable; 28 29 /** 30 * Interface that is required for all operators in an operator dictionary for evaluation of ··· 134 EvaluationValue evaluate( 135 EvaluationContext context, Token operatorToken, EvaluationValue... operands) 136 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 }
··· 17 18 import com.ezylang.evalex.EvaluationContext; 19 import com.ezylang.evalex.EvaluationException; 20 import com.ezylang.evalex.config.ExpressionConfiguration; 21 import com.ezylang.evalex.data.EvaluationValue; 22 import com.ezylang.evalex.parser.Token; 23 24 /** 25 * Interface that is required for all operators in an operator dictionary for evaluation of ··· 129 EvaluationValue evaluate( 130 EvaluationContext context, Token operatorToken, EvaluationValue... operands) 131 throws EvaluationException; 132 }
+2 -5
src/main/java/com/ezylang/evalex/operators/booleans/InfixAndOperator.java
··· 34 EvaluationContext context, Token operatorToken, EvaluationValue... operands) 35 throws EvaluationException { 36 return BooleanValue.of( 37 - context 38 - .expression() 39 - .evaluateSubtree(operands[0].getExpressionNode(), context) 40 - .getBooleanValue() 41 && context 42 .expression() 43 - .evaluateSubtree(operands[1].getExpressionNode(), context) 44 .getBooleanValue()); 45 } 46 }
··· 34 EvaluationContext context, Token operatorToken, EvaluationValue... operands) 35 throws EvaluationException { 36 return BooleanValue.of( 37 + context.expression().evaluateSubtree(operands[0].getSolvable(), context).getBooleanValue() 38 && context 39 .expression() 40 + .evaluateSubtree(operands[1].getSolvable(), context) 41 .getBooleanValue()); 42 } 43 }
+2 -5
src/main/java/com/ezylang/evalex/operators/booleans/InfixOrOperator.java
··· 34 EvaluationContext context, Token operatorToken, EvaluationValue... operands) 35 throws EvaluationException { 36 return BooleanValue.of( 37 - context 38 - .expression() 39 - .evaluateSubtree(operands[0].getExpressionNode(), context) 40 - .getBooleanValue() 41 || context 42 .expression() 43 - .evaluateSubtree(operands[1].getExpressionNode(), context) 44 .getBooleanValue()); 45 } 46 }
··· 34 EvaluationContext context, Token operatorToken, EvaluationValue... operands) 35 throws EvaluationException { 36 return BooleanValue.of( 37 + context.expression().evaluateSubtree(operands[0].getSolvable(), context).getBooleanValue() 38 || context 39 .expression() 40 + .evaluateSubtree(operands[1].getSolvable(), context) 41 .getBooleanValue()); 42 } 43 }
+107 -67
src/main/java/com/ezylang/evalex/parser/ExpressionParser.java
··· 18 import com.ezylang.evalex.EvaluationException; 19 import com.ezylang.evalex.Expression; 20 import com.ezylang.evalex.config.ExpressionConfiguration; 21 import com.ezylang.evalex.data.EvaluationValue; 22 import com.ezylang.evalex.functions.FunctionIfc; 23 import com.ezylang.evalex.operators.OperatorIfc; 24 - import java.util.Arrays; 25 import lombok.Getter; 26 - import org.jetbrains.annotations.NotNull; 27 28 @Getter 29 public final class ExpressionParser { ··· 41 public Expression parse(String expression) throws ParseException { 42 return new Expression( 43 expression, 44 - converter.toAbstractSyntaxTree(tokenizer.parse(expression), expression), 45 configuration); 46 } 47 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); 52 } 53 54 - public Expression inlineExpression(Expression expression) throws EvaluationException { 55 - return new Expression( 56 - expression.getExpressionString(), 57 - this.inlineASTNode(expression, expression.getAbstractSyntaxTree()), 58 - expression.getConfiguration()); 59 } 60 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(); 74 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 - } 89 } 90 - return node; 91 - } 92 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); 98 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 - } 110 } 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; 120 } 121 } 122 } 123 - return node; 124 } 125 }
··· 18 import com.ezylang.evalex.EvaluationException; 19 import com.ezylang.evalex.Expression; 20 import com.ezylang.evalex.config.ExpressionConfiguration; 21 + import com.ezylang.evalex.data.DataAccessorIfc; 22 import com.ezylang.evalex.data.EvaluationValue; 23 + import com.ezylang.evalex.data.IndexedAccessor; 24 + import com.ezylang.evalex.data.types.SolvableValue; 25 import com.ezylang.evalex.functions.FunctionIfc; 26 import com.ezylang.evalex.operators.OperatorIfc; 27 import lombok.Getter; 28 29 @Getter 30 public final class ExpressionParser { ··· 42 public Expression parse(String expression) throws ParseException { 43 return new Expression( 44 expression, 45 + this.toSolvable(converter.toAbstractSyntaxTree(tokenizer.parse(expression), expression)), 46 configuration); 47 } 48 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)); 73 } 74 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)); 91 } 92 93 + private Solvable arrayIndexToSolvable(ASTNode node) { 94 + Token token = node.getToken(); 95 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; 112 } 113 + throw EvaluationException.ofUnsupportedDataTypeInOperation(token); 114 + }; 115 + } 116 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(); 121 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; 132 } 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]); 152 } 153 } 154 } 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 + }; 164 } 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 protected EvaluationValue evaluate(String expressionString) 41 throws EvaluationException, ParseException { 42 Expression expression = 43 - TestConfigurationProvider.StandardParserWithAdditionalTestOperators.parseAndInline( 44 - expressionString); 45 return expression.evaluate(EvaluationContext.builder(expression).build()); 46 } 47 48 private EvaluationValue evaluate(String expressionString, ExpressionConfiguration configuration) 49 throws EvaluationException, ParseException { 50 ExpressionParser parser = new ExpressionParser(configuration); 51 - Expression expression = parser.parseAndInline(expressionString); 52 53 return expression.evaluate(EvaluationContext.builder(expression).build()); 54 }
··· 40 protected EvaluationValue evaluate(String expressionString) 41 throws EvaluationException, ParseException { 42 Expression expression = 43 + TestConfigurationProvider.StandardParserWithAdditionalTestOperators.parse(expressionString); 44 return expression.evaluate(EvaluationContext.builder(expression).build()); 45 } 46 47 private EvaluationValue evaluate(String expressionString, ExpressionConfiguration configuration) 48 throws EvaluationException, ParseException { 49 ExpressionParser parser = new ExpressionParser(configuration); 50 + Expression expression = parser.parse(expressionString); 51 52 return expression.evaluate(EvaluationContext.builder(expression).build()); 53 }
+2 -3
src/test/java/com/ezylang/evalex/ExpressionEvaluationExceptionsTest.java
··· 22 import com.ezylang.evalex.parser.ParseException; 23 import com.ezylang.evalex.parser.Token; 24 import com.ezylang.evalex.parser.Token.TokenType; 25 - import java.util.function.UnaryOperator; 26 import org.junit.jupiter.api.Test; 27 28 class ExpressionEvaluationExceptionsTest { ··· 34 assertThatThrownBy( 35 () -> { 36 ASTNode node = ASTNode.of(new Token(1, "(", TokenType.BRACE_OPEN)); 37 - expression.evaluateSubtree(node, UnaryOperator.identity()); 38 }) 39 - .isInstanceOf(EvaluationException.class) 40 .hasMessage( 41 "Unexpected evaluation token: Token(startPosition=1, value=(, type=BRACE_OPEN)"); 42 }
··· 22 import com.ezylang.evalex.parser.ParseException; 23 import com.ezylang.evalex.parser.Token; 24 import com.ezylang.evalex.parser.Token.TokenType; 25 import org.junit.jupiter.api.Test; 26 27 class ExpressionEvaluationExceptionsTest { ··· 33 assertThatThrownBy( 34 () -> { 35 ASTNode node = ASTNode.of(new Token(1, "(", TokenType.BRACE_OPEN)); 36 + ExpressionConfiguration.defaultExpressionParser().toSolvable(node); 37 }) 38 + .isInstanceOf(IllegalStateException.class) 39 .hasMessage( 40 "Unexpected evaluation token: Token(startPosition=1, value=(, type=BRACE_OPEN)"); 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 import com.ezylang.evalex.data.DataAccessorIfc; 23 import com.ezylang.evalex.data.EvaluationValue; 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; 29 import java.math.MathContext; 30 import java.util.HashMap; 31 - import java.util.List; 32 import java.util.Map; 33 import java.util.function.Supplier; 34 import org.junit.jupiter.api.Test; ··· 66 @Test 67 void testExpressionNode() throws ParseException, EvaluationException { 68 Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("a*b"); 69 - ASTNode subExpression = 70 - ExpressionConfiguration.defaultExpressionParser() 71 - .parseAndInline("4+3") 72 - .getAbstractSyntaxTree(); 73 74 EvaluationValue result = 75 expression.evaluate(builder -> builder.parameter("a", 2).parameter("b", subExpression)); ··· 176 .parse("1"); 177 assertThat(limitedMathContextExpression.convertDoubleValue(1.6789).getNumberValue()) 178 .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 } 218 219 @Test
··· 22 import com.ezylang.evalex.data.DataAccessorIfc; 23 import com.ezylang.evalex.data.EvaluationValue; 24 import com.ezylang.evalex.data.types.StringValue; 25 + import com.ezylang.evalex.parser.*; 26 import java.math.MathContext; 27 import java.util.HashMap; 28 import java.util.Map; 29 import java.util.function.Supplier; 30 import org.junit.jupiter.api.Test; ··· 62 @Test 63 void testExpressionNode() throws ParseException, EvaluationException { 64 Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("a*b"); 65 + Solvable subExpression = 66 + ExpressionConfiguration.defaultExpressionParser().parse("4+3").getSolvable(); 67 68 EvaluationValue result = 69 expression.evaluate(builder -> builder.parameter("a", 2).parameter("b", subExpression)); ··· 170 .parse("1"); 171 assertThat(limitedMathContextExpression.convertDoubleValue(1.6789).getNumberValue()) 172 .isEqualByComparingTo("1.68"); 173 } 174 175 @Test
+13 -19
src/test/java/com/ezylang/evalex/data/EvaluationValueTest.java
··· 16 package com.ezylang.evalex.data; 17 18 import static com.ezylang.evalex.config.ExpressionConfiguration.defaultConfiguration; 19 import static org.assertj.core.api.Assertions.assertThat; 20 21 import com.ezylang.evalex.EvaluationException; ··· 26 import com.ezylang.evalex.data.types.NumberValue; 27 import com.ezylang.evalex.parser.ASTNode; 28 import com.ezylang.evalex.parser.ParseException; 29 import com.ezylang.evalex.parser.Token; 30 import com.ezylang.evalex.parser.Token.TokenType; 31 import java.math.BigDecimal; ··· 45 assertThat(value.isBooleanValue()).isFalse(); 46 assertThat(value.isStructureValue()).isFalse(); 47 assertThat(value.isArrayValue()).isFalse(); 48 - assertThat(value.isExpressionNode()).isFalse(); 49 assertThat(value.isNullValue()).isFalse(); 50 assertDataIsCorrect( 51 value, "Hello World", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, String.class); ··· 85 assertThat(value.isStringValue()).isFalse(); 86 assertThat(value.isStructureValue()).isFalse(); 87 assertThat(value.isArrayValue()).isFalse(); 88 - assertThat(value.isExpressionNode()).isFalse(); 89 assertThat(value.isNullValue()).isFalse(); 90 assertDataIsCorrect( 91 value, "true", BigDecimal.ONE, true, Instant.EPOCH, Duration.ZERO, Boolean.class); ··· 399 assertThat(value.isBooleanValue()).isFalse(); 400 assertThat(value.isStructureValue()).isFalse(); 401 assertThat(value.isStringValue()).isFalse(); 402 - assertThat(value.isExpressionNode()).isFalse(); 403 assertThat(value.isNullValue()).isFalse(); 404 405 assertThat(value.getArrayValue()).hasSize(2); ··· 435 assertThat(value.isBooleanValue()).isFalse(); 436 assertThat(value.isStringValue()).isFalse(); 437 assertThat(value.isArrayValue()).isFalse(); 438 - assertThat(value.isExpressionNode()).isFalse(); 439 assertThat(value.isNullValue()).isFalse(); 440 441 assertThat(value.getStructureValue()).hasSize(2); ··· 461 @Test 462 void testExpressionNode() { 463 ASTNode node = ASTNode.of(new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT)); 464 - EvaluationValue value = EvaluationValue.of(node, defaultConfiguration()); 465 466 - assertThat(value.isExpressionNode()).isTrue(); 467 assertThat(value.isNumberValue()).isFalse(); 468 assertThat(value.isBooleanValue()).isFalse(); 469 assertThat(value.isStructureValue()).isFalse(); ··· 472 assertThat(value.isNullValue()).isFalse(); 473 474 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); 482 } 483 484 @Test 485 void testExpressionNodeReturnsNull() { 486 EvaluationValue value = EvaluationValue.of(false, defaultConfiguration()); 487 - assertThat(value.getExpressionNode()).isNull(); 488 } 489 490 @Test ··· 529 assertThat(value.isBooleanValue()).isFalse(); 530 assertThat(value.isStructureValue()).isFalse(); 531 assertThat(value.isArrayValue()).isFalse(); 532 - assertThat(value.isExpressionNode()).isFalse(); 533 assertThat(value.isNullValue()).isTrue(); 534 assertDataIsCorrect(value, null, null, null); 535 } ··· 561 562 private void assertDataIsCorrect( 563 EvaluationValue value, String stringValue, BigDecimal numberValue, Boolean booleanValue) { 564 - assertThat(value.getStringValue()).isEqualTo(stringValue); 565 assertThat(value.getNumberValue()).isEqualTo(numberValue); 566 assertThat(value.getBooleanValue()).isEqualTo(booleanValue); 567 } ··· 575 Duration durationValue, 576 Class<?> valueInstance) { 577 assertDataIsCorrect(value, stringValue, numberValue, booleanValue); 578 - assertThat(value.getStringValue()).isEqualTo(stringValue); 579 - assertThat(value.getNumberValue()).isEqualTo(numberValue); 580 - assertThat(value.getBooleanValue()).isEqualTo(booleanValue); 581 assertThat(value.getDateTimeValue()).isEqualTo(dateTimeValue); 582 assertThat(value.getDurationValue()).isEqualTo(durationValue); 583 assertThat(value.getValue()).isInstanceOf(valueInstance);
··· 16 package com.ezylang.evalex.data; 17 18 import static com.ezylang.evalex.config.ExpressionConfiguration.defaultConfiguration; 19 + import static com.ezylang.evalex.config.ExpressionConfiguration.defaultExpressionParser; 20 import static org.assertj.core.api.Assertions.assertThat; 21 22 import com.ezylang.evalex.EvaluationException; ··· 27 import com.ezylang.evalex.data.types.NumberValue; 28 import com.ezylang.evalex.parser.ASTNode; 29 import com.ezylang.evalex.parser.ParseException; 30 + import com.ezylang.evalex.parser.Solvable; 31 import com.ezylang.evalex.parser.Token; 32 import com.ezylang.evalex.parser.Token.TokenType; 33 import java.math.BigDecimal; ··· 47 assertThat(value.isBooleanValue()).isFalse(); 48 assertThat(value.isStructureValue()).isFalse(); 49 assertThat(value.isArrayValue()).isFalse(); 50 + assertThat(value.isSolvable()).isFalse(); 51 assertThat(value.isNullValue()).isFalse(); 52 assertDataIsCorrect( 53 value, "Hello World", BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, String.class); ··· 87 assertThat(value.isStringValue()).isFalse(); 88 assertThat(value.isStructureValue()).isFalse(); 89 assertThat(value.isArrayValue()).isFalse(); 90 + assertThat(value.isSolvable()).isFalse(); 91 assertThat(value.isNullValue()).isFalse(); 92 assertDataIsCorrect( 93 value, "true", BigDecimal.ONE, true, Instant.EPOCH, Duration.ZERO, Boolean.class); ··· 401 assertThat(value.isBooleanValue()).isFalse(); 402 assertThat(value.isStructureValue()).isFalse(); 403 assertThat(value.isStringValue()).isFalse(); 404 + assertThat(value.isSolvable()).isFalse(); 405 assertThat(value.isNullValue()).isFalse(); 406 407 assertThat(value.getArrayValue()).hasSize(2); ··· 437 assertThat(value.isBooleanValue()).isFalse(); 438 assertThat(value.isStringValue()).isFalse(); 439 assertThat(value.isArrayValue()).isFalse(); 440 + assertThat(value.isSolvable()).isFalse(); 441 assertThat(value.isNullValue()).isFalse(); 442 443 assertThat(value.getStructureValue()).hasSize(2); ··· 463 @Test 464 void testExpressionNode() { 465 ASTNode node = ASTNode.of(new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT)); 466 + EvaluationValue value = 467 + EvaluationValue.of(defaultExpressionParser().toSolvable(node), defaultConfiguration()); 468 469 + assertThat(value.isSolvable()).isTrue(); 470 assertThat(value.isNumberValue()).isFalse(); 471 assertThat(value.isBooleanValue()).isFalse(); 472 assertThat(value.isStructureValue()).isFalse(); ··· 475 assertThat(value.isNullValue()).isFalse(); 476 477 assertDataIsCorrect( 478 + value, null, BigDecimal.ZERO, false, Instant.EPOCH, Duration.ZERO, Solvable.class); 479 } 480 481 @Test 482 void testExpressionNodeReturnsNull() { 483 EvaluationValue value = EvaluationValue.of(false, defaultConfiguration()); 484 + assertThat(value.getSolvable()).isNull(); 485 } 486 487 @Test ··· 526 assertThat(value.isBooleanValue()).isFalse(); 527 assertThat(value.isStructureValue()).isFalse(); 528 assertThat(value.isArrayValue()).isFalse(); 529 + assertThat(value.isSolvable()).isFalse(); 530 assertThat(value.isNullValue()).isTrue(); 531 assertDataIsCorrect(value, null, null, null); 532 } ··· 558 559 private void assertDataIsCorrect( 560 EvaluationValue value, String stringValue, BigDecimal numberValue, Boolean booleanValue) { 561 + if (stringValue != null) assertThat(value.getStringValue()).isEqualTo(stringValue); 562 assertThat(value.getNumberValue()).isEqualTo(numberValue); 563 assertThat(value.getBooleanValue()).isEqualTo(booleanValue); 564 } ··· 572 Duration durationValue, 573 Class<?> valueInstance) { 574 assertDataIsCorrect(value, stringValue, numberValue, booleanValue); 575 assertThat(value.getDateTimeValue()).isEqualTo(dateTimeValue); 576 assertThat(value.getDurationValue()).isEqualTo(durationValue); 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 20 import com.ezylang.evalex.config.ExpressionConfiguration; 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; 25 import java.math.BigDecimal; 26 import org.junit.jupiter.api.Test; 27 28 - class ExpressionNodeConverterTest { 29 30 private final ExpressionConfiguration defaultConfiguration = 31 ExpressionConfiguration.defaultConfiguration(); 32 33 - private final ExpressionNodeConverter converter = new ExpressionNodeConverter(); 34 35 - private final ASTNode testNode = 36 - ASTNode.of(new Token(1, "a", Token.TokenType.VARIABLE_OR_CONSTANT)); 37 38 @Test 39 void testDuration() { 40 - 41 EvaluationValue converted = converter.convert(testNode, defaultConfiguration); 42 43 - assertThat(converted).isInstanceOf(ExpressionNodeValue.class); 44 - assertThat(converted.getExpressionNode().toJSON()).isEqualTo(testNode.toJSON()); 45 } 46 47 @Test
··· 19 20 import com.ezylang.evalex.config.ExpressionConfiguration; 21 import com.ezylang.evalex.data.EvaluationValue; 22 + import com.ezylang.evalex.data.types.SolvableValue; 23 + import com.ezylang.evalex.data.types.StringValue; 24 + import com.ezylang.evalex.parser.Solvable; 25 import java.math.BigDecimal; 26 import org.junit.jupiter.api.Test; 27 28 + class SolvableConverterTest { 29 30 private final ExpressionConfiguration defaultConfiguration = 31 ExpressionConfiguration.defaultConfiguration(); 32 33 + private final SolvableConverter converter = new SolvableConverter(); 34 35 + private final Solvable testNode = context -> StringValue.of("Hello!"); 36 37 @Test 38 void testDuration() { 39 EvaluationValue converted = converter.convert(testNode, defaultConfiguration); 40 41 + assertThat(converted).isInstanceOf(SolvableValue.class); 42 + assertThat(converted.getSolvable()).isEqualTo(testNode); 43 } 44 45 @Test