Merge pull request #64 from lichray/negative

Determine negative numeric values with a grammar
This commit is contained in:
Pranav 2019-11-24 19:53:53 -06:00 committed by GitHub
commit c851668339
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 335 additions and 22 deletions

View File

@ -301,33 +301,179 @@ public:
} }
private: private:
static bool is_integer(const std::string &aValue) { static constexpr int eof = std::char_traits<char>::eof();
if (aValue.empty() ||
((!isdigit(aValue[0])) && (aValue[0] != '-') && (aValue[0] != '+'))) static auto lookahead(std::string_view s) -> int {
if (s.empty())
return eof;
else
return static_cast<int>(static_cast<unsigned char>(s[0]));
}
/*
* decimal-literal:
* '0'
* nonzero-digit digit-sequence_opt
* integer-part fractional-part
* fractional-part
* integer-part '.' exponent-part_opt
* integer-part exponent-part
*
* integer-part:
* digit-sequence
*
* fractional-part:
* '.' post-decimal-point
*
* post-decimal-point:
* digit-sequence exponent-part_opt
*
* exponent-part:
* 'e' post-e
* 'E' post-e
*
* post-e:
* sign_opt digit-sequence
*
* sign: one of
* '+' '-'
*/
static bool is_decimal_literal(std::string_view s) {
auto is_digit = [](auto c) constexpr {
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return true;
default:
return false;
}
};
// precondition: we have consumed or will consume at least one digit
auto consume_digits = [=](std::string_view s) {
auto it = std::find_if_not(begin(s), end(s), is_digit);
return s.substr(it - begin(s));
};
switch (lookahead(s)) {
case '0': {
s.remove_prefix(1);
if (s.empty())
return true;
else
goto integer_part;
}
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
s = consume_digits(s);
if (s.empty())
return true;
else
goto integer_part_consumed;
}
case '.': {
s.remove_prefix(1);
goto post_decimal_point;
}
default:
return false; return false;
char *tPtr;
strtol(aValue.c_str(), &tPtr, 10);
return (*tPtr == 0);
} }
static bool is_float(const std::string &aValue) { integer_part:
std::istringstream tStream(aValue); s = consume_digits(s);
float tFloat; integer_part_consumed:
// noskipws considers leading whitespace invalid switch (lookahead(s)) {
tStream >> std::noskipws >> tFloat; case '.': {
// Check the entire string was consumed s.remove_prefix(1);
// and if either failbit or badbit is set if (is_digit(lookahead(s)))
return tStream.eof() && !tStream.fail(); goto post_decimal_point;
else
goto exponent_part_opt;
}
case 'e':
case 'E': {
s.remove_prefix(1);
goto post_e;
}
default:
return false;
} }
// If an argument starts with "-" or "--", then it's optional post_decimal_point:
static bool is_optional(const std::string &aName) { if (is_digit(lookahead(s))) {
return (aName.size() > 1 && aName[0] == '-' && !is_integer(aName) && s = consume_digits(s);
!is_float(aName)); goto exponent_part_opt;
} else {
return false;
} }
static bool is_positional(const std::string &aName) { exponent_part_opt:
return !is_optional(aName); switch (lookahead(s)) {
case eof:
return true;
case 'e':
case 'E': {
s.remove_prefix(1);
goto post_e;
}
default:
return false;
}
post_e:
switch (lookahead(s)) {
case '-':
case '+':
s.remove_prefix(1);
}
if (is_digit(lookahead(s))) {
s = consume_digits(s);
return s.empty();
} else {
return false;
}
}
static bool is_optional(std::string_view aName) {
return !is_positional(aName);
}
/*
* positional:
* _empty_
* '-'
* '-' decimal-literal
* !'-' anything
*/
static bool is_positional(std::string_view aName) {
switch (lookahead(aName)) {
case eof:
return true;
case '-': {
aName.remove_prefix(1);
if (aName.empty())
return true;
else
return is_decimal_literal(aName);
}
default:
return true;
}
} }
/* /*

View File

@ -97,3 +97,170 @@ TEST_CASE("Parse numbers in E notation (capital E)" *
program.parse_args({"./main", "-1.32E4"}); program.parse_args({"./main", "-1.32E4"});
REQUIRE(program.get<double>("number") == -13200.0); REQUIRE(program.get<double>("number") == -13200.0);
} }
TEST_CASE("Recognize negative decimal numbers" *
test_suite("positional_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("positional");
SUBCASE("zero") { REQUIRE_NOTHROW(program.parse_args({"test", "-0"})); }
SUBCASE("not a decimal") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-00"}), std::runtime_error);
}
SUBCASE("looks like an octal") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-003"}), std::runtime_error);
}
SUBCASE("nonzero-digit") {
REQUIRE_NOTHROW(program.parse_args({"test", "-9"}));
}
SUBCASE("nonzero-digit digit-sequence") {
REQUIRE_NOTHROW(program.parse_args({"test", "-92180"}));
}
SUBCASE("zero dot") { REQUIRE_NOTHROW(program.parse_args({"test", "-0."})); }
SUBCASE("nonzero-digit dot") {
REQUIRE_NOTHROW(program.parse_args({"test", "-8."}));
}
SUBCASE("nonzero-digit digit-sequence dot") {
REQUIRE_NOTHROW(program.parse_args({"test", "-200."}));
}
SUBCASE("integer-part dot") {
REQUIRE_NOTHROW(program.parse_args({"test", "-003."}));
}
SUBCASE("dot digit-sequence") {
REQUIRE_NOTHROW(program.parse_args({"test", "-.0927"}));
}
SUBCASE("not a single dot") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-."}), std::runtime_error);
}
SUBCASE("not a single e") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-e"}), std::runtime_error);
}
SUBCASE("not dot e") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-.e"}), std::runtime_error);
}
SUBCASE("integer-part exponent-part without sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-1e32"}));
}
SUBCASE("integer-part exponent-part with positive sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-1e+32"}));
}
SUBCASE("integer-part exponent-part with negative sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-00e-0"}));
}
SUBCASE("missing mantissa") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-e32"}), std::runtime_error);
}
SUBCASE("missing mantissa but with positive sign") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-e+7"}), std::runtime_error);
}
SUBCASE("missing mantissa but with negative sign") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-e-1"}), std::runtime_error);
}
SUBCASE("nothing after e followed by zero") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-0e"}), std::runtime_error);
}
SUBCASE("nothing after e followed by integer-part") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-13e"}), std::runtime_error);
}
SUBCASE("integer-part dot exponent-part without sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-18.e0"}));
}
SUBCASE("integer-part dot exponent-part with positive sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-18.e+92"}));
}
SUBCASE("integer-part dot exponent-part with negative sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-0.e-92"}));
}
SUBCASE("nothing after e followed by integer-part dot") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-13.e"}),
std::runtime_error);
}
SUBCASE("dot digit-sequence exponent-part without sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-.023e0"}));
}
SUBCASE("dot digit-sequence exponent-part with positive sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-.2e+92"}));
}
SUBCASE("dot digit-sequence exponent-part with negative sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-.71564e-92"}));
}
SUBCASE("nothing after e in fractional-part") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-.283e"}),
std::runtime_error);
}
SUBCASE("exponent-part followed by only a dot") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-.e3"}), std::runtime_error);
}
SUBCASE("exponent-part followed by only a dot but with positive sign") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-.e+3"}),
std::runtime_error);
}
SUBCASE("exponent-part followed by only a dot but with negative sign") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-.e-3"}),
std::runtime_error);
}
SUBCASE("integer-part dot digit-sequence exponent-part without sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-02.023e4000"}));
}
SUBCASE("integer-part dot digit-sequence exponent-part with positive sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-3.239e+76"}));
}
SUBCASE("integer-part dot digit-sequence exponent-part with negative sign") {
REQUIRE_NOTHROW(program.parse_args({"test", "-238237.0e-2"}));
}
SUBCASE("nothing after e") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-3.14e"}),
std::runtime_error);
}
SUBCASE("nothing after e and positive sign") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-2.17e+"}),
std::runtime_error);
}
SUBCASE("nothing after e and negative sign") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e-"}),
std::runtime_error);
}
SUBCASE("more than one sign present in exponent-part") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e+-23"}),
std::runtime_error);
}
SUBCASE("sign at wrong position") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e23+"}),
std::runtime_error);
}
SUBCASE("more than one exponent-part") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e2e9"}),
std::runtime_error);
}
SUBCASE("more than one fractional-part") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6.3"}),
std::runtime_error);
}
SUBCASE("number has its own sign") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-+42"}), std::runtime_error);
}
SUBCASE("looks like hexadecimal integer") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-0x0"}), std::runtime_error);
}
SUBCASE("looks like hexadecimal floating-point") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-0x27.8p1"}),
std::runtime_error);
}
SUBCASE("looks like hexadecimal floating-point without prefix") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-3.8p1"}),
std::runtime_error);
}
SUBCASE("Richard's pp-number") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-0x1e+2"}),
std::runtime_error);
}
SUBCASE("Infinity") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-inf"}), std::runtime_error);
REQUIRE_THROWS_AS(program.parse_args({"test", "-INFINITY"}),
std::runtime_error);
}
SUBCASE("NaN") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-nan"}), std::runtime_error);
REQUIRE_THROWS_AS(program.parse_args({"test", "-NAN"}), std::runtime_error);
}
}