Skip to content

Commit

Permalink
Add preliminary user-defined type support
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Jun 8, 2019
1 parent 5d48733 commit 4faadff
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 160 deletions.
199 changes: 39 additions & 160 deletions test/scan-test.cc
Original file line number Diff line number Diff line change
@@ -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 <array>
#include <time.h>
#include <climits>

#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 <size_t N>
scan_args(const std::array<internal::scan_arg, N>& 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 <typename T = unsigned> 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 <typename T = int> T read_int() {
T value = 0;
bool negative = begin_ != end_ && *begin_ == '-';
if (negative) ++begin_;
value = read_uint<typename std::make_unsigned<T>::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<long long>();
break;
case scan_type::ulong_long_type:
*arg_.ulong_long_value = read_uint<unsigned long long>();
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 <typename... Args>
std::array<internal::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) {
return std::array<internal::scan_arg, sizeof...(Args)>{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<false>(format_str, h);
return input.begin() + (h.pos() - &*input.begin());
}

template <typename... Args>
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";
Expand Down Expand Up @@ -219,6 +63,41 @@ TEST(ScanTest, ReadStringView) {
EXPECT_EQ(s, "foo");
}

#ifndef _WIN32
namespace fmt {
template <> struct scanner<tm> {
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 <class ScanContext>
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");
Expand Down
Loading

0 comments on commit 4faadff

Please sign in to comment.