From 8cc4c1e82a9e35b651dfc171e694fecf96c391b0 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Thu, 28 Jul 2016 13:36:23 +1000 Subject: [PATCH] format: reimplement format rendering requires literal string arrays, and implements more of the specifier specification. does not implement 'n' or '$' specifiers. falls back to snprintf for real arguments. --- debug.hpp | 5 +- debug.ipp | 14 +- format.cpp | 56 ++ format.hpp | 71 ++- format.ipp | 1067 +++++++++++++++++++++++++++++++------- log.hpp | 4 +- log.ipp | 6 +- maths.cpp | 20 - maths.hpp | 49 +- tap.hpp | 44 +- tap.ipp | 84 ++- test/cmdopt.cpp | 4 +- test/colour.cpp | 4 +- test/crypto/arc4.cpp | 5 +- test/crypto/tea.cpp | 13 +- test/crypto/xtea.cpp | 13 +- test/crypto/xxtea.cpp | 13 +- test/fixed.cpp | 20 +- test/format.cpp | 191 ++++++- test/hash/hmac.cpp | 2 +- test/hash/ripemd.cpp | 2 +- test/hash/sha1.cpp | 2 +- test/hash/sha2.cpp | 2 +- test/ip.cpp | 10 +- test/polynomial.cpp | 2 +- test/roots/bisection.cpp | 2 +- test/vector.cpp | 4 +- test/version.cpp | 2 +- 28 files changed, 1301 insertions(+), 410 deletions(-) diff --git a/debug.hpp b/debug.hpp index c9b84114..82e00ffa 100644 --- a/debug.hpp +++ b/debug.hpp @@ -286,8 +286,9 @@ /////////////////////////////////////////////////////////////////////////////// constexpr void panic [[noreturn]] (const char*); -template -constexpr void panic [[noreturn]] (const char *fmt, const Args&...); +template +constexpr +void panic [[noreturn]] (const char (&fmt)[N], const Args&...); /////////////////////////////////////////////////////////////////////////////// diff --git a/debug.ipp b/debug.ipp index 80f97f57..9720bbc1 100644 --- a/debug.ipp +++ b/debug.ipp @@ -28,9 +28,13 @@ namespace util { namespace debug { namespace detail { void panic [[noreturn]] (const char *msg); - template - void panic [[noreturn]] (const char *msg, const Args& ...args) - { panic (util::format::render (msg, args...).c_str ()); } + template + constexpr + void panic [[noreturn]] (const char (&fmt)[N], const Args& ...args) + { + auto msg = util::format::render (fmt, args...); + panic (msg.c_str ()); + } void not_implemented [[noreturn]] (const char *msg); void unreachable [[noreturn]] (const char *msg); @@ -92,10 +96,10 @@ constexpr void panic [[noreturn]] (const char *msg) //----------------------------------------------------------------------------- -template +template constexpr void -panic [[noreturn]] (const char *fmt, const Args& ...args) +panic [[noreturn]] (const char (&fmt)[N], const Args& ...args) { ! fmt ? panic ("unreachable constexpr panic helper") diff --git a/format.cpp b/format.cpp index 78104472..7c9808e0 100644 --- a/format.cpp +++ b/format.cpp @@ -17,3 +17,59 @@ #include "format.hpp" #include + + +namespace util { namespace format { namespace detail { + //------------------------------------------------------------------------- + std::ostream& + operator<< (std::ostream &os, specifier::repr r) + { + switch (r) { + case specifier::repr::FIXED: return os << "FIXED"; + case specifier::repr::SCIENTIFIC: return os << "SCIENTIFIC"; + case specifier::repr::AUTO: return os << "AUTO"; + } + + unreachable (); + } + + + //------------------------------------------------------------------------- + std::ostream& + operator<< (std::ostream &os, specifier::kind t) + { + switch (t) { + case specifier::kind::UNSIGNED: return os << "UNSIGNED"; + case specifier::kind::SIGNED: return os << "SIGNED"; + case specifier::kind::REAL: return os << "REAL"; + case specifier::kind::STRING: return os << "STRING"; + case specifier::kind::POINTER: return os << "POINTER"; + case specifier::kind::CHARACTER: return os << "CHARACTER"; + case specifier::kind::ESCAPE: return os << "ESCAPE"; + case specifier::kind::OSTREAM: return os << "OSTREAM"; + } + + unreachable (); + } + + + //------------------------------------------------------------------------- + std::ostream& + operator<< (std::ostream &os, const specifier &s) + { + return os << "specifier {" + "alternate_form: " << s.alternate_form << ", " + "left_adjusted: " << s.left_adjusted << ", " + "thousands_grouping: " << s.thousands_grouping << ", " + "padding_char: '" << s.padding_char << "', " + "positive_char: '" << s.positive_char << "', " + "uppercase: " << s.uppercase << ", " + "base: " << s.base << ", " + "repr: " << s.r << ", " + "kind: " << s.k << ", " + "width: " << s.width << ", " + "precision: " << s.precision << ", " + "length: " << s.length << + " }"; + } +} } } diff --git a/format.hpp b/format.hpp index 3947a18e..7828d009 100644 --- a/format.hpp +++ b/format.hpp @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2015-2016 Danny Robson + * Copyright 2016 Danny Robson */ #ifndef __UTIL_FORMAT_HPP @@ -20,41 +20,56 @@ #include #include -namespace util { - namespace format { - template - std::string - render (const std::string &fmt, Args&&...); +namespace util { namespace format { + //------------------------------------------------------------------------- + // render a format string using the provided values. + // + // we deliberately only take char[] formats so as to promote the use of + // only literal strings as format strings. + template + std::string + render (const char (&fmt)[N], const Args&...); - class error : public std::runtime_error - { using runtime_error::runtime_error; }; + //------------------------------------------------------------------------- + class error : public std::runtime_error + { using runtime_error::runtime_error; }; - // value-specifier mismatch - class value_error : public error - { using error::error; }; + // value-specifier mismatch + class value_error : public error + { using error::error; }; - // malformed format specifier - class format_error : public error - { using error::error; }; + struct conversion_error : public error + { using error::error; }; - template - class invalid_specifier : public format_error { - public: - using value_type = ValueT; + struct length_error : public error + { using error::error; }; - invalid_specifier (char specifier); + // malformed format specifier + class syntax_error : public error + { using error::error; }; - char specifier (void) const; + template + class invalid_specifier : error { + public: + using value_type = ValueT; - private: - char m_specifier; - }; + invalid_specifier (char specifier); - // missing format specifier - class missing_error : public error - { using error::error; }; - } -} + char specifier (void) const; + + private: + char m_specifier; + }; + + // missing format specifier + class missing_error : public error + { + public: + missing_error (): + error ("missing argument for specifier") + { ; } + }; +} } #include "format.ipp" diff --git a/format.ipp b/format.ipp index 3394a228..c740e1c8 100644 --- a/format.ipp +++ b/format.ipp @@ -14,207 +14,900 @@ * Copyright 2015-2016 Danny Robson */ -#if defined(__UTIL_FORMAT_IPP) -#error -#endif -#define __UTIL_FORMAT_IPP +#include "./ascii.hpp" +#include "./debug.hpp" +#include "./maths.hpp" -#include "debug.hpp" - -#include +#include +#include #include -#include -#include -#include - -namespace util { - namespace detail { namespace format { - /////////////////////////////////////////////////////////////////////// - template - inline bool - is_type_specifier (const char*) - { return false; } - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { return *s == 'c'; } +namespace util { namespace format { namespace detail { + /////////////////////////////////////////////////////////////////////////// + // GCC: workaround which allows a throw to appear in constexpr codepaths + // that do not execute at compile time. See gcc#67371 + template + constexpr void + constexpr_throw [[noreturn]] (Args&& ...args) + { + ! true + ? constexpr_throw (std::forward (args)...) + : throw ExceptT (std::forward (args)...); + } - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { return *s == 's'; } - - - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { return *s == 's'; } - - - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { return *s == 's'; } - - - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { return *s == 's'; } - - - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { return *s == 'u' || *s == 'x'; } - - - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { return *s == 'u' || *s == 'x'; } - - - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { - return *s == 'i' || *s == 'd' || *s == 'x'; - } - - - //--------------------------------------------------------------------- - template <> - inline bool - is_type_specifier (const char *s) - { - switch (*s) { - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - case 'a': - case 'A': - return true; - - default: - return false; - } - } - - - /////////////////////////////////////////////////////////////////////// - template - inline bool - is_valid_specifier (const char *s) - { - return *s == '!' || is_type_specifier (s); - } - - - /////////////////////////////////////////////////////////////////////// - template - void - render (InputIt first, - InputIt last, - std::ostringstream &dest) - { - static const char DELIMITER = '%'; - if (std::find (first, last, DELIMITER) != last) - throw util::format::missing_error ("format specifier without value"); - - std::copy (first, last, std::ostream_iterator (dest)); - } - - - //--------------------------------------------------------------------- - template - void - render (InputIt first, - InputIt last, - std::ostringstream &dest, - const ValueT& val, - Args&& ...args) - { - CHECK (first <= last); - - using namespace std::literals; - - static const char DELIMITER = '%'; - auto cursor = std::find (first, last, DELIMITER); - std::copy (first, cursor, std::ostream_iterator (dest)); - - if (cursor == last) - return; - - auto spec = cursor + 1; - if (spec == last) - throw util::format::format_error ("missing format specifier"s); - - if (!is_valid_specifier::type> (&*spec)) - throw util::format::invalid_specifier (*spec); - - if (*spec == 'x') { - dest << std::hex << val << std::dec; - } else - dest << val; - - render (spec + 1, last, dest, std::forward (args)...); - } - } } - /////////////////////////////////////////////////////////////////////////// - namespace format { - template - std::string - render (const std::string &fmt, Args&&... args) - { - std::ostringstream out; + // record all formatting information for a specifier. + // + // does not record redundant/unneeded information, and will default values + // like width and precision, so may not guarantee round-trip to/from + // string specifiers. + struct specifier { + bool alternate_form = false; + bool left_adjusted = false; + bool thousands_grouping = false; - util::detail::format::render ( - fmt.begin (), - fmt.end (), - out, - std::forward (args)... - ); + char padding_char = ' '; + char positive_char = '\0'; - return out.str (); + bool uppercase = false; + + // the rendered base for the value. + // 8 for octal + // 16 for hex + // 10 for decimal + // + // other values are theoretically supportable, but do not form part of + // the printf specification. + unsigned base = 10; + + enum class repr { + FIXED, + SCIENTIFIC, + AUTO + } r = repr::AUTO; + + enum class kind { + UNSIGNED, + SIGNED, + REAL, + + STRING, + POINTER, + CHARACTER, + ESCAPE, + + OSTREAM + } k; + + unsigned width = 0; // field width, ie: how many characters + int precision = -1; // how many digits after the decimal + + size_t length = 0; // bytesize of underlying type + }; + + + /////////////////////////////////////////////////////////////////////////// + // provides the kind, a conversion specifier, and expected length for a + // given type. + // + // the conversion specifier is only one valid specifier. there may be + // multiple valid values, eg 'd' and 'i' for signed integers; or 'e', 'f', + // 'g', and 'a' for reals. + template + struct specifier_traits { + static constexpr specifier::kind kind = specifier::kind::OSTREAM; + static constexpr char conversion = '!'; + static constexpr unsigned length = sizeof (T); + }; + + #define MAKE_SPECIFIER_TRAIT(NATIVE,KIND,CONV) \ + template <> \ + struct specifier_traits { \ + static constexpr specifier::kind kind = specifier::kind:: KIND; \ + static constexpr char conversion = CONV; \ + static constexpr unsigned length = sizeof (NATIVE); \ + }; + + MAKE_SPECIFIER_TRAIT(uint8_t, UNSIGNED,'u'); + MAKE_SPECIFIER_TRAIT(uint16_t,UNSIGNED,'u'); + MAKE_SPECIFIER_TRAIT(uint32_t,UNSIGNED,'u'); + MAKE_SPECIFIER_TRAIT(uint64_t,UNSIGNED,'u'); + + MAKE_SPECIFIER_TRAIT(int8_t, SIGNED,'i'); + MAKE_SPECIFIER_TRAIT(int16_t,SIGNED,'i'); + MAKE_SPECIFIER_TRAIT(int32_t,SIGNED,'i'); + MAKE_SPECIFIER_TRAIT(int64_t,SIGNED,'i'); + + MAKE_SPECIFIER_TRAIT(float,REAL,'g'); + MAKE_SPECIFIER_TRAIT(double,REAL,'g'); + + MAKE_SPECIFIER_TRAIT(char*,STRING,'s'); + MAKE_SPECIFIER_TRAIT(const char*,STRING,'s'); + MAKE_SPECIFIER_TRAIT(const unsigned char*,STRING,'s'); + + MAKE_SPECIFIER_TRAIT(char,CHARACTER,'c'); + MAKE_SPECIFIER_TRAIT(void*,POINTER,'p'); + + template + struct specifier_traits { + static constexpr specifier::kind kind = specifier::kind::STRING; + static constexpr char conversion = 's'; + static constexpr unsigned length = sizeof (const char*); + }; + + #undef MAKE_SPECIFIER_TRAIT + + + /////////////////////////////////////////////////////////////////////////// + static constexpr + specifier::kind + to_kind (const char c) + { + switch (c) { + case 'd': + case 'i': + return specifier::kind::SIGNED; + + case 'u': + case 'o': + case 'x': + case 'X': + return specifier::kind::UNSIGNED; + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'a': + case 'A': + return specifier::kind::REAL; + + case 'c': + case 'C': + return specifier::kind::CHARACTER; + + case 's': + case 'S': + return specifier::kind::STRING; + + case 'p': + return specifier::kind::POINTER; + + case '%': + return specifier::kind::ESCAPE; + + case '!': + return specifier::kind::OSTREAM; + } + + constexpr_throw ("invalid conversion specifier"); + } + + + //------------------------------------------------------------------------- + inline constexpr + size_t + length (specifier::kind k) + { + switch (k) { + case specifier::kind::SIGNED: return sizeof (int); + case specifier::kind::UNSIGNED: return sizeof (unsigned); + case specifier::kind::REAL: return sizeof (double); + case specifier::kind::CHARACTER: return sizeof (char); + case specifier::kind::STRING: return sizeof (const char*); + case specifier::kind::POINTER: return sizeof (void*); + case specifier::kind::ESCAPE: return 0; + case specifier::kind::OSTREAM: return 0; + } + + unreachable (); + } + + + //------------------------------------------------------------------------- + inline constexpr + int + precision (specifier &s) + { + switch (s.k) { + case specifier::kind::SIGNED: + case specifier::kind::UNSIGNED: + return 1; + + case specifier::kind::REAL: + if (s.base != 16) + return 6; + else + return -1; + + case specifier::kind::STRING: + return std::numeric_limits::max (); + + case specifier::kind::POINTER: + case specifier::kind::CHARACTER: + case specifier::kind::ESCAPE: + case specifier::kind::OSTREAM: + return 0; + } + + unreachable (); + } + + + //------------------------------------------------------------------------- + inline constexpr + specifier::repr + repr (const char c) + { + switch (c) { + case 'e': + case 'E': + return specifier::repr::SCIENTIFIC; + + case 'f': + case 'F': + return specifier::repr::FIXED; + + default: + return specifier::repr::AUTO; + } + } + + + //------------------------------------------------------------------------- + inline constexpr + unsigned + base (const char c) + { + switch (c) { + case 'o': + return 8; + + case 'x': + case 'X': + case 'a': + case 'A': + return 16; + + default: + return 10; } } /////////////////////////////////////////////////////////////////////////// - // TODO: we'd like to use typeid here for type naming, but we don't allow - // RTTI. revisit this when introspection is more advanced. - template - format::invalid_specifier::invalid_specifier (char _specifier): - format_error ( - format::render ("invalid specifier '%c' for type '%s'", - _specifier, - "unimplemented") - ), - m_specifier (_specifier) - { ; } + // returns the count of specifiers within the range. + // + // if the string is not a valid format string the count may be slightly high; + // particularly in cases of truncation. + inline constexpr + size_t + specifier_count (const char *first, const char *last) + { + size_t count = 0; + + for (auto cursor = first; cursor != last; ++cursor) { + if (*cursor != '%') + continue; + + ++cursor; + if (cursor == last) + return 1; + + ++count; + } + + return count; + } //------------------------------------------------------------------------- - template - char - format::invalid_specifier::specifier (void) const - { return m_specifier; } -} + template + inline constexpr + size_t + specifier_count (const char (&fmt)[N]) + { + return specifier_count (fmt, fmt + N); + } + + + + /////////////////////////////////////////////////////////////////////////// + // parses a format specifier into the details struct + // + // * first:last must form a contiguous range + // * first must be the first character of a specifier, ie a % + // + // returns the character after the last character of the specifier + // + // throws on an invalid specifier (including a truncated specifier) + constexpr + const char* + parse (const char *first, const char *last, specifier &spec) + { + if (last - first < 2) + constexpr_throw ("specifiers require at least two characters"); + + if (*first != '%') + constexpr_throw ("specifiers must start with %"); + + auto cursor = first + 1; + + // read the format flags + for (bool more_flags = true; more_flags; ) { + switch (*cursor) { + case '#': spec.alternate_form = true; ++cursor; break; + case '0': spec.padding_char = '0'; ++cursor; break; + case '-': spec.left_adjusted = true; ++cursor; break; + case ' ': spec.positive_char = ' '; ++cursor; break; + case '+': spec.positive_char = '+'; ++cursor; break; + // ''', thousands grouping + + default: + more_flags = false; + break; + } + } + + // read the width + spec.width = 0; + while (1) { + if (!ascii::is_digit (*cursor)) + break; + + spec.width *= 10; + spec.width += *cursor - '0'; + ++cursor; + } + + // read the precision + if (*cursor == '.') { + ++cursor; + spec.precision = 0; + + while (1) { + if (!ascii::is_digit (*cursor)) + break; + + spec.precision *= 10; + spec.precision += *cursor - '0'; + ++cursor; + } + } + + // read the length modifiers + // TODO: ensure these values make sense given the conversion + // specifier about to come. + switch (*cursor) { + case 'h': { + spec.length = sizeof (short); + ++cursor; + + if (*cursor == 'h') { + spec.length = sizeof (char); + ++cursor; + } + + break; + } + + case 'l': { + spec.length = sizeof (long); + ++cursor; + + if (*cursor == 'l') { + spec.length = sizeof (long long); + ++cursor; + } + + break; + } + + case 'L': spec.length = sizeof (long double); ++cursor; break; + case 'j': spec.length = sizeof (uintmax_t); ++cursor; break; + case 'z': spec.length = sizeof (size_t); ++cursor; break; + case 't': spec.length = sizeof (ptrdiff_t); ++cursor; break; + default: + break; + } + + // read the conversion specifier + auto conv = *cursor++; + spec.k = to_kind (conv); + spec.length = spec.length ?: length (spec.k); + spec.uppercase = ascii::is_upper (conv); + spec.base = base (conv); + spec.r = repr (conv); + if (spec.precision < 0) + spec.precision = precision (spec); + + return cursor; + } + + + //------------------------------------------------------------------------- + template + constexpr + auto + parse (const char (&fmt)[N], specifier &spec) + { + return parse (fmt, fmt + N, spec); + } + + + + /////////////////////////////////////////////////////////////////////////// + template + struct conversion_traits; + + #define CONVERSION_TRAITS(VALUE,TYPE,UPPER) \ + template <> struct conversion_traits { \ + using value_type = TYPE; \ + static constexpr bool is_upper = UPPER; \ + }; + + CONVERSION_TRAITS ('d', int, false) + CONVERSION_TRAITS ('i', int, false) + + CONVERSION_TRAITS ('u', unsigned, false) + CONVERSION_TRAITS ('o', unsigned, false) + CONVERSION_TRAITS ('x', unsigned, false) + CONVERSION_TRAITS ('X', unsigned, true) + + CONVERSION_TRAITS ('e', double, false) + CONVERSION_TRAITS ('E', double, true) + CONVERSION_TRAITS ('f', double, false) + CONVERSION_TRAITS ('F', double, true) + CONVERSION_TRAITS ('g', double, false) + CONVERSION_TRAITS ('G', double, true) + CONVERSION_TRAITS ('a', double, false) + CONVERSION_TRAITS ('A', double, true) + + CONVERSION_TRAITS ('c', char, false) + + CONVERSION_TRAITS ('s', char*, false) + CONVERSION_TRAITS ('p', void*, false) + + CONVERSION_TRAITS ('%', void, false) + + + /////////////////////////////////////////////////////////////////////////// + // calculates the length of a fully rendered specifier/value, or format + // string and parameters. does not include any trailing null. + template + std::enable_if_t::value, size_t> + format_length (const specifier &s, const T &t) + { + return digits (t, s.base); + } + + + //------------------------------------------------------------------------- + inline constexpr + size_t + format_length (const specifier&, const char *str) + { + auto cursor = str; + while (*cursor != '\0') + ++cursor; + + return cursor - str; + } + + + //------------------------------------------------------------------------- + inline + size_t + format_length (const char *first, const char *last) + { + size_t length = 0; + + for (auto cursor = first; cursor != last; ++cursor) { + if (*cursor != '%') { + ++length; + continue; + } + + specifier spec {}; + cursor = parse (cursor, last, spec); + + if (spec.k != specifier::kind::ESCAPE) + unreachable (); + + ++length; + } + + return length; + } + + + //------------------------------------------------------------------------- + template + inline + size_t + format_length (const char *first, const char *last, ValueT value, Args&& ...args) + { + size_t length = 0; + + for (auto cursor = first; cursor != last; ++cursor) { + if (*cursor != '%') { + ++length; + continue; + } + + specifier spec {}; + cursor = parse (cursor, last, spec); + + return + length + + format_length (spec, value) + ( + (spec.k == specifier::kind::ESCAPE) ? + format_length (cursor, last, value, std::forward (args)...) : + format_length (cursor, last, std::forward (args)...) + ); + } + + return length; + } + + + //------------------------------------------------------------------------- + template + inline + size_t + format_length (const char (&fmt)[N], const Args& ...args) + { + if (N <= 1) + return 0; + + return format_length (fmt, fmt + N - 1, args...); + } + + + //------------------------------------------------------------------------- + template + inline + size_t + format_length (const char (&fmt)[N]) + { + if (N <= 1) + return 0; + + return format_length (fmt, fmt + N - 1); + } + + + /////////////////////////////////////////////////////////////////////////// + // render a single value to an ostream given the parsed specifier + + //------------------------------------------------------------------------- + // without a provided value we can only write escaped % characters + template + OutputT + write (OutputT os, const specifier s) + { + if (s.k != specifier::kind::ESCAPE) + throw missing_error (); + + *os = '%'; + return ++os; + } + + + //------------------------------------------------------------------------- + template + OutputT + write (OutputT os, const specifier spec, const char *t) + { + if (spec.k != specifier::kind::STRING) + throw conversion_error ("invalid specifier kind for string argumetn"); + + const auto len = spec.precision < 0 ? spec.precision : + (size_t)spec.precision < strlen (t) ? spec.precision : + strlen (t); + + // perform left padding + if (spec.width > len && !spec.left_adjusted) + os = std::fill_n (os, spec.width - len, spec.padding_char); + + os = std::copy_n (t, len, os); + + // perform right padding + if (spec.width > len && spec.left_adjusted) + os = std::fill_n (os, spec.width - len, spec.padding_char); + + return os; + } + + + //------------------------------------------------------------------------- + template + OutputT + write (OutputT os, const specifier spec, const std::string &val) + { + return write (os, spec, val.c_str ()); + } + + + //------------------------------------------------------------------------- + template + OutputT + write (OutputT os, const specifier s, const char t) + { + if (s.k != specifier::kind::CHARACTER) + throw conversion_error ("invalid specifier kind for char argument"); + + *os = t; + return ++os; + } + + + //------------------------------------------------------------------------- + // if the value isn't a builtin type then, if we asked for an OSTREAM + // conversion, render to a string and forward as such. + template + std::enable_if_t< + !std::is_fundamental::value && !std::is_pointer::value, + OutputT + > + write (OutputT os, const specifier& spec, const ValueT &val) + { + if (spec.k != specifier::kind::OSTREAM) + throw conversion_error ("invalid conversion specifier for user value"); + + std::ostringstream ss; + ss << val; + + specifier strspec = spec; + strspec.k = specifier::kind::STRING; + strspec.length = specifier_traits::length; + return write (os, strspec, ss.str ()); + } + + + //------------------------------------------------------------------------- + template + std::enable_if_t< + std::is_pointer::value, + OutputT + > + write (OutputT &os, const specifier &spec, const T t) + { + if (spec.k != specifier::kind::POINTER) + throw conversion_error ("invalid conversion specifier for pointer value"); + + // glibc at least uses a special form for null pointers + auto uint = reinterpret_cast (t); + if (!uint) { + static const std::string MSG = "(nil)"; + return std::copy (std::cbegin (MSG), std::cend (MSG), os); + } + + // %p specifiers are an implied %#x or %#lx + specifier uintspec = spec; + uintspec.k = specifier::kind::UNSIGNED; + uintspec.alternate_form = true; + uintspec.length = sizeof (t); + uintspec.base = 16; + + return write (os, uintspec, reinterpret_cast (t)); + } + + + //------------------------------------------------------------------------- + template + OutputT + write (OutputT os, const specifier &spec, std::nullptr_t) + { + return write (os, spec, (void*)0); + } + + + //------------------------------------------------------------------------- + template + std::enable_if_t< + std::is_integral::value, + OutputT + > + write (OutputT os, const specifier spec, ValueT t) + { + if (spec.k == specifier::kind::POINTER && !t) + { + return write (os, spec, reinterpret_cast (t)); + } + + if (spec.k != (std::is_unsigned::value ? specifier::kind::UNSIGNED : specifier::kind::SIGNED)) + throw conversion_error ("invalid conversion specifier for integer value"); + + if (spec.length != sizeof (ValueT)) + throw length_error ("incorrect value size"); + + const auto numerals = digits (t, spec.base); + const auto characters = numerals + (spec.positive_char ? 1 : 0); + + // add any requested positive signifier + if (spec.positive_char) + *os++ = spec.positive_char; + + // perform left padding + if (spec.width > characters && !spec.left_adjusted) + os = std::fill_n (os, spec.width - characters, spec.padding_char); + + // write the base prefix + if (spec.alternate_form) { + switch (spec.base) { + case 8: + *os++ = '0'; + break; + + case 16: + *os++ = '0'; + *os++ = spec.uppercase ? 'X' : 'x'; + break; + + case 10: + break; + } + } + + // actually write the number in the desired base. + // + // as a special case, if the value is zero and precision is zero then the + // output is blank (though space padding/etc is still preserved). + if (t != 0 || spec.precision != 0) { + const char *NUMERALS = spec.uppercase ? + "0123456789ABCDEF" : + "0123456789abcdef"; + + char buffer[numerals]; + for (auto cursor = buffer; t; t /= spec.base) + *cursor++ = NUMERALS[t % spec.base]; + std::reverse_copy (buffer, buffer + numerals, os); + } + + // perform right padding + if (spec.width > characters && spec.left_adjusted) + os = std::fill_n (os, spec.width - characters, spec.padding_char); + + return os; + } + + + //------------------------------------------------------------------------- + template + std::enable_if_t< + std::is_floating_point::value, + OutputT + > + write (OutputT os, const specifier spec, T t) + { + if (spec.k != specifier::kind::REAL) + throw conversion_error ("invalid conversion specifier for real value"); + + static const size_t buffer_len = strlen ("+0x") + std::numeric_limits::digits10 + strlen ("e+999") + 1; + char buffer[buffer_len]; + + static const size_t format_len = strlen ("%0-+99.99lle") + 1; + char format[format_len]; + + { + auto cursor = format; + *cursor++ = '%'; + + if (spec.alternate_form) *cursor++ = '#'; + if (spec.left_adjusted) *cursor++ = '-'; + if (spec.positive_char) *cursor++ = spec.positive_char; + + cursor += sprintf (cursor, "%u", spec.width); + + if (spec.precision >= 0) { + *cursor++ = '.'; + + if (spec.precision) + cursor += sprintf (cursor, "%i", spec.precision); + } + + if (spec.r == specifier::repr::SCIENTIFIC) + *cursor = 'e'; + else if (spec.r == specifier::repr::FIXED) + *cursor = 'f'; + else if (spec.base == 16) + *cursor = 'a'; + else + *cursor = 'g'; + + if (spec.uppercase) + *cursor = ascii::to_upper (*cursor); + ++cursor; + + *cursor++ = '\0'; + } + + auto len = snprintf (buffer, buffer_len, format, t); + if (len < 0) + throw error ("snprintf output error"); + + CHECK_LT ((size_t)len, buffer_len); + return std::copy_n (buffer, len, os); + } + + + ////////////////////////////////////////////////////////////////////////// + template + OutputT + _render (OutputT os, const char *first, const char *last) + { + auto start = std::find (first, last, '%'); + os = std::copy (first, start, os); + if (start == last) + return os; + + specifier spec; + auto cursor = parse (start, last, spec); + + return _render (write (os, spec), cursor, last); + } + + + //------------------------------------------------------------------------ + template + OutputT + _render (OutputT os, const char *first, const char *last, ValueT val, Args&& ...args) + { + auto start = std::find (first, last, '%'); + os = std::copy (first, start, os); + if (start == last) + return os; + + specifier spec; + auto cursor = parse (start, last, spec); + return _render (write (os, spec, val), cursor, last, std::forward (args)...); + } + + + //------------------------------------------------------------------------ + template + OutputT + render (OutputT os, const char (&fmt)[N], ValueT val, Args&& ...args) + { + if (N <= 1) + return os; + + const auto first = fmt; + const auto last = fmt + N - 1; + + return _render (os, first, last, val, std::forward (args)...); + } + + + //------------------------------------------------------------------------ + template + OutputT + render (OutputT os, const char (&fmt)[N]) + { + if (N <= 1) + return os; + + auto first = fmt; + auto last = fmt + N - 1; + + return _render (os, first, last); + } +} } } + + +/////////////////////////////////////////////////////////////////////////////// +namespace util { namespace format { + template + std::string + render (const char (&fmt)[N], const Args& ...args) + { + std::string res; + detail::render (std::back_inserter (res), fmt, args...); + return res; + } +} } diff --git a/log.hpp b/log.hpp index 9005772d..dd2e588c 100644 --- a/log.hpp +++ b/log.hpp @@ -61,8 +61,8 @@ namespace util { /////////////////////////////////////////////////////////////////////////// void log (level_t, const std::string &msg); - template - void log (level_t, const std::string &format, tail&& ..._tail); + template + void log (level_t, const char (&fmt)[N], const Args&...); //------------------------------------------------------------------------- diff --git a/log.ipp b/log.ipp index 6afa260f..6c3cbb16 100644 --- a/log.ipp +++ b/log.ipp @@ -24,11 +24,11 @@ //----------------------------------------------------------------------------- namespace util { - template + template void - log (level_t l, const std::string &format, tail&& ..._tail) + log (level_t l, const char (&fmt)[N], const Args& ...args) { - log (l, format::render (format, std::forward (_tail)...)); + log (l, format::render (fmt, args...)); } } diff --git a/maths.cpp b/maths.cpp index 7f5dc582..aac72953 100644 --- a/maths.cpp +++ b/maths.cpp @@ -57,26 +57,6 @@ template uint32_t util::log2 (uint32_t); template uint64_t util::log2 (uint64_t); -/////////////////////////////////////////////////////////////////////////////// -namespace util { - template <> - unsigned - digits (const uint32_t &v) - { - return (v >= 1000000000) ? 10 : - (v >= 100000000) ? 9 : - (v >= 10000000) ? 8 : - (v >= 1000000) ? 7 : - (v >= 100000) ? 6 : - (v >= 10000) ? 5 : - (v >= 1000) ? 4 : - (v >= 100) ? 3 : - (v >= 10) ? 2 : - 1; - } -} - - /////////////////////////////////////////////////////////////////////////////// template std::enable_if_t< diff --git a/maths.hpp b/maths.hpp index 410d21df..426b5b5e 100644 --- a/maths.hpp +++ b/maths.hpp @@ -17,10 +17,14 @@ #ifndef __MATHS_HPP #define __MATHS_HPP -#include "./debug.hpp" +// DO NOT INCLUDE debug.hpp +// it triggers a circular dependency; debug -> format -> maths -> debug +// instead, just use cassert + #include "./types/traits.hpp" #include "./float.hpp" +#include #include #include #include @@ -281,9 +285,40 @@ namespace util { //----------------------------------------------------------------------------- - template + constexpr unsigned - digits (const T& value); + digits10 (uint32_t v) noexcept + { + return (v >= 1000000000) ? 10 : + (v >= 100000000) ? 9 : + (v >= 10000000) ? 8 : + (v >= 1000000) ? 7 : + (v >= 100000) ? 6 : + (v >= 10000) ? 5 : + (v >= 1000) ? 4 : + (v >= 100) ? 3 : + (v >= 10) ? 2 : + 1; + } + + + template + constexpr + std::enable_if_t< + std::is_integral::value && std::is_unsigned::value, + unsigned + > + digits (ValueT value, BaseT base) noexcept + { + if (value < 0) + return digits (-value, base); + + unsigned tally = 1; + while (value /= base) + ++tally; + + return tally; + } ///---------------------------------------------------------------------------- @@ -326,8 +361,8 @@ namespace util { constexpr T gcd (T a, T b) { - CHECK_NEZ (a); - CHECK_NEZ (b); + assert (a); + assert (b); while (a != b) { if (a > b) @@ -506,7 +541,7 @@ namespace util { constexpr T limit (const T val, const U lo, const V hi) { - CHECK_LE (lo, hi); + assert (lo <= hi); return val > hi ? hi: val < lo ? lo: @@ -520,7 +555,7 @@ namespace util { T smoothstep (T a, T b, T x) { - CHECK_LE(a, b); + assert (a <= b); x = limit ((x - a) / (b - a), T{0}, T{1}); return x * x * (3 - 2 * x); } diff --git a/tap.hpp b/tap.hpp index bc1c462a..8bb82893 100644 --- a/tap.hpp +++ b/tap.hpp @@ -37,42 +37,42 @@ namespace util { namespace TAP { ~logger (); //--------------------------------------------------------------------- - template - void expect (bool, const std::string &fmt, Args&&...); + template + void expect (bool, const char (&fmt)[N], Args&&...); - template - void expect (const std::function&, Args&&..., const std::string& msg); + template + void expect (const std::function&, Args&&..., const char (&msg)[N]); //--------------------------------------------------------------------- - template - void expect_eq (const T&, const U&, const std::string &fmt, Args&&...); + template + void expect_eq (const T&, const U&, const char (&fmt)[N], Args&&...); - template - void expect_neq (const T&, const U&, const std::string &fmt, Args&&...); + template + void expect_neq (const T&, const U&, const char (&fmt)[N], Args&&...); //--------------------------------------------------------------------- - template - void expect_gt (const T&, const U&, const std::string &fmt, Args&&...); + template + void expect_gt (const T&, const U&, const char (&fmt)[N], Args&&...); - template - void expect_ge (const T&, const U&, const std::string &fmt, Args&&...); + template + void expect_ge (const T&, const U&, const char (&fmt)[N], Args&&...); - template - void expect_lt (const T&, const U&, const std::string &fmt, Args&&...); + template + void expect_lt (const T&, const U&, const char (&fmt)[N], Args&&...); - template - void expect_le (const T&, const U&, const std::string &fmt, Args&&...); + template + void expect_le (const T&, const U&, const char (&fmt)[N], Args&&...); //--------------------------------------------------------------------- - template - void expect_nan (const T&, const std::string &fmt, Args&&...); + template + void expect_nan (const T&, const char (&fmt)[N], Args&&...); //--------------------------------------------------------------------- - template - void expect_nothrow (T&&, const std::string &fmt, Args&&...); + template + void expect_nothrow (T&&, const char (&fmt)[N], Args&&...); - template - void expect_throw (T&&, const std::string &fmt, Args&&...); + template + void expect_throw (T&&, const char (&fmt)[N], Args&&...); //--------------------------------------------------------------------- void skip (const std::string &msg); diff --git a/tap.ipp b/tap.ipp index c0de09b4..8fa4d1d7 100644 --- a/tap.ipp +++ b/tap.ipp @@ -29,9 +29,9 @@ /////////////////////////////////////////////////////////////////////////////// -template +template void -util::TAP::logger::expect (bool test, const std::string &fmt, Args&&... args) +util::TAP::logger::expect (bool test, const char (&fmt)[N], Args&&... args) { std::cout << (test ? "ok " : "not ok ") << ++m_size << " - " @@ -43,61 +43,42 @@ util::TAP::logger::expect (bool test, const std::string &fmt, Args&&... args) //----------------------------------------------------------------------------- -template +template void -util::TAP::logger::expect (const std::function &test, Args&&... args, const std::string &msg) +util::TAP::logger::expect (const std::function &test, Args&&... args, const char (&fmt)[N]) { - expect (test (std::forward (args)...), msg); + expect (test (std::forward (args)...), fmt); +} + + +/////////////////////////////////////////////////////////////////////////////// +template +void +util::TAP::logger::expect_eq (const T&a, const U &b, const char (&fmt)[N], Args&&... args) +{ + expect (almost_equal (a, b), fmt, std::forward (args)...); } //----------------------------------------------------------------------------- -template +template void -util::TAP::logger::expect_eq (const T&a, const U &b, const std::string &fmt, Args&&... args) +util::TAP::logger::expect_neq (const T&a, const U &b, const char (&fmt)[N], Args&&... args) { - static const std::function TEST = [] (const T &t, const U &u) -> bool { - return almost_equal (t, u); - }; - - expect (TEST, a, b, util::format::render (fmt, std::forward (args)...)); + expect (!almost_equal (a, b), fmt, std::forward (args)...); } -//----------------------------------------------------------------------------- -template -void -util::TAP::logger::expect_neq (const T&a, const U &b, const std::string &fmt, Args&&... args) -{ - static const std::function TEST = [] (const T &t, const U &u) -> bool { - return !almost_equal (t, u); - }; - - expect (TEST, a, b, util::format::render (fmt, std::forward (args)...)); -} - - -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// #define TAP_TEST(SUFFIX,OP) \ -template \ +template \ void \ util::TAP::logger::expect_ ## SUFFIX (const T &a, \ const U &b, \ - const std::string &fmt, \ + const char (&fmt)[N], \ Args&&... args) \ { \ - static const std::function< \ - bool(const T&,const U&) \ - > TEST = [] (const T&t, const U&u) { return t OP u; }; \ - \ - expect ( \ - TEST, \ - a, b, \ - util::format::render ( \ - fmt, \ - std::forward (args)... \ - ) \ - ); \ + expect ((a) OP (b), fmt, std::forward (args)...); \ } TAP_TEST(gt, > ) @@ -109,23 +90,18 @@ TAP_TEST(le, <=) //----------------------------------------------------------------------------- -template +template void -util::TAP::logger::expect_nan (const T &t, const std::string &fmt, Args&&... args) +util::TAP::logger::expect_nan (const T &t, const char (&fmt)[N], Args&&... args) { - bool(*func)(T) = std::isnan; - expect ( - std::function (func), - t, - util::format::render (fmt, std::forward (args)...) - ); + expect (std::isnan (t), fmt, std::forward (args)...); } //----------------------------------------------------------------------------- -template +template void -util::TAP::logger::expect_nothrow (T &&t, const std::string &fmt, Args&&... args) +util::TAP::logger::expect_nothrow (T &&t, const char (&fmt)[N], Args&&... args) { bool success = true; @@ -135,14 +111,14 @@ util::TAP::logger::expect_nothrow (T &&t, const std::string &fmt, Args&&... args success = false; } - expect (success, util::format::render (fmt, std::forward (args)...)); + expect (success, fmt, std::forward (args)...); } //----------------------------------------------------------------------------- -template +template void -util::TAP::logger::expect_throw (T &&t, const std::string &fmt, Args&&... args) +util::TAP::logger::expect_throw (T &&t, const char (&fmt)[N], Args&&... args) { bool success = false; @@ -154,5 +130,5 @@ util::TAP::logger::expect_throw (T &&t, const std::string &fmt, Args&&... args) success = false; } - expect (success, util::format::render (fmt, std::forward (args)...)); + expect (success, fmt, std::forward (args)...); } diff --git a/test/cmdopt.cpp b/test/cmdopt.cpp index 8b7432cf..d023ade0 100644 --- a/test/cmdopt.cpp +++ b/test/cmdopt.cpp @@ -86,13 +86,13 @@ test_bool (util::TAP::logger &tap) for (auto i: positive) { argv[2] = i; p.scan (argv.size (), argv.data ()); - tap.expect_eq (value, true, i, "read bool, %s", i); + tap.expect_eq (value, true, "read bool, %s", i); } for (auto i: negative) { argv[2] = i; p.scan (argv.size (), argv.data ()); - tap.expect_eq (value, false, i, "read bool, %s", i); + tap.expect_eq (value, false, "read bool, %s", i); } // Check that invalid forms of boolean all throw exceptions diff --git a/test/colour.cpp b/test/colour.cpp index 3dc20f4f..15b9d7e8 100644 --- a/test/colour.cpp +++ b/test/colour.cpp @@ -45,8 +45,8 @@ main (int, char**) }; for (auto i: TESTS) { - tap.expect_eq (util::rgb_to_hsv (i.rgb), i.hsv, i.name); - tap.expect_eq (util::hsv_to_rgb (i.hsv), i.rgb, i.name); + tap.expect_eq (util::rgb_to_hsv (i.rgb), i.hsv, "rgb-to-hsv %s", i.name); + tap.expect_eq (util::hsv_to_rgb (i.hsv), i.rgb, "hsv-to-rgb %s", i.name); } } } diff --git a/test/crypto/arc4.cpp b/test/crypto/arc4.cpp index 9b70293a..6e3c2b1e 100644 --- a/test/crypto/arc4.cpp +++ b/test/crypto/arc4.cpp @@ -2,6 +2,7 @@ #include "tap.hpp" #include "types.hpp" + int main () { @@ -384,8 +385,6 @@ main () success = success && std::equal (std::begin (data), std::end (data), std::begin (t.data[j])); }; - std::ostringstream os; - os << "ARC4: " << i; - tap.expect (success, os.str ()); + tap.expect (success, "ARC4 %zu", i); } } diff --git a/test/crypto/tea.cpp b/test/crypto/tea.cpp index 3eef45ac..ab4bdca9 100644 --- a/test/crypto/tea.cpp +++ b/test/crypto/tea.cpp @@ -50,17 +50,8 @@ main () std::array dec (t.enc); gen.decrypt (dec.data (), dec.size ()); - { - std::ostringstream os; - os << "TEA_enc " << i; - tap.expect (enc == t.enc, os.str ()); - } - - { - std::ostringstream os; - os << "TEA_dec " << i; - tap.expect (dec == t.dec, os.str ()); - } + tap.expect (enc == t.enc, "TEA_enc %zu", i); + tap.expect (dec == t.dec, "TEA_dec %zu", i); } return tap.status (); diff --git a/test/crypto/xtea.cpp b/test/crypto/xtea.cpp index 2263080b..17ca469f 100644 --- a/test/crypto/xtea.cpp +++ b/test/crypto/xtea.cpp @@ -49,17 +49,8 @@ main () std::array dec (t.enc); gen.decrypt (dec.data (), dec.size ()); - { - std::ostringstream os; - os << "XTEA_enc " << i; - tap.expect (enc == t.enc, os.str ()); - } - - { - std::ostringstream os; - os << "XTEA_dec " << i; - tap.expect (dec == t.dec, os.str ()); - } + tap.expect (enc == t.enc, "XTEA_enc %zu", i); + tap.expect (dec == t.dec, "XTEA_dec %zu", i); } return tap.status (); diff --git a/test/crypto/xxtea.cpp b/test/crypto/xxtea.cpp index 0a4dbb39..a568196c 100644 --- a/test/crypto/xxtea.cpp +++ b/test/crypto/xxtea.cpp @@ -104,17 +104,8 @@ main () std::vector dec (enc); gen.decrypt (dec.data (), dec.size ()); - { - std::ostringstream os; - os << "XXTEA_enc " << i; - tap.expect (enc == t.enc, os.str ()); - } - - { - std::ostringstream os; - os << "XXTEA_dec " << i; - tap.expect (dec == t.dec, os.str ()); - } + tap.expect (enc == t.enc, "XXTEA_enc %zu", i); + tap.expect (dec == t.dec, "XXTEA_dec %zu", i); } return tap.status (); diff --git a/test/fixed.cpp b/test/fixed.cpp index 0af75841..03db9675 100644 --- a/test/fixed.cpp +++ b/test/fixed.cpp @@ -16,19 +16,19 @@ test_simple (util::TAP::logger &tap) std::ostringstream os; os << "fixed<" << type_to_string () << ',' << I << ',' << E << '>'; - tap.expect_eq (lo, lo, os.str () + " self equality"); - tap.expect_eq (hi, hi, os.str () + " self equality"); + tap.expect_eq (lo, lo, "%s self equality", os.str ()); + tap.expect_eq (hi, hi, "%s self equality", os.str ()); - tap.expect_neq (hi, lo, os.str () + " inequality"); - tap.expect_neq (lo, hi, os.str () + " inequality"); + tap.expect_neq (hi, lo, "%s inequality", os.str ()); + tap.expect_neq (lo, hi, "%s inequality", os.str ()); - tap.expect_lt (lo, hi, os.str () + " less than"); - tap.expect_le (lo, hi, os.str () + " less than equal"); - tap.expect_le (lo, lo, os.str () + " less than equal"); + tap.expect_lt (lo, hi, "%s less than", os.str ()); + tap.expect_le (lo, hi, "%s less than equal", os.str ()); + tap.expect_le (lo, lo, "%s less than equal", os.str ()); - tap.expect_gt (hi, lo, os.str () + " greater than"); - tap.expect_ge (lo, lo, os.str () + " greater than equal"); - tap.expect_ge (hi, lo, os.str () + " greater than equal"); + tap.expect_gt (hi, lo, "%s greater than", os.str ()); + tap.expect_ge (lo, lo, "%s greater than equal", os.str ()); + tap.expect_ge (hi, lo, "%s greater than equal", os.str ()); } diff --git a/test/format.cpp b/test/format.cpp index c3aa6fb1..b343e33e 100644 --- a/test/format.cpp +++ b/test/format.cpp @@ -5,25 +5,186 @@ int main (void) { - using namespace std::string_literals; - util::TAP::logger tap; - tap.expect_eq (util::format::render ("identity"), "identity"s, "identity literal"); - tap.expect_eq (util::format::render ("%s", "identity"s), "identity"s, "identity string substitution"); - tap.expect_eq (util::format::render ("%s", "identity" ), "identity"s, "identity char[] substitution"); + #define CHECK_RENDER(fmt,res,...) do { \ + auto val = util::format::render (fmt, ##__VA_ARGS__); \ + tap.expect_eq (val, res, "render '%s'", fmt); \ + } while (0) - tap.expect_throw ([] (void) { - util::format::render ("%s"); - }, "missing value"); + CHECK_RENDER ("foo", "foo"); - tap.expect_throw> ([] (void) { - util::format::render ("%<", 42); - }, "invalid specifier"); + CHECK_RENDER ("%i", "1", 1 ); + CHECK_RENDER ("%3i", " 1", 1 ); + CHECK_RENDER ("%03i", "001", 1 ); + CHECK_RENDER ("%.i", "", 0); + CHECK_RENDER ("% .i", " ", 0); // zero precision still requires a space - tap.expect_throw ([] (void) { - util::format::render ("%", 42); - }, "truncated specifier"); + CHECK_RENDER ("%hhi", "1", (signed char)1); + CHECK_RENDER ("%hi", "1", (signed short)1); + CHECK_RENDER ("%li", "1", (signed long)1); + CHECK_RENDER ("%lli", "1", (signed long long)1); + CHECK_RENDER ("%ji", "1", (intmax_t)1); + CHECK_RENDER ("%zi", "1", (ssize_t)1); + CHECK_RENDER ("%ti", "1", (ptrdiff_t)1); - return tap.status (); + CHECK_RENDER ("%u", "1", 1u); + CHECK_RENDER ("%03u", "001", 1u); + CHECK_RENDER ("% u", " 1", 1u); + CHECK_RENDER ("% 3u", " 1", 1u); + CHECK_RENDER ("% 03u", " 01", 1u); + CHECK_RENDER ("%-3u", "1 ", 1u); + CHECK_RENDER ("%64u", " 1", 1u); + + CHECK_RENDER ("%hhu", "1", (unsigned char)1); + CHECK_RENDER ("%hu", "1", (unsigned short)1); + CHECK_RENDER ("%lu", "1", (unsigned long)1); + CHECK_RENDER ("%llu", "1", (unsigned long long)1); + CHECK_RENDER ("%ju", "1", (uintmax_t)1); + CHECK_RENDER ("%zu", "1", (size_t)1); + + CHECK_RENDER ("%o", "1", 01u); + CHECK_RENDER ("%o", "13", 013u); + CHECK_RENDER ("%o", "13", 013u); + CHECK_RENDER ("%#o", "013", 013u); + + CHECK_RENDER ("%x", "1", 0x1u); + CHECK_RENDER ("%x", "fe", 0xfeu); + CHECK_RENDER ("%X", "FE", 0xFEu); + CHECK_RENDER ("%#x", "0xfe", 0xfeu); + CHECK_RENDER ("%#X", "0XFE", 0xFEu); + + CHECK_RENDER ("%e", "1.000000e+00", 1.); + CHECK_RENDER ("%e", "1.200000e+00", 1.2); + CHECK_RENDER ("%e", "1.234568e+00", 1.2345678); + + CHECK_RENDER ("%E", "1.234568E+00", 1.2345678); + + CHECK_RENDER ("%f", "1.000000", 1.); + CHECK_RENDER ("%f", "1.200000", 1.2); + CHECK_RENDER ("%f", "1.234560", 1.23456); + CHECK_RENDER ("%f", "1.234567", 1.234567); + CHECK_RENDER ("%f", "1.234568", 1.2345678); + + CHECK_RENDER ("%g", "1", 1.); + CHECK_RENDER ("%g", "1.2", 1.2); + CHECK_RENDER ("%g", "1.23457", 1.2345678); + CHECK_RENDER ("%g", "0.000123457", 0.00012345678); + CHECK_RENDER ("%g", "1.23457e-05", 0.000012345678); + CHECK_RENDER ("%G", "1.23457E-05", 0.000012345678); + + CHECK_RENDER ("%+e", "+1.234568e+00", 1.2345678); + CHECK_RENDER ("%+f", "+1.234568", 1.2345678); + CHECK_RENDER ("%+g", "+1.23457", 1.2345678); + CHECK_RENDER ("%+a", "+0x1.3c0ca2a5b1d5dp+0", 1.2345678); + + CHECK_RENDER ("%#.e", "1.e+00", 1.2345678); + CHECK_RENDER ("%#.f", "1.", 1.2345678); + CHECK_RENDER ("%#.g", "1.", 1.2345678); + //CHECK_RENDER ("%#.a", "0x1.p+0", 1.2345678); + + CHECK_RENDER ("%a", "0x1.3c0ca2a5b1d5dp+0", 1.2345678); + CHECK_RENDER ("%A", "0X1.3C0CA2A5B1D5DP+0", 1.2345678); + + CHECK_RENDER ("%e", "inf", std::numeric_limits::infinity ()); + CHECK_RENDER ("%E", "INF", std::numeric_limits::infinity ()); + CHECK_RENDER ("%f", "inf", std::numeric_limits::infinity ()); + CHECK_RENDER ("%F", "INF", std::numeric_limits::infinity ()); + CHECK_RENDER ("%g", "inf", std::numeric_limits::infinity ()); + CHECK_RENDER ("%G", "INF", std::numeric_limits::infinity ()); + CHECK_RENDER ("%a", "inf", std::numeric_limits::infinity ()); + CHECK_RENDER ("%A", "INF", std::numeric_limits::infinity ()); + + CHECK_RENDER ("%e", "nan", std::numeric_limits::quiet_NaN ()); + CHECK_RENDER ("%E", "NAN", std::numeric_limits::quiet_NaN ()); + CHECK_RENDER ("%f", "nan", std::numeric_limits::quiet_NaN ()); + CHECK_RENDER ("%F", "NAN", std::numeric_limits::quiet_NaN ()); + CHECK_RENDER ("%g", "nan", std::numeric_limits::quiet_NaN ()); + CHECK_RENDER ("%G", "NAN", std::numeric_limits::quiet_NaN ()); + CHECK_RENDER ("%a", "nan", std::numeric_limits::quiet_NaN ()); + CHECK_RENDER ("%A", "NAN", std::numeric_limits::quiet_NaN ()); + + CHECK_RENDER ("%.f", "1", 1.2345678); + CHECK_RENDER ("%3.f", " 1", 1.2345678); + CHECK_RENDER ("%3.2f", "1.23", 1.2345678); + CHECK_RENDER ("%3.2f", "1234.57", 1234.5678); + + CHECK_RENDER ("%c", "A", 'A'); + + CHECK_RENDER ("%s", "foo", "foo"); + CHECK_RENDER ("%.s", "", "foo"); + CHECK_RENDER ("%.0s", "", "foo"); + CHECK_RENDER ("%.2s", "fo", "foo"); + CHECK_RENDER ("%.02s", "fo", "foo"); + CHECK_RENDER ("%.64s", "foo", "foo"); + CHECK_RENDER ("%3.1s", " f", "foo"); + CHECK_RENDER ("%-3.1s", "f ", "foo"); + + CHECK_RENDER ("%p", "0x1234567", (void*)0x01234567); + CHECK_RENDER ("%p", "0x1234567", (int*)0x01234567); + CHECK_RENDER ("%p", "(nil)", nullptr); + CHECK_RENDER ("%p", "(nil)", NULL); + + CHECK_RENDER ("%%", "%"); + CHECK_RENDER ("%10%", "%"); + CHECK_RENDER ("%.%", "%"); + CHECK_RENDER ("% 0-+#12.34%", "%"); // escaped conversions should largely ignore flags, width, and precision. + + // multiple components + CHECK_RENDER ("%%%%", "%%"); + + CHECK_RENDER (" %%", " %"); + CHECK_RENDER ("%% ", "% "); + CHECK_RENDER ("%% %%", "% %"); + CHECK_RENDER (" %% %% ", " % % "); + + CHECK_RENDER ("%u %u", "1 2", 1u, 2u); + + CHECK_RENDER ("%#o %o", "010 10", 8u, 8u); + CHECK_RENDER ("%#o %o %#o", "010 10 010", 8u, 8u, 8u); + CHECK_RENDER ("%X%x%X", "FfF", 0xfu, 0xfu, 0xfu); + + tap.expect_eq (util::format::render ("%u\n", 1u), "1\n", "newline"); + + #define CHECK_THROW(fmt,except,...) do { \ + tap.expect_throw ([&] { \ + util::format::render (fmt, ##__VA_ARGS__); \ + }, "exception '%s' for format '%s'", #except, fmt); \ + } while (0) + + CHECK_THROW("%", syntax_error); + CHECK_THROW("%_", syntax_error); + CHECK_THROW("%_u", syntax_error); + + CHECK_THROW("%u", missing_error); + CHECK_THROW("%!", missing_error); + + CHECK_THROW("%d", conversion_error, 1u); + CHECK_THROW("%i", conversion_error, 1u); + CHECK_THROW("%i", conversion_error, nullptr); + + CHECK_THROW("%hhi", length_error, (long long)1); + CHECK_THROW("%lli", length_error, (signed char)1); + + CHECK_THROW("%u", conversion_error, 1.); + CHECK_THROW("%u", conversion_error, "foo"); + CHECK_THROW("%u", conversion_error, (void*)0); + CHECK_THROW("%u", conversion_error, 1); + CHECK_THROW("%u", conversion_error, nullptr); + + CHECK_THROW("%hhu", length_error, (unsigned long long)1); + CHECK_THROW("%llu", length_error, (unsigned char)1); + + CHECK_THROW("%f", conversion_error, 1u); + CHECK_THROW("%f", conversion_error, "foo"); + CHECK_THROW("%f", conversion_error, nullptr); + + CHECK_THROW("%s", conversion_error, 1u); + CHECK_THROW("%s", conversion_error, '_'); + CHECK_THROW("%s", conversion_error, nullptr); + + CHECK_THROW("%c", conversion_error, 1u); + CHECK_THROW("%c", conversion_error, "foo"); + + CHECK_THROW("%!", conversion_error, 1u); } diff --git a/test/hash/hmac.cpp b/test/hash/hmac.cpp index 47406b49..089d8b28 100644 --- a/test/hash/hmac.cpp +++ b/test/hash/hmac.cpp @@ -274,7 +274,7 @@ main (int, char**) util::TAP::logger tap; for (size_t i = 0; i < elems (TESTS); ++i) - tap.expect (TESTS[i].fun (TESTS[i].key, TESTS[i].dat, TESTS[i].res), "standard test vector %u", i); + tap.expect (TESTS[i].fun (TESTS[i].key, TESTS[i].dat, TESTS[i].res), "standard test vector %zu", i); return tap.status (); } diff --git a/test/hash/ripemd.cpp b/test/hash/ripemd.cpp index 6dc1ac44..cc8f4d7c 100644 --- a/test/hash/ripemd.cpp +++ b/test/hash/ripemd.cpp @@ -116,7 +116,7 @@ main(int, char**) { obj.update (reinterpret_cast (i.data), strlen (i.data)); obj.finish (); - tap.expect_eq (obj.digest (), i.output, i.msg); + tap.expect_eq (obj.digest (), i.output, "%s", i.msg); } // Perform 'million-a' check diff --git a/test/hash/sha1.cpp b/test/hash/sha1.cpp index a687245b..5c4ee9b0 100644 --- a/test/hash/sha1.cpp +++ b/test/hash/sha1.cpp @@ -66,7 +66,7 @@ main (int, char**) obj.update (reinterpret_cast (i.input), strlen (i.input)); obj.finish (); - tap.expect_eq (obj.digest (), i.output, i.msg); + tap.expect_eq (obj.digest (), i.output, "%s", i.msg); } return tap.status (); diff --git a/test/hash/sha2.cpp b/test/hash/sha2.cpp index c49b823c..7a01f1c4 100644 --- a/test/hash/sha2.cpp +++ b/test/hash/sha2.cpp @@ -55,7 +55,7 @@ main (int, char **) { obj.update (reinterpret_cast (i.input), strlen (i.input)); obj.finish (); - tap.expect_eq (obj.digest (), i.output, i.msg); + tap.expect_eq (obj.digest (), i.output, "%s", i.msg); } return tap.status (); diff --git a/test/ip.cpp b/test/ip.cpp index dfaa0ac5..2f9c3be0 100644 --- a/test/ip.cpp +++ b/test/ip.cpp @@ -23,9 +23,8 @@ test_good (util::TAP::logger &tap) { "127.0.0.1", { 127, 0, 0, 1 }, "localhost" } }; - for (const auto &i: TESTS) { - tap.expect_eq (ipv4::ip::parse (i.str), i.ip, i.msg); - } + for (const auto &i: TESTS) + tap.expect_eq (ipv4::ip::parse (i.str), i.ip, "%s", i.msg); } @@ -43,9 +42,8 @@ test_bad (util::TAP::logger &tap) { "256.0.0.1", "overflow" } }; - for (const auto &i: TESTS) { - tap.expect_throw ([&] { ipv4::ip::parse (i.str); }, i.msg); - } + for (const auto &i: TESTS) + tap.expect_throw ([&] { ipv4::ip::parse (i.str); }, "%s", i.msg); } diff --git a/test/polynomial.cpp b/test/polynomial.cpp index 3ceba424..a1762c79 100644 --- a/test/polynomial.cpp +++ b/test/polynomial.cpp @@ -52,7 +52,7 @@ main (int, char**) ok = false; } - test.expect (ok, i.name); + test.expect (ok, "%s", i.name); } return 0; diff --git a/test/roots/bisection.cpp b/test/roots/bisection.cpp index 10a5e645..f0e763b7 100644 --- a/test/roots/bisection.cpp +++ b/test/roots/bisection.cpp @@ -33,7 +33,7 @@ main (void) for (const auto &t: TESTS) { constexpr float TOLERANCE = 0.00001f; auto root = util::roots::bisection (t.lo, t.hi, t.func, TOLERANCE); - tap.expect_eq (root, t.root, t.msg); + tap.expect_eq (root, t.root, "%s", t.msg); } return tap.status (); diff --git a/test/vector.cpp b/test/vector.cpp index 62081404..c38a26da 100644 --- a/test/vector.cpp +++ b/test/vector.cpp @@ -47,7 +47,7 @@ test_polar (util::TAP::logger &tap) auto in_cart = t.cartesian; auto to_cart = util::polar_to_cartesian (t.polar); - tap.expect_lt ((in_cart - to_cart).magnitude (), 0.00001f, t.desc); + tap.expect_lt ((in_cart - to_cart).magnitude (), 0.00001f, "%s", t.desc); // Compare polar representations. Make sure to normalise them first. auto in_polar = t.polar; @@ -56,7 +56,7 @@ test_polar (util::TAP::logger &tap) in_polar[1] = std::fmod (in_polar[1], 2 * util::PI); to_polar[1] = std::fmod (to_polar[1], 2 * util::PI); - tap.expect_eq (in_polar, to_polar, t.desc); + tap.expect_eq (in_polar, to_polar, "%s", t.desc); } } diff --git a/test/version.cpp b/test/version.cpp index f54247ad..f8abf25c 100644 --- a/test/version.cpp +++ b/test/version.cpp @@ -52,7 +52,7 @@ main () { for (const auto &i: PARSE_TESTS) { util::version v (i.str); - tap.expect (std::equal (v.begin (), v.end (), i.parts) && v.release == i.release, i.msg); + tap.expect (std::equal (v.begin (), v.end (), i.parts) && v.release == i.release, "%s", i.msg); }