From 16e2a1da72c2c670435796304765f1bc8c281730 Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Sun, 5 Nov 2023 19:06:50 -0600 Subject: [PATCH] Added support for binary notation, e.g., 0b101 --- include/argparse/argparse.hpp | 77 +++++++++++++++++++++++++++++++++-- test/test_scan.cpp | 43 +++++++++++++++++++ 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 27ab3e42..0c85127d 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -148,6 +148,7 @@ constexpr bool standard_unsigned_integer = true; } // namespace +constexpr int radix_2 = 2; constexpr int radix_8 = 8; constexpr int radix_10 = 10; constexpr int radix_16 = 16; @@ -183,12 +184,28 @@ constexpr bool starts_with(std::basic_string_view prefix, } enum class chars_format { - scientific = 0x1, - fixed = 0x2, - hex = 0x4, + scientific = 0xf1, + fixed = 0xf2, + hex = 0xf4, + binary = 0xf8, general = fixed | scientific }; +struct ConsumeBinaryPrefixResult { + bool is_binary; + std::string_view rest; +}; + +constexpr auto consume_binary_prefix(std::string_view s) + -> ConsumeBinaryPrefixResult { + if (starts_with(std::string_view{"0b"}, s) || + starts_with(std::string_view{"0B"}, s)) { + s.remove_prefix(2); + return {true, s}; + } + return {false, s}; +} + struct ConsumeHexPrefixResult { bool is_hexadecimal; std::string_view rest; @@ -232,6 +249,15 @@ template struct parse_number { } }; +template struct parse_number { + auto operator()(std::string_view s) -> T { + if (auto [ok, rest] = consume_binary_prefix(s); ok) { + return do_from_chars(rest); + } + throw std::invalid_argument{"pattern not found"}; + } +}; + template struct parse_number { auto operator()(std::string_view s) -> T { if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { @@ -280,6 +306,19 @@ template struct parse_number { } } + auto [ok_binary, rest_binary] = consume_binary_prefix(s); + if (ok_binary) { + try { + return do_from_chars(rest_binary); + } catch (const std::invalid_argument &err) { + throw std::invalid_argument("Failed to parse '" + std::string(s) + + "' as binary: " + err.what()); + } catch (const std::range_error &err) { + throw std::range_error("Failed to parse '" + std::string(s) + + "' as binary: " + err.what()); + } + } + if (starts_with("0"sv, s)) { try { return do_from_chars(rest); @@ -342,6 +381,10 @@ template struct parse_number { throw std::invalid_argument{ "chars_format::general does not parse hexfloat"}; } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::general does not parse binfloat"}; + } try { return do_strtod(s); @@ -360,6 +403,9 @@ template struct parse_number { if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { throw std::invalid_argument{"chars_format::hex parses hexfloat"}; } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{"chars_format::hex does not parse binfloat"}; + } try { return do_strtod(s); @@ -373,12 +419,30 @@ template struct parse_number { } }; +template struct parse_number { + auto operator()(std::string const &s) -> T { + if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { + throw std::invalid_argument{ + "chars_format::binary does not parse hexfloat"}; + } + if (auto r = consume_binary_prefix(s); !r.is_binary) { + throw std::invalid_argument{"chars_format::binary parses binfloat"}; + } + + return do_strtod(s); + } +}; + template struct parse_number { auto operator()(std::string const &s) -> T { if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { throw std::invalid_argument{ "chars_format::scientific does not parse hexfloat"}; } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::scientific does not parse binfloat"}; + } if (s.find_first_of("eE") == std::string::npos) { throw std::invalid_argument{ "chars_format::scientific requires exponent part"}; @@ -402,6 +466,10 @@ template struct parse_number { throw std::invalid_argument{ "chars_format::fixed does not parse hexfloat"}; } + if (auto r = consume_binary_prefix(s); r.is_binary) { + throw std::invalid_argument{ + "chars_format::fixed does not parse binfloat"}; + } if (s.find_first_of("eE") != std::string::npos) { throw std::invalid_argument{ "chars_format::fixed does not parse exponent part"}; @@ -629,6 +697,9 @@ class Argument { } else if constexpr (is_one_of(Shape, 'u') && details::standard_unsigned_integer) { action(details::parse_number()); + } else if constexpr (is_one_of(Shape, 'b') && + details::standard_unsigned_integer) { + action(details::parse_number()); } else if constexpr (is_one_of(Shape, 'o') && details::standard_unsigned_integer) { action(details::parse_number()); diff --git a/test/test_scan.cpp b/test/test_scan.cpp index 902954fe..c556cb90 100644 --- a/test/test_scan.cpp +++ b/test/test_scan.cpp @@ -209,6 +209,49 @@ TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"), } } +TEST_CASE_TEMPLATE("Parse a binary argument" * test_suite("scan"), T, uint8_t, + uint16_t, uint32_t, uint64_t) { + argparse::ArgumentParser program("test"); + program.add_argument("-n").scan<'b', T>(); + + SUBCASE("with binary digit") { + program.parse_args({"test", "-n", "0b101"}); + REQUIRE(program.get("-n") == 0b101); + } + + SUBCASE("minus sign produces an optional argument") { + REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0b101"}), + std::runtime_error); + } + + SUBCASE("plus sign is not allowed") { + REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0b101"}), + std::invalid_argument); + } + + SUBCASE("does not fit") { + REQUIRE_THROWS_AS( + program.parse_args( + {"test", "-n", + "0b111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111111111111111111111111111111111" + "1"}), + std::range_error); + } +} + #define FLOAT_G(t, literal) \ ([] { \ if constexpr (std::is_same_v) \