From 0000bfc604b985c47ab153f4172596b860c7cce8 Mon Sep 17 00:00:00 2001 From: Balajiganapathi S Date: Sat, 21 Oct 2017 01:05:08 +0530 Subject: Allow underscores in numbers. --- .gitignore | 2 + Changelog.md | 1 + docs/types.rst | 3 + libsolidity/parsing/Scanner.cpp | 28 +++++- test/libsolidity/SolidityScanner.cpp | 166 +++++++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 87a3e593..13d03600 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ browse.VC.db CMakeLists.txt.user /CMakeSettings.json /.vs +/.cproject +/.project diff --git a/Changelog.md b/Changelog.md index 0790c0ca..0fd967a8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -147,6 +147,7 @@ Features: * General: Introduce new constructor syntax using the ``constructor`` keyword as experimental 0.5.0 feature. * General: Limit the number of errors output in a single run to 256. * General: Support accessing dynamic return data in post-byzantium EVMs. + * General: Allow underscores in numeric and hex literals to separate thousands and quads. * Inheritance: Error when using empty parentheses for base class constructors that require arguments as experimental 0.5.0 feature. * Inheritance: Error when using no parentheses in modifier-style constructor calls as experimental 0.5.0 feature. * Interfaces: Allow overriding external functions in interfaces with public in an implementing contract. diff --git a/docs/types.rst b/docs/types.rst index fbc83972..84e58fde 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -284,6 +284,9 @@ one side. Examples include ``1.``, ``.1`` and ``1.3``. Scientific notation is also supported, where the base can have fractions, while the exponent cannot. Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``. +Underscores can be used to separate digits of a numeric literal to aid readability. +For example, ``123_000``, ``0x2eff_abde``, ``1_233e34_89`` are all valid. Underscores are only allowed between two digits. + Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by using them together with a non-literal expression). This means that computations do not overflow and divisions do not truncate diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp index 801d2cc4..65189b19 100644 --- a/libsolidity/parsing/Scanner.cpp +++ b/libsolidity/parsing/Scanner.cpp @@ -726,8 +726,21 @@ Token::Value Scanner::scanHexString() void Scanner::scanDecimalDigits() { - while (isDecimalDigit(m_char)) + if (!isDecimalDigit(m_char)) // avoid underscore at beginning + return; + while (isDecimalDigit(m_char) || m_char == '_') + { + if (m_char == '_') + { + advance(); + if (!isDecimalDigit(m_char)) // avoid trailing underscore + { + rollback(1); + break; + } + } addLiteralCharAndAdvance(); + } } Token::Value Scanner::scanNumber(char _charSeen) @@ -755,8 +768,19 @@ Token::Value Scanner::scanNumber(char _charSeen) addLiteralCharAndAdvance(); if (!isHexDigit(m_char)) return Token::Illegal; // we must have at least one hex digit after 'x'/'X' - while (isHexDigit(m_char)) + while (isHexDigit(m_char) || m_char == '_') // same logic as scanDecimalDigits + { + if (m_char == '_') + { + advance(); + if (!isHexDigit(m_char)) // avoid trailing underscore + { + rollback(1); + break; + } + } addLiteralCharAndAdvance(); + } } else if (isDecimalDigit(m_char)) // We do not allow octal numbers diff --git a/test/libsolidity/SolidityScanner.cpp b/test/libsolidity/SolidityScanner.cpp index 42e1b18e..b650d918 100644 --- a/test/libsolidity/SolidityScanner.cpp +++ b/test/libsolidity/SolidityScanner.cpp @@ -155,6 +155,172 @@ BOOST_AUTO_TEST_CASE(trailing_dot) BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } +BOOST_AUTO_TEST_CASE(underscores_in_integer) +{ + Scanner scanner(CharStream("var x = 1_23_4;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Number); + BOOST_CHECK_EQUAL(scanner.currentLiteral(), "1234"); + BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(underscores_in_scientific_notation) +{ + Scanner scanner(CharStream("var x = 1_2e10;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Number); + BOOST_CHECK_EQUAL(scanner.currentLiteral(), "12e10"); + BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(underscores_in_scientific_notation_in_exp_part) +{ + Scanner scanner(CharStream("var x = 12e1_0;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Number); + BOOST_CHECK_EQUAL(scanner.currentLiteral(), "12e10"); + BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + + +BOOST_AUTO_TEST_CASE(underscores_in_hex) +{ + Scanner scanner(CharStream("var x = 0xab_19cf;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Number); + BOOST_CHECK_EQUAL(scanner.currentLiteral(), "0xab19cf"); + BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_integer_is_identifier) +{ + Scanner scanner(CharStream("var x = _12;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_decimal_is_identifier) +{ + Scanner scanner(CharStream("var x = _1.2;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_decimal_after_dot_illegal) +{ + Scanner scanner(CharStream("var x = 1._2;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_exp_are_identifier) +{ + Scanner scanner(CharStream("var x = _1e2;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_exp_after_e_illegal) +{ + Scanner scanner(CharStream("var x = 1e_2;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_hex_illegal) +{ + Scanner scanner(CharStream("var x = 0x_abc;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(trailing_underscore_integer_illegal) +{ + Scanner scanner(CharStream("var x = 12_;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_after_decimal_illegal) +{ + Scanner scanner(CharStream("var x = 1.2_;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(leading_underscore_before_decimal_illegal) +{ + Scanner scanner(CharStream("var x = 1_.2;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(trailing_underscore_exp_illegal) +{ + Scanner scanner(CharStream("var x = 1e2_;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(trailing_underscore_exp_before_e_illegal) +{ + Scanner scanner(CharStream("var x = 1_e2;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(trailing_underscore_hex_illegal) +{ + Scanner scanner(CharStream("var x = 0xabc_;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + +BOOST_AUTO_TEST_CASE(double_underscore_illegal) +{ + Scanner scanner(CharStream("var x = 1__2;")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); +} + BOOST_AUTO_TEST_CASE(negative_numbers) { Scanner scanner(CharStream("var x = -.2 + -0x78 + -7.3 + 8.9 + 2e-2;")); -- cgit v1.2.3 From 09a36cba0223c16248335703412cee019c7aa59f Mon Sep 17 00:00:00 2001 From: Balajiganapathi S Date: Wed, 25 Oct 2017 13:42:07 +0530 Subject: Add stricter hex underscore rules --- docs/types.rst | 4 +-- libsolidity/analysis/SyntaxChecker.cpp | 43 +++++++++++++++++++++++++++++++ libsolidity/analysis/SyntaxChecker.h | 1 + libsolidity/ast/Types.cpp | 5 +++- libsolidity/parsing/Scanner.cpp | 35 +++++++++++++------------ test/libsolidity/SolidityEndToEndTest.cpp | 16 ++++++++++++ test/libsolidity/SolidityScanner.cpp | 2 +- 7 files changed, 86 insertions(+), 20 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 84e58fde..4bade37c 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -284,8 +284,8 @@ one side. Examples include ``1.``, ``.1`` and ``1.3``. Scientific notation is also supported, where the base can have fractions, while the exponent cannot. Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``. -Underscores can be used to separate digits of a numeric literal to aid readability. -For example, ``123_000``, ``0x2eff_abde``, ``1_233e34_89`` are all valid. Underscores are only allowed between two digits. +Underscores can be used to separate the digits of a numeric literal to aid readability. +For example, ``123_000``, ``0x2eff_abde``, ``1233_e348_9a`` are all valid. Underscores are only allowed between two digits. For hex literals, underscores are only allowed to separate groups of 4 hex digits. Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by using them together with a non-literal expression). diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 77492499..dc2e35e5 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -24,6 +24,9 @@ #include #include +#include +#include + using namespace std; using namespace dev; using namespace dev::solidity; @@ -183,6 +186,46 @@ bool SyntaxChecker::visit(Throw const& _throwStatement) return true; } +bool SyntaxChecker::visit(Literal const& _literal) +{ + if (!_literal.isHexNumber()) + return true; + // We have a hex literal. Do underscore validation + solAssert(_literal.value().substr(0, 2) == "0x", ""); + ASTString value = _literal.value().substr(2); // Skip the 0x + vector parts; + boost::split(parts, value, boost::is_any_of("_")); + + if (parts.size() == 1) // no underscores + return true; + // Everything except first and last part must be 4 chars in length + for (size_t i = 1; i + 1 < parts.size(); ++i) + { + if (parts[i].size() != 4) + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in hex literal. Found inner part with " + to_string(parts[i].size()) + " digits (has to be 4 digits)."); + } + + // Validate rightmost block + if (parts.back().size() == 4) // If ends with 4 digits, then no need to validate first block + return true; + + // Validate leftmost block + // If first part is 4 digits then last part's length has to be even to avoid ambiguity over zero padding + if (parts.front().size() == 4) + { + if (parts.back().size() % 2 == 0) + return true; + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in hex literal. If the first part has 4 digits, it is assumed to be a byte sequence instead of a number and thus the last part should have an even number of digits."); + } + else + { + // Both first and last part is invalid + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in hex literal. First or last part must have 4 digits."); + } + + return true; +} + bool SyntaxChecker::visit(UnaryOperation const& _operation) { if (_operation.getOperator() == Token::Add) diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 28a0f66e..897df676 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -73,6 +73,7 @@ private: virtual bool visit(VariableDeclarationStatement const& _statement) override; virtual bool visit(StructDefinition const& _struct) override; + virtual bool visit(Literal const& _literal) override; ErrorReporter& m_errorReporter; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3eccc6d4..73137ba9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include @@ -779,7 +780,9 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal if (boost::starts_with(_literal.value(), "0x")) { // process as hex - value = bigint(_literal.value()); + ASTString valueString = _literal.value(); + boost::erase_all(valueString, "_");// Remove underscore separators + value = bigint(valueString); } else if (expPoint != _literal.value().end()) { diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp index 65189b19..c223779e 100644 --- a/libsolidity/parsing/Scanner.cpp +++ b/libsolidity/parsing/Scanner.cpp @@ -726,21 +726,26 @@ Token::Value Scanner::scanHexString() void Scanner::scanDecimalDigits() { - if (!isDecimalDigit(m_char)) // avoid underscore at beginning - return; - while (isDecimalDigit(m_char) || m_char == '_') + // Parse for regex [:digit:]+(_[:digit:]+)* + + do { + if (!isDecimalDigit(m_char)) + return; + while (isDecimalDigit(m_char)) + addLiteralCharAndAdvance(); + if (m_char == '_') { advance(); - if (!isDecimalDigit(m_char)) // avoid trailing underscore + if (!isDecimalDigit(m_char)) // Trailing underscore. Rollback and allow next step to flag it as illegal { rollback(1); - break; + return; } } - addLiteralCharAndAdvance(); } + while (isDecimalDigit(m_char)); } Token::Value Scanner::scanNumber(char _charSeen) @@ -768,19 +773,17 @@ Token::Value Scanner::scanNumber(char _charSeen) addLiteralCharAndAdvance(); if (!isHexDigit(m_char)) return Token::Illegal; // we must have at least one hex digit after 'x'/'X' - while (isHexDigit(m_char) || m_char == '_') // same logic as scanDecimalDigits + char last = m_char; + while (isHexDigit(m_char) || m_char == '_') // Unlike decimal digits, we keep the underscores for later validation { - if (m_char == '_') - { - advance(); - if (!isHexDigit(m_char)) // avoid trailing underscore - { - rollback(1); - break; - } - } + if (m_char == '_' && last == '_') + return Token::Illegal; // Double underscore + + last = m_char; addLiteralCharAndAdvance(); } + if (last == '_') + return Token::Illegal; // Trailing underscore } else if (isDecimalDigit(m_char)) // We do not allow octal numbers diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a6c1372b..12da1fa1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -12836,6 +12836,22 @@ BOOST_AUTO_TEST_CASE(write_storage_external) ABI_CHECK(callContractFunction("h()"), encodeArgs(12)); } +BOOST_AUTO_TEST_CASE(test_underscore_in_hex) +{ + char const* sourceCode = R"( + contract test { + function f(bool cond) returns (uint) { + uint32 x = 0x1234_ab; + uint y = 0x1234_abcd_1234; + return cond ? x : y; + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(bool)", true), encodeArgs(u256(0x1234ab))); + ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(u256(0x1234abcd1234))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityScanner.cpp b/test/libsolidity/SolidityScanner.cpp index b650d918..9ad738ae 100644 --- a/test/libsolidity/SolidityScanner.cpp +++ b/test/libsolidity/SolidityScanner.cpp @@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE(underscores_in_hex) BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); BOOST_CHECK_EQUAL(scanner.next(), Token::Number); - BOOST_CHECK_EQUAL(scanner.currentLiteral(), "0xab19cf"); + BOOST_CHECK_EQUAL(scanner.currentLiteral(), "0xab_19cf"); BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } -- cgit v1.2.3 From b9222808f61e00833f8c11cd196cafb50ec9e1b9 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Fri, 3 Aug 2018 16:13:52 +0200 Subject: Cleanup & polish numbers-with-underscores parsing, also improving tests. --- docs/types.rst | 4 +- libsolidity/analysis/SyntaxChecker.cpp | 49 +++--- libsolidity/ast/Types.cpp | 21 +-- libsolidity/parsing/Scanner.cpp | 60 +++---- test/libsolidity/SolidityEndToEndTest.cpp | 2 +- test/libsolidity/SolidityScanner.cpp | 172 +++++---------------- .../lexer_numbers_with_underscores_decimal.sol | 13 ++ ...lexer_numbers_with_underscores_decimal_fail.sol | 13 ++ .../lexer_numbers_with_underscores_fixed.sol | 9 ++ .../lexer_numbers_with_underscores_fixed_fail.sol | 17 ++ .../parsing/lexer_numbers_with_underscores_hex.sol | 13 ++ .../lexer_numbers_with_underscores_hex_fail.sol | 7 + 12 files changed, 180 insertions(+), 200 deletions(-) create mode 100644 test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal.sol create mode 100644 test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal_fail.sol create mode 100644 test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol create mode 100644 test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed_fail.sol create mode 100644 test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex.sol create mode 100644 test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex_fail.sol diff --git a/docs/types.rst b/docs/types.rst index 4bade37c..5c4a30ca 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -285,7 +285,9 @@ Scientific notation is also supported, where the base can have fractions, while Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``. Underscores can be used to separate the digits of a numeric literal to aid readability. -For example, ``123_000``, ``0x2eff_abde``, ``1233_e348_9a`` are all valid. Underscores are only allowed between two digits. For hex literals, underscores are only allowed to separate groups of 4 hex digits. +For example, decimal ``123_000``, hexadecimal ``0x2eff_abde``, scientific decimal notation ``1_2e345_678`` are all valid. +Underscores are only allowed between two digits and only one consecutive underscore is allowed. +There is no additional semantic meaning added to a number literal containing underscores. Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by using them together with a non-literal expression). diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index dc2e35e5..ac4fa72b 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -188,39 +188,38 @@ bool SyntaxChecker::visit(Throw const& _throwStatement) bool SyntaxChecker::visit(Literal const& _literal) { - if (!_literal.isHexNumber()) + if (_literal.token() != Token::Number) return true; - // We have a hex literal. Do underscore validation - solAssert(_literal.value().substr(0, 2) == "0x", ""); - ASTString value = _literal.value().substr(2); // Skip the 0x - vector parts; - boost::split(parts, value, boost::is_any_of("_")); - if (parts.size() == 1) // no underscores - return true; - // Everything except first and last part must be 4 chars in length - for (size_t i = 1; i + 1 < parts.size(); ++i) - { - if (parts[i].size() != 4) - m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in hex literal. Found inner part with " + to_string(parts[i].size()) + " digits (has to be 4 digits)."); - } + ASTString const& value = _literal.value(); + solAssert(!value.empty(), ""); - // Validate rightmost block - if (parts.back().size() == 4) // If ends with 4 digits, then no need to validate first block + // Generic checks no matter what base this number literal is of: + if (value.back() == '_') + { + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No trailing underscores allowed."); return true; + } - // Validate leftmost block - // If first part is 4 digits then last part's length has to be even to avoid ambiguity over zero padding - if (parts.front().size() == 4) + if (value.find("__") != ASTString::npos) { - if (parts.back().size() % 2 == 0) - return true; - m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in hex literal. If the first part has 4 digits, it is assumed to be a byte sequence instead of a number and thus the last part should have an even number of digits."); + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. Only one consecutive underscores between digits allowed."); + return true; } - else + + if (!_literal.isHexNumber()) // decimal literal { - // Both first and last part is invalid - m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in hex literal. First or last part must have 4 digits."); + if (value.find("._") != ASTString::npos) + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscores in front of the fraction part allowed."); + + if (value.find("_.") != ASTString::npos) + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscores in front of the fraction part allowed."); + + if (value.find("_e") != ASTString::npos) + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscore at the end of the mantissa allowed."); + + if (value.find("e_") != ASTString::npos) + m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscore in front of exponent allowed."); } return true; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 73137ba9..0094c5ed 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -773,21 +773,22 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal rational value; try { - auto expPoint = find(_literal.value().begin(), _literal.value().end(), 'e'); - if (expPoint == _literal.value().end()) - expPoint = find(_literal.value().begin(), _literal.value().end(), 'E'); + ASTString valueString = _literal.value(); + boost::erase_all(valueString, "_");// Remove underscore separators - if (boost::starts_with(_literal.value(), "0x")) + auto expPoint = find(valueString.begin(), valueString.end(), 'e'); + if (expPoint == valueString.end()) + expPoint = find(valueString.begin(), valueString.end(), 'E'); + + if (boost::starts_with(valueString, "0x")) { // process as hex - ASTString valueString = _literal.value(); - boost::erase_all(valueString, "_");// Remove underscore separators value = bigint(valueString); } - else if (expPoint != _literal.value().end()) + else if (expPoint != valueString.end()) { // Parse mantissa and exponent. Checks numeric limit. - tuple mantissa = parseRational(string(_literal.value().begin(), expPoint)); + tuple mantissa = parseRational(string(valueString.begin(), expPoint)); if (!get<0>(mantissa)) return make_tuple(false, rational(0)); @@ -797,7 +798,7 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal if (value == 0) return make_tuple(true, rational(0)); - bigint exp = bigint(string(expPoint + 1, _literal.value().end())); + bigint exp = bigint(string(expPoint + 1, valueString.end())); if (exp > numeric_limits::max() || exp < numeric_limits::min()) return make_tuple(false, rational(0)); @@ -826,7 +827,7 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal else { // parse as rational number - tuple tmp = parseRational(_literal.value()); + tuple tmp = parseRational(valueString); if (!get<0>(tmp)) return tmp; value = get<1>(tmp); diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp index c223779e..30fdf21d 100644 --- a/libsolidity/parsing/Scanner.cpp +++ b/libsolidity/parsing/Scanner.cpp @@ -724,28 +724,18 @@ Token::Value Scanner::scanHexString() return Token::StringLiteral; } +// Parse for regex [:digit:]+(_[:digit:]+)* void Scanner::scanDecimalDigits() { - // Parse for regex [:digit:]+(_[:digit:]+)* + // MUST begin with a decimal digit. + if (!isDecimalDigit(m_char)) + return; - do - { - if (!isDecimalDigit(m_char)) - return; - while (isDecimalDigit(m_char)) - addLiteralCharAndAdvance(); + // May continue with decimal digit or underscore for grouping. + do addLiteralCharAndAdvance(); + while (!m_source.isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_')); - if (m_char == '_') - { - advance(); - if (!isDecimalDigit(m_char)) // Trailing underscore. Rollback and allow next step to flag it as illegal - { - rollback(1); - return; - } - } - } - while (isDecimalDigit(m_char)); + // Defer further validation of underscore to SyntaxChecker. } Token::Value Scanner::scanNumber(char _charSeen) @@ -756,6 +746,8 @@ Token::Value Scanner::scanNumber(char _charSeen) { // we have already seen a decimal point of the float addLiteralChar('.'); + if (m_char == '_') + return Token::Illegal; scanDecimalDigits(); // we know we have at least one digit } else @@ -773,17 +765,9 @@ Token::Value Scanner::scanNumber(char _charSeen) addLiteralCharAndAdvance(); if (!isHexDigit(m_char)) return Token::Illegal; // we must have at least one hex digit after 'x'/'X' - char last = m_char; - while (isHexDigit(m_char) || m_char == '_') // Unlike decimal digits, we keep the underscores for later validation - { - if (m_char == '_' && last == '_') - return Token::Illegal; // Double underscore - last = m_char; + while (isHexDigit(m_char) || m_char == '_') // We keep the underscores for later validation addLiteralCharAndAdvance(); - } - if (last == '_') - return Token::Illegal; // Trailing underscore } else if (isDecimalDigit(m_char)) // We do not allow octal numbers @@ -795,9 +779,17 @@ Token::Value Scanner::scanNumber(char _charSeen) scanDecimalDigits(); // optional if (m_char == '.') { - // A '.' has to be followed by a number. + if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_') + { + // Assume the input may be a floating point number with leading '_' in fraction part. + // Recover by consuming it all but returning `Illegal` right away. + addLiteralCharAndAdvance(); // '.' + addLiteralCharAndAdvance(); // '_' + scanDecimalDigits(); + } if (m_source.isPastEndOfInput() || !isDecimalDigit(m_source.get(1))) { + // A '.' has to be followed by a number. literal.complete(); return Token::Number; } @@ -812,8 +804,18 @@ Token::Value Scanner::scanNumber(char _charSeen) solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number"); if (kind != DECIMAL) return Token::Illegal; + else if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_') + { + // Recover from wrongly placed underscore as delimiter in literal with scientific + // notation by consuming until the end. + addLiteralCharAndAdvance(); // 'e' + addLiteralCharAndAdvance(); // '_' + scanDecimalDigits(); + literal.complete(); + return Token::Number; + } // scan exponent - addLiteralCharAndAdvance(); + addLiteralCharAndAdvance(); // 'e' | 'E' if (m_char == '+' || m_char == '-') addLiteralCharAndAdvance(); if (!isDecimalDigit(m_char)) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 12da1fa1..3fd610a0 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -12840,7 +12840,7 @@ BOOST_AUTO_TEST_CASE(test_underscore_in_hex) { char const* sourceCode = R"( contract test { - function f(bool cond) returns (uint) { + function f(bool cond) public pure returns (uint) { uint32 x = 0x1234_ab; uint y = 0x1234_abcd_1234; return cond ? x : y; diff --git a/test/libsolidity/SolidityScanner.cpp b/test/libsolidity/SolidityScanner.cpp index 9ad738ae..f2e756bb 100644 --- a/test/libsolidity/SolidityScanner.cpp +++ b/test/libsolidity/SolidityScanner.cpp @@ -155,170 +155,74 @@ BOOST_AUTO_TEST_CASE(trailing_dot) BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } -BOOST_AUTO_TEST_CASE(underscores_in_integer) -{ - Scanner scanner(CharStream("var x = 1_23_4;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Number); - BOOST_CHECK_EQUAL(scanner.currentLiteral(), "1234"); - BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); - BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); -} - -BOOST_AUTO_TEST_CASE(underscores_in_scientific_notation) +BOOST_AUTO_TEST_CASE(leading_underscore_decimal_is_identifier) { - Scanner scanner(CharStream("var x = 1_2e10;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); + // Actual error is cought by SyntaxChecker. + Scanner scanner(CharStream("_1.2")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Identifier); BOOST_CHECK_EQUAL(scanner.next(), Token::Number); - BOOST_CHECK_EQUAL(scanner.currentLiteral(), "12e10"); - BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } -BOOST_AUTO_TEST_CASE(underscores_in_scientific_notation_in_exp_part) +BOOST_AUTO_TEST_CASE(leading_underscore_decimal_after_dot_illegal) { - Scanner scanner(CharStream("var x = 12e1_0;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Number); - BOOST_CHECK_EQUAL(scanner.currentLiteral(), "12e10"); - BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); + // Actual error is cought by SyntaxChecker. + Scanner scanner(CharStream("1._2")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number); BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); -} - -BOOST_AUTO_TEST_CASE(underscores_in_hex) -{ - Scanner scanner(CharStream("var x = 0xab_19cf;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Number); - BOOST_CHECK_EQUAL(scanner.currentLiteral(), "0xab_19cf"); - BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon); + scanner.reset(CharStream("1._"), ""); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number); BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } -BOOST_AUTO_TEST_CASE(leading_underscore_integer_is_identifier) -{ - Scanner scanner(CharStream("var x = _12;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); -} - -BOOST_AUTO_TEST_CASE(leading_underscore_decimal_is_identifier) -{ - Scanner scanner(CharStream("var x = _1.2;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); -} - -BOOST_AUTO_TEST_CASE(leading_underscore_decimal_after_dot_illegal) -{ - Scanner scanner(CharStream("var x = 1._2;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); -} - BOOST_AUTO_TEST_CASE(leading_underscore_exp_are_identifier) { - Scanner scanner(CharStream("var x = _1e2;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); + // Actual error is cought by SyntaxChecker. + Scanner scanner(CharStream("_1e2")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } BOOST_AUTO_TEST_CASE(leading_underscore_exp_after_e_illegal) { - Scanner scanner(CharStream("var x = 1e_2;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); + // Actual error is cought by SyntaxChecker. + Scanner scanner(CharStream("1e_2")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number); + BOOST_CHECK_EQUAL(scanner.currentLiteral(), "1e_2"); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } BOOST_AUTO_TEST_CASE(leading_underscore_hex_illegal) { - Scanner scanner(CharStream("var x = 0x_abc;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); -} - -BOOST_AUTO_TEST_CASE(trailing_underscore_integer_illegal) -{ - Scanner scanner(CharStream("var x = 12_;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); -} - -BOOST_AUTO_TEST_CASE(leading_underscore_after_decimal_illegal) -{ - Scanner scanner(CharStream("var x = 1.2_;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); -} - -BOOST_AUTO_TEST_CASE(leading_underscore_before_decimal_illegal) -{ - Scanner scanner(CharStream("var x = 1_.2;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); + Scanner scanner(CharStream("0x_abc")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Illegal); BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } -BOOST_AUTO_TEST_CASE(trailing_underscore_exp_illegal) +BOOST_AUTO_TEST_CASE(fixed_number_invalid_underscore_front) { - Scanner scanner(CharStream("var x = 1e2_;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); + // Actual error is cought by SyntaxChecker. + Scanner scanner(CharStream("12._1234_1234")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } -BOOST_AUTO_TEST_CASE(trailing_underscore_exp_before_e_illegal) +BOOST_AUTO_TEST_CASE(number_literals_with_trailing_underscore_at_eos) { - Scanner scanner(CharStream("var x = 1_e2;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); -} + // Actual error is cought by SyntaxChecker. + Scanner scanner(CharStream("0x123_")); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); -BOOST_AUTO_TEST_CASE(trailing_underscore_hex_illegal) -{ - Scanner scanner(CharStream("var x = 0xabc_;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); -} + scanner.reset(CharStream("123_"), ""); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); -BOOST_AUTO_TEST_CASE(double_underscore_illegal) -{ - Scanner scanner(CharStream("var x = 1__2;")); - BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var); - BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); - BOOST_CHECK_EQUAL(scanner.next(), Token::Assign); - BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal); + scanner.reset(CharStream("12.34_"), ""); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); } BOOST_AUTO_TEST_CASE(negative_numbers) diff --git a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal.sol b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal.sol new file mode 100644 index 00000000..d4d3299f --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal.sol @@ -0,0 +1,13 @@ +contract C { + function f() public pure { + uint d1 = 654_321; + uint d2 = 54_321; + uint d3 = 4_321; + uint d4 = 5_43_21; + uint d5 = 1_2e10; + uint d6 = 12e1_0; + + d1; d2; d3; d4; d5; d6; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal_fail.sol b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal_fail.sol new file mode 100644 index 00000000..8978996d --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_decimal_fail.sol @@ -0,0 +1,13 @@ +contract C { + function f() public pure { + uint D1 = 1234_; + uint D2 = 12__34; + uint D3 = 12_e34; + uint D4 = 12e_34; + } +} +// ---- +// SyntaxError: (56-61): Invalid use of underscores in number literal. No trailing underscores allowed. +// SyntaxError: (77-83): Invalid use of underscores in number literal. Only one consecutive underscores between digits allowed. +// SyntaxError: (99-105): Invalid use of underscores in number literal. No underscore at the end of the mantissa allowed. +// SyntaxError: (121-127): Invalid use of underscores in number literal. No underscore in front of exponent allowed. diff --git a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol new file mode 100644 index 00000000..4910ee82 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol @@ -0,0 +1,9 @@ +contract C { + function f() public pure { + fixed f1 = 3.14_15; + fixed f2 = 3_1.4_15; + + f1; f2; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed_fail.sol b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed_fail.sol new file mode 100644 index 00000000..3b91895d --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed_fail.sol @@ -0,0 +1,17 @@ +contract C { + function f() public pure { + fixed F1 = 3.1415_; + fixed F2 = 3__1.4__15; + fixed F3 = 1_.2; + fixed F4 = 1._2; + fixed F5 = 1.2e_12; + fixed F6 = 1._; + } +} +// ---- +// SyntaxError: (57-64): Invalid use of underscores in number literal. No trailing underscores allowed. +// SyntaxError: (81-91): Invalid use of underscores in number literal. Only one consecutive underscores between digits allowed. +// SyntaxError: (108-112): Invalid use of underscores in number literal. No underscores in front of the fraction part allowed. +// SyntaxError: (129-133): Invalid use of underscores in number literal. No underscores in front of the fraction part allowed. +// SyntaxError: (150-157): Invalid use of underscores in number literal. No underscore in front of exponent allowed. +// SyntaxError: (174-177): Invalid use of underscores in number literal. No trailing underscores allowed. diff --git a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex.sol b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex.sol new file mode 100644 index 00000000..999a4634 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex.sol @@ -0,0 +1,13 @@ +contract C { + function f() public pure { + uint x1 = 0x8765_4321; + uint x2 = 0x765_4321; + uint x3 = 0x65_4321; + uint x4 = 0x5_4321; + uint x5 = 0x123_1234_1234_1234; + uint x6 = 0x123456_1234_1234; + + x1; x2; x3; x4; x5; x6; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex_fail.sol b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex_fail.sol new file mode 100644 index 00000000..a8a488c1 --- /dev/null +++ b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_hex_fail.sol @@ -0,0 +1,7 @@ +contract C { + function f() public pure { + uint X1 = 0x1234__1234__1234__123; + } +} +// ---- +// SyntaxError: (56-79): Invalid use of underscores in number literal. Only one consecutive underscores between digits allowed. -- cgit v1.2.3