From 4faadff0a0ea78daaefeccce75eb1eeab897d6b3 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 8 Jun 2019 06:50:03 -0700 Subject: [PATCH] Add preliminary user-defined type support --- test/scan-test.cc | 199 ++++++++------------------------------- test/scan.h | 233 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+), 160 deletions(-) create mode 100644 test/scan.h diff --git a/test/scan-test.cc b/test/scan-test.cc index 82e83249..0b49fd9f 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -1,172 +1,16 @@ -// Formatting library for C++ - scanning API proof of concept +// Formatting library for C++ - scanning API test // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2019 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. -#include +#include #include -#include "fmt/format.h" #include "gmock.h" #include "gtest-extra.h" - -FMT_BEGIN_NAMESPACE -namespace internal { -enum class scan_type { - none_type, - int_type, - uint_type, - long_long_type, - ulong_long_type, - string_type, - string_view_type -}; - -struct scan_arg { - scan_type arg_type; - union { - int* int_value; - unsigned* uint_value; - long long* long_long_value; - unsigned long long* ulong_long_value; - std::string* string; - fmt::string_view* string_view; - // TODO: more types - }; - - scan_arg() : arg_type(scan_type::none_type) {} - scan_arg(int& value) : arg_type(scan_type::int_type), int_value(&value) {} - scan_arg(unsigned& value) - : arg_type(scan_type::uint_type), uint_value(&value) {} - scan_arg(long long& value) - : arg_type(scan_type::long_long_type), long_long_value(&value) {} - scan_arg(unsigned long long& value) - : arg_type(scan_type::ulong_long_type), ulong_long_value(&value) {} - scan_arg(std::string& value) - : arg_type(scan_type::string_type), string(&value) {} - scan_arg(fmt::string_view& value) - : arg_type(scan_type::string_view_type), string_view(&value) {} -}; -} // namespace internal - -struct scan_args { - int size; - const internal::scan_arg* data; - - template - scan_args(const std::array& store) - : size(N), data(store.data()) { - static_assert(N < INT_MAX, "too many arguments"); - } -}; - -namespace internal { - -struct scan_handler : error_handler { - private: - const char* begin_; - const char* end_; - scan_args args_; - int next_arg_id_; - scan_arg arg_; - - template T read_uint() { - T value = 0; - while (begin_ != end_) { - char c = *begin_++; - if (c < '0' || c > '9') on_error("invalid input"); - // TODO: check overflow - value = value * 10 + (c - '0'); - } - return value; - } - - template T read_int() { - T value = 0; - bool negative = begin_ != end_ && *begin_ == '-'; - if (negative) ++begin_; - value = read_uint::type>(); - if (negative) value = -value; - return value; - } - - public: - scan_handler(string_view input, scan_args args) - : begin_(input.data()), - end_(begin_ + input.size()), - args_(args), - next_arg_id_(0) {} - - const char* pos() const { return begin_; } - - void on_text(const char* begin, const char* end) { - auto size = end - begin; - if (begin_ + size > end_ || !std::equal(begin, end, begin_)) - on_error("invalid input"); - begin_ += size; - } - - void on_arg_id() { - if (next_arg_id_ >= args_.size) on_error("argument index out of range"); - arg_ = args_.data[next_arg_id_++]; - } - void on_arg_id(unsigned) { on_error("invalid format"); } - void on_arg_id(string_view) { on_error("invalid format"); } - - void on_replacement_field(const char*) { - switch (arg_.arg_type) { - case scan_type::int_type: - *arg_.int_value = read_int(); - break; - case scan_type::uint_type: - *arg_.uint_value = read_uint(); - break; - case scan_type::long_long_type: - *arg_.long_long_value = read_int(); - break; - case scan_type::ulong_long_type: - *arg_.ulong_long_value = read_uint(); - break; - case scan_type::string_type: - while (begin_ != end_ && *begin_ != ' ') - arg_.string->push_back(*begin_++); - break; - case scan_type::string_view_type: { - auto s = begin_; - while (begin_ != end_ && *begin_ != ' ') - ++begin_; - *arg_.string_view = fmt::string_view(s, begin_ - s); - break; - } - default: - assert(false); - } - } - - const char* on_format_specs(const char* begin, const char*) { return begin; } -}; -} // namespace internal - -template -std::array make_scan_args(Args&... args) { - return std::array{args...}; -} - -string_view::iterator vscan(string_view input, string_view format_str, - scan_args args) { - internal::scan_handler h(input, args); - internal::parse_format_string(format_str, h); - return input.begin() + (h.pos() - &*input.begin()); -} - -template -string_view::iterator scan(string_view input, string_view format_str, - Args&... args) { - return vscan(input, format_str, make_scan_args(args...)); -} -FMT_END_NAMESPACE +#include "scan.h" TEST(ScanTest, ReadText) { fmt::string_view s = "foo"; @@ -219,6 +63,41 @@ TEST(ScanTest, ReadStringView) { EXPECT_EQ(s, "foo"); } +#ifndef _WIN32 +namespace fmt { +template <> struct scanner { + std::string format; + + scan_parse_context::iterator parse(scan_parse_context& ctx) { + auto it = ctx.begin(); + if (it != ctx.end() && *it == ':') ++it; + auto end = it; + while (end != ctx.end() && *end != '}') ++end; + format.reserve(internal::to_unsigned(end - it + 1)); + format.append(it, end); + format.push_back('\0'); + return end; + } + + template + typename ScanContext::iterator scan(tm& t, ScanContext& ctx) { + auto result = strptime(ctx.begin(), format.c_str(), &t); + if (!result) throw format_error("failed to parse time"); + return result; + } +}; +} // namespace fmt + +TEST(ScanTest, ReadCustom) { + const char* input = "Date: 1985-10-25"; + auto t = tm(); + fmt::scan(input, "Date: {0:%Y-%m-%d}", t); + EXPECT_EQ(t.tm_year, 85); + EXPECT_EQ(t.tm_mon, 9); + EXPECT_EQ(t.tm_mday, 25); +} +#endif + TEST(ScanTest, InvalidFormat) { EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error, "argument index out of range"); diff --git a/test/scan.h b/test/scan.h new file mode 100644 index 00000000..9f0b35f4 --- /dev/null +++ b/test/scan.h @@ -0,0 +1,233 @@ +// Formatting library for C++ - scanning API proof of concept +// +// Copyright (c) 2019 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include + +#include "fmt/format.h" + +FMT_BEGIN_NAMESPACE +template struct scanner { + // A deleted default constructor indicates a disabled scanner. + scanner() = delete; +}; + +class scan_parse_context { + private: + string_view format_; + + public: + using iterator = string_view::iterator; + + explicit FMT_CONSTEXPR scan_parse_context(string_view format) + : format_(format) {} + + FMT_CONSTEXPR iterator begin() const { return format_.begin(); } + FMT_CONSTEXPR iterator end() const { return format_.end(); } + + void advance_to(iterator it) { + format_.remove_prefix(internal::to_unsigned(it - begin())); + } +}; + +struct scan_context { + private: + string_view input_; + + public: + using iterator = const char*; + + explicit scan_context(string_view input) : input_(input) {} + + iterator begin() const { return input_.data(); } + iterator end() const { return begin() + input_.size(); } + + void advance_to(iterator it) { + input_.remove_prefix(internal::to_unsigned(it - begin())); + } +}; + +namespace internal { +enum class scan_type { + none_type, + int_type, + uint_type, + long_long_type, + ulong_long_type, + string_type, + string_view_type, + custom_type +}; + +struct custom_scan_arg { + void* value; + void (*scan)(void* arg, scan_parse_context& parse_ctx, scan_context& ctx); +}; + +class scan_arg { + public: + scan_type type; + union { + int* int_value; + unsigned* uint_value; + long long* long_long_value; + unsigned long long* ulong_long_value; + std::string* string; + fmt::string_view* string_view; + custom_scan_arg custom; + // TODO: more types + }; + + scan_arg() : type(scan_type::none_type) {} + scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {} + scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {} + scan_arg(long long& value) + : type(scan_type::long_long_type), long_long_value(&value) {} + scan_arg(unsigned long long& value) + : type(scan_type::ulong_long_type), ulong_long_value(&value) {} + scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {} + scan_arg(fmt::string_view& value) + : type(scan_type::string_view_type), string_view(&value) {} + template scan_arg(T& value) : type(scan_type::custom_type) { + custom.value = &value; + custom.scan = scan_custom_arg; + } + + private: + template + static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx, + scan_context& ctx) { + scanner s; + parse_ctx.advance_to(s.parse(parse_ctx)); + ctx.advance_to(s.scan(*static_cast(arg), ctx)); + } +}; +} // namespace internal + +struct scan_args { + int size; + const internal::scan_arg* data; + + template + scan_args(const std::array& store) + : size(N), data(store.data()) { + static_assert(N < INT_MAX, "too many arguments"); + } +}; + +namespace internal { + +struct scan_handler : error_handler { + private: + scan_parse_context parse_ctx_; + scan_context scan_ctx_; + scan_args args_; + int next_arg_id_; + scan_arg arg_; + + template T read_uint() { + T value = 0; + auto it = scan_ctx_.begin(), end = scan_ctx_.end(); + while (it != end) { + char c = *it++; + if (c < '0' || c > '9') on_error("invalid input"); + // TODO: check overflow + value = value * 10 + (c - '0'); + } + scan_ctx_.advance_to(it); + return value; + } + + template T read_int() { + T value = 0; + auto it = scan_ctx_.begin(), end = scan_ctx_.end(); + bool negative = it != end && *it == '-'; + if (negative) ++it; + scan_ctx_.advance_to(it); + value = read_uint::type>(); + if (negative) value = -value; + return value; + } + + public: + scan_handler(string_view format, string_view input, scan_args args) + : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {} + + const char* pos() const { return scan_ctx_.begin(); } + + void on_text(const char* begin, const char* end) { + auto size = end - begin; + auto it = scan_ctx_.begin(); + if (it + size > scan_ctx_.end() || !std::equal(begin, end, it)) + on_error("invalid input"); + scan_ctx_.advance_to(it + size); + } + + void on_arg_id() { on_arg_id(next_arg_id_++); } + void on_arg_id(unsigned id) { + if (id >= args_.size) on_error("argument index out of range"); + arg_ = args_.data[id]; + } + void on_arg_id(string_view) { on_error("invalid format"); } + + void on_replacement_field(const char*) { + auto it = scan_ctx_.begin(), end = scan_ctx_.end(); + switch (arg_.type) { + case scan_type::int_type: + *arg_.int_value = read_int(); + break; + case scan_type::uint_type: + *arg_.uint_value = read_uint(); + break; + case scan_type::long_long_type: + *arg_.long_long_value = read_int(); + break; + case scan_type::ulong_long_type: + *arg_.ulong_long_value = read_uint(); + break; + case scan_type::string_type: + while (it != end && *it != ' ') arg_.string->push_back(*it++); + scan_ctx_.advance_to(it); + break; + case scan_type::string_view_type: { + auto s = it; + while (it != end && *it != ' ') ++it; + *arg_.string_view = fmt::string_view(s, it - s); + scan_ctx_.advance_to(it); + break; + } + default: + assert(false); + } + } + + const char* on_format_specs(const char* begin, const char*) { + if (arg_.type != scan_type::custom_type) return begin; + parse_ctx_.advance_to(begin); + arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_); + return parse_ctx_.begin(); + } +}; +} // namespace internal + +template +std::array make_scan_args(Args&... args) { + return std::array{args...}; +} + +string_view::iterator vscan(string_view input, string_view format_str, + scan_args args) { + internal::scan_handler h(format_str, input, args); + internal::parse_format_string(format_str, h); + return input.begin() + (h.pos() - &*input.begin()); +} + +template +string_view::iterator scan(string_view input, string_view format_str, + Args&... args) { + return vscan(input, format_str, make_scan_args(args...)); +} +FMT_END_NAMESPACE