Improve scan
This commit is contained in:
parent
e420a58f24
commit
3647feaad5
@ -20,66 +20,50 @@ TEST(scan_test, read_text) {
|
|||||||
fmt::string_view s = "foo";
|
fmt::string_view s = "foo";
|
||||||
auto end = fmt::scan_to(s, "foo");
|
auto end = fmt::scan_to(s, "foo");
|
||||||
EXPECT_EQ(end, s.end());
|
EXPECT_EQ(end, s.end());
|
||||||
EXPECT_THROW_MSG(fmt::scan_to("fob", "foo"), fmt::format_error,
|
EXPECT_THROW_MSG(fmt::scan<int>("fob", "foo"), fmt::format_error,
|
||||||
"invalid input");
|
"invalid input");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, read_int) {
|
TEST(scan_test, read_int) {
|
||||||
int n = 0;
|
EXPECT_EQ(fmt::scan<int>("42", "{}")->value(), 42);
|
||||||
fmt::scan_to("42", "{}", n);
|
EXPECT_EQ(fmt::scan<int>("-42", "{}")->value(), -42);
|
||||||
EXPECT_EQ(n, 42);
|
EXPECT_EQ(fmt::scan<int>("42", "{:}")->value(), 42);
|
||||||
fmt::scan_to("-42", "{}", n);
|
EXPECT_THROW_MSG(fmt::scan<int>(std::to_string(INT_MAX + 1u), "{}"),
|
||||||
EXPECT_EQ(n, -42);
|
|
||||||
fmt::scan_to("42", "{:}", n);
|
|
||||||
EXPECT_EQ(n, 42);
|
|
||||||
EXPECT_THROW_MSG(fmt::scan_to(std::to_string(INT_MAX + 1u), "{}", n),
|
|
||||||
fmt::format_error, "number is too big");
|
fmt::format_error, "number is too big");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, read_longlong) {
|
TEST(scan_test, read_long_long) {
|
||||||
long long n = 0;
|
EXPECT_EQ(fmt::scan<long long>("42", "{}")->value(), 42);
|
||||||
fmt::scan_to("42", "{}", n);
|
EXPECT_EQ(fmt::scan<long long>("-42", "{}")->value(), -42);
|
||||||
EXPECT_EQ(n, 42);
|
|
||||||
fmt::scan_to("-42", "{}", n);
|
|
||||||
EXPECT_EQ(n, -42);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, read_uint) {
|
TEST(scan_test, read_uint) {
|
||||||
unsigned n = 0;
|
EXPECT_EQ(fmt::scan<unsigned>("42", "{}")->value(), 42);
|
||||||
fmt::scan_to("42", "{}", n);
|
EXPECT_THROW_MSG(fmt::scan<unsigned>("-42", "{}"), fmt::format_error,
|
||||||
EXPECT_EQ(n, 42);
|
|
||||||
EXPECT_THROW_MSG(fmt::scan_to("-42", "{}", n), fmt::format_error,
|
|
||||||
"invalid input");
|
"invalid input");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, read_ulonglong) {
|
TEST(scan_test, read_ulong_long) {
|
||||||
unsigned long long n = 0;
|
EXPECT_EQ(fmt::scan<unsigned long long>("42", "{}")->value(), 42);
|
||||||
fmt::scan_to("42", "{}", n);
|
EXPECT_THROW_MSG(fmt::scan<unsigned long long>("-42", "{}")->value(),
|
||||||
EXPECT_EQ(n, 42);
|
fmt::format_error, "invalid input");
|
||||||
EXPECT_THROW_MSG(fmt::scan_to("-42", "{}", n), fmt::format_error,
|
|
||||||
"invalid input");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, read_hex) {
|
TEST(scan_test, read_hex) {
|
||||||
unsigned n = 0;
|
EXPECT_EQ(fmt::scan<unsigned>("2a", "{:x}")->value(), 42);
|
||||||
fmt::scan_to("2a", "{:x}", n);
|
|
||||||
EXPECT_EQ(n, 42);
|
|
||||||
auto num_digits = std::numeric_limits<unsigned>::digits / 4;
|
auto num_digits = std::numeric_limits<unsigned>::digits / 4;
|
||||||
EXPECT_THROW_MSG(
|
EXPECT_THROW_MSG(
|
||||||
fmt::scan_to(fmt::format("1{:0{}}", 0, num_digits), "{:x}", n),
|
fmt::scan<unsigned>(fmt::format("1{:0{}}", 0, num_digits), "{:x}")
|
||||||
|
->value(),
|
||||||
fmt::format_error, "number is too big");
|
fmt::format_error, "number is too big");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, read_string) {
|
TEST(scan_test, read_string) {
|
||||||
std::string s;
|
EXPECT_EQ(fmt::scan<std::string>("foo", "{}")->value(), "foo");
|
||||||
fmt::scan_to("foo", "{}", s);
|
|
||||||
EXPECT_EQ(s, "foo");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, read_string_view) {
|
TEST(scan_test, read_string_view) {
|
||||||
fmt::string_view s;
|
EXPECT_EQ(fmt::scan<fmt::string_view>("foo", "{}")->value(), "foo");
|
||||||
fmt::scan_to("foo", "{}", s);
|
|
||||||
EXPECT_EQ(s, "foo");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, separator) {
|
TEST(scan_test, separator) {
|
||||||
@ -109,16 +93,14 @@ template <> struct scanner<num> {
|
|||||||
|
|
||||||
template <class ScanContext>
|
template <class ScanContext>
|
||||||
auto scan(num& n, ScanContext& ctx) const -> typename ScanContext::iterator {
|
auto scan(num& n, ScanContext& ctx) const -> typename ScanContext::iterator {
|
||||||
// TODO: handle specifier
|
return hex ? scan_to(ctx, "{:x}", n.value) : scan_to(ctx, "{}", n.value);
|
||||||
return fmt::scan(ctx, "{}", n.value);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace fmt
|
} // namespace fmt
|
||||||
|
|
||||||
TEST(scan_test, read_custom) {
|
TEST(scan_test, read_custom) {
|
||||||
auto n = num();
|
EXPECT_EQ(fmt::scan<num>("42", "{}")->value().value, 42);
|
||||||
fmt::scan_to("42", "{}", n);
|
EXPECT_EQ(fmt::scan<num>("2a", "{:x}")->value().value, 42);
|
||||||
EXPECT_EQ(n.value, 42);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, invalid_format) {
|
TEST(scan_test, invalid_format) {
|
||||||
@ -136,10 +118,7 @@ TEST(scan_test, example) {
|
|||||||
EXPECT_EQ(value, 42);
|
EXPECT_EQ(value, 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(scan_test, end_of_input) {
|
TEST(scan_test, end_of_input) { fmt::scan<int>("", "{}"); }
|
||||||
int value = 0;
|
|
||||||
fmt::scan_to("", "{}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FMT_USE_FCNTL
|
#if FMT_USE_FCNTL
|
||||||
TEST(scan_test, file) {
|
TEST(scan_test, file) {
|
||||||
@ -151,7 +130,7 @@ TEST(scan_test, file) {
|
|||||||
|
|
||||||
int n1 = 0, n2 = 0;
|
int n1 = 0, n2 = 0;
|
||||||
fmt::buffered_file f = pipe.read_end.fdopen("r");
|
fmt::buffered_file f = pipe.read_end.fdopen("r");
|
||||||
fmt::scan(f.get(), "{} {}", n1, n2);
|
fmt::scan_to(f.get(), "{} {}", n1, n2);
|
||||||
EXPECT_EQ(n1, 10);
|
EXPECT_EQ(n1, 10);
|
||||||
EXPECT_EQ(n2, 20);
|
EXPECT_EQ(n2, 20);
|
||||||
}
|
}
|
||||||
@ -170,7 +149,7 @@ TEST(scan_test, lock) {
|
|||||||
fmt::buffered_file f = pipe.read_end.fdopen("r");
|
fmt::buffered_file f = pipe.read_end.fdopen("r");
|
||||||
auto fun = [&]() {
|
auto fun = [&]() {
|
||||||
int value = 0;
|
int value = 0;
|
||||||
while (fmt::scan(f.get(), "{}", value)) {
|
while (fmt::scan_to(f.get(), "{}", value)) {
|
||||||
if (value != 42) {
|
if (value != 42) {
|
||||||
pipe.read_end.close();
|
pipe.read_end.close();
|
||||||
EXPECT_EQ(value, 42);
|
EXPECT_EQ(value, 42);
|
||||||
|
67
test/scan.h
67
test/scan.h
@ -450,8 +450,7 @@ const char* parse_scan_specs(const char* begin, const char* end,
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
||||||
auto read(scan_iterator it, T& value)
|
auto read(scan_iterator it, T& value) -> scan_iterator {
|
||||||
-> scan_iterator {
|
|
||||||
if (it == scan_sentinel()) return it;
|
if (it == scan_sentinel()) return it;
|
||||||
char c = *it;
|
char c = *it;
|
||||||
if (c < '0' || c > '9') throw_format_error("invalid input");
|
if (c < '0' || c > '9') throw_format_error("invalid input");
|
||||||
@ -484,8 +483,7 @@ auto read(scan_iterator it, T& value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
||||||
auto read_hex(scan_iterator it, T& value)
|
auto read_hex(scan_iterator it, T& value) -> scan_iterator {
|
||||||
-> scan_iterator {
|
|
||||||
if (it == scan_sentinel()) return it;
|
if (it == scan_sentinel()) return it;
|
||||||
int digit = to_hex_digit(*it);
|
int digit = to_hex_digit(*it);
|
||||||
if (digit < 0) throw_format_error("invalid input");
|
if (digit < 0) throw_format_error("invalid input");
|
||||||
@ -510,13 +508,12 @@ auto read_hex(scan_iterator it, T& value)
|
|||||||
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
|
||||||
auto read(scan_iterator it, T& value, const format_specs<>& specs)
|
auto read(scan_iterator it, T& value, const format_specs<>& specs)
|
||||||
-> scan_iterator {
|
-> scan_iterator {
|
||||||
if (specs.type == presentation_type::hex_lower)
|
if (specs.type == presentation_type::hex_lower) return read_hex(it, value);
|
||||||
return read_hex(it, value);
|
|
||||||
return read(it, value);
|
return read(it, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_signed<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_signed<T>::value)>
|
||||||
auto read(scan_iterator it, T& value, const format_specs<>& = {})
|
auto read(scan_iterator it, T& value, const format_specs<>& specs = {})
|
||||||
-> scan_iterator {
|
-> scan_iterator {
|
||||||
bool negative = it != scan_sentinel() && *it == '-';
|
bool negative = it != scan_sentinel() && *it == '-';
|
||||||
if (negative) {
|
if (negative) {
|
||||||
@ -525,7 +522,7 @@ auto read(scan_iterator it, T& value, const format_specs<>& = {})
|
|||||||
}
|
}
|
||||||
using unsigned_type = typename std::make_unsigned<T>::type;
|
using unsigned_type = typename std::make_unsigned<T>::type;
|
||||||
unsigned_type abs_value = 0;
|
unsigned_type abs_value = 0;
|
||||||
it = read(it, abs_value);
|
it = read(it, abs_value, specs);
|
||||||
auto n = static_cast<T>(abs_value);
|
auto n = static_cast<T>(abs_value);
|
||||||
value = negative ? -n : n;
|
value = negative ? -n : n;
|
||||||
return it;
|
return it;
|
||||||
@ -636,6 +633,29 @@ auto make_scan_args(T&... args) -> std::array<scan_arg, sizeof...(T)> {
|
|||||||
return {{args...}};
|
return {{args...}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> class scan_value {
|
||||||
|
private:
|
||||||
|
T value_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
scan_value(T value) : value_(std::move(value)) {}
|
||||||
|
|
||||||
|
const T& value() const { return value_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// A rudimentary version of std::expected for testing the API shape.
|
||||||
|
template <typename T> class expected {
|
||||||
|
private:
|
||||||
|
T value_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
expected(T value) : value_(std::move(value)) {}
|
||||||
|
|
||||||
|
const T* operator->() const { return &value_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> using scan_result = expected<scan_value<T>>;
|
||||||
|
|
||||||
void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
|
void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
|
||||||
auto h = detail::scan_handler(fmt, buf, args);
|
auto h = detail::scan_handler(fmt, buf, args);
|
||||||
detail::parse_format_string<false>(fmt, h);
|
detail::parse_format_string<false>(fmt, h);
|
||||||
@ -650,32 +670,6 @@ auto scan_to(string_view input, string_view fmt, T&... args)
|
|||||||
return input.begin() + (buf.begin().base() - input.data());
|
return input.begin() + (buf.begin().base() - input.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class scan_value {
|
|
||||||
private:
|
|
||||||
T value_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
scan_value(T value) : value_(std::move(value)) {}
|
|
||||||
|
|
||||||
const T& value() const {
|
|
||||||
return value_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// A rudimentary version of std::expected for testing the API shape.
|
|
||||||
template <typename T>
|
|
||||||
class expected {
|
|
||||||
private:
|
|
||||||
T value_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
expected(T value) : value_(std::move(value)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using scan_result = expected<scan_value<T>>;
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
auto scan(string_view input, string_view fmt) -> scan_result<T> {
|
auto scan(string_view input, string_view fmt) -> scan_result<T> {
|
||||||
static_assert(std::is_same<remove_cvref_t<T>, T>::value, "");
|
static_assert(std::is_same<remove_cvref_t<T>, T>::value, "");
|
||||||
@ -686,14 +680,15 @@ auto scan(string_view input, string_view fmt) -> scan_result<T> {
|
|||||||
|
|
||||||
template <typename InputRange, typename... T,
|
template <typename InputRange, typename... T,
|
||||||
FMT_ENABLE_IF(!std::is_convertible<InputRange, string_view>::value)>
|
FMT_ENABLE_IF(!std::is_convertible<InputRange, string_view>::value)>
|
||||||
auto scan(InputRange&& input, string_view fmt, T&... args)
|
auto scan_to(InputRange&& input, string_view fmt, T&... args)
|
||||||
-> decltype(std::begin(input)) {
|
-> decltype(std::begin(input)) {
|
||||||
auto it = std::begin(input);
|
auto it = std::begin(input);
|
||||||
vscan(get_buffer(it), fmt, make_scan_args(args...));
|
vscan(get_buffer(it), fmt, make_scan_args(args...));
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T> bool scan(std::FILE* f, string_view fmt, T&... args) {
|
template <typename... T>
|
||||||
|
bool scan_to(FILE* f, string_view fmt, T&... args) {
|
||||||
auto&& buf = detail::file_scan_buffer(f);
|
auto&& buf = detail::file_scan_buffer(f);
|
||||||
vscan(buf, fmt, make_scan_args(args...));
|
vscan(buf, fmt, make_scan_args(args...));
|
||||||
return buf.begin() != buf.end();
|
return buf.begin() != buf.end();
|
||||||
|
Loading…
Reference in New Issue
Block a user