add support for manual indexing and named fields, add tests

This commit is contained in:
Alexey Ochapov 2021-01-22 04:45:52 +03:00
parent 9c418bc468
commit f627e73e27
No known key found for this signature in database
GPG Key ID: 9DC52E8F031B8DA8
5 changed files with 238 additions and 95 deletions

View File

@ -1090,7 +1090,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
context.check_arg_id(arg_id);
return arg_ref_type(arg_id);
return arg_ref_type(arg_id, true);
}
FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) {
@ -1099,7 +1099,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
}
FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) {
return arg_ref_type(context.next_arg_id());
return arg_ref_type(context.next_arg_id(), false);
}
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }

View File

@ -72,7 +72,13 @@ const T& first(const T& value, const Tail&...) {
// Part of a compiled format string. It can be either literal text or a
// replacement field.
template <typename Char> struct format_part {
enum class kind { arg_index, arg_name, text, replacement };
enum class kind {
arg_index_auto,
arg_index_manual,
arg_name,
text,
replacement
};
struct replacement {
arg_ref<Char> arg_id;
@ -92,11 +98,14 @@ template <typename Char> struct format_part {
// Position past the end of the argument id.
const Char* arg_id_end = nullptr;
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
FMT_CONSTEXPR format_part(kind k = kind::arg_index_auto, value v = {})
: part_kind(k), val(v) {}
static FMT_CONSTEXPR format_part make_arg_index(int index) {
return format_part(kind::arg_index, index);
static FMT_CONSTEXPR format_part make_arg_index_auto(int index) {
return format_part(kind::arg_index_auto, index);
}
static FMT_CONSTEXPR format_part make_arg_index_manual(int index) {
return format_part(kind::arg_index_manual, index);
}
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
return format_part(kind::arg_name, name);
@ -173,13 +182,13 @@ class format_string_compiler : public error_handler {
}
FMT_CONSTEXPR int on_arg_id() {
part_ = part::make_arg_index(parse_context_.next_arg_id());
part_ = part::make_arg_index_auto(parse_context_.next_arg_id());
return 0;
}
FMT_CONSTEXPR int on_arg_id(int id) {
parse_context_.check_arg_id(id);
part_ = part::make_arg_index(id);
part_ = part::make_arg_index_manual(id);
return 0;
}
@ -200,9 +209,12 @@ class format_string_compiler : public error_handler {
repl.specs, parse_context_);
auto it = parse_format_specs(begin, end, handler);
if (*it != '}') on_error("missing '}' in format string");
repl.arg_id = part_.part_kind == part::kind::arg_index
? arg_ref<Char>(part_.val.arg_index)
: arg_ref<Char>(part_.val.str);
repl.arg_id =
(part_.part_kind == part::kind::arg_index_auto ||
part_.part_kind == part::kind::arg_index_manual)
? arg_ref<Char>(part_.val.arg_index,
part_.part_kind == part::kind::arg_index_manual)
: arg_ref<Char>(part_.val.str);
auto replacement_part = part::make_replacement(repl);
replacement_part.arg_id_end = begin;
handler_(replacement_part);
@ -255,7 +267,8 @@ auto vformat_to(OutputIt out, CompiledFormat& cf,
break;
}
case format_part_t::kind::arg_index:
case format_part_t::kind::arg_index_auto:
case format_part_t::kind::arg_index_manual:
advance_to(parse_ctx, part.arg_id_end);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
break;
@ -267,7 +280,8 @@ auto vformat_to(OutputIt out, CompiledFormat& cf,
case format_part_t::kind::replacement: {
const auto& arg_id_value = value.repl.arg_id.val;
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
const auto arg = (value.repl.arg_id.kind == arg_id_kind::index_auto ||
value.repl.arg_id.kind == arg_id_kind::index_manual)
? ctx.arg(arg_id_value.index)
: ctx.arg(arg_id_value.name);
@ -463,6 +477,39 @@ template <typename Char, typename T, int N> struct field {
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name.
template <typename Char> struct runtime_named_field {
using char_type = Char;
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static bool try_format_argument(OutputIt& end, OutputIt out,
basic_string_view<Char> arg_name,
const T& arg) {
if constexpr (!is_named_arg<typename std::remove_cv<T>::type>::value) {
return false;
}
if (arg_name == arg.name) {
end = write<Char>(out, arg.value);
return true;
}
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
auto end = out;
bool found = (try_format_argument(end, out, name, args) || ...);
if (!found) {
throw format_error("argument with specified name is not found");
}
return end;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
@ -482,6 +529,47 @@ template <typename Char, typename T, int N> struct spec_field {
template <typename Char, typename T, int N>
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name and format specifiers.
template <typename Char> struct runtime_named_spec_field {
using char_type = Char;
basic_string_view<Char> name;
basic_format_parse_context<char_type> context;
template <typename OutputIt, typename T, typename... Args>
constexpr static bool try_format_argument(
OutputIt& end, OutputIt out, basic_string_view<Char> arg_name,
basic_format_parse_context<char_type> parse_context, const T& arg,
const Args&... args) {
if constexpr (!is_named_arg<typename std::remove_cv<T>::type>::value) {
return false;
}
if (arg_name == arg.name) {
auto fmt = formatter<fmt::remove_cvref_t<decltype(arg.value)>, Char>();
fmt.parse(parse_context);
const auto& vargs =
make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> format_context(out, vargs);
end = fmt.format(arg.value, format_context);
return true;
}
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
auto end = out;
bool found =
(try_format_argument(end, out, name, context, args, args...) || ...);
if (!found) {
throw format_error("argument with specified name is not found");
}
return end;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_spec_field<Char>> : std::true_type {};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
@ -512,77 +600,107 @@ constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
return pos;
}
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) {
if constexpr (POS !=
basic_string_view<typename S::char_type>(format_str).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
return tail;
else
return make_concat(head, tail);
} else {
return head;
}
}
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int arg_id) {
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str, {}, arg_id + 1);
auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
ctx.next_arg_id()};
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1};
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str) {
using char_type = typename S::char_type;
constexpr basic_string_view<char_type> str = format_str;
if constexpr (str[POS] == '{') {
if (POS + 1 == str.size())
throw format_error("unmatched '{' in format string");
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') {
using id_type = get_type<ID, Args>;
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, id_type, ID>(),
format_str);
} else if constexpr (str[POS + 1] == ':') {
using id_type = get_type<ID, Args>;
constexpr auto result = parse_specs<id_type>(str, POS + 2, ID);
return parse_tail<Args, result.end, result.next_arg_id>(
spec_field<char_type, id_type, ID>{result.fmt}, format_str);
} else {
return unknown_format();
template <typename Char, size_t max_size> struct parts_array {
constexpr void append(const format_part<Char>& part) {
array_[size_++] = part;
}
constexpr const auto& operator[](size_t index) const { return array_[index]; }
constexpr size_t size() const { return size_; }
private:
std::array<format_part<Char>, max_size> array_{};
size_t size_{};
};
template <typename CompiledString> constexpr auto get_parts_array() {
using char_type = typename CompiledString::char_type;
constexpr basic_string_view<char_type> format_str = CompiledString{};
constexpr auto dumb_estimate_counter = [format_str]() {
auto c = format_str.data();
size_t result = 0;
while (c != format_str.data() + format_str.size()) {
if (*c == static_cast<char_type>('{')) {
++result;
}
++c;
}
} else if constexpr (str[POS] == '}') {
if (POS + 1 == str.size())
throw format_error("unmatched '}' in format string");
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
return result * 2 + 1; // "[]{}[]" : #{} = 1, #[] = #{} + 1 = 2
};
constexpr auto estimated_parts_amount = dumb_estimate_counter();
parts_array<char_type, estimated_parts_amount> result;
compile_format_string<true>(
format_str,
[&result](const format_part<char_type>& part) { result.append(part); });
return result;
}
template <typename CompiledString>
constexpr static auto compiled_parts = get_parts_array<CompiledString>();
template <typename Args, typename CompiledString, size_t part_index>
constexpr auto make_compiled_part() {
using char_type = typename CompiledString::char_type;
constexpr format_part part = compiled_parts<CompiledString>[part_index];
if constexpr (part.part_kind == decltype(part)::kind::arg_index_auto ||
part.part_kind == decltype(part)::kind::arg_index_manual) {
using id_type = get_type<part.val.arg_index, Args>;
return field<char_type, id_type, part.val.arg_index>{};
} else if constexpr (part.part_kind == decltype(part)::kind::arg_name) {
return runtime_named_field<char_type>{part.val.str};
} else if constexpr (part.part_kind == decltype(part)::kind::text) {
if constexpr (part.val.str.size() == 1) {
return code_unit<char_type>{part.val.str[0]};
} else {
return text<char_type>{part.val.str};
}
} else if constexpr (part.part_kind == decltype(part)::kind::replacement) {
constexpr basic_string_view<char_type> str = CompiledString{};
constexpr auto pos = static_cast<size_t>(part.arg_id_end - str.data());
if constexpr (part.val.repl.arg_id.kind == arg_id_kind::index_auto ||
part.val.repl.arg_id.kind == arg_id_kind::index_manual) {
constexpr auto arg_index = part.val.repl.arg_id.val.index;
using id_type = get_type<arg_index, Args>;
constexpr bool is_manual_indexing =
part.val.repl.arg_id.kind == arg_id_kind::index_manual;
constexpr auto next_arg_id = is_manual_indexing ? 0 : arg_index + 1;
constexpr auto result = parse_specs<id_type>(str, pos, next_arg_id);
return spec_field<char_type, id_type, arg_index>{result.fmt};
} else if constexpr (part.val.repl.arg_id.kind == arg_id_kind::name) {
constexpr auto arg_name = part.val.repl.arg_id.val.name;
constexpr auto specs_str =
basic_string_view<char_type>(part.arg_id_end, str.size() - pos);
auto parse_context =
basic_format_parse_context<char_type>(specs_str, {}, -1);
return runtime_named_spec_field<char_type>{arg_name, parse_context};
}
}
}
// Prepares a compiled representation for non-empty format string
template <typename Args, typename CompiledString, size_t part_index>
constexpr auto make_compiled_parts() {
if constexpr (part_index == compiled_parts<CompiledString>.size() - 1) {
return make_compiled_part<Args, CompiledString, part_index>();
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
return make_concat(
make_compiled_part<Args, CompiledString, part_index>(),
make_compiled_parts<Args, CompiledString, part_index + 1>());
}
}
@ -595,14 +713,8 @@ constexpr auto compile(S format_str) {
return detail::make_text(str, 0, 0);
} else {
constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
detail::unknown_format>()) {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
} else {
return result;
}
make_compiled_parts<detail::type_list<Args...>, S, 0>();
return result;
}
}
#else

View File

@ -2699,23 +2699,17 @@ class specs_handler : public specs_setter<typename Context::char_type> {
Context& context_;
};
enum class arg_id_kind { none, index, name };
enum class arg_id_kind { none, index_auto, index_manual, name };
// An argument reference.
template <typename Char> struct arg_ref {
FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
FMT_CONSTEXPR explicit arg_ref(int index)
: kind(arg_id_kind::index), val(index) {}
FMT_CONSTEXPR explicit arg_ref(int index, bool is_manual)
: kind(is_manual ? arg_id_kind::index_manual : arg_id_kind::index_auto), val(index) {}
FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
: kind(arg_id_kind::name), val(name) {}
FMT_CONSTEXPR arg_ref& operator=(int idx) {
kind = arg_id_kind::index;
val.index = idx;
return *this;
}
arg_id_kind kind;
union value {
FMT_CONSTEXPR value(int id = 0) : index{id} {}
@ -2769,11 +2763,11 @@ class dynamic_specs_handler
FMT_CONSTEXPR arg_ref_type make_arg_ref(int arg_id) {
context_.check_arg_id(arg_id);
return arg_ref_type(arg_id);
return arg_ref_type(arg_id, true);
}
FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) {
return arg_ref_type(context_.next_arg_id());
return arg_ref_type(context_.next_arg_id(), false);
}
FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<char_type> arg_id) {
@ -3332,7 +3326,8 @@ FMT_CONSTEXPR void handle_dynamic_spec(int& value,
switch (ref.kind) {
case arg_id_kind::none:
break;
case arg_id_kind::index:
case arg_id_kind::index_auto:
case arg_id_kind::index_manual:
value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
ctx.error_handler());
break;

View File

@ -148,6 +148,40 @@ TEST(CompileTest, DynamicWidth) {
fmt::format(FMT_COMPILE("{:{}}{:{}}"), 42, 4, "foo", 5));
}
TEST(CompileTest, ManualOrdering) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{0}"), 42));
EXPECT_EQ(" -42", fmt::format(FMT_COMPILE("{0:4}"), -42));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {1}"), 41, 43));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{1} {0}"), 43, 41));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {2}"), 41, 42, 43));
EXPECT_EQ(" 41 43", fmt::format(FMT_COMPILE("{1:{2}} {0:4}"), 43, 41, 4));
EXPECT_EQ(
"true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
}
TEST(CompileTest, Named) {
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{name1} {name2}"),
fmt::arg("name1", 41), fmt::arg("name2", 43)));
EXPECT_EQ("41 43",
fmt::format(FMT_COMPILE("{name1} {name2}"), fmt::arg("name1", 41),
fmt::arg("name2", 43), fmt::arg("name3", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{name2} {name1}"),
fmt::arg("name1", 43), fmt::arg("name2", 41)));
EXPECT_EQ(" 42",
fmt::format(FMT_COMPILE("{name1:4}"), fmt::arg("name1", 42)));
EXPECT_EQ(" 41 43",
fmt::format(FMT_COMPILE("{name1:{name2}} {name3:4}"),
fmt::arg("name2", 4), fmt::arg("name3", 43),
fmt::arg("name1", 41)));
EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)),
fmt::format_error);
}
TEST(CompileTest, FormatTo) {
char buf[8];
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
@ -174,9 +208,7 @@ TEST(CompileTest, TextAndArg) {
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
}
TEST(CompileTest, Empty) {
EXPECT_EQ("", fmt::format(FMT_COMPILE("")));
}
TEST(CompileTest, Empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); }
#endif
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS

View File

@ -2177,12 +2177,16 @@ struct test_format_specs_handler {
FMT_CONSTEXPR void on_width(int w) { width = w; }
FMT_CONSTEXPR void on_dynamic_width(fmt::detail::auto_id) {}
FMT_CONSTEXPR void on_dynamic_width(int index) { width_ref = index; }
FMT_CONSTEXPR void on_dynamic_width(int index) {
width_ref = fmt::detail::arg_ref<char>(index, true);
}
FMT_CONSTEXPR void on_dynamic_width(string_view) {}
FMT_CONSTEXPR void on_precision(int p) { precision = p; }
FMT_CONSTEXPR void on_dynamic_precision(fmt::detail::auto_id) {}
FMT_CONSTEXPR void on_dynamic_precision(int index) { precision_ref = index; }
FMT_CONSTEXPR void on_dynamic_precision(int index) {
precision_ref = fmt::detail::arg_ref<char>(index, true);
}
FMT_CONSTEXPR void on_dynamic_precision(string_view) {}
FMT_CONSTEXPR void end_precision() {}