Support named arguments
This commit is contained in:
parent
ed2dfe5124
commit
7487bde587
@ -72,6 +72,10 @@ Write API
|
|||||||
Utilities
|
Utilities
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::arg(StringRef, const T&)
|
||||||
|
|
||||||
|
.. doxygendefine:: FMT_CAPTURE
|
||||||
|
|
||||||
.. doxygendefine:: FMT_VARIADIC
|
.. doxygendefine:: FMT_VARIADIC
|
||||||
|
|
||||||
.. doxygenclass:: fmt::ArgList
|
.. doxygenclass:: fmt::ArgList
|
||||||
|
@ -15,13 +15,15 @@ literal text, it can be escaped by doubling: ``{{`` and ``}}``.
|
|||||||
The grammar for a replacement field is as follows:
|
The grammar for a replacement field is as follows:
|
||||||
|
|
||||||
.. productionlist:: sf
|
.. productionlist:: sf
|
||||||
replacement_field: "{" [`arg_index`] [":" `format_spec`] "}"
|
replacement_field: "{" [`arg_field`] [":" `format_spec`] "}"
|
||||||
|
arg_field: `arg_index` | `arg_name`
|
||||||
arg_index: `integer`
|
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
|
that specifies the argument whose value is to be formatted and inserted into
|
||||||
the output instead of the replacement field.
|
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.
|
by a colon ``':'``. These specify a non-default format for the replacement value.
|
||||||
|
|
||||||
See also the :ref:`formatspec` section.
|
See also the :ref:`formatspec` section.
|
||||||
@ -73,8 +75,8 @@ The general form of a *standard format specifier* is:
|
|||||||
fill: <a character other than '{' or '}'>
|
fill: <a character other than '{' or '}'>
|
||||||
align: "<" | ">" | "=" | "^"
|
align: "<" | ">" | "=" | "^"
|
||||||
sign: "+" | "-" | " "
|
sign: "+" | "-" | " "
|
||||||
width: `integer` | "{" `arg_index` "}"
|
width: `integer` | "{" `arg_field` "}"
|
||||||
precision: `integer` | "{" `arg_index` "}"
|
precision: `integer` | "{" `arg_field` "}"
|
||||||
type: `int_type` | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
type: `int_type` | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||||
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
||||||
|
|
||||||
|
103
format.cc
103
format.cc
@ -265,6 +265,11 @@ int parse_nonnegative_int(const Char *&s) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
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) {
|
inline void require_numeric_argument(const Arg &arg, char spec) {
|
||||||
if (arg.type > Arg::LAST_NUMERIC_TYPE) {
|
if (arg.type > Arg::LAST_NUMERIC_TYPE) {
|
||||||
std::string message =
|
std::string message =
|
||||||
@ -580,6 +585,49 @@ FMT_FUNC void fmt::internal::format_windows_error(
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
void fmt::ArgList::Map<Char>::init(const ArgList &args) {
|
||||||
|
if (!map_.empty())
|
||||||
|
return;
|
||||||
|
const internal::NamedArg<Char>* named_arg;
|
||||||
|
bool use_values = args.type(MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
|
||||||
|
if (use_values) {
|
||||||
|
for (unsigned i = 0;/*nothing*/; ++i) {
|
||||||
|
internal::Arg::Type arg_type = args.type(i);
|
||||||
|
switch (arg_type) {
|
||||||
|
case internal::Arg::NONE:
|
||||||
|
return;
|
||||||
|
case internal::Arg::NAMED_ARG:
|
||||||
|
named_arg = static_cast<const internal::NamedArg<Char>*>(args.values_[i].pointer);
|
||||||
|
map_.insert(Pair(named_arg->name, *named_arg));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/*nothing*/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (unsigned i = 0; i != MAX_PACKED_ARGS; ++i) {
|
||||||
|
internal::Arg::Type arg_type = args.type(i);
|
||||||
|
if (arg_type == internal::Arg::NAMED_ARG) {
|
||||||
|
named_arg = static_cast<const internal::NamedArg<Char>*>(args.args_[i].pointer);
|
||||||
|
map_.insert(Pair(named_arg->name, *named_arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (unsigned i = MAX_PACKED_ARGS;/*nothing*/; ++i) {
|
||||||
|
switch (args.args_[i].type) {
|
||||||
|
case internal::Arg::NONE:
|
||||||
|
return;
|
||||||
|
case internal::Arg::NAMED_ARG:
|
||||||
|
named_arg = static_cast<const internal::NamedArg<Char>*>(args.args_[i].pointer);
|
||||||
|
map_.insert(Pair(named_arg->name, *named_arg));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/*nothing*/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// An argument formatter.
|
// An argument formatter.
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
class fmt::internal::ArgFormatter :
|
class fmt::internal::ArgFormatter :
|
||||||
@ -681,6 +729,20 @@ void fmt::BasicWriter<Char>::write_str(
|
|||||||
write_str(str_value, str_size, spec);
|
write_str(str_value, str_size, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
inline Arg fmt::BasicFormatter<Char>::get_arg(
|
||||||
|
const BasicStringRef<Char>& arg_name, const char *&error) {
|
||||||
|
if (check_no_auto_index(error)) {
|
||||||
|
next_arg_index_ = -1;
|
||||||
|
map_.init(args_);
|
||||||
|
const Arg* arg = map_.find(arg_name);
|
||||||
|
if (arg)
|
||||||
|
return *arg;
|
||||||
|
error = "argument not found";
|
||||||
|
}
|
||||||
|
return Arg();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) {
|
inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) {
|
||||||
const char *error = 0;
|
const char *error = 0;
|
||||||
@ -693,11 +755,33 @@ inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) {
|
|||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
inline Arg fmt::BasicFormatter<Char>::parse_arg_name(const Char *&s) {
|
||||||
|
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 char *error = 0;
|
||||||
|
Arg arg = get_arg(fmt::BasicStringRef<Char>(start, s - start), error);
|
||||||
|
if (error)
|
||||||
|
FMT_THROW(fmt::FormatError(error));
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg(
|
FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg(
|
||||||
unsigned arg_index, const char *&error) {
|
unsigned arg_index, const char *&error) {
|
||||||
Arg arg = args_[arg_index];
|
Arg arg = args_[arg_index];
|
||||||
if (arg.type == Arg::NONE)
|
switch (arg.type) {
|
||||||
|
case Arg::NONE:
|
||||||
error = "argument index out of range";
|
error = "argument index out of range";
|
||||||
|
break;
|
||||||
|
case Arg::NAMED_ARG:
|
||||||
|
arg = *static_cast<const internal::Arg*>(arg.pointer);
|
||||||
|
default:
|
||||||
|
/*nothing*/;
|
||||||
|
}
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,13 +792,20 @@ inline Arg fmt::internal::FormatterBase::next_arg(const char *&error) {
|
|||||||
return Arg();
|
return Arg();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool fmt::internal::FormatterBase::check_no_auto_index(const char *&error) {
|
||||||
|
if (next_arg_index_ > 0) {
|
||||||
|
error = "cannot switch from automatic to manual argument indexing";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
inline Arg fmt::internal::FormatterBase::get_arg(
|
inline Arg fmt::internal::FormatterBase::get_arg(
|
||||||
unsigned arg_index, const char *&error) {
|
unsigned arg_index, const char *&error) {
|
||||||
if (next_arg_index_ <= 0) {
|
if (check_no_auto_index(error)) {
|
||||||
next_arg_index_ = -1;
|
next_arg_index_ = -1;
|
||||||
return do_get_arg(arg_index, error);
|
return do_get_arg(arg_index, error);
|
||||||
}
|
}
|
||||||
error = "cannot switch from automatic to manual argument indexing";
|
|
||||||
return Arg();
|
return Arg();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1038,7 +1129,7 @@ const Char *fmt::BasicFormatter<Char>::format(
|
|||||||
spec.width_ = parse_nonnegative_int(s);
|
spec.width_ = parse_nonnegative_int(s);
|
||||||
} else if (*s == '{') {
|
} else if (*s == '{') {
|
||||||
++s;
|
++s;
|
||||||
const Arg &width_arg = parse_arg_index(s);
|
const Arg &width_arg = is_name_start(*s) ? parse_arg_name(s) : parse_arg_index(s);
|
||||||
if (*s++ != '}')
|
if (*s++ != '}')
|
||||||
FMT_THROW(FormatError("invalid format string"));
|
FMT_THROW(FormatError("invalid format string"));
|
||||||
ULongLong value = 0;
|
ULongLong value = 0;
|
||||||
@ -1075,7 +1166,7 @@ const Char *fmt::BasicFormatter<Char>::format(
|
|||||||
spec.precision_ = parse_nonnegative_int(s);
|
spec.precision_ = parse_nonnegative_int(s);
|
||||||
} else if (*s == '{') {
|
} else if (*s == '{') {
|
||||||
++s;
|
++s;
|
||||||
const Arg &precision_arg = parse_arg_index(s);
|
const Arg &precision_arg = is_name_start(*s) ? parse_arg_name(s) : parse_arg_index(s);
|
||||||
if (*s++ != '}')
|
if (*s++ != '}')
|
||||||
FMT_THROW(FormatError("invalid format string"));
|
FMT_THROW(FormatError("invalid format string"));
|
||||||
ULongLong value = 0;
|
ULongLong value = 0;
|
||||||
@ -1142,7 +1233,7 @@ void fmt::BasicFormatter<Char>::format(
|
|||||||
if (c == '}')
|
if (c == '}')
|
||||||
FMT_THROW(FormatError("unmatched '}' in format string"));
|
FMT_THROW(FormatError("unmatched '}' in format string"));
|
||||||
write(writer_, start_, s - 1);
|
write(writer_, start_, s - 1);
|
||||||
Arg arg = parse_arg_index(s);
|
Arg arg = is_name_start(*s) ? parse_arg_name(s) : parse_arg_index(s);
|
||||||
s = format(s, arg);
|
s = format(s, arg);
|
||||||
}
|
}
|
||||||
write(writer_, start_, s);
|
write(writer_, start_, s);
|
||||||
|
113
format.h
113
format.h
@ -39,6 +39,7 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#if _SECURE_SCL
|
#if _SECURE_SCL
|
||||||
# include <iterator>
|
# include <iterator>
|
||||||
@ -163,10 +164,12 @@ inline uint32_t clzll(uint64_t x) {
|
|||||||
// This should be used in the private: declarations for a class
|
// This should be used in the private: declarations for a class
|
||||||
#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \
|
#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \
|
||||||
(FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1800
|
(FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1800
|
||||||
|
# define FMT_DELETED_OR_UNDEFINED = delete
|
||||||
# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||||
TypeName(const TypeName&) = delete; \
|
TypeName(const TypeName&) = delete; \
|
||||||
TypeName& operator=(const TypeName&) = delete
|
TypeName& operator=(const TypeName&) = delete
|
||||||
#else
|
#else
|
||||||
|
# define FMT_DELETED_OR_UNDEFINED
|
||||||
# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||||
TypeName(const TypeName&); \
|
TypeName(const TypeName&); \
|
||||||
TypeName& operator=(const TypeName&)
|
TypeName& operator=(const TypeName&)
|
||||||
@ -274,6 +277,9 @@ class BasicStringRef {
|
|||||||
friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) {
|
friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) {
|
||||||
return lhs.data_ != rhs.data_;
|
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<char> StringRef;
|
typedef BasicStringRef<char> StringRef;
|
||||||
@ -752,7 +758,7 @@ struct Value {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
NONE,
|
NONE, NAMED_ARG,
|
||||||
// Integer types should go first,
|
// Integer types should go first,
|
||||||
INT, UINT, LONG_LONG, ULONG_LONG, CHAR, LAST_INTEGER_TYPE = CHAR,
|
INT, UINT, LONG_LONG, ULONG_LONG, CHAR, LAST_INTEGER_TYPE = CHAR,
|
||||||
// followed by floating-point types.
|
// followed by floating-point types.
|
||||||
@ -767,6 +773,9 @@ struct Arg : Value {
|
|||||||
Type type;
|
Type type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct NamedArg;
|
||||||
|
|
||||||
template <typename T = void>
|
template <typename T = void>
|
||||||
struct None {};
|
struct None {};
|
||||||
|
|
||||||
@ -962,6 +971,24 @@ class MakeValue : public Arg {
|
|||||||
static uint64_t type(const T &) {
|
static uint64_t type(const T &) {
|
||||||
return IsConvertibleToInt<T>::value ? Arg::INT : Arg::CUSTOM;
|
return IsConvertibleToInt<T>::value ? Arg::INT : Arg::CUSTOM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional template param `Char_` is needed here because make_type always uses MakeValue<char>.
|
||||||
|
template <typename Char_>
|
||||||
|
MakeValue(const NamedArg<Char_> &value) { pointer = &value; }
|
||||||
|
|
||||||
|
template <typename Char_>
|
||||||
|
static uint64_t type(const NamedArg<Char_> &) { return Arg::NAMED_ARG; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct NamedArg : Arg {
|
||||||
|
BasicStringRef<Char> name;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
NamedArg(BasicStringRef<Char> name, const T &value)
|
||||||
|
: name(name), Arg(MakeValue<Char>(value)) {
|
||||||
|
type = static_cast<internal::Arg::Type>(MakeValue<Char>::type(value));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call
|
#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call
|
||||||
@ -1112,6 +1139,9 @@ class ArgList {
|
|||||||
// Maximum number of arguments with packed types.
|
// Maximum number of arguments with packed types.
|
||||||
enum { MAX_PACKED_ARGS = 16 };
|
enum { MAX_PACKED_ARGS = 16 };
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct Map;
|
||||||
|
|
||||||
ArgList() : types_(0) {}
|
ArgList() : types_(0) {}
|
||||||
|
|
||||||
ArgList(ULongLong types, const internal::Value *values)
|
ArgList(ULongLong types, const internal::Value *values)
|
||||||
@ -1146,12 +1176,31 @@ class ArgList {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct fmt::ArgList::Map {
|
||||||
|
typedef std::map<fmt::BasicStringRef<Char>, internal::Arg> MapType;
|
||||||
|
typedef typename MapType::value_type Pair;
|
||||||
|
|
||||||
|
void init(const ArgList &args);
|
||||||
|
|
||||||
|
const internal::Arg* find(const fmt::BasicStringRef<Char> &name) const {
|
||||||
|
typename MapType::const_iterator it = map_.find(name);
|
||||||
|
if (it != map_.end())
|
||||||
|
return &it->second;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
MapType map_;
|
||||||
|
};
|
||||||
|
|
||||||
struct FormatSpec;
|
struct FormatSpec;
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
class FormatterBase {
|
class FormatterBase {
|
||||||
private:
|
protected:
|
||||||
ArgList args_;
|
ArgList args_;
|
||||||
int next_arg_index_;
|
int next_arg_index_;
|
||||||
|
|
||||||
@ -1171,6 +1220,8 @@ class FormatterBase {
|
|||||||
// specified index.
|
// specified index.
|
||||||
Arg get_arg(unsigned arg_index, const char *&error);
|
Arg get_arg(unsigned arg_index, const char *&error);
|
||||||
|
|
||||||
|
bool check_no_auto_index(const char *&error);
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void write(BasicWriter<Char> &w, const Char *start, const Char *end) {
|
void write(BasicWriter<Char> &w, const Char *start, const Char *end) {
|
||||||
if (start != end)
|
if (start != end)
|
||||||
@ -1204,12 +1255,22 @@ class BasicFormatter : private internal::FormatterBase {
|
|||||||
private:
|
private:
|
||||||
BasicWriter<Char> &writer_;
|
BasicWriter<Char> &writer_;
|
||||||
const Char *start_;
|
const Char *start_;
|
||||||
|
ArgList::Map<Char> map_;
|
||||||
|
|
||||||
FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter);
|
FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter);
|
||||||
|
|
||||||
|
using FormatterBase::get_arg;
|
||||||
|
|
||||||
|
// Checks if manual indexing is used and returns the argument with
|
||||||
|
// specified name.
|
||||||
|
internal::Arg get_arg(const BasicStringRef<Char>& arg_name, const char *&error);
|
||||||
|
|
||||||
// Parses argument index and returns corresponding argument.
|
// Parses argument index and returns corresponding argument.
|
||||||
internal::Arg parse_arg_index(const Char *&s);
|
internal::Arg parse_arg_index(const Char *&s);
|
||||||
|
|
||||||
|
// Parses argument name and returns corresponding argument.
|
||||||
|
internal::Arg parse_arg_name(const Char *&s);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BasicFormatter(BasicWriter<Char> &w) : writer_(w) {}
|
explicit BasicFormatter(BasicWriter<Char> &w) : writer_(w) {}
|
||||||
|
|
||||||
@ -2675,6 +2736,32 @@ inline void format_decimal(char *&buffer, T value) {
|
|||||||
internal::format_decimal(buffer, abs_value, num_digits);
|
internal::format_decimal(buffer, abs_value, num_digits);
|
||||||
buffer += num_digits;
|
buffer += num_digits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Returns a named argument for formatting functions.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
print("Elapsed time: {s:.2f} seconds", arg("s", 1.23));
|
||||||
|
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
inline internal::NamedArg<char> arg(StringRef name, const T &arg) {
|
||||||
|
return internal::NamedArg<char>(name, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline internal::NamedArg<wchar_t> arg(WStringRef name, const T &arg) {
|
||||||
|
return internal::NamedArg<wchar_t>(name, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
void arg(StringRef name, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED;
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
void arg(WStringRef name, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FMT_GCC_VERSION
|
#if FMT_GCC_VERSION
|
||||||
@ -2779,6 +2866,28 @@ inline void format_decimal(char *&buffer, T value) {
|
|||||||
#define FMT_VARIADIC_W(ReturnType, func, ...) \
|
#define FMT_VARIADIC_W(ReturnType, func, ...) \
|
||||||
FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__)
|
FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id)
|
||||||
|
|
||||||
|
#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id)
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Convenient macro to capture the arguments' names and values into several
|
||||||
|
`fmt::arg(name, value)`.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
int x = 1, y = 2;
|
||||||
|
print("point: ({x}, {y})", FMT_CAPTURE(x, y));
|
||||||
|
// same as:
|
||||||
|
// print("point: ({x}, {y})", arg("x", x), arg("y", y));
|
||||||
|
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__)
|
||||||
|
|
||||||
namespace fmt {
|
namespace fmt {
|
||||||
FMT_VARIADIC(std::string, format, StringRef)
|
FMT_VARIADIC(std::string, format, StringRef)
|
||||||
FMT_VARIADIC_W(std::wstring, format, WStringRef)
|
FMT_VARIADIC_W(std::wstring, format, WStringRef)
|
||||||
|
@ -562,7 +562,7 @@ TEST(FormatterTest, ArgsInDifferentPositions) {
|
|||||||
|
|
||||||
TEST(FormatterTest, ArgErrors) {
|
TEST(FormatterTest, ArgErrors) {
|
||||||
EXPECT_THROW_MSG(format("{"), FormatError, "invalid format string");
|
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, "invalid format string");
|
||||||
EXPECT_THROW_MSG(format("{0}"), FormatError, "argument index out of range");
|
EXPECT_THROW_MSG(format("{0}"), FormatError, "argument index out of range");
|
||||||
|
|
||||||
@ -609,6 +609,23 @@ TEST(FormatterTest, ManyArgs) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
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("{a}"), FormatError, "argument not found");
|
||||||
|
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)));
|
||||||
|
int n = 100;
|
||||||
|
EXPECT_EQ(L"n=100", format(L"n={n}", FMT_CAPTURE_W(n)));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(FormatterTest, AutoArgIndex) {
|
TEST(FormatterTest, AutoArgIndex) {
|
||||||
EXPECT_EQ("abc", format("{}{}{}", 'a', 'b', 'c'));
|
EXPECT_EQ("abc", format("{}{}{}", 'a', 'b', 'c'));
|
||||||
EXPECT_THROW_MSG(format("{0}{}", 'a', 'b'),
|
EXPECT_THROW_MSG(format("{0}{}", 'a', 'b'),
|
||||||
@ -920,7 +937,7 @@ TEST(FormatterTest, RuntimeWidth) {
|
|||||||
FormatError, "invalid format string");
|
FormatError, "invalid format string");
|
||||||
EXPECT_THROW_MSG(format("{0:{}", 0),
|
EXPECT_THROW_MSG(format("{0:{}", 0),
|
||||||
FormatError, "cannot switch from manual to automatic argument indexing");
|
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");
|
FormatError, "invalid format string");
|
||||||
EXPECT_THROW_MSG(format("{0:{1}}", 0),
|
EXPECT_THROW_MSG(format("{0:{1}}", 0),
|
||||||
FormatError, "argument index out of range");
|
FormatError, "argument index out of range");
|
||||||
@ -1037,7 +1054,7 @@ TEST(FormatterTest, RuntimePrecision) {
|
|||||||
FormatError, "invalid format string");
|
FormatError, "invalid format string");
|
||||||
EXPECT_THROW_MSG(format("{0:.{}", 0),
|
EXPECT_THROW_MSG(format("{0:.{}", 0),
|
||||||
FormatError, "cannot switch from manual to automatic argument indexing");
|
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");
|
FormatError, "invalid format string");
|
||||||
EXPECT_THROW_MSG(format("{0:.{1}", 0, 0),
|
EXPECT_THROW_MSG(format("{0:.{1}", 0, 0),
|
||||||
FormatError, "precision not allowed in integer format specifier");
|
FormatError, "precision not allowed in integer format specifier");
|
||||||
|
Loading…
Reference in New Issue
Block a user