Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support named arguments #169

Closed
wants to merge 12 commits into from
4 changes: 4 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ Write API
Utilities
=========

.. doxygenfunction:: fmt::arg(StringRef, const T&)

.. doxygendefine:: FMT_CAPTURE

.. doxygendefine:: FMT_VARIADIC

.. doxygenclass:: fmt::ArgList
Expand Down
12 changes: 7 additions & 5 deletions doc/syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"

Expand Down
103 changes: 97 additions & 6 deletions format.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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 :
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -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();
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Loading