mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-04-29 12:13:52 +00:00
Add check if lexical_cast fully consumed input on integer conversion (#68)
* Split signed/unsigned parsing, add check if input is fully consumed, improve tests * Fix complex numbers parsing * Add check if input is fully consumed for double lexical_cast, improve tests * Add non-num non-string lexical_cast for parsable types, add tests * Style fixes from check-style * Some small fixes for corner case tests
This commit is contained in:
parent
f089255ad0
commit
15f9a4f128
@ -575,6 +575,8 @@ class App {
|
||||
|
||||
std::string simple_name = CLI::detail::split(name, ',').at(0);
|
||||
CLI::callback_t fun = [&variable, simple_name, label](results_t res) {
|
||||
if(res[1].back() == 'i')
|
||||
res[1].pop_back();
|
||||
double x, y;
|
||||
bool worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(res[1], y);
|
||||
if(worked)
|
||||
|
@ -72,13 +72,33 @@ constexpr const char *type_name() {
|
||||
|
||||
// Lexical cast
|
||||
|
||||
/// Integers / enums
|
||||
/// Signed integers / enums
|
||||
template <typename T,
|
||||
enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value, detail::enabler> = detail::dummy>
|
||||
enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value) || std::is_enum<T>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T &output) {
|
||||
try {
|
||||
output = static_cast<T>(std::stoll(input));
|
||||
return true;
|
||||
size_t n = 0;
|
||||
output = static_cast<T>(std::stoll(input, &n, 0));
|
||||
return n == input.size();
|
||||
} catch(const std::invalid_argument &) {
|
||||
return false;
|
||||
} catch(const std::out_of_range &) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Unsigned integers
|
||||
template <typename T,
|
||||
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T &output) {
|
||||
if(!input.empty() && input.front() == '-')
|
||||
return false; // std::stoull happily converts negative values to junk without any errors.
|
||||
|
||||
try {
|
||||
size_t n = 0;
|
||||
output = static_cast<T>(std::stoull(input, &n, 0));
|
||||
return n == input.size();
|
||||
} catch(const std::invalid_argument &) {
|
||||
return false;
|
||||
} catch(const std::out_of_range &) {
|
||||
@ -90,8 +110,9 @@ bool lexical_cast(std::string input, T &output) {
|
||||
template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T &output) {
|
||||
try {
|
||||
output = static_cast<T>(std::stold(input));
|
||||
return true;
|
||||
size_t n = 0;
|
||||
output = static_cast<T>(std::stold(input, &n));
|
||||
return n == input.size();
|
||||
} catch(const std::invalid_argument &) {
|
||||
return false;
|
||||
} catch(const std::out_of_range &) {
|
||||
@ -101,12 +122,33 @@ bool lexical_cast(std::string input, T &output) {
|
||||
|
||||
/// String and similar
|
||||
template <typename T,
|
||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value,
|
||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value &&
|
||||
std::is_assignable<T &, std::string>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T &output) {
|
||||
output = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Non-string parsable
|
||||
template <typename T,
|
||||
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value &&
|
||||
!std::is_assignable<T &, std::string>::value,
|
||||
detail::enabler> = detail::dummy>
|
||||
bool lexical_cast(std::string input, T &output) {
|
||||
|
||||
// On GCC 4.7, thread_local is not available, so this optimization
|
||||
// is turned off (avoiding multiple initialisations on multiple usages
|
||||
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && __GNUC__ == 4 && (__GNUC_MINOR__ < 8)
|
||||
std::istringstream is;
|
||||
#else
|
||||
static thread_local std::istringstream is;
|
||||
#endif
|
||||
|
||||
is.str(input);
|
||||
is >> output;
|
||||
return !is.fail() && !is.rdbuf()->in_avail();
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <fstream>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <complex>
|
||||
|
||||
TEST(Split, SimpleByToken) {
|
||||
auto out = CLI::detail::split("one.two.three", '.');
|
||||
@ -348,17 +349,30 @@ TEST(Types, TypeName) {
|
||||
}
|
||||
|
||||
TEST(Types, LexicalCastInt) {
|
||||
std::string input = "912";
|
||||
int x;
|
||||
EXPECT_TRUE(CLI::detail::lexical_cast(input, x));
|
||||
EXPECT_EQ(912, x);
|
||||
std::string signed_input = "-912";
|
||||
int x_signed;
|
||||
EXPECT_TRUE(CLI::detail::lexical_cast(signed_input, x_signed));
|
||||
EXPECT_EQ(-912, x_signed);
|
||||
|
||||
std::string unsigned_input = "912";
|
||||
unsigned int x_unsigned;
|
||||
EXPECT_TRUE(CLI::detail::lexical_cast(unsigned_input, x_unsigned));
|
||||
EXPECT_EQ(912, x_unsigned);
|
||||
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(signed_input, x_unsigned));
|
||||
|
||||
unsigned char y;
|
||||
std::string overflow_input = std::to_string(UINT64_MAX) + "0";
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, y));
|
||||
|
||||
char y_signed;
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, y_signed));
|
||||
|
||||
std::string bad_input = "hello";
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(bad_input, y));
|
||||
|
||||
std::string extra_input = "912i";
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, y));
|
||||
}
|
||||
|
||||
TEST(Types, LexicalCastDouble) {
|
||||
@ -372,6 +386,9 @@ TEST(Types, LexicalCastDouble) {
|
||||
|
||||
std::string overflow_input = "1" + std::to_string(LDBL_MAX);
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, x));
|
||||
|
||||
std::string extra_input = "9.12i";
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, x));
|
||||
}
|
||||
|
||||
TEST(Types, LexicalCastString) {
|
||||
@ -381,6 +398,20 @@ TEST(Types, LexicalCastString) {
|
||||
EXPECT_EQ(input, output);
|
||||
}
|
||||
|
||||
TEST(Types, LexicalCastParsable) {
|
||||
std::string input = "(4.2,7.3)";
|
||||
std::string fail_input = "4.2,7.3";
|
||||
std::string extra_input = "(4.2,7.3)e";
|
||||
|
||||
std::complex<double> output;
|
||||
EXPECT_TRUE(CLI::detail::lexical_cast(input, output));
|
||||
EXPECT_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble
|
||||
EXPECT_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const
|
||||
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
|
||||
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
|
||||
}
|
||||
|
||||
TEST(FixNewLines, BasicCheck) {
|
||||
std::string input = "one\ntwo";
|
||||
std::string output = "one\n; two";
|
||||
|
Loading…
x
Reference in New Issue
Block a user