format: rework parser for currying support

This commit is contained in:
Danny Robson 2018-01-09 16:28:25 +11:00
parent 3ad0339474
commit 2713da45f4
19 changed files with 824 additions and 1126 deletions

View File

@ -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

View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
* Copyright 2015-2018, Danny Robson <danny@nerdcruft.net>
*/
#ifdef __UTIL_DEBUG_IPP
@ -20,20 +20,23 @@
#define __UTIL_DEBUG_IPP
#include "./format.hpp"
#include "backtrace.hpp"
#include "format.hpp"
#include <limits>
#include <iostream>
///////////////////////////////////////////////////////////////////////////////
namespace util::debug::detail {
void panic [[noreturn]] (const char *msg);
template <typename ...Args, size_t N>
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);

View File

@ -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 <danny@nerdcruft.net>
*/
#include "format.hpp"
#include <utility>
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 <<
" }";
}
}

222
format.cpp.rl Normal file
View File

@ -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 <danny@nerdcruft.net>
*/
#include "./format.hpp"
#include <iostream>
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<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;
}%%
///////////////////////////////////////////////////////////////////////////////
util::format::parsed
util::format::printf (util::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.
util::format::parsed
util::format::python (util::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

@ -11,66 +11,562 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2016 Danny Robson <danny@nerdcruft.net>
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_FORMAT_HPP
#define __UTIL_FORMAT_HPP
#ifndef CRUFT_UTIL_FORMAT_HPP
#define CRUFT_UTIL_FORMAT_HPP
#include <stdexcept>
#include <string>
#include "maths.hpp"
#include "view.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iomanip>
#include <iterator>
#include <sstream>
#include <vector>
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 <typename ...Args, size_t N>
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 <typename ValueT>
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<const char*> fmt = util::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) &&;
};
/// parameter collection for a non-owning sequence of specifiers
template <typename ...ValueT>
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 <size_t Index>
auto
get (void) const& { return std::get<Index> (m_values); }
private:
char m_specifier;
const parsed &m_parsed;
std::tuple<const ValueT&...> m_values;
};
// missing format specifier
class missing_error : public error
{
/// parameter collection for an owning squence of specifiers.
template <typename ...ValueT>
class stored {
public:
missing_error ():
error ("missing argument for specifier")
stored (std::vector<specifier> &&_specifiers, const ValueT &...args):
m_specifiers {std::move (_specifiers)},
m_values {args...}
{ ; }
auto
specifiers (void) const&
{
return util::make_view (m_specifiers);
}
template <size_t Index>
const auto&
get (void) const& { return std::get<Index> (m_values); }
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 (util::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 (util::view<const char*>);
/// parses a printf format string and binds parameters for rendering.
template <typename ...Args>
auto
printf (util::view<const char*> fmt, const Args &...args)
{
return printf (fmt) (args...);
}
/// parses a python format string and binds parameters for rendering.
template <typename ...Args>
auto
python (util::view<const char*> fmt, const 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, util::view<const char*>> && !std::is_same_v<ValueT, std::string>)
throw std::runtime_error ("expected string value");
break;
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<util::view<const char*>, ValueT>) {
if (spec.precision >= 0) {
std::copy_n (
std::begin (val),
util::min (spec.precision, 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)
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)
return value<util::view<const char*>>::write (os, spec, util::view<const char*> (val));
}
};
template <size_t N>
struct value<char[N]> {
static std::ostream&
write (std::ostream &os, specifier spec, const char (&val)[N]) {
return value<util::view<const char*>>::write (os, spec, util::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)
return value<util::view<const char*>>::write (os, spec, util::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)
return value<util::view<const char*>>::write (os, spec, util::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<util::view<const char*>>::write (
os, spec, util::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, 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<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 ();
}
}
#include "format.ipp"
#endif

View File

@ -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 <danny@nerdcruft.net>
*/
#include "./ascii.hpp"
#include "./debug.hpp"
#include "./maths.hpp"
#include <cstring>
#include <cstddef>
#include <sstream>
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 <typename ExceptT, typename ...Args>
constexpr void
constexpr_throw [[noreturn]] (Args&& ...args)
{
! true
? constexpr_throw<ExceptT> (std::forward<Args> (args)...)
: throw ExceptT (std::forward<Args> (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 <typename T>
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<NATIVE> { \
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 <size_t N>
struct specifier_traits<char[N]> {
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<syntax_error> ("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<int>::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 <size_t N>
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<syntax_error> ("specifiers require at least two characters");
if (*first != '%')
constexpr_throw<syntax_error> ("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 <size_t N>
constexpr
auto
parse (const char (&fmt)[N], specifier &spec)
{
return parse (fmt, fmt + N, spec);
}
///////////////////////////////////////////////////////////////////////////
template <char Conv>
struct conversion_traits;
#define CONVERSION_TRAITS(VALUE,TYPE,UPPER) \
template <> struct conversion_traits<VALUE> { \
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 <typename T>
std::enable_if_t<std::is_arithmetic<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 <typename ValueT, typename ...Args>
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> (args)...) :
format_length (cursor, last, std::forward<Args> (args)...)
);
}
return length;
}
//-------------------------------------------------------------------------
template <typename ...Args, size_t N>
inline
size_t
format_length (const char (&fmt)[N], const Args& ...args)
{
if (N <= 1)
return 0;
return format_length<Args...> (fmt, fmt + N - 1, args...);
}
//-------------------------------------------------------------------------
template <size_t N>
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 <typename OutputT>
OutputT
write (OutputT os, const specifier s)
{
if (s.k != specifier::kind::ESCAPE)
throw missing_error ();
*os = '%';
return ++os;
}
//-------------------------------------------------------------------------
template <typename OutputT>
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<const void*> (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 <typename OutputT>
OutputT
write (OutputT os, const specifier &spec, const unsigned char *t)
{
return write (os, spec, reinterpret_cast<const char*> (t));
}
//-------------------------------------------------------------------------
template <typename OutputT>
OutputT
write (OutputT os, const specifier spec, const std::string &val)
{
return write (os, spec, val.c_str ());
}
//-------------------------------------------------------------------------
template <typename OutputT>
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 <typename OutputT, typename ValueT>
std::enable_if_t<
!std::is_fundamental<ValueT>::value && !std::is_pointer<ValueT>::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<std::string>::length;
return write (os, strspec, ss.str ());
}
//-------------------------------------------------------------------------
template <typename T, typename OutputT>
std::enable_if_t<
std::is_pointer<T>::value && !std::is_same<std::remove_pointer_t<T>, 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<uintptr_t> (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<uintptr_t> (t));
}
//-------------------------------------------------------------------------
template <typename OutputT>
OutputT
write (OutputT os, const specifier &spec, std::nullptr_t)
{
return write (os, spec, (void*)0);
}
//-------------------------------------------------------------------------
template <typename ValueT, typename OutputT>
std::enable_if_t<
std::is_integral<ValueT>::value,
OutputT
>
write (OutputT os, const specifier spec, ValueT t)
{
if (spec.k == specifier::kind::POINTER && !t) {
return write (os, spec, reinterpret_cast<void*> (t));
}
if (!(spec.k == specifier::kind::UNSIGNED && std::is_unsigned<ValueT>::value ||
spec.k == specifier::kind::SIGNED && std::is_signed <ValueT>::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 <typename T, typename OutputT>
std::enable_if_t<
std::is_floating_point<T>::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<T>::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 <typename OutputT>
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 <typename ValueT, typename ...Args, typename OutputT>
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 <typename ValueT, typename ...Args, typename OutputT, size_t N>
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 <typename OutputT, size_t N>
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 <typename ...Args, size_t N>
std::string
render (const char (&fmt)[N], const Args& ...args)
{
std::string res;
detail::render (std::back_inserter (res), fmt, args...);
return res;
}
}

View File

@ -20,6 +20,8 @@
#include "./bitwise.hpp"
#include "./endian.hpp"
#include <cstring>
using util::hash::xxhash;

5
io.cpp
View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2010-2017 Danny Robson <danny@nerdcruft.net>
* Copyright 2010-2018 Danny Robson <danny@nerdcruft.net>
*/
#include "io.hpp"
@ -19,6 +19,7 @@
#include "debug.hpp"
#include "except.hpp"
#include "cast.hpp"
#include "format.hpp"
#include <cstdio>
#include <fcntl.h>
@ -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)
{ ; }

View File

@ -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)
{ ; }

16
log.hpp
View File

@ -11,15 +11,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2012 Danny Robson <danny@nerdcruft.net>
* Copyright 2012-2018 Danny Robson <danny@nerdcruft.net>
*/
#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 <ostream>
#include <string>
@ -81,7 +82,10 @@ namespace util {
template <typename ...Args, size_t N>
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...)));
}
//-------------------------------------------------------------------------

19
log.ipp
View File

@ -13,22 +13,3 @@
*
* Copyright 2012-2016 Danny Robson <danny@nerdcruft.net>
*/
#ifdef __UTIL_LOG_IPP
#error
#endif
#define __UTIL_LOG_IPP
#include "format.hpp"
//-----------------------------------------------------------------------------
namespace util {
template <typename ...Args, size_t N>
void
log (level_t l, const char (&fmt)[N], const Args& ...args)
{
log (l, format::render (fmt, args...));
}
}

View File

@ -47,7 +47,7 @@ namespace util::TAP {
{
m_output << (test ? "ok " : "not ok ") << ++m_size
<< " - "
<< util::format::render (fmt, std::forward<Args> (args)...) << '\n';
<< format::printf (fmt) (std::forward<Args> (args)...) << '\n';
if (!test)
m_status = EXIT_FAILURE;

View File

@ -1,6 +1,8 @@
#include "tap.hpp"
#include "alloc/arena.hpp"
#include "alloc/raw/linear.hpp"
#include "debug.hpp"
///////////////////////////////////////////////////////////////////////////////

View File

@ -1,4 +1,6 @@
#include "crypto/arc4.hpp"
#include "debug.hpp"
#include "tap.hpp"
#include "types.hpp"

View File

@ -1,5 +1,6 @@
#include "crypto/xxtea.hpp"
#include "debug.hpp"
#include "tap.hpp"
#include "types.hpp"

View File

@ -3,6 +3,8 @@
#include "tap.hpp"
#include <sstream>
///////////////////////////////////////////////////////////////////////////////
template <typename T, unsigned I, unsigned E>

View File

@ -2,6 +2,7 @@
#include "tap.hpp"
#include <iostream>
///////////////////////////////////////////////////////////////////////////////
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::except> ([&] { \
util::format::render (fmt, ##__VA_ARGS__); \
}, "exception '%s' for format '%s'", #except, fmt); \
#define CHECK_THROW(fmt,except,...) do { \
tap.expect_throw<std::exception> ([&] { \
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");

View File

@ -2,6 +2,8 @@
#include "stream.hpp"
#include <sstream>
///////////////////////////////////////////////////////////////////////////////
template <typename T>

View File

@ -18,9 +18,9 @@
#ifndef CRUFT_UTIL_VIEW_HPP
#define CRUFT_UTIL_VIEW_HPP
#include "./debug.hpp"
#include "./types/traits.hpp"
#include <cassert>
#include <cstdlib>
#include <ostream>
#include <string>
@ -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 <std::size_t N>
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 <typename ContainerT>
@ -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 };