Commit 45911770 authored by Victor Zverovich's avatar Victor Zverovich

Separate parsing and formatting in extension API

parent 7bd776e7
...@@ -495,6 +495,10 @@ class format_error : public std::runtime_error { ...@@ -495,6 +495,10 @@ class format_error : public std::runtime_error {
~format_error() throw(); ~format_error() throw();
}; };
// A formatter for objects of type T.
template <typename Char, typename T>
class formatter;
namespace internal { namespace internal {
// make_unsigned<T>::type gives an unsigned type corresponding to integer // make_unsigned<T>::type gives an unsigned type corresponding to integer
...@@ -1099,6 +1103,16 @@ enum Type { ...@@ -1099,6 +1103,16 @@ enum Type {
CSTRING, STRING, TSTRING, POINTER, CUSTOM CSTRING, STRING, TSTRING, POINTER, CUSTOM
}; };
inline bool is_integral(Type type) {
FMT_ASSERT(type != internal::NAMED_ARG, "invalid argument type");
return type > internal::NONE && type <= internal::LAST_INTEGER_TYPE;
}
inline bool is_numeric(Type type) {
FMT_ASSERT(type != internal::NAMED_ARG, "invalid argument type");
return type > internal::NONE && type <= internal::LAST_NUMERIC_TYPE;
}
template <typename Char> template <typename Char>
struct string_value { struct string_value {
const Char *value; const Char *value;
...@@ -1375,19 +1389,11 @@ class basic_arg { ...@@ -1375,19 +1389,11 @@ class basic_arg {
explicit operator bool() const noexcept { return type_ != internal::NONE; } explicit operator bool() const noexcept { return type_ != internal::NONE; }
bool is_integral() const { internal::Type type() const { return type_; }
FMT_ASSERT(type_ != internal::NAMED_ARG, "invalid argument type");
return type_ > internal::NONE && type_ <= internal::LAST_INTEGER_TYPE;
}
bool is_numeric() const {
FMT_ASSERT(type_ != internal::NAMED_ARG, "invalid argument type");
return type_ > internal::NONE && type_ <= internal::LAST_NUMERIC_TYPE;
}
bool is_pointer() const { bool is_integral() const { return internal::is_integral(type_); }
return type_ == internal::POINTER; bool is_numeric() const { return internal::is_numeric(type_); }
} bool is_pointer() const { return type_ == internal::POINTER; }
}; };
/** /**
...@@ -3167,35 +3173,18 @@ unsigned parse_nonnegative_int(Iterator &it) { ...@@ -3167,35 +3173,18 @@ unsigned parse_nonnegative_int(Iterator &it) {
return value; return value;
} }
template <typename Char> inline void require_numeric_argument(Type type, char spec) {
inline void require_numeric_argument( if (!is_numeric(type)) {
const basic_arg<Char> &arg, char spec) {
if (!arg.is_numeric()) {
FMT_THROW(fmt::format_error( FMT_THROW(fmt::format_error(
fmt::format("format specifier '{}' requires numeric argument", spec))); fmt::format("format specifier '{}' requires numeric argument", spec)));
} }
} }
// An argument visitor that checks if argument is unsigned. template <typename Iterator>
struct is_unsigned { void check_sign(Iterator &it, Type type) {
template <typename T>
typename std::enable_if<std::is_unsigned<T>::value, bool>::type
operator()(T value) {
return true;
}
template <typename T>
typename std::enable_if<!std::is_unsigned<T>::value, bool>::type
operator()(T value) {
return false;
}
};
template <typename Iterator, typename Context>
void check_sign(Iterator &it, const basic_arg<Context> &arg) {
char sign = static_cast<char>(*it); char sign = static_cast<char>(*it);
require_numeric_argument(arg, sign); require_numeric_argument(type, sign);
if (visit(is_unsigned(), arg)) { if (is_integral(type) && type != INT && type != LONG_LONG && type != CHAR) {
FMT_THROW(format_error(fmt::format( FMT_THROW(format_error(fmt::format(
"format specifier '{}' requires signed argument", sign))); "format specifier '{}' requires signed argument", sign)));
} }
...@@ -3263,11 +3252,10 @@ struct precision_handler { ...@@ -3263,11 +3252,10 @@ struct precision_handler {
} }
}; };
// Parses standard format specifiers. // Parses standard format specifiers.
template <typename Context> template <typename Context>
basic_format_specs<typename Context::char_type> basic_format_specs<typename Context::char_type>
parse_format_specs(const basic_arg<Context>& arg, Context &ctx) { parse_format_specs(Type arg_type, Context &ctx) {
typedef typename Context::char_type Char; typedef typename Context::char_type Char;
basic_format_specs<Char> spec; basic_format_specs<Char> spec;
// Parse fill and alignment. // Parse fill and alignment.
...@@ -3299,7 +3287,7 @@ basic_format_specs<typename Context::char_type> ...@@ -3299,7 +3287,7 @@ basic_format_specs<typename Context::char_type>
spec.fill_ = c; spec.fill_ = c;
} else ++it; } else ++it;
if (spec.align_ == ALIGN_NUMERIC) if (spec.align_ == ALIGN_NUMERIC)
internal::require_numeric_argument(arg, '='); internal::require_numeric_argument(arg_type, '=');
break; break;
} }
} while (--p >= it); } while (--p >= it);
...@@ -3308,28 +3296,28 @@ basic_format_specs<typename Context::char_type> ...@@ -3308,28 +3296,28 @@ basic_format_specs<typename Context::char_type>
// Parse sign. // Parse sign.
switch (*it) { switch (*it) {
case '+': case '+':
internal::check_sign(it, arg); internal::check_sign(it, arg_type);
spec.flags_ |= SIGN_FLAG | PLUS_FLAG; spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
break; break;
case '-': case '-':
internal::check_sign(it, arg); internal::check_sign(it, arg_type);
spec.flags_ |= MINUS_FLAG; spec.flags_ |= MINUS_FLAG;
break; break;
case ' ': case ' ':
internal::check_sign(it, arg); internal::check_sign(it, arg_type);
spec.flags_ |= SIGN_FLAG; spec.flags_ |= SIGN_FLAG;
break; break;
} }
if (*it == '#') { if (*it == '#') {
internal::require_numeric_argument(arg, '#'); internal::require_numeric_argument(arg_type, '#');
spec.flags_ |= HASH_FLAG; spec.flags_ |= HASH_FLAG;
++it; ++it;
} }
// Parse zero flag. // Parse zero flag.
if (*it == '0') { if (*it == '0') {
internal::require_numeric_argument(arg, '0'); internal::require_numeric_argument(arg_type, '0');
spec.align_ = ALIGN_NUMERIC; spec.align_ = ALIGN_NUMERIC;
spec.fill_ = '0'; spec.fill_ = '0';
++it; ++it;
...@@ -3368,10 +3356,10 @@ basic_format_specs<typename Context::char_type> ...@@ -3368,10 +3356,10 @@ basic_format_specs<typename Context::char_type>
} else { } else {
FMT_THROW(format_error("missing precision specifier")); FMT_THROW(format_error("missing precision specifier"));
} }
if (arg.is_integral() || arg.is_pointer()) { if (is_integral(arg_type) || arg_type == POINTER) {
FMT_THROW(format_error( FMT_THROW(format_error(
fmt::format("precision not allowed in {} format specifier", fmt::format("precision not allowed in {} format specifier",
arg.is_pointer() ? "pointer" : "integer"))); arg_type == POINTER ? "pointer" : "integer")));
} }
} }
...@@ -3383,7 +3371,7 @@ basic_format_specs<typename Context::char_type> ...@@ -3383,7 +3371,7 @@ basic_format_specs<typename Context::char_type>
// Formats a single argument. // Formats a single argument.
template <typename ArgFormatter, typename Char, typename Context> template <typename ArgFormatter, typename Char, typename Context>
void do_format_arg(basic_buffer<Char> &buffer, const basic_arg<Context>& arg, void do_format_arg(basic_buffer<Char> &buffer, const basic_arg<Context> &arg,
Context &ctx) { Context &ctx) {
auto &it = ctx.pos(); auto &it = ctx.pos();
basic_format_specs<Char> spec; basic_format_specs<Char> spec;
...@@ -3391,7 +3379,7 @@ void do_format_arg(basic_buffer<Char> &buffer, const basic_arg<Context>& arg, ...@@ -3391,7 +3379,7 @@ void do_format_arg(basic_buffer<Char> &buffer, const basic_arg<Context>& arg,
if (visit(internal::custom_formatter<Char, Context>(buffer, ctx), arg)) if (visit(internal::custom_formatter<Char, Context>(buffer, ctx), arg))
return; return;
++it; ++it;
spec = internal::parse_format_specs(arg, ctx); spec = internal::parse_format_specs(arg.type(), ctx);
} }
if (*it != '}') if (*it != '}')
...@@ -3402,6 +3390,26 @@ void do_format_arg(basic_buffer<Char> &buffer, const basic_arg<Context>& arg, ...@@ -3402,6 +3390,26 @@ void do_format_arg(basic_buffer<Char> &buffer, const basic_arg<Context>& arg,
} }
} // namespace internal } // namespace internal
template <typename T, typename Char = char>
class formatter {
public:
explicit formatter(basic_context<Char> &ctx) {
auto &it = ctx.pos();
if (*it == ':') {
++it;
specs_ = parse_format_specs(internal::gettype<T>(), ctx);
}
}
void format(basic_buffer<Char> &buf, const T &val, basic_context<Char> &ctx) {
visit(arg_formatter<Char>(buf, ctx, specs_),
internal::make_arg<basic_context<Char>>(val));
}
private:
basic_format_specs<Char> specs_;
};
template <typename Char> template <typename Char>
inline typename basic_context<Char>::format_arg inline typename basic_context<Char>::format_arg
basic_context<Char>::get_arg( basic_context<Char>::get_arg(
......
...@@ -1240,12 +1240,14 @@ TEST(FormatterTest, FormatCustom) { ...@@ -1240,12 +1240,14 @@ TEST(FormatterTest, FormatCustom) {
class Answer {}; class Answer {};
template <typename Char> template <typename Char>
void format_value(fmt::basic_buffer<Char> &buf, Answer, fmt::context &) { void format_value(fmt::basic_buffer<Char> &buf, Answer, fmt::context &ctx) {
fmt::format_to(buf, "{}", 42); fmt::formatter<int> f(ctx);
f.format(buf, 42, ctx);
} }
TEST(FormatterTest, CustomFormat) { TEST(FormatterTest, CustomFormat) {
EXPECT_EQ("42", format("{0}", Answer())); EXPECT_EQ("42", format("{0}", Answer()));
EXPECT_EQ("0042", format("{:04}", Answer()));
} }
TEST(FormatterTest, WideFormatString) { TEST(FormatterTest, WideFormatString) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment