mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-04 07:04:39 +00:00
parent
426a5dbb76
commit
e8a44d289d
@ -30,6 +30,7 @@ SOFTWARE.
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <any>
|
#include <any>
|
||||||
|
#include <cerrno>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -190,6 +191,76 @@ template <class T> struct parse_number<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <class T> constexpr auto generic_strtod = nullptr;
|
||||||
|
template <> constexpr auto generic_strtod<float> = strtof;
|
||||||
|
template <> constexpr auto generic_strtod<double> = strtod;
|
||||||
|
template <> constexpr auto generic_strtod<long double> = strtold;
|
||||||
|
|
||||||
|
template <class T> inline auto do_strtod(std::string const &s) -> T {
|
||||||
|
if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+')
|
||||||
|
throw std::invalid_argument{"pattern not found"};
|
||||||
|
|
||||||
|
auto [first, last] = pointer_range(s);
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
if (auto x = generic_strtod<T>(first, &ptr); errno == 0) {
|
||||||
|
if (ptr == last)
|
||||||
|
return x;
|
||||||
|
else
|
||||||
|
throw std::invalid_argument{"pattern does not match to the end"};
|
||||||
|
} else if (errno == ERANGE) {
|
||||||
|
throw std::range_error{"not representable"};
|
||||||
|
} else {
|
||||||
|
return x; // unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T> struct parse_number<T, chars_format::general> {
|
||||||
|
auto operator()(std::string const &s) -> T {
|
||||||
|
if (auto r = consume_hex_prefix(s); r.is_hexadecimal)
|
||||||
|
throw std::invalid_argument{
|
||||||
|
"chars_format::general does not parse hexfloat"};
|
||||||
|
|
||||||
|
return do_strtod<T>(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> struct parse_number<T, chars_format::hex> {
|
||||||
|
auto operator()(std::string const &s) -> T {
|
||||||
|
if (auto r = consume_hex_prefix(s); !r.is_hexadecimal)
|
||||||
|
throw std::invalid_argument{"chars_format::hex parses hexfloat"};
|
||||||
|
|
||||||
|
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 (s.find_first_of("eE") == s.npos)
|
||||||
|
throw std::invalid_argument{
|
||||||
|
"chars_format::scientific requires exponent part"};
|
||||||
|
|
||||||
|
return do_strtod<T>(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> struct parse_number<T, chars_format::fixed> {
|
||||||
|
auto operator()(std::string const &s) -> T {
|
||||||
|
if (auto r = consume_hex_prefix(s); r.is_hexadecimal)
|
||||||
|
throw std::invalid_argument{
|
||||||
|
"chars_format::fixed does not parse hexfloat"};
|
||||||
|
if (s.find_first_of("eE") != s.npos)
|
||||||
|
throw std::invalid_argument{
|
||||||
|
"chars_format::fixed does not parse exponent part"};
|
||||||
|
|
||||||
|
return do_strtod<T>(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace details
|
} // namespace details
|
||||||
|
|
||||||
class ArgumentParser;
|
class ArgumentParser;
|
||||||
|
@ -171,3 +171,178 @@ TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"),
|
|||||||
std::invalid_argument);
|
std::invalid_argument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define FLOAT_G(t, literal) \
|
||||||
|
([] { \
|
||||||
|
if constexpr (std::is_same_v<t, float>) \
|
||||||
|
return literal##f; \
|
||||||
|
else if constexpr (std::is_same_v<t, double>) \
|
||||||
|
return literal; \
|
||||||
|
else if constexpr (std::is_same_v<t, long double>) \
|
||||||
|
return literal##l; \
|
||||||
|
}())
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("Parse floating-point argument of general format" *
|
||||||
|
test_suite("scan"),
|
||||||
|
T, float, double, long double) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-n").scan<'g', T>();
|
||||||
|
|
||||||
|
SUBCASE("zero") {
|
||||||
|
program.parse_args({"test", "-n", "0"});
|
||||||
|
REQUIRE(program.get<T>("-n") == 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("non-negative") {
|
||||||
|
program.parse_args({"test", "-n", "3.14"});
|
||||||
|
REQUIRE(program.get<T>("-n") == FLOAT_G(T, 3.14));
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("negative") {
|
||||||
|
program.parse_args({"test", "-n", "-0.12"});
|
||||||
|
REQUIRE(program.get<T>("-n") == FLOAT_G(T, -0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("left-padding is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "\t.32"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("right-padding is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", ".32\n"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("plus sign is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("plus sign after padding is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", " +.12"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("hexfloat is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1a.3p+1"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("does not fit") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}),
|
||||||
|
std::range_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("Parse hexadecimal floating-point argument" *
|
||||||
|
test_suite("scan"),
|
||||||
|
T, float, double, long double) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-n").scan<'a', T>();
|
||||||
|
|
||||||
|
SUBCASE("zero") {
|
||||||
|
// binary-exponent-part is not optional in C++ grammar
|
||||||
|
program.parse_args({"test", "-n", "0x0"});
|
||||||
|
REQUIRE(program.get<T>("-n") == 0x0.p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("non-negative") {
|
||||||
|
program.parse_args({"test", "-n", "0x1a.3p+1"});
|
||||||
|
REQUIRE(program.get<T>("-n") == 0x1a.3p+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("minus sign produces an optional argument") {
|
||||||
|
// XXX may worth a fix
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0x0.12p1"}),
|
||||||
|
std::runtime_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("plus sign is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0x1p0"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("general format is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("Parse floating-point argument of scientific format" *
|
||||||
|
test_suite("scan"),
|
||||||
|
T, float, double, long double) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-n").scan<'e', T>();
|
||||||
|
|
||||||
|
SUBCASE("zero") {
|
||||||
|
program.parse_args({"test", "-n", "0e0"});
|
||||||
|
REQUIRE(program.get<T>("-n") == 0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("non-negative") {
|
||||||
|
program.parse_args({"test", "-n", "3.14e-1"});
|
||||||
|
REQUIRE(program.get<T>("-n") == FLOAT_G(T, 3.14e-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("negative") {
|
||||||
|
program.parse_args({"test", "-n", "-0.12e+1"});
|
||||||
|
REQUIRE(program.get<T>("-n") == FLOAT_G(T, -0.12e+1));
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("plus sign is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12e+1"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("fixed format is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("hexfloat is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("does not fit") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}),
|
||||||
|
std::range_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("Parse floating-point argument of fixed format" *
|
||||||
|
test_suite("scan"),
|
||||||
|
T, float, double, long double) {
|
||||||
|
argparse::ArgumentParser program("test");
|
||||||
|
program.add_argument("-n").scan<'f', T>();
|
||||||
|
|
||||||
|
SUBCASE("zero") {
|
||||||
|
program.parse_args({"test", "-n", ".0"});
|
||||||
|
REQUIRE(program.get<T>("-n") == .0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("non-negative") {
|
||||||
|
program.parse_args({"test", "-n", "3.14"});
|
||||||
|
REQUIRE(program.get<T>("-n") == FLOAT_G(T, 3.14));
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("negative") {
|
||||||
|
program.parse_args({"test", "-n", "-0.12"});
|
||||||
|
REQUIRE(program.get<T>("-n") == FLOAT_G(T, -0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("plus sign is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("scientific format is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14e+0"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("hexfloat is not allowed") {
|
||||||
|
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}),
|
||||||
|
std::invalid_argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user