From 246bdafc74aaff7b207d1baa532aa746294b4b88 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 11 Nov 2017 07:39:12 -0800 Subject: [PATCH] Add FMT_STRING macro for compile-time strings --- include/fmt/format.h | 1288 +++++++++++++++++++++--------------------- test/format-test.cc | 1 + 2 files changed, 652 insertions(+), 637 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 4bf79b4c..6dffee02 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2027,6 +2027,639 @@ class context_base : public parse_context{ public: parse_context &get_parse_context() { return *this; } }; + +struct format_string {}; + +template +constexpr bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +// Parses the input as an unsigned integer. This function assumes that the +// first character is a digit and presence of a non-digit character at the end. +// it: an iterator pointing to the beginning of the input range. +template +constexpr unsigned parse_nonnegative_int(Iterator &it, ErrorHandler& handler) { + assert('0' <= *it && *it <= '9'); + unsigned value = 0; + do { + unsigned new_value = value * 10 + (*it - '0'); + // Workaround for MSVC "setup_exception stack overflow" error: + auto next = it; + ++next; + it = next; + // Check if value wrapped around. + if (new_value < value) { + value = (std::numeric_limits::max)(); + break; + } + value = new_value; + } while ('0' <= *it && *it <= '9'); + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits::max)(); + if (value > max_int) + handler.on_error("number is too big"); + return value; +} + +template +class custom_formatter { + private: + basic_buffer &buffer_; + Context &ctx_; + + public: + custom_formatter(basic_buffer &buffer, Context &ctx) + : buffer_(buffer), ctx_(ctx) {} + + bool operator()(internal::custom_value custom) { + custom.format(buffer_, custom.value, &ctx_); + return true; + } + + template + bool operator()(T) { return false; } +}; + +template +struct is_integer { + enum { + value = std::is_integral::value && !std::is_same::value && + !std::is_same::value && !std::is_same::value + }; +}; + +template +class width_checker { + public: + explicit constexpr width_checker(ErrorHandler &eh) : handler_(eh) {} + + template + constexpr typename std::enable_if< + is_integer::value, unsigned long long>::type operator()(T value) { + if (is_negative(value)) + handler_.on_error("negative width"); + return value; + } + + template + constexpr typename std::enable_if< + !is_integer::value, unsigned long long>::type operator()(T) { + handler_.on_error("width is not integer"); + return 0; + } + + private: + ErrorHandler &handler_; +}; + +template +class precision_checker { + public: + explicit constexpr precision_checker(ErrorHandler &eh) : handler_(eh) {} + + template + constexpr typename std::enable_if< + is_integer::value, unsigned long long>::type operator()(T value) { + if (is_negative(value)) + handler_.on_error("negative precision"); + return value; + } + + template + constexpr typename std::enable_if< + !is_integer::value, unsigned long long>::type operator()(T) { + handler_.on_error("precision is not integer"); + return 0; + } + + private: + ErrorHandler &handler_; +}; + +// A format specifier handler that sets fields in basic_format_specs. +template +class specs_setter : public error_handler { + public: + explicit constexpr specs_setter(basic_format_specs &specs): + specs_(specs) {} + + constexpr void on_align(alignment align) { specs_.align_ = align; } + constexpr void on_fill(Char fill) { specs_.fill_ = fill; } + constexpr void on_plus() { specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; } + constexpr void on_minus() { specs_.flags_ |= MINUS_FLAG; } + constexpr void on_space() { specs_.flags_ |= SIGN_FLAG; } + constexpr void on_hash() { specs_.flags_ |= HASH_FLAG; } + + constexpr void on_zero() { + specs_.align_ = ALIGN_NUMERIC; + specs_.fill_ = '0'; + } + + constexpr void on_width(unsigned width) { specs_.width_ = width; } + constexpr void on_precision(unsigned precision) { + specs_.precision_ = precision; + } + constexpr void end_precision() {} + + constexpr void on_type(Char type) { specs_.type_ = type; } + + protected: + basic_format_specs &specs_; +}; + +// A format specifier handler that checks if specifiers are consistent with the +// argument type. +template +class specs_checker : public Handler { + public: + constexpr specs_checker(const Handler& handler, internal::type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + constexpr void on_align(alignment align) { + if (align == ALIGN_NUMERIC) + require_numeric_argument('='); + Handler::on_align(align); + } + + constexpr void on_plus() { + check_sign('+'); + Handler::on_plus(); + } + + constexpr void on_minus() { + check_sign('-'); + Handler::on_minus(); + } + + constexpr void on_space() { + check_sign(' '); + Handler::on_space(); + } + + constexpr void on_hash() { + require_numeric_argument('#'); + Handler::on_hash(); + } + + constexpr void on_zero() { + require_numeric_argument('0'); + Handler::on_zero(); + } + + constexpr void end_precision() { + if (is_integral(arg_type_) || arg_type_ == POINTER) { + report_error("precision not allowed in {} format specifier", + arg_type_ == POINTER ? "pointer" : "integer"); + } + } + + private: + template + void report_error(string_view format_str, const Args &... args) { + this->on_error(format(format_str, args...).c_str()); + } + + template + constexpr void require_numeric_argument(Char spec) { + if (!is_numeric(arg_type_)) { + report_error("format specifier '{}' requires numeric argument", + static_cast(spec)); + } + } + + template + constexpr void check_sign(Char sign) { + require_numeric_argument(sign); + if (is_integral(arg_type_) && arg_type_ != INT && arg_type_ != LONG_LONG && + arg_type_ != CHAR) { + report_error("format specifier '{}' requires signed argument", + static_cast(sign)); + } + } + + internal::type arg_type_; +}; + +template