| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/expr/number_parser.h" |
| |
| #include <string.h> |
| |
| #include <variant> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/developer/debug/zxdb/expr/expr_token.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/symbols/base_type.h" |
| |
| namespace zxdb { |
| |
| TEST(NumberParser, ExtractIntegerPrefix) { |
| struct PrefixCase { |
| const char* input; // Input with prefix. |
| const char* num; // Number without prefix. |
| IntegerPrefix::Sign sign; |
| IntegerPrefix::Base base; |
| IntegerPrefix::OctalType oct; |
| } kCases[] = { |
| // clang-format off |
| // input num sign base oct |
| {"", "", IntegerPrefix::kPositive, IntegerPrefix::kDec, IntegerPrefix::OctalType::kC}, |
| {"-", "", IntegerPrefix::kNegative, IntegerPrefix::kDec, IntegerPrefix::OctalType::kC}, |
| {"76", "76", IntegerPrefix::kPositive, IntegerPrefix::kDec, IntegerPrefix::OctalType::kC}, |
| {"- 76", "76", IntegerPrefix::kNegative, IntegerPrefix::kDec, IntegerPrefix::OctalType::kC}, |
| {"0b101", "101", IntegerPrefix::kPositive, IntegerPrefix::kBin, IntegerPrefix::OctalType::kC}, |
| {"-0b101", "101", IntegerPrefix::kNegative, IntegerPrefix::kBin, IntegerPrefix::OctalType::kC}, |
| {"0xabc", "abc", IntegerPrefix::kPositive, IntegerPrefix::kHex, IntegerPrefix::OctalType::kC}, |
| {"0o123", "123", IntegerPrefix::kPositive, IntegerPrefix::kOct, IntegerPrefix::OctalType::kRust}, |
| {"0", "0", IntegerPrefix::kPositive, IntegerPrefix::kDec, IntegerPrefix::OctalType::kC}, |
| {"-\t0", "0", IntegerPrefix::kNegative, IntegerPrefix::kDec, IntegerPrefix::OctalType::kC}, |
| {"hello", "hello", IntegerPrefix::kPositive, IntegerPrefix::kDec, IntegerPrefix::OctalType::kC}, |
| // clang-format on |
| }; |
| |
| for (const auto& cur : kCases) { |
| std::string_view view(cur.input); |
| IntegerPrefix prefix = ExtractIntegerPrefix(&view); |
| |
| EXPECT_EQ(0, view.compare(cur.num)) << "Input " << cur.input; |
| EXPECT_EQ(cur.sign, prefix.sign) << "Input " << cur.input; |
| EXPECT_EQ(cur.base, prefix.base) << "Input " << cur.input; |
| if (cur.base == IntegerPrefix::kOct) |
| EXPECT_EQ(cur.oct, prefix.octal_type) << "Input " << cur.input; |
| } |
| } |
| |
| TEST(NumberParser, ExtractIntegerSuffix) { |
| struct SuffixCase { |
| const char* input; // Input with suffix. |
| const char* err_msg; // Null for no error. |
| const char* num; // Number without suffix (when no error). |
| IntegerSuffix::Signed sign; |
| IntegerSuffix::Length length; |
| } kCases[] = { |
| // clang-format off |
| // input err_msg num sign length |
| {"", nullptr, "", IntegerSuffix::kSigned, IntegerSuffix::Length::kInteger}, |
| {"1234", nullptr, "1234", IntegerSuffix::kSigned, IntegerSuffix::Length::kInteger}, |
| {"12l", nullptr, "12", IntegerSuffix::kSigned, IntegerSuffix::Length::kLong}, |
| {"12LL", nullptr, "12", IntegerSuffix::kSigned, IntegerSuffix::Length::kLongLong}, |
| {"13u", nullptr, "13", IntegerSuffix::kUnsigned, IntegerSuffix::Length::kInteger}, |
| {"14ul", nullptr, "14", IntegerSuffix::kUnsigned, IntegerSuffix::Length::kLong}, |
| {"15LU", nullptr, "15", IntegerSuffix::kUnsigned, IntegerSuffix::Length::kLong}, |
| {"16Llu", nullptr, "16", IntegerSuffix::kUnsigned, IntegerSuffix::Length::kLongLong}, |
| {"17ulL", nullptr, "17", IntegerSuffix::kUnsigned, IntegerSuffix::Length::kLongLong}, |
| |
| // Bad number (still extract suffix). |
| {"taLl", nullptr, "ta", IntegerSuffix::kSigned, IntegerSuffix::Length::kLongLong}, |
| |
| // Error cases. |
| {"12lul", "Duplicate 'l' or 'll' in number suffix.", "", IntegerSuffix::kSigned, IntegerSuffix::Length::kLong}, |
| {"12uu", "Duplicate 'u' in number suffix.", "", IntegerSuffix::kSigned, IntegerSuffix::Length::kLong}, |
| // clang-format on |
| }; |
| |
| for (const auto& cur : kCases) { |
| std::string_view view(cur.input); |
| |
| ErrOr<IntegerSuffix> result = ExtractIntegerSuffix(&view); |
| if (cur.err_msg) { |
| // Expected error. |
| ASSERT_TRUE(result.has_error()); |
| EXPECT_EQ(cur.err_msg, result.err().msg()); |
| } else { |
| // No expected error. |
| ASSERT_FALSE(result.has_error()) << result.err().msg(); |
| EXPECT_EQ(0, view.compare(cur.num)) << "For input " << cur.input; |
| EXPECT_EQ(cur.sign, result.value().type_signed) << "For input " << cur.input; |
| EXPECT_EQ(cur.length, result.value().length) << "For input " << cur.input; |
| } |
| } |
| } |
| |
| TEST(NumberParser, StringToNumber) { |
| using ExpectedType = std::variant<int32_t, uint32_t, int64_t, uint64_t>; |
| |
| struct Case { |
| const char* input; // Input with suffix. |
| const char* err_msg; // Null for no error. |
| |
| ExpectedType expected; |
| const char* expected_type_name; |
| } kCases[] = { |
| // Normal positive input. |
| {"0", nullptr, int32_t(0), "int"}, |
| {"23", nullptr, int32_t(23), "int"}, |
| {"23u", nullptr, uint32_t(23), "unsigned"}, |
| {"23l", nullptr, int64_t(23), "long"}, |
| {"23ul", nullptr, uint64_t(23), "unsigned long"}, |
| {"23ll", nullptr, int64_t(23), "long long"}, |
| {"23ull", nullptr, uint64_t(23), "unsigned long long"}, |
| |
| // Normal negative input. |
| {"-0", nullptr, int32_t(0), "int"}, |
| {"-23", nullptr, int32_t(-23), "int"}, |
| {"-23u", nullptr, uint32_t(-23), "unsigned"}, |
| {"-23l", nullptr, int64_t(-23), "long"}, |
| {"-23lu", nullptr, uint64_t(-23), "unsigned long"}, |
| {"-23ll", nullptr, int64_t(-23), "long long"}, |
| {"-23llu", nullptr, uint64_t(-23), "unsigned long long"}, |
| |
| // Hex input. |
| {"0xabcd", nullptr, int32_t(0xabcd), "int"}, |
| {"- 0x614u", nullptr, uint32_t(-0x614), "unsigned"}, |
| {"0xabcdull", nullptr, uint64_t(0xabcd), "unsigned long long"}, |
| {"0xffffFFFFffffFFFF", nullptr, uint64_t(0xffffFFFFffffFFFF), "unsigned long"}, |
| // This overflow case gets promoted to "long long" because it's "bigger". C++ would put this |
| // in a long. |
| {"-0xffffFFFFffffFFFF", nullptr, uint64_t(-0xffffFFFFffffFFFF), "unsigned long long"}, |
| |
| // Octal input ("0" prefix disallowed). |
| {"0o567", nullptr, int32_t(0567), "int"}, |
| {"-0o567", nullptr, int32_t(-0567), "int"}, |
| {"-0o0567llu", nullptr, uint64_t(-0567), "unsigned long long"}, |
| {"0567", "Octal numbers must be prefixed with '0o'.", int32_t(0567), "int"}, |
| {"-0567llu", "Octal numbers must be prefixed with '0o'.", uint64_t(-0567), |
| "unsigned long long"}, |
| |
| // Binary input. |
| {"0b0", nullptr, int32_t(0), "int"}, |
| {"0b110010", nullptr, int32_t(0b110010), "int"}, |
| {"0b110010l", nullptr, int64_t(0b110010), "long"}, |
| {"-0b110010l", nullptr, int64_t(-0b110010), "long"}, |
| |
| // This number is one too large to put in an signed 32-bit type. C++ would expand this to a |
| // "long" unless it was in a non-decimal base, but our simpler rules put it in an unsigned |
| // since it fits. |
| {"2147483648", nullptr, uint32_t(2147483648), "unsigned"}, |
| // Long override makes it more clear. |
| {"2147483648l", nullptr, int64_t(2147483648), "long"}, |
| |
| // Largest 32-bit negative number. Forcing it to unsigned gives the same bit pattern and same |
| // size type, but in an unsigned type. |
| {"-2147483648", nullptr, int32_t(-2147483648), "int"}, |
| {"-2147483648u", nullptr, uint32_t(-2147483648u), "unsigned"}, |
| |
| // Some error cases. |
| {"", "Expected a number.", 0, nullptr}, |
| {"0x56g", "Invalid character in number.", 0, nullptr}, |
| {"0x56 56", "Invalid character in number.", 0, nullptr}, |
| {"0b5", "Invalid character in number.", 0, nullptr}, |
| {"0x0x34", "Invalid character in number.", 0, nullptr}, |
| {"--45", "Invalid character in number.", 0, nullptr}, |
| {"67lll", "Duplicate 'l' or 'll' in number suffix.", 0, nullptr}, |
| }; |
| |
| for (const auto& cur : kCases) { |
| ErrOrValue result = StringToNumber(cur.input); |
| |
| if (cur.err_msg) { |
| // Expected failure. |
| ASSERT_TRUE(result.has_error()) << " Input = " << cur.input; |
| EXPECT_EQ(cur.err_msg, result.err().msg()) << "Input = " << cur.input; |
| } else { |
| // Expected success. |
| ASSERT_FALSE(result.has_error()) << result.err().msg() << " for input = " << cur.input; |
| |
| // This craziness calls the lambda with the given expected type and value. We can compare the |
| // type of the expected parameter based on the test case with the type generated by our code |
| // in the expr_value. |
| std::visit( |
| [&result, cur](auto expected) { |
| using ExpectedType = std::decay_t<decltype(expected)>; |
| |
| const BaseType* expr_type = result.value().type()->AsBaseType(); |
| ASSERT_TRUE(expr_type) << "Input = " << cur.input; |
| |
| // Size of types should match. |
| EXPECT_EQ(sizeof(ExpectedType), result.value().data().size()) |
| << "Input = " << cur.input; |
| |
| // Sign on types should match. |
| if (std::is_signed_v<ExpectedType>) { |
| EXPECT_EQ(expr_type->base_type(), BaseType::kBaseTypeSigned) |
| << "Input = " << cur.input; |
| } else { |
| EXPECT_EQ(expr_type->base_type(), BaseType::kBaseTypeUnsigned) |
| << "Input = " << cur.input; |
| } |
| |
| // Names of types should match. |
| EXPECT_EQ(cur.expected_type_name, expr_type->GetFullName()) << "Input = " << cur.input; |
| |
| // Values should match. |
| EXPECT_EQ(expected, result.value().GetAs<ExpectedType>()) << "Input = " << cur.input; |
| }, |
| cur.expected); |
| } |
| } |
| } |
| |
| TEST(NumberParser, GetFloatTokenLength) { |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, std::string_view())); |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, " 2.3")); // Whitespace doesn't count. |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12")); // Integer, not a float. |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "-12.2")); // Signs not counted. |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12foo")); // Not a number. |
| |
| // Simple valid cases. |
| EXPECT_EQ(3u, GetFloatTokenLength(ExprLanguage::kC, "2.3")); |
| EXPECT_EQ(3u, GetFloatTokenLength(ExprLanguage::kC, "2.3")); |
| EXPECT_EQ(2u, GetFloatTokenLength(ExprLanguage::kC, "2. foo")); |
| EXPECT_EQ(3u, GetFloatTokenLength(ExprLanguage::kC, "12.+float")); |
| |
| // Exponents. |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12e")); // Exponent needs digits. |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12extremely")); |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12e+")); // Exponent needs digits. |
| EXPECT_EQ(5u, GetFloatTokenLength(ExprLanguage::kC, "12e12")); |
| EXPECT_EQ(6u, GetFloatTokenLength(ExprLanguage::kC, "12.e12 ")); |
| EXPECT_EQ(9u, GetFloatTokenLength(ExprLanguage::kC, "12.019e12+aa")); |
| EXPECT_EQ(5u, GetFloatTokenLength(ExprLanguage::kC, |
| "12e12.2")); // ".2" not counted as part of the number. |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12e+")); |
| EXPECT_EQ(6u, GetFloatTokenLength(ExprLanguage::kC, "12e+12")); |
| EXPECT_EQ(6u, GetFloatTokenLength(ExprLanguage::kC, "12E-12")); |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12 e-12")); |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, "12e- 12")); |
| |
| // Suffixes. A valid number with any alphanumeric characters following is included. The suffixed |
| // will be validated in the parsing phase, not this tokenizing phase. |
| // TODO(bug 43220) Handle Rust-specific suffixes. |
| EXPECT_EQ(4u, GetFloatTokenLength(ExprLanguage::kC, "12.f")); |
| EXPECT_EQ(8u, GetFloatTokenLength(ExprLanguage::kC, "12.float")); |
| EXPECT_EQ(14u, GetFloatTokenLength(ExprLanguage::kC, "12e-12nonsense")); |
| |
| // Rust requires a leading digit, C doesn't (as long as there's a digit following); |
| EXPECT_EQ(3u, GetFloatTokenLength(ExprLanguage::kC, ".14")); |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kC, ".e12")); |
| EXPECT_EQ(0u, GetFloatTokenLength(ExprLanguage::kRust, ".14")); |
| } |
| |
| TEST(NumberParser, StripFloatSuffix) { |
| std::string_view input; |
| EXPECT_EQ(FloatSuffix::kNone, StripFloatSuffix(&input)); |
| |
| input = "2.4"; |
| EXPECT_EQ(FloatSuffix::kNone, StripFloatSuffix(&input)); |
| EXPECT_EQ(input, "2.4"); |
| |
| input = "2.F"; |
| EXPECT_EQ(FloatSuffix::kFloat, StripFloatSuffix(&input)); |
| EXPECT_EQ(input, "2."); |
| |
| // Extra characters are tolerated, these will be identified when parsed. |
| input = "2.4e7randomf"; |
| EXPECT_EQ(FloatSuffix::kFloat, StripFloatSuffix(&input)); |
| EXPECT_EQ(input, "2.4e7random"); |
| |
| input = "9L"; |
| EXPECT_EQ(FloatSuffix::kLong, StripFloatSuffix(&input)); |
| EXPECT_EQ(input, "9"); |
| |
| input = "l"; |
| EXPECT_EQ(FloatSuffix::kLong, StripFloatSuffix(&input)); |
| EXPECT_EQ(input, ""); |
| } |
| |
| namespace { |
| |
| template <typename FloatType> |
| bool FloatValuesEqual(ExprLanguage lang, const char* input, const char* expected_type, |
| FloatType expected_value) { |
| ExprToken token(ExprTokenType::kFloat, input, 0); |
| |
| ErrOrValue result = ValueForFloatToken(lang, token); |
| EXPECT_TRUE(result.ok()) << result.err().msg(); |
| if (!result.ok()) |
| return false; |
| |
| EXPECT_EQ(expected_type, result.value().type()->GetFullName()); |
| EXPECT_EQ(sizeof(expected_value), result.value().data().size()); |
| if (sizeof(expected_value) != result.value().data().size()) |
| return false; |
| |
| // Say the float converter makes the same floating-point value as the current compiler. This |
| // should be the case for all sane compilers. |
| FloatType result_float; |
| memcpy(&result_float, &result.value().data()[0], sizeof(FloatType)); |
| EXPECT_EQ(expected_value, result_float); |
| return expected_value == result_float; |
| } |
| |
| } // namespace |
| |
| TEST(NumberParser, ValueForFloatToken) { |
| EXPECT_TRUE(FloatValuesEqual(ExprLanguage::kC, "2.3", "double", 2.3)); |
| EXPECT_TRUE(FloatValuesEqual(ExprLanguage::kC, "3.14e+0f", "float", 3.14f)); |
| EXPECT_TRUE(FloatValuesEqual(ExprLanguage::kC, "2e9", "double", 2e9)); |
| |
| EXPECT_TRUE(FloatValuesEqual(ExprLanguage::kRust, ".3", "f64", .3)); |
| EXPECT_TRUE(FloatValuesEqual(ExprLanguage::kRust, "3.", "f64", 3.)); |
| // Technically this line is wrong. It should be "2e9f32". |
| // TODO(bug 43220) Handle Rust-specific suffixes. |
| EXPECT_TRUE(FloatValuesEqual(ExprLanguage::kRust, "2e9f", "f32", 2e9f)); |
| |
| // Some error cases. |
| const char kTrailingMsg[] = "Trailing characters on floating-point constant."; |
| EXPECT_EQ(kTrailingMsg, |
| ValueForFloatToken(ExprLanguage::kC, ExprToken(ExprTokenType::kFloat, "3blah", 0)) |
| .err() |
| .msg()); |
| EXPECT_EQ(kTrailingMsg, |
| ValueForFloatToken(ExprLanguage::kC, ExprToken(ExprTokenType::kFloat, "3.2.3", 0)) |
| .err() |
| .msg()); |
| } |
| |
| } // namespace zxdb |