diff --git a/doc/syntax.rst b/doc/syntax.rst index 109b7175..c019945b 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -15,13 +15,15 @@ literal text, it can be escaped by doubling: ``{{`` and ``}}``. The grammar for a replacement field is as follows: .. productionlist:: sf - replacement_field: "{" [`arg_index`] [":" `format_spec`] "}" + replacement_field: "{" [`arg_field`] [":" `format_spec`] "}" + arg_field: `arg_index` | `arg_name` arg_index: `integer` + arg_name: \^[a-zA-Z_][a-zA-Z0-9_]*$\ -In less formal terms, the replacement field can start with an *arg_index* +In less formal terms, the replacement field can start with an *arg_field* that specifies the argument whose value is to be formatted and inserted into the output instead of the replacement field. -The *arg_index* is optionally followed by a *format_spec*, which is preceded +The *arg_field* is optionally followed by a *format_spec*, which is preceded by a colon ``':'``. These specify a non-default format for the replacement value. See also the :ref:`formatspec` section. @@ -73,8 +75,8 @@ The general form of a *standard format specifier* is: fill: align: "<" | ">" | "=" | "^" sign: "+" | "-" | " " - width: `integer` | "{" `arg_index` "}" - precision: `integer` | "{" `arg_index` "}" + width: `integer` | "{" `arg_field` "}" + precision: `integer` | "{" `arg_field` "}" type: `int_type` | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s" int_type: "b" | "B" | "d" | "o" | "x" | "X" diff --git a/format.cc b/format.cc index d7c2ca90..398a2bbc 100644 --- a/format.cc +++ b/format.cc @@ -265,6 +265,12 @@ int parse_nonnegative_int(const Char *&s) { return value; } +template +inline bool is_name_start(Char c) +{ + return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '_' == c; +} + inline void require_numeric_argument(const Arg &arg, char spec) { if (arg.type > Arg::LAST_NUMERIC_TYPE) { std::string message = @@ -402,10 +408,10 @@ inline Arg::StringValue ignore_incompatible_str( } // namespace FMT_FUNC void fmt::SystemError::init( - int err_code, StringRef format_str, ArgList args) { + int err_code, StringRef format_str, ArgList args, const ArgMap &map) { error_code_ = err_code; MemoryWriter w; - internal::format_system_error(w, err_code, format(format_str, args)); + internal::format_system_error(w, err_code, format(format_str, args, map)); std::runtime_error &base = *this; base = std::runtime_error(w.str()); } @@ -518,10 +524,10 @@ FMT_FUNC int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) { } FMT_FUNC void fmt::WindowsError::init( - int err_code, StringRef format_str, ArgList args) { + int err_code, StringRef format_str, ArgList args, const ArgMap &map) { error_code_ = err_code; MemoryWriter w; - internal::format_windows_error(w, err_code, format(format_str, args)); + internal::format_windows_error(w, err_code, format(format_str, args, map)); std::runtime_error &base = *this; base = std::runtime_error(w.str()); } @@ -693,6 +699,24 @@ inline Arg fmt::BasicFormatter::parse_arg_index(const Char *&s) { return arg; } +template +inline Arg fmt::BasicFormatter::parse_arg_name(const Char *&s, const fmt::BasicArgMap &map) { + assert(is_name_start(*s)); + const Char *start = s; + Char c; + do { + c = *++s; + } while ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9'); + const int* index = map.find(fmt::BasicStringRef(start, s - start)); + if (!index) + FMT_THROW(fmt::FormatError("argument not found")); + const char *error = 0; + Arg arg = get_arg(*index, error); + if (error) + FMT_THROW(fmt::FormatError(error)); + return arg; +} + FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg( unsigned arg_index, const char *&error) { Arg arg = args_[arg_index]; @@ -960,7 +984,7 @@ void fmt::internal::PrintfFormatter::format( template const Char *fmt::BasicFormatter::format( - const Char *&format_str, const Arg &arg) { + const Char *&format_str, const Arg &arg, const BasicArgMap &map) { const Char *s = format_str; FormatSpec spec; if (*s == ':') { @@ -1038,7 +1062,7 @@ const Char *fmt::BasicFormatter::format( spec.width_ = parse_nonnegative_int(s); } else if (*s == '{') { ++s; - const Arg &width_arg = parse_arg_index(s); + const Arg &width_arg = is_name_start(*s) ? parse_arg_name(s, map) : parse_arg_index(s); if (*s++ != '}') FMT_THROW(FormatError("invalid format string")); ULongLong value = 0; @@ -1075,7 +1099,7 @@ const Char *fmt::BasicFormatter::format( spec.precision_ = parse_nonnegative_int(s); } else if (*s == '{') { ++s; - const Arg &precision_arg = parse_arg_index(s); + const Arg &precision_arg = is_name_start(*s) ? parse_arg_name(s, map) : parse_arg_index(s); if (*s++ != '}') FMT_THROW(FormatError("invalid format string")); ULongLong value = 0; @@ -1128,7 +1152,7 @@ const Char *fmt::BasicFormatter::format( template void fmt::BasicFormatter::format( - BasicStringRef format_str, const ArgList &args) { + BasicStringRef format_str, const ArgList &args, const BasicArgMap &map) { const Char *s = start_ = format_str.c_str(); set_args(args); while (*s) { @@ -1142,8 +1166,8 @@ void fmt::BasicFormatter::format( if (c == '}') FMT_THROW(FormatError("unmatched '}' in format string")); write(writer_, start_, s - 1); - Arg arg = parse_arg_index(s); - s = format(s, arg); + Arg arg = is_name_start(*s) ? parse_arg_name(s, map) : parse_arg_index(s); + s = format(s, arg, map); } write(writer_, start_, s); } @@ -1160,33 +1184,33 @@ FMT_FUNC void fmt::report_windows_error( } #endif -FMT_FUNC void fmt::print(std::FILE *f, StringRef format_str, ArgList args) { +FMT_FUNC void fmt::print(std::FILE *f, StringRef format_str, ArgList args, const ArgMap &map) { MemoryWriter w; - w.write(format_str, args); + w.write(format_str, args, map); std::fwrite(w.data(), 1, w.size(), f); } -FMT_FUNC void fmt::print(StringRef format_str, ArgList args) { - print(stdout, format_str, args); +FMT_FUNC void fmt::print(StringRef format_str, ArgList args, const ArgMap &map) { + print(stdout, format_str, args, map); } -FMT_FUNC void fmt::print(std::ostream &os, StringRef format_str, ArgList args) { +FMT_FUNC void fmt::print(std::ostream &os, StringRef format_str, ArgList args, const ArgMap &map) { MemoryWriter w; - w.write(format_str, args); + w.write(format_str, args, map); os.write(w.data(), w.size()); } -FMT_FUNC void fmt::print_colored(Color c, StringRef format, ArgList args) { +FMT_FUNC void fmt::print_colored(Color c, StringRef format, ArgList args, const ArgMap &map) { char escape[] = "\x1b[30m"; escape[3] = '0' + static_cast(c); std::fputs(escape, stdout); - print(format, args); + print(format, args, map); std::fputs(RESET_COLOR, stdout); } -FMT_FUNC int fmt::fprintf(std::FILE *f, StringRef format, ArgList args) { +FMT_FUNC int fmt::fprintf(std::FILE *f, StringRef format, ArgList args, const ArgMap &map) { MemoryWriter w; - printf(w, format, args); + printf(w, format, args, map); std::size_t size = w.size(); return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); } @@ -1200,10 +1224,10 @@ template struct fmt::internal::BasicData; template void fmt::internal::FixedBuffer::grow(std::size_t); template const char *fmt::BasicFormatter::format( - const char *&format_str, const fmt::internal::Arg &arg); + const char *&format_str, const fmt::internal::Arg &arg, const BasicArgMap &map); template void fmt::BasicFormatter::format( - BasicStringRef format, const ArgList &args); + BasicStringRef format, const ArgList &args, const BasicArgMap &map); template void fmt::internal::PrintfFormatter::format( BasicWriter &writer, BasicStringRef format, const ArgList &args); @@ -1221,10 +1245,10 @@ template int fmt::internal::CharTraits::format_float( template void fmt::internal::FixedBuffer::grow(std::size_t); template const wchar_t *fmt::BasicFormatter::format( - const wchar_t *&format_str, const fmt::internal::Arg &arg); + const wchar_t *&format_str, const fmt::internal::Arg &arg, const BasicArgMap &map); template void fmt::BasicFormatter::format( - BasicStringRef format, const ArgList &args); + BasicStringRef format, const ArgList &args, const BasicArgMap &map); template void fmt::internal::PrintfFormatter::format( BasicWriter &writer, BasicStringRef format, diff --git a/format.h b/format.h index 2e558edc..3fd5ba4a 100644 --- a/format.h +++ b/format.h @@ -227,6 +227,11 @@ class BasicStringRef { std::size_t size_; public: + /** + Constructs an empty string reference object. + */ + BasicStringRef() : data_(), size_() {} + /** Constructs a string reference object from a C string and a size. */ @@ -274,6 +279,9 @@ class BasicStringRef { friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) { return lhs.data_ != rhs.data_; } + friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) { + return std::lexicographical_compare(lhs.data_, lhs.data_ + lhs.size_, rhs.data_, rhs.data_ + rhs.size_); + } }; typedef BasicStringRef StringRef; @@ -759,6 +767,15 @@ struct Value { }; }; +template +struct NamedArg +{ + BasicStringRef name; + T const& arg; + + NamedArg(BasicStringRef name, T const& arg) : name(name), arg(arg) {} +}; + // A formatting argument. It is a POD type to allow storage in // internal::MemoryBuffer. struct Arg : Value { @@ -962,6 +979,50 @@ class MakeValue : public Arg { } }; +template +inline const T &strip_name(const T &arg) +{ + return arg; +} + +template +inline const T &strip_name(const NamedArg &namedArg) +{ + return namedArg.arg; +} + +template +struct NamedArgsCounter +{ + static const int value = 0; +}; + +template +struct NamedArgsCounter : NamedArgsCounter +{}; + +template +struct NamedArgsCounter, U...> +{ + static const int value = 1 + NamedArgsCounter::value; +}; + +template +inline void add_named_args(NameIndexPair*) {} + +template +inline void add_named_args(NameIndexPair* map, T const&, U const&... rest) +{ + add_named_args(map, rest...); +} + +template +inline void add_named_args(NameIndexPair* map, const NamedArg &namedArg, U const&... rest) +{ + *map = NameIndexPair(namedArg.name, N); + add_named_args(map + 1, rest...); +} + #define FMT_DISPATCH(call) static_cast(this)->call // An argument visitor. @@ -1146,6 +1207,48 @@ class ArgList { struct FormatSpec; +template +struct BasicArgMap +{ + typedef std::pair, int> value_type; + + BasicArgMap() : map_(), size_() {} + + BasicArgMap(value_type* map, int size) + : map_(map), size_(size) + { + std::sort(map, map + size, [](const value_type &lhs, const value_type &rhs) + { + return lhs.first < rhs.first; + }); + } + + const int* find(BasicStringRef name) const + { + value_type* first = map_; + value_type* last = map_ + size_; + while (first != last) + { + value_type* it(first + (last - first >> 1)); + if (name < it->first) + last = it; + else if (it->first < name) + first = ++it; + else + return &it->second; + } + return nullptr; + } + +private: + + value_type* map_; + int size_; +}; + +typedef BasicArgMap ArgMap; +typedef BasicArgMap WArgMap; + namespace internal { class FormatterBase { @@ -1208,14 +1311,17 @@ class BasicFormatter : private internal::FormatterBase { // Parses argument index and returns corresponding argument. internal::Arg parse_arg_index(const Char *&s); + // Parses argument name and returns corresponding argument. + internal::Arg parse_arg_name(const Char *&s, const BasicArgMap &map); + public: explicit BasicFormatter(BasicWriter &w) : writer_(w) {} BasicWriter &writer() { return writer_; } - void format(BasicStringRef format_str, const ArgList &args); + void format(BasicStringRef format_str, const ArgList &args, const BasicArgMap &map); - const Char *format(const Char *&format_str, const internal::Arg &arg); + const Char *format(const Char *&format_str, const internal::Arg &arg, const BasicArgMap &map); }; enum Alignment { @@ -1556,7 +1662,11 @@ inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) { template \ void func(arg_type arg0, const Args & ... args) { \ typename fmt::internal::ArgArray::Type array; \ - func(arg0, fmt::internal::make_arg_list(array, args...)); \ + const int count = fmt::internal::NamedArgsCounter::value; \ + fmt::BasicArgMap::value_type mapArray[count + 1]; \ + fmt::internal::add_named_args<0>(mapArray, args...); \ + fmt::BasicArgMap map(mapArray, count); \ + func(arg0, fmt::internal::make_arg_list(array, fmt::internal::strip_name(args)...), map); \ } // Defines a variadic constructor. @@ -1564,7 +1674,11 @@ inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) { template \ ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ typename fmt::internal::ArgArray::Type array; \ - func(arg0, arg1, fmt::internal::make_arg_list(array, args...)); \ + const int count = fmt::internal::NamedArgsCounter::value; \ + fmt::BasicArgMap::value_type mapArray[count + 1]; \ + fmt::internal::add_named_args<0>(mapArray, args...); \ + fmt::BasicArgMap map(mapArray, count); \ + func(arg0, arg1, fmt::internal::make_arg_list(array, fmt::internal::strip_name(args)...), map); \ } #else @@ -1641,7 +1755,7 @@ inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) { */ class SystemError : public internal::RuntimeError { private: - void init(int err_code, StringRef format_str, ArgList args); + void init(int err_code, StringRef format_str, ArgList args, const ArgMap &map); protected: int error_code_; @@ -1677,7 +1791,7 @@ class SystemError : public internal::RuntimeError { \endrst */ SystemError(int error_code, StringRef message) { - init(error_code, message, ArgList()); + init(error_code, message, ArgList(), ArgMap()); } FMT_VARIADIC_CTOR(SystemError, init, int, StringRef) @@ -1852,8 +1966,8 @@ class BasicWriter { See also :ref:`syntax`. \endrst */ - void write(BasicStringRef format, ArgList args) { - BasicFormatter(*this).format(format, args); + void write(BasicStringRef format, ArgList args, const BasicArgMap &map) { + BasicFormatter(*this).format(format, args, map); } FMT_VARIADIC_VOID(write, BasicStringRef) @@ -2401,7 +2515,7 @@ void format(BasicFormatter &f, const Char *&format_str, const T &value) { internal::Arg arg = internal::MakeValue(str); arg.type = static_cast( internal::MakeValue::type(str)); - format_str = f.format(format_str, arg); + format_str = f.format(format_str, arg, BasicArgMap()); } // Reports a system error without throwing an exception. @@ -2413,7 +2527,7 @@ void report_system_error(int error_code, StringRef message) FMT_NOEXCEPT; /** A Windows error. */ class WindowsError : public SystemError { private: - void init(int error_code, StringRef format_str, ArgList args); + void init(int error_code, StringRef format_str, ArgList args, const ArgMap &map); public: /** @@ -2445,7 +2559,7 @@ class WindowsError : public SystemError { \endrst */ WindowsError(int error_code, StringRef message) { - init(error_code, message, ArgList()); + init(error_code, message, ArgList(), ArgMap()); } FMT_VARIADIC_CTOR(WindowsError, init, int, StringRef) }; @@ -2464,7 +2578,7 @@ enum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; Example: PrintColored(fmt::RED, "Elapsed time: {0:.2f} seconds") << 1.23; */ -void print_colored(Color c, StringRef format, ArgList args); +void print_colored(Color c, StringRef format, ArgList args, const ArgMap &map); /** \rst @@ -2475,15 +2589,15 @@ void print_colored(Color c, StringRef format, ArgList args); std::string message = format("The answer is {}", 42); \endrst */ -inline std::string format(StringRef format_str, ArgList args) { +inline std::string format(StringRef format_str, ArgList args, const ArgMap &map) { MemoryWriter w; - w.write(format_str, args); + w.write(format_str, args, map); return w.str(); } -inline std::wstring format(WStringRef format_str, ArgList args) { +inline std::wstring format(WStringRef format_str, ArgList args, const WArgMap &map) { WMemoryWriter w; - w.write(format_str, args); + w.write(format_str, args, map); return w.str(); } @@ -2496,7 +2610,7 @@ inline std::wstring format(WStringRef format_str, ArgList args) { print(stderr, "Don't {}!", "panic"); \endrst */ -void print(std::FILE *f, StringRef format_str, ArgList args); +void print(std::FILE *f, StringRef format_str, ArgList args, const ArgMap &map); /** \rst @@ -2507,7 +2621,7 @@ void print(std::FILE *f, StringRef format_str, ArgList args); print("Elapsed time: {0:.2f} seconds", 1.23); \endrst */ -void print(StringRef format_str, ArgList args); +void print(StringRef format_str, ArgList args, const ArgMap &map); /** \rst @@ -2518,10 +2632,10 @@ void print(StringRef format_str, ArgList args); print(cerr, "Don't {}!", "panic"); \endrst */ -void print(std::ostream &os, StringRef format_str, ArgList args); +void print(std::ostream &os, StringRef format_str, ArgList args, const ArgMap &map); template -void printf(BasicWriter &w, BasicStringRef format, ArgList args) { +void printf(BasicWriter &w, BasicStringRef format, ArgList args, const ArgMap &) { internal::PrintfFormatter().format(w, format, args); } @@ -2534,9 +2648,9 @@ void printf(BasicWriter &w, BasicStringRef format, ArgList args) { std::string message = fmt::sprintf("The answer is %d", 42); \endrst */ -inline std::string sprintf(StringRef format, ArgList args) { +inline std::string sprintf(StringRef format, ArgList args, const ArgMap &map) { MemoryWriter w; - printf(w, format, args); + printf(w, format, args, map); return w.str(); } @@ -2549,7 +2663,7 @@ inline std::string sprintf(StringRef format, ArgList args) { fmt::fprintf(stderr, "Don't %s!", "panic"); \endrst */ -int fprintf(std::FILE *f, StringRef format, ArgList args); +int fprintf(std::FILE *f, StringRef format, ArgList args, const ArgMap &); /** \rst @@ -2560,8 +2674,8 @@ int fprintf(std::FILE *f, StringRef format, ArgList args); fmt::printf("Elapsed time: %.2f seconds", 1.23); \endrst */ -inline int printf(StringRef format, ArgList args) { - return fprintf(stdout, format, args); +inline int printf(StringRef format, ArgList args, const ArgMap &map) { + return fprintf(stdout, format, args, map); } /** @@ -2667,6 +2781,16 @@ inline void format_decimal(char *&buffer, T value) { internal::format_decimal(buffer, abs_value, num_digits); buffer += num_digits; } + +template +inline internal::NamedArg arg(StringRef name, T const& arg) { + return internal::NamedArg(name, arg); +} + +template +inline internal::NamedArg arg(WStringRef name, T const& arg) { + return internal::NamedArg(name, arg); +} } #if FMT_GCC_VERSION @@ -2702,8 +2826,12 @@ inline void format_decimal(char *&buffer, T value) { ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ const Args & ... args) { \ typename fmt::internal::ArgArray::Type array; \ + const int count = fmt::internal::NamedArgsCounter::value; \ + fmt::BasicArgMap::value_type mapArray[count + 1]; \ + fmt::internal::add_named_args<0>(mapArray, args...); \ + fmt::BasicArgMap map(mapArray, count); \ call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ - fmt::internal::make_arg_list(array, args...)); \ + fmt::internal::make_arg_list(array, fmt::internal::strip_name(args)...), map); \ } #else // Defines a wrapper for a function taking __VA_ARGS__ arguments @@ -2771,6 +2899,10 @@ inline void format_decimal(char *&buffer, T value) { #define FMT_VARIADIC_W(ReturnType, func, ...) \ FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__) +#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id) + +#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__) + namespace fmt { FMT_VARIADIC(std::string, format, StringRef) FMT_VARIADIC_W(std::wstring, format, WStringRef) diff --git a/posix.h b/posix.h index 70a0db1a..00928e11 100644 --- a/posix.h +++ b/posix.h @@ -197,8 +197,8 @@ public: // of MinGW that define fileno as a macro. int (fileno)() const; - void print(fmt::StringRef format_str, const ArgList &args) { - fmt::print(file_, format_str, args); + void print(fmt::StringRef format_str, const ArgList &args, const ArgMap &map) { + fmt::print(file_, format_str, args, map); } FMT_VARIADIC(void, print, fmt::StringRef) }; diff --git a/test/format-test.cc b/test/format-test.cc index 67ce0811..0c641d3c 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -561,7 +561,7 @@ TEST(FormatterTest, ArgsInDifferentPositions) { TEST(FormatterTest, ArgErrors) { EXPECT_THROW_MSG(format("{"), FormatError, "invalid format string"); - EXPECT_THROW_MSG(format("{x}"), FormatError, "invalid format string"); + EXPECT_THROW_MSG(format("{?}"), FormatError, "invalid format string"); EXPECT_THROW_MSG(format("{0"), FormatError, "invalid format string"); EXPECT_THROW_MSG(format("{0}"), FormatError, "argument index out of range"); @@ -606,6 +606,20 @@ TEST(FormatterTest, ManyArgs) { EXPECT_THROW_MSG(TestFormat::format(format_str), FormatError, "argument index out of range"); } + +TEST(FormatterTest, NamedArg) { + char a = 'A', b = 'B', c = 'C'; + EXPECT_EQ("BBAACC", format("{1}{b}{0}{a}{2}{c}", FMT_CAPTURE(a, b, c))); + EXPECT_EQ(" A", format("{a:>2}", FMT_CAPTURE(a))); + EXPECT_THROW_MSG(format("{a+}", FMT_CAPTURE(a)), FormatError, "missing '}' in format string"); + EXPECT_THROW_MSG(format("{d}", FMT_CAPTURE(a, b, c)), FormatError, "argument not found"); + EXPECT_THROW_MSG(format("{a}{}", FMT_CAPTURE(a)), + FormatError, "cannot switch from manual to automatic argument indexing"); + EXPECT_THROW_MSG(format("{}{a}", FMT_CAPTURE(a)), + FormatError, "cannot switch from automatic to manual argument indexing"); + EXPECT_EQ(" -42", format("{0:{width}}", -42, fmt::arg("width", 4))); + EXPECT_EQ("st", format("{0:.{precision}}", "str", fmt::arg("precision", 2))); +} #endif TEST(FormatterTest, AutoArgIndex) { @@ -919,7 +933,7 @@ TEST(FormatterTest, RuntimeWidth) { FormatError, "invalid format string"); EXPECT_THROW_MSG(format("{0:{}", 0), FormatError, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG(format("{0:{x}}", 0), + EXPECT_THROW_MSG(format("{0:{?}}", 0), FormatError, "invalid format string"); EXPECT_THROW_MSG(format("{0:{1}}", 0), FormatError, "argument index out of range"); @@ -1036,7 +1050,7 @@ TEST(FormatterTest, RuntimePrecision) { FormatError, "invalid format string"); EXPECT_THROW_MSG(format("{0:.{}", 0), FormatError, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG(format("{0:.{x}}", 0), + EXPECT_THROW_MSG(format("{0:.{?}}", 0), FormatError, "invalid format string"); EXPECT_THROW_MSG(format("{0:.{1}", 0, 0), FormatError, "precision not allowed in integer format specifier"); @@ -1550,10 +1564,10 @@ TEST(StrTest, Convert) { } std::string format_message(int id, const char *format, - const fmt::ArgList &args) { + const fmt::ArgList &args, const fmt::ArgMap &map) { MemoryWriter w; w.write("[{}] ", id); - w.write(format, args); + w.write(format, args, map); return w.str(); }