format: remove in favour of libfmt

This commit is contained in:
Danny Robson 2021-04-14 10:23:33 +10:00
parent a94cd677bd
commit 462776dafa
19 changed files with 76 additions and 1102 deletions

View File

@ -29,7 +29,6 @@ endif()
############################################################################### ###############################################################################
RAGEL_TARGET(uri uri.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/uri.cpp COMPILE_FLAGS -G2) RAGEL_TARGET(uri uri.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/uri.cpp COMPILE_FLAGS -G2)
RAGEL_TARGET(version version.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/version.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)
RAGEL_TARGET(parse8601 time/parse8601.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/time/parse8601.cpp) RAGEL_TARGET(parse8601 time/parse8601.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/time/parse8601.cpp)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR})
@ -337,8 +336,6 @@ list (
fixed_string.hpp fixed_string.hpp
float.cpp float.cpp
float.hpp float.hpp
${CMAKE_CURRENT_BINARY_DIR}/format.cpp
format.hpp
fourcc.cpp fourcc.cpp
fourcc.hpp fourcc.hpp
functor.hpp functor.hpp
@ -703,7 +700,6 @@ if (TESTS)
extent extent
fixed fixed
float float
format
geom/aabb geom/aabb
geom/ellipse geom/ellipse
geom/frustum geom/frustum

View File

@ -10,9 +10,10 @@
#include "endian.hpp" #include "endian.hpp"
#include "view.hpp" #include "view.hpp"
#include "format.hpp"
#include "bitwise.hpp" #include "bitwise.hpp"
#include <fmt/ostream.h>
#include <cstdint> #include <cstdint>
#include <ostream> #include <ostream>
@ -158,10 +159,11 @@ cruft::cpu::operator<< (std::ostream &os, const x86 &val)
}; };
}; };
return os << cruft::format::printf ( fmt::print (
"{ name: { vendor: '%!', product: '%!' }" os,
", cores: { logical: %!, physical: %!, hyper_threading: %! }" "{ name: { vendor: '{}', product: '{}' }"
", simd: { sse: %!, sse2: %!, sse3: %!, ssse3: %!, sse41: %!, sse42: %!, avx: %! }" ", cores: { logical: {}, physical: {}, hyper_threading: {} }"
", simd: { sse: {}, sse2: {}, sse3: {}, ssse3: {}, sse41: {}, sse42: {}, avx: {} }"
" }", " }",
to_string (val.vendor_name), to_string (val.vendor_name),
to_string (val.product_name), to_string (val.product_name),
@ -176,4 +178,6 @@ cruft::cpu::operator<< (std::ostream &os, const x86 &val)
(val.simd.sse42 ? "true" : "false"), (val.simd.sse42 ? "true" : "false"),
(val.simd.avx ? "true" : "false") (val.simd.avx ? "true" : "false")
); );
return os;
} }

View File

@ -8,9 +8,10 @@
#include "./system.hpp" #include "./system.hpp"
#include "debugger.hpp" #include "./assert.hpp"
#include "except.hpp" #include "./debugger.hpp"
#include "crash.hpp" #include "./except.hpp"
#include "./crash.hpp"
#include "../backtrace.hpp" #include "../backtrace.hpp"
#include "../log.hpp" #include "../log.hpp"
@ -44,14 +45,14 @@ static void abort_with_trace (void)
try { try {
std::rethrow_exception (ptr); std::rethrow_exception (ptr);
} catch (std::exception const &x) { } catch (std::exception const &x) {
LOG_EMERGENCY ("unhandled exception: %!\n%!", x.what (), ::cruft::backtrace {}); LOG_EMERGENCY ("unhandled exception: {}\n{}", x.what (), ::cruft::backtrace {});
} catch (cruft::error const &x) { } catch (cruft::error const &x) {
LOG_EMERGENCY ("unhandled exception: %!\n%!", x, ::cruft::backtrace {}); LOG_EMERGENCY ("unhandled exception: {}\n{}", x, ::cruft::backtrace {});
} catch (...) { } catch (...) {
LOG_EMERGENCY ("unhandled exception\n%!", ::cruft::backtrace {}); LOG_EMERGENCY ("unhandled exception\n{}", ::cruft::backtrace {});
} }
} else { } else {
LOG_EMERGENCY ("aborting: %!", ::cruft::backtrace {}); LOG_EMERGENCY ("aborting: {}", ::cruft::backtrace {});
} }
old_handler (); old_handler ();

View File

@ -31,6 +31,6 @@ warn (const std::string &msg)
void void
warn (const char *msg) warn (const char *msg)
{ {
LOG_WARN (msg); LOG_WARN ("{}", msg);
} }

View File

@ -44,7 +44,7 @@ prepare_debugger (void)
// as possible. // as possible.
if (nullptr == LoadLibrary("exchndl.dll")) { if (nullptr == LoadLibrary("exchndl.dll")) {
auto code = GetLastError (); auto code = GetLastError ();
LOG_WARNING("Emergency debugger not loaded, %s", cruft::win32::error::code_string (code)); LOG_WARNING("Emergency debugger not loaded, {:s}", cruft::win32::error::code_string (code));
} }
} }

View File

@ -1,218 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#include "./format.hpp"
#include <iostream>
// We generate some really old style C code via ragel here, so we have to
// disable some noisy warnings (doubly so given -Werror)
#pragma GCC diagnostic ignored "-Wold-style-cast"
namespace cruft::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<std::underlying_type_t<type_t>> (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;
}%%
///////////////////////////////////////////////////////////////////////////////
cruft::format::parsed
cruft::format::printf (cruft::view<const char*> fmt)
{
std::vector<specifier> 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.
cruft::format::parsed
cruft::format::python (cruft::view<const char*> fmt)
{
std::vector<specifier> 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)};
}

View File

@ -1,599 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_FORMAT_HPP
#define CRUFT_UTIL_FORMAT_HPP
#include "view.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iomanip>
#include <iterator>
#include <sstream>
#include <vector>
namespace cruft::format {
/// 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,
/// a literal '%' symbol
ESCAPE,
/// numeric types
SIGNED,
UNSIGNED,
REAL,
/// a C style string, or equivalent C++ type (std::string,
/// std::string_view, cruft::view, etc, ...)
STRING,
/// a single character
CHAR,
/// a raw pointer (rather than value that needs to be dereferenced)
POINTER,
/// number of characters written
COUNT
};
/// 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.
view<const char*> fmt = view<const char*> {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 <typename ...Args> class bound;
template <typename ...Args> class stored;
/// a sequence of parsed specifiers that can be used to render some
/// collection parameters in the future.
struct parsed {
std::vector<specifier> 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 <typename ...Args>
bound<Args...>
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 <typename ...Args>
stored<Args...>
operator() (const Args &...args) &&;
};
template <typename... ValueT>
class bound;
template <typename ...Args>
std::string
to_string (const bound<Args...>&);
template <typename... ValueT>
class stored;
template <typename ...Args>
std::string
to_string (const bound<Args...>&);
/// parameter collection for a non-owning sequence of specifiers
template <typename ...ValueT>
class bound {
public:
bound (const parsed &_parsed, const ValueT &...args):
m_parsed {_parsed},
m_values {args...}
{ ; }
auto specifiers (void) const
{ return view (m_parsed.m_specifiers); }
template <size_t Index>
auto
get (void) const& { return std::get<Index> (m_values); }
operator ::std::string () const
{
return to_string (*this);
}
private:
const parsed &m_parsed;
std::tuple<const ValueT&...> m_values;
};
/// parameter collection for an owning squence of specifiers.
template <typename ...ValueT>
class stored {
public:
stored (std::vector<specifier> &&_specifiers, const ValueT &...args):
m_specifiers {std::move (_specifiers)},
m_values {args...}
{ ; }
auto
specifiers (void) const&
{
return view {m_specifiers};
}
template <size_t Index>
const auto&
get (void) const& { return std::get<Index> (m_values); }
operator ::std::string () const
{
return to_string (*this);
}
private:
std::vector<specifier> m_specifiers;
std::tuple<const ValueT&...> m_values;
};
template <typename ...Args>
bound<Args...>
parsed::operator() (const Args &...args) &
{ return bound { *this, args... }; }
template <typename ...Args>
stored<Args...>
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 (view<const char*>);
/// 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 (view<const char*>);
/// parses a printf format string and binds parameters for rendering.
template <typename ...Args>
auto
printf (view<const char*> fmt, Args &&...args)
{
return printf (fmt) (args...);
}
/// parses a python format string and binds parameters for rendering.
template <typename ...Args>
auto
python (view<const char*> fmt, Args &&...args)
{
return python (fmt) (args...);
}
template <typename ValueT>
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<ValueT>)
throw std::runtime_error ("expected real value");
break;
case type_t::UNSIGNED:
if (!std::is_unsigned_v<ValueT>)
throw std::runtime_error ("expected unsigned value");
break;
case type_t::SIGNED:
if (!std::is_signed_v<ValueT>)
throw std::runtime_error ("expected signed value");
break;
case type_t::STRING:
if (std::is_same_v<ValueT, view<const char*>>)
break;
if (std::is_same_v<ValueT, std::string>)
break;
if (std::is_same_v<ValueT, std::string_view>)
break;
throw std::runtime_error ("expected string value");
case type_t::POINTER:
if (!std::is_pointer_v<ValueT> && !std::is_integral_v<ValueT>)
throw std::runtime_error ("expected pointer value");
break;
case type_t::CHAR:
if (!std::is_same_v<ValueT, char> &&
!std::is_same_v<ValueT, wchar_t> &&
!std::is_same_v<ValueT, char16_t> &&
!std::is_same_v<ValueT, char32_t> &&
!std::is_same_v<ValueT, signed char> &&
!std::is_same_v<ValueT, unsigned char>)
throw std::runtime_error ("expected character value");
break;
case type_t::COUNT:
if (!std::is_pointer_v<ValueT> && !std::is_reference_v<ValueT>)
if (!std::is_integral_v<std::remove_pointer_t<std::remove_reference_t<ValueT>>>)
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<ValueT> && 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<ValueT>) {
if (spec.type == type_t::POINTER) {
if (!val)
return os << "(nil)";
return os << reinterpret_cast<const void*> (val);
}
}
if constexpr (std::is_floating_point_v<ValueT>) {
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<ValueT>) {
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<view<const char*>, ValueT>) {
if (spec.precision >= 0) {
std::copy_n (
std::begin (val),
min (spec.precision, static_cast<int> (val.size ())),
std::ostream_iterator<char> (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<ValueT>) {
if (spec.type == type_t::CHAR)
return os << val;
if constexpr (!std::is_null_pointer_v<ValueT>)
if (spec.type != type_t::USER)
return os << +val;
}
return os << val;
}
};
template <typename ValueT>
struct value<const ValueT*> {
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<const void*> (val);
}
};
template <typename ValueT>
struct value<ValueT*> {
static std::ostream&
write (std::ostream &os, specifier spec, ValueT *val) {
return value<const ValueT*>::write (os, spec, val);
}
};
template <>
struct value<std::nullptr_t> {
static std::ostream&
write (std::ostream &os, specifier s, const std::nullptr_t &val)
{
if (s.type != type_t::POINTER || s.type == type_t::USER)
throw std::runtime_error ("expected pointer specifier");
return value<const void*>::write (os, s, val);
}
};
template <size_t N>
struct value<const char[N]> {
static std::ostream&
write (std::ostream &os, specifier spec, const char (&val)[N]) {
if (spec.type == type_t::STRING || spec.type == type_t::USER)
return value<view<const char*>>::write (os, spec, view<const char*> (val));
throw std::runtime_error ("invalid data type");
}
};
template <size_t N>
struct value<char[N]> {
static std::ostream&
write (std::ostream &os, specifier spec, const char (&val)[N]) {
return value<view<const char*>>::write (os, spec, view<const char*> (val));
}
};
template <>
struct value<char*> {
static std::ostream&
write (std::ostream &os, specifier spec, char *val) {
if (!val)
return os << "(nil)";
if (spec.type == type_t::STRING || spec.type == type_t::USER)
return value<view<const char*>>::write (os, spec, view<const char*> { val, val + strlen (val) });
if (spec.type == type_t::POINTER)
return value<const void*>::write (os, spec, val);
throw std::runtime_error ("invalid data type");
}
};
template <>
struct value<const char*> {
static std::ostream&
write (std::ostream &os, specifier spec, const char *val) {
if (!val)
return os << "(nil)";
if (spec.type == type_t::STRING || spec.type == type_t::USER)
return value<view<const char*>>::write (os, spec, view<const char*> { val, val + strlen (val) });
if (spec.type == type_t::POINTER)
return value<const void*>::write (os, spec, val);
throw std::runtime_error ("invalid data type");
}
};
template <>
struct value<const std::string&> {
static std::ostream&
write (std::ostream &os, specifier spec, const std::string &val) {
return value<view<const char*>>::write (
os, spec, view<const char*> (val.data (), val.data () + val.size ())
);
}
};
template <>
struct value<std::string&> {
static std::ostream&
write (std::ostream &os, specifier spec, std::string &val) {
return value<const std::string&>::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 <int Index, typename SpecifiersT, template <typename...> class HolderT, typename ...DataT>
static std::ostream&
write (std::ostream &os, const SpecifiersT &specifiers, const HolderT<DataT...> &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<char> (os));
continue;
}
if (s.type == type_t::ESCAPE) {
os << '%';
continue;
}
if constexpr (Index < sizeof... (DataT)) {
using value_t = std::tuple_element_t<Index,std::tuple<DataT...>>;
value<value_t>::write (os, s, data.template get<Index> ());
return write<Index+1> (os, 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<Args...> &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<Args...> &val)
{
return write<0> (os, val.specifiers (), val);
}
template <typename ...Args>
std::string
to_string (const bound<Args...> &fmt)
{
std::ostringstream os;
os << fmt;
return os.str ();
}
template <typename ...Args>
std::string
to_string (const stored<Args...> &fmt)
{
std::ostringstream os;
os << fmt;
return os.str ();
}
}
#endif

5
io.cpp
View File

@ -10,9 +10,10 @@
#include "debug/assert.hpp" #include "debug/assert.hpp"
#include "cast.hpp" #include "cast.hpp"
#include "format.hpp"
#include "posix/except.hpp" #include "posix/except.hpp"
#include <fmt/ostream.h>
#include <cstdio> #include <cstdio>
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -232,7 +233,7 @@ scoped_cwd::~scoped_cwd ()
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
path_error::path_error (std::filesystem::path const &_path): path_error::path_error (std::filesystem::path const &_path):
runtime_error (to_string (format::printf ("Unknown path: %!", _path))), runtime_error (fmt::format ("Unknown path: {}", _path)),
m_path (_path) m_path (_path)
{ ; } { ; }

View File

@ -11,7 +11,6 @@
#include "level.hpp" #include "level.hpp"
#include "packet.hpp" #include "packet.hpp"
#include "sink/base.hpp" #include "sink/base.hpp"
#include "../format.hpp"
#include <memory> #include <memory>
#include <string> #include <string>
@ -42,26 +41,41 @@ namespace cruft::log {
) )
); );
} }
//-------------------------------------------------------------------------
// Various convenience macros for logging specific strings with a well
// known severity.
//
// LOG_DEBUG is treated similarly to assert; if NDEBUG is defined then we
// compile out the statement so as to gain a little runtime efficiency
// speed.
#define LOG_EMERGENCY(...) do { cruft::log::write (cruft::log::EMERGENCY, ##__VA_ARGS__); } while (0)
#define LOG_ALERT(...) do { cruft::log::write (cruft::log::ALERT, ##__VA_ARGS__); } while (0)
#define LOG_CRITICAL(...) do { cruft::log::write (cruft::log::CRITICAL, ##__VA_ARGS__); } while (0)
#define LOG_ERROR(...) do { cruft::log::write (cruft::log::ERROR, ##__VA_ARGS__); } while (0)
#define LOG_WARNING(...) do { cruft::log::write (cruft::log::WARNING, ##__VA_ARGS__); } while (0)
#define LOG_WARN(...) do { cruft::log::write (cruft::log::WARN, ##__VA_ARGS__); } while (0)
#define LOG_NOTICE(...) do { cruft::log::write (cruft::log::NOTICE, ##__VA_ARGS__); } while (0)
#define LOG_INFO(...) do { cruft::log::write (cruft::log::INFO, ##__VA_ARGS__); } while (0)
#if !defined(NDEBUG)
#define LOG_DEBUG(...) do { cruft::log::write (cruft::log::DEBUG, ##__VA_ARGS__); } while (0)
#else
#define LOG_DEBUG(...) do { ; } while (0)
#endif
} }
//-------------------------------------------------------------------------
// Various convenience macros for logging specific strings with a well
// known severity.
//
// The format string _must_ be a compile time literal so that compile time
// checking of strings and arguments is possible.
//
// LOG_DEBUG is treated similarly to assert; if NDEBUG is defined then we
// compile out the statement so as to gain a little runtime efficiency.
#define LOG(LEVEL, FMT, ...) \
do { \
::cruft::log::write ( \
(LEVEL), \
FMT_STRING(FMT) \
__VA_OPT__(,) \
__VA_ARGS__ \
); \
} while (0)
#define LOG_EMERGENCY(FMT, ...) LOG(::cruft::log::EMERGENCY, FMT __VA_OPT__(,) __VA_ARGS__)
#define LOG_ALERT(FMT, ...) LOG(::cruft::log::ALERT, FMT __VA_OPT__(,) __VA_ARGS__)
#define LOG_CRITICAL(FMT, ...) LOG(::cruft::log::CRITICAL, FMT __VA_OPT__(,) __VA_ARGS__)
#define LOG_ERROR(FMT, ...) LOG(::cruft::log::ERROR, FMT __VA_OPT__(,) __VA_ARGS__)
#define LOG_WARNING(FMT, ...) LOG(::cruft::log::WARNING, FMT __VA_OPT__(,) __VA_ARGS__)
#define LOG_NOTICE(FMT, ...) LOG(::cruft::log::NOTICE, FMT __VA_OPT__(,) __VA_ARGS__)
#define LOG_INFO(FMT, ...) LOG(::cruft::log::INFO, FMT __VA_OPT__(,) __VA_ARGS__)
#if !defined(NDEBUG)
#define LOG_DEBUG(FMT, ...) LOG(::cruft::log::DEBUG, FMT __VA_OPT__(,) __VA_ARGS__)
#else
#define LOG_DEBUG(...) do { ; } while (0)
#endif
#define LOG_WARN(...) LOG_WARNING(__VA_ARGS__)

View File

@ -1,7 +1,8 @@
#pragma once #pragma once
#include "level.hpp" #include "level.hpp"
#include "../format.hpp"
#include <fmt/ostream.h>
#include <chrono> #include <chrono>
@ -26,12 +27,9 @@ namespace cruft::log {
ArgsT &&..._args ArgsT &&..._args
) : packet ( ) : packet (
_level, _level,
cruft::format::to_string ( fmt::format (
cruft::format::printf ( std::forward<FormatT> (_format),
std::forward<FormatT> (_format) std::forward<ArgsT> (_args)...
) (
std::forward<ArgsT> (_args)...
)
) )
) )
{ ; } { ; }

View File

@ -50,7 +50,7 @@ cruft::log::scoped_timer::~scoped_timer ()
write ( write (
m_level, m_level,
"%fs, %s", "{:f}s, {:s}",
float (duration) / 1'000'000'000.f, float (duration) / 1'000'000'000.f,
m_message m_message
); );

View File

@ -12,6 +12,7 @@
#include "../packet.hpp" #include "../packet.hpp"
#include "../../paths.hpp" #include "../../paths.hpp"
#include "../../debug/warn.hpp" #include "../../debug/warn.hpp"
#include "../../cast.hpp"
using cruft::log::sink::path; using cruft::log::sink::path;

View File

@ -184,7 +184,7 @@ namespace cruft::parse::enumeration {
if (!success) if (!success)
LOG_WARN ( LOG_WARN (
"duplicate parse setup for %! was ignored", "duplicate parse setup for {:s} was ignored",
cruft::introspection::name::bare<EnumT> () cruft::introspection::name::bare<EnumT> ()
); );

View File

@ -59,14 +59,14 @@ namespace cruft {
if (success) { if (success) {
LOG_INFO ( LOG_INFO (
"Registered %! for %!", "Registered {} for {}",
key, key,
cruft::introspection::name::full<FactoryT> () cruft::introspection::name::full<FactoryT> ()
); );
return cookie { key }; return cookie { key };
} { } {
LOG_ERROR ( LOG_ERROR (
"Unable to register %! for %!", "Unable to register {} for {}",
key, key,
cruft::introspection::name::full<FactoryT> () cruft::introspection::name::full<FactoryT> ()
); );

View File

@ -52,7 +52,6 @@ namespace cruft::TAP {
bool bool
expect (const bool test, const char (&fmt)[N], Args&&... args) expect (const bool test, const char (&fmt)[N], Args&&... args)
{ {
CHECK (!strstr (fmt, "%"));
m_output << (test ? "ok " : "not ok ") << ++m_size m_output << (test ? "ok " : "not ok ") << ++m_size
<< " - "; << " - ";
fmt::print (m_output, fmt, std::forward<Args> (args)...); fmt::print (m_output, fmt, std::forward<Args> (args)...);
@ -112,7 +111,7 @@ namespace cruft::TAP {
if (almost_equal (a, b)) if (almost_equal (a, b))
return expect (true, fmt, std::forward<Args> (args)...); return expect (true, fmt, std::forward<Args> (args)...);
else else
return expect (false, "%! # %! != %!", format::printf (fmt)(std::forward<Args> (args)...), a, b); return expect (false, "{} # {} != {}", format::printf (fmt)(std::forward<Args> (args)...), a, b);
#endif #endif
} }
@ -244,9 +243,9 @@ namespace cruft::TAP {
function (tap, args...); function (tap, args...);
return tap.status (); return tap.status ();
} catch (std::exception const &err) { } catch (std::exception const &err) {
tap.fail ("no exceptions: %s", err.what ()); tap.fail ("no exceptions: {:s}", err.what ());
} catch (cruft::error const &err) { } catch (cruft::error const &err) {
tap.fail ("no exceptions: %s", err); tap.fail ("no exceptions: {:s}", err);
} catch (...) { } catch (...) {
tap.fail ("no exceptions"); tap.fail ("no exceptions");
} }

View File

@ -1,223 +0,0 @@
#include "format.hpp"
#include "tap.hpp"
///////////////////////////////////////////////////////////////////////////////
struct userobj { };
static std::ostream&
operator<< (std::ostream &os, const userobj&)
{
return os << "userobj";
}
///////////////////////////////////////////////////////////////////////////////
int
main (void)
{
cruft::TAP::logger tap;
#define CHECK_RENDER(fmt,res,...) do { \
auto val = to_string (cruft::format::printf (fmt)(__VA_ARGS__)); \
tap.expect_eq (val, res, "render '{}', # {} == {}", fmt, val, res); \
} while (0)
CHECK_RENDER ("foo", "foo");
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
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});
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", "0", size_t{0});
CHECK_RENDER ("%zu", "1", size_t{1});
CHECK_RENDER ("%!", "1", 1u);
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);
#if !defined(PLATFORM_WIN32)
// msys2#xxx: hexfloat output is broken under msys2
CHECK_RENDER ("%+a", "+0x1.3c0ca2a5b1d5dp+0", +0x1.3c0ca2a5b1d5dp+0);
#endif
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);
#if !defined(PLATFORM_WIN32)
// msys2#xxx: hexfloat output is broken under msys2
CHECK_RENDER ("%a", "0x1.3c0ca2a5b1d5dp+0", 0x1.3c0ca2a5b1d5dp+0);
CHECK_RENDER ("%A", "0X1.3C0CA2A5B1D5DP+0", 0X1.3C0CA2A5B1D5DP+0);
#endif
CHECK_RENDER ("%e", "inf", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%E", "INF", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%f", "inf", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%F", "INF", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%g", "inf", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%G", "INF", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%a", "inf", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%A", "INF", std::numeric_limits<double>::infinity ());
CHECK_RENDER ("%e", "nan", std::numeric_limits<double>::quiet_NaN ());
CHECK_RENDER ("%E", "NAN", std::numeric_limits<double>::quiet_NaN ());
CHECK_RENDER ("%f", "nan", std::numeric_limits<double>::quiet_NaN ());
CHECK_RENDER ("%F", "NAN", std::numeric_limits<double>::quiet_NaN ());
CHECK_RENDER ("%g", "nan", std::numeric_limits<double>::quiet_NaN ());
CHECK_RENDER ("%G", "NAN", std::numeric_limits<double>::quiet_NaN ());
CHECK_RENDER ("%a", "nan", std::numeric_limits<double>::quiet_NaN ());
CHECK_RENDER ("%A", "NAN", std::numeric_limits<double>::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 ("%!", "1", 1.);
CHECK_RENDER ("%c", "A", 'A');
CHECK_RENDER ("%!", "A", 'A');
CHECK_RENDER ("%s", "foo", "foo");
CHECK_RENDER ("%s", "foo", std::string ("foo"));
CHECK_RENDER ("%s", "foo", const_cast<char*> ("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 ("%!", "foo", "foo");
CHECK_RENDER ("%!", "userobj", userobj {});
CHECK_RENDER ("%p", "0x1234567", reinterpret_cast<void*>(0x01234567));
CHECK_RENDER ("%p", "0x1234567", reinterpret_cast<int*> (0x01234567));
CHECK_RENDER ("%p", "0x1234567", reinterpret_cast<char*>(0x01234567));
CHECK_RENDER ("%p", "(nil)", nullptr);
CHECK_RENDER ("%p", "(nil)", NULL);
CHECK_RENDER ("%!", "0x1234567", reinterpret_cast<void*>(0x01234567));
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 ("%%%d%%", "%0%", 0);
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 (to_string (cruft::format::printf ("%u\n")(1u)), "1\n", "newline");
#define CHECK_THROW(fmt,except,...) do { \
tap.expect_throw<std::exception> ([&] { \
to_string (cruft::format::printf (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");
return tap.status ();
}

View File

@ -24,7 +24,7 @@ test_good (cruft::TAP::logger &tap)
}; };
for (const auto &i: TESTS) for (const auto &i: TESTS)
tap.expect_eq (ipv4::ip::parse (i.str), i.ip, "%s", i.msg); tap.expect_eq (ipv4::ip::parse (i.str), i.ip, "{:s}", i.msg);
} }
@ -43,7 +43,7 @@ test_bad (cruft::TAP::logger &tap)
}; };
for (const auto &i: TESTS) for (const auto &i: TESTS)
tap.expect_throw<ipv4::error> ([&] { ipv4::ip::parse (i.str); }, "%s", i.msg); tap.expect_throw<ipv4::error> ([&] { ipv4::ip::parse (i.str); }, "{:s}", i.msg);
} }

View File

@ -23,7 +23,7 @@ int main ()
for (auto const &t: TESTS) { for (auto const &t: TESTS) {
auto res = cruft::parse::si<std::size_t> (t.str); auto res = cruft::parse::si<std::size_t> (t.str);
if (!res) { if (!res) {
tap.fail ("SI parsing %!", t.str); tap.fail ("SI parsing {}", t.str);
} else { } else {
tap.expect_eq (t.val, *res, "SI parsing '{}'", t.str); tap.expect_eq (t.val, *res, "SI parsing '{}'", t.str);
} }

View File

@ -115,7 +115,7 @@ cruft::polled_duration::stop (void) {
m_series.add (dt / MILLISECOND); m_series.add (dt / MILLISECOND);
if (m_next < now) { if (m_next < now) {
LOG_DEBUG ("timing: '%s'. %s", m_name, m_series); LOG_DEBUG ("timing: '{:s}'. {:s}", m_name, m_series);
m_series.reset (); m_series.reset ();
m_next = now + m_interval; m_next = now + m_interval;
} }