diff --git a/CMakeLists.txt b/CMakeLists.txt index 89854088..d96e4af4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ endif() RAGEL_TARGET(json-flat json/flat.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/json/flat.cpp) RAGEL_TARGET(uri uri.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/uri.cpp) RAGEL_TARGET(version version.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) +RAGEL_TARGET(format.cpp format.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/format.cpp) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) @@ -223,9 +224,8 @@ list ( fixed.hpp float.cpp float.hpp - format.cpp + ${CMAKE_CURRENT_BINARY_DIR}/format.cpp format.hpp - format.ipp fourcc.cpp fourcc.hpp geom/fwd.hpp diff --git a/debug.ipp b/debug.ipp index 5371c2e0..becbc0b2 100644 --- a/debug.ipp +++ b/debug.ipp @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2015 Danny Robson + * Copyright 2015-2018, Danny Robson */ #ifdef __UTIL_DEBUG_IPP @@ -20,20 +20,23 @@ #define __UTIL_DEBUG_IPP -#include "./format.hpp" +#include "backtrace.hpp" +#include "format.hpp" #include +#include + /////////////////////////////////////////////////////////////////////////////// namespace util::debug::detail { void panic [[noreturn]] (const char *msg); template - constexpr void panic [[noreturn]] (const char (&fmt)[N], const Args& ...args) { - auto msg = util::format::render (fmt, args...); - panic (msg.c_str ()); + std::cerr << format::printf (fmt, args...) << ::debug::backtrace () << std::endl; + breakpoint (); + abort (); } void not_implemented [[noreturn]] (const char *msg); diff --git a/format.cpp b/format.cpp deleted file mode 100644 index 32291cf3..00000000 --- a/format.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Copyright 2015 Danny Robson - */ - -#include "format.hpp" - -#include - - -namespace util::format::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.cpp.rl b/format.cpp.rl new file mode 100644 index 00000000..73a1312b --- /dev/null +++ b/format.cpp.rl @@ -0,0 +1,222 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2017 Danny Robson + */ + +#include "./format.hpp" + +#include + +namespace util::format { + std::ostream& + operator<< (std::ostream &os, type_t val) + { + switch (val) { + case type_t::LITERAL: return os << "LITERAL"; + case type_t::USER: return os << "USER"; + case type_t::ESCAPE: return os << "ESCAPE"; + case type_t::SIGNED: return os << "SIGNED"; + case type_t::UNSIGNED: return os << "UNSIGNED"; + case type_t::REAL: return os << "REAL"; + case type_t::STRING: return os << "STRING"; + case type_t::CHAR: return os << "CHAR"; + case type_t::POINTER: return os << "POINTER"; + case type_t::COUNT: return os << "COUNT"; + } + + return os << "UNKNOWN_" << static_cast> (val); + } + + + std::ostream& + operator<< (std::ostream &os, const specifier &val) + { + return os << "{ fmt: " << val.fmt << ", type: " << val.type << " }"; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +%%{ + machine printf; + + parameter = digit+ '$'; + + flag = '+' %{ s.flags.plus = true; } + | '-' %{ s.flags.minus = true; } + | ' ' %{ s.flags.space = true; } + | '0' %{ s.flags.zero = true; } + | '#' %{ s.flags.hash = true; } + ; + + width = digit+ >{ s.width = 0; } ${ s.width *= 10; s.width += fc - '0'; }; + + # precision may be zero digits which implies zero precision. + precision = '.' >{ s.precision = 0; } digit* ${ s.precision *= 10; s.precision += fc - '0'; }; + + length = + 'hh' %{ s.length = sizeof (char); } + | 'h' %{ s.length = sizeof (short); } + | 'l' %{ s.length = sizeof (long); } + | 'll' %{ s.length = sizeof (long long); } + | 'L' %{ s.length = sizeof (long double); } + | 'z' %{ s.length = sizeof (size_t); } + | 'j' %{ s.length = sizeof (intmax_t); } + | 't' %{ s.length = sizeof (ptrdiff_t); } + ; + + type = ( + '!' >{ s.type = type_t::USER; } + | '%' >{ s.type = type_t::ESCAPE; } + | ( + 'd' + | 'i' + ) >{ s.type = type_t::SIGNED; } + | ( + 'u' + | 'x' %{ s.base = 16; } + | 'X' %{ s.base = 16; s.upper = true; } + | 'o' %{ s.base = 8; } + ) >{ s.type = type_t::UNSIGNED; } + | ( + ('f' | 'F' %{ s.upper = true; }) %{ s.representation = specifier::FIXED; } + | ('e' | 'E' %{ s.upper = true; }) %{ s.representation = specifier::SCIENTIFIC; } + | ('g' | 'G' %{ s.upper = true; }) %{ s.representation = specifier::DEFAULT; } + | ('a' | 'A' %{ s.upper = true; }) %{ s.representation = specifier::HEX; s.base = 16; } + ) >{ s.type = type_t::REAL; } + | 's' >{ s.type = type_t::STRING; } + | 'c' >{ s.type = type_t::CHAR; } + | 'p' >{ s.type = type_t::POINTER; } + | 'n' >{ s.type = type_t::COUNT; } + ); + + literal = ([^%]+) + >{ + s = specifier {}; + s.fmt = {fpc,fpc}; + s.type = type_t::LITERAL; + } + %{ + s.fmt = {s.fmt.begin(),fpc}; + if (!s.fmt.empty ()) { + specs.push_back (s); + } + }; + + specifier = ( + '%' + parameter? + flag** + width? + precision? + length? + type + ) + >{ + s = specifier {}; + s.fmt = {fpc,fpc}; + } + %{ + s.fmt = {s.fmt.begin(),fpc}; + specs.push_back (s); + }; + + format := literal? (specifier literal?)** + >{ success = false; } + %{ success = true; } + ; + + write data; +}%% + + +/////////////////////////////////////////////////////////////////////////////// +util::format::parsed +util::format::printf (util::view fmt) +{ + std::vector specs; + specifier s; + bool success = false; + (void)s; + + int cs; + + auto p = std::cbegin (fmt); + auto pe = std::cend (fmt); + auto eof = pe; + + %%write init; + %%write exec; + + if (!success) + throw std::runtime_error ("invalid format specification"); + + return parsed { std::move (specs) }; +} + + +/// parses a format specifier in the style of PEP3101 (with the notable +/// exception of named parameters). +/// +/// in the event of a parsing error the function will throw. makes no +/// attempt to cater for constexpr validation. +util::format::parsed +util::format::python (util::view fmt) +{ + std::vector specs; + + const auto *prev = std::begin (fmt); + const auto *cursor = std::begin (fmt); + while (*cursor) { + switch (*cursor) { + case '{': + { + { + specifier s; + s.fmt = { prev, cursor }; + s.type = type_t::LITERAL; + specs.push_back (s); + } + + auto first = cursor; + while (*++cursor != '}') + ; + + ++cursor; + { + specifier s; + s.fmt = {first, cursor}; + s.type = type_t::USER; + specs.push_back (s); + } + + prev = cursor; + break; + } + + default: + ++cursor; + break; + } + } + + { + specifier s; + s.fmt = {prev,cursor}; + s.type = type_t::LITERAL; + specs.push_back (s); + } + + return parsed {std::move (specs)}; +} diff --git a/format.hpp b/format.hpp index 48dd6103..bd47b24d 100644 --- a/format.hpp +++ b/format.hpp @@ -11,66 +11,562 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2016 Danny Robson + * Copyright 2017 Danny Robson */ -#ifndef __UTIL_FORMAT_HPP -#define __UTIL_FORMAT_HPP +#ifndef CRUFT_UTIL_FORMAT_HPP +#define CRUFT_UTIL_FORMAT_HPP -#include -#include +#include "maths.hpp" +#include "view.hpp" + +#include +#include +#include +#include +#include +#include +#include namespace util::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&...); + /// denotes the stated data type of one specifier + enum class type_t { + /// an internal type that indicates a span of literal text to copy + /// from the format specifier string + LITERAL, + /// a type that has implemented an ostream operator + USER, - //------------------------------------------------------------------------- - class error : public std::runtime_error - { using runtime_error::runtime_error; }; + /// a literal '%' symbol + ESCAPE, - // value-specifier mismatch - class value_error : public error - { using error::error; }; + /// numeric types + SIGNED, + UNSIGNED, + REAL, - struct conversion_error : public error - { using error::error; }; + /// a C style string, or equivalent C++ type (std::string, + /// std::string_view, util::view, etc, ...) + STRING, + /// a single character + CHAR, - struct length_error : public error - { using error::error; }; + /// a raw pointer (rather than value that needs to be dereferenced) + POINTER, - // malformed format specifier - class syntax_error : public error - { using error::error; }; + /// number of characters written + COUNT + }; - template - class invalid_specifier : error { + /// formatting information for a single specifier. + /// + /// TODO: investigate using a proper tagged union to compress the data size + struct specifier { + /// the sub-region of the format specifier that we parsed for this + /// information. probably only useful for the LITERAL type as we just + /// copy the view into the output buffer directly. + util::view fmt = util::view {nullptr}; + + int parameter = -1; + + struct { + bool plus = false; + bool minus = false; + bool space = false; + bool zero = false; + bool hash = false; + } flags; + + int width = -1; + + int precision = -1; + + int length = -1; + + type_t type = type_t::USER; + + bool upper = false; + int base = 10; + enum { + FIXED, + SCIENTIFIC, + DEFAULT, + HEX, + } representation = DEFAULT; + }; + + + struct parsed; + template class bound; + template class stored; + + /// a sequence of parsed specifiers that can be used to render some + /// collection parameters in the future. + struct parsed { + std::vector m_specifiers; + + auto begin (void) const { return std::begin (m_specifiers); } + auto end (void) const { return std::end (m_specifiers); } + + /// records a complete collection of parameters for rendering in the + /// future. the caller must maintain the 'parsed' object for the + /// lifetime of the return value. + template + bound + operator () (const Args &...args) &; + + /// records a complete collection of parameters for rendering in the + /// future. takes ownership of the specifiers so there is no lifetime + /// requirement. + template + stored + operator () (const Args &...args) &&; + }; + + + /// parameter collection for a non-owning sequence of specifiers + template + class bound { public: - using value_type = ValueT; + bound (const parsed &_parsed, const ValueT &...args): + m_parsed {_parsed}, + m_values {args...} + { ; } - explicit invalid_specifier (char specifier); + auto specifiers (void) const + { return util::make_view (m_parsed.m_specifiers); } - char specifier (void) const; + template + auto + get (void) const& { return std::get (m_values); } private: - char m_specifier; + const parsed &m_parsed; + std::tuple m_values; }; - // missing format specifier - class missing_error : public error - { + + /// parameter collection for an owning squence of specifiers. + template + class stored { public: - missing_error (): - error ("missing argument for specifier") + stored (std::vector &&_specifiers, const ValueT &...args): + m_specifiers {std::move (_specifiers)}, + m_values {args...} { ; } + + auto + specifiers (void) const& + { + return util::make_view (m_specifiers); + } + + template + const auto& + get (void) const& { return std::get (m_values); } + + private: + std::vector m_specifiers; + std::tuple m_values; }; + + + template + bound + parsed::operator () (const Args &...args) & + { return bound { *this, args... }; } + + + template + stored + parsed::operator () (const Args &...args) && + { + return stored { std::move (m_specifiers), args... }; + } + + + /// parses a format string in the style of std::printf + /// + /// if the format specifier is invalid the function will throw an error at + /// runtime. specifically does not make allowances for constexpr + /// validation. + parsed printf (util::view); + + + /// parses a format specifier in the style of PEP3101 (with the notable + /// exception of named parameters). + /// + /// in the event of a parsing error the function will throw. makes no + /// attempt to cater for constexpr validation. + parsed python (util::view); + + + /// parses a printf format string and binds parameters for rendering. + template + auto + printf (util::view fmt, const Args &...args) + { + return printf (fmt) (args...); + } + + + /// parses a python format string and binds parameters for rendering. + template + auto + python (util::view fmt, const Args &...args) + { + return python (fmt) (args...); + } + + + template + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, const ValueT &val) + { + os << std::resetiosflags (~std::ios_base::fmtflags{}); + + switch (spec.type) { + case type_t::REAL: + if (!std::is_floating_point_v) + throw std::runtime_error ("expected real value"); + break; + + case type_t::UNSIGNED: + if (!std::is_unsigned_v) + throw std::runtime_error ("expected unsigned value"); + break; + + case type_t::SIGNED: + if (!std::is_signed_v) + throw std::runtime_error ("expected signed value"); + break; + + case type_t::STRING: + if (!std::is_same_v> && !std::is_same_v) + throw std::runtime_error ("expected string value"); + break; + + case type_t::POINTER: + if (!std::is_pointer_v && !std::is_integral_v) + throw std::runtime_error ("expected pointer value"); + break; + + case type_t::CHAR: + if (!std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v) + throw std::runtime_error ("expected character value"); + break; + + case type_t::COUNT: + if (!std::is_pointer_v && !std::is_reference_v) + if (!std::is_integral_v>>) + throw std::runtime_error ("expected pointer/reference to integral"); + break; + + case type_t::USER: + break; + + case type_t::ESCAPE: + case type_t::LITERAL: + break; + } + + // easy case where we just throw it to ostream + if (spec.type == type_t::USER) + return os << val; + + if (spec.length > 0 && sizeof (val) != spec.length) + throw std::runtime_error ("mismatched argument size"); + + const bool uses_space = std::is_arithmetic_v && spec.flags.space && !spec.flags.plus; + if (uses_space) + os << ' '; + + if (spec.flags.plus) + os << std::showpos; + if (spec.flags.minus) + os << std::left; + if (spec.flags.zero) + os << std::setfill ('0'); + + if (spec.base >= 0) { + switch (spec.base) { + case 10: os << std::dec; break; + case 16: os << std::hex; break; + case 8: os << std::oct; break; + default: + throw std::runtime_error ("unhandled numeric base"); + } + } + + if (spec.precision >= 0) { + os << std::setprecision (spec.precision); + } + + if (spec.width >= 0) + os << std::setw (spec.width - (uses_space ? 1 : 0)); + + if (spec.upper) + os << std::uppercase; + + if (spec.type == type_t::UNSIGNED || spec.type == type_t::SIGNED) + if (spec.flags.hash) + os << std::showbase; + if (spec.type == type_t::REAL) + if (spec.flags.hash) + os << std::showpoint; + + if (spec.type == type_t::REAL) { + switch (spec.representation) { + case specifier::FIXED: os << std::fixed; break; + case specifier::SCIENTIFIC: os << std::scientific; break; + case specifier::DEFAULT: os << std::defaultfloat; break; + case specifier::HEX: os << std::hexfloat; break; + } + } + + if constexpr (std::is_integral_v) { + if (spec.type == type_t::POINTER) { + if (!val) + return os << "(nil)"; + return os << reinterpret_cast (val); + } + } + + if constexpr (std::is_floating_point_v) { + if (spec.type == type_t::REAL) { + if (std::isnan (val)) + return os << (spec.upper ? "NAN" : "nan"); + if (std::isinf (val)) + return os << (spec.upper ? "INF" : "inf"); + } + } + + if constexpr (std::is_integral_v) { + if (spec.type == type_t::SIGNED || spec.type == type_t::UNSIGNED) { + // explicitly handle the zero width case as blank because + // there's no easy way to do this using iomanip. + if (spec.precision == 0 && !val) { + return os; + } + } + } + + if constexpr (std::is_same_v, ValueT>) { + if (spec.precision >= 0) { + std::copy_n ( + std::begin (val), + util::min (spec.precision, val.size ()), + std::ostream_iterator (os) + ); + return os; + } + } + + // the final output calls. we need to use unary plus so that + // chars get promoted to ints for correct stream rendering when + // the intention is to output a number. + if constexpr (std::is_fundamental_v) { + if (spec.type == type_t::CHAR) + return os << val; + if constexpr (!std::is_null_pointer_v) + if (spec.type != type_t::USER) + return os << +val; + } + + return os << val; + } + }; + + + template + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, const ValueT *val) + { + if (spec.type != type_t::POINTER && spec.type != type_t::USER) + throw std::runtime_error ("expected pointer specification"); + + if (!val) + return os << "(nil)"; + return os << reinterpret_cast (val); + } + }; + + + template + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, ValueT *val) { + return value::write (os, spec, val); + } + }; + + + template <> + struct value { + static std::ostream& + write (std::ostream &os, specifier s, const std::nullptr_t &val) + { + if (s.type != type_t::POINTER) + throw std::runtime_error ("expected pointer specifier"); + return value::write (os, s, val); + } + }; + + + template + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, const char (&val)[N]) { + if (spec.type == type_t::STRING) + return value>::write (os, spec, util::view (val)); + } + }; + + + template + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, const char (&val)[N]) { + return value>::write (os, spec, util::view (val)); + } + }; + + + template <> + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, char *val) { + if (!val) + return os << "(nil)"; + if (spec.type == type_t::STRING) + return value>::write (os, spec, util::view { val, val + strlen (val) }); + if (spec.type == type_t::POINTER) + return value::write (os, spec, val); + throw std::runtime_error ("invalid data type"); + } + }; + + + template <> + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, const char *val) { + if (!val) + return os << "(nil)"; + if (spec.type == type_t::STRING) + return value>::write (os, spec, util::view { val, val + strlen (val) }); + if (spec.type == type_t::POINTER) + return value::write (os, spec, val); + throw std::runtime_error ("invalid data type"); + } + }; + + + template <> + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, const std::string &val) { + return value>::write ( + os, spec, util::view (val.data (), val.data () + val.size ()) + ); + } + }; + + template <> + struct value { + static std::ostream& + write (std::ostream &os, specifier spec, std::string &val) { + return value::write (os, spec, val); + } + }; + + + /// renders an LITERAL specifiers followed by one parameter, then + /// recurses for any following specifiers. + /// + /// \tparam Index the index of the next parameter to render + /// \tparam SpecifierT a forward iterator container + /// \tparam DataT a tuple-like object template class + /// \tparam Args a paramater pack of all parameter types + /// + /// \param os the ostream that we render to + /// \param specifiers the sequence of all specifiers to be rendered + /// \param data a tuple-like object containing references to all parameters + template class HolderT, typename ...DataT> + static std::ostream& + write (std::ostream &os, const SpecifiersT &specifiers, const HolderT &data) + { + for (auto cursor = std::cbegin (specifiers); cursor != std::cend (specifiers); ++cursor) { + const auto &s = *cursor; + + if (s.type == type_t::LITERAL) { + std::copy (std::begin (s.fmt), std::end (s.fmt), std::ostream_iterator (os)); + continue; + } + + if (s.type == type_t::ESCAPE) { + os << '%'; + continue; + } + + if constexpr (Index < sizeof... (DataT)) { + using value_t = std::tuple_element_t>; + value::write (os, s, data.template get ()); + return write (os, util::make_view (cursor+1,specifiers.end ()), data); + } else { + throw std::runtime_error ("insufficient data parameters"); + } + } + + return os; + } + + /// dispatches rendering of formats with associated parameters + template < + typename ...Args + > + std::ostream& + operator<< (std::ostream &os, const bound &val) + { + return write<0> (os, val.specifiers (), val); + } + + + /// dispatches rendering of formats with associated parameters + template < + typename ...Args + > + std::ostream& + operator<< (std::ostream &os, const stored &val) + { + return write<0> (os, val.specifiers (), val); + } + + + template + std::string + to_string (const bound &fmt) + { + std::ostringstream os; + os << fmt; + return os.str (); + } + + template + std::string + to_string (const stored &fmt) + { + std::ostringstream os; + os << fmt; + return os.str (); + } } -#include "format.ipp" - #endif diff --git a/format.ipp b/format.ipp deleted file mode 100644 index 6bd06082..00000000 --- a/format.ipp +++ /dev/null @@ -1,949 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Copyright 2015-2016 Danny Robson - */ - -#include "./ascii.hpp" -#include "./debug.hpp" -#include "./maths.hpp" - -#include -#include -#include - - -namespace util::format::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)...); - } - - - /////////////////////////////////////////////////////////////////////////// - // 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; - - char padding_char = ' '; - char positive_char = '\0'; - - 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. - int base = 10; - - enum class repr { - FIXED, - SCIENTIFIC, - AUTO - } r = repr::AUTO; - - enum class kind { - UNSIGNED, - SIGNED, - REAL, - - STRING, - POINTER, - CHARACTER, - ESCAPE, - - OSTREAM - } k; - - int 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 - }; - - - /////////////////////////////////////////////////////////////////////////// - std::ostream& - operator<< (std::ostream &os, specifier::kind k); - - - /////////////////////////////////////////////////////////////////////////// - // 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: - case specifier::kind::OSTREAM: - return std::numeric_limits::max (); - - case specifier::kind::POINTER: - case specifier::kind::CHARACTER: - case specifier::kind::ESCAPE: - 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; - } - } - - - /////////////////////////////////////////////////////////////////////////// - // 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 - 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) - { - // we need to forward to the pointer write function rather than the - // other way around to reduce ambiguity and the potential for - // recursion. - if (spec.k == specifier::kind::POINTER) - return write (os, spec, reinterpret_cast (t)); - - if (spec.k != specifier::kind::STRING && spec.k != specifier::kind::OSTREAM) - 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 : - (int)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 unsigned char *t) - { - return write (os, spec, reinterpret_cast (t)); - } - - - //------------------------------------------------------------------------- - 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 spec, const char t) - { - if (spec.k != specifier::kind::CHARACTER && spec.k != specifier::kind::OSTREAM) - throw conversion_error (render ("invalid specifier kind for char argument: %!", spec.k)); - - *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 && !std::is_same, char>::value, - OutputT - > - write (OutputT &os, const specifier &spec, const T t) - { - if (spec.k != specifier::kind::POINTER && spec.k != specifier::kind::OSTREAM) - 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 == specifier::kind::UNSIGNED && std::is_unsigned::value || - spec.k == specifier::kind::SIGNED && std::is_signed ::value || - spec.k == specifier::kind::OSTREAM)) - { - throw conversion_error ("invalid conversion specifier for integer"); - } - - if (sizeof (ValueT) > spec.length && spec.k != specifier::kind::OSTREAM) - throw length_error ("overlength value parameter"); - - const auto numerals = digits (t, spec.base); - const auto characters = numerals + (spec.positive_char ? 1 : 0); - CHECK_NEZ (numerals); - - // 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]; - auto remain = numerals; - - for (auto cursor = buffer; remain--; 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, specifier spec, T t) - { - if (spec.k == specifier::kind::OSTREAM) { - spec = specifier {}; - spec.k = specifier::kind::REAL; - } - - 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; - - if (spec.width) - 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, const ValueT &val, const 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); - - if (spec.k == specifier::kind::ESCAPE) - return _render (write (os, spec ), cursor, last, val, args...); - else - return _render (write (os, spec, val), cursor, last, args...); - } - - - //------------------------------------------------------------------------ - template - OutputT - render (OutputT os, const char (&fmt)[N], const ValueT &val, const Args &...args) - { - if (N <= 1) - return os; - - const auto first = fmt; - const auto last = fmt + N - 1; - - return _render (os, first, last, val, 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::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/hash/xxhash.cpp b/hash/xxhash.cpp index 465cc640..a5096107 100644 --- a/hash/xxhash.cpp +++ b/hash/xxhash.cpp @@ -20,6 +20,8 @@ #include "./bitwise.hpp" #include "./endian.hpp" +#include + using util::hash::xxhash; diff --git a/io.cpp b/io.cpp index 218e3a10..e90059ce 100644 --- a/io.cpp +++ b/io.cpp @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2010-2017 Danny Robson + * Copyright 2010-2018 Danny Robson */ #include "io.hpp" @@ -19,6 +19,7 @@ #include "debug.hpp" #include "except.hpp" #include "cast.hpp" +#include "format.hpp" #include #include @@ -212,7 +213,7 @@ scoped_cwd::~scoped_cwd () /////////////////////////////////////////////////////////////////////////////// path_error::path_error (const std::experimental::filesystem::path &_path): - runtime_error (format::render ("Unknown path: %!", m_path)), + runtime_error (to_string (format::printf ("Unknown path: %!", m_path))), m_path (_path) { ; } diff --git a/json/except.cpp b/json/except.cpp index 757ba427..2eb0551f 100644 --- a/json/except.cpp +++ b/json/except.cpp @@ -37,6 +37,6 @@ json::parse_error::what (void) const noexcept /////////////////////////////////////////////////////////////////////////////// json::key_error::key_error (std::string _key): - error (util::format::render ("missing key '%s'", _key)), + error (to_string (util::format::printf ("missing key '%s'", _key))), key (_key) { ; } diff --git a/log.hpp b/log.hpp index da9da9e1..8868c919 100644 --- a/log.hpp +++ b/log.hpp @@ -11,15 +11,16 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2012 Danny Robson + * Copyright 2012-2018 Danny Robson */ -#ifndef __UTIL_LOG_HPP -#define __UTIL_LOG_HPP +#ifndef CRUFT_UTIL_LOG_HPP +#define CRUFT_UTIL_LOG_HPP -#include "./nocopy.hpp" +#include "nocopy.hpp" -#include "./preprocessor.hpp" +#include "preprocessor.hpp" +#include "format.hpp" #include #include @@ -81,7 +82,10 @@ namespace util { template void - log (level_t, const char (&fmt)[N], const Args&...); + log (level_t l, const char (&fmt)[N], const Args&...args) + { + log (l, to_string (format::printf (fmt) (args...))); + } //------------------------------------------------------------------------- diff --git a/log.ipp b/log.ipp index 6c3cbb16..2027df36 100644 --- a/log.ipp +++ b/log.ipp @@ -13,22 +13,3 @@ * * Copyright 2012-2016 Danny Robson */ - -#ifdef __UTIL_LOG_IPP -#error -#endif - -#define __UTIL_LOG_IPP - -#include "format.hpp" - -//----------------------------------------------------------------------------- -namespace util { - template - void - log (level_t l, const char (&fmt)[N], const Args& ...args) - { - log (l, format::render (fmt, args...)); - } -} - diff --git a/tap.hpp b/tap.hpp index 3e8ec2fd..96e1dc17 100644 --- a/tap.hpp +++ b/tap.hpp @@ -47,7 +47,7 @@ namespace util::TAP { { m_output << (test ? "ok " : "not ok ") << ++m_size << " - " - << util::format::render (fmt, std::forward (args)...) << '\n'; + << format::printf (fmt) (std::forward (args)...) << '\n'; if (!test) m_status = EXIT_FAILURE; diff --git a/test/alloc/arena.cpp b/test/alloc/arena.cpp index 6ea41e32..732429a8 100644 --- a/test/alloc/arena.cpp +++ b/test/alloc/arena.cpp @@ -1,6 +1,8 @@ #include "tap.hpp" + #include "alloc/arena.hpp" #include "alloc/raw/linear.hpp" +#include "debug.hpp" /////////////////////////////////////////////////////////////////////////////// diff --git a/test/crypto/arc4.cpp b/test/crypto/arc4.cpp index 7bf53689..2d122cda 100644 --- a/test/crypto/arc4.cpp +++ b/test/crypto/arc4.cpp @@ -1,4 +1,6 @@ #include "crypto/arc4.hpp" + +#include "debug.hpp" #include "tap.hpp" #include "types.hpp" diff --git a/test/crypto/xxtea.cpp b/test/crypto/xxtea.cpp index a37827bc..85f09ff6 100644 --- a/test/crypto/xxtea.cpp +++ b/test/crypto/xxtea.cpp @@ -1,5 +1,6 @@ #include "crypto/xxtea.hpp" +#include "debug.hpp" #include "tap.hpp" #include "types.hpp" diff --git a/test/fixed.cpp b/test/fixed.cpp index 7d14e0ad..dc4b720c 100644 --- a/test/fixed.cpp +++ b/test/fixed.cpp @@ -3,6 +3,8 @@ #include "tap.hpp" +#include + /////////////////////////////////////////////////////////////////////////////// template diff --git a/test/format.cpp b/test/format.cpp index f7757817..be82834c 100644 --- a/test/format.cpp +++ b/test/format.cpp @@ -2,6 +2,7 @@ #include "tap.hpp" +#include /////////////////////////////////////////////////////////////////////////////// struct userobj { }; @@ -19,9 +20,13 @@ main (void) { util::TAP::logger tap; - #define CHECK_RENDER(fmt,res,...) do { \ - auto val = util::format::render (fmt, ##__VA_ARGS__); \ - tap.expect_eq (val, res, "render '%s'", fmt); \ + #define CHECK_RENDER(fmt,res,...) do { \ + auto val = to_string (util::format::printf (fmt)(__VA_ARGS__)); \ + if (val != res) { \ + std::clog << "got: '" << val << "'\n"; \ + std::clog << "expected: '" << res << "'\n"; \ + } \ + tap.expect_eq (val, res, "render '%s'", fmt); \ } while (0) CHECK_RENDER ("foo", "foo"); @@ -170,12 +175,12 @@ main (void) 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"); + tap.expect_eq (to_string (util::format::printf ("%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); \ + #define CHECK_THROW(fmt,except,...) do { \ + tap.expect_throw ([&] { \ + to_string (util::format::printf (fmt)(__VA_ARGS__)); \ + }, "exception '%s' for format '%s'", #except, fmt); \ } while (0) CHECK_THROW("%", syntax_error); @@ -190,7 +195,7 @@ main (void) CHECK_THROW("%i", conversion_error, nullptr); CHECK_THROW("%hhi", length_error, (long long)1); - //CHECK_THROW("%lli", length_error, (signed char)1); + CHECK_THROW("%lli", length_error, (signed char)1); CHECK_THROW("%u", conversion_error, 1.); CHECK_THROW("%u", conversion_error, "foo"); @@ -199,7 +204,7 @@ main (void) 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("%llu", length_error, (unsigned char)1); CHECK_THROW("%f", conversion_error, 1u); CHECK_THROW("%f", conversion_error, "foo"); diff --git a/test/stream.cpp b/test/stream.cpp index 320ef3ff..b4164875 100644 --- a/test/stream.cpp +++ b/test/stream.cpp @@ -2,6 +2,8 @@ #include "stream.hpp" +#include + /////////////////////////////////////////////////////////////////////////////// template diff --git a/view.hpp b/view.hpp index 285edaea..6d69552d 100644 --- a/view.hpp +++ b/view.hpp @@ -18,9 +18,9 @@ #ifndef CRUFT_UTIL_VIEW_HPP #define CRUFT_UTIL_VIEW_HPP -#include "./debug.hpp" #include "./types/traits.hpp" +#include #include #include #include @@ -46,11 +46,8 @@ namespace util { //--------------------------------------------------------------------- - constexpr - view (const view &rhs) noexcept: - m_begin (rhs.m_begin), - m_end (rhs.m_end) - { ; } + constexpr view (const view &rhs) noexcept = default; + view& operator= (const view &rhs) noexcept = default; //--------------------------------------------------------------------- @@ -65,6 +62,18 @@ namespace util { std::swap (m_end, rhs.m_end); } + template + constexpr view (const char (&str)[N]): + m_begin (std::begin (str)), + m_end (std::begin (str) + N - 1) + { ; } + + + explicit constexpr view (std::nullptr_t): + m_begin (nullptr), + m_end (nullptr) + { ; } + //--------------------------------------------------------------------- template @@ -84,16 +93,6 @@ namespace util { { ; } - //--------------------------------------------------------------------- - view& - operator= (const view &rhs) noexcept - { - m_begin = rhs.m_begin; - m_end = rhs.m_end; - return *this; - } - - //--------------------------------------------------------------------- view& operator= (view &&rhs) noexcept @@ -142,7 +141,7 @@ namespace util { constexpr auto redim (int count) const { - CHECK_GT (count, 0); + assert (count > 0); if (count > size ()) throw std::invalid_argument ("redim to higher size not allowed"); return view { m_begin, m_begin + count };