add support for manual indexing and named fields, add tests
This commit is contained in:
parent
9c418bc468
commit
f627e73e27
@ -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) {
|
template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
|
||||||
context.check_arg_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) {
|
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) {
|
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)); }
|
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
|
||||||
|
|||||||
@ -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
|
// Part of a compiled format string. It can be either literal text or a
|
||||||
// replacement field.
|
// replacement field.
|
||||||
template <typename Char> struct format_part {
|
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 {
|
struct replacement {
|
||||||
arg_ref<Char> arg_id;
|
arg_ref<Char> arg_id;
|
||||||
@ -92,11 +98,14 @@ template <typename Char> struct format_part {
|
|||||||
// Position past the end of the argument id.
|
// Position past the end of the argument id.
|
||||||
const Char* arg_id_end = nullptr;
|
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) {}
|
: part_kind(k), val(v) {}
|
||||||
|
|
||||||
static FMT_CONSTEXPR format_part make_arg_index(int index) {
|
static FMT_CONSTEXPR format_part make_arg_index_auto(int index) {
|
||||||
return format_part(kind::arg_index, 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) {
|
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
||||||
return format_part(kind::arg_name, 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() {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR int on_arg_id(int id) {
|
FMT_CONSTEXPR int on_arg_id(int id) {
|
||||||
parse_context_.check_arg_id(id);
|
parse_context_.check_arg_id(id);
|
||||||
part_ = part::make_arg_index(id);
|
part_ = part::make_arg_index_manual(id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,9 +209,12 @@ class format_string_compiler : public error_handler {
|
|||||||
repl.specs, parse_context_);
|
repl.specs, parse_context_);
|
||||||
auto it = parse_format_specs(begin, end, handler);
|
auto it = parse_format_specs(begin, end, handler);
|
||||||
if (*it != '}') on_error("missing '}' in format string");
|
if (*it != '}') on_error("missing '}' in format string");
|
||||||
repl.arg_id = part_.part_kind == part::kind::arg_index
|
repl.arg_id =
|
||||||
? arg_ref<Char>(part_.val.arg_index)
|
(part_.part_kind == part::kind::arg_index_auto ||
|
||||||
: arg_ref<Char>(part_.val.str);
|
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);
|
auto replacement_part = part::make_replacement(repl);
|
||||||
replacement_part.arg_id_end = begin;
|
replacement_part.arg_id_end = begin;
|
||||||
handler_(replacement_part);
|
handler_(replacement_part);
|
||||||
@ -255,7 +267,8 @@ auto vformat_to(OutputIt out, CompiledFormat& cf,
|
|||||||
break;
|
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);
|
advance_to(parse_ctx, part.arg_id_end);
|
||||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
|
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
|
||||||
break;
|
break;
|
||||||
@ -267,7 +280,8 @@ auto vformat_to(OutputIt out, CompiledFormat& cf,
|
|||||||
|
|
||||||
case format_part_t::kind::replacement: {
|
case format_part_t::kind::replacement: {
|
||||||
const auto& arg_id_value = value.repl.arg_id.val;
|
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.index)
|
||||||
: ctx.arg(arg_id_value.name);
|
: 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>
|
template <typename Char, typename T, int N>
|
||||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
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.
|
// A replacement field that refers to argument N and has format specifiers.
|
||||||
template <typename Char, typename T, int N> struct spec_field {
|
template <typename Char, typename T, int N> struct spec_field {
|
||||||
using char_type = Char;
|
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>
|
template <typename Char, typename T, int N>
|
||||||
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
|
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 {
|
template <typename L, typename R> struct concat {
|
||||||
L lhs;
|
L lhs;
|
||||||
R rhs;
|
R rhs;
|
||||||
@ -512,77 +600,107 @@ constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
|||||||
return 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 {
|
template <typename T, typename Char> struct parse_specs_result {
|
||||||
formatter<T, Char> fmt;
|
formatter<T, Char> fmt;
|
||||||
size_t end;
|
size_t end;
|
||||||
int next_arg_id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
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);
|
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 f = formatter<T, Char>();
|
||||||
auto end = f.parse(ctx);
|
auto end = f.parse(ctx);
|
||||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
|
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1};
|
||||||
ctx.next_arg_id()};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compiles a non-empty format string and returns the compiled representation
|
template <typename Char, size_t max_size> struct parts_array {
|
||||||
// or unknown_format() on unrecognized input.
|
constexpr void append(const format_part<Char>& part) {
|
||||||
template <typename Args, size_t POS, int ID, typename S>
|
array_[size_++] = part;
|
||||||
constexpr auto compile_format_string(S format_str) {
|
}
|
||||||
using char_type = typename S::char_type;
|
constexpr const auto& operator[](size_t index) const { return array_[index]; }
|
||||||
constexpr basic_string_view<char_type> str = format_str;
|
constexpr size_t size() const { return size_; }
|
||||||
if constexpr (str[POS] == '{') {
|
|
||||||
if (POS + 1 == str.size())
|
private:
|
||||||
throw format_error("unmatched '{' in format string");
|
std::array<format_part<Char>, max_size> array_{};
|
||||||
if constexpr (str[POS + 1] == '{') {
|
size_t size_{};
|
||||||
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>;
|
template <typename CompiledString> constexpr auto get_parts_array() {
|
||||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, id_type, ID>(),
|
using char_type = typename CompiledString::char_type;
|
||||||
format_str);
|
constexpr basic_string_view<char_type> format_str = CompiledString{};
|
||||||
} else if constexpr (str[POS + 1] == ':') {
|
constexpr auto dumb_estimate_counter = [format_str]() {
|
||||||
using id_type = get_type<ID, Args>;
|
auto c = format_str.data();
|
||||||
constexpr auto result = parse_specs<id_type>(str, POS + 2, ID);
|
size_t result = 0;
|
||||||
return parse_tail<Args, result.end, result.next_arg_id>(
|
while (c != format_str.data() + format_str.size()) {
|
||||||
spec_field<char_type, id_type, ID>{result.fmt}, format_str);
|
if (*c == static_cast<char_type>('{')) {
|
||||||
} else {
|
++result;
|
||||||
return unknown_format();
|
}
|
||||||
|
++c;
|
||||||
}
|
}
|
||||||
} else if constexpr (str[POS] == '}') {
|
return result * 2 + 1; // "[]{}[]" : #{} = 1, #[] = #{} + 1 = 2
|
||||||
if (POS + 1 == str.size())
|
};
|
||||||
throw format_error("unmatched '}' in format string");
|
constexpr auto estimated_parts_amount = dumb_estimate_counter();
|
||||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
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 {
|
} else {
|
||||||
constexpr auto end = parse_text(str, POS + 1);
|
return make_concat(
|
||||||
if constexpr (end - POS > 1) {
|
make_compiled_part<Args, CompiledString, part_index>(),
|
||||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
make_compiled_parts<Args, CompiledString, part_index + 1>());
|
||||||
format_str);
|
|
||||||
} else {
|
|
||||||
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
|
|
||||||
format_str);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,14 +713,8 @@ constexpr auto compile(S format_str) {
|
|||||||
return detail::make_text(str, 0, 0);
|
return detail::make_text(str, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
constexpr auto result =
|
constexpr auto result =
|
||||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
make_compiled_parts<detail::type_list<Args...>, S, 0>();
|
||||||
format_str);
|
return result;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|||||||
@ -2699,23 +2699,17 @@ class specs_handler : public specs_setter<typename Context::char_type> {
|
|||||||
Context& context_;
|
Context& context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class arg_id_kind { none, index, name };
|
enum class arg_id_kind { none, index_auto, index_manual, name };
|
||||||
|
|
||||||
// An argument reference.
|
// An argument reference.
|
||||||
template <typename Char> struct arg_ref {
|
template <typename Char> struct arg_ref {
|
||||||
FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
|
FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
|
||||||
|
|
||||||
FMT_CONSTEXPR explicit arg_ref(int index)
|
FMT_CONSTEXPR explicit arg_ref(int index, bool is_manual)
|
||||||
: kind(arg_id_kind::index), val(index) {}
|
: 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)
|
FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
|
||||||
: kind(arg_id_kind::name), val(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;
|
arg_id_kind kind;
|
||||||
union value {
|
union value {
|
||||||
FMT_CONSTEXPR value(int id = 0) : index{id} {}
|
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) {
|
FMT_CONSTEXPR arg_ref_type make_arg_ref(int arg_id) {
|
||||||
context_.check_arg_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(auto_id) {
|
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) {
|
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) {
|
switch (ref.kind) {
|
||||||
case arg_id_kind::none:
|
case arg_id_kind::none:
|
||||||
break;
|
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),
|
value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
|
||||||
ctx.error_handler());
|
ctx.error_handler());
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -148,6 +148,40 @@ TEST(CompileTest, DynamicWidth) {
|
|||||||
fmt::format(FMT_COMPILE("{:{}}{:{}}"), 42, 4, "foo", 5));
|
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) {
|
TEST(CompileTest, FormatTo) {
|
||||||
char buf[8];
|
char buf[8];
|
||||||
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
|
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
|
||||||
@ -174,9 +208,7 @@ TEST(CompileTest, TextAndArg) {
|
|||||||
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
|
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CompileTest, Empty) {
|
TEST(CompileTest, Empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); }
|
||||||
EXPECT_EQ("", fmt::format(FMT_COMPILE("")));
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
|||||||
@ -2177,12 +2177,16 @@ struct test_format_specs_handler {
|
|||||||
|
|
||||||
FMT_CONSTEXPR void on_width(int w) { width = w; }
|
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(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_dynamic_width(string_view) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_precision(int p) { precision = p; }
|
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(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 on_dynamic_precision(string_view) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR void end_precision() {}
|
FMT_CONSTEXPR void end_precision() {}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user