Merge pull request #173 from jamboree/feature/named-arg

Support named arguments
This commit is contained in:
Victor Zverovich 2015-06-10 06:38:27 -07:00
commit 83659f425b
5 changed files with 239 additions and 16 deletions

View File

@ -72,6 +72,10 @@ Write API
Utilities
=========
.. doxygenfunction:: fmt::arg(StringRef, const T&)
.. doxygendefine:: FMT_CAPTURE
.. doxygendefine:: FMT_VARIADIC
.. 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:
.. 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
View File

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

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

View File

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