format: rework parser for currying support
This commit is contained in:
parent
3ad0339474
commit
2713da45f4
@ -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
|
||||
|
13
debug.ipp
13
debug.ipp
@ -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);
|
||||
|
75
format.cpp
75
format.cpp
@ -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
222
format.cpp.rl
Normal 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)};
|
||||
}
|
574
format.hpp
574
format.hpp
@ -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
|
||||
|
949
format.ipp
949
format.ipp
@ -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;
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@
|
||||
#include "./bitwise.hpp"
|
||||
#include "./endian.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using util::hash::xxhash;
|
||||
|
||||
|
||||
|
5
io.cpp
5
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 <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)
|
||||
{ ; }
|
||||
|
||||
|
@ -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
16
log.hpp
@ -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
19
log.ipp
@ -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...));
|
||||
}
|
||||
}
|
||||
|
||||
|
2
tap.hpp
2
tap.hpp
@ -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;
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "tap.hpp"
|
||||
|
||||
#include "alloc/arena.hpp"
|
||||
#include "alloc/raw/linear.hpp"
|
||||
#include "debug.hpp"
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "crypto/arc4.hpp"
|
||||
|
||||
#include "debug.hpp"
|
||||
#include "tap.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "crypto/xxtea.hpp"
|
||||
|
||||
#include "debug.hpp"
|
||||
#include "tap.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "tap.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename T, unsigned I, unsigned E>
|
||||
|
@ -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");
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "stream.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename T>
|
||||
|
33
view.hpp
33
view.hpp
@ -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 };
|
||||
|
Loading…
x
Reference in New Issue
Block a user