Skip to content

Commit

Permalink
Merge pull request #13 from ridencww/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ridencww authored Nov 8, 2018
2 parents 2d4d852 + 2418887 commit 3bfdca0
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 54 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ If you're using Maven for your project, add the following to your project's pom.
<dependency>
<groupId>com.creativewidgetworks</groupId>
<artifactId>expression-evaluator</artifactId>
<version>2.2.5</version>
<version>2.2.8</version>
</dependency>

## Data types
Expand Down Expand Up @@ -127,6 +127,11 @@ Examples:
RESULT: ABCDEF [string]

## Version History
2.2.8

2.2.8
* Fixed issue with FormatByLen where the parser's added constants and functions were cleared.
* Added copy constructor in Parser so sub-parsers like the one used in FormatByLen can inherit functions and other settings from parent parser.

2.2.7
* Fixed issue with tenary statements containing references to empty arrays throwing exceptions.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<groupId>com.creativewidgetworks</groupId>
<artifactId>expression-evaluator</artifactId>
<version>2.2.7</version>
<version>2.2.8</version>

<packaging>jar</packaging>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ public Value _FORMATBYLEN(Token function, Stack<Token> stack) throws ParserExcep

// Create temp parser if one hasn't already been created
if (tmpParser == null) {
tmpParser = new Parser();
tmpParser = new Parser(parser);
}

String variations = stack.pop().asString();
Expand Down
71 changes: 58 additions & 13 deletions src/main/java/com/creativewidgetworks/expressionparser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.text.ParseException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Parser {
// Internal VO class to hold a function's argument count
Expand All @@ -32,19 +33,20 @@ public class ArgCount { public boolean haveArgs; public int count; }

// RegEx tokenizer - package level for testing
private boolean caseSensitive;
private final String expressionDelimiter;
private Pattern combinedPattern;
private String expressionDelimiter;
final Map<String,List<Token>> tokenizedExpressions = new HashMap<>();

// Status
private ParserException lastException;
private String lastExpression;

// Containers for constants, functions, and variables
private final Map<String, BigDecimal> constants = new HashMap<>();
private final Map<String, Function> functions = new HashMap<>();
private final Map<String, Value> globals = new TreeMap<>();
private final Map<String, Value> variables = new TreeMap<>();

private Map<String, BigDecimal> constants = new HashMap<>();
private Map<String, Function> functions = new HashMap<>();
private Map<String, Value> globals = new TreeMap<>();
private Map<String, Value> variables = new TreeMap<>();
private FieldInterface fieldInterface;

private boolean suppressParseExceptions;
Expand All @@ -56,6 +58,21 @@ public Parser() {
clearFunctions();
}

public Parser(Parser parser) {
this();
allowProperties = parser.allowProperties;
expressionDelimiter = parser.expressionDelimiter;
fieldInterface = parser.fieldInterface;
localTimeZone = parser.localTimeZone;
precision = parser.precision;
suppressParseExceptions = parser.suppressParseExceptions;
caseSensitive = parser.getCaseSensitive();
constants = parser.getConstants();
functions = parser.getFunctions();
globals = parser.getGlobalVariables();
variables = parser.getVariables();
}

/*----------------------------------------------------------------------------*/

public boolean setAllowProperties(boolean allowProperties) {
Expand Down Expand Up @@ -110,20 +127,20 @@ public String listOfNullParameters(Stack<Token> stack, int argCount) {
public void addConstant(String name, BigDecimal value) {
if (name != null) {
constants.put(caseSensitive ? name : name.toUpperCase(), value);
TokenType.invalidatePattern();
invalidatePattern();
}
}

public void clearConstant(String name) {
constants.remove(caseSensitive ? name : name.toUpperCase());
TokenType.invalidatePattern();
invalidatePattern();
}

public void clearConstants() {
constants.clear();
addConstant("null", null);
addConstant("pi", BigDecimal.valueOf(Math.PI));
TokenType.invalidatePattern();
invalidatePattern();
}

public BigDecimal getConstant(String name) {
Expand Down Expand Up @@ -181,13 +198,13 @@ public FieldInterface setFieldInterface(FieldInterface fieldInterface) {
public void addFunction(Function function) {
if (function != null) {
functions.put(caseSensitive ? function.getName() : function.getName().toUpperCase(), function);
TokenType.invalidatePattern();
invalidatePattern();
}
}

public void clearFunction(String name) {
functions.remove(caseSensitive ? name : name.toUpperCase());
TokenType.invalidatePattern();
invalidatePattern();
}

public void clearFunctions() {
Expand All @@ -199,9 +216,37 @@ public void clearFunctions() {
addFunction(new Function("setGlobal", this, "_SETGLOBAL", 2, 2, ValueType.STRING));
addFunction(new Function("now", this, "_NOW", 0, 1));
addFunction(new Function("precision", this, "_PRECISION", 1, 1, ValueType.NUMBER));
TokenType.invalidatePattern();
invalidatePattern();
}

/*---------------------------------------------------------------------------------*/

/**
* Returns a regex that will be used to parse OPERATOR tokens
* @param parser instance using the TokenType
* @return String, regex expression
*/
public Pattern getPattern(Parser parser) {
if (combinedPattern == null) {
StringBuilder sb = new StringBuilder();
for (TokenType tokenType : TokenType.values()) {
sb.append(String.format("|(?<%s>%s)", tokenType.name(), tokenType.getRegex(parser)));
}

int options = parser.getCaseSensitive() ? Pattern.UNICODE_CASE : Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE;
options |= Pattern.UNICODE_CHARACTER_CLASS;
combinedPattern = Pattern.compile(sb.substring(1), options);
}
return combinedPattern;
}


public void invalidatePattern() {
combinedPattern = null;
}

/*---------------------------------------------------------------------------------*/

public Function getFunction(String functionName) {
return functionName == null ? null : functions.get(caseSensitive ? functionName : functionName.toUpperCase());
}
Expand Down Expand Up @@ -447,7 +492,7 @@ public List<Token> tokenize(String input, boolean wantWhitespace) throws ParserE

List<Token> tokens = new ArrayList<>();

Matcher matcher = TokenType.getPattern(this).matcher(input);
Matcher matcher = getPattern(this).matcher(input);
while (matcher.find()) {
if (wantWhitespace || matcher.group(TokenType.WHITESPACE.name()) == null) {
for (TokenType tokenType : TokenType.values()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ enum TokenType {
* @/metadata/number1/<sp>/<sp>@/metadata/number2/
*/

private static Pattern combinedPattern;

private final String regex;
private final Pattern pattern;

Expand Down Expand Up @@ -166,30 +164,4 @@ String resolve(String text) {
return text;
}

/*---------------------------------------------------------------------------------*/

/**
* Returns a regex that will be used to parse OPERATOR tokens
* @param parser instance using the TokenType
* @return String, regex expression
*/
public static Pattern getPattern(Parser parser) {
if (combinedPattern == null) {
StringBuilder sb = new StringBuilder();
for (TokenType tokenType : TokenType.values()) {
sb.append(String.format("|(?<%s>%s)", tokenType.name(), tokenType.getRegex(parser)));
}

int options = parser.getCaseSensitive() ? Pattern.UNICODE_CASE : Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE;
options |= Pattern.UNICODE_CHARACTER_CLASS;
combinedPattern = Pattern.compile(sb.substring(1), options);
}
return combinedPattern;
}

/*---------------------------------------------------------------------------------*/

public static void invalidatePattern() {
combinedPattern = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ public void test_add_constant_case_sensitive() {

@Test
public void test_add_invalidates_tokenType_pattern() {
String pattern = TokenType.getPattern(parser).pattern();
String pattern = parser.getPattern(parser).pattern();
parser.addConstant("my-constant", BigDecimal.valueOf(Math.E));
assertTrue("should have constant regex", TokenType.getPattern(parser).pattern().contains("MY-CONSTANT"));
assertTrue("should have constant regex", parser.getPattern(parser).pattern().contains("MY-CONSTANT"));
}

@Test
Expand All @@ -80,9 +80,9 @@ public void testClearConstants() {
@Test
public void test_clear_invalidates_tokenType_pattern() {
parser.addConstant("my-constant", BigDecimal.valueOf(Math.E));
assertTrue("should have constant regex", TokenType.getPattern(parser).pattern().contains("MY-CONSTANT"));
assertTrue("should have constant regex", parser.getPattern(parser).pattern().contains("MY-CONSTANT"));
parser.clearConstants();
assertFalse("should not have constant regex", TokenType.getPattern(parser).pattern().contains("MY-CONSTANT"));
assertFalse("should not have constant regex", parser.getPattern(parser).pattern().contains("MY-CONSTANT"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public void test_add_function_case_insensitive() {

@Test
public void test_add_invalidatss_tokenType_pattern() {
assertFalse("should not have function regex", TokenType.getPattern(parser).pattern().contains("testme"));
assertFalse("should not have function regex", parser.getPattern(parser).pattern().contains("testme"));
parser.addFunction(new Function("testme", this, "_ALPHA", 0, 0));
assertTrue("should have function regex", TokenType.getPattern(parser).pattern().contains("testme"));
assertTrue("should have function regex", parser.getPattern(parser).pattern().contains("testme"));
}

@Test
Expand All @@ -85,9 +85,9 @@ public void testClearFunctions() {
@Test
public void test_clear_invalidatss_tokenType_pattern() {
parser.addFunction(new Function("testme", this, "_ALPHA", 0, 0));
assertTrue("should have function regex", TokenType.getPattern(parser).pattern().contains("testme"));
assertTrue("should have function regex", parser.getPattern(parser).pattern().contains("testme"));
parser.clearFunctions();
assertFalse("should not have function regex", TokenType.getPattern(parser).pattern().contains("testme"));
assertFalse("should not have function regex", parser.getPattern(parser).pattern().contains("testme"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,29 @@ public void testFORMATBYLEN() throws Exception {
validateBooleanResult(parser, "A=FORMATBYLEN(null, null, null)", Boolean.TRUE);
}

@Test
public void testFORMATBYLEN_preserve_patterns() throws Exception {
// Forces creation of expression regex pattern object
parser.eval("1 + 1");

// FormatByLen creates an internal parser instance for its use.
String variations = "0=:7= ###-####:10=(###) ###-####:?='invalid'";
validateStringResult(parser, "FORMATBYLEN('1', '[0-9]*', \"" + variations + "\")", "invalid");

// Afterwards, the original pattern object in parser should remain - Do not use FORMATBYLEN for test due to token cacheing
validateBooleanResult(parser, "ISNUMBER('1')", Boolean.TRUE);
}

@Test
public void testFORMATBYLEN_inherits_parent_patterns() throws Exception {
// Forces creation of expression regex pattern object
parser.eval("1 + 1");

// FormatByLen creates an internal parser instance for its use.
String variations = "0=:7= ###-####:10=(###) ###-####:?=ABS(-5)";
validateStringResult(parser, "FORMATBYLEN('1', '[0-9]*', \"" + variations + "\")", "5");
}

@Test
public void testGUID() throws Exception {
validatePattern(parser, "GUID");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void testClearConstants() {

// Really remove all and verify regex is not available
parser.getConstants().clear();
TokenType.invalidatePattern();;
parser.invalidatePattern();;
assertEquals("no regex", "~~no-constants-defined~~", parser.getConstantRegex());
}

Expand All @@ -88,7 +88,7 @@ public void testClearFunctions() {

// Really remove all and verify regex is not available
parser.getFunctions().clear();
TokenType.invalidatePattern();;
parser.invalidatePattern();;
assertEquals("no regex", "~~no-functions-defined~~", parser.getFunctionRegex());
}

Expand Down

0 comments on commit 3bfdca0

Please sign in to comment.