Support named arguments

This commit is contained in:
jamboree 2015-06-10 09:32:59 +08:00
parent ed2dfe5124
commit 7487bde587
5 changed files with 239 additions and 16 deletions

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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)

View File

@ -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");