mirror of https://github.com/gabime/spdlog.git
Merge branch 'tgpfeiffer-bump-fmtlib-6.0.0' into v1.x
commit
e89d59995a
@ -1,23 +1,27 @@
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
|
||||
All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
||||
|
||||
@ -0,0 +1,466 @@
|
||||
// Formatting library for C++ - experimental format string compilation
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COMPILE_H_
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#include <vector>
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
template <typename Char> struct format_part {
|
||||
public:
|
||||
struct named_argument_id {
|
||||
FMT_CONSTEXPR named_argument_id(internal::string_view_metadata id)
|
||||
: id(id) {}
|
||||
internal::string_view_metadata id;
|
||||
};
|
||||
|
||||
struct argument_id {
|
||||
FMT_CONSTEXPR argument_id() : argument_id(0u) {}
|
||||
|
||||
FMT_CONSTEXPR argument_id(unsigned id)
|
||||
: which(which_arg_id::index), val(id) {}
|
||||
|
||||
FMT_CONSTEXPR argument_id(internal::string_view_metadata id)
|
||||
: which(which_arg_id::named_index), val(id) {}
|
||||
|
||||
enum class which_arg_id { index, named_index };
|
||||
|
||||
which_arg_id which;
|
||||
|
||||
union value {
|
||||
FMT_CONSTEXPR value() : index(0u) {}
|
||||
FMT_CONSTEXPR value(unsigned id) : index(id) {}
|
||||
FMT_CONSTEXPR value(internal::string_view_metadata id)
|
||||
: named_index(id) {}
|
||||
|
||||
unsigned index;
|
||||
internal::string_view_metadata named_index;
|
||||
} val;
|
||||
};
|
||||
|
||||
struct specification {
|
||||
FMT_CONSTEXPR specification() : arg_id(0u) {}
|
||||
FMT_CONSTEXPR specification(unsigned id) : arg_id(id) {}
|
||||
|
||||
FMT_CONSTEXPR specification(internal::string_view_metadata id)
|
||||
: arg_id(id) {}
|
||||
|
||||
argument_id arg_id;
|
||||
internal::dynamic_format_specs<Char> parsed_specs;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR format_part()
|
||||
: which(kind::argument_id), end_of_argument_id(0u), val(0u) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(internal::string_view_metadata text)
|
||||
: which(kind::text), end_of_argument_id(0u), val(text) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(unsigned id)
|
||||
: which(kind::argument_id), end_of_argument_id(0u), val(id) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(named_argument_id arg_id)
|
||||
: which(kind::named_argument_id), end_of_argument_id(0u), val(arg_id) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(specification spec)
|
||||
: which(kind::specification), end_of_argument_id(0u), val(spec) {}
|
||||
|
||||
enum class kind { argument_id, named_argument_id, text, specification };
|
||||
|
||||
kind which;
|
||||
std::size_t end_of_argument_id;
|
||||
union value {
|
||||
FMT_CONSTEXPR value() : arg_id(0u) {}
|
||||
FMT_CONSTEXPR value(unsigned id) : arg_id(id) {}
|
||||
FMT_CONSTEXPR value(named_argument_id named_id)
|
||||
: named_arg_id(named_id.id) {}
|
||||
FMT_CONSTEXPR value(internal::string_view_metadata t) : text(t) {}
|
||||
FMT_CONSTEXPR value(specification s) : spec(s) {}
|
||||
unsigned arg_id;
|
||||
internal::string_view_metadata named_arg_id;
|
||||
internal::string_view_metadata text;
|
||||
specification spec;
|
||||
} val;
|
||||
};
|
||||
|
||||
template <typename Char, typename PartsContainer>
|
||||
class format_preparation_handler : public internal::error_handler {
|
||||
private:
|
||||
using part = format_part<Char>;
|
||||
|
||||
public:
|
||||
using iterator = typename basic_string_view<Char>::iterator;
|
||||
|
||||
FMT_CONSTEXPR format_preparation_handler(basic_string_view<Char> format,
|
||||
PartsContainer& parts)
|
||||
: parts_(parts), format_(format), parse_context_(format) {}
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin == end) return;
|
||||
const auto offset = begin - format_.data();
|
||||
const auto size = end - begin;
|
||||
parts_.push_back(part(string_view_metadata(offset, size)));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() {
|
||||
parts_.push_back(part(parse_context_.next_arg_id()));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(unsigned id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
parts_.push_back(part(id));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
||||
const auto view = string_view_metadata(format_, id);
|
||||
const auto arg_id = typename part::named_argument_id(view);
|
||||
parts_.push_back(part(arg_id));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
||||
parts_.back().end_of_argument_id = ptr - format_.begin();
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
const auto specs_offset = to_unsigned(begin - format_.begin());
|
||||
|
||||
using parse_context = basic_parse_context<Char>;
|
||||
internal::dynamic_format_specs<Char> parsed_specs;
|
||||
dynamic_specs_handler<parse_context> handler(parsed_specs, parse_context_);
|
||||
begin = parse_format_specs(begin, end, handler);
|
||||
|
||||
if (*begin != '}') on_error("missing '}' in format string");
|
||||
|
||||
auto& last_part = parts_.back();
|
||||
auto specs = last_part.which == part::kind::argument_id
|
||||
? typename part::specification(last_part.val.arg_id)
|
||||
: typename part::specification(last_part.val.named_arg_id);
|
||||
specs.parsed_specs = parsed_specs;
|
||||
last_part = part(specs);
|
||||
last_part.end_of_argument_id = specs_offset;
|
||||
return begin;
|
||||
}
|
||||
|
||||
private:
|
||||
PartsContainer& parts_;
|
||||
basic_string_view<Char> format_;
|
||||
basic_parse_context<Char> parse_context_;
|
||||
};
|
||||
|
||||
template <typename Format, typename PreparedPartsProvider, typename... Args>
|
||||
class prepared_format {
|
||||
public:
|
||||
using char_type = char_t<Format>;
|
||||
using format_part_t = format_part<char_type>;
|
||||
|
||||
constexpr prepared_format(Format f)
|
||||
: format_(std::move(f)), parts_provider_(to_string_view(format_)) {}
|
||||
|
||||
prepared_format() = delete;
|
||||
|
||||
using context = buffer_context<char_type>;
|
||||
|
||||
template <typename Range, typename Context>
|
||||
auto vformat_to(Range out, basic_format_args<Context> args) const ->
|
||||
typename Context::iterator {
|
||||
const auto format_view = internal::to_string_view(format_);
|
||||
basic_parse_context<char_type> parse_ctx(format_view);
|
||||
Context ctx(out.begin(), args);
|
||||
|
||||
const auto& parts = parts_provider_.parts();
|
||||
for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) {
|
||||
const auto& part = *part_it;
|
||||
const auto& value = part.val;
|
||||
|
||||
switch (part.which) {
|
||||
case format_part_t::kind::text: {
|
||||
const auto text = value.text.to_view(format_view.data());
|
||||
auto output = ctx.out();
|
||||
auto&& it = internal::reserve(output, text.size());
|
||||
it = std::copy_n(text.begin(), text.size(), it);
|
||||
ctx.advance_to(output);
|
||||
} break;
|
||||
|
||||
case format_part_t::kind::argument_id: {
|
||||
advance_parse_context_to_specification(parse_ctx, part);
|
||||
format_arg<Range>(parse_ctx, ctx, value.arg_id);
|
||||
} break;
|
||||
|
||||
case format_part_t::kind::named_argument_id: {
|
||||
advance_parse_context_to_specification(parse_ctx, part);
|
||||
const auto named_arg_id =
|
||||
value.named_arg_id.to_view(format_view.data());
|
||||
format_arg<Range>(parse_ctx, ctx, named_arg_id);
|
||||
} break;
|
||||
case format_part_t::kind::specification: {
|
||||
const auto& arg_id_value = value.spec.arg_id.val;
|
||||
const auto arg = value.spec.arg_id.which ==
|
||||
format_part_t::argument_id::which_arg_id::index
|
||||
? ctx.arg(arg_id_value.index)
|
||||
: ctx.arg(arg_id_value.named_index.to_view(
|
||||
to_string_view(format_).data()));
|
||||
|
||||
auto specs = value.spec.parsed_specs;
|
||||
|
||||
handle_dynamic_spec<internal::width_checker>(
|
||||
specs.width, specs.width_ref, ctx, format_view.begin());
|
||||
handle_dynamic_spec<internal::precision_checker>(
|
||||
specs.precision, specs.precision_ref, ctx, format_view.begin());
|
||||
|
||||
check_prepared_specs(specs, arg.type());
|
||||
advance_parse_context_to_specification(parse_ctx, part);
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
private:
|
||||
void advance_parse_context_to_specification(
|
||||
basic_parse_context<char_type>& parse_ctx,
|
||||
const format_part_t& part) const {
|
||||
const auto view = to_string_view(format_);
|
||||
const auto specification_begin = view.data() + part.end_of_argument_id;
|
||||
advance_to(parse_ctx, specification_begin);
|
||||
}
|
||||
|
||||
template <typename Range, typename Context, typename Id>
|
||||
void format_arg(basic_parse_context<char_type>& parse_ctx, Context& ctx,
|
||||
Id arg_id) const {
|
||||
parse_ctx.check_arg_id(arg_id);
|
||||
const auto stopped_at =
|
||||
visit_format_arg(arg_formatter<Range>(ctx), ctx.arg(arg_id));
|
||||
ctx.advance_to(stopped_at);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void check_prepared_specs(const basic_format_specs<Char>& specs,
|
||||
internal::type arg_type) const {
|
||||
internal::error_handler h;
|
||||
numeric_specs_checker<internal::error_handler> checker(h, arg_type);
|
||||
if (specs.align == align::numeric) checker.require_numeric_argument();
|
||||
if (specs.sign != sign::none) checker.check_sign();
|
||||
if (specs.alt) checker.require_numeric_argument();
|
||||
if (specs.precision >= 0) checker.check_precision();
|
||||
}
|
||||
|
||||
private:
|
||||
Format format_;
|
||||
PreparedPartsProvider parts_provider_;
|
||||
};
|
||||
|
||||
template <typename Char> struct part_counter {
|
||||
unsigned num_parts = 0;
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end) ++num_parts;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
// Find the matching brace.
|
||||
unsigned braces_counter = 0;
|
||||
for (; begin != end; ++begin) {
|
||||
if (*begin == '{') {
|
||||
++braces_counter;
|
||||
} else if (*begin == '}') {
|
||||
if (braces_counter == 0u) break;
|
||||
--braces_counter;
|
||||
}
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char*) {}
|
||||
};
|
||||
|
||||
template <typename Format> class compiletime_prepared_parts_type_provider {
|
||||
private:
|
||||
using char_type = char_t<Format>;
|
||||
|
||||
static FMT_CONSTEXPR unsigned count_parts() {
|
||||
FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{});
|
||||
part_counter<char_type> counter;
|
||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(text, counter);
|
||||
return counter.num_parts;
|
||||
}
|
||||
|
||||
// Workaround for old compilers. Compiletime parts preparation will not be
|
||||
// performed with them anyway.
|
||||
#if FMT_USE_CONSTEXPR
|
||||
static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts =
|
||||
compiletime_prepared_parts_type_provider::count_parts();
|
||||
#else
|
||||
static const unsigned number_of_format_parts = 0u;
|
||||
#endif
|
||||
|
||||
public:
|
||||
template <unsigned N> struct format_parts_array {
|
||||
using value_type = format_part<char_type>;
|
||||
|
||||
FMT_CONSTEXPR format_parts_array() : arr{} {}
|
||||
|
||||
FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; }
|
||||
|
||||
FMT_CONSTEXPR const value_type* begin() const { return arr; }
|
||||
FMT_CONSTEXPR const value_type* end() const { return begin() + N; }
|
||||
|
||||
private:
|
||||
value_type arr[N];
|
||||
};
|
||||
|
||||
struct empty {
|
||||
// Parts preparator will search for it
|
||||
using value_type = format_part<char_type>;
|
||||
};
|
||||
|
||||
using type = conditional_t<number_of_format_parts != 0,
|
||||
format_parts_array<number_of_format_parts>, empty>;
|
||||
};
|
||||
|
||||
template <typename Parts> class compiletime_prepared_parts_collector {
|
||||
private:
|
||||
using format_part = typename Parts::value_type;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR explicit compiletime_prepared_parts_collector(Parts& parts)
|
||||
: parts_{parts}, counter_{0u} {}
|
||||
|
||||
FMT_CONSTEXPR void push_back(format_part part) { parts_[counter_++] = part; }
|
||||
|
||||
FMT_CONSTEXPR format_part& back() { return parts_[counter_ - 1]; }
|
||||
|
||||
private:
|
||||
Parts& parts_;
|
||||
unsigned counter_;
|
||||
};
|
||||
|
||||
template <typename PartsContainer, typename Char>
|
||||
FMT_CONSTEXPR PartsContainer prepare_parts(basic_string_view<Char> format) {
|
||||
PartsContainer parts;
|
||||
internal::parse_format_string</*IS_CONSTEXPR=*/false>(
|
||||
format, format_preparation_handler<Char, PartsContainer>(format, parts));
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename PartsContainer, typename Char>
|
||||
FMT_CONSTEXPR PartsContainer
|
||||
prepare_compiletime_parts(basic_string_view<Char> format) {
|
||||
using collector = compiletime_prepared_parts_collector<PartsContainer>;
|
||||
|
||||
PartsContainer parts;
|
||||
collector c(parts);
|
||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(
|
||||
format, format_preparation_handler<Char, collector>(format, c));
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename PartsContainer> class runtime_parts_provider {
|
||||
public:
|
||||
runtime_parts_provider() = delete;
|
||||
template <typename Char>
|
||||
runtime_parts_provider(basic_string_view<Char> format)
|
||||
: parts_(prepare_parts<PartsContainer>(format)) {}
|
||||
|
||||
const PartsContainer& parts() const { return parts_; }
|
||||
|
||||
private:
|
||||
PartsContainer parts_;
|
||||
};
|
||||
|
||||
template <typename Format, typename PartsContainer>
|
||||
struct compiletime_parts_provider {
|
||||
compiletime_parts_provider() = delete;
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR compiletime_parts_provider(basic_string_view<Char>) {}
|
||||
|
||||
const PartsContainer& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const PartsContainer prepared_parts =
|
||||
prepare_compiletime_parts<PartsContainer>(
|
||||
internal::to_string_view(Format{}));
|
||||
|
||||
return prepared_parts;
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
FMT_CONSTEXPR auto compile(S format_str) -> internal::prepared_format<
|
||||
S,
|
||||
internal::compiletime_parts_provider<
|
||||
S,
|
||||
typename internal::compiletime_prepared_parts_type_provider<S>::type>,
|
||||
Args...> {
|
||||
return format_str;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename... Args, typename Char, size_t N>
|
||||
auto compile(const Char (&format_str)[N]) -> internal::prepared_format<
|
||||
std::basic_string<Char>,
|
||||
internal::runtime_parts_provider<std::vector<internal::format_part<Char>>>,
|
||||
Args...> {
|
||||
return std::basic_string<Char>(format_str, N - 1);
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using range = internal::buffer_range<Char>;
|
||||
using context = buffer_context<Char>;
|
||||
cf.template vformat_to<range, context>(range(buffer),
|
||||
{make_format_args<context>(args...)});
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
using char_type = typename CompiledFormat::char_type;
|
||||
using range = internal::output_range<OutputIt, char_type>;
|
||||
using context = format_context_t<OutputIt, char_type>;
|
||||
return cf.template vformat_to<range, context>(
|
||||
range(out), {make_format_args<context>(args...)});
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto it =
|
||||
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return fmt::format_to(
|
||||
internal::counting_iterator<typename CompiledFormat::char_type>(),
|
||||
cf, args...)
|
||||
.count();
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* For conversion between std::chrono::durations without undefined
|
||||
* behaviour or erroneous results.
|
||||
* This is a stripped down version of duration_cast, for inclusion in fmt.
|
||||
* See https://github.com/pauldreik/safe_duration_cast
|
||||
*
|
||||
* Copyright Paul Dreik 2019
|
||||
*
|
||||
* This file is licensed under the fmt license, see format.h
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace safe_duration_cast {
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed ==
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(F::is_integer, "From must be integral");
|
||||
static_assert(T::is_integer, "To must be integral");
|
||||
|
||||
// A and B are both signed, or both unsigned.
|
||||
if (F::digits <= T::digits) {
|
||||
// From fits in To without any problem.
|
||||
} else {
|
||||
// From does not always fit in To, resort to a dynamic check.
|
||||
if (from < T::min() || from > T::max()) {
|
||||
// outside range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts From to To, without loss. If the dynamic value of from
|
||||
* can't be converted to To without loss, ec is set.
|
||||
*/
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed !=
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(F::is_integer, "From must be integral");
|
||||
static_assert(T::is_integer, "To must be integral");
|
||||
|
||||
if (F::is_signed && !T::is_signed) {
|
||||
// From may be negative, not allowed!
|
||||
if (from < 0) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
// From is positive. Can it always fit in To?
|
||||
if (F::digits <= T::digits) {
|
||||
// yes, From always fits in To.
|
||||
} else {
|
||||
// from may not fit in To, we have to do a dynamic check
|
||||
if (from > static_cast<From>(T::max())) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!F::is_signed && T::is_signed) {
|
||||
// can from be held in To?
|
||||
if (F::digits < T::digits) {
|
||||
// yes, From always fits in To.
|
||||
} else {
|
||||
// from may not fit in To, we have to do a dynamic check
|
||||
if (from > static_cast<From>(T::max())) {
|
||||
// outside range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reaching here means all is ok for lossless conversion.
|
||||
return static_cast<To>(from);
|
||||
|
||||
} // function
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
return from;
|
||||
} // function
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* converts From to To if possible, otherwise ec is set.
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------|---------------
|
||||
* NaN | NaN
|
||||
* Inf | Inf
|
||||
* normal, fits in output | converted (possibly lossy)
|
||||
* normal, does not fit in output | ec is set
|
||||
* subnormal | best effort
|
||||
* -Inf | -Inf
|
||||
*/
|
||||
// clang-format on
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
static_assert(std::is_floating_point<To>::value, "To must be floating");
|
||||
|
||||
// catch the only happy case
|
||||
if (std::isfinite(from)) {
|
||||
if (from >= T::lowest() && from <= T::max()) {
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
// not within range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
// nan and inf will be preserved
|
||||
return static_cast<To>(from);
|
||||
} // function
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
return from;
|
||||
}
|
||||
|
||||
/**
|
||||
* safe duration cast between integral durations
|
||||
*/
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_integral<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
// the basic idea is that we need to convert from count() in the from type
|
||||
// to count() in the To type, by multiplying it with this:
|
||||
using Factor = std::ratio_divide<typename From::period, typename To::period>;
|
||||
|
||||
static_assert(Factor::num > 0, "num must be positive");
|
||||
static_assert(Factor::den > 0, "den must be positive");
|
||||
|
||||
// the conversion is like this: multiply from.count() with Factor::num
|
||||
// /Factor::den and convert it to To::rep, all this without
|
||||
// overflow/underflow. let's start by finding a suitable type that can hold
|
||||
// both To, From and Factor::num
|
||||
using IntermediateRep =
|
||||
typename std::common_type<typename From::rep, typename To::rep,
|
||||
decltype(Factor::num)>::type;
|
||||
|
||||
// safe conversion to IntermediateRep
|
||||
IntermediateRep count =
|
||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 =
|
||||
std::numeric_limits<IntermediateRep>::max() / Factor::num;
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
constexpr auto min1 =
|
||||
std::numeric_limits<IntermediateRep>::min() / Factor::num;
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
count *= Factor::num;
|
||||
}
|
||||
|
||||
// this can't go wrong, right? den>0 is checked earlier.
|
||||
if (Factor::den != 1) {
|
||||
count /= Factor::den;
|
||||
}
|
||||
// convert to the to type, safely
|
||||
using ToRep = typename To::rep;
|
||||
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
return To{tocount};
|
||||
}
|
||||
|
||||
/**
|
||||
* safe duration_cast between floating point durations
|
||||
*/
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
if (std::isnan(from.count())) {
|
||||
// nan in, gives nan out. easy.
|
||||
return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
|
||||
}
|
||||
// maybe we should also check if from is denormal, and decide what to do about
|
||||
// it.
|
||||
|
||||
// +-inf should be preserved.
|
||||
if (std::isinf(from.count())) {
|
||||
return To{from.count()};
|
||||
}
|
||||
|
||||
// the basic idea is that we need to convert from count() in the from type
|
||||
// to count() in the To type, by multiplying it with this:
|
||||
using Factor = std::ratio_divide<typename From::period, typename To::period>;
|
||||
|
||||
static_assert(Factor::num > 0, "num must be positive");
|
||||
static_assert(Factor::den > 0, "den must be positive");
|
||||
|
||||
// the conversion is like this: multiply from.count() with Factor::num
|
||||
// /Factor::den and convert it to To::rep, all this without
|
||||
// overflow/underflow. let's start by finding a suitable type that can hold
|
||||
// both To, From and Factor::num
|
||||
using IntermediateRep =
|
||||
typename std::common_type<typename From::rep, typename To::rep,
|
||||
decltype(Factor::num)>::type;
|
||||
|
||||
// force conversion of From::rep -> IntermediateRep to be safe,
|
||||
// even if it will never happen be narrowing in this context.
|
||||
IntermediateRep count =
|
||||
safe_float_conversion<IntermediateRep>(from.count(), ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
count *= static_cast<IntermediateRep>(Factor::num);
|
||||
}
|
||||
|
||||
// this can't go wrong, right? den>0 is checked earlier.
|
||||
if (Factor::den != 1) {
|
||||
using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
|
||||
count /= static_cast<common_t>(Factor::den);
|
||||
}
|
||||
|
||||
// convert to the to type, safely
|
||||
using ToRep = typename To::rep;
|
||||
|
||||
const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
return To{tocount};
|
||||
}
|
||||
|
||||
} // namespace safe_duration_cast
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
@ -1,160 +0,0 @@
|
||||
// Formatting library for C++ - time formatting
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_TIME_H_
|
||||
#define FMT_TIME_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <ctime>
|
||||
#include <locale>
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// Prevents expansion of a preceding token as a function-style macro.
|
||||
// Usage: f FMT_NOMACRO()
|
||||
#define FMT_NOMACRO
|
||||
|
||||
namespace internal{
|
||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
||||
inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
} // namespace internal
|
||||
|
||||
// Thread-safe replacement for std::localtime
|
||||
inline std::tm localtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(localtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(localtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
#if !FMT_MSC_VER
|
||||
bool fallback(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
std::tm *tm = std::localtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
dispatcher lt(time);
|
||||
// Too big time values may be unsupported.
|
||||
if (!lt.run())
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
return lt.tm_;
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::gmtime
|
||||
inline std::tm gmtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(gmtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(gmtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
#if !FMT_MSC_VER
|
||||
bool fallback(internal::null<>) {
|
||||
std::tm *tm = std::gmtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
dispatcher gt(time);
|
||||
// Too big time values may be unsupported.
|
||||
if (!gt.run())
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
return gt.tm_;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
inline std::size_t strftime(char *str, std::size_t count, const char *format,
|
||||
const std::tm *time) {
|
||||
return std::strftime(str, count, format, time);
|
||||
}
|
||||
|
||||
inline std::size_t strftime(wchar_t *str, std::size_t count,
|
||||
const wchar_t *format, const std::tm *time) {
|
||||
return std::wcsftime(str, count, format, time);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<std::tm, Char> {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
if (it != ctx.end() && *it == ':')
|
||||
++it;
|
||||
auto end = it;
|
||||
while (end != ctx.end() && *end != '}')
|
||||
++end;
|
||||
tm_format.reserve(internal::to_unsigned(end - it + 1));
|
||||
tm_format.append(it, end);
|
||||
tm_format.push_back('\0');
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
std::size_t start = buf.size();
|
||||
for (;;) {
|
||||
std::size_t size = buf.capacity() - start;
|
||||
std::size_t count =
|
||||
internal::strftime(&buf[start], size, &tm_format[0], &tm);
|
||||
if (count != 0) {
|
||||
buf.resize(start + count);
|
||||
break;
|
||||
}
|
||||
if (size >= tm_format.size() * 256) {
|
||||
// If the buffer is 256 times larger than the format string, assume
|
||||
// that `strftime` gives an empty result. There doesn't seem to be a
|
||||
// better way to distinguish the two cases:
|
||||
// https://github.com/fmtlib/fmt/issues/367
|
||||
break;
|
||||
}
|
||||
const std::size_t MIN_GROWTH = 10;
|
||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||
}
|
||||
return std::copy(buf.begin(), buf.end(), ctx.out());
|
||||
}
|
||||
|
||||
basic_memory_buffer<Char> tm_format;
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_TIME_H_
|
||||
Loading…
Reference in New Issue