mirror of
https://github.com/fmtlib/fmt.git
synced 2025-07-30 10:47:35 +02:00
Separate parsing and formatting in extension API
This commit is contained in:
102
fmt/format.h
102
fmt/format.h
@ -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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
bool is_integral() const { return internal::is_integral(type_); }
|
||||||
FMT_ASSERT(type_ != internal::NAMED_ARG, "invalid argument type");
|
bool is_numeric() const { return internal::is_numeric(type_); }
|
||||||
return type_ > internal::NONE && type_ <= internal::LAST_NUMERIC_TYPE;
|
bool is_pointer() const { return type_ == internal::POINTER; }
|
||||||
}
|
|
||||||
|
|
||||||
bool is_pointer() const {
|
|
||||||
return type_ == internal::POINTER;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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 {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 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>
|
|||||||
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>
|
|||||||
// 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>
|
|||||||
} 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>
|
|||||||
|
|
||||||
// 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,
|
|||||||
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,
|
|||||||
}
|
}
|
||||||
} // 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) {
|
|||||||
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) {
|
||||||
|
Reference in New Issue
Block a user