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

Added support for binary notation, e.g., 0b101 #306

Merged
merged 1 commit into from
Nov 6, 2023
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
77 changes: 74 additions & 3 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ constexpr bool standard_unsigned_integer<unsigned long long int> = true;

} // namespace

constexpr int radix_2 = 2;
constexpr int radix_8 = 8;
constexpr int radix_10 = 10;
constexpr int radix_16 = 16;
Expand Down Expand Up @@ -183,12 +184,28 @@ constexpr bool starts_with(std::basic_string_view<CharT, Traits> 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;
Expand Down Expand Up @@ -232,6 +249,15 @@ template <class T, auto Param = 0> struct parse_number {
}
};

template <class T> struct parse_number<T, radix_2> {
auto operator()(std::string_view s) -> T {
if (auto [ok, rest] = consume_binary_prefix(s); ok) {
return do_from_chars<T, radix_2>(rest);
}
throw std::invalid_argument{"pattern not found"};
}
};

template <class T> struct parse_number<T, radix_16> {
auto operator()(std::string_view s) -> T {
if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
Expand Down Expand Up @@ -280,6 +306,19 @@ template <class T> struct parse_number<T> {
}
}

auto [ok_binary, rest_binary] = consume_binary_prefix(s);
if (ok_binary) {
try {
return do_from_chars<T, radix_2>(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<T, radix_8>(rest);
Expand Down Expand Up @@ -342,6 +381,10 @@ template <class T> struct parse_number<T, chars_format::general> {
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<T>(s);
Expand All @@ -360,6 +403,9 @@ template <class T> struct parse_number<T, chars_format::hex> {
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<T>(s);
Expand All @@ -373,12 +419,30 @@ template <class T> struct parse_number<T, chars_format::hex> {
}
};

template <class T> struct parse_number<T, chars_format::binary> {
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<T>(s);
}
};

template <class T> struct parse_number<T, chars_format::scientific> {
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"};
Expand All @@ -402,6 +466,10 @@ template <class T> struct parse_number<T, chars_format::fixed> {
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"};
Expand Down Expand Up @@ -629,6 +697,9 @@ class Argument {
} else if constexpr (is_one_of(Shape, 'u') &&
details::standard_unsigned_integer<T>) {
action(details::parse_number<T, details::radix_10>());
} else if constexpr (is_one_of(Shape, 'b') &&
details::standard_unsigned_integer<T>) {
action(details::parse_number<T, details::radix_2>());
} else if constexpr (is_one_of(Shape, 'o') &&
details::standard_unsigned_integer<T>) {
action(details::parse_number<T, details::radix_8>());
Expand Down
43 changes: 43 additions & 0 deletions test/test_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>("-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<t, float>) \
Expand Down
Loading