From 7487bde5871f0b44a40edcceda8276eed0952246 Mon Sep 17 00:00:00 2001 From: jamboree Date: Wed, 10 Jun 2015 09:32:59 +0800 Subject: [PATCH] Support named arguments --- doc/api.rst | 4 ++ doc/syntax.rst | 12 +++-- format.cc | 103 +++++++++++++++++++++++++++++++++++++--- format.h | 113 +++++++++++++++++++++++++++++++++++++++++++- test/format-test.cc | 23 +++++++-- 5 files changed, 239 insertions(+), 16 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index 5464f08b..725f2ec1 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -72,6 +72,10 @@ Write API Utilities ========= +.. doxygenfunction:: fmt::arg(StringRef, const T&) + +.. doxygendefine:: FMT_CAPTURE + .. doxygendefine:: FMT_VARIADIC .. doxygenclass:: fmt::ArgList 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 afb21487..9d05991d 100644 --- a/format.cc +++ b/format.cc @@ -265,6 +265,11 @@ 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 = @@ -580,6 +585,49 @@ FMT_FUNC void fmt::internal::format_windows_error( } #endif +template +void fmt::ArgList::Map::init(const ArgList &args) { + if (!map_.empty()) + return; + const internal::NamedArg* 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*>(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*>(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*>(args.args_[i].pointer); + map_.insert(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } +} + // An argument formatter. template class fmt::internal::ArgFormatter : @@ -681,6 +729,20 @@ void fmt::BasicWriter::write_str( write_str(str_value, str_size, spec); } +template +inline Arg fmt::BasicFormatter::get_arg( + const BasicStringRef& 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 inline Arg fmt::BasicFormatter::parse_arg_index(const Char *&s) { const char *error = 0; @@ -693,11 +755,33 @@ inline Arg fmt::BasicFormatter::parse_arg_index(const Char *&s) { return arg; } +template +inline Arg fmt::BasicFormatter::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(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(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::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::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::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); diff --git a/format.h b/format.h index bee4cf78..985af298 100644 --- a/format.h +++ b/format.h @@ -39,6 +39,7 @@ #include #include #include +#include #if _SECURE_SCL # include @@ -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 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 +struct NamedArg; + template struct None {}; @@ -962,6 +971,24 @@ class MakeValue : public Arg { static uint64_t type(const T &) { return IsConvertibleToInt::value ? Arg::INT : Arg::CUSTOM; } + + // Additional template param `Char_` is needed here because make_type always uses MakeValue. + template + MakeValue(const NamedArg &value) { pointer = &value; } + + template + static uint64_t type(const NamedArg &) { return Arg::NAMED_ARG; } +}; + +template +struct NamedArg : Arg { + BasicStringRef name; + + template + NamedArg(BasicStringRef name, const T &value) + : name(name), Arg(MakeValue(value)) { + type = static_cast(MakeValue::type(value)); + } }; #define FMT_DISPATCH(call) static_cast(this)->call @@ -1112,6 +1139,9 @@ class ArgList { // Maximum number of arguments with packed types. enum { MAX_PACKED_ARGS = 16 }; + template + struct Map; + ArgList() : types_(0) {} ArgList(ULongLong types, const internal::Value *values) @@ -1146,12 +1176,31 @@ class ArgList { } }; +template +struct fmt::ArgList::Map { + typedef std::map, internal::Arg> MapType; + typedef typename MapType::value_type Pair; + + void init(const ArgList &args); + + const internal::Arg* find(const fmt::BasicStringRef &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 void write(BasicWriter &w, const Char *start, const Char *end) { if (start != end) @@ -1204,12 +1255,22 @@ class BasicFormatter : private internal::FormatterBase { private: BasicWriter &writer_; const Char *start_; + ArgList::Map 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& 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 &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 +inline internal::NamedArg arg(StringRef name, const T &arg) { + return internal::NamedArg(name, arg); +} + +template +inline internal::NamedArg arg(WStringRef name, const T &arg) { + return internal::NamedArg(name, arg); +} + +template +void arg(StringRef name, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; + +template +void arg(WStringRef name, const internal::NamedArg&) 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) diff --git a/test/format-test.cc b/test/format-test.cc index 11d5425b..4a9e1574 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -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");