From daac6e839fe576bb594e2ea6a99b9aff7c9cc6db Mon Sep 17 00:00:00 2001 From: Udo Klimaschewski Date: Mon, 26 Sep 2022 11:28:09 +0200 Subject: [PATCH] separates constants from variables, making constants constant (#304) also fixes some documentation typos and issues --- README.md | 2 +- docs/README.md | 9 +++-- docs/concepts/changes.md | 38 +++++++++---------- docs/concepts/parsing_evaluation.md | 3 +- docs/configuration/configuration.md | 9 ++--- docs/index.md | 2 +- .../java/com/ezylang/evalex/Expression.java | 29 +++++++++----- .../ExpressionEvaluatorConstantsTest.java | 4 +- ...xpressionEvaluatorSimpleVariablesTest.java | 8 ++++ 9 files changed, 60 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 5b0f36c..9422e0f 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Expression expression = new Expression("\"Hello \" + name + \", you are \" + age System.out.println(expression.evaluate().getStringValue()); // prints Hello Frank, you are 38 ``` -### Arrays are supported and can be passed as Java _Lists_. +### Arrays (also multidimensional) are supported and can be passed as Java _Lists_. See the [Documentation](https://ezylang.github.io/pages-playground/concepts/datatypes.html#array) for more details. diff --git a/docs/README.md b/docs/README.md index 10f9906..57ea0bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,17 +2,18 @@ For documentation, EvalEx uses GitHub Pages, which uses Jekyll to transform markdown to static HTML. -The site HTML is generated whenever something is committed to the main branch. +The sites HTML is generated automatically whenever something is committed to the main branch. -Jekyll will then transform all files it finds in the "docs" directory. +Jekyll will transform all files it finds in the "docs" directory. This may take a few minutes, +depending on the overall GitHub load. -For the layout the JustTheDocs template is used, which supports a lot of good things like a sidebar +For the layout the _JustTheDocs_ template is used, which supports a lot of good things like a sidebar menu, page hierarchy and searching. ## EvalEx documentation home: https://ezylang.github.io/EvalEx/ -## Using JustTheDocs template: +## Using _JustTheDocs_ template: https://just-the-docs.github.io/just-the-docs/ diff --git a/docs/concepts/changes.md b/docs/concepts/changes.md index 509e573..5074171 100644 --- a/docs/concepts/changes.md +++ b/docs/concepts/changes.md @@ -9,15 +9,15 @@ nav_order: 4 ### New features -There are a lot of new features, see the README of the [home page](../index.html) of this +There are a lot of new features, see the [home page](../index.html) of this documentation for more information. Most notably new features: * Better boolean support. * Full support for string operations and functions. -* Full support for arrays. -* Full support for structures. +* Full support for multidimensional arrays. +* Full support for nestable structures. * Structures and arrays can be combined to work with arbitrary data structures. * New data access support, connecting expressions with you data made easy. * New configuration concept. @@ -34,18 +34,17 @@ EvalEx 3 now requires at least Java 11 to run and compile. ### New package, group id and repository home. EvalEx has moved from a personal repository to an organizational repository. -This decouples the product from a single person and allows more ad better control over the product. +This decouples the product from a single person and allows more and better control over the product. ### Complete rewrite EvalEx 3 is a complete rewrite. After 10 years of adding features and trying to stay backward compatible, I felt that this is the time for a big cut. Trying to stay backward compatible has introduced several code constructs, that didn't feel good. -The lazy evaluation is one of that features to name. -This has direct impact to the integration of EvalEx in an existing application. +This cut has direct impact to the integration of EvalEx in an existing application. Though I believe that the new version has much better integration possibilities (e.g. the data -access interface, the separate configuration object), existing integration need some refactorings. +access interface, the separate configuration object), existing integrations need some refactorings. Depending on how you used EvalEx until now, this may not need very much changes, e.g.: @@ -63,8 +62,7 @@ Here are the main changes, when it comes to integration: Configurations are now separated from the expression. You can create a configuration once and then re-use it with your expressions. All the heavy constructor work is now in the configuration, making -the instantiation of an expression much faster. The new concept was designed with better thread -safety in mind. +the instantiation of an expression much faster. ### New default MathContext @@ -81,32 +79,34 @@ Now the parser transforms the infix notation to an AST (Abstract Syntax Tree), a improvements and new features. The main reason for the switch was the lazy evaluation needed for the IF function. With the use of an AST, this was easy to achieve. -If you were using the getRPN() function in EvalEx 2, then the new getAllASTNodes() -getAbstractSyntaxTree() method may be useful for you. -If you still need RPN support, it should be possible to create an RPN notation out of the AST. +If you were using the getRPN() function in EvalEx 2, then the new getAllASTNodes() or +getAbstractSyntaxTree() methods may be useful for you. +If you still need RPN support, it should be possible to create an RPN notation out of the AST with a +bit of effort. ### New return value type -EvalEx 2 evaluation always returned a BigDecimal. With the support of strings, arrays and +EvalEx 2 evaluation always returned a BigDecimal. With the new support of strings, arrays and structures, this was not suitable anymore. The new EvaluationValue return type for an evaluation has a type member that defines what data type it holds (boolean, number, string, array, structure, or an -AST). It also has some convenience methods to transform the types, e.g. number to string. +AST). It also has some convenience methods to convert the values, e.g. number to string. ### New data access concept -EvalEx 2 only had the possibility to pass and store variable values just before the evaluation into -a local java.util.Map data structure. This is also the default with EvalEx 3, so no change to +EvalEx 2 had an own storage for variable and constant values, that had to be filled with the +required values before evaluation. This is also the default with EvalEx 3, so no change to existing integrations is required. -The new concept allows additionally to configure a data access interface. +But the new concept allows additionally to configure a custom data access interface, which could be +backed by any kind of storage. See chapter [Data Access](../customization/data_access.html) for details. ### Custom Operators Adding custom operators is now easier, but has changed significantly. -See chapter [Custom Functions](../customization/custom_functions.html) for details. +See chapter [Custom Operators](../customization/custom_operators.html) for details. ### Custom function Adding custom functions is now easier, but has changed significantly. -See chapter [Custom Operators](../customization/custom_operators.html) for details. +See chapter [Custom Functions](../customization/custom_functions.html) for details. diff --git a/docs/concepts/parsing_evaluation.md b/docs/concepts/parsing_evaluation.md index 2f32c0c..f930da1 100644 --- a/docs/concepts/parsing_evaluation.md +++ b/docs/concepts/parsing_evaluation.md @@ -94,8 +94,7 @@ System.out.println(result.getNumberValue()); // prints 6.00 ``` Alternatively, the variable values can be set by defining a map with names and values and then -passing it to -the _withValues()_ method: +passing it to the _withValues()_ method: ```java Expression expression = new Expression("(a + b) * (a - b)"); diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 02d5b62..a1efe08 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -47,9 +47,6 @@ The supplier is called whenever a new Expression is created, so that each expres instance of an accessor. Custom implementations of the supplier and data accessor may allow expressions to share the same space. -Upon creation of an expression, the configured default constants will be added to the data accessor -through the set method. - ### Decimal Places Rounding Specifies the amount of decimal places to round to in each operation or function. @@ -58,9 +55,9 @@ See chapter [Precision, Scale and Rounding](../concepts/rounding.html) for detai ### Default Constants Specifies the default constants that can be used in every expression as a _Map_ with the constant -name and _EvaluationValue_ as value. -See the reference chapter for a -list: [Default Constants](../references/constants.html) +name and _EvaluationValue_ as value. Each expression has a case-insensitive copy of the default +constants. +See the reference chapter for a list: [Default Constants](../references/constants.html) ### Function Dictionary diff --git a/docs/index.md b/docs/index.md index 3279525..8c35a08 100644 --- a/docs/index.md +++ b/docs/index.md @@ -77,7 +77,7 @@ Expression expression = new Expression("\"Hello \" + name + \", you are \" + age System.out.println(expression.evaluate().getStringValue()); // prints Hello Frank, you are 38 ``` -Arrays are supported and can be passed as Java _Lists_: +Arrays (also multidimensional) are supported and can be passed as Java _Lists_: ```java Expression expression = new Expression("values[i-1] * factors[i-1]"); diff --git a/src/main/java/com/ezylang/evalex/Expression.java b/src/main/java/com/ezylang/evalex/Expression.java index af36f59..5b53736 100644 --- a/src/main/java/com/ezylang/evalex/Expression.java +++ b/src/main/java/com/ezylang/evalex/Expression.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import lombok.Getter; @@ -44,6 +45,10 @@ public class Expression { @Getter private final DataAccessorIfc dataAccessor; + @Getter + private final Map constants = + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private ASTNode abstractSyntaxTree; /** @@ -66,12 +71,7 @@ public class Expression { this.expressionString = expressionString; this.configuration = configuration; this.dataAccessor = configuration.getDataAccessorSupplier().get(); - - // add default constants to data - for (Map.Entry constant : - configuration.getDefaultConstants().entrySet()) { - getDataAccessor().setData(constant.getKey(), constant.getValue()); - } + this.constants.putAll(configuration.getDefaultConstants()); } /** @@ -103,7 +103,7 @@ public class Expression { result = new EvaluationValue(token.getValue()); break; case VARIABLE_OR_CONSTANT: - result = getDataAccessor().getData(token.getValue()); + result = getVariableOrConstant(token); if (result.isExpressionNode()) { result = evaluateSubtree(result.getExpressionNode()); } @@ -141,6 +141,18 @@ public class Expression { return result.isNumberValue() ? roundAndStripZerosIfNeeded(result) : result; } + private EvaluationValue getVariableOrConstant(Token token) throws EvaluationException { + EvaluationValue result = constants.get(token.getValue()); + if (result == null) { + result = getDataAccessor().getData(token.getValue()); + } + if (result == null) { + throw new EvaluationException( + token, String.format("Variable or constant value for '%s' not found", token.getValue())); + } + return result; + } + private EvaluationValue evaluateFunction(ASTNode startNode, Token token) throws EvaluationException { List parameterResults = new ArrayList<>(); @@ -331,8 +343,7 @@ public class Expression { for (ASTNode node : getAllASTNodes()) { if (node.getToken().getType() == Token.TokenType.VARIABLE_OR_CONSTANT - && !configuration.getDefaultConstants().containsKey(node.getToken().getValue()) - && !variables.contains(node.getToken().getValue())) { + && !constants.containsKey(node.getToken().getValue())) { variables.add(node.getToken().getValue()); } } diff --git a/src/test/java/com/ezylang/evalex/ExpressionEvaluatorConstantsTest.java b/src/test/java/com/ezylang/evalex/ExpressionEvaluatorConstantsTest.java index 34432bd..1baedd4 100644 --- a/src/test/java/com/ezylang/evalex/ExpressionEvaluatorConstantsTest.java +++ b/src/test/java/com/ezylang/evalex/ExpressionEvaluatorConstantsTest.java @@ -46,7 +46,7 @@ class ExpressionEvaluatorConstantsTest extends BaseExpressionEvaluatorTest { } @Test - void testCustomConstants() throws EvaluationException, ParseException { + void testCustomConstantsMixedCase() throws EvaluationException, ParseException { Map constants = new HashMap<>() { { @@ -58,7 +58,7 @@ class ExpressionEvaluatorConstantsTest extends BaseExpressionEvaluatorTest { ExpressionConfiguration configuration = ExpressionConfiguration.builder().defaultConstants(constants).build(); - Expression expression = new Expression("a+b", configuration); + Expression expression = new Expression("a+B", configuration); assertThat(expression.evaluate().getStringValue()).isEqualTo("6.4"); } diff --git a/src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleVariablesTest.java b/src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleVariablesTest.java index b665f8c..9b4396d 100644 --- a/src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleVariablesTest.java +++ b/src/test/java/com/ezylang/evalex/ExpressionEvaluatorSimpleVariablesTest.java @@ -16,6 +16,7 @@ package com.ezylang.evalex; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.ezylang.evalex.data.EvaluationValue; import com.ezylang.evalex.parser.ParseException; @@ -74,4 +75,11 @@ class ExpressionEvaluatorSimpleVariablesTest extends BaseExpressionEvaluatorTest assertThat(result.isStringValue()).isTrue(); assertThat(result.getStringValue()).isEqualTo("Hello2world"); } + + @Test + void testUnknownVariable() { + assertThatThrownBy(() -> createExpression("a").evaluate()) + .isInstanceOf(EvaluationException.class) + .hasMessage("Variable or constant value for 'a' not found"); + } } -- 2.51.2