diff --git a/src/main/java/com/ezylang/evalex/parser/Tokenizer.java b/src/main/java/com/ezylang/evalex/parser/Tokenizer.java index abb20856..08d2979d 100644 --- a/src/main/java/com/ezylang/evalex/parser/Tokenizer.java +++ b/src/main/java/com/ezylang/evalex/parser/Tokenizer.java @@ -351,33 +351,54 @@ private boolean infixOperatorAllowed() { } private Token parseNumberLiteral() throws ParseException { - int tokenStartIndex = currentColumnIndex; - StringBuilder tokenValue = new StringBuilder(); int nextChar = peekNextChar(); if (currentChar == '0' && (nextChar == 'x' || nextChar == 'X')) { - // hexadecimal number, consume "0x" + return parseHexNumberLiteral(); + } else { + return parseDecimalNumberLiteral(); + } + } + + private Token parseDecimalNumberLiteral() throws ParseException { + int tokenStartIndex = currentColumnIndex; + StringBuilder tokenValue = new StringBuilder(); + + int lastChar = -1; + boolean scientificNotation = false; + while (currentChar != -1 && isAtNumberChar()) { + if (currentChar == 'e' || currentChar == 'E') { + scientificNotation = true; + } tokenValue.append((char) currentChar); + lastChar = currentChar; consumeChar(); + } + // illegal scientific format literal + if (scientificNotation + && (lastChar == 'e' + || lastChar == 'E' + || lastChar == '+' + || lastChar == '-' + || lastChar == '.')) { + throw new ParseException( + new Token(tokenStartIndex, tokenValue.toString(), TokenType.NUMBER_LITERAL), + "Illegal scientific format"); + } + return new Token(tokenStartIndex, tokenValue.toString(), TokenType.NUMBER_LITERAL); + } + + private Token parseHexNumberLiteral() { + int tokenStartIndex = currentColumnIndex; + StringBuilder tokenValue = new StringBuilder(); + + // hexadecimal number, consume "0x" + tokenValue.append((char) currentChar); + consumeChar(); + tokenValue.append((char) currentChar); + consumeChar(); + while (currentChar != -1 && isAtHexChar()) { tokenValue.append((char) currentChar); consumeChar(); - while (currentChar != -1 && isAtHexChar()) { - tokenValue.append((char) currentChar); - consumeChar(); - } - } else { - // decimal number - int lastChar = -1; - while (currentChar != -1 && isAtNumberChar()) { - tokenValue.append((char) currentChar); - lastChar = currentChar; - consumeChar(); - } - // illegal scientific format literal - if (lastChar == 'e' || lastChar == 'E' || lastChar == '+' || lastChar == '-') { - throw new ParseException( - new Token(tokenStartIndex, tokenValue.toString(), TokenType.NUMBER_LITERAL), - "Illegal scientific format"); - } } return new Token(tokenStartIndex, tokenValue.toString(), TokenType.NUMBER_LITERAL); } @@ -485,7 +506,7 @@ private boolean isAtNumberStart() { private boolean isAtNumberChar() { int previousChar = peekPreviousChar(); - if (previousChar == 'e' || previousChar == 'E') { + if ((previousChar == 'e' || previousChar == 'E') && currentChar != '.') { return Character.isDigit(currentChar) || currentChar == '+' || currentChar == '-'; } diff --git a/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java b/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java index 8f237231..9a261b12 100644 --- a/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java +++ b/src/test/java/com/ezylang/evalex/parser/ShuntingYardExceptionsTest.java @@ -151,7 +151,9 @@ void testFunctionTooManyParameters() { "Hello 1 + 1", "Hello World", "Hello 1", - "1 2" + "1 2", + "e.1", + "E.1" }) void testTooManyOperands(String expressionString) { Expression expression = new Expression(expressionString); diff --git a/src/test/java/com/ezylang/evalex/parser/TokenizerNumberLiteralTest.java b/src/test/java/com/ezylang/evalex/parser/TokenizerNumberLiteralTest.java index 2c41fcbf..a27c2296 100644 --- a/src/test/java/com/ezylang/evalex/parser/TokenizerNumberLiteralTest.java +++ b/src/test/java/com/ezylang/evalex/parser/TokenizerNumberLiteralTest.java @@ -83,7 +83,7 @@ void testSciOK() throws ParseException { } @ParameterizedTest - @ValueSource(strings = {"2e", "2E", "2e+", "2E+", "2e-", "2E-", "2e."}) + @ValueSource(strings = {"2e", "2E", "2e+", "2E+", "2e-", "2E-", "2e.", "2E.", "2ex", "2Ex"}) void testScientificLiteralsParseException(String expression) { assertThatThrownBy(() -> new Tokenizer(expression, configuration).parse()) .isInstanceOf(ParseException.class)