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

We did it reddit, we fixed inlining.

+233 -33
+1 -1
gradle.properties
··· 2 2 org.gradle.daemon=true 3 3 org.gradle.caching=true 4 4 5 - version=4.0.0 5 + version=4.1.0
+4
src/main/java/me/melontini/mevalex/functions/FunctionIfc.java
··· 88 88 int numOfParameters = getFunctionParameterDefinitions().size(); 89 89 return hasVarArgs() ? numOfParameters - 1 : numOfParameters; 90 90 } 91 + 92 + default boolean canInline() { 93 + return true; 94 + } 91 95 }
+5
src/main/java/me/melontini/mevalex/functions/basic/RandomFunction.java
··· 32 32 33 33 return context.expression().convertDoubleValue(secureRandom.nextDouble()); 34 34 } 35 + 36 + @Override 37 + public boolean canInline() { 38 + return false; 39 + } 35 40 }
+5
src/main/java/me/melontini/mevalex/functions/datetime/DateTimeNowFunction.java
··· 43 43 EvaluationContext context, Token functionToken, EvaluationValue... parameterValues) { 44 44 return DateTimeValue.of(Instant.now()); 45 45 } 46 + 47 + @Override 48 + public boolean canInline() { 49 + return false; 50 + } 46 51 }
+5
src/main/java/me/melontini/mevalex/functions/datetime/DateTimeTodayFunction.java
··· 64 64 } 65 65 return expression.getConfiguration().getZoneId(); 66 66 } 67 + 68 + @Override 69 + public boolean canInline() { 70 + return false; 71 + } 67 72 }
+4
src/main/java/me/melontini/mevalex/operators/OperatorIfc.java
··· 129 129 EvaluationValue evaluate( 130 130 EvaluationContext context, Token operatorToken, EvaluationValue... operands) 131 131 throws EvaluationException; 132 + 133 + default boolean canInline() { 134 + return true; 135 + } 132 136 }
+3 -3
src/main/java/me/melontini/mevalex/parser/ASTNode.java
··· 43 43 public static final ASTNode[] EMPTY = new ASTNode[0]; 44 44 45 45 /** The children od the tree. */ 46 - ASTNode[] parameters; 46 + protected ASTNode[] parameters; 47 47 48 48 /** The token associated with this tree node. */ 49 - Token token; 49 + protected Token token; 50 50 51 51 protected ASTNode(Token token, ASTNode... parameters) { 52 52 this.token = token; 53 - this.parameters = parameters; 53 + this.parameters = parameters.length == 0 ? EMPTY : parameters; 54 54 } 55 55 56 56 public static ASTNode of(Token token) {
+169 -5
src/main/java/me/melontini/mevalex/parser/ExpressionParser.java
··· 15 15 */ 16 16 package me.melontini.mevalex.parser; 17 17 18 + import java.util.Arrays; 19 + import java.util.Objects; 18 20 import lombok.Getter; 21 + import me.melontini.mevalex.EvaluationContext; 19 22 import me.melontini.mevalex.EvaluationException; 20 23 import me.melontini.mevalex.Expression; 21 24 import me.melontini.mevalex.config.ExpressionConfiguration; ··· 39 42 this.converter = new ShuntingYardConverter(configuration); 40 43 } 41 44 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); 45 + public Expression parse(String expression) throws ParseException, EvaluationException { 46 + ASTNode root = converter.toAbstractSyntaxTree(tokenizer.parse(expression), expression); 47 + var proxy = new Expression(expression, toSolvable(root), configuration); 48 + return new Expression(expression, toSolvable(inline(proxy, root)), configuration); 49 + } 50 + 51 + public ASTNode inline(Expression parent, ASTNode node) throws EvaluationException { 52 + if (node instanceof InlinedASTNode) return tryRound(parent, node); 53 + 54 + // We declare the index not inlineable, but its parameters on the other hand... 55 + var parameters = node.getParameters(); 56 + for (int i = 0; i < parameters.length; i++) { 57 + switch (parameters[i].getToken().getType()) { 58 + case ARRAY_INDEX, STRUCTURE_SEPARATOR -> parameters[i] = inline(parent, parameters[i]); 59 + } 60 + } 61 + 62 + Token token = node.getToken(); 63 + return tryRound( 64 + parent, 65 + switch (token.getType()) { 66 + case VARIABLE_OR_CONSTANT -> { 67 + if (!configuration.isAllowOverwriteConstants()) { 68 + var result = configuration.getConstants().get(token.getValue()); 69 + if (result != null) yield InlinedASTNode.of(token, result); 70 + } 71 + yield node; 72 + } 73 + case PREFIX_OPERATOR, POSTFIX_OPERATOR -> inlinePrePostfix(parent, token, node); 74 + case INFIX_OPERATOR -> inlineInfix(parent, token, node); 75 + case FUNCTION -> inlineFunction(parent, token, node); 76 + case ARRAY_INDEX -> { 77 + for (int i1 = 0; i1 < 2; i1++) 78 + node.getParameters()[i1] = inline(parent, node.getParameters()[i1]); 79 + yield node; 80 + } 81 + case STRUCTURE_SEPARATOR -> { 82 + node.getParameters()[0] = inline(parent, node.getParameters()[0]); 83 + yield node; 84 + } 85 + default -> throw new IllegalStateException("Unexpected evaluation token: " + token); 86 + }); 87 + } 88 + 89 + private ASTNode tryRound(Expression parent, ASTNode node) { 90 + if (!(node instanceof InlinedASTNode inlined)) return node; 91 + 92 + var result = parent.tryRoundValue(inlined.value()); 93 + if (Objects.equals(result.getValue(), inlined.value().getValue())) return inlined; 94 + return InlinedASTNode.of(node.getToken(), result, node.getParameters()); 95 + } 96 + 97 + private ASTNode inlineFunction(Expression parent, Token token, ASTNode node) 98 + throws EvaluationException { 99 + var function = token.getFunctionDefinition(); 100 + if (!function.canInline()) return node; 101 + var parameters = node.getParameters(); 102 + 103 + EvaluationValue[] result = new EvaluationValue[parameters.length]; 104 + boolean allMatch = true; 105 + for (int i = 0; i < parameters.length; i++) { 106 + ASTNode parameter = parameters[i]; 107 + 108 + if (function.isParameterLazy(i)) { 109 + if (!canInline(parameter)) allMatch = false; 110 + result[i] = SolvableValue.of(toSolvable(node)); 111 + } else { 112 + parameters[i] = inline(parent, parameters[i]); 113 + if (!(parameters[i] instanceof InlinedASTNode inlined)) { 114 + allMatch = false; 115 + continue; 116 + } 117 + result[i] = inlined.value(); 118 + } 119 + } 120 + if (!allMatch) return node; 121 + 122 + return InlinedASTNode.of( 123 + token, 124 + function.evaluate(EvaluationContext.builder(parent).build(), token, result), 125 + parameters); 126 + } 127 + 128 + private ASTNode inlineInfix(Expression parent, Token token, ASTNode node) 129 + throws EvaluationException { 130 + var operator = token.getOperatorDefinition(); 131 + var parameters = node.getParameters(); 132 + 133 + if (!operator.isOperandLazy()) { 134 + boolean allMatch = true; 135 + for (int i = 0; i < 2; i++) { 136 + if (!((parameters[i] = inline(parent, parameters[i])) instanceof InlinedASTNode)) 137 + allMatch = false; 138 + } 139 + if (!allMatch) return node; 140 + 141 + if (operator.canInline()) { 142 + return InlinedASTNode.of( 143 + token, 144 + operator.evaluate( 145 + EvaluationContext.builder(parent).build(), 146 + token, 147 + Arrays.stream(parameters) 148 + .map(node1 -> ((InlinedASTNode) node1).value()) 149 + .toArray(EvaluationValue[]::new)), 150 + parameters); 151 + } 152 + return node; 153 + } else { 154 + if (!operator.canInline()) return node; 155 + 156 + SolvableValue[] lazy = new SolvableValue[parameters.length]; 157 + for (int i = 0; i < parameters.length; i++) { 158 + ASTNode parameter = parameters[i]; 159 + if (!canInline(parameter)) return node; 160 + lazy[i] = SolvableValue.of(toSolvable(parameter)); 161 + } 162 + return InlinedASTNode.of( 163 + token, 164 + operator.evaluate(EvaluationContext.builder(parent).build(), token, lazy), 165 + parameters); 166 + } 167 + } 168 + 169 + /** 170 + * When working with lazy operand we cannot immediately inline the operand as it can throw an 171 + * {@link EvaluationException}. 172 + * 173 + * @return If the node can be safely inlined. 174 + */ 175 + private boolean canInline(ASTNode node) { 176 + if (node instanceof InlinedASTNode) return true; 177 + 178 + Token token = node.getToken(); 179 + return switch (token.getType()) { 180 + case VARIABLE_OR_CONSTANT -> !configuration.isAllowOverwriteConstants() 181 + && configuration.getConstants().containsKey(token.getValue()); 182 + case PREFIX_OPERATOR, POSTFIX_OPERATOR, INFIX_OPERATOR -> { 183 + if (!token.getOperatorDefinition().canInline()) yield false; 184 + for (ASTNode parameter : node.getParameters()) { 185 + if (!canInline(parameter)) yield false; 186 + } 187 + yield true; 188 + } 189 + case FUNCTION -> { 190 + if (!token.getFunctionDefinition().canInline()) yield false; 191 + for (ASTNode parameter : node.getParameters()) { 192 + if (!canInline(parameter)) yield false; 193 + } 194 + yield true; 195 + } 196 + default -> false; 197 + }; 198 + } 199 + 200 + private ASTNode inlinePrePostfix(Expression parent, Token token, ASTNode node) 201 + throws EvaluationException { 202 + var operator = token.getOperatorDefinition(); 203 + node.getParameters()[0] = inline(parent, node.getParameters()[0]); 204 + if (node.getParameters()[0] instanceof InlinedASTNode inlined && operator.canInline()) { 205 + return InlinedASTNode.of( 206 + token, 207 + operator.evaluate(EvaluationContext.builder(parent).build(), token, inlined.value()), 208 + node.getParameters()); 209 + } 210 + return node; 47 211 } 48 212 49 213 public Solvable toSolvable(ASTNode node) {
+14 -2
src/main/java/me/melontini/mevalex/parser/InlinedASTNode.java
··· 15 15 */ 16 16 package me.melontini.mevalex.parser; 17 17 18 + import java.util.Arrays; 19 + import java.util.stream.Collectors; 18 20 import lombok.AccessLevel; 19 21 import lombok.EqualsAndHashCode; 20 22 import lombok.Getter; ··· 47 49 return new InlinedASTNode(token, constant, nodes); 48 50 } 49 51 50 - static InlinedASTNode trusted(Token token, EvaluationValue constant, ASTNode... nodes) { 51 - return new InlinedASTNode(token, constant, nodes); 52 + public String toJSON() { 53 + if (parameters.length == 0) { 54 + return String.format( 55 + "{" + "\"type\":\"%s\",\"value\":\"%s\",\"result\":\"%s\"}", 56 + token.getType(), token.getValue(), value.getStringValue()); 57 + } else { 58 + String childrenJson = 59 + Arrays.stream(parameters).map(ASTNode::toJSON).collect(Collectors.joining(",")); 60 + return String.format( 61 + "{" + "\"type\":\"%s\",\"value\":\"%s\",\"result\":\"%s\",\"children\":[%s]}", 62 + token.getType(), token.getValue(), value.getStringValue(), childrenJson); 63 + } 52 64 } 53 65 54 66 @Override
+1 -1
src/test/java/me/melontini/mevalex/BaseExpressionEvaluatorTest.java
··· 32 32 return expression.evaluate(EvaluationContext.builder(expression).build()).getStringValue(); 33 33 } 34 34 35 - Expression createExpression(String expressionString) throws ParseException { 35 + Expression createExpression(String expressionString) throws ParseException, EvaluationException { 36 36 return parser.parse(expressionString); 37 37 } 38 38 }
+1 -1
src/test/java/me/melontini/mevalex/ExpressionEvaluationExceptionsTest.java
··· 26 26 class ExpressionEvaluationExceptionsTest { 27 27 28 28 @Test 29 - void testUnexpectedToken() throws ParseException { 29 + void testUnexpectedToken() throws ParseException, EvaluationException { 30 30 Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("1"); 31 31 32 32 assertThatThrownBy(
+1 -1
src/test/java/me/melontini/mevalex/ExpressionEvaluationMultiThreadedTest.java
··· 30 30 31 31 class ExpressionEvaluationMultiThreadedTest { 32 32 @Test 33 - void testThreadLocal() throws InterruptedException, ParseException { 33 + void testThreadLocal() throws InterruptedException, ParseException, EvaluationException { 34 34 35 35 AtomicInteger errorCount = new AtomicInteger(); 36 36
+1 -1
src/test/java/me/melontini/mevalex/ExpressionEvaluatorConstantsTest.java
··· 92 92 } 93 93 94 94 @Test 95 - void testOverwriteConstantsNotAllowed() throws ParseException { 95 + void testOverwriteConstantsNotAllowed() throws ParseException, EvaluationException { 96 96 Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("e"); 97 97 assertThatThrownBy(() -> expression.evaluate(builder -> builder.parameter("e", 9))) 98 98 .isInstanceOf(UnsupportedOperationException.class)
+1 -1
src/test/java/me/melontini/mevalex/ExpressionEvaluatorNullTest.java
··· 76 76 } 77 77 78 78 @Test 79 - void testFailWithNoHandling() throws ParseException { 79 + void testFailWithNoHandling() throws ParseException, EvaluationException { 80 80 Expression expression1 = createExpression("a * 5"); 81 81 assertThatThrownBy(() -> expression1.evaluate(builder -> builder.parameter("a", null))) 82 82 .isInstanceOf(EvaluationException.class)
+6 -5
src/test/java/me/melontini/mevalex/ExpressionTest.java
··· 35 35 class ExpressionTest { 36 36 37 37 @Test 38 - void testExpressionDefaults() throws ParseException { 38 + void testExpressionDefaults() throws ParseException, EvaluationException { 39 39 Expression expression = ExpressionConfiguration.defaultExpressionParser().parse("a+b"); 40 40 41 41 assertThat(expression.getExpressionString()).isEqualTo("a+b"); ··· 51 51 } 52 52 53 53 @Test 54 - void testValidateOK() throws ParseException { 54 + void testValidateOK() throws ParseException, EvaluationException { 55 55 ExpressionConfiguration.defaultExpressionParser().parse("1+1"); 56 56 } 57 57 ··· 131 131 132 132 @SuppressWarnings("Convert2Lambda") 133 133 @Test 134 - void testDefaultExpressionOwnsOwnConfigurationEntries() throws ParseException { 134 + void testDefaultExpressionOwnsOwnConfigurationEntries() 135 + throws ParseException, EvaluationException { 135 136 Supplier<ExpressionConfiguration> configuration = 136 137 () -> 137 138 ExpressionConfiguration.builder() ··· 158 159 } 159 160 160 161 @Test 161 - void testDoubleConverterDefaultMathContext() throws ParseException { 162 + void testDoubleConverterDefaultMathContext() throws ParseException, EvaluationException { 162 163 Expression defaultMathContextExpression = 163 164 ExpressionConfiguration.defaultExpressionParser().parse("1"); 164 165 assertThat(defaultMathContextExpression.convertDoubleValue(1.67987654321).getNumberValue()) ··· 166 167 } 167 168 168 169 @Test 169 - void testDoubleConverterLimitedMathContext() throws ParseException { 170 + void testDoubleConverterLimitedMathContext() throws ParseException, EvaluationException { 170 171 Expression limitedMathContextExpression = 171 172 new ExpressionParser( 172 173 ExpressionConfiguration.builder().mathContext(new MathContext(3)).build())
+3 -3
src/test/java/me/melontini/mevalex/parser/ShuntingYardArrayTest.java
··· 23 23 void testSimpleArray() throws ParseException { 24 24 assertASTTreeIsEqualTo( 25 25 "a[0]", 26 - "{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"0\"}]}"); 26 + "{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"0\",\"result\":\"0\"}]}"); 27 27 } 28 28 29 29 @Test ··· 37 37 void testArrayNested() throws ParseException { 38 38 assertASTTreeIsEqualTo( 39 39 "a[b[1]]", 40 - "{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}]}"); 40 + "{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"}]}]}"); 41 41 } 42 42 43 43 @Test 44 44 void testComplex() throws ParseException { 45 45 assertASTTreeIsEqualTo( 46 46 "a[b[100*(a+b)]-c[2+d[x+y]]]", 47 - "{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"100\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"}]}]}]},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"c\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"d\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"x\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"y\"}]}]}]}]}]}]}"); 47 + "{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"100\",\"result\":\"100\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"a\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"b\"}]}]}]},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"c\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"},{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"d\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"x\"},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"y\"}]}]}]}]}]}]}"); 48 48 } 49 49 }
+8 -8
src/test/java/me/melontini/mevalex/parser/ShuntingYardConverterTest.java
··· 21 21 22 22 @Test 23 23 void testSingleNumber() throws ParseException { 24 - assertASTTreeIsEqualTo("1", "{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}"); 24 + assertASTTreeIsEqualTo("1", "{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"}"); 25 25 } 26 26 27 27 @Test ··· 33 33 void testPrefix() throws ParseException { 34 34 assertASTTreeIsEqualTo( 35 35 "-1", 36 - "{\"type\":\"PREFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}"); 36 + "{\"type\":\"PREFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"}]}"); 37 37 } 38 38 39 39 @Test 40 40 void testPostfix() throws ParseException { 41 41 assertASTTreeIsEqualTo( 42 42 "1?", 43 - "{\"type\":\"POSTFIX_OPERATOR\",\"value\":\"?\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}"); 43 + "{\"type\":\"POSTFIX_OPERATOR\",\"value\":\"?\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"}]}"); 44 44 } 45 45 46 46 @Test 47 47 void testPrefixPostfix() throws ParseException { 48 48 assertASTTreeIsEqualTo( 49 49 "-1?", 50 - "{\"type\":\"PREFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"POSTFIX_OPERATOR\",\"value\":\"?\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}]}"); 50 + "{\"type\":\"PREFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"POSTFIX_OPERATOR\",\"value\":\"?\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"}]}]}"); 51 51 } 52 52 53 53 @Test 54 54 void testSequential() throws ParseException { 55 55 assertASTTreeIsEqualTo( 56 56 "1+2+3-3-2-1", 57 - "{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"}]}"); 57 + "{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"}]}"); 58 58 } 59 59 60 60 @Test 61 61 void testPrecedence() throws ParseException { 62 62 assertASTTreeIsEqualTo( 63 63 "1+2*3-3^2-1/4", 64 - "{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]}]},{\"type\":\"INFIX_OPERATOR\",\"value\":\"^\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]}]},{\"type\":\"INFIX_OPERATOR\",\"value\":\"/\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"4\"}]}]}"); 64 + "{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"}]}]},{\"type\":\"INFIX_OPERATOR\",\"value\":\"^\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"}]}]},{\"type\":\"INFIX_OPERATOR\",\"value\":\"/\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"4\",\"result\":\"4\"}]}]}"); 65 65 } 66 66 67 67 @Test 68 68 void testBraces() throws ParseException { 69 69 assertASTTreeIsEqualTo( 70 70 "2*(1/(2+3))", 71 - "{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"/\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]}]}]}"); 71 + "{\"type\":\"INFIX_OPERATOR\",\"value\":\"*\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"/\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"},{\"type\":\"INFIX_OPERATOR\",\"value\":\"+\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"}]}]}]}"); 72 72 } 73 73 74 74 @Test 75 75 void testFunctions() throws ParseException { 76 76 assertASTTreeIsEqualTo( 77 77 "MAX(1,2,3)-MIN(3,2,SUM(1,2,3))", 78 - "{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"FUNCTION\",\"value\":\"MAX\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]},{\"type\":\"FUNCTION\",\"value\":\"MIN\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"FUNCTION\",\"value\":\"SUM\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\"}]}]}]}"); 78 + "{\"type\":\"INFIX_OPERATOR\",\"value\":\"-\",\"children\":[{\"type\":\"FUNCTION\",\"value\":\"MAX\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"}]},{\"type\":\"FUNCTION\",\"value\":\"MIN\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"},{\"type\":\"FUNCTION\",\"value\":\"SUM\",\"children\":[{\"type\":\"NUMBER_LITERAL\",\"value\":\"1\",\"result\":\"1\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"3\",\"result\":\"3\"}]}]}]}"); 79 79 } 80 80 }
+1 -1
src/test/java/me/melontini/mevalex/parser/ShuntingYardStructureTest.java
··· 40 40 void testArrayCombination() throws ParseException { 41 41 assertASTTreeIsEqualTo( 42 42 "order[4].position[2].amount", 43 - "{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"order\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"4\"}]},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"position\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\"}]},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"amount\"}]}"); 43 + "{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"STRUCTURE_SEPARATOR\",\"value\":\".\",\"children\":[{\"type\":\"ARRAY_INDEX\",\"value\":\"[\",\"children\":[{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"order\"},{\"type\":\"NUMBER_LITERAL\",\"value\":\"4\",\"result\":\"4\"}]},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"position\"}]},{\"type\":\"NUMBER_LITERAL\",\"value\":\"2\",\"result\":\"2\"}]},{\"type\":\"VARIABLE_OR_CONSTANT\",\"value\":\"amount\"}]}"); 44 44 } 45 45 46 46 @Test