Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

360: BUG: Undefined operator '.' ParseException when letter 'e' is after dot '.' #362

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 43 additions & 22 deletions src/main/java/com/ezylang/evalex/parser/Tokenizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ private boolean implicitMultiplicationPossible(Token currentToken) {
}

return ((previousToken.getType() == BRACE_CLOSE && currentToken.getType() == BRACE_OPEN)
|| ((previousToken.getType() == NUMBER_LITERAL
&& currentToken.getType() == VARIABLE_OR_CONSTANT))
|| ((previousToken.getType() == NUMBER_LITERAL && currentToken.getType() == BRACE_OPEN)));
|| (previousToken.getType() == NUMBER_LITERAL
&& currentToken.getType() == VARIABLE_OR_CONSTANT)
|| (previousToken.getType() == NUMBER_LITERAL && currentToken.getType() == BRACE_OPEN));
}

private void validateToken(Token currentToken) throws ParseException {
Expand Down Expand Up @@ -150,16 +150,16 @@ private Token getNextToken() throws ParseException {
} else if (currentChar == ']' && configuration.isArraysAllowed()) {
return parseArrayClose();
} else if (currentChar == '.'
&& !isNumberChar(peekNextChar())
&& !isNextCharNumberChar()
&& configuration.isStructuresAllowed()) {
return parseStructureSeparator();
} else if (currentChar == ',') {
Token token = new Token(currentColumnIndex, ",", TokenType.COMMA);
consumeChar();
return token;
} else if (isIdentifierStart(currentChar)) {
} else if (isAtIdentifierStart()) {
return parseIdentifier();
} else if (isNumberStart(currentChar)) {
} else if (isAtNumberStart()) {
return parseNumberLiteral();
} else {
return parseOperator();
Expand Down Expand Up @@ -247,6 +247,8 @@ private Token parseOperator() throws ParseException {
} else if (operatorDictionary.hasInfixOperator(tokenString)) {
OperatorIfc operator = operatorDictionary.getInfixOperator(tokenString);
return new Token(tokenStartIndex, tokenString, TokenType.INFIX_OPERATOR, operator);
} else if (tokenString.equals(".") && configuration.isStructuresAllowed()) {
return new Token(tokenStartIndex, tokenString, STRUCTURE_SEPARATOR);
}
throw new ParseException(
tokenStartIndex,
Expand Down Expand Up @@ -357,14 +359,14 @@ private Token parseNumberLiteral() throws ParseException {
consumeChar();
tokenValue.append((char) currentChar);
consumeChar();
while (currentChar != -1 && isHexChar(currentChar)) {
while (currentChar != -1 && isAtHexChar()) {
tokenValue.append((char) currentChar);
consumeChar();
}
} else {
// decimal number
int lastChar = -1;
while (currentChar != -1 && isNumberChar(currentChar)) {
while (currentChar != -1 && isAtNumberChar()) {
tokenValue.append((char) currentChar);
lastChar = currentChar;
consumeChar();
Expand All @@ -382,7 +384,7 @@ private Token parseNumberLiteral() throws ParseException {
private Token parseIdentifier() throws ParseException {
int tokenStartIndex = currentColumnIndex;
StringBuilder tokenValue = new StringBuilder();
while (currentChar != -1 && isIdentifierChar(currentChar)) {
while (currentChar != -1 && isAtIdentifierChar()) {
tokenValue.append((char) currentChar);
consumeChar();
}
Expand Down Expand Up @@ -472,24 +474,43 @@ private char escapeCharacter(int character) throws ParseException {
}
}

private boolean isNumberStart(int ch) {
if (Character.isDigit(ch)) {
private boolean isAtNumberStart() {
if (Character.isDigit(currentChar)) {
return true;
}
return ch == '.' && Character.isDigit(peekNextChar());
return currentChar == '.' && Character.isDigit(peekNextChar());
}

private boolean isNumberChar(int ch) {
private boolean isAtNumberChar() {
int previousChar = peekPreviousChar();

if (previousChar == 'e' || previousChar == 'E') {
return Character.isDigit(ch) || ch == '+' || ch == '-';
} else {
return Character.isDigit(ch) || ch == '.' || ch == 'e' || ch == 'E';
return Character.isDigit(currentChar) || currentChar == '+' || currentChar == '-';
}

if (previousChar == '.') {
return Character.isDigit(currentChar) || currentChar == 'e' || currentChar == 'E';
}

return Character.isDigit(currentChar)
|| currentChar == '.'
|| currentChar == 'e'
|| currentChar == 'E';
}

private boolean isNextCharNumberChar() {
if (peekNextChar() == -1) {
return false;
}
consumeChar();
boolean isAtNumber = isAtNumberChar();
currentColumnIndex--;
currentChar = expressionString.charAt(currentColumnIndex - 1);
return isAtNumber;
}

private boolean isHexChar(int ch) {
switch (ch) {
private boolean isAtHexChar() {
switch (currentChar) {
case '0':
case '1':
case '2':
Expand Down Expand Up @@ -518,12 +539,12 @@ private boolean isHexChar(int ch) {
}
}

private boolean isIdentifierStart(int ch) {
return Character.isLetter(ch) || ch == '_';
private boolean isAtIdentifierStart() {
return Character.isLetter(currentChar) || currentChar == '_';
}

private boolean isIdentifierChar(int ch) {
return Character.isLetter(ch) || Character.isDigit(ch) || ch == '_';
private boolean isAtIdentifierChar() {
return Character.isLetter(currentChar) || Character.isDigit(currentChar) || currentChar == '_';
}

private void skipBlanks() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ class ExpressionEvaluatorScientificTest extends BaseExpressionEvaluatorTest {
"0.5e2 : 50",
"0.35E4 + 0.5e1 : 3505",
"2135E-4 : 0.2135",
"2135E+4 : 21350000"
"2135E+4 : 21350000",
"3.e1: 30",
"3.e-1: 0.3",
"3.e+2: 300",
"3.E1: 30",
"3.E-1: 0.3",
"3.E+2: 300"
})
void testScientificLiteralsEvaluation(String expression, String expectedResult)
throws ParseException, EvaluationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,35 @@

class ExpressionEvaluatorStructureTest extends BaseExpressionEvaluatorTest {

@Test
void testStructureScientificNumberDistinction() throws EvaluationException, ParseException {
Map<String, BigDecimal> structure =
new HashMap<>() {
{
put("environment_id", new BigDecimal(12345));
}
};
Expression expression = new Expression("order.environment_id").with("order", structure);

assertThat(expression.evaluate().getStringValue()).isEqualTo("12345");
}

@Test
void testStructureScientificNumberDistinctionMultiple()
throws EvaluationException, ParseException {
Map<String, Object> structure1 = new HashMap<>();
Map<String, Object> structure2 = new HashMap<>();
Map<String, Object> structure3 = new HashMap<>();

structure3.put("e", new BigDecimal("765"));
structure2.put("var_x", structure3);
structure1.put("e_id_e", structure2);

Expression expression = new Expression("order.e_id_e.var_x.e").with("order", structure1);

assertThat(expression.evaluate().getStringValue()).isEqualTo("765");
}

@Test
void testSimpleStructure() throws ParseException, EvaluationException {
Map<String, BigDecimal> structure =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,51 @@ void testStructureSimple() throws ParseException {
new Token(3, "b", TokenType.VARIABLE_OR_CONSTANT));
}

@Test
void testStructureLeftIsE() throws ParseException {
assertAllTokensParsedCorrectly(
"e.b",
new Token(1, "e", TokenType.VARIABLE_OR_CONSTANT),
new Token(2, ".", TokenType.STRUCTURE_SEPARATOR),
new Token(3, "b", TokenType.VARIABLE_OR_CONSTANT));
}

@Test
void testStructureRightIsE() throws ParseException {
assertAllTokensParsedCorrectly(
"a.e",
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
new Token(2, ".", TokenType.STRUCTURE_SEPARATOR),
new Token(3, "e", TokenType.VARIABLE_OR_CONSTANT));
}

@Test
void testStructureBothAreE() throws ParseException {
assertAllTokensParsedCorrectly(
"e.e",
new Token(1, "e", TokenType.VARIABLE_OR_CONSTANT),
new Token(2, ".", TokenType.STRUCTURE_SEPARATOR),
new Token(3, "e", TokenType.VARIABLE_OR_CONSTANT));
}

@Test
void testStructureLeftEndsE() throws ParseException {
assertAllTokensParsedCorrectly(
"variable.a",
new Token(1, "variable", TokenType.VARIABLE_OR_CONSTANT),
new Token(9, ".", TokenType.STRUCTURE_SEPARATOR),
new Token(10, "a", TokenType.VARIABLE_OR_CONSTANT));
}

@Test
void testStructureRightStartsE() throws ParseException {
assertAllTokensParsedCorrectly(
"a.end",
new Token(1, "a", TokenType.VARIABLE_OR_CONSTANT),
new Token(2, ".", TokenType.STRUCTURE_SEPARATOR),
new Token(3, "end", TokenType.VARIABLE_OR_CONSTANT));
}

@Test
void testStructureSeparatorNotAllowedBegin() {
assertThatThrownBy(() -> new Tokenizer(".", configuration).parse())
Expand Down