mirror of
https://github.com/fmtlib/fmt.git
synced 2025-12-25 08:18:31 +01:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48f76dbb52 | ||
|
|
ca3dacba47 | ||
|
|
6d47e093c5 | ||
|
|
d1626e96ef | ||
|
|
160bcb723c | ||
|
|
c7ea093c27 | ||
|
|
00434c93ef | ||
|
|
b12033fd68 | ||
|
|
e5ab813ffb | ||
|
|
0fc7bd1573 | ||
|
|
b4f1988c4b | ||
|
|
4b8f8fac96 | ||
|
|
3b2fc033d1 | ||
|
|
ac59d9f3a4 | ||
|
|
8f27ce4d8b | ||
|
|
89b0c71fa9 | ||
|
|
f6a783ad2e | ||
|
|
ccc8f5db02 | ||
|
|
20fdb88a1c | ||
|
|
f29901097f | ||
|
|
758446c80d | ||
|
|
f7aedc5fc4 | ||
|
|
840a817ed2 | ||
|
|
79c923ba2c | ||
|
|
5dc577c064 | ||
|
|
c6d1a94a9f | ||
|
|
0656045d02 | ||
|
|
c85ae23c73 | ||
|
|
b3bf665764 | ||
|
|
0887887e23 | ||
|
|
ac71d853be | ||
|
|
6649b8e0ca | ||
|
|
56b5c192a0 | ||
|
|
b2f0b6e44e | ||
|
|
3d9f3c163b | ||
|
|
19547d5148 | ||
|
|
972ffd3151 | ||
|
|
2ed412fa38 | ||
|
|
df4dcf2ece | ||
|
|
f7a4b4ab91 | ||
|
|
611cf0b3c6 | ||
|
|
58a8f2f539 | ||
|
|
1882b9687b | ||
|
|
6de0454b42 | ||
|
|
16e3c48bb0 | ||
|
|
8ce5f680f2 | ||
|
|
2fd8f9ec8a | ||
|
|
fe642d7648 | ||
|
|
a128b5b2cb | ||
|
|
466128de00 | ||
|
|
22e98a5b6a | ||
|
|
f18a3f36a7 | ||
|
|
7cad33563c | ||
|
|
e1ab6bc006 | ||
|
|
24a88545d9 | ||
|
|
422e7b9d70 | ||
|
|
d1dd9d5327 | ||
|
|
9a56a608ee | ||
|
|
c76957565c | ||
|
|
200ee6f108 | ||
|
|
bcd9b9331a | ||
|
|
345ba07f1d | ||
|
|
9e2490be4c | ||
|
|
3f75e2b69e | ||
|
|
744302add0 | ||
|
|
f5556225a4 | ||
|
|
ad3c7855e2 |
@@ -33,6 +33,8 @@ if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING ${doc})
|
||||
endif ()
|
||||
|
||||
option(FMT_USE_TEXT "Use the text library." OFF)
|
||||
|
||||
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
||||
option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
||||
OFF)
|
||||
@@ -160,6 +162,10 @@ if (HAVE_OPEN)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} src/posix.cc)
|
||||
endif ()
|
||||
|
||||
if (FMT_USE_TEXT)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} src/text/grapheme_break.cpp)
|
||||
endif ()
|
||||
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
|
||||
@@ -180,12 +186,17 @@ target_include_directories(fmt PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
if (FMT_USE_TEXT)
|
||||
target_include_directories(fmt PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src/text>)
|
||||
endif ()
|
||||
|
||||
set_target_properties(fmt PROPERTIES
|
||||
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX d)
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
if (UNIX AND NOT APPLE)
|
||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
|
||||
# Fix rpmlint warning:
|
||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||
target_link_libraries(fmt -Wl,--as-needed)
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
* Modernized the codebase using more C++11 features and removing workarounds.
|
||||
Most importantly, ``buffer_context`` is now an alias template, so
|
||||
use ``buffer_context<T>`` instead of ``buffer_context<T>::type``.
|
||||
These features require GCC 4.8 or later.
|
||||
|
||||
* ``formatter`` specializations now always take precedence over implicit
|
||||
conversions to ``int`` and the undocumented ``convert_to_int`` trait
|
||||
@@ -298,7 +299,7 @@
|
||||
`#1222 <https://github.com/fmtlib/fmt/issues/1222>`_).
|
||||
Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
|
||||
|
||||
* Fixed bugs discovered by fuzzing or during fuzzing integation
|
||||
* Fixed bugs discovered by fuzzing or during fuzzing integration
|
||||
(`#1124 <https://github.com/fmtlib/fmt/issues/1124>`_,
|
||||
`#1127 <https://github.com/fmtlib/fmt/issues/1127>`_,
|
||||
`#1132 <https://github.com/fmtlib/fmt/issues/1132>`_,
|
||||
|
||||
14
README.rst
14
README.rst
@@ -11,6 +11,10 @@
|
||||
:alt: Ask questions at StackOverflow with the tag fmt
|
||||
:target: http://stackoverflow.com/questions/tagged/fmt
|
||||
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
|
||||
:alt: fmt is continuously fuzzed att oss-fuzz
|
||||
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1
|
||||
|
||||
**{fmt}** is an open-source formatting library for C++.
|
||||
It can be used as a safe and fast alternative to (s)printf and iostreams.
|
||||
|
||||
@@ -39,12 +43,12 @@ Features
|
||||
consists of just three header files, ``core.h``, ``format.h`` and
|
||||
``format-inl.h``) and compiled code. See `Compile time and code bloat`_.
|
||||
* Reliability: the library has an extensive set of `unit tests
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_.
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed.
|
||||
* Safety: the library is fully type safe, errors in format strings can be
|
||||
reported at compile time, automatic memory management prevents buffer overflow
|
||||
errors.
|
||||
* Ease of use: small self-contained code base, no external dependencies,
|
||||
permissive BSD `license
|
||||
permissive MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
||||
consistent output across platforms and support for older compilers.
|
||||
@@ -278,6 +282,8 @@ Projects using this library
|
||||
|
||||
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
|
||||
|
||||
* `ccache <https://ccache.dev/>`_: A compiler cache
|
||||
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
|
||||
@@ -343,7 +349,7 @@ Projects using this library
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
|
||||
MMORPG framework
|
||||
|
||||
`More... <https://github.com/search?q=cppformat&type=Code>`_
|
||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||
|
||||
If you are aware of other projects using this library, please let me know
|
||||
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
|
||||
@@ -469,7 +475,7 @@ A: use ``std::tuple``:
|
||||
License
|
||||
-------
|
||||
|
||||
{fmt} is distributed under the BSD `license
|
||||
{fmt} is distributed under the MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
|
||||
|
||||
The `Format String Syntax
|
||||
|
||||
42
doc/api.rst
42
doc/api.rst
@@ -10,6 +10,8 @@ The {fmt} library API consists of the following parts:
|
||||
facilities and a lightweight subset of formatting functions
|
||||
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
|
||||
format string checks, output iterator and user-defined type support
|
||||
* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
|
||||
and tuples
|
||||
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
||||
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
|
||||
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||
@@ -23,7 +25,8 @@ Core API
|
||||
========
|
||||
|
||||
``fmt/core.h`` defines the core API which provides argument handling facilities
|
||||
and a lightweight subset of formatting functions.
|
||||
and a lightweight subset of formatting functions. In the header-only mode
|
||||
include ``fmt/format.h`` instead of ``fmt/core.h``.
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>`
|
||||
similar to that of Python's `str.format
|
||||
@@ -278,21 +281,21 @@ Custom Formatting of Built-in Types
|
||||
It is possible to change the way arguments are formatted by providing a
|
||||
custom argument formatter class::
|
||||
|
||||
using arg_formatter =
|
||||
fmt::arg_formatter<fmt::back_insert_range<fmt::internal::buffer>>;
|
||||
using arg_formatter = fmt::arg_formatter<fmt::buffer_range<char>>;
|
||||
|
||||
// A custom argument formatter that formats negative integers as unsigned
|
||||
// with the ``x`` format specifier.
|
||||
class custom_arg_formatter : public arg_formatter {
|
||||
public:
|
||||
custom_arg_formatter(fmt::format_context &ctx,
|
||||
fmt::format_specs *spec = nullptr)
|
||||
: arg_formatter(ctx, spec) {}
|
||||
custom_arg_formatter(fmt::format_context& ctx,
|
||||
fmt::format_parse_context* parse_ctx = nullptr,
|
||||
fmt::format_specs* spec = nullptr)
|
||||
: arg_formatter(ctx, parse_ctx, spec) {}
|
||||
|
||||
using arg_formatter::operator();
|
||||
|
||||
auto operator()(int value) {
|
||||
if (spec().type() == 'x')
|
||||
if (specs() && specs()->type == 'x')
|
||||
return (*this)(static_cast<unsigned>(value)); // convert to unsigned and format
|
||||
return arg_formatter::operator()(value);
|
||||
}
|
||||
@@ -316,6 +319,31 @@ custom argument formatter class::
|
||||
.. doxygenclass:: fmt::arg_formatter
|
||||
:members:
|
||||
|
||||
.. _ranges-api:
|
||||
|
||||
Ranges and Tuple Formatting
|
||||
===========================
|
||||
|
||||
The library also supports convenient formatting of ranges and tuples::
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
std::tuple<char, int, float> t{'a', 1, 2.0f};
|
||||
// Prints "('a', 1, 2.0)"
|
||||
fmt::print("{}", t);
|
||||
|
||||
|
||||
NOTE: currently, the overload of ``fmt::join`` for iterables exists in the main
|
||||
``format.h`` header, but expect this to change in the future.
|
||||
|
||||
Using ``fmt::join``, you can separate tuple elements with a custom separator::
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
// Prints "1, a"
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
|
||||
.. _chrono-api:
|
||||
|
||||
Date and Time Formatting
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
**fmt** (formerly cppformat) is an open-source formatting library.
|
||||
**fmt** is an open-source formatting library.
|
||||
It can be used as a fast and safe alternative to printf and IOStreams.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
@@ -101,11 +101,6 @@ The meaning of the various alignment options is as follows:
|
||||
| ``'>'`` | Forces the field to be right-aligned within the |
|
||||
| | available space (this is the default for numbers). |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'='`` | Forces the padding to be placed after the sign (if any) |
|
||||
| | but before the digits. This is used for printing fields |
|
||||
| | in the form '+000000120'. This alignment option is only |
|
||||
| | valid for numeric types. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'^'`` | Forces the field to be centered within the available |
|
||||
| | space. |
|
||||
+---------+----------------------------------------------------------+
|
||||
@@ -154,9 +149,11 @@ conversions, trailing zeros are not removed from the result.
|
||||
*width* is a decimal integer defining the minimum field width. If not
|
||||
specified, then the field width will be determined by the content.
|
||||
|
||||
Preceding the *width* field by a zero (``'0'``) character enables
|
||||
sign-aware zero-padding for numeric types. This is equivalent to a *fill*
|
||||
character of ``'0'`` with an *alignment* type of ``'='``.
|
||||
Preceding the *width* field by a zero (``'0'``) character enables sign-aware
|
||||
zero-padding for numeric types. It forces the padding to be placed after the
|
||||
sign or base (if any) but before the digits. This is used for printing fields in
|
||||
the form '+000000120'. This option is only valid for numeric types and it has no
|
||||
effect on formatting of infinity and NaN.
|
||||
|
||||
The *precision* is a decimal number indicating how many digits should be
|
||||
displayed after the decimal point for a floating-point value formatted with
|
||||
@@ -264,6 +261,10 @@ The available presentation types for floating-point values are:
|
||||
| | ``'E'`` if the number gets too large. The |
|
||||
| | representations of infinity and NaN are uppercased, too. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'n'`` | Number. This is the same as ``'g'``, except that it uses |
|
||||
| | the current locale setting to insert the appropriate |
|
||||
| | number separator characters. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'%'`` | Fixed point as a percentage. This is similar to ``'f'``, |
|
||||
| | but the argument is multiplied by 100 and a percent sign |
|
||||
| | ``%`` is appended. |
|
||||
|
||||
@@ -403,7 +403,7 @@ inline bool isfinite(T value) {
|
||||
return std::isfinite(value);
|
||||
}
|
||||
|
||||
// Convers value to int and checks that it's in the range [0, upper).
|
||||
// Converts value to int and checks that it's in the range [0, upper).
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
inline int to_nonnegative_int(T value, int upper) {
|
||||
FMT_ASSERT(value >= 0 && value <= upper, "invalid value");
|
||||
@@ -582,8 +582,8 @@ struct chrono_formatter {
|
||||
void write(Rep value, int width) {
|
||||
write_sign();
|
||||
if (isnan(value)) return write_nan();
|
||||
uint32_or_64_t<int> n = to_unsigned(
|
||||
to_nonnegative_int(value, (std::numeric_limits<int>::max)()));
|
||||
uint32_or_64_or_128_t<int> n = to_unsigned(
|
||||
to_nonnegative_int(value, max_value<int>()));
|
||||
int num_digits = internal::count_digits(n);
|
||||
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
|
||||
out = format_decimal<char_type>(out, n, num_digits);
|
||||
@@ -738,8 +738,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
|
||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) {
|
||||
context.check_arg_id(arg_id);
|
||||
const auto str_val = internal::string_view_metadata(format_str, arg_id);
|
||||
return arg_ref_type(str_val);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
|
||||
@@ -750,7 +749,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
void on_fill(Char fill) { f.specs.fill[0] = fill; }
|
||||
void on_align(align_t align) { f.specs.align = align; }
|
||||
void on_width(unsigned width) { f.specs.width = width; }
|
||||
void on_precision(unsigned precision) { f.precision = precision; }
|
||||
void on_precision(unsigned _precision) { f.precision = _precision; }
|
||||
void end_precision() {}
|
||||
|
||||
template <typename Id> void on_dynamic_width(Id arg_id) {
|
||||
@@ -806,10 +805,10 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
auto out = std::back_inserter(buf);
|
||||
using range = internal::output_range<decltype(ctx.out()), Char>;
|
||||
internal::basic_writer<range> w(range(ctx.out()));
|
||||
internal::handle_dynamic_spec<internal::width_checker>(
|
||||
specs.width, width_ref, ctx, format_str.begin());
|
||||
internal::handle_dynamic_spec<internal::width_checker>(specs.width,
|
||||
width_ref, ctx);
|
||||
internal::handle_dynamic_spec<internal::precision_checker>(
|
||||
precision, precision_ref, ctx, format_str.begin());
|
||||
precision, precision_ref, ctx);
|
||||
if (begin == end || *begin == '}') {
|
||||
out = internal::format_chrono_duration_value(out, d.count(), precision);
|
||||
internal::format_chrono_duration_unit<Period>(out);
|
||||
|
||||
@@ -576,8 +576,9 @@ inline std::basic_string<Char> vformat(
|
||||
template <typename S, typename... Args, typename Char = char_t<S> >
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return internal::vformat(ts, to_string_view(format_str),
|
||||
{internal::make_args_checked(format_str, args...)});
|
||||
return internal::vformat(
|
||||
ts, to_string_view(format_str),
|
||||
{internal::make_args_checked<Args...>(format_str, args...)});
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@@ -14,250 +14,44 @@
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// Part of a compiled format string. It can be either literal text or a
|
||||
// replacement field.
|
||||
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;
|
||||
enum class kind { arg_index, arg_name, text, replacement };
|
||||
|
||||
struct replacement {
|
||||
arg_ref<Char> arg_id;
|
||||
dynamic_format_specs<Char> specs;
|
||||
};
|
||||
|
||||
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;
|
||||
kind part_kind;
|
||||
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;
|
||||
unsigned arg_index;
|
||||
basic_string_view<Char> str;
|
||||
replacement repl;
|
||||
|
||||
FMT_CONSTEXPR value(unsigned index = 0) : arg_index(index) {}
|
||||
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
|
||||
FMT_CONSTEXPR value(replacement r) : repl(r) {}
|
||||
} val;
|
||||
};
|
||||
// Position past the end of the argument id.
|
||||
const Char* arg_id_end = nullptr;
|
||||
|
||||
template <typename Char, typename PartsContainer>
|
||||
class format_preparation_handler : public internal::error_handler {
|
||||
private:
|
||||
using part = format_part<Char>;
|
||||
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
||||
: part_kind(k), val(v) {}
|
||||
|
||||
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)));
|
||||
static FMT_CONSTEXPR format_part make_arg_index(unsigned index) {
|
||||
return format_part(kind::arg_index, index);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() {
|
||||
parts_.push_back(part(parse_context_.next_arg_id()));
|
||||
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
||||
return format_part(kind::arg_name, name);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(unsigned id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
parts_.push_back(part(id));
|
||||
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
|
||||
return format_part(kind::text, text);
|
||||
}
|
||||
|
||||
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));
|
||||
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
||||
return format_part(kind::replacement, repl);
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -276,13 +70,13 @@ template <typename Char> struct part_counter {
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
// Find the matching brace.
|
||||
unsigned braces_counter = 0;
|
||||
unsigned brace_counter = 0;
|
||||
for (; begin != end; ++begin) {
|
||||
if (*begin == '{') {
|
||||
++braces_counter;
|
||||
++brace_counter;
|
||||
} else if (*begin == '}') {
|
||||
if (braces_counter == 0u) break;
|
||||
--braces_counter;
|
||||
if (brace_counter == 0u) break;
|
||||
--brace_counter;
|
||||
}
|
||||
}
|
||||
return begin;
|
||||
@@ -291,156 +85,477 @@ template <typename Char> struct part_counter {
|
||||
FMT_CONSTEXPR void on_error(const char*) {}
|
||||
};
|
||||
|
||||
template <typename Format> class compiletime_prepared_parts_type_provider {
|
||||
private:
|
||||
using char_type = char_t<Format>;
|
||||
// Counts the number of parts in a format string.
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
|
||||
part_counter<Char> counter;
|
||||
parse_format_string<true>(format_str, counter);
|
||||
return counter.num_parts;
|
||||
}
|
||||
|
||||
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;
|
||||
template <typename Char, typename PartHandler>
|
||||
class format_string_compiler : public error_handler {
|
||||
private:
|
||||
using part = format_part<Char>;
|
||||
|
||||
PartHandler handler_;
|
||||
part part_;
|
||||
basic_string_view<Char> format_str_;
|
||||
basic_parse_context<Char> parse_context_;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
|
||||
PartHandler handler)
|
||||
: handler_(handler),
|
||||
format_str_(format_str),
|
||||
parse_context_(format_str) {}
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin != end)
|
||||
handler_(part::make_text({begin, to_unsigned(end - begin)}));
|
||||
}
|
||||
|
||||
// Workaround for old compilers. Compiletime parts preparation will not be
|
||||
// performed with them anyway.
|
||||
FMT_CONSTEXPR void on_arg_id() {
|
||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(unsigned id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
part_ = part::make_arg_index(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
||||
part_ = part::make_arg_name(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
||||
part_.arg_id_end = ptr;
|
||||
handler_(part_);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
auto repl = typename part::replacement();
|
||||
dynamic_specs_handler<basic_parse_context<Char>> handler(repl.specs,
|
||||
parse_context_);
|
||||
auto it = parse_format_specs(begin, end, handler);
|
||||
if (*it != '}') on_error("missing '}' in format string");
|
||||
repl.arg_id = part_.part_kind == part::kind::arg_index
|
||||
? arg_ref<Char>(part_.val.arg_index)
|
||||
: arg_ref<Char>(part_.val.str);
|
||||
auto part = part::make_replacement(repl);
|
||||
part.arg_id_end = begin;
|
||||
handler_(part);
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
// Compiles a format string and invokes handler(part) for each parsed part.
|
||||
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
|
||||
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
||||
PartHandler handler) {
|
||||
parse_format_string<IS_CONSTEXPR>(
|
||||
format_str,
|
||||
format_string_compiler<Char, PartHandler>(format_str, handler));
|
||||
}
|
||||
|
||||
template <typename Range, typename Context, typename Id>
|
||||
void format_arg(basic_parse_context<typename Range::value_type>& parse_ctx,
|
||||
Context& ctx, Id arg_id) {
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
|
||||
}
|
||||
|
||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
||||
namespace cf {
|
||||
template <typename Context, typename Range, typename CompiledFormat>
|
||||
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
|
||||
-> typename Context::iterator {
|
||||
using char_type = typename Context::char_type;
|
||||
basic_parse_context<char_type> parse_ctx(to_string_view(cf.format_str_));
|
||||
Context ctx(out.begin(), args);
|
||||
|
||||
const auto& parts = cf.parts();
|
||||
for (auto part_it = std::begin(parts); part_it != std::end(parts);
|
||||
++part_it) {
|
||||
const auto& part = *part_it;
|
||||
const auto& value = part.val;
|
||||
|
||||
using format_part_t = format_part<char_type>;
|
||||
switch (part.part_kind) {
|
||||
case format_part_t::kind::text: {
|
||||
const auto text = value.str;
|
||||
auto output = ctx.out();
|
||||
auto&& it = reserve(output, text.size());
|
||||
it = std::copy_n(text.begin(), text.size(), it);
|
||||
ctx.advance_to(output);
|
||||
break;
|
||||
}
|
||||
|
||||
case format_part_t::kind::arg_index:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::arg_name:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
internal::format_arg<Range>(parse_ctx, ctx, value.str);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::replacement: {
|
||||
const auto& arg_id_value = value.repl.arg_id.val;
|
||||
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
|
||||
? ctx.arg(arg_id_value.index)
|
||||
: ctx.arg(arg_id_value.name);
|
||||
|
||||
auto specs = value.repl.specs;
|
||||
|
||||
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
|
||||
handle_dynamic_spec<precision_checker>(specs.precision,
|
||||
specs.precision_ref, ctx);
|
||||
|
||||
error_handler h;
|
||||
numeric_specs_checker<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();
|
||||
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.out();
|
||||
}
|
||||
} // namespace cf
|
||||
|
||||
struct basic_compiled_format {};
|
||||
|
||||
template <typename S, typename = void>
|
||||
struct compiled_format_base : basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
using parts_container = std::vector<internal::format_part<char_type>>;
|
||||
|
||||
parts_container compiled_parts;
|
||||
|
||||
explicit compiled_format_base(basic_string_view<char_type> format_str) {
|
||||
compile_format_string<false>(format_str,
|
||||
[this](const format_part<char_type>& part) {
|
||||
compiled_parts.push_back(part);
|
||||
});
|
||||
}
|
||||
|
||||
const parts_container& parts() const { return compiled_parts; }
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N> struct format_part_array {
|
||||
format_part<Char> data[N] = {};
|
||||
FMT_CONSTEXPR format_part_array() = default;
|
||||
};
|
||||
|
||||
template <typename Char, unsigned N>
|
||||
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
|
||||
basic_string_view<Char> format_str) {
|
||||
format_part_array<Char, N> parts;
|
||||
unsigned counter = 0;
|
||||
// This is not a lambda for compatibility with older compilers.
|
||||
struct {
|
||||
format_part<Char>* parts;
|
||||
unsigned* counter;
|
||||
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
|
||||
parts[(*counter)++] = part;
|
||||
}
|
||||
} collector{parts.data, &counter};
|
||||
compile_format_string<true>(format_str, collector);
|
||||
if (counter < N) {
|
||||
parts.data[counter] =
|
||||
format_part<Char>::make_text(basic_string_view<Char>());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
|
||||
return (a < b) ? b : a;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
|
||||
: basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
|
||||
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
|
||||
|
||||
// Workaround for old compilers. Format string compilation will not be
|
||||
// performed there anyway.
|
||||
#if FMT_USE_CONSTEXPR
|
||||
static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts =
|
||||
compiletime_prepared_parts_type_provider::count_parts();
|
||||
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
|
||||
constexpr_max(count_parts(to_string_view(S())), 1u);
|
||||
#else
|
||||
static const unsigned number_of_format_parts = 0u;
|
||||
static const unsigned num_format_parts = 1;
|
||||
#endif
|
||||
|
||||
public:
|
||||
template <unsigned N> struct format_parts_array {
|
||||
using value_type = format_part<char_type>;
|
||||
using parts_container = format_part<char_type>[num_format_parts];
|
||||
|
||||
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;
|
||||
const parts_container& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
||||
compile_to_parts<char_type, num_format_parts>(
|
||||
internal::to_string_view(S()));
|
||||
return compiled_parts.data;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename... Args>
|
||||
class compiled_format : private compiled_format_base<S> {
|
||||
public:
|
||||
using typename compiled_format_base<S>::char_type;
|
||||
|
||||
private:
|
||||
basic_string_view<char_type> format_str_;
|
||||
|
||||
template <typename Context, typename Range, typename CompiledFormat>
|
||||
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) ->
|
||||
typename Context::iterator;
|
||||
|
||||
public:
|
||||
compiled_format() = delete;
|
||||
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
|
||||
: compiled_format_base<S>(format_str), format_str_(format_str) {}
|
||||
};
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
using get_type = typename get_type_impl<N, T>::type;
|
||||
|
||||
template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&...) const {
|
||||
// TODO: reserve
|
||||
return copy_str<Char>(data.begin(), data.end(), out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
OutputIt format_default(OutputIt out, T value) {
|
||||
// TODO: reserve
|
||||
format_int fi(value);
|
||||
return std::copy(fi.data(), fi.data() + fi.size(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, Char value) {
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, const Char* value) {
|
||||
auto length = std::char_traits<Char>::length(value);
|
||||
return copy_str<Char>(value, value + length, out);
|
||||
}
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
return format_default<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS != to_string_view(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
else
|
||||
return make_concat(head, tail);
|
||||
} else {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr basic_string_view<char_type> str = format_str;
|
||||
if constexpr (str[POS] == '{') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}') {
|
||||
using type = get_type<ID, Args>;
|
||||
if constexpr (std::is_same<type, int>::value) {
|
||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
||||
format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
# ifdef __cpp_if_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);
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
||||
if constexpr (str.size() == 0) {
|
||||
return internal::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
||||
internal::unknown_format>()) {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type>
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using range = internal::buffer_range<Char>;
|
||||
using range = buffer_range<Char>;
|
||||
using context = buffer_context<Char>;
|
||||
cf.template vformat_to<range, context>(range(buffer),
|
||||
{make_format_args<context>(args...)});
|
||||
cf.format(std::back_inserter(buffer), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args>
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
# else
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
}
|
||||
# endif // __cpp_if_constexpr
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
// Compiles the format string which must be a string literal.
|
||||
template <typename... Args, typename Char, size_t N>
|
||||
auto compile(const Char (&format_str)[N])
|
||||
-> internal::compiled_format<const Char*, Args...> {
|
||||
return internal::compiled_format<const Char*, Args...>(
|
||||
basic_string_view<Char>(format_str, N - 1));
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using range = buffer_range<Char>;
|
||||
using context = buffer_context<Char>;
|
||||
internal::cf::vformat_to<context>(range(buffer), cf,
|
||||
{make_format_args<context>(args...)});
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
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...)});
|
||||
return internal::cf::vformat_to<context>(
|
||||
range(out), cf, {make_format_args<context>(args...)});
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
@@ -455,7 +570,7 @@ format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return fmt::format_to(
|
||||
return format_to(
|
||||
internal::counting_iterator<typename CompiledFormat::char_type>(),
|
||||
cf, args...)
|
||||
.count();
|
||||
|
||||
@@ -133,6 +133,13 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Workaround broken [[deprecated]] in the Intel compiler and NVCC.
|
||||
#if defined(__INTEL_COMPILER) || defined(__NVCC__) || defined(__CUDACC__)
|
||||
# define FMT_DEPRECATED_ALIAS
|
||||
#else
|
||||
# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED
|
||||
#endif
|
||||
|
||||
#ifndef FMT_BEGIN_NAMESPACE
|
||||
# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
|
||||
FMT_MSC_VER >= 1900
|
||||
@@ -200,6 +207,8 @@ template <typename T>
|
||||
using remove_reference_t = typename std::remove_reference<T>::type;
|
||||
template <typename T>
|
||||
using remove_const_t = typename std::remove_const<T>::type;
|
||||
template <typename T>
|
||||
using remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type;
|
||||
|
||||
struct monostate {};
|
||||
|
||||
@@ -222,7 +231,21 @@ using std_string_view = std::experimental::basic_string_view<Char>;
|
||||
template <typename T> struct std_string_view {};
|
||||
#endif
|
||||
|
||||
// Casts nonnegative integer to unsigned.
|
||||
#ifdef FMT_USE_INT128
|
||||
// Do nothing.
|
||||
#elif defined(__SIZEOF_INT128__)
|
||||
# define FMT_USE_INT128 1
|
||||
using int128_t = __int128_t;
|
||||
using uint128_t = __uint128_t;
|
||||
#else
|
||||
# define FMT_USE_INT128 0
|
||||
#endif
|
||||
#if !FMT_USE_INT128
|
||||
struct int128_t {};
|
||||
struct uint128_t {};
|
||||
#endif
|
||||
|
||||
// Casts a nonnegative integer to unsigned.
|
||||
template <typename Int>
|
||||
FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) {
|
||||
FMT_ASSERT(value >= 0, "negative value");
|
||||
@@ -286,6 +309,8 @@ template <typename Char> class basic_string_view {
|
||||
FMT_CONSTEXPR iterator begin() const { return data_; }
|
||||
FMT_CONSTEXPR iterator end() const { return data_ + size_; }
|
||||
|
||||
FMT_CONSTEXPR const Char& operator[](size_t pos) const { return data_[pos]; }
|
||||
|
||||
FMT_CONSTEXPR void remove_prefix(size_t n) {
|
||||
data_ += n;
|
||||
size_ -= n;
|
||||
@@ -474,8 +499,8 @@ class basic_parse_context : private ErrorHandler {
|
||||
using format_parse_context = basic_parse_context<char>;
|
||||
using wformat_parse_context = basic_parse_context<wchar_t>;
|
||||
|
||||
using parse_context FMT_DEPRECATED = basic_parse_context<char>;
|
||||
using wparse_context FMT_DEPRECATED = basic_parse_context<wchar_t>;
|
||||
using parse_context FMT_DEPRECATED_ALIAS = basic_parse_context<char>;
|
||||
using wparse_context FMT_DEPRECATED_ALIAS = basic_parse_context<wchar_t>;
|
||||
|
||||
template <typename Context> class basic_format_arg;
|
||||
template <typename Context> class basic_format_args;
|
||||
@@ -626,6 +651,8 @@ enum type {
|
||||
uint_type,
|
||||
long_long_type,
|
||||
ulong_long_type,
|
||||
int128_type,
|
||||
uint128_type,
|
||||
bool_type,
|
||||
char_type,
|
||||
last_integer_type = char_type,
|
||||
@@ -652,6 +679,8 @@ FMT_TYPE_CONSTANT(int, int_type);
|
||||
FMT_TYPE_CONSTANT(unsigned, uint_type);
|
||||
FMT_TYPE_CONSTANT(long long, long_long_type);
|
||||
FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type);
|
||||
FMT_TYPE_CONSTANT(int128_t, int128_type);
|
||||
FMT_TYPE_CONSTANT(uint128_t, uint128_type);
|
||||
FMT_TYPE_CONSTANT(bool, bool_type);
|
||||
FMT_TYPE_CONSTANT(Char, char_type);
|
||||
FMT_TYPE_CONSTANT(double, double_type);
|
||||
@@ -660,12 +689,12 @@ FMT_TYPE_CONSTANT(const Char*, cstring_type);
|
||||
FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type);
|
||||
FMT_TYPE_CONSTANT(const void*, pointer_type);
|
||||
|
||||
FMT_CONSTEXPR bool is_integral(type t) {
|
||||
FMT_CONSTEXPR bool is_integral_type(type t) {
|
||||
FMT_ASSERT(t != named_arg_type, "invalid argument type");
|
||||
return t > none_type && t <= last_integer_type;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool is_arithmetic(type t) {
|
||||
FMT_CONSTEXPR bool is_arithmetic_type(type t) {
|
||||
FMT_ASSERT(t != named_arg_type, "invalid argument type");
|
||||
return t > none_type && t <= last_numeric_type;
|
||||
}
|
||||
@@ -691,6 +720,8 @@ template <typename Context> class value {
|
||||
unsigned uint_value;
|
||||
long long long_long_value;
|
||||
unsigned long long ulong_long_value;
|
||||
int128_t int128_value;
|
||||
uint128_t uint128_value;
|
||||
bool bool_value;
|
||||
char_type char_value;
|
||||
double double_value;
|
||||
@@ -705,6 +736,8 @@ template <typename Context> class value {
|
||||
FMT_CONSTEXPR value(unsigned val) : uint_value(val) {}
|
||||
value(long long val) : long_long_value(val) {}
|
||||
value(unsigned long long val) : ulong_long_value(val) {}
|
||||
value(int128_t val) : int128_value(val) {}
|
||||
value(uint128_t val) : uint128_value(val) {}
|
||||
value(double val) : double_value(val) {}
|
||||
value(long double val) : long_double_value(val) {}
|
||||
value(bool val) : bool_value(val) {}
|
||||
@@ -764,6 +797,8 @@ template <typename Context> struct arg_mapper {
|
||||
FMT_CONSTEXPR ulong_type map(unsigned long val) { return val; }
|
||||
FMT_CONSTEXPR long long map(long long val) { return val; }
|
||||
FMT_CONSTEXPR unsigned long long map(unsigned long long val) { return val; }
|
||||
FMT_CONSTEXPR int128_t map(int128_t val) { return val; }
|
||||
FMT_CONSTEXPR uint128_t map(uint128_t val) { return val; }
|
||||
FMT_CONSTEXPR bool map(bool val) { return val; }
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
|
||||
@@ -818,8 +853,9 @@ template <typename Context> struct arg_mapper {
|
||||
FMT_ENABLE_IF(std::is_enum<T>::value &&
|
||||
!has_formatter<T, Context>::value &&
|
||||
!has_fallback_formatter<T, Context>::value)>
|
||||
FMT_CONSTEXPR int map(const T& val) {
|
||||
return static_cast<int>(val);
|
||||
FMT_CONSTEXPR auto map(const T& val) -> decltype(
|
||||
map(static_cast<typename std::underlying_type<T>::type>(val))) {
|
||||
return map(static_cast<typename std::underlying_type<T>::type>(val));
|
||||
}
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
|
||||
@@ -893,8 +929,8 @@ template <typename Context> class basic_format_arg {
|
||||
|
||||
internal::type type() const { return type_; }
|
||||
|
||||
bool is_integral() const { return internal::is_integral(type_); }
|
||||
bool is_arithmetic() const { return internal::is_arithmetic(type_); }
|
||||
bool is_integral() const { return internal::is_integral_type(type_); }
|
||||
bool is_arithmetic() const { return internal::is_arithmetic_type(type_); }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -923,6 +959,16 @@ FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
|
||||
return vis(arg.value_.long_long_value);
|
||||
case internal::ulong_long_type:
|
||||
return vis(arg.value_.ulong_long_value);
|
||||
#if FMT_USE_INT128
|
||||
case internal::int128_type:
|
||||
return vis(arg.value_.int128_value);
|
||||
case internal::uint128_type:
|
||||
return vis(arg.value_.uint128_value);
|
||||
#else
|
||||
case internal::int128_type:
|
||||
case internal::uint128_type:
|
||||
break;
|
||||
#endif
|
||||
case internal::bool_type:
|
||||
return vis(arg.value_.bool_value);
|
||||
case internal::char_type:
|
||||
|
||||
@@ -49,9 +49,6 @@
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable : 4127) // conditional expression is constant
|
||||
# pragma warning(disable : 4702) // unreachable code
|
||||
// Disable deprecation warning for strerror. The latter is not called but
|
||||
// MSVC fails to detect it.
|
||||
# pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
// Dummy implementations of strerror_r and strerror_s called if corresponding
|
||||
@@ -81,7 +78,7 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) {
|
||||
|
||||
using format_func = void (*)(internal::buffer<char>&, int, string_view);
|
||||
|
||||
// Portable thread-safe version of strerror.
|
||||
// A portable thread-safe version of strerror.
|
||||
// Sets buffer to point to a string describing the error code.
|
||||
// This can be either a pointer to a string stored in buffer,
|
||||
// or a pointer to some static immutable string.
|
||||
@@ -158,7 +155,7 @@ FMT_FUNC void format_error_code(internal::buffer<char>& out, int error_code,
|
||||
static const char ERROR_STR[] = "error ";
|
||||
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
|
||||
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
|
||||
auto abs_value = static_cast<uint32_or_64_t<int>>(error_code);
|
||||
auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);
|
||||
if (internal::is_negative(error_code)) {
|
||||
abs_value = 0 - abs_value;
|
||||
++error_code_size;
|
||||
@@ -352,6 +349,9 @@ template <typename T> struct bits {
|
||||
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
|
||||
};
|
||||
|
||||
class fp;
|
||||
template <int SHIFT = 0> fp normalize(fp value);
|
||||
|
||||
// A handmade floating-point number f * pow(2, e).
|
||||
class fp {
|
||||
private:
|
||||
@@ -396,28 +396,30 @@ class fp {
|
||||
}
|
||||
|
||||
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
|
||||
template <int SHIFT = 0> void normalize() {
|
||||
template <int SHIFT> friend fp normalize(fp value) {
|
||||
// Handle subnormals.
|
||||
auto shifted_implicit_bit = implicit_bit << SHIFT;
|
||||
while ((f & shifted_implicit_bit) == 0) {
|
||||
f <<= 1;
|
||||
--e;
|
||||
const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
|
||||
while ((value.f & shifted_implicit_bit) == 0) {
|
||||
value.f <<= 1;
|
||||
--value.e;
|
||||
}
|
||||
// Subtract 1 to account for hidden bit.
|
||||
auto offset = significand_size - double_significand_size - SHIFT - 1;
|
||||
f <<= offset;
|
||||
e -= offset;
|
||||
const auto offset =
|
||||
fp::significand_size - fp::double_significand_size - SHIFT - 1;
|
||||
value.f <<= offset;
|
||||
value.e -= offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
// Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where
|
||||
// Computes lower and upper boundaries (m^- and m^+ in the Grisu paper), where
|
||||
// a boundary is a value half way between the number and its predecessor
|
||||
// (lower) or successor (upper). The upper boundary is normalized and lower
|
||||
// has the same exponent but may be not normalized.
|
||||
void compute_boundaries(fp& lower, fp& upper) const {
|
||||
lower =
|
||||
f == implicit_bit ? fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1);
|
||||
upper = fp((f << 1) + 1, e - 1);
|
||||
upper.normalize<1>(); // 1 is to account for the exponent shift above.
|
||||
// 1 in normalize accounts for the exponent shift above.
|
||||
upper = normalize<1>(fp((f << 1) + 1, e - 1));
|
||||
lower.f <<= lower.e - upper.e;
|
||||
lower.e = upper.e;
|
||||
}
|
||||
@@ -451,8 +453,8 @@ FMT_FUNC fp operator*(fp x, fp y) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its
|
||||
// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 28.
|
||||
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
|
||||
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
|
||||
FMT_FUNC fp get_cached_power(int min_exponent, int& pow10_exponent) {
|
||||
const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10)
|
||||
int index = static_cast<int>(
|
||||
@@ -466,6 +468,88 @@ FMT_FUNC fp get_cached_power(int min_exponent, int& pow10_exponent) {
|
||||
return fp(data::pow10_significands[index], data::pow10_exponents[index]);
|
||||
}
|
||||
|
||||
class bigint {
|
||||
private:
|
||||
// A bigint is stored as an array of bigits (big digits), with bigit at index
|
||||
// 0 being the least significant one.
|
||||
using bigit = uint32_t;
|
||||
using double_bigit = uint64_t;
|
||||
basic_memory_buffer<bigit> bigits_;
|
||||
|
||||
static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
|
||||
|
||||
friend struct formatter<bigint>;
|
||||
|
||||
void assign(uint64_t n) {
|
||||
int num_bigits = bits<uint64_t>::value / bigit_bits;
|
||||
bigits_.resize(num_bigits);
|
||||
for (int i = 0; i < num_bigits; ++i) {
|
||||
bigits_[i] = n & ~bigit(0);
|
||||
n >>= bigit_bits;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
bigint() {}
|
||||
explicit bigint(uint64_t n) { assign(n); }
|
||||
|
||||
// Assigns pow(10, exp) to this bigint.
|
||||
void assign_pow10(int exp) {
|
||||
assert(exp >= 0);
|
||||
if (exp == 0) return assign(1);
|
||||
// Find the top bit.
|
||||
int bitmask = 1;
|
||||
while (exp >= bitmask) bitmask <<= 1;
|
||||
bitmask >>= 1;
|
||||
// pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by
|
||||
// repeated squaring and multiplication.
|
||||
assign(5);
|
||||
bitmask >>= 1;
|
||||
while (bitmask != 0) {
|
||||
square();
|
||||
if ((exp & bitmask) != 0) *this *= 5;
|
||||
bitmask >>= 1;
|
||||
}
|
||||
*this <<= exp; // Multiply by pow(2, exp) by shifting.
|
||||
}
|
||||
|
||||
bigint(const bigint&) = delete;
|
||||
void operator=(const bigint&) = delete;
|
||||
|
||||
void square() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
bigint& operator<<=(int shift) {
|
||||
assert(shift >= 0 && shift < bigit_bits);
|
||||
bigit carry = 0;
|
||||
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
|
||||
bigit c = shift != 0 ? bigits_[i] >> (bigit_bits - shift) : 0;
|
||||
bigits_[i] = (bigits_[i] << shift) + carry;
|
||||
carry = c;
|
||||
}
|
||||
if (carry != 0) bigits_.push_back(carry);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bigint& operator*=(uint32_t value) {
|
||||
assert(value > 0);
|
||||
// Verify that the computation doesn't overflow.
|
||||
constexpr double_bigit max32 = max_value<bigit>();
|
||||
constexpr double_bigit max64 = max_value<double_bigit>();
|
||||
static_assert(max32 * max32 <= max64 - max32, "");
|
||||
bigit carry = 0;
|
||||
const double_bigit wide_value = value;
|
||||
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
|
||||
double_bigit result = bigits_[i] * wide_value + carry;
|
||||
bigits_[i] = static_cast<bigit>(result);
|
||||
carry = static_cast<bigit>(result >> bigit_bits);
|
||||
}
|
||||
if (carry != 0) bigits_.push_back(carry);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
enum round_direction { unknown, up, down };
|
||||
|
||||
// Given the divisor (normally a power of 10), the remainder = v % divisor for
|
||||
@@ -686,35 +770,40 @@ template <int GRISU_VERSION> struct grisu_shortest_handler {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_FUNC void fallback_format(const fp& value, int exp10) {
|
||||
(void)value; // TODO
|
||||
(void)exp10;
|
||||
}
|
||||
|
||||
template <typename Double,
|
||||
enable_if_t<(sizeof(Double) == sizeof(uint64_t)), int>>
|
||||
FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
|
||||
unsigned options, int& exp) {
|
||||
FMT_ASSERT(value >= 0, "value is negative");
|
||||
bool fixed = (options & grisu_options::fixed) != 0;
|
||||
const bool fixed = (options & grisu_options::fixed) != 0;
|
||||
if (value <= 0) { // <= instead of == to silence a warning.
|
||||
if (precision <= 0 || !fixed) {
|
||||
exp = 0;
|
||||
buf.push_back('0');
|
||||
} else {
|
||||
exp = -precision;
|
||||
buf.resize(precision);
|
||||
buf.resize(to_unsigned(precision));
|
||||
std::uninitialized_fill_n(buf.data(), precision, '0');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fp fp_value(value);
|
||||
const fp fp_value(value);
|
||||
const int min_exp = -60; // alpha in Grisu.
|
||||
int cached_exp10 = 0; // K in Grisu.
|
||||
if (precision != -1) {
|
||||
if (precision > 17) return false;
|
||||
fp_value.normalize();
|
||||
auto cached_pow = get_cached_power(
|
||||
min_exp - (fp_value.e + fp::significand_size), cached_exp10);
|
||||
fp_value = fp_value * cached_pow;
|
||||
fp normalized = normalize(fp_value);
|
||||
const auto cached_pow = get_cached_power(
|
||||
min_exp - (normalized.e + fp::significand_size), cached_exp10);
|
||||
normalized = normalized * cached_pow;
|
||||
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
|
||||
if (grisu_gen_digits(fp_value, 1, exp, handler) == digits::error)
|
||||
if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error)
|
||||
return false;
|
||||
buf.resize(to_unsigned(handler.size));
|
||||
} else {
|
||||
@@ -722,10 +811,10 @@ FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
|
||||
fp_value.compute_boundaries(lower, upper);
|
||||
// Find a cached power of 10 such that multiplying upper by it will bring
|
||||
// the exponent in the range [min_exp, -32].
|
||||
auto cached_pow = get_cached_power( // \tilde{c}_{-k} in Grisu.
|
||||
const auto cached_pow = get_cached_power( // \tilde{c}_{-k} in Grisu.
|
||||
min_exp - (upper.e + fp::significand_size), cached_exp10);
|
||||
fp_value.normalize();
|
||||
fp_value = fp_value * cached_pow;
|
||||
fp normalized = normalize(fp_value);
|
||||
normalized = normalized * cached_pow;
|
||||
lower = lower * cached_pow; // \tilde{M}^- in Grisu.
|
||||
upper = upper * cached_pow; // \tilde{M}^+ in Grisu.
|
||||
assert(min_exp <= upper.e && upper.e <= -32);
|
||||
@@ -735,17 +824,21 @@ FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
|
||||
--lower.f; // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}.
|
||||
++upper.f; // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}.
|
||||
// Numbers outside of (lower, upper) definitely do not round to value.
|
||||
grisu_shortest_handler<3> handler{buf.data(), 0, (upper - fp_value).f};
|
||||
grisu_shortest_handler<3> handler{buf.data(), 0, (upper - normalized).f};
|
||||
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
||||
size = handler.size;
|
||||
if (result == digits::error) {
|
||||
fallback_format(fp_value, exp - cached_exp10);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
++lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
||||
--upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
|
||||
grisu_shortest_handler<2> handler{buf.data(), 0, (upper - fp_value).f};
|
||||
grisu_shortest_handler<2> handler{buf.data(), 0, (upper - normalized).f};
|
||||
result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
|
||||
size = handler.size;
|
||||
assert(result != digits::error);
|
||||
}
|
||||
if (result == digits::error) return false;
|
||||
buf.resize(to_unsigned(size));
|
||||
}
|
||||
exp -= cached_exp10;
|
||||
@@ -831,6 +924,29 @@ char* sprintf_format(Double value, internal::buffer<char>& buf,
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template <> struct formatter<internal::bigint> {
|
||||
format_parse_context::iterator parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
format_context::iterator format(const internal::bigint& n,
|
||||
format_context& ctx) {
|
||||
auto out = ctx.out();
|
||||
bool first = true;
|
||||
for (auto i = n.bigits_.size(); i > 0; --i) {
|
||||
auto value = n.bigits_[i - 1];
|
||||
if (first) {
|
||||
if (value == 0 && i > 1) continue;
|
||||
out = format_to(out, "{:x}", value);
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
out = format_to(out, "{:08x}", value);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
|
||||
@@ -45,6 +45,14 @@
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#ifndef FMT_USE_TEXT
|
||||
# define FMT_USE_TEXT 0
|
||||
#endif
|
||||
#if FMT_USE_TEXT
|
||||
# include <boost/text/grapheme_break.hpp>
|
||||
# include <boost/text/transcode_iterator.hpp>
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
|
||||
#else
|
||||
@@ -123,14 +131,6 @@ FMT_END_NAMESPACE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef FMT_USE_INT128
|
||||
// Do nothing.
|
||||
#elif defined(__SIZEOF_INT128__)
|
||||
# define FMT_USE_INT128 1
|
||||
#else
|
||||
# define FMT_USE_INT128 0
|
||||
#endif
|
||||
|
||||
// __builtin_clz is broken in clang with Microsoft CodeGen:
|
||||
// https://github.com/fmtlib/fmt/issues/519
|
||||
#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER
|
||||
@@ -193,6 +193,10 @@ inline uint32_t clzll(uint64_t x) {
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#ifndef FMT_NUMERIC_ALIGN
|
||||
# define FMT_NUMERIC_ALIGN 1
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
@@ -217,6 +221,12 @@ inline Dest bit_cast(const Source& source) {
|
||||
return dest;
|
||||
}
|
||||
|
||||
// Returns the largest possible value for type T. Same as
|
||||
// std::numeric_limits<T>::max() but shorter and not affected by the max macro.
|
||||
template <typename T> constexpr T max_value() {
|
||||
return (std::numeric_limits<T>::max)();
|
||||
}
|
||||
|
||||
// An approximation of iterator_t for pre-C++20 systems.
|
||||
template <typename T>
|
||||
using iterator_t = decltype(std::begin(std::declval<T&>()));
|
||||
@@ -413,22 +423,6 @@ class output_range {
|
||||
sentinel end() const { return {}; } // Sentinel is not used yet.
|
||||
};
|
||||
|
||||
// A range with an iterator appending to a buffer.
|
||||
template <typename T>
|
||||
class buffer_range
|
||||
: public output_range<std::back_insert_iterator<buffer<T>>, T> {
|
||||
public:
|
||||
using iterator = std::back_insert_iterator<buffer<T>>;
|
||||
using output_range<iterator, T>::output_range;
|
||||
buffer_range(buffer<T>& buf)
|
||||
: output_range<iterator, T>(std::back_inserter(buf)) {}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
inline size_t count_code_points(basic_string_view<Char> s) {
|
||||
return s.size();
|
||||
}
|
||||
|
||||
// Counts the number of code points in a UTF-8 string.
|
||||
inline size_t count_code_points(basic_string_view<char8_t> s) {
|
||||
const char8_t* data = s.data();
|
||||
@@ -460,7 +454,7 @@ OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) {
|
||||
}
|
||||
|
||||
#ifndef FMT_USE_GRISU
|
||||
# define FMT_USE_GRISU 0
|
||||
# define FMT_USE_GRISU 1
|
||||
#endif
|
||||
|
||||
template <typename T> constexpr bool use_grisu() {
|
||||
@@ -478,6 +472,17 @@ void buffer<T>::append(const U* begin, const U* end) {
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
// A range with an iterator appending to a buffer.
|
||||
template <typename T>
|
||||
class buffer_range : public internal::output_range<
|
||||
std::back_insert_iterator<internal::buffer<T>>, T> {
|
||||
public:
|
||||
using iterator = std::back_insert_iterator<internal::buffer<T>>;
|
||||
using internal::output_range<iterator, T>::output_range;
|
||||
buffer_range(internal::buffer<T>& buf)
|
||||
: internal::output_range<iterator, T>(std::back_inserter(buf)) {}
|
||||
};
|
||||
|
||||
// A UTF-8 string view.
|
||||
class u8string_view : public basic_string_view<char8_t> {
|
||||
public:
|
||||
@@ -644,11 +649,12 @@ FMT_CONSTEXPR bool is_negative(T) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Smallest of uint32_t and uint64_t that is large enough to represent all
|
||||
// values of T.
|
||||
// Smallest of uint32_t, uint64_t, uint128_t that is large enough to
|
||||
// represent all values of T.
|
||||
template <typename T>
|
||||
using uint32_or_64_t =
|
||||
conditional_t<std::numeric_limits<T>::digits <= 32, uint32_t, uint64_t>;
|
||||
using uint32_or_64_or_128_t = conditional_t<
|
||||
std::numeric_limits<T>::digits <= 32, uint32_t,
|
||||
conditional_t<std::numeric_limits<T>::digits <= 64, uint64_t, uint128_t>>;
|
||||
|
||||
// Static data is placed in this class template for the header-only config.
|
||||
template <typename T = void> struct FMT_EXTERN_TEMPLATE_API basic_data {
|
||||
@@ -697,6 +703,23 @@ inline int count_digits(uint64_t n) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_USE_INT128
|
||||
inline int count_digits(uint128_t n) {
|
||||
int count = 1;
|
||||
for (;;) {
|
||||
// Integer division is slow so do it for a group of four digits instead
|
||||
// of for every digit. The idea comes from the talk by Alexandrescu
|
||||
// "Three Optimization Tips for C++". See speed-test for a comparison.
|
||||
if (n < 10) return count;
|
||||
if (n < 100) return count + 1;
|
||||
if (n < 1000) return count + 2;
|
||||
if (n < 10000) return count + 3;
|
||||
n /= 10000u;
|
||||
count += 4;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Counts the number of digits in n. BITS = log2(radix).
|
||||
template <unsigned BITS, typename UInt> inline int count_digits(UInt n) {
|
||||
int num_digits = 0;
|
||||
@@ -826,12 +849,18 @@ inline Char* format_decimal(Char* buffer, UInt value, int num_digits,
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename Int> constexpr int digits10() noexcept {
|
||||
return std::numeric_limits<Int>::digits10;
|
||||
}
|
||||
template <> constexpr int digits10<int128_t>() noexcept { return 38; }
|
||||
template <> constexpr int digits10<uint128_t>() noexcept { return 38; }
|
||||
|
||||
template <typename Char, typename UInt, typename Iterator, typename F>
|
||||
inline Iterator format_decimal(Iterator out, UInt value, int num_digits,
|
||||
F add_thousands_sep) {
|
||||
FMT_ASSERT(num_digits >= 0, "invalid digit count");
|
||||
// Buffer should be large enough to hold all digits (<= digits10 + 1).
|
||||
enum { max_size = std::numeric_limits<UInt>::digits10 + 1 };
|
||||
enum { max_size = digits10<UInt>() + 1 };
|
||||
Char buffer[max_size + max_size / 3];
|
||||
auto end = format_decimal(buffer, value, num_digits, add_thousands_sep);
|
||||
return internal::copy_str<Char>(buffer, end, out);
|
||||
@@ -886,6 +915,59 @@ inline It format_uint(It out, UInt value, int num_digits, bool upper = false) {
|
||||
return internal::copy_str<Char>(buffer, buffer + num_digits, out);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline size_t compute_width(basic_string_view<Char> s) {
|
||||
return s.size();
|
||||
}
|
||||
|
||||
inline size_t compute_width(string_view s) {
|
||||
#if FMT_USE_TEXT
|
||||
basic_memory_buffer<uint32_t> code_points;
|
||||
const char* s_end = s.data() + s.size();
|
||||
boost::text::utf_8_to_32_iterator<const char*> begin(s.data(), s.data(),
|
||||
s_end),
|
||||
end(s.data(), s_end, s_end);
|
||||
for (auto it = begin; it != end; ++it) code_points.push_back(*it);
|
||||
size_t width = 0;
|
||||
for (auto it = code_points.begin(), end = code_points.end(); it != end;
|
||||
it = boost::text::next_grapheme_break(it, end)) {
|
||||
auto cp = *it;
|
||||
// Based on http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c by Markus Kuhn.
|
||||
width +=
|
||||
1 + (cp >= 0x1100 &&
|
||||
(cp <= 0x115f || // Hangul Jamo init. consonants
|
||||
cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET〈
|
||||
cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET 〉
|
||||
// CJK ... Yi except Unicode Character “〿”:
|
||||
(cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) ||
|
||||
(cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables
|
||||
(cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs
|
||||
(cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms
|
||||
(cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms
|
||||
(cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms
|
||||
(cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms
|
||||
(cp >= 0x20000 && cp <= 0x2fffd) || // CJK
|
||||
(cp >= 0x30000 && cp <= 0x3fffd) ||
|
||||
// Miscellaneous Symbols and Pictographs + Emoticons:
|
||||
(cp >= 0x1f300 && cp <= 0x1f64f) ||
|
||||
// Supplemental Symbols and Pictographs:
|
||||
(cp >= 0x1f900 && cp <= 0x1f9ff)));
|
||||
}
|
||||
return width;
|
||||
#else
|
||||
return s.size();
|
||||
#endif // FMT_USE_TEXT
|
||||
}
|
||||
|
||||
inline size_t compute_width(basic_string_view<char8_t> s) {
|
||||
#if FMT_USE_TEXT
|
||||
return compute_width(
|
||||
string_view(reinterpret_cast<const char*>(s.data(), s.size())));
|
||||
#else
|
||||
return count_code_points(s);
|
||||
#endif // FMT_USE_TEXT
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
# define FMT_USE_WINDOWS_H 0
|
||||
#elif !defined(FMT_USE_WINDOWS_H)
|
||||
@@ -1332,7 +1414,7 @@ template <typename Range> class basic_writer {
|
||||
|
||||
// Writes a decimal integer.
|
||||
template <typename Int> void write_decimal(Int value) {
|
||||
auto abs_value = static_cast<uint32_or_64_t<Int>>(value);
|
||||
auto abs_value = static_cast<uint32_or_64_or_128_t<Int>>(value);
|
||||
bool is_negative = internal::is_negative(value);
|
||||
if (is_negative) abs_value = 0 - abs_value;
|
||||
int num_digits = internal::count_digits(abs_value);
|
||||
@@ -1344,7 +1426,7 @@ template <typename Range> class basic_writer {
|
||||
|
||||
// The handle_int_type_spec handler that writes an integer.
|
||||
template <typename Int, typename Specs> struct int_writer {
|
||||
using unsigned_type = uint32_or_64_t<Int>;
|
||||
using unsigned_type = uint32_or_64_or_128_t<Int>;
|
||||
|
||||
basic_writer<Range>& writer;
|
||||
const Specs& specs;
|
||||
@@ -1425,7 +1507,7 @@ template <typename Range> class basic_writer {
|
||||
|
||||
void on_oct() {
|
||||
int num_digits = internal::count_digits<3>(abs_value);
|
||||
if (specs.alt && specs.precision <= num_digits) {
|
||||
if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
|
||||
// Octal prefix '0' is counted as a digit, so only add it if precision
|
||||
// is not greater than the number of digits.
|
||||
prefix[prefix_size++] = '0';
|
||||
@@ -1557,7 +1639,7 @@ template <typename Range> class basic_writer {
|
||||
|
||||
size_t size() const { return size_; }
|
||||
size_t width() const {
|
||||
return internal::count_code_points(basic_string_view<Char>(s, size_));
|
||||
return internal::compute_width(basic_string_view<Char>(s, size_));
|
||||
}
|
||||
|
||||
template <typename It> void operator()(It&& it) const {
|
||||
@@ -1621,6 +1703,11 @@ template <typename Range> class basic_writer {
|
||||
void write(unsigned long value) { write_decimal(value); }
|
||||
void write(unsigned long long value) { write_decimal(value); }
|
||||
|
||||
#if FMT_USE_INT128
|
||||
void write(int128_t value) { write_decimal(value); }
|
||||
void write(uint128_t value) { write_decimal(value); }
|
||||
#endif
|
||||
|
||||
// Writes a formatted integer.
|
||||
template <typename T, typename Spec>
|
||||
void write_int(T value, const Spec& spec) {
|
||||
@@ -1702,6 +1789,10 @@ template <typename Range> class basic_writer {
|
||||
|
||||
using writer = basic_writer<buffer_range<char>>;
|
||||
|
||||
template <typename T> struct is_integral : std::is_integral<T> {};
|
||||
template <> struct is_integral<int128_t> : std::true_type {};
|
||||
template <> struct is_integral<uint128_t> : std::true_type {};
|
||||
|
||||
template <typename Range, typename ErrorHandler = internal::error_handler>
|
||||
class arg_formatter_base {
|
||||
public:
|
||||
@@ -1764,7 +1855,7 @@ class arg_formatter_base {
|
||||
return out();
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
if (specs_)
|
||||
writer_.write_int(value, *specs_);
|
||||
@@ -1859,7 +1950,7 @@ FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end,
|
||||
}
|
||||
unsigned value = 0;
|
||||
// Convert to unsigned to prevent a warning.
|
||||
constexpr unsigned max_int = (std::numeric_limits<int>::max)();
|
||||
constexpr unsigned max_int = max_value<int>();
|
||||
unsigned big = max_int / 10;
|
||||
do {
|
||||
// Check for overflow.
|
||||
@@ -1896,7 +1987,7 @@ template <typename Context> class custom_formatter {
|
||||
|
||||
template <typename T>
|
||||
using is_integer =
|
||||
bool_constant<std::is_integral<T>::value && !std::is_same<T, bool>::value &&
|
||||
bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value &&
|
||||
!std::is_same<T, char>::value &&
|
||||
!std::is_same<T, wchar_t>::value>;
|
||||
|
||||
@@ -1981,20 +2072,20 @@ template <typename ErrorHandler> class numeric_specs_checker {
|
||||
: error_handler_(eh), arg_type_(arg_type) {}
|
||||
|
||||
FMT_CONSTEXPR void require_numeric_argument() {
|
||||
if (!is_arithmetic(arg_type_))
|
||||
if (!is_arithmetic_type(arg_type_))
|
||||
error_handler_.on_error("format specifier requires numeric argument");
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void check_sign() {
|
||||
require_numeric_argument();
|
||||
if (is_integral(arg_type_) && arg_type_ != int_type &&
|
||||
if (is_integral_type(arg_type_) && arg_type_ != int_type &&
|
||||
arg_type_ != long_long_type && arg_type_ != internal::char_type) {
|
||||
error_handler_.on_error("format specifier requires signed argument");
|
||||
}
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void check_precision() {
|
||||
if (is_integral(arg_type_) || arg_type_ == internal::pointer_type)
|
||||
if (is_integral_type(arg_type_) || arg_type_ == internal::pointer_type)
|
||||
error_handler_.on_error("precision not allowed for this argument type");
|
||||
}
|
||||
|
||||
@@ -2049,14 +2140,12 @@ template <typename Handler> class specs_checker : public Handler {
|
||||
numeric_specs_checker<Handler> checker_;
|
||||
};
|
||||
|
||||
template <template <typename> class Handler, typename T, typename FormatArg,
|
||||
template <template <typename> class Handler, typename FormatArg,
|
||||
typename ErrorHandler>
|
||||
FMT_CONSTEXPR void set_dynamic_spec(T& value, FormatArg arg, ErrorHandler eh) {
|
||||
unsigned long long big_value =
|
||||
visit_format_arg(Handler<ErrorHandler>(eh), arg);
|
||||
if (big_value > to_unsigned((std::numeric_limits<int>::max)()))
|
||||
eh.on_error("number is too big");
|
||||
value = static_cast<T>(big_value);
|
||||
FMT_CONSTEXPR int get_dynamic_spec(FormatArg arg, ErrorHandler eh) {
|
||||
unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg);
|
||||
if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big");
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
|
||||
struct auto_id {};
|
||||
@@ -2081,13 +2170,13 @@ class specs_handler : public specs_setter<typename Context::char_type> {
|
||||
context_(ctx) {}
|
||||
|
||||
template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
|
||||
set_dynamic_spec<width_checker>(this->specs_.width, get_arg(arg_id),
|
||||
context_.error_handler());
|
||||
this->specs_.width = get_dynamic_spec<width_checker>(
|
||||
get_arg(arg_id), context_.error_handler());
|
||||
}
|
||||
|
||||
template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
|
||||
set_dynamic_spec<precision_checker>(this->specs_.precision, get_arg(arg_id),
|
||||
context_.error_handler());
|
||||
this->specs_.precision = get_dynamic_spec<precision_checker>(
|
||||
get_arg(arg_id), context_.error_handler());
|
||||
}
|
||||
|
||||
void on_error(const char* message) { context_.on_error(message); }
|
||||
@@ -2114,24 +2203,6 @@ class specs_handler : public specs_setter<typename Context::char_type> {
|
||||
Context& context_;
|
||||
};
|
||||
|
||||
struct string_view_metadata {
|
||||
FMT_CONSTEXPR string_view_metadata() : offset_(0u), size_(0u) {}
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR string_view_metadata(basic_string_view<Char> primary_string,
|
||||
basic_string_view<Char> view)
|
||||
: offset_(to_unsigned(view.data() - primary_string.data())),
|
||||
size_(view.size()) {}
|
||||
FMT_CONSTEXPR string_view_metadata(std::size_t offset, std::size_t size)
|
||||
: offset_(offset), size_(size) {}
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR basic_string_view<Char> to_view(const Char* str) const {
|
||||
return {str + offset_, size_};
|
||||
}
|
||||
|
||||
std::size_t offset_;
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
enum class arg_id_kind { none, index, name };
|
||||
|
||||
// An argument reference.
|
||||
@@ -2139,7 +2210,7 @@ template <typename Char> struct arg_ref {
|
||||
FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
|
||||
FMT_CONSTEXPR explicit arg_ref(int index)
|
||||
: kind(arg_id_kind::index), val(index) {}
|
||||
FMT_CONSTEXPR explicit arg_ref(string_view_metadata name)
|
||||
FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
|
||||
: kind(arg_id_kind::name), val(name) {}
|
||||
|
||||
FMT_CONSTEXPR arg_ref& operator=(int idx) {
|
||||
@@ -2152,10 +2223,10 @@ template <typename Char> struct arg_ref {
|
||||
union value {
|
||||
FMT_CONSTEXPR value() : index(0u) {}
|
||||
FMT_CONSTEXPR value(int id) : index(id) {}
|
||||
FMT_CONSTEXPR value(string_view_metadata n) : name(n) {}
|
||||
FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {}
|
||||
|
||||
int index;
|
||||
string_view_metadata name;
|
||||
basic_string_view<Char> name;
|
||||
} val;
|
||||
};
|
||||
|
||||
@@ -2213,8 +2284,7 @@ class dynamic_specs_handler
|
||||
context_.check_arg_id(arg_id);
|
||||
basic_string_view<char_type> format_str(
|
||||
context_.begin(), to_unsigned(context_.end() - context_.begin()));
|
||||
const auto id_metadata = string_view_metadata(format_str, arg_id);
|
||||
return arg_ref_type(id_metadata);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
dynamic_format_specs<char_type>& specs_;
|
||||
@@ -2294,9 +2364,11 @@ FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end,
|
||||
case '>':
|
||||
align = align::right;
|
||||
break;
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
case '=':
|
||||
align = align::numeric;
|
||||
break;
|
||||
#endif
|
||||
case '^':
|
||||
align = align::center;
|
||||
break;
|
||||
@@ -2439,7 +2511,7 @@ template <typename Handler, typename Char> struct id_adapter {
|
||||
template <bool IS_CONSTEXPR, typename Char, typename Handler>
|
||||
FMT_CONSTEXPR void parse_format_string(basic_string_view<Char> format_str,
|
||||
Handler&& handler) {
|
||||
struct writer {
|
||||
struct pfs_writer {
|
||||
FMT_CONSTEXPR void operator()(const Char* begin, const Char* end) {
|
||||
if (begin == end) return;
|
||||
for (;;) {
|
||||
@@ -2509,7 +2581,7 @@ class format_string_checker {
|
||||
public:
|
||||
explicit FMT_CONSTEXPR format_string_checker(
|
||||
basic_string_view<Char> format_str, ErrorHandler eh)
|
||||
: arg_id_((std::numeric_limits<unsigned>::max)()),
|
||||
: arg_id_(max_value<unsigned>()),
|
||||
context_(format_str, eh),
|
||||
parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
|
||||
|
||||
@@ -2573,32 +2645,29 @@ void check_format_string(S format_str) {
|
||||
(void)invalid_format;
|
||||
}
|
||||
|
||||
template <template <typename> class Handler, typename Spec, typename Context>
|
||||
void handle_dynamic_spec(Spec& value, arg_ref<typename Context::char_type> ref,
|
||||
Context& ctx,
|
||||
const typename Context::char_type* format_str) {
|
||||
template <template <typename> class Handler, typename Context>
|
||||
void handle_dynamic_spec(int& value, arg_ref<typename Context::char_type> ref,
|
||||
Context& ctx) {
|
||||
switch (ref.kind) {
|
||||
case arg_id_kind::none:
|
||||
break;
|
||||
case arg_id_kind::index:
|
||||
internal::set_dynamic_spec<Handler>(value, ctx.arg(ref.val.index),
|
||||
ctx.error_handler());
|
||||
value = internal::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
|
||||
ctx.error_handler());
|
||||
break;
|
||||
case arg_id_kind::name: {
|
||||
const auto arg_id = ref.val.name.to_view(format_str);
|
||||
internal::set_dynamic_spec<Handler>(value, ctx.arg(arg_id),
|
||||
ctx.error_handler());
|
||||
case arg_id_kind::name:
|
||||
value = internal::get_dynamic_spec<Handler>(ctx.arg(ref.val.name),
|
||||
ctx.error_handler());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template <typename Range>
|
||||
using basic_writer FMT_DEPRECATED = internal::basic_writer<Range>;
|
||||
using writer FMT_DEPRECATED = internal::writer;
|
||||
using wwriter FMT_DEPRECATED =
|
||||
internal::basic_writer<internal::buffer_range<wchar_t>>;
|
||||
using basic_writer FMT_DEPRECATED_ALIAS = internal::basic_writer<Range>;
|
||||
using writer FMT_DEPRECATED_ALIAS = internal::writer;
|
||||
using wwriter FMT_DEPRECATED_ALIAS =
|
||||
internal::basic_writer<buffer_range<wchar_t>>;
|
||||
|
||||
/** The default argument formatter. */
|
||||
template <typename Range>
|
||||
@@ -2777,12 +2846,12 @@ void internal::basic_writer<Range>::write_double(T value,
|
||||
memory_buffer buffer;
|
||||
int exp = 0;
|
||||
int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
|
||||
unsigned options = handler.fixed ? internal::grisu_options::fixed : 0;
|
||||
bool use_grisu = USE_GRISU &&
|
||||
(specs.type != 'a' && specs.type != 'A' &&
|
||||
specs.type != 'e' && specs.type != 'E') &&
|
||||
internal::grisu_format(static_cast<double>(value), buffer,
|
||||
precision, options, exp);
|
||||
internal::grisu_format(
|
||||
static_cast<double>(value), buffer, precision,
|
||||
handler.fixed ? internal::grisu_options::fixed : 0, exp);
|
||||
char* decimal_point_pos = nullptr;
|
||||
if (!use_grisu)
|
||||
decimal_point_pos = internal::sprintf_format(value, buffer, specs);
|
||||
@@ -2954,13 +3023,12 @@ template <typename T, typename Char>
|
||||
struct formatter<T, Char,
|
||||
enable_if_t<internal::type_constant<T, Char>::value !=
|
||||
internal::custom_type>> {
|
||||
FMT_CONSTEXPR formatter() : format_str_(nullptr) {}
|
||||
FMT_CONSTEXPR formatter() {}
|
||||
|
||||
// Parses format specifiers stopping either at the end of the range or at the
|
||||
// terminating '}'.
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
format_str_ = ctx.begin();
|
||||
using handler_type = internal::dynamic_specs_handler<ParseContext>;
|
||||
auto type = internal::type_constant<T, Char>::value;
|
||||
internal::specs_checker<handler_type> handler(handler_type(specs_, ctx),
|
||||
@@ -2976,6 +3044,8 @@ struct formatter<T, Char,
|
||||
case internal::uint_type:
|
||||
case internal::long_long_type:
|
||||
case internal::ulong_long_type:
|
||||
case internal::int128_type:
|
||||
case internal::uint128_type:
|
||||
case internal::bool_type:
|
||||
handle_int_type_spec(specs_.type,
|
||||
internal::int_type_checker<decltype(eh)>(eh));
|
||||
@@ -3010,9 +3080,9 @@ struct formatter<T, Char,
|
||||
template <typename FormatContext>
|
||||
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
internal::handle_dynamic_spec<internal::width_checker>(
|
||||
specs_.width, specs_.width_ref, ctx, format_str_);
|
||||
specs_.width, specs_.width_ref, ctx);
|
||||
internal::handle_dynamic_spec<internal::precision_checker>(
|
||||
specs_.precision, specs_.precision_ref, ctx, format_str_);
|
||||
specs_.precision, specs_.precision_ref, ctx);
|
||||
using range_type =
|
||||
internal::output_range<typename FormatContext::iterator,
|
||||
typename FormatContext::char_type>;
|
||||
@@ -3022,7 +3092,6 @@ struct formatter<T, Char,
|
||||
|
||||
private:
|
||||
internal::dynamic_format_specs<Char> specs_;
|
||||
const Char* format_str_;
|
||||
};
|
||||
|
||||
#define FMT_FORMAT_AS(Type, Base) \
|
||||
@@ -3123,9 +3192,9 @@ template <typename Char = char> class dynamic_formatter {
|
||||
private:
|
||||
template <typename Context> void handle_specs(Context& ctx) {
|
||||
internal::handle_dynamic_spec<internal::width_checker>(
|
||||
specs_.width, specs_.width_ref, ctx, format_str_);
|
||||
specs_.width, specs_.width_ref, ctx);
|
||||
internal::handle_dynamic_spec<internal::precision_checker>(
|
||||
specs_.precision, specs_.precision_ref, ctx, format_str_);
|
||||
specs_.precision, specs_.precision_ref, ctx);
|
||||
}
|
||||
|
||||
internal::dynamic_format_specs<Char> specs_;
|
||||
@@ -3466,7 +3535,7 @@ template <typename Char, Char... CHARS> class udl_formatter {
|
||||
std::basic_string<Char> operator()(Args&&... args) const {
|
||||
FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'};
|
||||
FMT_CONSTEXPR_DECL bool invalid_format =
|
||||
do_check_format_string<Char, error_handler, Args...>(
|
||||
do_check_format_string<Char, error_handler, remove_cvref_t<Args>...>(
|
||||
basic_string_view<Char>(s, sizeof...(CHARS)));
|
||||
(void)invalid_format;
|
||||
return format(s, std::forward<Args>(args)...);
|
||||
|
||||
@@ -46,9 +46,13 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
struct null;
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void operator<<(null);
|
||||
void_t<> operator<<(null<>);
|
||||
void_t<> operator<<(const Char*);
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
|
||||
!std::is_enum<T>::value)>
|
||||
void_t<> operator<<(T);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
@@ -56,9 +60,9 @@ template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static decltype((void)(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
std::true_type())
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
@@ -75,8 +79,7 @@ void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size =
|
||||
to_unsigned((std::numeric_limits<std::streamsize>::max)());
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
|
||||
@@ -24,7 +24,7 @@ template <typename T> inline T const_check(T value) { return value; }
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
unsigned max = std::numeric_limits<int>::max();
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
@@ -33,7 +33,7 @@ template <bool IsSigned> struct int_checker {
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
return value >= std::numeric_limits<int>::min() &&
|
||||
value <= std::numeric_limits<int>::max();
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
@@ -158,12 +158,12 @@ template <typename Char> class printf_width_handler {
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto width = static_cast<uint32_or_64_t<T>>(value);
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = std::numeric_limits<int>::max();
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
@@ -339,7 +339,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is equal
|
||||
// to the maximum unsigned value, the next argument.
|
||||
format_arg get_arg(unsigned arg_index = std::numeric_limits<unsigned>::max());
|
||||
format_arg get_arg(unsigned arg_index = internal::max_value<unsigned>());
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
unsigned parse_header(const Char*& it, const Char* end, format_specs& specs);
|
||||
@@ -368,8 +368,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
}
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter =
|
||||
printf_arg_formatter<internal::buffer_range<Char>>>
|
||||
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
|
||||
OutputIt format();
|
||||
};
|
||||
|
||||
@@ -403,7 +402,7 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
template <typename OutputIt, typename Char>
|
||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
||||
basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) {
|
||||
if (arg_index == std::numeric_limits<unsigned>::max())
|
||||
if (arg_index == internal::max_value<unsigned>())
|
||||
arg_index = parse_ctx_.next_arg_id();
|
||||
else
|
||||
parse_ctx_.check_arg_id(--arg_index);
|
||||
@@ -413,7 +412,7 @@ basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) {
|
||||
template <typename OutputIt, typename Char>
|
||||
unsigned basic_printf_context<OutputIt, Char>::parse_header(
|
||||
const Char*& it, const Char* end, format_specs& specs) {
|
||||
unsigned arg_index = std::numeric_limits<unsigned>::max();
|
||||
unsigned arg_index = internal::max_value<unsigned>();
|
||||
char_type c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
|
||||
@@ -246,7 +246,8 @@ template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_range_<T>::value &&
|
||||
!internal::is_like_std_string<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value;
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<internal::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
@@ -283,6 +284,82 @@ struct formatter<RangeT, Char,
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple{t}, sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
||||
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename FormatContext, size_t... N>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
internal::index_sequence<N...>) {
|
||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
|
||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||
// can be replaced with a constexpr branch in the variadic overload.
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, typename Arg, typename... Args>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
const Arg& arg, const Args&... args) {
|
||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||
auto out = ctx.out();
|
||||
out = base{}.format(arg, ctx);
|
||||
if (sizeof...(Args) > 0) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
||||
string_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
wstring_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
||||
@@ -62,7 +62,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
|
||||
if (F::is_signed && !T::is_signed) {
|
||||
// From may be negative, not allowed!
|
||||
if (from < 0) {
|
||||
if (fmt::internal::is_negative(from)) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
@@ -161,7 +161,8 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
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>;
|
||||
struct 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");
|
||||
@@ -182,14 +183,12 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
}
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 =
|
||||
std::numeric_limits<IntermediateRep>::max() / Factor::num;
|
||||
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
constexpr auto min1 =
|
||||
std::numeric_limits<IntermediateRep>::min() / Factor::num;
|
||||
const auto min1 = std::numeric_limits<IntermediateRep>::min() / Factor::num;
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
@@ -234,7 +233,8 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
|
||||
// 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>;
|
||||
struct 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");
|
||||
@@ -257,7 +257,7 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() /
|
||||
constexpr auto max1 = internal::max_value<IntermediateRep>() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
#include "fmt/posix.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <climits>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
typedef int RWResult;
|
||||
using RWResult = int;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
@@ -58,7 +58,7 @@ inline unsigned convert_rwcount(std::size_t count) {
|
||||
}
|
||||
#else
|
||||
// Return type of read and write functions.
|
||||
typedef ssize_t RWResult;
|
||||
using RWResult = ssize_t;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
@@ -138,7 +138,7 @@ long long file::size() const {
|
||||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
#else
|
||||
typedef struct stat Stat;
|
||||
using Stat = struct stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
|
||||
4
src/text/boost/assert.hpp
Normal file
4
src/text/boost/assert.hpp
Normal file
@@ -0,0 +1,4 @@
|
||||
#ifndef BOOST_ASSERT
|
||||
#include <assert.h>
|
||||
# define BOOST_ASSERT(condition) assert(condition)
|
||||
#endif
|
||||
13
src/text/boost/container/small_vector.hpp
Normal file
13
src/text/boost/container/small_vector.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef TEXT_BOOST_CONTAINER_SMALL_VECTOR_HPP
|
||||
#define TEXT_BOOST_CONTAINER_SMALL_VECTOR_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace container {
|
||||
template <typename T, size_t>
|
||||
using small_vector = std::vector<T>;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TEXT_BOOST_CONTAINER_SMALL_VECTOR_HPP
|
||||
102
src/text/boost/text/config.hpp
Executable file
102
src/text/boost/text/config.hpp
Executable file
@@ -0,0 +1,102 @@
|
||||
#ifndef BOOST_TEXT_CONFIG_HPP
|
||||
#define BOOST_TEXT_CONFIG_HPP
|
||||
|
||||
|
||||
/** There are ICU-based implementations of many operations, but those are only
|
||||
defined when BOOST_TEXT_HAS_ICU is nonzero. If you define this, you must
|
||||
make sure the the ICU headers are in your path, and that your build
|
||||
properly links in ICU. */
|
||||
#ifndef BOOST_TEXT_HAS_ICU
|
||||
# define BOOST_TEXT_HAS_ICU 0
|
||||
#endif
|
||||
|
||||
/** There are ICU-based implementations of many operations, but those are only
|
||||
used when BOOST_TEXT_HAS_ICU and BOOST_TEXT_USE_ICU are both nonzero. */
|
||||
#ifndef BOOST_TEXT_USE_ICU
|
||||
# define BOOST_TEXT_USE_ICU 0
|
||||
#endif
|
||||
|
||||
/** When you insert into a rope, the incoming sequence may be inserted as a
|
||||
new segment, or if it falls within an existing string-segment, it may be
|
||||
inserted into the string object used to represent that segment. This only
|
||||
happens if the incoming sequence will fit within the existing segment's
|
||||
capacity, or if the segment is smaller than a certain limit.
|
||||
BOOST_TEXT_STRING_INSERT_MAX is that limit. */
|
||||
#ifndef BOOST_TEXT_STRING_INSERT_MAX
|
||||
# define BOOST_TEXT_STRING_INSERT_MAX 4096
|
||||
#endif
|
||||
|
||||
#ifndef BOOST_TEXT_DOXYGEN
|
||||
|
||||
// Nothing before GCC 6 has proper C++14 constexpr support.
|
||||
#if defined(__GNUC__) && __GNUC__ < 6 && !defined(__clang__)
|
||||
# define BOOST_TEXT_CXX14_CONSTEXPR
|
||||
# define BOOST_TEXT_NO_CXX14_CONSTEXPR
|
||||
#elif defined(_MSC_VER) && _MSC_VER <= 1915
|
||||
# define BOOST_TEXT_CXX14_CONSTEXPR
|
||||
# define BOOST_TEXT_NO_CXX14_CONSTEXPR
|
||||
#else
|
||||
# define BOOST_TEXT_CXX14_CONSTEXPR
|
||||
# if defined(BOOST_NO_CXX14_CONSTEXPR)
|
||||
# define BOOST_TEXT_NO_CXX14_CONSTEXPR
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Implements separate compilation features as described in
|
||||
// http://www.boost.org/more/separate_compilation.html
|
||||
|
||||
// normalize macros
|
||||
|
||||
#if !defined(BOOST_TEXT_DYN_LINK) && !defined(BOOST_TEXT_STATIC_LINK) && \
|
||||
!defined(BOOST_ALL_DYN_LINK) && !defined(BOOST_ALL_STATIC_LINK)
|
||||
# define BOOST_TEXT_STATIC_LINK
|
||||
#endif
|
||||
|
||||
#if defined(BOOST_ALL_DYN_LINK) && !defined(BOOST_TEXT_DYN_LINK)
|
||||
# define BOOST_TEXT_DYN_LINK
|
||||
#elif defined(BOOST_ALL_STATIC_LINK) && !defined(BOOST_TEXT_STATIC_LINK)
|
||||
# define BOOST_TEXT_STATIC_LINK
|
||||
#endif
|
||||
|
||||
#if defined(BOOST_TEXT_DYN_LINK) && defined(BOOST_TEXT_STATIC_LINK)
|
||||
# error Must not define both BOOST_TEXT_DYN_LINK and BOOST_TEXT_STATIC_LINK
|
||||
#endif
|
||||
|
||||
// enable dynamic or static linking as requested
|
||||
|
||||
#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_TEXT_DYN_LINK)
|
||||
# if defined(BOOST_TEXT_SOURCE)
|
||||
# define BOOST_TEXT_DECL BOOST_SYMBOL_EXPORT
|
||||
# else
|
||||
# define BOOST_TEXT_DECL BOOST_SYMBOL_IMPORT
|
||||
# endif
|
||||
#else
|
||||
# define BOOST_TEXT_DECL
|
||||
#endif
|
||||
|
||||
#if 0 // TODO: Disabled for now.
|
||||
// enable automatic library variant selection
|
||||
|
||||
#if !defined(BOOST_TEXT_SOURCE) && !defined(BOOST_ALL_NO_LIB) && \
|
||||
!defined(BOOST_TEXT_NO_LIB)
|
||||
//
|
||||
// Set the name of our library, this will get undef'ed by auto_link.hpp
|
||||
// once it's done with it:
|
||||
//
|
||||
#define BOOST_LIB_NAME boost_text
|
||||
//
|
||||
// If we're importing code from a dll, then tell auto_link.hpp about it:
|
||||
//
|
||||
#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_TEXT_DYN_LINK)
|
||||
# define BOOST_DYN_LINK
|
||||
#endif
|
||||
//
|
||||
// And include the header that does the work:
|
||||
//
|
||||
#include <boost/config/auto_link.hpp>
|
||||
#endif // auto-linking disabled
|
||||
#endif
|
||||
|
||||
#endif // doxygen
|
||||
|
||||
#endif
|
||||
51
src/text/boost/text/detail/break_prop_iter.hpp
Normal file
51
src/text/boost/text/detail/break_prop_iter.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef BOOST_TEXT_DETAIL_BREAK_PROP_ITER_HPP
|
||||
#define BOOST_TEXT_DETAIL_BREAK_PROP_ITER_HPP
|
||||
|
||||
#include <boost/text/detail/lzw.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace boost { namespace text { namespace detail {
|
||||
|
||||
template<typename Enum>
|
||||
struct lzw_to_break_prop_iter
|
||||
{
|
||||
using value_type = std::pair<uint32_t, Enum>;
|
||||
using difference_type = int;
|
||||
using pointer = unsigned char *;
|
||||
using reference = unsigned char &;
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using buffer_t = container::small_vector<unsigned char, 256>;
|
||||
|
||||
lzw_to_break_prop_iter(
|
||||
std::unordered_map<uint32_t, Enum> & map, buffer_t & buf) :
|
||||
map_(&map),
|
||||
buf_(&buf)
|
||||
{}
|
||||
|
||||
lzw_to_break_prop_iter & operator=(unsigned char c)
|
||||
{
|
||||
buf_->push_back(c);
|
||||
auto const element_bytes = 4;
|
||||
auto it = buf_->begin();
|
||||
for (auto end = buf_->end() - buf_->size() % element_bytes;
|
||||
it != end;
|
||||
it += element_bytes) {
|
||||
(*map_)[bytes_to_cp(&*it)] = Enum(*(it + 3));
|
||||
}
|
||||
buf_->erase(buf_->begin(), it);
|
||||
return *this;
|
||||
}
|
||||
lzw_to_break_prop_iter & operator*() { return *this; }
|
||||
lzw_to_break_prop_iter & operator++() { return *this; }
|
||||
lzw_to_break_prop_iter & operator++(int) { return *this; }
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, Enum> * map_;
|
||||
buffer_t * buf_;
|
||||
};
|
||||
|
||||
}}}
|
||||
|
||||
#endif
|
||||
104
src/text/boost/text/detail/lzw.hpp
Normal file
104
src/text/boost/text/detail/lzw.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#ifndef BOOST_TEXT_DETAIL_LZW_HPP
|
||||
#define BOOST_TEXT_DETAIL_LZW_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace boost { namespace text { namespace detail {
|
||||
|
||||
inline uint32_t bytes_to_uint32_t(unsigned char const * chars)
|
||||
{
|
||||
return chars[0] << 24 | chars[1] << 16 | chars[2] << 8 | chars[3] << 0;
|
||||
}
|
||||
|
||||
inline uint32_t bytes_to_cp(unsigned char const * chars)
|
||||
{
|
||||
return chars[0] << 16 | chars[1] << 8 | chars[2] << 0;
|
||||
}
|
||||
|
||||
inline uint32_t bytes_to_uint16_t(unsigned char const * chars)
|
||||
{
|
||||
return chars[0] << 8 | chars[1] << 0;
|
||||
}
|
||||
|
||||
enum : uint16_t { no_predecessor = 0xffff, no_value = 0xffff };
|
||||
|
||||
struct lzw_reverse_table_element
|
||||
{
|
||||
lzw_reverse_table_element(
|
||||
uint16_t pred = no_predecessor, uint16_t value = no_value) :
|
||||
pred_(pred),
|
||||
value_(value)
|
||||
{}
|
||||
uint16_t pred_;
|
||||
uint16_t value_;
|
||||
};
|
||||
|
||||
using lzw_reverse_table = std::vector<lzw_reverse_table_element>;
|
||||
|
||||
template<typename OutIter>
|
||||
OutIter
|
||||
copy_table_entry(lzw_reverse_table const & table, uint16_t i, OutIter out)
|
||||
{
|
||||
*out++ = table[i].value_;
|
||||
while (table[i].pred_ != no_predecessor) {
|
||||
i = table[i].pred_;
|
||||
*out++ = table[i].value_;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Hardcoded to 16 bits. Takes unsigned 16-bit LZW-compressed values as
|
||||
// input and writes the decompressed unsigned char values to out.
|
||||
template<typename Iter, typename OutIter>
|
||||
OutIter lzw_decompress(Iter first, Iter last, OutIter out)
|
||||
{
|
||||
lzw_reverse_table reverse_table(1 << 16);
|
||||
for (uint16_t i = 0; i < 256u; ++i) {
|
||||
reverse_table[i].value_ = i;
|
||||
}
|
||||
|
||||
container::small_vector<unsigned char, 256> table_entry;
|
||||
|
||||
uint32_t next_table_value = 256;
|
||||
uint32_t const end_table_value = 1 << 16;
|
||||
|
||||
uint16_t prev_code = *first++;
|
||||
BOOST_ASSERT(prev_code < 256);
|
||||
unsigned char c = (unsigned char)prev_code;
|
||||
table_entry.push_back(c);
|
||||
*out++ = table_entry;
|
||||
|
||||
while (first != last) {
|
||||
uint16_t const code = *first++;
|
||||
|
||||
table_entry.clear();
|
||||
if (reverse_table[code].value_ == no_value) {
|
||||
table_entry.push_back(c);
|
||||
copy_table_entry(
|
||||
reverse_table, prev_code, std::back_inserter(table_entry));
|
||||
} else {
|
||||
copy_table_entry(
|
||||
reverse_table, code, std::back_inserter(table_entry));
|
||||
}
|
||||
|
||||
*out++ = table_entry;
|
||||
c = table_entry.back();
|
||||
|
||||
if (next_table_value < end_table_value) {
|
||||
reverse_table[next_table_value++] =
|
||||
lzw_reverse_table_element{prev_code, c};
|
||||
}
|
||||
|
||||
prev_code = code;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
}}}
|
||||
|
||||
#endif
|
||||
224
src/text/boost/text/grapheme_break.hpp
Normal file
224
src/text/boost/text/grapheme_break.hpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#ifndef BOOST_TEXT_GRAPHEME_BREAK_HPP
|
||||
#define BOOST_TEXT_GRAPHEME_BREAK_HPP
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BOOST_TEXT_DECL
|
||||
|
||||
namespace boost { namespace text {
|
||||
|
||||
/** Analogue of `std::find()` that finds the last value `v` in `[first,
|
||||
last)` for which `p(v)` is true. */
|
||||
template<typename BidiIter, typename Pred>
|
||||
BidiIter find_if_backward(BidiIter first, BidiIter last, Pred p)
|
||||
{
|
||||
auto it = last;
|
||||
while (it != first) {
|
||||
if (p(*--it))
|
||||
return it;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
/** The grapheme properties defined by Unicode. */
|
||||
enum class grapheme_property {
|
||||
Other,
|
||||
CR,
|
||||
LF,
|
||||
Control,
|
||||
Extend,
|
||||
Regional_Indicator,
|
||||
Prepend,
|
||||
SpacingMark,
|
||||
L,
|
||||
V,
|
||||
T,
|
||||
LV,
|
||||
LVT,
|
||||
ExtPict,
|
||||
ZWJ
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct grapheme_prop_interval
|
||||
{
|
||||
uint32_t lo_;
|
||||
uint32_t hi_;
|
||||
grapheme_property prop_;
|
||||
};
|
||||
|
||||
inline bool operator<(
|
||||
grapheme_prop_interval lhs, grapheme_prop_interval rhs) noexcept
|
||||
{
|
||||
return lhs.hi_ <= rhs.lo_;
|
||||
}
|
||||
|
||||
BOOST_TEXT_DECL std::array<grapheme_prop_interval, 6> const &
|
||||
make_grapheme_prop_intervals();
|
||||
BOOST_TEXT_DECL std::unordered_map<uint32_t, grapheme_property>
|
||||
make_grapheme_prop_map();
|
||||
}
|
||||
|
||||
/** Returns the grapheme property associated with code point `cp`. */
|
||||
inline grapheme_property grapheme_prop(uint32_t cp) noexcept
|
||||
{
|
||||
static auto const map = detail::make_grapheme_prop_map();
|
||||
static auto const intervals = detail::make_grapheme_prop_intervals();
|
||||
|
||||
auto const it = map.find(cp);
|
||||
if (it == map.end()) {
|
||||
auto const it2 = std::lower_bound(
|
||||
intervals.begin(),
|
||||
intervals.end(),
|
||||
detail::grapheme_prop_interval{cp, cp + 1});
|
||||
if (it2 == intervals.end() || cp < it2->lo_ || it2->hi_ <= cp)
|
||||
return grapheme_property::Other;
|
||||
return it2->prop_;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
inline bool skippable(grapheme_property prop) noexcept
|
||||
{
|
||||
return prop == grapheme_property::Extend;
|
||||
}
|
||||
|
||||
enum class grapheme_break_emoji_state_t {
|
||||
none,
|
||||
first_emoji, // Indicates that prop points to an odd-count
|
||||
// emoji.
|
||||
second_emoji // Indicates that prop points to an even-count
|
||||
// emoji.
|
||||
};
|
||||
|
||||
template<typename CPIter>
|
||||
struct grapheme_break_state
|
||||
{
|
||||
CPIter it;
|
||||
|
||||
grapheme_property prev_prop;
|
||||
grapheme_property prop;
|
||||
|
||||
grapheme_break_emoji_state_t emoji_state;
|
||||
};
|
||||
|
||||
template<typename CPIter>
|
||||
grapheme_break_state<CPIter> next(grapheme_break_state<CPIter> state)
|
||||
{
|
||||
++state.it;
|
||||
state.prev_prop = state.prop;
|
||||
return state;
|
||||
}
|
||||
|
||||
template<typename CPIter>
|
||||
grapheme_break_state<CPIter> prev(grapheme_break_state<CPIter> state)
|
||||
{
|
||||
--state.it;
|
||||
state.prop = state.prev_prop;
|
||||
return state;
|
||||
}
|
||||
|
||||
template<typename CPIter>
|
||||
bool gb11_prefix(CPIter first, CPIter prev_it)
|
||||
{
|
||||
auto final_prop = grapheme_property::Other;
|
||||
find_if_backward(first, prev_it, [&final_prop](uint32_t cp) {
|
||||
final_prop = grapheme_prop(cp);
|
||||
return final_prop != grapheme_property::Extend;
|
||||
});
|
||||
return final_prop == grapheme_property::ExtPict;
|
||||
}
|
||||
|
||||
inline bool table_grapheme_break(
|
||||
grapheme_property lhs, grapheme_property rhs) noexcept
|
||||
{
|
||||
// Note that RI.RI was changed to '1' since that case is handled
|
||||
// in the grapheme break FSM.
|
||||
|
||||
// clang-format off
|
||||
// See chart at https://unicode.org/Public/11.0.0/ucd/auxiliary/GraphemeBreakTest.html .
|
||||
constexpr std::array<std::array<bool, 15>, 15> grapheme_breaks = {{
|
||||
// Other CR LF Ctrl Ext RI Pre SpcMk L V T LV LVT ExtPict ZWJ
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0}}, // Other
|
||||
{{1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}, // CR
|
||||
{{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}, // LF
|
||||
{{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}, // Control
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0}}, // Extend
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0}}, // RI
|
||||
{{0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, // Prepend
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0}}, // SpacingMark
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0}}, // L
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0}}, // V
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0}}, // T
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0}}, // LV
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0}}, // LVT
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0}}, // ExtPict
|
||||
{{1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0}}, // ZWJ
|
||||
|
||||
}};
|
||||
// clang-format on
|
||||
auto const lhs_int = static_cast<int>(lhs);
|
||||
auto const rhs_int = static_cast<int>(rhs);
|
||||
return grapheme_breaks[lhs_int][rhs_int];
|
||||
}
|
||||
}
|
||||
|
||||
template<typename CPIter, typename Sentinel>
|
||||
CPIter next_grapheme_break(CPIter first, Sentinel last) noexcept
|
||||
{
|
||||
if (first == last)
|
||||
return first;
|
||||
|
||||
detail::grapheme_break_state<CPIter> state;
|
||||
state.it = first;
|
||||
|
||||
if (++state.it == last)
|
||||
return state.it;
|
||||
|
||||
state.prev_prop = grapheme_prop(*std::prev(state.it));
|
||||
state.prop = grapheme_prop(*state.it);
|
||||
|
||||
state.emoji_state =
|
||||
state.prev_prop == grapheme_property::Regional_Indicator
|
||||
? detail::grapheme_break_emoji_state_t::first_emoji
|
||||
: detail::grapheme_break_emoji_state_t::none;
|
||||
|
||||
for (; state.it != last; state = next(state)) {
|
||||
state.prop = grapheme_prop(*state.it);
|
||||
|
||||
// GB11
|
||||
if (state.prev_prop == grapheme_property::ZWJ &&
|
||||
state.prop == grapheme_property::ExtPict &&
|
||||
detail::gb11_prefix(first, std::prev(state.it))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state.emoji_state ==
|
||||
detail::grapheme_break_emoji_state_t::first_emoji) {
|
||||
if (state.prop == grapheme_property::Regional_Indicator) {
|
||||
state.emoji_state =
|
||||
detail::grapheme_break_emoji_state_t::none;
|
||||
continue;
|
||||
} else {
|
||||
state.emoji_state =
|
||||
detail::grapheme_break_emoji_state_t::none;
|
||||
}
|
||||
} else if (state.prop == grapheme_property::Regional_Indicator) {
|
||||
state.emoji_state =
|
||||
detail::grapheme_break_emoji_state_t::first_emoji;
|
||||
}
|
||||
|
||||
if (detail::table_grapheme_break(state.prev_prop, state.prop))
|
||||
return state.it;
|
||||
}
|
||||
|
||||
return state.it;
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
2503
src/text/boost/text/transcode_iterator.hpp
Normal file
2503
src/text/boost/text/transcode_iterator.hpp
Normal file
File diff suppressed because it is too large
Load Diff
9
src/text/boost/throw_exception.hpp
Normal file
9
src/text/boost/throw_exception.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef TEXT_BOOST_THROW_EXCEPTION_HPP
|
||||
#define TEXT_BOOST_THROW_EXCEPTION_HPP
|
||||
|
||||
namespace boost {
|
||||
template <typename E>
|
||||
void throw_exception(const E& e) { throw e; }
|
||||
}
|
||||
|
||||
#endif // TEXT_BOOST_THROW_EXCEPTION_HPP
|
||||
3589
src/text/grapheme_break.cpp
Normal file
3589
src/text/grapheme_break.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
|
||||
|
||||
Name: fmt
|
||||
Description: A modern formatting library
|
||||
|
||||
@@ -84,6 +84,9 @@ function(add_fmt_test name)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
if (FMT_WERROR)
|
||||
target_compile_options(${name} PRIVATE ${WERROR_FLAG})
|
||||
endif ()
|
||||
target_include_directories(${name} SYSTEM PUBLIC gtest gmock)
|
||||
add_test(NAME ${name} COMMAND ${name})
|
||||
endfunction()
|
||||
@@ -96,6 +99,9 @@ add_fmt_test(grisu-test)
|
||||
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
|
||||
add_fmt_test(gtest-extra-test)
|
||||
add_fmt_test(format-test mock-allocator.h)
|
||||
if (MSVC)
|
||||
target_compile_options(format-test PRIVATE /bigobj)
|
||||
endif ()
|
||||
if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
||||
add_fmt_test(format-impl-test)
|
||||
endif ()
|
||||
@@ -107,7 +113,7 @@ add_fmt_test(custom-formatter-test)
|
||||
add_fmt_test(ranges-test)
|
||||
add_fmt_test(scan-test)
|
||||
|
||||
if (HAVE_OPEN)
|
||||
if (HAVE_OPEN AND NOT MSVC_BUILD_STATIC)
|
||||
add_fmt_executable(posix-mock-test
|
||||
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
|
||||
target_include_directories(
|
||||
@@ -225,3 +231,21 @@ if (FMT_PEDANTIC AND NOT WIN32)
|
||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||
endif ()
|
||||
|
||||
# Activate optional CUDA tests if CUDA is found. For version selection, see
|
||||
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
||||
if (${CMAKE_VERSION} VERSION_LESS 3.15)
|
||||
find_package(CUDA 9.0)
|
||||
else ()
|
||||
include(CheckLanguage)
|
||||
check_language(CUDA)
|
||||
if (CMAKE_CUDA_COMPILER)
|
||||
enable_language(CUDA OPTIONAL)
|
||||
set(CUDA_FOUND TRUE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (CUDA_FOUND)
|
||||
add_subdirectory(cuda-test)
|
||||
add_test(NAME cuda-test COMMAND fmt-in-cuda-test)
|
||||
endif ()
|
||||
|
||||
@@ -47,7 +47,7 @@ TEST(ColorsTest, ColorsPrint) {
|
||||
"\x1b[105mtbmagenta\x1b[0m");
|
||||
}
|
||||
|
||||
TEST(ColorsTest, ColorsFormat) {
|
||||
TEST(ColorsTest, Format) {
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
||||
@@ -78,4 +78,6 @@ TEST(ColorsTest, ColorsFormat) {
|
||||
"\x1b[92mtbgreen\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
|
||||
"\x1b[105mtbmagenta\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
|
||||
"\x1b[31mfoo\x1b[0m");
|
||||
}
|
||||
|
||||
@@ -34,205 +34,17 @@
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
bool operator==(const internal::string_view_metadata& lhs,
|
||||
const internal::string_view_metadata& rhs) {
|
||||
return std::tie(lhs.offset_, lhs.size_) == std::tie(rhs.offset_, rhs.size_);
|
||||
}
|
||||
bool operator!=(const internal::string_view_metadata& lhs,
|
||||
const internal::string_view_metadata& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator==(const format_part<char>::specification& lhs,
|
||||
const format_part<char>::specification& rhs) {
|
||||
if (lhs.arg_id.which != rhs.arg_id.which) {
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef format_part<char>::argument_id::which_arg_id which_arg_id;
|
||||
switch (lhs.arg_id.which) {
|
||||
case which_arg_id::index: {
|
||||
if (lhs.arg_id.val.index != rhs.arg_id.val.index) {
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case which_arg_id::named_index: {
|
||||
if (lhs.arg_id.val.named_index != rhs.arg_id.val.named_index) {
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
return std::tie(lhs.parsed_specs.width, lhs.parsed_specs.fill[0],
|
||||
lhs.parsed_specs.align, lhs.parsed_specs.precision,
|
||||
lhs.parsed_specs.sign, lhs.parsed_specs.type) ==
|
||||
std::tie(rhs.parsed_specs.width, rhs.parsed_specs.fill[0],
|
||||
rhs.parsed_specs.align, rhs.parsed_specs.precision,
|
||||
rhs.parsed_specs.sign, rhs.parsed_specs.type);
|
||||
}
|
||||
|
||||
bool operator!=(const format_part<char>::specification& lhs,
|
||||
const format_part<char>::specification& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator==(const format_part<char>& lhs,
|
||||
const fmt::internal::format_part<char>& rhs) {
|
||||
typedef format_part<char>::kind kind;
|
||||
|
||||
if (lhs.which != rhs.which ||
|
||||
lhs.end_of_argument_id != rhs.end_of_argument_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (lhs.which) {
|
||||
case kind::argument_id: {
|
||||
return lhs.val.arg_id == rhs.val.arg_id;
|
||||
}
|
||||
|
||||
case kind::named_argument_id: {
|
||||
return lhs.val.named_arg_id == rhs.val.named_arg_id;
|
||||
}
|
||||
|
||||
case kind::text: {
|
||||
return lhs.val.text == rhs.val.text;
|
||||
}
|
||||
|
||||
case kind::specification: {
|
||||
return lhs.val.spec == rhs.val.spec;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator!=(const fmt::internal::format_part<char>& lhs,
|
||||
const fmt::internal::format_part<char>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CompileTest, FormatPart_ComparisonOperators) {
|
||||
typedef fmt::internal::format_part<char> format_part;
|
||||
typedef fmt::internal::dynamic_format_specs<char> prepared_specs;
|
||||
|
||||
{
|
||||
const auto part = format_part(0u);
|
||||
const auto other = format_part(0u);
|
||||
EXPECT_EQ(part, other);
|
||||
}
|
||||
{
|
||||
const auto lhs = format_part(0u);
|
||||
const auto rhs = format_part(1u);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto lhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
const auto rhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto lhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
const auto rhs = format_part(fmt::internal::string_view_metadata(0, 4422));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
auto lhs = format_part(0u);
|
||||
auto rhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
rhs = format_part(fmt::internal::string_view_metadata(0, 0));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
auto lhs = format_part(0u);
|
||||
lhs.end_of_argument_id = 42;
|
||||
auto rhs = format_part(0u);
|
||||
rhs.end_of_argument_id = 42;
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
rhs.end_of_argument_id = 13;
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto specs_argument_id = 0u;
|
||||
const auto specs_named_argument_id =
|
||||
fmt::internal::string_view_metadata(0, 42);
|
||||
auto specs = format_part::specification(0u);
|
||||
auto lhs = format_part(specs);
|
||||
auto rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part(specs);
|
||||
rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
specs = format_part::specification(specs_named_argument_id);
|
||||
lhs = format_part(specs);
|
||||
rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part(specs);
|
||||
rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
auto lhs_spec = format_part::specification(specs_argument_id);
|
||||
auto rhs_spec = format_part::specification(specs_named_argument_id);
|
||||
lhs = format_part(lhs_spec);
|
||||
rhs = format_part(rhs_spec);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
|
||||
lhs_spec = format_part::specification(specs_argument_id);
|
||||
rhs_spec = format_part::specification(specs_argument_id);
|
||||
lhs_spec.parsed_specs.precision = 1;
|
||||
rhs_spec.parsed_specs.precision = 2;
|
||||
lhs = format_part(lhs_spec);
|
||||
rhs = format_part(rhs_spec);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto specs_argument_id = 0u;
|
||||
const auto specs_named_argument_id =
|
||||
fmt::internal::string_view_metadata(0, 42);
|
||||
auto specs = format_part::specification(specs_argument_id);
|
||||
auto lhs = format_part(specs);
|
||||
auto rhs = format_part(0u);
|
||||
auto rhs2 = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part{specs};
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
|
||||
specs = format_part::specification(specs_named_argument_id);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part(specs);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
}
|
||||
}
|
||||
|
||||
// compiletime_prepared_parts_type_provider is useful only with relaxed
|
||||
// constexpr.
|
||||
#if FMT_USE_CONSTEXPR
|
||||
template <unsigned EXPECTED_PARTS_COUNT, typename Format>
|
||||
void check_prepared_parts_type(Format format) {
|
||||
typedef fmt::internal::compiletime_prepared_parts_type_provider<decltype(
|
||||
format)>
|
||||
provider;
|
||||
typedef typename provider::template format_parts_array<EXPECTED_PARTS_COUNT>
|
||||
expected_parts_type;
|
||||
static_assert(
|
||||
std::is_same<typename provider::type, expected_parts_type>::value,
|
||||
"CompileTimePreparedPartsTypeProvider test failed");
|
||||
typedef fmt::internal::compiled_format_base<decltype(format)> provider;
|
||||
typedef fmt::internal::format_part<char>
|
||||
expected_parts_type[EXPECTED_PARTS_COUNT];
|
||||
static_assert(std::is_same<typename provider::parts_container,
|
||||
expected_parts_type>::value,
|
||||
"CompileTimePreparedPartsTypeProvider test failed");
|
||||
}
|
||||
|
||||
TEST(CompileTest, CompileTimePreparedPartsTypeProvider) {
|
||||
@@ -253,40 +65,6 @@ TEST(CompileTest, CompileTimePreparedPartsTypeProvider) {
|
||||
}
|
||||
#endif
|
||||
|
||||
class custom_parts_container {
|
||||
public:
|
||||
typedef fmt::internal::format_part<char> format_part_type;
|
||||
|
||||
private:
|
||||
typedef std::deque<format_part_type> parts;
|
||||
|
||||
public:
|
||||
void add(format_part_type part) { parts_.push_back(std::move(part)); }
|
||||
|
||||
void substitute_last(format_part_type part) {
|
||||
parts_.back() = std::move(part);
|
||||
}
|
||||
|
||||
format_part_type last() { return parts_.back(); }
|
||||
|
||||
auto begin() -> decltype(std::declval<parts>().begin()) {
|
||||
return parts_.begin();
|
||||
}
|
||||
|
||||
auto begin() const -> decltype(std::declval<const parts>().begin()) {
|
||||
return parts_.begin();
|
||||
}
|
||||
|
||||
auto end() -> decltype(std::declval<parts>().begin()) { return parts_.end(); }
|
||||
|
||||
auto end() const -> decltype(std::declval<const parts>().begin()) {
|
||||
return parts_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
parts parts_;
|
||||
};
|
||||
|
||||
TEST(CompileTest, PassStringLiteralFormat) {
|
||||
const auto prepared = fmt::compile<int>("test {}");
|
||||
EXPECT_EQ("test 42", fmt::format(prepared, 42));
|
||||
@@ -338,3 +116,28 @@ TEST(CompileTest, FormattedSize) {
|
||||
auto f = fmt::compile<int>("{:10}");
|
||||
EXPECT_EQ(fmt::formatted_size(f, 42), 10);
|
||||
}
|
||||
|
||||
TEST(CompileTest, MultipleTypes) {
|
||||
auto f = fmt::compile<int, int>("{} {}");
|
||||
EXPECT_EQ(fmt::format(f, 42, 42), "42 42");
|
||||
}
|
||||
|
||||
struct formattable {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<formattable> : formatter<const char*> {
|
||||
auto format(formattable, format_context& ctx) -> decltype(ctx.out()) {
|
||||
return formatter<const char*>::format("foo", ctx);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CompileTest, FormatUserDefinedType) {
|
||||
auto f = fmt::compile<formattable>("{}");
|
||||
EXPECT_EQ(fmt::format(f, formattable()), "foo");
|
||||
}
|
||||
|
||||
TEST(CompileTest, EmptyFormatString) {
|
||||
auto f = fmt::compile<>("");
|
||||
EXPECT_EQ(fmt::format(f), "");
|
||||
}
|
||||
|
||||
73
test/cuda-test/CMakeLists.txt
Normal file
73
test/cuda-test/CMakeLists.txt
Normal file
@@ -0,0 +1,73 @@
|
||||
# We can find some usecases which follow the guide of CMake which uses
|
||||
# `enable_language(CUDA)` instead of `find_package(CUDA)` and let the CMake
|
||||
# built-in functions use NVCC.
|
||||
|
||||
# See: https://cmake.org/cmake/help/latest/module/FindCUDA.html#replacement
|
||||
#
|
||||
# However, this requires CMake version 3.10 or higher and we can't be sure most
|
||||
# of the CUDA projects are using those.
|
||||
#
|
||||
# This test relies on `find_package(CUDA)` in the parent CMake config.
|
||||
|
||||
# These can be updated when NVCC becomes ready for C++ 17 features
|
||||
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
||||
set(CMAKE_CUDA_STANDARD 14)
|
||||
set(CMAKE_CUDA_STANDARD_REQUIRED 14)
|
||||
|
||||
# In this test, we assume that the user is going to compile CUDA source code
|
||||
# with some libraries (fmt in this case).
|
||||
#
|
||||
# In addition to that, this test invokes both the C++ host compiler and NVCC
|
||||
# by providing another (non-CUDA) C++ source code.
|
||||
if (${CMAKE_VERSION} VERSION_LESS 3.15)
|
||||
# https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html
|
||||
list(APPEND CUDA_NVCC_FLAGS "-std=c++14")
|
||||
if (MSVC)
|
||||
# This is the solution of pytorch:
|
||||
# https://github.com/pytorch/pytorch/pull/7118
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/std:c++14")
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/Zc:__cplusplus")
|
||||
# for the reason of this -Xcompiler options, see below.
|
||||
endif ()
|
||||
cuda_add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
|
||||
target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
|
||||
if (MSVC)
|
||||
# This part is for (non-CUDA) C++ code. MSVC can define incorrect
|
||||
# `__cplusplus` macro. Fix for the issue is to use additional compiler flag.
|
||||
#
|
||||
# See Also:
|
||||
# https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
|
||||
# https://github.com/Microsoft/vscode-cpptools/issues/2595
|
||||
target_compile_options(fmt-in-cuda-test PRIVATE /Zc:__cplusplus /permissive-)
|
||||
endif ()
|
||||
else()
|
||||
# now using a "new" way of handling CUDA
|
||||
add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
|
||||
set_target_properties(fmt-in-cuda-test PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
|
||||
target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
|
||||
if (MSVC)
|
||||
# with MSVC, 'cxx_std_14' will only propagate to the host code (MSVC), but will
|
||||
# not set __cplusplus correctly anyway, while nvcc will ignore it.
|
||||
# If specified for nvcc on the command line as '-std=c++14' nvcc will emit this
|
||||
# message instead:
|
||||
# nvcc warning : The -std=c++14 flag is not supported with the configured host
|
||||
# compiler. Flag will be ignored.
|
||||
set_property(SOURCE cuda-cpp14.cu APPEND PROPERTY
|
||||
COMPILE_OPTIONS -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus)
|
||||
set_property(SOURCE cpp14.cc APPEND PROPERTY
|
||||
COMPILE_OPTIONS /std:c++14 /Zc:__cplusplus)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
get_target_property(IN_USE_CUDA_STANDARD fmt-in-cuda-test CUDA_STANDARD)
|
||||
message(STATUS "cuda_standard: ${IN_USE_CUDA_STANDARD}")
|
||||
|
||||
get_target_property(IN_USE_CUDA_STANDARD_REQUIRED
|
||||
fmt-in-cuda-test CUDA_STANDARD_REQUIRED)
|
||||
message(STATUS "cuda_standard_required: ${IN_USE_CUDA_STANDARD_REQUIRED}")
|
||||
|
||||
# We don't use PUBLIC or other keyword for reasons explained in the
|
||||
# CUDA_LINK_LIBRARIES_KEYWORD section in
|
||||
# https://cmake.org/cmake/help/latest/module/FindCUDA.html
|
||||
target_link_libraries(fmt-in-cuda-test fmt::fmt)
|
||||
|
||||
11
test/cuda-test/cpp14.cc
Normal file
11
test/cuda-test/cpp14.cc
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
// The purpose of this part is to ensure NVCC's host compiler also supports
|
||||
// the standard version. See 'cuda-cpp14.cu'.
|
||||
//
|
||||
// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
|
||||
static_assert(__cplusplus >= 201402L, "expect C++ 2014 for host compiler");
|
||||
|
||||
auto make_message_cpp() -> std::string {
|
||||
return fmt::format("host compiler \t: __cplusplus == {}", __cplusplus);
|
||||
}
|
||||
28
test/cuda-test/cuda-cpp14.cu
Normal file
28
test/cuda-test/cuda-cpp14.cu
Normal file
@@ -0,0 +1,28 @@
|
||||
// Direct NVCC command line example:
|
||||
//
|
||||
// nvcc ./cuda-cpp14.cu -x cu -I"../include" -l"fmtd" -L"../build/Debug" \
|
||||
// -std=c++14 -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus
|
||||
|
||||
// Ensure that we are using the latest C++ standard for NVCC
|
||||
// The version is C++14
|
||||
//
|
||||
// https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support
|
||||
// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
|
||||
static_assert(__cplusplus >= 201402L, "expect C++ 2014 for nvcc");
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <cuda.h>
|
||||
#include <iostream>
|
||||
|
||||
extern auto make_message_cpp() -> std::string;
|
||||
extern auto make_message_cuda() -> std::string;
|
||||
|
||||
int main() {
|
||||
std::cout << make_message_cuda() << std::endl;
|
||||
std::cout << make_message_cpp() << std::endl;
|
||||
}
|
||||
|
||||
auto make_message_cuda() -> std::string {
|
||||
return fmt::format("nvcc compiler \t: __cplusplus == {}", __cplusplus);
|
||||
}
|
||||
@@ -18,9 +18,9 @@
|
||||
// A custom argument formatter that doesn't print `-` for floating-point values
|
||||
// rounded to 0.
|
||||
class custom_arg_formatter
|
||||
: public fmt::arg_formatter<fmt::internal::buffer_range<char>> {
|
||||
: public fmt::arg_formatter<fmt::buffer_range<char>> {
|
||||
public:
|
||||
using range = fmt::internal::buffer_range<char>;
|
||||
using range = fmt::buffer_range<char>;
|
||||
typedef fmt::arg_formatter<range> base;
|
||||
|
||||
custom_arg_formatter(fmt::format_context& ctx,
|
||||
|
||||
@@ -690,9 +690,9 @@ struct formatter {
|
||||
template <typename FormatContext>
|
||||
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
fmt::internal::handle_dynamic_spec<fmt::internal::width_checker>(
|
||||
specs_.width, specs_.width_ref, ctx, nullptr);
|
||||
specs_.width, specs_.width_ref, ctx);
|
||||
fmt::internal::handle_dynamic_spec<fmt::internal::precision_checker>(
|
||||
specs_.precision, specs_.precision_ref, ctx, nullptr);
|
||||
specs_.precision, specs_.precision_ref, ctx);
|
||||
using range_type = fmt::internal::output_range<typename FormatContext::iterator,
|
||||
typename FormatContext::char_type>;
|
||||
return visit_format_arg(arg_formatter<range_type>(ctx, nullptr, &specs_),
|
||||
@@ -718,7 +718,7 @@ template<class... Args>
|
||||
string vformat(string_view fmt, format_args args) {
|
||||
fmt::memory_buffer mbuf;
|
||||
fmt::internal::buffer<char>& buf = mbuf;
|
||||
using range = fmt::internal::buffer_range<char>;
|
||||
using range = fmt::buffer_range<char>;
|
||||
detail::format_handler<detail::arg_formatter<range>, char, format_context>
|
||||
h(range(std::back_inserter(buf)), fmt, args, {});
|
||||
fmt::internal::parse_format_string<false>(fmt::to_string_view(fmt), h);
|
||||
|
||||
@@ -22,7 +22,42 @@
|
||||
|
||||
#undef max
|
||||
|
||||
using fmt::internal::bigint;
|
||||
using fmt::internal::fp;
|
||||
using fmt::internal::max_value;
|
||||
|
||||
static_assert(!std::is_copy_constructible<bigint>::value, "");
|
||||
static_assert(!std::is_copy_assignable<bigint>::value, "");
|
||||
|
||||
TEST(BigIntTest, Construct) {
|
||||
EXPECT_EQ("", fmt::format("{}", bigint()));
|
||||
EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
|
||||
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, ShiftLeft) {
|
||||
bigint n(0x42);
|
||||
n <<= 0;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
n <<= 1;
|
||||
EXPECT_EQ("84", fmt::format("{}", n));
|
||||
n <<= 25;
|
||||
EXPECT_EQ("108000000", fmt::format("{}", n));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Multiply) {
|
||||
bigint n(0x42);
|
||||
n *= 1;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
n *= 2;
|
||||
EXPECT_EQ("84", fmt::format("{}", n));
|
||||
n *= 0x12345678;
|
||||
EXPECT_EQ("962fc95e0", fmt::format("{}", n));
|
||||
auto max = max_value<uint32_t>();
|
||||
bigint bigmax(max);
|
||||
bigmax *= max;
|
||||
EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax));
|
||||
}
|
||||
|
||||
template <bool is_iec559> void test_construct_from_double() {
|
||||
fmt::print("warning: double is not IEC559, skipping FP tests\n");
|
||||
@@ -39,10 +74,10 @@ TEST(FPTest, ConstructFromDouble) {
|
||||
}
|
||||
|
||||
TEST(FPTest, Normalize) {
|
||||
auto v = fp(0xbeef, 42);
|
||||
v.normalize();
|
||||
EXPECT_EQ(0xbeef000000000000, v.f);
|
||||
EXPECT_EQ(-6, v.e);
|
||||
const auto v = fp(0xbeef, 42);
|
||||
auto normalized = normalize(v);
|
||||
EXPECT_EQ(0xbeef000000000000, normalized.f);
|
||||
EXPECT_EQ(-6, normalized.e);
|
||||
}
|
||||
|
||||
TEST(FPTest, ComputeBoundariesSubnormal) {
|
||||
@@ -100,7 +135,7 @@ TEST(FPTest, GetRoundDirection) {
|
||||
EXPECT_EQ(fmt::internal::up, get_round_direction(100, 60, 10));
|
||||
for (int i = 41; i < 60; ++i)
|
||||
EXPECT_EQ(fmt::internal::unknown, get_round_direction(100, i, 10));
|
||||
uint64_t max = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
|
||||
@@ -132,7 +167,7 @@ TEST(FPTest, FixedHandler) {
|
||||
// Check that divisor - error doesn't overflow.
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
|
||||
// Check that 2 * error doesn't overflow.
|
||||
uint64_t max = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false),
|
||||
digits::error);
|
||||
}
|
||||
@@ -149,10 +184,21 @@ template <typename T> struct value_extractor {
|
||||
template <typename U> FMT_NORETURN T operator()(U) {
|
||||
throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name()));
|
||||
}
|
||||
|
||||
#ifdef __apple_build_version__
|
||||
// Apple Clang does not define typeid for __int128_t and __uint128_t.
|
||||
FMT_NORETURN T operator()(__int128_t) {
|
||||
throw std::runtime_error(fmt::format("invalid type {}", "__int128_t"));
|
||||
}
|
||||
|
||||
FMT_NORETURN T operator()(__uint128_t) {
|
||||
throw std::runtime_error(fmt::format("invalid type {}", "__uint128_t"));
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
TEST(FormatTest, ArgConverter) {
|
||||
long long value = std::numeric_limits<long long>::max();
|
||||
long long value = max_value<long long>();
|
||||
auto arg = fmt::internal::make_arg<fmt::format_context>(value);
|
||||
fmt::visit_format_arg(
|
||||
fmt::internal::arg_converter<long long, fmt::format_context>(arg, 'd'),
|
||||
@@ -243,7 +289,7 @@ TEST(FormatTest, CountCodePoints) {
|
||||
// Tests fmt::internal::count_digits for integer type Int.
|
||||
template <typename Int> void test_count_digits() {
|
||||
for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::internal::count_digits(i));
|
||||
for (Int i = 1, n = 1, end = std::numeric_limits<Int>::max() / 10; n <= end;
|
||||
for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end;
|
||||
++i) {
|
||||
n *= 10;
|
||||
EXPECT_EQ(i, fmt::internal::count_digits(n - 1));
|
||||
|
||||
@@ -35,6 +35,7 @@ using std::size_t;
|
||||
|
||||
using fmt::basic_memory_buffer;
|
||||
using fmt::internal::basic_writer;
|
||||
using fmt::internal::max_value;
|
||||
using fmt::format;
|
||||
using fmt::format_error;
|
||||
using fmt::memory_buffer;
|
||||
@@ -99,7 +100,7 @@ void std_format(long double value, std::wstring& result) {
|
||||
template <typename Char, typename T>
|
||||
::testing::AssertionResult check_write(const T& value, const char* type) {
|
||||
fmt::basic_memory_buffer<Char> buffer;
|
||||
using range = fmt::internal::buffer_range<Char>;
|
||||
using range = fmt::buffer_range<Char>;
|
||||
basic_writer<range> writer(buffer);
|
||||
writer.write(value);
|
||||
std::basic_string<Char> actual = to_string(buffer);
|
||||
@@ -162,7 +163,7 @@ TEST(UtilTest, Increment) {
|
||||
}
|
||||
|
||||
TEST(UtilTest, ParseNonnegativeInt) {
|
||||
if (std::numeric_limits<int>::max() !=
|
||||
if (max_value<int>() !=
|
||||
static_cast<int>(static_cast<unsigned>(1) << 31)) {
|
||||
fmt::print("Skipping parse_nonnegative_int test\n");
|
||||
return;
|
||||
@@ -477,7 +478,7 @@ TEST(UtilTest, FormatSystemError) {
|
||||
message = fmt::memory_buffer();
|
||||
|
||||
// Check if std::allocator throws on allocating max size_t / 2 chars.
|
||||
size_t max_size = std::numeric_limits<size_t>::max() / 2;
|
||||
size_t max_size = max_value<size_t>() / 2;
|
||||
bool throws_on_alloc = false;
|
||||
try {
|
||||
std::allocator<char> alloc;
|
||||
@@ -527,7 +528,7 @@ TEST(UtilTest, FormatWindowsError) {
|
||||
actual_message.resize(0);
|
||||
fmt::internal::format_windows_error(
|
||||
actual_message, ERROR_FILE_EXISTS,
|
||||
fmt::string_view(0, std::numeric_limits<size_t>::max()));
|
||||
fmt::string_view(0, max_value<size_t>()));
|
||||
EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS),
|
||||
fmt::to_string(actual_message));
|
||||
}
|
||||
@@ -593,31 +594,31 @@ TEST(WriterTest, WriteInt) {
|
||||
CHECK_WRITE(static_cast<short>(12));
|
||||
CHECK_WRITE(34u);
|
||||
CHECK_WRITE(std::numeric_limits<int>::min());
|
||||
CHECK_WRITE(std::numeric_limits<int>::max());
|
||||
CHECK_WRITE(std::numeric_limits<unsigned>::max());
|
||||
CHECK_WRITE(max_value<int>());
|
||||
CHECK_WRITE(max_value<unsigned>());
|
||||
}
|
||||
|
||||
TEST(WriterTest, WriteLong) {
|
||||
CHECK_WRITE(56l);
|
||||
CHECK_WRITE(78ul);
|
||||
CHECK_WRITE(std::numeric_limits<long>::min());
|
||||
CHECK_WRITE(std::numeric_limits<long>::max());
|
||||
CHECK_WRITE(std::numeric_limits<unsigned long>::max());
|
||||
CHECK_WRITE(max_value<long>());
|
||||
CHECK_WRITE(max_value<unsigned long>());
|
||||
}
|
||||
|
||||
TEST(WriterTest, WriteLongLong) {
|
||||
CHECK_WRITE(56ll);
|
||||
CHECK_WRITE(78ull);
|
||||
CHECK_WRITE(std::numeric_limits<long long>::min());
|
||||
CHECK_WRITE(std::numeric_limits<long long>::max());
|
||||
CHECK_WRITE(std::numeric_limits<unsigned long long>::max());
|
||||
CHECK_WRITE(max_value<long long>());
|
||||
CHECK_WRITE(max_value<unsigned long long>());
|
||||
}
|
||||
|
||||
TEST(WriterTest, WriteDouble) {
|
||||
CHECK_WRITE(4.2);
|
||||
CHECK_WRITE(-4.2);
|
||||
auto min = std::numeric_limits<double>::min();
|
||||
auto max = std::numeric_limits<double>::max();
|
||||
auto max = max_value<double>();
|
||||
if (fmt::internal::use_grisu<double>()) {
|
||||
EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min));
|
||||
EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max));
|
||||
@@ -637,7 +638,7 @@ TEST(WriterTest, WriteLongDouble) {
|
||||
else
|
||||
fmt::print("warning: long double formatting with std::swprintf is broken");
|
||||
auto min = std::numeric_limits<long double>::min();
|
||||
auto max = std::numeric_limits<long double>::max();
|
||||
auto max = max_value<long double>();
|
||||
if (fmt::internal::use_grisu<long double>()) {
|
||||
EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min));
|
||||
EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max));
|
||||
@@ -697,12 +698,6 @@ TEST(FormatToTest, WideString) {
|
||||
EXPECT_STREQ(buf.data(), L"42");
|
||||
}
|
||||
|
||||
TEST(FormatToTest, FormatToNonbackInsertIteratorWithSignAndNumericAlignment) {
|
||||
char buffer[16] = {};
|
||||
fmt::format_to(fmt::internal::make_checked(buffer, 16), "{: =+}", 42.0);
|
||||
EXPECT_STREQ("+42.0", buffer);
|
||||
}
|
||||
|
||||
TEST(FormatToTest, FormatToMemoryBuffer) {
|
||||
fmt::basic_memory_buffer<char, 100> buffer;
|
||||
fmt::format_to(buffer, "{}", "foo");
|
||||
@@ -856,6 +851,7 @@ TEST(FormatterTest, RightAlign) {
|
||||
EXPECT_EQ(" 0xface", format("{0:>8}", reinterpret_cast<void*>(0xface)));
|
||||
}
|
||||
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
TEST(FormatterTest, NumericAlign) {
|
||||
EXPECT_EQ(" 42", format("{0:=4}", 42));
|
||||
EXPECT_EQ("+ 42", format("{0:=+4}", 42));
|
||||
@@ -882,6 +878,13 @@ TEST(FormatterTest, NumericAlign) {
|
||||
EXPECT_EQ(" 1.0", fmt::format("{:= }", 1.0));
|
||||
}
|
||||
|
||||
TEST(FormatToTest, FormatToNonbackInsertIteratorWithSignAndNumericAlignment) {
|
||||
char buffer[16] = {};
|
||||
fmt::format_to(fmt::internal::make_checked(buffer, 16), "{: =+}", 42.0);
|
||||
EXPECT_STREQ("+42.0", buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(FormatterTest, CenterAlign) {
|
||||
EXPECT_EQ(" 42 ", format("{0:^5}", 42));
|
||||
EXPECT_EQ(" 42 ", format("{0:^5o}", 042));
|
||||
@@ -1001,6 +1004,7 @@ TEST(FormatterTest, HashFlag) {
|
||||
EXPECT_EQ("0x42", format("{0:#x}", 0x42));
|
||||
EXPECT_EQ("0X42", format("{0:#X}", 0x42));
|
||||
EXPECT_EQ("-0x42", format("{0:#x}", -0x42));
|
||||
EXPECT_EQ("0", format("{0:#o}", 0));
|
||||
EXPECT_EQ("042", format("{0:#o}", 042));
|
||||
EXPECT_EQ("-042", format("{0:#o}", -042));
|
||||
EXPECT_EQ("42", format("{0:#}", 42u));
|
||||
@@ -1330,9 +1334,17 @@ TEST(FormatterTest, FormatBin) {
|
||||
EXPECT_EQ("10010001101000101011001111000", format("{0:b}", 0x12345678));
|
||||
EXPECT_EQ("10010000101010111100110111101111", format("{0:b}", 0x90ABCDEF));
|
||||
EXPECT_EQ("11111111111111111111111111111111",
|
||||
format("{0:b}", std::numeric_limits<uint32_t>::max()));
|
||||
format("{0:b}", max_value<uint32_t>()));
|
||||
}
|
||||
|
||||
#if FMT_USE_INT128
|
||||
constexpr auto int128_max = static_cast<__int128_t>(
|
||||
(static_cast<__uint128_t>(1) << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1);
|
||||
constexpr auto int128_min = -int128_max - 1;
|
||||
|
||||
constexpr auto uint128_max = ~static_cast<__uint128_t>(0);
|
||||
#endif
|
||||
|
||||
TEST(FormatterTest, FormatDec) {
|
||||
EXPECT_EQ("0", format("{0}", 0));
|
||||
EXPECT_EQ("42", format("{0}", 42));
|
||||
@@ -1341,6 +1353,23 @@ TEST(FormatterTest, FormatDec) {
|
||||
EXPECT_EQ("-42", format("{0}", -42));
|
||||
EXPECT_EQ("12345", format("{0}", 12345));
|
||||
EXPECT_EQ("67890", format("{0}", 67890));
|
||||
#if FMT_USE_INT128
|
||||
EXPECT_EQ("0", format("{0}", static_cast<__int128_t>(0)));
|
||||
EXPECT_EQ("0", format("{0}", static_cast<__uint128_t>(0)));
|
||||
EXPECT_EQ("9223372036854775808",
|
||||
format("{0}", static_cast<__int128_t>(INT64_MAX) + 1));
|
||||
EXPECT_EQ("-9223372036854775809",
|
||||
format("{0}", static_cast<__int128_t>(INT64_MIN) - 1));
|
||||
EXPECT_EQ("18446744073709551616",
|
||||
format("{0}", static_cast<__int128_t>(UINT64_MAX) + 1));
|
||||
EXPECT_EQ("170141183460469231731687303715884105727",
|
||||
format("{0}", int128_max));
|
||||
EXPECT_EQ("-170141183460469231731687303715884105728",
|
||||
format("{0}", int128_min));
|
||||
EXPECT_EQ("340282366920938463463374607431768211455",
|
||||
format("{0}", uint128_max));
|
||||
#endif
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "%d", INT_MIN);
|
||||
EXPECT_EQ(buffer, format("{0}", INT_MIN));
|
||||
@@ -1365,6 +1394,19 @@ TEST(FormatterTest, FormatHex) {
|
||||
EXPECT_EQ("90abcdef", format("{0:x}", 0x90abcdef));
|
||||
EXPECT_EQ("12345678", format("{0:X}", 0x12345678));
|
||||
EXPECT_EQ("90ABCDEF", format("{0:X}", 0x90ABCDEF));
|
||||
#if FMT_USE_INT128
|
||||
EXPECT_EQ("0", format("{0:x}", static_cast<__int128_t>(0)));
|
||||
EXPECT_EQ("0", format("{0:x}", static_cast<__uint128_t>(0)));
|
||||
EXPECT_EQ("8000000000000000",
|
||||
format("{0:x}", static_cast<__int128_t>(INT64_MAX) + 1));
|
||||
EXPECT_EQ("-8000000000000001",
|
||||
format("{0:x}", static_cast<__int128_t>(INT64_MIN) - 1));
|
||||
EXPECT_EQ("10000000000000000",
|
||||
format("{0:x}", static_cast<__int128_t>(UINT64_MAX) + 1));
|
||||
EXPECT_EQ("7fffffffffffffffffffffffffffffff", format("{0:x}", int128_max));
|
||||
EXPECT_EQ("-80000000000000000000000000000000", format("{0:x}", int128_min));
|
||||
EXPECT_EQ("ffffffffffffffffffffffffffffffff", format("{0:x}", uint128_max));
|
||||
#endif
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "-%x", 0 - static_cast<unsigned>(INT_MIN));
|
||||
@@ -1387,6 +1429,23 @@ TEST(FormatterTest, FormatOct) {
|
||||
EXPECT_EQ("42", format("{0:o}", 042u));
|
||||
EXPECT_EQ("-42", format("{0:o}", -042));
|
||||
EXPECT_EQ("12345670", format("{0:o}", 012345670));
|
||||
#if FMT_USE_INT128
|
||||
EXPECT_EQ("0", format("{0:o}", static_cast<__int128_t>(0)));
|
||||
EXPECT_EQ("0", format("{0:o}", static_cast<__uint128_t>(0)));
|
||||
EXPECT_EQ("1000000000000000000000",
|
||||
format("{0:o}", static_cast<__int128_t>(INT64_MAX) + 1));
|
||||
EXPECT_EQ("-1000000000000000000001",
|
||||
format("{0:o}", static_cast<__int128_t>(INT64_MIN) - 1));
|
||||
EXPECT_EQ("2000000000000000000000",
|
||||
format("{0:o}", static_cast<__int128_t>(UINT64_MAX) + 1));
|
||||
EXPECT_EQ("1777777777777777777777777777777777777777777",
|
||||
format("{0:o}", int128_max));
|
||||
EXPECT_EQ("-2000000000000000000000000000000000000000000",
|
||||
format("{0:o}", int128_min));
|
||||
EXPECT_EQ("3777777777777777777777777777777777777777777",
|
||||
format("{0:o}", uint128_max));
|
||||
#endif
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "-%o", 0 - static_cast<unsigned>(INT_MIN));
|
||||
EXPECT_EQ(buffer, format("{0:o}", INT_MIN));
|
||||
@@ -1407,7 +1466,7 @@ TEST(FormatterTest, FormatIntLocale) {
|
||||
EXPECT_EQ("1,234", format("{:n}", 1234));
|
||||
EXPECT_EQ("1,234,567", format("{:n}", 1234567));
|
||||
EXPECT_EQ("4,294,967,295",
|
||||
format("{:n}", std::numeric_limits<uint32_t>::max()));
|
||||
format("{:n}", max_value<uint32_t>()));
|
||||
}
|
||||
|
||||
struct ConvertibleToLongLong {
|
||||
@@ -1745,9 +1804,9 @@ TEST(FormatIntTest, FormatInt) {
|
||||
EXPECT_EQ("42", fmt::format_int(42ull).str());
|
||||
EXPECT_EQ("-42", fmt::format_int(-42ll).str());
|
||||
std::ostringstream os;
|
||||
os << std::numeric_limits<int64_t>::max();
|
||||
os << max_value<int64_t>();
|
||||
EXPECT_EQ(os.str(),
|
||||
fmt::format_int(std::numeric_limits<int64_t>::max()).str());
|
||||
fmt::format_int(max_value<int64_t>()).str());
|
||||
}
|
||||
|
||||
TEST(FormatTest, Print) {
|
||||
@@ -1894,6 +1953,11 @@ TEST(FormatTest, UdlTemplate) {
|
||||
EXPECT_EQ("foo", "foo"_format());
|
||||
EXPECT_EQ(" 42", "{0:10}"_format(42));
|
||||
}
|
||||
|
||||
TEST(FormatTest, UdlPassUserDefinedObjectAsLvalue) {
|
||||
Date date(2015, 10, 21);
|
||||
EXPECT_EQ("2015-10-21", "{}"_format(date));
|
||||
}
|
||||
#endif // FMT_USE_USER_DEFINED_LITERALS
|
||||
|
||||
enum TestEnum { A };
|
||||
@@ -1906,17 +1970,23 @@ TEST(FormatTest, FormatterNotSpecialized) {
|
||||
}
|
||||
|
||||
#if FMT_HAS_FEATURE(cxx_strong_enums)
|
||||
enum TestFixedEnum : short { B };
|
||||
enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
|
||||
|
||||
TEST(FormatTest, FixedEnum) { EXPECT_EQ("0", fmt::format("{}", B)); }
|
||||
TEST(FormatTest, StrongEnum) {
|
||||
EXPECT_EQ("5000000000", fmt::format("{}", big_enum_value));
|
||||
}
|
||||
#endif
|
||||
|
||||
using buffer_range = fmt::internal::buffer_range<char>;
|
||||
using buffer_range = fmt::buffer_range<char>;
|
||||
|
||||
class mock_arg_formatter
|
||||
: public fmt::internal::arg_formatter_base<buffer_range> {
|
||||
private:
|
||||
#if FMT_USE_INT128
|
||||
MOCK_METHOD1(call, void(__int128_t value));
|
||||
#else
|
||||
MOCK_METHOD1(call, void(long long value));
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef fmt::internal::arg_formatter_base<buffer_range> base;
|
||||
@@ -1929,14 +1999,14 @@ class mock_arg_formatter
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, iterator>::type
|
||||
typename std::enable_if<fmt::internal::is_integral<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
call(value);
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, iterator>::type
|
||||
typename std::enable_if<!fmt::internal::is_integral<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
@@ -1988,8 +2058,10 @@ TEST(FormatTest, DynamicFormatter) {
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG(format("{:{0}}", num), format_error,
|
||||
"cannot switch from automatic to manual argument indexing");
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
EXPECT_THROW_MSG(format("{:=}", str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
#endif
|
||||
EXPECT_THROW_MSG(format("{:+}", str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG(format("{:-}", str), format_error,
|
||||
@@ -2372,13 +2444,15 @@ TEST(FormatTest, FormatStringErrors) {
|
||||
EXPECT_ERROR("{0:s", "unknown format specifier", Date);
|
||||
# if FMT_MSC_VER >= 1916
|
||||
// This causes an internal compiler error in MSVC2017.
|
||||
EXPECT_ERROR("{0:=5", "unknown format specifier", int);
|
||||
EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
|
||||
EXPECT_ERROR("{:10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR("{:.10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR_NOARGS("{:x}", "argument index out of range");
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
EXPECT_ERROR("{0:=5", "unknown format specifier", int);
|
||||
EXPECT_ERROR("{:=}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
#endif
|
||||
EXPECT_ERROR("{:+}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
EXPECT_ERROR("{:-}", "format specifier requires numeric argument",
|
||||
|
||||
@@ -64,7 +64,7 @@ TEST(OStreamTest, Enum) {
|
||||
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
|
||||
}
|
||||
|
||||
using range = fmt::internal::buffer_range<char>;
|
||||
using range = fmt::buffer_range<char>;
|
||||
|
||||
struct test_arg_formatter : fmt::arg_formatter<range> {
|
||||
fmt::format_parse_context parse_ctx;
|
||||
@@ -95,8 +95,10 @@ TEST(OStreamTest, Format) {
|
||||
TEST(OStreamTest, FormatSpecs) {
|
||||
EXPECT_EQ("def ", format("{0:<5}", TestString("def")));
|
||||
EXPECT_EQ(" def", format("{0:>5}", TestString("def")));
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
#endif
|
||||
EXPECT_EQ(" def ", format("{0:^5}", TestString("def")));
|
||||
EXPECT_EQ("def**", format("{0:*<5}", TestString("def")));
|
||||
EXPECT_THROW_MSG(format("{0:+}", TestString()), format_error,
|
||||
@@ -143,8 +145,8 @@ TEST(OStreamTest, WriteToOStream) {
|
||||
}
|
||||
|
||||
TEST(OStreamTest, WriteToOStreamMaxSize) {
|
||||
std::size_t max_size = std::numeric_limits<std::size_t>::max();
|
||||
std::streamsize max_streamsize = std::numeric_limits<std::streamsize>::max();
|
||||
std::size_t max_size = fmt::internal::max_value<std::size_t>();
|
||||
std::streamsize max_streamsize = fmt::internal::max_value<std::streamsize>();
|
||||
if (max_size <= fmt::internal::to_unsigned(max_streamsize)) return;
|
||||
|
||||
struct test_buffer : fmt::internal::buffer<char> {
|
||||
@@ -240,3 +242,16 @@ TEST(FormatTest, UDL) {
|
||||
EXPECT_EQ("{}"_format("test"), "test");
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
struct convertible {
|
||||
T value;
|
||||
explicit convertible(const T& val) : value(val) {}
|
||||
operator T() const { return value; }
|
||||
};
|
||||
|
||||
TEST(OStreamTest, DisableBuiltinOStreamOperators) {
|
||||
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
|
||||
EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible<unsigned short>(42)));
|
||||
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
|
||||
}
|
||||
|
||||
@@ -368,12 +368,6 @@ TEST(FileTest, Fdopen) {
|
||||
EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get())));
|
||||
}
|
||||
|
||||
TEST(FileTest, FdopenError) {
|
||||
file f;
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(f.fdopen("r"), EBADF,
|
||||
"cannot associate stream with file descriptor");
|
||||
}
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
TEST(LocaleTest, Strtod) {
|
||||
fmt::Locale locale;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
using fmt::format;
|
||||
using fmt::format_error;
|
||||
using fmt::internal::max_value;
|
||||
|
||||
const unsigned BIG_NUM = INT_MAX + 1u;
|
||||
|
||||
@@ -295,13 +296,12 @@ void TestLength(const char* length_spec, U value) {
|
||||
long long signed_value = 0;
|
||||
unsigned long long unsigned_value = 0;
|
||||
// Apply integer promotion to the argument.
|
||||
using std::numeric_limits;
|
||||
unsigned long long max = numeric_limits<U>::max();
|
||||
unsigned long long max = max_value<U>();
|
||||
using fmt::internal::const_check;
|
||||
if (const_check(max <= static_cast<unsigned>(numeric_limits<int>::max()))) {
|
||||
if (const_check(max <= static_cast<unsigned>(max_value<int>()))) {
|
||||
signed_value = static_cast<int>(value);
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
} else if (const_check(max <= numeric_limits<unsigned>::max())) {
|
||||
} else if (const_check(max <= max_value<unsigned>())) {
|
||||
signed_value = static_cast<unsigned>(value);
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
}
|
||||
@@ -332,25 +332,25 @@ void TestLength(const char* length_spec, U value) {
|
||||
}
|
||||
|
||||
template <typename T> void TestLength(const char* length_spec) {
|
||||
T min = std::numeric_limits<T>::min(), max = std::numeric_limits<T>::max();
|
||||
T min = std::numeric_limits<T>::min(), max = max_value<T>();
|
||||
TestLength<T>(length_spec, 42);
|
||||
TestLength<T>(length_spec, -42);
|
||||
TestLength<T>(length_spec, min);
|
||||
TestLength<T>(length_spec, max);
|
||||
TestLength<T>(length_spec, static_cast<long long>(min) - 1);
|
||||
unsigned long long long_long_max = std::numeric_limits<long long>::max();
|
||||
unsigned long long long_long_max = max_value<long long>();
|
||||
if (static_cast<unsigned long long>(max) < long_long_max)
|
||||
TestLength<T>(length_spec, static_cast<long long>(max) + 1);
|
||||
TestLength<T>(length_spec, std::numeric_limits<short>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned short>::max());
|
||||
TestLength<T>(length_spec, max_value<unsigned short>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<int>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<int>::max());
|
||||
TestLength<T>(length_spec, max_value<int>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned>::max());
|
||||
TestLength<T>(length_spec, max_value<unsigned>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<long long>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<long long>::max());
|
||||
TestLength<T>(length_spec, max_value<long long>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::max());
|
||||
TestLength<T>(length_spec, max_value<unsigned long long>());
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Length) {
|
||||
@@ -366,7 +366,7 @@ TEST(PrintfTest, Length) {
|
||||
TestLength<intmax_t>("j");
|
||||
TestLength<std::size_t>("z");
|
||||
TestLength<std::ptrdiff_t>("t");
|
||||
long double max = std::numeric_limits<long double>::max();
|
||||
long double max = max_value<long double>();
|
||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
|
||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
|
||||
}
|
||||
@@ -389,7 +389,7 @@ TEST(PrintfTest, Int) {
|
||||
TEST(PrintfTest, long_long) {
|
||||
// fmt::printf allows passing long long arguments to %d without length
|
||||
// specifiers.
|
||||
long long max = std::numeric_limits<long long>::max();
|
||||
long long max = max_value<long long>();
|
||||
EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ TEST(PrintfTest, Inf) {
|
||||
|
||||
TEST(PrintfTest, Char) {
|
||||
EXPECT_PRINTF("x", "%c", 'x');
|
||||
int max = std::numeric_limits<int>::max();
|
||||
int max = max_value<int>();
|
||||
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
||||
// EXPECT_PRINTF("x", "%lc", L'x');
|
||||
EXPECT_PRINTF(L"x", L"%c", L'x');
|
||||
@@ -561,8 +561,7 @@ TEST(PrintfTest, VSPrintfMakeWArgsExample) {
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef fmt::printf_arg_formatter<fmt::internal::buffer_range<char>>
|
||||
formatter_t;
|
||||
typedef fmt::printf_arg_formatter<fmt::buffer_range<char>> formatter_t;
|
||||
typedef fmt::basic_printf_context<formatter_t::iterator, char> context_t;
|
||||
|
||||
// A custom printf argument formatter that doesn't print `-` for floating-point
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
/// Check if 'if constexpr' is supported.
|
||||
#include "fmt/ranges.h"
|
||||
#include "gtest.h"
|
||||
|
||||
// Check if 'if constexpr' is supported.
|
||||
#if (__cplusplus > 201402L) || \
|
||||
(defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)
|
||||
|
||||
# include "fmt/ranges.h"
|
||||
# include "gtest.h"
|
||||
|
||||
# include <array>
|
||||
# include <map>
|
||||
# include <string>
|
||||
@@ -49,6 +49,25 @@ TEST(RangesTest, FormatTuple) {
|
||||
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1));
|
||||
}
|
||||
|
||||
TEST(RangesTest, JoinTuple) {
|
||||
// Value tuple args
|
||||
std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f);
|
||||
EXPECT_EQ("(a, 1, 2.0)", fmt::format("({})", fmt::join(t1, ", ")));
|
||||
|
||||
// Testing lvalue tuple args
|
||||
int x = 4;
|
||||
std::tuple<char, int&> t2{'b', x};
|
||||
EXPECT_EQ("b + 4", fmt::format("{}", fmt::join(t2, " + ")));
|
||||
|
||||
// Empty tuple
|
||||
std::tuple<> t3;
|
||||
EXPECT_EQ("", fmt::format("{}", fmt::join(t3, "|")));
|
||||
|
||||
// Single element tuple
|
||||
std::tuple<float> t4{4.0f};
|
||||
EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/")));
|
||||
}
|
||||
|
||||
struct my_struct {
|
||||
int32_t i;
|
||||
std::string str; // can throw
|
||||
@@ -100,3 +119,16 @@ TEST(RangesTest, PathLike) {
|
||||
|
||||
#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >
|
||||
// 201402L && _MSC_VER >= 1910)
|
||||
|
||||
#ifdef FMT_USE_STRING_VIEW
|
||||
struct string_like {
|
||||
const char* begin();
|
||||
const char* end();
|
||||
explicit operator fmt::string_view() const { return "foo"; }
|
||||
explicit operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(RangesTest, FormatStringLike) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", string_like()));
|
||||
}
|
||||
#endif // FMT_USE_STRING_VIEW
|
||||
|
||||
@@ -30,18 +30,14 @@ TEST(StdFormatTest, Alignment) {
|
||||
// Error: '=' with charT and no integer presentation type
|
||||
EXPECT_THROW(string s5 = format("{:=6}", 'x'), std::format_error);
|
||||
string s6 = format("{:6d}", c); // s6 == " 120"
|
||||
string s7 = format("{:=+06d}", c); // s7 == "+00120"
|
||||
string s8 = format("{:0=#6x}", 0xa); // s8 == "0x000a"
|
||||
string s9 = format("{:6}", true); // s9 == "true "
|
||||
string s7 = format("{:6}", true); // s9 == "true "
|
||||
EXPECT_EQ(s0, " 42");
|
||||
EXPECT_EQ(s1, "x ");
|
||||
EXPECT_EQ(s2, "x*****");
|
||||
EXPECT_EQ(s3, "*****x");
|
||||
EXPECT_EQ(s4, "**x***");
|
||||
EXPECT_EQ(s6, " 120");
|
||||
EXPECT_EQ(s7, "+00120");
|
||||
EXPECT_EQ(s8, "0x000a");
|
||||
EXPECT_EQ(s9, "true ");
|
||||
EXPECT_EQ(s7, "true ");
|
||||
}
|
||||
|
||||
TEST(StdFormatTest, Float) {
|
||||
|
||||
Reference in New Issue
Block a user