mirror of
https://github.com/fmtlib/fmt.git
synced 2024-11-11 05:30:05 +00:00
Merge pull request #173 from jamboree/feature/named-arg
Support named arguments
This commit is contained in:
commit
83659f425b
@ -72,6 +72,10 @@ Write API
|
||||
Utilities
|
||||
=========
|
||||
|
||||
.. doxygenfunction:: fmt::arg(StringRef, const T&)
|
||||
|
||||
.. doxygendefine:: FMT_CAPTURE
|
||||
|
||||
.. doxygendefine:: FMT_VARIADIC
|
||||
|
||||
.. 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:
|
||||
|
||||
.. 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: <a character other than '{' or '}'>
|
||||
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"
|
||||
|
||||
|
103
format.cc
103
format.cc
@ -265,6 +265,11 @@ int parse_nonnegative_int(const Char *&s) {
|
||||
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) {
|
||||
if (arg.type > Arg::LAST_NUMERIC_TYPE) {
|
||||
std::string message =
|
||||
@ -580,6 +585,49 @@ FMT_FUNC void fmt::internal::format_windows_error(
|
||||
}
|
||||
#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.
|
||||
template <typename Char>
|
||||
class fmt::internal::ArgFormatter :
|
||||
@ -681,6 +729,20 @@ void fmt::BasicWriter<Char>::write_str(
|
||||
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>
|
||||
inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) {
|
||||
const char *error = 0;
|
||||
@ -693,11 +755,33 @@ inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) {
|
||||
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(
|
||||
unsigned arg_index, const char *&error) {
|
||||
Arg arg = args_[arg_index];
|
||||
if (arg.type == Arg::NONE)
|
||||
switch (arg.type) {
|
||||
case Arg::NONE:
|
||||
error = "argument index out of range";
|
||||
break;
|
||||
case Arg::NAMED_ARG:
|
||||
arg = *static_cast<const internal::Arg*>(arg.pointer);
|
||||
default:
|
||||
/*nothing*/;
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
@ -708,13 +792,20 @@ inline Arg fmt::internal::FormatterBase::next_arg(const char *&error) {
|
||||
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(
|
||||
unsigned arg_index, const char *&error) {
|
||||
if (next_arg_index_ <= 0) {
|
||||
if (check_no_auto_index(error)) {
|
||||
next_arg_index_ = -1;
|
||||
return do_get_arg(arg_index, error);
|
||||
}
|
||||
error = "cannot switch from automatic to manual argument indexing";
|
||||
return Arg();
|
||||
}
|
||||
|
||||
@ -1038,7 +1129,7 @@ const Char *fmt::BasicFormatter<Char>::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) : parse_arg_index(s);
|
||||
if (*s++ != '}')
|
||||
FMT_THROW(FormatError("invalid format string"));
|
||||
ULongLong value = 0;
|
||||
@ -1075,7 +1166,7 @@ const Char *fmt::BasicFormatter<Char>::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) : parse_arg_index(s);
|
||||
if (*s++ != '}')
|
||||
FMT_THROW(FormatError("invalid format string"));
|
||||
ULongLong value = 0;
|
||||
@ -1142,7 +1233,7 @@ void fmt::BasicFormatter<Char>::format(
|
||||
if (c == '}')
|
||||
FMT_THROW(FormatError("unmatched '}' in format string"));
|
||||
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);
|
||||
}
|
||||
write(writer_, start_, s);
|
||||
|
113
format.h
113
format.h
@ -39,6 +39,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
|
||||
#if _SECURE_SCL
|
||||
# include <iterator>
|
||||
@ -163,10 +164,12 @@ inline uint32_t clzll(uint64_t x) {
|
||||
// This should be used in the private: declarations for a class
|
||||
#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \
|
||||
(FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1800
|
||||
# define FMT_DELETED_OR_UNDEFINED = delete
|
||||
# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&) = delete; \
|
||||
TypeName& operator=(const TypeName&) = delete
|
||||
#else
|
||||
# define FMT_DELETED_OR_UNDEFINED
|
||||
# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
TypeName& operator=(const TypeName&)
|
||||
@ -274,6 +277,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<char> StringRef;
|
||||
@ -752,7 +758,7 @@ struct Value {
|
||||
};
|
||||
|
||||
enum Type {
|
||||
NONE,
|
||||
NONE, NAMED_ARG,
|
||||
// Integer types should go first,
|
||||
INT, UINT, LONG_LONG, ULONG_LONG, CHAR, LAST_INTEGER_TYPE = CHAR,
|
||||
// followed by floating-point types.
|
||||
@ -767,6 +773,9 @@ struct Arg : Value {
|
||||
Type type;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct NamedArg;
|
||||
|
||||
template <typename T = void>
|
||||
struct None {};
|
||||
|
||||
@ -962,6 +971,24 @@ class MakeValue : public Arg {
|
||||
static uint64_t type(const T &) {
|
||||
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
|
||||
@ -1112,6 +1139,9 @@ class ArgList {
|
||||
// Maximum number of arguments with packed types.
|
||||
enum { MAX_PACKED_ARGS = 16 };
|
||||
|
||||
template <typename Char>
|
||||
struct Map;
|
||||
|
||||
ArgList() : types_(0) {}
|
||||
|
||||
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;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class FormatterBase {
|
||||
private:
|
||||
protected:
|
||||
ArgList args_;
|
||||
int next_arg_index_;
|
||||
|
||||
@ -1171,6 +1220,8 @@ class FormatterBase {
|
||||
// specified index.
|
||||
Arg get_arg(unsigned arg_index, const char *&error);
|
||||
|
||||
bool check_no_auto_index(const char *&error);
|
||||
|
||||
template <typename Char>
|
||||
void write(BasicWriter<Char> &w, const Char *start, const Char *end) {
|
||||
if (start != end)
|
||||
@ -1204,12 +1255,22 @@ class BasicFormatter : private internal::FormatterBase {
|
||||
private:
|
||||
BasicWriter<Char> &writer_;
|
||||
const Char *start_;
|
||||
ArgList::Map<Char> map_;
|
||||
|
||||
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.
|
||||
internal::Arg parse_arg_index(const Char *&s);
|
||||
|
||||
// Parses argument name and returns corresponding argument.
|
||||
internal::Arg parse_arg_name(const Char *&s);
|
||||
|
||||
public:
|
||||
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);
|
||||
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
|
||||
@ -2779,6 +2866,28 @@ 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_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 {
|
||||
FMT_VARIADIC(std::string, format, StringRef)
|
||||
FMT_VARIADIC_W(std::wstring, format, WStringRef)
|
||||
|
@ -562,7 +562,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");
|
||||
|
||||
@ -609,6 +609,23 @@ TEST(FormatterTest, ManyArgs) {
|
||||
}
|
||||
#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) {
|
||||
EXPECT_EQ("abc", format("{}{}{}", 'a', 'b', 'c'));
|
||||
EXPECT_THROW_MSG(format("{0}{}", 'a', 'b'),
|
||||
@ -920,7 +937,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");
|
||||
@ -1037,7 +1054,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");
|
||||
|
Loading…
Reference in New Issue
Block a user