diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 402c5a0f..5f154a92 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -12,6 +12,7 @@ jobs: - name: Create Build Environment run: | + sudo apt update sudo apt install doxygen python3-virtualenv sudo npm install -g less clean-css cmake -E make_directory ${{runner.workspace}}/build diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2c32fbcb..7f8500ef 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -24,10 +24,11 @@ jobs: build_type: Debug std: 17 os: ubuntu-18.04 - - cxx: g++-10 + - cxx: g++-11 build_type: Debug std: 20 os: ubuntu-20.04 + install: sudo apt install g++-11 - cxx: clang++-9 build_type: Debug fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON diff --git a/CMakeLists.txt b/CMakeLists.txt index ae9c5692..3bf80f80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ endif () # Joins arguments and places the results in ${result_var}. function(join result_var) - set(result ) + set(result "") foreach (arg ${ARGN}) set(result "${result}${arg}") endforeach () @@ -81,12 +81,13 @@ option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_MODULE "Build a module instead of a traditional library." OFF) +option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF) set(FMT_CAN_MODULE OFF) if (CMAKE_CXX_STANDARD GREATER 17 AND # msvc 16.10-pre4 MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035) - set(FMT_CAN_MODULE ON) + set(FMT_CAN_MODULE OFF) endif () if (NOT FMT_CAN_MODULE) set(FMT_MODULE OFF) @@ -96,6 +97,10 @@ if (FMT_TEST AND FMT_MODULE) # The tests require {fmt} to be compiled as traditional library message(STATUS "Testing is incompatible with build mode 'module'.") endif () +set(FMT_SYSTEM_HEADERS_ATTRIBUTE "") +if (FMT_SYSTEM_HEADERS) + set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) +endif () # Get version from core.h file(READ include/fmt/core.h core_h) @@ -151,7 +156,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") -Wcast-align -Wctor-dtor-privacy -Wdisabled-optimization -Winvalid-pch -Woverloaded-virtual - -Wconversion -Wswitch-enum -Wundef + -Wconversion -Wundef -Wno-ctor-dtor-privacy -Wno-format-nonliteral) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} @@ -244,7 +249,7 @@ if (HAVE_STRTOD_L) endif () if (MINGW) - check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ) + check_cxx_compiler_flag("-Wa,-mbig-obj" FMT_HAS_MBIG_OBJ) if (${FMT_HAS_MBIG_OBJ}) target_compile_options(fmt PUBLIC "-Wa,-mbig-obj") endif() @@ -262,7 +267,7 @@ endif () target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) -target_include_directories(fmt PUBLIC +target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC $ $) @@ -298,7 +303,7 @@ add_library(fmt::fmt-header-only ALIAS fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) -target_include_directories(fmt-header-only INTERFACE +target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE $ $) diff --git a/ChangeLog.rst b/ChangeLog.rst index 2a6d7ffe..18b84c7d 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,7 +1,427 @@ +8.1.1 - 2022-01-06 +------------------ + +* Restored ABI compatibility with version 8.0.x + (`#2695 `_, + `#2696 `_). + Thanks `@saraedum (Julian Rüth) `_. + +* Fixed chorno formatting on big endian systems + (`#2698 `_, + `#2699 `_). + Thanks `@phprus (Vladislav Shchapov) `_ and + `@xvitaly (Vitaly Zaitsev) `_. + +* Fixed a linkage error with mingw + (`#2691 `_, + `#2692 `_). + Thanks `@rbberger (Richard Berger) `_. + +8.1.0 - 2022-01-02 +------------------ + +* Optimized chrono formatting + (`#2500 `_, + `#2537 `_, + `#2541 `_, + `#2544 `_, + `#2550 `_, + `#2551 `_, + `#2576 `_, + `#2577 `_, + `#2586 `_, + `#2591 `_, + `#2594 `_, + `#2602 `_, + `#2617 `_, + `#2628 `_, + `#2633 `_, + `#2670 `_, + `#2671 `_). + + Processing of some specifiers such as ``%z`` and ``%Y`` is now up to 10-20 + times faster, for example on GCC 11 with libstdc++:: + + ---------------------------------------------------------------------------- + Benchmark Before After + ---------------------------------------------------------------------------- + FMTFormatter_z 261 ns 26.3 ns + FMTFormatterCompile_z 246 ns 11.6 ns + FMTFormatter_Y 263 ns 26.1 ns + FMTFormatterCompile_Y 244 ns 10.5 ns + ---------------------------------------------------------------------------- + + Thanks `@phprus (Vladislav Shchapov) `_ and + `@toughengineer (Pavel Novikov) `_. + +* Implemented subsecond formatting for chrono durations + (`#2623 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + fmt::print("{:%S}", std::chrono::milliseconds(1234)); + } + + prints "01.234". + + Thanks `@matrackif `_. + +* Fixed handling of precision 0 when formatting chrono durations + (`#2587 `_, + `#2588 `_). + Thanks `@lukester1975 `_. + +* Fixed an overflow on invalid inputs in the ``tm`` formatter + (`#2564 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Added ``fmt::group_digits`` that formats integers with a non-localized digit + separator (comma) for groups of three digits. + For example (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + fmt::print("{} dollars", fmt::group_digits(1000000)); + } + + prints "1,000,000 dollars". + +* Added support for faint, conceal, reverse and blink text styles + (`#2394 `_): + + https://user-images.githubusercontent.com/576385/147710227-c68f5317-f8fa-42c3-9123-7c4ba3c398cb.mp4 + + Thanks `@benit8 (Benoît Lormeau) `_ and + `@data-man (Dmitry Atamanov) `_. + +* Added experimental support for compile-time floating point formatting + (`#2426 `_, + `#2470 `_). + It is currently limited to the header-only mode. + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Added UDL-based named argument support to compile-time format string checks + (`#2640 `_, + `#2649 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + using namespace fmt::literals; + fmt::print("{answer:s}", "answer"_a=42); + } + + gives a compile-time error on compilers with C++20 ``consteval`` and non-type + template parameter support (gcc 10+) because ``s`` is not a valid format + specifier for an integer. + + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Implemented escaping of string range elements. + For example (`godbolt `__): + + .. code:: c++ + + #include + #include + + int main() { + fmt::print("{}", std::vector{"\naan"}); + } + + is now printed as:: + + ["\naan"] + + instead of:: + + [" + aan"] + +* Switched to JSON-like representation of maps and sets for consistency with + Python's ``str.format``. + For example (`godbolt `__): + + .. code:: c++ + + #include + #include + + int main() { + fmt::print("{}", std::map{{"answer", 42}}); + } + + is now printed as:: + + {"answer": 42} + +* Extended ``fmt::join`` to support C++20-only ranges + (`#2549 `_). + Thanks `@BRevzin (Barry Revzin) `_. + +* Optimized handling of non-const-iterable ranges and implemented initial + support for non-const-formattable types. + +* Disabled implicit conversions of scoped enums to integers that was + accidentally introduced in earlier versions + (`#1841 `_). + +* Deprecated implicit conversion of ``[const] signed char*`` and + ``[const] unsigned char*`` to C strings. + +* Deprecated ``_format``, a legacy UDL-based format API + (`#2646 `_). + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Marked ``format``, ``formatted_size`` and ``to_string`` as ``[[nodiscard]]`` + (`#2612 `_). + `@0x8000-0000 (Florin Iucha) `_. + +* Added missing diagnostic when trying to format function and member pointers + as well as objects convertible to pointers which is explicitly disallowed + (`#2598 `_, + `#2609 `_, + `#2610 `_). + Thanks `@AlexGuteniev (Alex Guteniev) `_. + +* Optimized writing to a contiguous buffer with ``format_to_n`` + (`#2489 `_). + Thanks `@Roman-Koshelev `_. + +* Optimized writing to non-``char`` buffers + (`#2477 `_). + Thanks `@Roman-Koshelev `_. + +* Decimal point is now localized when using the ``L`` specifier. + +* Improved floating point formatter implementation + (`#2498 `_, + `#2499 `_). + Thanks `@Roman-Koshelev `_. + +* Fixed handling of very large precision in fixed format + (`#2616 `_). + +* Made a table of cached powers used in FP formatting static + (`#2509 `_). + Thanks `@jk-jeon (Junekey Jeon) `_. + +* Resolved a lookup ambiguity with C++20 format-related functions due to ADL + (`#2639 `_, + `#2641 `_). + Thanks `@mkurdej (Marek Kurdej) `_. + +* Removed unnecessary inline namespace qualification + (`#2642 `_, + `#2643 `_). + Thanks `@mkurdej (Marek Kurdej) `_. + +* Implemented argument forwarding in ``format_to_n`` + (`#2462 `_, + `#2463 `_). + Thanks `@owent (WenTao Ou) `_. + +* Fixed handling of implicit conversions in ``fmt::to_string`` and format string + compilation (`#2565 `_). + +* Changed the default access mode of files created by ``fmt::output_file`` to + ``-rw-r--r--`` for consistency with ``fopen`` + (`#2530 `_). + +* Make ``fmt::ostream::flush`` public + (`#2435 `_). + +* Improved C++14/17 attribute detection + (`#2615 `_). + Thanks `@AlexGuteniev (Alex Guteniev) `_. + +* Improved ``consteval`` detection for MSVC + (`#2559 `_). + Thanks `@DanielaE (Daniela Engert) `_. + +* Improved documentation + (`#2406 `_, + `#2446 `_, + `#2493 `_, + `#2513 `_, + `#2515 `_, + `#2522 `_, + `#2562 `_, + `#2575 `_, + `#2606 `_, + `#2620 `_, + `#2676 `_). + Thanks `@sobolevn (Nikita Sobolev) `_, + `@UnePierre (Max FERGER) `_, + `@zhsj `_, + `@phprus (Vladislav Shchapov) `_, + `@ericcurtin (Eric Curtin) `_, + `@Lounarok `_. + +* Improved fuzzers and added a fuzzer for chrono timepoint formatting + (`#2461 `_, + `#2469 `_). + `@pauldreik (Paul Dreik) `_, + +* Added the ``FMT_SYSTEM_HEADERS`` CMake option setting which marks {fmt}'s + headers as system. It can be used to suppress warnings + (`#2644 `_, + `#2651 `_). + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Added the Bazel build system support + (`#2505 `_, + `#2516 `_). + Thanks `@Vertexwahn `_. + +* Improved build configuration and tests + (`#2437 `_, + `#2558 `_, + `#2648 `_, + `#2650 `_, + `#2663 `_, + `#2677 `_). + Thanks `@DanielaE (Daniela Engert) `_, + `@alexezeder (Alexey Ochapov) `_, + `@phprus (Vladislav Shchapov) `_. + +* Fixed various warnings and compilation issues + (`#2353 `_, + `#2356 `_, + `#2399 `_, + `#2408 `_, + `#2414 `_, + `#2427 `_, + `#2432 `_, + `#2442 `_, + `#2434 `_, + `#2439 `_, + `#2447 `_, + `#2450 `_, + `#2455 `_, + `#2465 `_, + `#2472 `_, + `#2474 `_, + `#2476 `_, + `#2478 `_, + `#2479 `_, + `#2481 `_, + `#2482 `_, + `#2483 `_, + `#2490 `_, + `#2491 `_, + `#2510 `_, + `#2518 `_, + `#2528 `_, + `#2529 `_, + `#2539 `_, + `#2540 `_, + `#2545 `_, + `#2555 `_, + `#2557 `_, + `#2570 `_, + `#2573 `_, + `#2582 `_, + `#2605 `_, + `#2611 `_, + `#2647 `_, + `#2627 `_, + `#2630 `_, + `#2635 `_, + `#2638 `_, + `#2653 `_, + `#2654 `_, + `#2661 `_, + `#2664 `_, + `#2684 `_). + Thanks `@DanielaE (Daniela Engert) `_, + `@mwinterb `_, + `@cdacamar (Cameron DaCamara) `_, + `@TrebledJ (Johnathan) `_, + `@bodomartin (brm) `_, + `@cquammen (Cory Quammen) `_, + `@white238 (Chris White) `_, + `@mmarkeloff (Max) `_, + `@palacaze (Pierre-Antoine Lacaze) `_, + `@jcelerier (Jean-Michaël Celerier) `_, + `@mborn-adi (Mathias Born) `_, + `@BrukerJWD (Jonathan W) `_, + `@spyridon97 (Spiros Tsalikis) `_, + `@phprus (Vladislav Shchapov) `_, + `@oliverlee (Oliver Lee) `_, + `@joshessman-llnl (Josh Essman) `_, + `@akohlmey (Axel Kohlmeyer) `_, + `@timkalu `_, + `@olupton (Olli Lupton) `_, + `@Acretock `_, + `@alexezeder (Alexey Ochapov) `_, + `@andrewcorrigan (Andrew Corrigan) `_, + `@lucpelletier `_, + `@HazardyKnusperkeks (Björn Schäpers) `_. + +8.0.1 - 2021-07-02 +------------------ + +* Fixed the version number in the inline namespace + (`#2374 `_). + +* Added a missing presentation type check for ``std::string`` + (`#2402 `_). + +* Fixed a linkage error when mixing code built with clang and gcc + (`#2377 `_). + +* Fixed documentation issues + (`#2396 `_, + `#2403 `_, + `#2406 `_). + Thanks `@mkurdej (Marek Kurdej) `_. + +* Removed dead code in FP formatter ( + `#2398 `_). + Thanks `@javierhonduco (Javier Honduvilla Coto) + `_. + +* Fixed various warnings and compilation issues + (`#2351 `_, + `#2359 `_, + `#2365 `_, + `#2368 `_, + `#2370 `_, + `#2376 `_, + `#2381 `_, + `#2382 `_, + `#2386 `_, + `#2389 `_, + `#2395 `_, + `#2397 `_, + `#2400 `_, + `#2401 `_, + `#2407 `_). + Thanks `@zx2c4 (Jason A. Donenfeld) `_, + `@AidanSun05 (Aidan Sun) `_, + `@mattiasljungstrom (Mattias Ljungström) + `_, + `@joemmett (Jonathan Emmett) `_, + `@erengy (Eren Okka) `_, + `@patlkli (Patrick Geltinger) `_, + `@gsjaardema (Greg Sjaardema) `_, + `@phprus (Vladislav Shchapov) `_. + 8.0.0 - 2021-06-21 ------------------ -* Enabled compile-time format string check by default. +* Enabled compile-time format string checks by default. For example (`godbolt `__): .. code:: c++ @@ -232,6 +652,9 @@ This doesn't introduce a dependency on ```` so there is virtually no compile time effect. +* Deprecated an undocumented ``format_to`` overload that takes + ``basic_memory_buffer``. + * Made parameter order in ``vformat_to`` consistent with ``format_to`` (`#2327 `_). @@ -301,9 +724,9 @@ ``make_format_to_n_args``. They have been replaced with ``format_context``, ``format_args` and ``make_format_args`` respectively. -* Moved ``wchar_t``-specific functions and types to ``fmt/wchar.h``. - You can define ``FMT_DEPRECATED_INCLUDE_WCHAR`` to automatically include - ``fmt/wchar.h`` from ``fmt/format.h`` but this will be disabled in the next +* Moved ``wchar_t``-specific functions and types to ``fmt/xchar.h``. + You can define ``FMT_DEPRECATED_INCLUDE_XCHAR`` to automatically include + ``fmt/xchar.h`` from ``fmt/format.h`` but this will be disabled in the next major release. * Fixed handling of the ``'+'`` specifier in localized formatting @@ -338,6 +761,9 @@ Thanks `@mikecrowe (Mike Crowe) `_. +* The undocumented support for specializing ``formatter`` for pointer types + has been removed. + * Fixed ``fmt::formatted_size`` with format string compilation (`#2141 `_, `#2161 `_). @@ -510,13 +936,13 @@ `#2067 `_, `#2068 `_, `#2073 `_, - `#2103 `_ - `#2105 `_ + `#2103 `_, + `#2105 `_, `#2106 `_, `#2107 `_, - `#2116 `_ + `#2116 `_, `#2117 `_, - `#2118 `_ + `#2118 `_, `#2119 `_, `#2127 `_, `#2128 `_, @@ -589,7 +1015,7 @@ `@yeswalrus (Walter Gray) `_, `@Finkman `_, `@HazardyKnusperkeks (Björn Schäpers) `_, - `@dkavolis (Daumantas Kavolis) `_ + `@dkavolis (Daumantas Kavolis) `_, `@concatime (Issam Maghni) `_, `@chronoxor (Ivan Shynkarenka) `_, `@summivox (Yin Zhong) `_, @@ -1046,7 +1472,7 @@ `#1912 `_, `#1928 `_, `#1929 `_, - `#1935 `_ + `#1935 `_, `#1937 `_, `#1942 `_, `#1949 `_). diff --git a/README.rst b/README.rst index 02c849c7..394f28d9 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ .. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows -.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v +.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true :target: https://ci.appveyor.com/project/vitaut/fmt .. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg @@ -143,10 +143,10 @@ Output:: .. code:: c++ - std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number"); + std::string s = fmt::format("{:d}", "I am not a number"); -This gives a compile-time error because ``d`` is an invalid format specifier for -a string. +This gives a compile-time error in C++20 because ``d`` is an invalid format +specifier for a string. **Write a file from a single thread** @@ -205,7 +205,7 @@ The above results were generated by building ``tinyformat_test.cpp`` on macOS best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"`` or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for further details refer to the `source -`_. +`_. {fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on floating-point formatting (`dtoa-benchmark `_) @@ -469,7 +469,7 @@ Boost Format This is a very powerful library which supports both ``printf``-like format strings and positional arguments. Its main drawback is performance. According to -various, benchmarks it is much slower than other methods considered here. Boost +various benchmarks, it is much slower than other methods considered here. Boost Format also has excessive build times and severe code bloat issues (see `Benchmarks`_). diff --git a/doc/api.rst b/doc/api.rst index e295f171..93fb3faf 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -37,10 +37,12 @@ similar to that of Python's `str.format `_. They take *fmt* and *args* as arguments. -*fmt* is a format string that contains literal text and replacement -fields surrounded by braces ``{}``. The fields are replaced with formatted -arguments in the resulting string. A function taking *fmt* doesn't -participate in an overload resolution if the latter is not a string. +*fmt* is a format string that contains literal text and replacement fields +surrounded by braces ``{}``. The fields are replaced with formatted arguments +in the resulting string. `~fmt::format_string` is a format string which can be +implicitly constructed from a string literal or a ``constexpr`` string and is +checked at compile time in C++20. To pass a runtime format string wrap it in +`fmt::runtime`. *args* is an argument list representing objects to be formatted. @@ -50,7 +52,7 @@ participate in an overload resolution if the latter is not a string. .. doxygenfunction:: vformat(string_view fmt, format_args args) -> std::string .. doxygenfunction:: format_to(OutputIt out, format_string fmt, T&&... args) -> OutputIt -.. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string fmt, const T&... args) -> format_to_n_result +.. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string fmt, T&&... args) -> format_to_n_result .. doxygenfunction:: formatted_size(format_string fmt, T&&... args) -> size_t .. doxygenstruct:: fmt::format_to_n_result @@ -59,7 +61,7 @@ participate in an overload resolution if the latter is not a string. .. _print: .. doxygenfunction:: fmt::print(format_string fmt, T&&... args) -.. doxygenfunction:: vprint(string_view fmt, format_args args) +.. doxygenfunction:: fmt::vprint(string_view fmt, format_args args) .. doxygenfunction:: print(std::FILE *f, format_string fmt, T&&... args) .. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args) @@ -70,6 +72,7 @@ Compile-time Format String Checks Compile-time checks are enabled when using ``FMT_STRING``. They support built-in and string types as well as user-defined types with ``constexpr`` ``parse`` functions in their ``formatter`` specializations. +Requires C++14 and is a no-op in C++11. .. doxygendefine:: FMT_STRING @@ -78,6 +81,13 @@ To force the use of compile-time checks, define the preprocessor variable will fail to compile with regular strings. Runtime-checked formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc. +.. doxygenclass:: fmt::basic_format_string + :members: + +.. doxygentypedef:: fmt::format_string + +.. doxygenfunction:: fmt::runtime(const S&) + Named Arguments --------------- @@ -177,7 +187,9 @@ template and implement ``parse`` and ``format`` methods:: #include - struct point { double x, y; }; + struct point { + double x, y; + }; template <> struct fmt::formatter { // Presentation format: 'f' - fixed, 'e' - exponential. @@ -201,8 +213,7 @@ template and implement ``parse`` and ``format`` methods:: if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++; // Check if reached the end of the range: - if (it != end && *it != '}') - throw format_error("invalid format"); + if (it != end && *it != '}') throw format_error("invalid format"); // Return an iterator past the end of the parsed range: return it; @@ -213,10 +224,9 @@ template and implement ``parse`` and ``format`` methods:: template auto format(const point& p, FormatContext& ctx) -> decltype(ctx.out()) { // ctx.out() is an output iterator to write to. - return format_to( - ctx.out(), - presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})", - p.x, p.y); + return presentation == 'f' + ? format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y) + : format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y); } }; @@ -314,6 +324,8 @@ Utilities .. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view +.. doxygenfunction:: fmt::group_digits(T value) -> group_digits_view + .. doxygenclass:: fmt::detail::buffer :members: @@ -350,8 +362,8 @@ allocator:: custom_string vformat(custom_allocator alloc, fmt::string_view format_str, fmt::format_args args) { - custom_memory_buffer buf(alloc); - fmt::vformat_to(buf, format_str, args); + auto buf = custom_memory_buffer(alloc); + fmt::vformat_to(std::back_inserter(buf), format_str, args); return custom_string(buf.data(), buf.size(), alloc); } @@ -519,8 +531,8 @@ argument type doesn't match its format specification. ``wchar_t`` Support =================== -The optional header ``fmt/wchar_t.h`` provides support for ``wchar_t`` and -exotic character types. +The optional header ``fmt/xchar.h`` provides support for ``wchar_t`` and exotic +character types. .. doxygenstruct:: fmt::is_char diff --git a/doc/basic-bootstrap/layout.html b/doc/basic-bootstrap/layout.html index a257b03d..5519c4b5 100644 --- a/doc/basic-bootstrap/layout.html +++ b/doc/basic-bootstrap/layout.html @@ -90,12 +90,14 @@ VERSION: '{{ release|e }}', COLLAPSE_INDEX: false, FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}', + LINK_SUFFIX: '{{ link_suffix }}', + SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}', HAS_SOURCE: {{ has_source|lower }}, SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}' }; {%- for scriptfile in script_files %} - + {{ js_tag(scriptfile) }} {%- endfor %} {%- endmacro %} diff --git a/doc/build.py b/doc/build.py index 406bb8cf..ae1ccfc8 100755 --- a/doc/build.py +++ b/doc/build.py @@ -4,7 +4,7 @@ import errno, os, re, sys from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT -versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0'] +versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1'] class Pip: def __init__(self, venv_dir): @@ -26,8 +26,10 @@ def create_build_env(venv_dir='virtualenv'): pip = Pip(venv_dir) pip.install('wheel') pip.install('six') + # See: https://github.com/sphinx-doc/sphinx/issues/9777 + pip.install('docutils==0.17.1') pip.install('sphinx-doc/sphinx', 'v3.3.0') - pip.install('michaeljones/breathe', 'v4.16.0') + pip.install('michaeljones/breathe', 'v4.25.0') def build_docs(version='dev', **kwargs): doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__))) @@ -58,6 +60,7 @@ def build_docs(version='dev', **kwargs): MACRO_EXPANSION = YES PREDEFINED = _WIN32=1 \ __linux__=1 \ + FMT_ENABLE_IF(...)= \ FMT_USE_VARIADIC_TEMPLATES=1 \ FMT_USE_RVALUE_REFERENCES=1 \ FMT_USE_USER_DEFINED_LITERALS=1 \ @@ -66,6 +69,8 @@ def build_docs(version='dev', **kwargs): "FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_END_NAMESPACE=}}" \ "FMT_STRING_ALIAS=1" \ + "FMT_VARIADIC(...)=" \ + "FMT_VARIADIC_W(...)=" \ "FMT_DOC=1" EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \ fmt::basic_format_arg::handle diff --git a/doc/syntax.rst b/doc/syntax.rst index 4eff1d4f..9d3cb57c 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -112,18 +112,18 @@ meaning in this case. The *sign* option is only valid for number types, and can be one of the following: -+---------+----------------------------------------------------------+ -| Option | Meaning | -+=========+==========================================================+ -| ``'+'`` | indicates that a sign should be used for both | -| | positive as well as negative numbers. | -+---------+----------------------------------------------------------+ -| ``'-'`` | indicates that a sign should be used only for negative | -| | numbers (this is the default behavior). | -+---------+----------------------------------------------------------+ -| space | indicates that a leading space should be used on | -| | positive numbers, and a minus sign on negative numbers. | -+---------+----------------------------------------------------------+ ++---------+------------------------------------------------------------+ +| Option | Meaning | ++=========+============================================================+ +| ``'+'`` | indicates that a sign should be used for both | +| | nonnegative as well as negative numbers. | ++---------+------------------------------------------------------------+ +| ``'-'`` | indicates that a sign should be used only for negative | +| | numbers (this is the default behavior). | ++---------+------------------------------------------------------------+ +| space | indicates that a leading space should be used on | +| | nonnegative numbers, and a minus sign on negative numbers. | ++---------+------------------------------------------------------------+ The ``'#'`` option causes the "alternate form" to be used for the conversion. The alternate form is defined differently for different @@ -161,7 +161,8 @@ displayed after the decimal point for a floating-point value formatted with value formatted with ``'g'`` or ``'G'``. For non-number types the field indicates the maximum field size - in other words, how many characters will be used from the field content. The *precision* is not allowed for integer, -character, Boolean, and pointer values. +character, Boolean, and pointer values. Note that a C string must be +null-terminated even if precision is specified. The ``'L'`` option uses the current locale setting to insert the appropriate number separator characters. This option is only valid for numeric types. @@ -449,7 +450,7 @@ Using type-specific formatting:: Using the comma as a thousands separator:: - #include + #include auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); // s == "1,234,567,890" diff --git a/include/fmt/args.h b/include/fmt/args.h index 562e8ab1..9a8e4ed2 100644 --- a/include/fmt/args.h +++ b/include/fmt/args.h @@ -143,6 +143,8 @@ class dynamic_format_arg_store } public: + constexpr dynamic_format_arg_store() = default; + /** \rst Adds an argument into the dynamic store for later passing to a formatting diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index c024fd71..682efd8d 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -11,13 +11,29 @@ #include #include #include +#include #include -#include +#include +#include #include "format.h" FMT_BEGIN_NAMESPACE +// Enable tzset. +#ifndef FMT_USE_TZSET +// UWP doesn't provide _tzset. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# define FMT_USE_TZSET 1 +# else +# define FMT_USE_TZSET 0 +# endif +#endif + // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 @@ -44,7 +60,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { static_assert(T::is_integer, "To must be integral"); // A and B are both signed, or both unsigned. - if (F::digits <= T::digits) { + if (detail::const_check(F::digits <= T::digits)) { // From fits in To without any problem. } else { // From does not always fit in To, resort to a dynamic check. @@ -79,14 +95,15 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { return {}; } // From is positive. Can it always fit in To? - if (F::digits > T::digits && + if (detail::const_check(F::digits > T::digits) && from > static_cast(detail::max_value())) { ec = 1; return {}; } } - if (!F::is_signed && T::is_signed && F::digits >= T::digits && + if (detail::const_check(!F::is_signed && T::is_signed && + F::digits >= T::digits) && from > static_cast(detail::max_value())) { ec = 1; return {}; @@ -243,7 +260,7 @@ To safe_duration_cast(std::chrono::duration from, } // multiply with Factor::num without overflow or underflow - if (Factor::num != 1) { + if (detail::const_check(Factor::num != 1)) { constexpr auto max1 = detail::max_value() / static_cast(Factor::num); if (count > max1) { @@ -260,7 +277,7 @@ To safe_duration_cast(std::chrono::duration from, } // this can't go wrong, right? den>0 is checked earlier. - if (Factor::den != 1) { + if (detail::const_check(Factor::den != 1)) { using common_t = typename std::common_type::type; count /= static_cast(Factor::den); } @@ -288,74 +305,139 @@ inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } -inline auto do_write(const std::tm& time, const std::locale& loc, char format, - char modifier) -> std::string { - auto&& os = std::ostringstream(); - os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); - auto end = facet.put(os, os, ' ', &time, format, modifier); - if (end.failed()) FMT_THROW(format_error("failed to format time")); - auto str = os.str(); - if (!detail::is_utf8() || loc == std::locale::classic()) return str; +inline const std::locale& get_classic_locale() { + static const auto& locale = std::locale::classic(); + return locale; +} + +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; +template +constexpr const size_t codecvt_result::max_size; + +template +void write_codecvt(codecvt_result& out, string_view in_buf, + const std::locale& loc) { + using codecvt = std::codecvt; +#if FMT_CLANG_VERSION +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated" + auto& f = std::use_facet(loc); +# pragma clang diagnostic pop +#else + auto& f = std::use_facet(loc); +#endif + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, + std::begin(out.buf), std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { + if (detail::is_utf8() && loc != get_classic_locale()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. #if FMT_MSC_VER != 0 || \ (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)) - // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 - // and newer. - using code_unit = wchar_t; + // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 + // and newer. + using code_unit = wchar_t; #else - using code_unit = char32_t; + using code_unit = char32_t; #endif - auto& f = std::use_facet>(loc); - auto mb = std::mbstate_t(); - const char* from_next = nullptr; - code_unit* to_next = nullptr; - constexpr size_t buf_size = 32; - code_unit buf[buf_size] = {}; - auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf, - buf + buf_size, to_next); - if (result != std::codecvt_base::ok) - FMT_THROW(format_error("failed to format time")); - str.clear(); - for (code_unit* p = buf; p != to_next; ++p) { - uint32_t c = static_cast(*p); - if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { - // surrogate pair - ++p; - if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto&& buf = basic_memory_buffer(); + for (code_unit* p = unit.buf; p != unit.end; ++p) { + uint32_t c = static_cast(*p); + if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { + // surrogate pair + ++p; + if (p == unit.end || (c & 0xfc00) != 0xd800 || + (*p & 0xfc00) != 0xdc00) { + FMT_THROW(format_error("failed to format time")); + } + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { FMT_THROW(format_error("failed to format time")); } - c = (c << 10) + static_cast(*p) - 0x35fdc00; - } - if (c < 0x80) { - str.push_back(static_cast(c)); - } else if (c < 0x800) { - str.push_back(static_cast(0xc0 | (c >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - str.push_back(static_cast(0xe0 | (c >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else if (c >= 0x10000 && c <= 0x10ffff) { - str.push_back(static_cast(0xf0 | (c >> 18))); - str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else { - FMT_THROW(format_error("failed to format time")); } + return copy_str(buf.data(), buf.data() + buf.size(), out); } - return str; + return copy_str(in.data(), in.data() + in.size(), out); } -template +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy_str(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + using iterator = std::ostreambuf_iterator; + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> auto write(OutputIt out, const std::tm& time, const std::locale& loc, char format, char modifier = 0) -> OutputIt { - auto str = do_write(time, loc, format, modifier); - return std::copy(str.begin(), str.end(), out); + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return buf.out(); } + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); +} + } // namespace detail FMT_MODULE_EXPORT_BEGIN @@ -453,103 +535,40 @@ inline std::tm gmtime( FMT_BEGIN_DETAIL_NAMESPACE -inline size_t strftime(char* str, size_t count, const char* format, - const std::tm* time) { - // Assign to a pointer to suppress GCCs -Wformat-nonliteral - // First assign the nullptr to suppress -Wsuggest-attribute=format - std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) = - nullptr; - strftime = std::strftime; - return strftime(str, count, format, time); +// Writes two-digit numbers a, b and c separated by sep to buf. +// The method by Pavel Novikov based on +// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. +inline void write_digit2_separated(char* buf, unsigned a, unsigned b, + unsigned c, char sep) { + unsigned long long digits = + a | (b << 24) | (static_cast(c) << 48); + // Convert each value to BCD. + // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. + // The difference is + // y - x = a * 6 + // a can be found from x: + // a = floor(x / 10) + // then + // y = x + a * 6 = x + floor(x / 10) * 6 + // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). + digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; + // Put low nibbles to high bytes and high nibbles to low bytes. + digits = ((digits & 0x00f00000f00000f0) >> 4) | + ((digits & 0x000f00000f00000f) << 8); + auto usep = static_cast(sep); + // Add ASCII '0' to each digit byte and insert separators. + digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); + + constexpr const size_t len = 8; + if (const_check(is_big_endian())) { + char tmp[len]; + memcpy(tmp, &digits, len); + std::reverse_copy(tmp, tmp + len, buf); + } else { + memcpy(buf, &digits, len); + } } -inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, - const std::tm* time) { - // See above - std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*, - const std::tm*) = nullptr; - wcsftime = std::wcsftime; - return wcsftime(str, count, format, time); -} - -FMT_END_DETAIL_NAMESPACE - -template -struct formatter, - Char> : formatter { - FMT_CONSTEXPR formatter() { - this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)}; - } - - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(); - if (it != ctx.end() && *it == ':') ++it; - auto end = it; - while (end != ctx.end() && *end != '}') ++end; - if (end != it) this->specs = {it, detail::to_unsigned(end - it)}; - return end; - } - - template - auto format(std::chrono::time_point val, - FormatContext& ctx) -> decltype(ctx.out()) { - std::tm time = localtime(val); - return formatter::format(time, ctx); - } - - static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-', - '%', 'd', ' ', '%', 'H', ':', - '%', 'M', ':', '%', 'S'}; -}; - -template -constexpr Char - formatter, - Char>::default_specs[]; - -template struct formatter { - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(); - if (it != ctx.end() && *it == ':') ++it; - auto end = it; - while (end != ctx.end() && *end != '}') ++end; - specs = {it, detail::to_unsigned(end - it)}; - return end; - } - - template - auto format(const std::tm& tm, FormatContext& ctx) const - -> decltype(ctx.out()) { - basic_memory_buffer tm_format; - tm_format.append(specs.begin(), specs.end()); - // By appending an extra space we can distinguish an empty result that - // indicates insufficient buffer size from a guaranteed non-empty result - // https://github.com/fmtlib/fmt/issues/2238 - tm_format.push_back(' '); - tm_format.push_back('\0'); - basic_memory_buffer buf; - size_t start = buf.size(); - for (;;) { - size_t size = buf.capacity() - start; - size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm); - if (count != 0) { - buf.resize(start + count); - break; - } - const size_t MIN_GROWTH = 10; - buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); - } - // Remove the extra space. - return std::copy(buf.begin(), buf.end() - 1, ctx.out()); - } - - basic_string_view specs; -}; - -FMT_BEGIN_DETAIL_NAMESPACE - template FMT_CONSTEXPR inline const char* get_units() { if (std::is_same::value) return "as"; if (std::is_same::value) return "fs"; @@ -610,6 +629,22 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_text(tab, tab + 1); break; } + // Year: + case 'Y': + handler.on_year(numeric_system::standard); + break; + case 'y': + handler.on_short_year(numeric_system::standard); + break; + case 'C': + handler.on_century(numeric_system::standard); + break; + case 'G': + handler.on_iso_week_based_year(); + break; + case 'g': + handler.on_iso_week_based_short_year(); + break; // Day of the week: case 'a': handler.on_abbr_weekday(); @@ -625,11 +660,34 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; // Month: case 'b': + case 'h': handler.on_abbr_month(); break; case 'B': handler.on_full_month(); break; + case 'm': + handler.on_dec_month(numeric_system::standard); + break; + // Day of the year/month: + case 'U': + handler.on_dec0_week_of_year(numeric_system::standard); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::standard); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::standard); + break; + case 'j': + handler.on_day_of_year(); + break; + case 'd': + handler.on_day_of_month(numeric_system::standard); + break; + case 'e': + handler.on_day_of_month_space(numeric_system::standard); + break; // Hour, minute, second: case 'H': handler.on_24_hour(numeric_system::standard); @@ -688,6 +746,15 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { + case 'Y': + handler.on_year(numeric_system::alternative); + break; + case 'y': + handler.on_offset_year(); + break; + case 'C': + handler.on_century(numeric_system::alternative); + break; case 'c': handler.on_datetime(numeric_system::alternative); break; @@ -706,6 +773,27 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { + case 'y': + handler.on_short_year(numeric_system::alternative); + break; + case 'm': + handler.on_dec_month(numeric_system::alternative); + break; + case 'U': + handler.on_dec0_week_of_year(numeric_system::alternative); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::alternative); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::alternative); + break; + case 'd': + handler.on_day_of_month(numeric_system::alternative); + break; + case 'e': + handler.on_day_of_month_space(numeric_system::alternative); + break; case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; @@ -741,12 +829,25 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void unsupported() { static_cast(this)->unsupported(); } + FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_offset_year() { unsupported(); } + FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } FMT_CONSTEXPR void on_full_weekday() { unsupported(); } FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_abbr_month() { unsupported(); } FMT_CONSTEXPR void on_full_month() { unsupported(); } + FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_day_of_year() { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } @@ -766,6 +867,509 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; +struct tm_format_checker : null_chrono_spec_handler { + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_year(numeric_system) {} + FMT_CONSTEXPR void on_short_year(numeric_system) {} + FMT_CONSTEXPR void on_offset_year() {} + FMT_CONSTEXPR void on_century(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_based_year() {} + FMT_CONSTEXPR void on_iso_week_based_short_year() {} + FMT_CONSTEXPR void on_abbr_weekday() {} + FMT_CONSTEXPR void on_full_weekday() {} + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} + FMT_CONSTEXPR void on_abbr_month() {} + FMT_CONSTEXPR void on_full_month() {} + FMT_CONSTEXPR void on_dec_month(numeric_system) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {} + FMT_CONSTEXPR void on_day_of_year() {} + FMT_CONSTEXPR void on_day_of_month(numeric_system) {} + FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} + FMT_CONSTEXPR void on_24_hour(numeric_system) {} + FMT_CONSTEXPR void on_12_hour(numeric_system) {} + FMT_CONSTEXPR void on_minute(numeric_system) {} + FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_datetime(numeric_system) {} + FMT_CONSTEXPR void on_loc_date(numeric_system) {} + FMT_CONSTEXPR void on_loc_time(numeric_system) {} + FMT_CONSTEXPR void on_us_date() {} + FMT_CONSTEXPR void on_iso_date() {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_utc_offset() {} + FMT_CONSTEXPR void on_tz_name() {} +}; + +inline const char* tm_wday_full_name(int wday) { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; +} +inline const char* tm_wday_short_name(int wday) { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; +} + +inline const char* tm_mon_full_name(int mon) { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; +} +inline const char* tm_mon_short_name(int mon) { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; +} + +template +struct has_member_data_tm_gmtoff : std::false_type {}; +template +struct has_member_data_tm_gmtoff> + : std::true_type {}; + +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + +#if FMT_USE_TZSET +inline void tzset_once() { + static bool init = []() -> bool { + _tzset(); + return true; + }(); + ignore_unused(init); +} +#endif + +template class tm_writer { + private: + static constexpr int days_per_week = 7; + + const std::locale& loc_; + const bool is_classic_; + OutputIt out_; + const std::tm& tm_; + + auto tm_sec() const noexcept -> int { + FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); + return tm_.tm_sec; + } + auto tm_min() const noexcept -> int { + FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); + return tm_.tm_min; + } + auto tm_hour() const noexcept -> int { + FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); + return tm_.tm_hour; + } + auto tm_mday() const noexcept -> int { + FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); + return tm_.tm_mday; + } + auto tm_mon() const noexcept -> int { + FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); + return tm_.tm_mon; + } + auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } + auto tm_wday() const noexcept -> int { + FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); + return tm_.tm_wday; + } + auto tm_yday() const noexcept -> int { + FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); + return tm_.tm_yday; + } + + auto tm_hour12() const noexcept -> int { + const auto h = tm_hour(); + const auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; + } + + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 characters, with more only if necessary. + auto split_year_lower(long long year) const noexcept -> int { + auto l = year % 100; + if (l < 0) l = -l; // l in [0, 99] + return static_cast(l); + } + + // Algorithm: + // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date + auto iso_year_weeks(long long curr_year) const noexcept -> int { + const auto prev_year = curr_year - 1; + const auto curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + days_per_week; + const auto prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + days_per_week; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); + } + auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { + return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / + days_per_week; + } + auto tm_iso_week_year() const noexcept -> long long { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return year - 1; + if (w > iso_year_weeks(year)) return year + 1; + return year; + } + auto tm_iso_week_of_year() const noexcept -> int { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return iso_year_weeks(year - 1); + if (w > iso_year_weeks(year)) return 1; + return w; + } + + void write1(int value) { + *out_++ = static_cast('0' + to_unsigned(value) % 10); + } + void write2(int value) { + const char* d = digits2(to_unsigned(value) % 100); + *out_++ = *d++; + *out_++ = *d; + } + + void write_year_extended(long long year) { + // At least 4 characters. + int width = 4; + if (year < 0) { + *out_++ = '-'; + year = 0 - year; + --width; + } + uint32_or_64_or_128_t n = to_unsigned(year); + const int num_digits = count_digits(n); + if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0'); + out_ = format_decimal(out_, n, num_digits).end; + } + void write_year(long long year) { + if (year >= 0 && year < 10000) { + write2(static_cast(year / 100)); + write2(static_cast(year % 100)); + } else { + write_year_extended(year); + } + } + + void write_utc_offset(long offset) { + if (offset < 0) { + *out_++ = '-'; + offset = -offset; + } else { + *out_++ = '+'; + } + offset /= 60; + write2(static_cast(offset / 60)); + write2(static_cast(offset % 60)); + } + template ::value)> + void format_utc_offset_impl(const T& tm) { + write_utc_offset(tm.tm_gmtoff); + } + template ::value)> + void format_utc_offset_impl(const T& tm) { +#if defined(_WIN32) && defined(_UCRT) +# if FMT_USE_TZSET + tzset_once(); +# endif + long offset = 0; + _get_timezone(&offset); + if (tm.tm_isdst) { + long dstbias = 0; + _get_dstbias(&dstbias); + offset += dstbias; + } + write_utc_offset(-offset); +#else + ignore_unused(tm); + format_localized('z'); +#endif + } + + template ::value)> + void format_tz_name_impl(const T& tm) { + if (is_classic_) + out_ = write_tm_str(out_, tm.tm_zone, loc_); + else + format_localized('Z'); + } + template ::value)> + void format_tz_name_impl(const T&) { + format_localized('Z'); + } + + void format_localized(char format, char modifier = 0) { + out_ = write(out_, tm_, loc_, format, modifier); + } + + public: + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + tm_(tm) {} + + OutputIt out() const { return out_; } + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + out_ = copy_str(begin, end, out_); + } + + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } + void on_dec0_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); + format_localized('w', 'O'); + } + void on_dec1_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write1(wday == 0 ? days_per_week : wday); + } else { + format_localized('u', 'O'); + } + } + + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } + + void on_datetime(numeric_system ns) { + if (is_classic_) { + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month_space(numeric_system::standard); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard); + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } + } + void on_loc_date(numeric_system ns) { + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_loc_time(numeric_system ns) { + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_us_date() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_mon() + 1), + to_unsigned(tm_mday()), + to_unsigned(split_year_lower(tm_year())), '/'); + out_ = copy_str(std::begin(buf), std::end(buf), out_); + } + void on_iso_date() { + auto year = tm_year(); + char buf[10]; + size_t offset = 0; + if (year >= 0 && year < 10000) { + copy2(buf, digits2(to_unsigned(year / 100))); + } else { + offset = 4; + write_year_extended(year); + year = 0; + } + write_digit2_separated(buf + 2, static_cast(year % 100), + to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), + '-'); + out_ = copy_str(std::begin(buf) + offset, std::end(buf), out_); + } + + void on_utc_offset() { format_utc_offset_impl(tm_); } + void on_tz_name() { format_tz_name_impl(tm_); } + + void on_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write_year(tm_year()); + format_localized('Y', 'E'); + } + void on_short_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(split_year_lower(tm_year())); + format_localized('y', 'O'); + } + void on_offset_year() { + if (is_classic_) return write2(split_year_lower(tm_year())); + format_localized('y', 'E'); + } + + void on_century(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto year = tm_year(); + auto upper = year / 100; + if (year >= -99 && year < 0) { + // Zero upper on negative year. + *out_++ = '-'; + *out_++ = '0'; + } else if (upper >= 0 && upper < 100) { + write2(static_cast(upper)); + } else { + out_ = write(out_, upper); + } + } else { + format_localized('C', 'E'); + } + } + + void on_dec_month(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mon() + 1); + format_localized('m', 'O'); + } + + void on_dec0_week_of_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week); + format_localized('U', 'O'); + } + void on_dec1_week_of_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write2((tm_yday() + days_per_week - + (wday == 0 ? (days_per_week - 1) : (wday - 1))) / + days_per_week); + } else { + format_localized('W', 'O'); + } + } + void on_iso_week_of_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_iso_week_of_year()); + format_localized('V', 'O'); + } + + void on_iso_week_based_year() { write_year(tm_iso_week_year()); } + void on_iso_week_based_short_year() { + write2(split_year_lower(tm_iso_week_year())); + } + + void on_day_of_year() { + auto yday = tm_yday() + 1; + write1(yday / 100); + write2(yday % 100); + } + void on_day_of_month(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday()); + format_localized('d', 'O'); + } + void on_day_of_month_space(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto mday = to_unsigned(tm_mday()) % 100; + const char* d2 = digits2(mday); + *out_++ = mday < 10 ? ' ' : d2[0]; + *out_++ = d2[1]; + } else { + format_localized('e', 'O'); + } + } + + void on_24_hour(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour()); + format_localized('H', 'O'); + } + void on_12_hour(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour12()); + format_localized('I', 'O'); + } + void on_minute(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_min()); + format_localized('M', 'O'); + } + void on_second(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_sec()); + format_localized('S', 'O'); + } + + void on_12_hour_time() { + if (is_classic_) { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy_str(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); + } else { + format_localized('r'); + } + } + void on_24_hour_time() { + write2(tm_hour()); + *out_++ = ':'; + write2(tm_min()); + } + void on_iso_time() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour()), to_unsigned(tm_min()), + to_unsigned(tm_sec()), ':'); + out_ = copy_str(std::begin(buf), std::end(buf), out_); + } + + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } + + // These apply to chrono durations but not tm. + void on_duration_value() {} + void on_duration_unit() {} +}; + struct chrono_format_checker : null_chrono_spec_handler { FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } @@ -796,26 +1400,20 @@ template ::value)> inline bool isfinite(T) { return true; } -template ::value)> -inline bool isfinite(T value) { - return std::isfinite(value); -} -// Converts value to int and checks that it's in the range [0, upper). -template ::value)> -inline int to_nonnegative_int(T value, int upper) { +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper), "invalid value"); (void)upper; - return static_cast(value); + return static_cast(value); } -template ::value)> -inline int to_nonnegative_int(T value, int upper) { - FMT_ASSERT( - std::isnan(value) || (value >= 0 && value <= static_cast(upper)), - "invalid value"); - (void)upper; - return static_cast(value); +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { + if (value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return static_cast(value); } template ::value)> @@ -872,15 +1470,37 @@ inline std::chrono::duration get_milliseconds( #endif } -template ::value)> -inline std::chrono::duration get_milliseconds( +// Returns the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +constexpr int count_fractional_digits(long long num, long long den, int n = 0) { + return num % den == 0 + ? n + : (n > 18 ? 6 : count_fractional_digits(num * 10, den, n + 1)); +} + +constexpr long long pow10(std::uint32_t n) { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +template ::is_signed)> +constexpr std::chrono::duration abs( std::chrono::duration d) { - using common_type = typename std::common_type::type; - auto ms = mod(d.count() * static_cast(Period::num) / - static_cast(Period::den) * 1000, - 1000); - return std::chrono::duration(static_cast(ms)); + // We need to compare the duration using the count() method directly + // due to a compiler bug in clang-11 regarding the spaceship operator, + // when -Wzero-as-null-pointer-constant is enabled. + // In clang-12 the bug has been fixed. See + // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: + // https://www.godbolt.org/z/Knbb5joYx. + return d.count() >= d.zero().count() ? d : -d; +} + +template ::is_signed)> +constexpr std::chrono::duration abs( + std::chrono::duration d) { + return d; } template (); specs.precision = precision; - specs.type = precision > 0 ? 'f' : 'g'; + specs.type = precision >= 0 ? presentation_type::fixed_lower + : presentation_type::general_lower; return write(out, val, specs); } @@ -926,6 +1547,26 @@ OutputIt format_duration_unit(OutputIt out) { return out; } +class get_locale { + private: + union { + std::locale locale_; + }; + bool has_locale_ = false; + + public: + get_locale(bool localized, locale_ref loc) : has_locale_(localized) { + if (localized) + ::new (&locale_) std::locale(loc.template get()); + } + ~get_locale() { + if (has_locale_) locale_.~locale(); + } + operator const std::locale&() const { + return has_locale_ ? locale_ : get_classic_locale(); + } +}; + template struct chrono_formatter { @@ -944,9 +1585,10 @@ struct chrono_formatter { bool negative; using char_type = typename FormatContext::char_type; + using tm_writer_type = tm_writer; - explicit chrono_formatter(FormatContext& ctx, OutputIt o, - std::chrono::duration d) + chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration d) : context(ctx), out(o), val(static_cast(d.count())), @@ -1021,15 +1663,48 @@ struct chrono_formatter { out = format_decimal(out, n, num_digits).end; } + template void write_fractional_seconds(Duration d) { + constexpr auto num_fractional_digits = + count_fractional_digits(Duration::period::num, Duration::period::den); + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + if (std::ratio_less::value) { + *out++ = '.'; + // Don't convert long double to integer seconds to avoid overflow. + using sec = conditional_t< + std::is_same::value, + std::chrono::duration, std::chrono::seconds>; + auto fractional = detail::abs(d) - std::chrono::duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : std::chrono::duration_cast(fractional) + .count(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(subseconds, max_value())); + int num_digits = detail::count_digits(n); + if (num_fractional_digits > num_digits) + out = std::fill_n(out, num_fractional_digits - num_digits, '0'); + out = format_decimal(out, n, num_digits).end; + } + } + void write_nan() { std::copy_n("nan", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } - void format_localized(const tm& time, char format, char modifier = 0) { + template + void format_tm(const tm& time, Callback cb, Args... args) { if (isnan(val)) return write_nan(); - const auto& loc = localized ? context.locale().template get() - : std::locale::classic(); - out = detail::write(out, time, loc, format, modifier); + get_locale loc(localized, context.locale()); + auto w = tm_writer_type(loc, out, time); + (w.*cb)(args...); + out = w.out(); } void on_text(const char_type* begin, const char_type* end) { @@ -1050,6 +1725,19 @@ struct chrono_formatter { void on_iso_date() {} void on_utc_offset() {} void on_tz_name() {} + void on_year(numeric_system) {} + void on_short_year(numeric_system) {} + void on_offset_year() {} + void on_century(numeric_system) {} + void on_iso_week_based_year() {} + void on_iso_week_based_short_year() {} + void on_dec_month(numeric_system) {} + void on_dec0_week_of_year(numeric_system) {} + void on_dec1_week_of_year(numeric_system) {} + void on_iso_week_of_year(numeric_system) {} + void on_day_of_year() {} + void on_day_of_month(numeric_system) {} + void on_day_of_month_space(numeric_system) {} void on_24_hour(numeric_system ns) { if (handle_nan_inf()) return; @@ -1057,7 +1745,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) return write(hour(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_localized(time, 'H', 'O'); + format_tm(time, &tm_writer_type::on_24_hour, ns); } void on_12_hour(numeric_system ns) { @@ -1066,7 +1754,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) return write(hour12(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_localized(time, 'I', 'O'); + format_tm(time, &tm_writer_type::on_12_hour, ns); } void on_minute(numeric_system ns) { @@ -1075,7 +1763,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) return write(minute(), 2); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_localized(time, 'M', 'O'); + format_tm(time, &tm_writer_type::on_minute, ns); } void on_second(numeric_system ns) { @@ -1083,29 +1771,17 @@ struct chrono_formatter { if (ns == numeric_system::standard) { write(second(), 2); -#if FMT_SAFE_DURATION_CAST - // convert rep->Rep - using duration_rep = std::chrono::duration; - using duration_Rep = std::chrono::duration; - auto tmpval = fmt_safe_duration_cast(duration_rep{val}); -#else - auto tmpval = std::chrono::duration(val); -#endif - auto ms = get_milliseconds(tmpval); - if (ms != std::chrono::milliseconds(0)) { - *out++ = '.'; - write(ms.count(), 3); - } + write_fractional_seconds(std::chrono::duration{val}); return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_localized(time, 'S', 'O'); + format_tm(time, &tm_writer_type::on_second, ns); } void on_12_hour_time() { if (handle_nan_inf()) return; - format_localized(time(), 'r'); + format_tm(time(), &tm_writer_type::on_12_hour_time); } void on_24_hour_time() { @@ -1124,12 +1800,12 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - write(second(), 2); + on_second(numeric_system::standard); } void on_am_pm() { if (handle_nan_inf()) return; - format_localized(time(), 'p'); + format_tm(time(), &tm_writer_type::on_am_pm); } void on_duration_value() { @@ -1159,15 +1835,18 @@ class weekday { : value(static_cast(wd != 7 ? wd : 0)) {} constexpr unsigned c_encoding() const noexcept { return value; } }; + +class year_month_day {}; #endif // A rudimentary weekday formatter. -template <> struct formatter { +template struct formatter { private: bool localized = false; public: - FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { auto begin = ctx.begin(), end = ctx.end(); if (begin != end && *begin == 'L') { ++begin; @@ -1176,12 +1855,14 @@ template <> struct formatter { return begin; } - auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) { + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); - const auto& loc = localized ? ctx.locale().template get() - : std::locale::classic(); - return detail::write(ctx.out(), time, loc, 'a'); + detail::get_locale loc(localized, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_weekday(); + return w.out(); } }; @@ -1260,7 +1941,8 @@ struct formatter, Char> { ++begin; localized = true; } - end = parse_chrono_format(begin, end, detail::chrono_format_checker()); + end = detail::parse_chrono_format(begin, end, + detail::chrono_format_checker()); return {begin, end}; } @@ -1302,6 +1984,83 @@ struct formatter, Char> { } }; +template +struct formatter, + Char> : formatter { + FMT_CONSTEXPR formatter() { + this->do_parse(default_specs, + default_specs + sizeof(default_specs) / sizeof(Char)); + } + + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return this->do_parse(ctx.begin(), ctx.end(), true); + } + + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + return formatter::format(localtime(val), ctx); + } + + static constexpr const Char default_specs[] = {'%', 'F', ' ', '%', 'T'}; +}; + +template +constexpr const Char + formatter, + Char>::default_specs[]; + +template struct formatter { + private: + enum class spec { + unknown, + year_month_day, + hh_mm_ss, + }; + spec spec_ = spec::unknown; + basic_string_view specs; + + protected: + template + FMT_CONSTEXPR auto do_parse(It begin, It end, bool with_default = false) + -> It { + if (begin != end && *begin == ':') ++begin; + end = detail::parse_chrono_format(begin, end, detail::tm_format_checker()); + if (!with_default || end != begin) + specs = {begin, detail::to_unsigned(end - begin)}; + // basic_string_view<>::compare isn't constexpr before C++17. + if (specs.size() == 2 && specs[0] == Char('%')) { + if (specs[1] == Char('F')) + spec_ = spec::year_month_day; + else if (specs[1] == Char('T')) + spec_ = spec::hh_mm_ss; + } + return end; + } + + public: + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return this->do_parse(ctx.begin(), ctx.end()); + } + + template + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = detail::tm_writer(loc, ctx.out(), tm); + if (spec_ == spec::year_month_day) + w.on_iso_date(); + else if (spec_ == spec::hh_mm_ss) + w.on_iso_time(); + else + detail::parse_chrono_format(specs.begin(), specs.end(), w); + return w.out(); + } +}; + FMT_MODULE_EXPORT_END FMT_END_NAMESPACE diff --git a/include/fmt/color.h b/include/fmt/color.h index 7fa5490e..dfbe4829 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -185,9 +185,13 @@ enum class terminal_color : uint8_t { enum class emphasis : uint8_t { bold = 1, - italic = 1 << 1, - underline = 1 << 2, - strikethrough = 1 << 3 + faint = 1 << 1, + italic = 1 << 2, + underline = 1 << 3, + blink = 1 << 4, + reverse = 1 << 5, + conceal = 1 << 6, + strikethrough = 1 << 7, }; // rgb is a struct for red, green and blue colors. @@ -409,16 +413,18 @@ template struct ansi_color_escape { buffer[19] = static_cast(0); } FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { - uint8_t em_codes[4] = {}; - uint8_t em_bits = static_cast(em); - if (em_bits & static_cast(emphasis::bold)) em_codes[0] = 1; - if (em_bits & static_cast(emphasis::italic)) em_codes[1] = 3; - if (em_bits & static_cast(emphasis::underline)) em_codes[2] = 4; - if (em_bits & static_cast(emphasis::strikethrough)) - em_codes[3] = 9; + uint8_t em_codes[num_emphases] = {}; + if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; + if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; + if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; + if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; + if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; + if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; + if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; + if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; size_t index = 0; - for (int i = 0; i < 4; ++i) { + for (size_t i = 0; i < num_emphases; ++i) { if (!em_codes[i]) continue; buffer[index++] = static_cast('\x1b'); buffer[index++] = static_cast('['); @@ -435,7 +441,8 @@ template struct ansi_color_escape { } private: - Char buffer[7u + 3u * 4u + 1u]; + static constexpr size_t num_emphases = 8; + Char buffer[7u + 3u * num_emphases + 1u]; static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, char delimiter) FMT_NOEXCEPT { @@ -444,6 +451,10 @@ template struct ansi_color_escape { out[2] = static_cast('0' + c % 10); out[3] = static_cast(delimiter); } + static FMT_CONSTEXPR bool has_emphasis(emphasis em, + emphasis mask) FMT_NOEXCEPT { + return static_cast(em) & static_cast(mask); + } }; template @@ -507,7 +518,7 @@ void vformat_to(buffer& buf, const text_style& ts, auto background = detail::make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } - detail::vformat_to(buf, format_str, args); + detail::vformat_to(buf, format_str, args, {}); if (has_style) detail::reset_color(buf); } diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 00000c92..1dba3ddb 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -156,7 +156,7 @@ struct is_compiled_string : std::is_base_of {}; std::string s = fmt::format(FMT_COMPILE("{}"), 42); \endrst */ -#ifdef __cpp_if_constexpr +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) # define FMT_COMPILE(s) \ FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) #else @@ -179,7 +179,7 @@ const T& first(const T& value, const Tail&...) { return value; } -#ifdef __cpp_if_constexpr +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template struct type_list {}; // Returns a reference to the argument at index N from [first, rest...]. @@ -190,7 +190,7 @@ constexpr const auto& get([[maybe_unused]] const T& first, if constexpr (N == 0) return first; else - return get(rest...); + return detail::get(rest...); } template @@ -202,7 +202,8 @@ constexpr int get_arg_index_by_name(basic_string_view name, template struct get_type_impl; template struct get_type_impl> { - using type = remove_cvref_t(std::declval()...))>; + using type = + remove_cvref_t(std::declval()...))>; }; template @@ -242,7 +243,7 @@ template struct code_unit { // This ensures that the argument type is convertible to `const T&`. template constexpr const T& get_arg_checked(const Args&... args) { - const auto& arg = get(args...); + const auto& arg = detail::get(args...); if constexpr (detail::is_named_arg>()) { return arg.value; } else { @@ -289,7 +290,7 @@ template struct runtime_named_field { constexpr OutputIt format(OutputIt out, const Args&... args) const { bool found = (try_format_argument(out, name, args) || ...); if (!found) { - throw format_error("argument with specified name is not found"); + FMT_THROW(format_error("argument with specified name is not found")); } return out; } @@ -399,7 +400,9 @@ template struct arg_id_handler { return 0; } - constexpr void on_error(const char* message) { throw format_error(message); } + constexpr void on_error(const char* message) { + FMT_THROW(format_error(message)); + } }; template struct parse_arg_id_result { @@ -451,7 +454,7 @@ constexpr auto compile_format_string(S format_str) { constexpr auto str = basic_string_view(format_str); if constexpr (str[POS] == '{') { if constexpr (POS + 1 == str.size()) - throw format_error("unmatched '{' in format string"); + FMT_THROW(format_error("unmatched '{' in format string")); if constexpr (str[POS + 1] == '{') { return parse_tail(make_text(str, POS, 1), format_str); } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { @@ -500,7 +503,7 @@ constexpr auto compile_format_string(S format_str) { } } else if constexpr (str[POS] == '}') { if constexpr (POS + 1 == str.size()) - throw format_error("unmatched '}' in format string"); + FMT_THROW(format_error("unmatched '}' in format string")); return parse_tail(make_text(str, POS, 1), format_str); } else { constexpr auto end = parse_text(str, POS + 1); @@ -527,12 +530,12 @@ constexpr auto compile(S format_str) { return result; } } -#endif // __cpp_if_constexpr +#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail FMT_MODULE_EXPORT_BEGIN -#ifdef __cpp_if_constexpr +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template // std::FILE +#include // std::byte +#include // std::FILE #include #include #include @@ -16,29 +17,33 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 80000 +#define FMT_VERSION 80101 -#ifdef __clang__ +#if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else # define FMT_CLANG_VERSION 0 #endif -#if defined(__GNUC__) && !defined(__clang__) +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \ + !defined(__NVCOMPILER) # define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -# define FMT_GCC_PRAGMA(arg) _Pragma(arg) #else # define FMT_GCC_VERSION 0 -# define FMT_GCC_PRAGMA(arg) #endif -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION -#else -# define FMT_HAS_GXX_CXX11 0 +#ifndef FMT_GCC_PRAGMA +// Workaround _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884. +# if FMT_GCC_VERSION >= 504 +# define FMT_GCC_PRAGMA(arg) _Pragma(arg) +# else +# define FMT_GCC_PRAGMA(arg) +# endif #endif -#if defined(__INTEL_COMPILER) +#ifdef __ICL +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) # define FMT_ICC_VERSION __INTEL_COMPILER #else # define FMT_ICC_VERSION 0 @@ -64,7 +69,8 @@ # define FMT_HAS_FEATURE(x) 0 #endif -#if defined(__has_include) && !defined(__INTELLISENSE__) && \ +#if defined(__has_include) && \ + (!defined(__INTELLISENSE__) || FMT_MSC_VER > 1900) && \ (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600) # define FMT_HAS_INCLUDE(x) __has_include(x) #else @@ -77,17 +83,23 @@ # define FMT_HAS_CPP_ATTRIBUTE(x) 0 #endif +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + #define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ - (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) #define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ - (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) // Check if relaxed C++14 constexpr is supported. // GCC doesn't allow throw in constexpr until version 6 (bug 67371). #ifndef FMT_USE_CONSTEXPR # define FMT_USE_CONSTEXPR \ - (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ + (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1912 || \ (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ !FMT_NVCC && !FMT_ICC_VERSION #endif @@ -99,6 +111,14 @@ # define FMT_CONSTEXPR_DECL #endif +#if ((__cplusplus >= 202002L) && \ + (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ + (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEXPR20 +#endif + // Check if constexpr std::char_traits<>::compare,length is supported. #if defined(__GLIBCXX__) # if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \ @@ -115,15 +135,6 @@ # define FMT_CONSTEXPR_CHAR_TRAITS #endif -#ifndef FMT_OVERRIDE -# if FMT_HAS_FEATURE(cxx_override_control) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_OVERRIDE override -# else -# define FMT_OVERRIDE -# endif -#endif - // Check if exceptions are disabled. #ifndef FMT_EXCEPTIONS # if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ @@ -140,7 +151,7 @@ #endif #if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 + FMT_GCC_VERSION >= 408 || FMT_MSC_VER >= 1900 # define FMT_DETECTED_NOEXCEPT noexcept # define FMT_HAS_CXX11_NOEXCEPT 1 #else @@ -165,14 +176,6 @@ # define FMT_NORETURN #endif -#ifndef FMT_MAYBE_UNUSED -# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) -# define FMT_MAYBE_UNUSED [[maybe_unused]] -# else -# define FMT_MAYBE_UNUSED -# endif -#endif - #if __cplusplus == 201103L || __cplusplus == 201402L # if defined(__INTEL_COMPILER) || defined(__PGI) # define FMT_FALLTHROUGH @@ -184,13 +187,20 @@ # else # define FMT_FALLTHROUGH # endif -#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ - (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) # define FMT_FALLTHROUGH [[fallthrough]] #else # define FMT_FALLTHROUGH #endif +#ifndef FMT_NODISCARD +# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +# else +# define FMT_NODISCARD +# endif +#endif + #ifndef FMT_USE_FLOAT # define FMT_USE_FLOAT 1 #endif @@ -209,31 +219,27 @@ # endif #endif -#ifndef FMT_USE_INLINE_NAMESPACES -# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ - (FMT_MSC_VER >= 1900 && (!defined(_MANAGED) || !_MANAGED)) -# define FMT_USE_INLINE_NAMESPACES 1 +#ifndef FMT_DEPRECATED +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 +# define FMT_DEPRECATED [[deprecated]] # else -# define FMT_USE_INLINE_NAMESPACES 0 +# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) +# define FMT_DEPRECATED __attribute__((deprecated)) +# elif FMT_MSC_VER +# define FMT_DEPRECATED __declspec(deprecated) +# else +# define FMT_DEPRECATED /* deprecated */ +# endif # endif #endif #ifndef FMT_BEGIN_NAMESPACE -# if FMT_USE_INLINE_NAMESPACES -# define FMT_INLINE_NAMESPACE inline namespace -# define FMT_END_NAMESPACE \ - } \ - } -# else -# define FMT_INLINE_NAMESPACE namespace -# define FMT_END_NAMESPACE \ - } \ - using namespace v7; \ - } -# endif # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ - FMT_INLINE_NAMESPACE v7 { + inline namespace v8 { +# define FMT_END_NAMESPACE \ + } \ + } #endif #ifndef FMT_MODULE_EXPORT @@ -263,12 +269,6 @@ # define FMT_API #endif -#if FMT_GCC_VERSION -# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) -#else -# define FMT_GCC_VISIBILITY_HIDDEN -#endif - // libc++ supports string_view in pre-c++17. #if (FMT_HAS_INCLUDE() && \ (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ @@ -285,10 +285,11 @@ #endif #ifndef FMT_CONSTEVAL -# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ - __cplusplus > 201703L) || \ - (defined(__cpp_consteval) && \ - !FMT_MSC_VER) // consteval is broken in MSVC. +# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ + __cplusplus > 201703L && !defined(__apple_build_version__)) || \ + (defined(__cpp_consteval) && \ + (!FMT_MSC_VER || _MSC_FULL_VER >= 193030704)) +// consteval is broken in MSVC before VS2022 and Apple clang 13. # define FMT_CONSTEVAL consteval # define FMT_HAS_CONSTEVAL # else @@ -316,14 +317,16 @@ FMT_BEGIN_NAMESPACE FMT_MODULE_EXPORT_BEGIN // Implementations of enable_if_t and other metafunctions for older systems. -template +template using enable_if_t = typename std::enable_if::type; -template +template using conditional_t = typename std::conditional::type; template using bool_constant = std::integral_constant; template using remove_reference_t = typename std::remove_reference::type; template +using remove_const_t = typename std::remove_const::type; +template using remove_cvref_t = typename std::remove_cv>::type; template struct type_identity { using type = T; }; template using type_identity_t = typename type_identity::type; @@ -343,16 +346,25 @@ struct monostate { FMT_BEGIN_DETAIL_NAMESPACE -constexpr FMT_INLINE auto is_constant_evaluated() FMT_NOEXCEPT -> bool { +// Suppress "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr FMT_INLINE auto is_constant_evaluated(bool default_value = false) + FMT_NOEXCEPT -> bool { #ifdef __cpp_lib_is_constant_evaluated + ignore_unused(default_value); return std::is_constant_evaluated(); #else - return false; + return default_value; #endif } // A function to suppress "conditional expression is constant" warnings. -template constexpr auto const_check(T value) -> T { return value; } +template constexpr FMT_INLINE auto const_check(T value) -> T { + return value; +} FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); @@ -360,7 +372,8 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, #ifndef FMT_ASSERT # ifdef NDEBUG // FMT_ASSERT is not empty to avoid -Werror=empty-body. -# define FMT_ASSERT(condition, message) ((void)0) +# define FMT_ASSERT(condition, message) \ + ::fmt::detail::ignore_unused((condition), (message)) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ @@ -369,6 +382,12 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, # endif #endif +#ifdef __cpp_lib_byte +using byte = std::byte; +#else +enum class byte : unsigned char {}; +#endif + #if defined(FMT_USE_STRING_VIEW) template using std_string_view = std::basic_string_view; #elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) @@ -450,13 +469,12 @@ template class basic_string_view { */ FMT_CONSTEXPR_CHAR_TRAITS FMT_INLINE - basic_string_view(const Char* s) : data_(s) { - if (detail::const_check(std::is_same::value && - !detail::is_constant_evaluated())) - size_ = std::strlen(reinterpret_cast(s)); - else - size_ = std::char_traits::length(s); - } + basic_string_view(const Char* s) + : data_(s), + size_(detail::const_check(std::is_same::value && + !detail::is_constant_evaluated(true)) + ? std::strlen(reinterpret_cast(s)) + : std::char_traits::length(s)) {} /** Constructs a string reference from a ``std::basic_string`` object. */ template @@ -471,19 +489,19 @@ template class basic_string_view { size_(s.size()) {} /** Returns a pointer to the string data. */ - constexpr auto data() const -> const Char* { return data_; } + constexpr auto data() const FMT_NOEXCEPT -> const Char* { return data_; } /** Returns the string size. */ - constexpr auto size() const -> size_t { return size_; } + constexpr auto size() const FMT_NOEXCEPT -> size_t { return size_; } - constexpr auto begin() const -> iterator { return data_; } - constexpr auto end() const -> iterator { return data_ + size_; } + constexpr auto begin() const FMT_NOEXCEPT -> iterator { return data_; } + constexpr auto end() const FMT_NOEXCEPT -> iterator { return data_ + size_; } - constexpr auto operator[](size_t pos) const -> const Char& { + constexpr auto operator[](size_t pos) const FMT_NOEXCEPT -> const Char& { return data_[pos]; } - FMT_CONSTEXPR void remove_prefix(size_t n) { + FMT_CONSTEXPR void remove_prefix(size_t n) FMT_NOEXCEPT { data_ += n; size_ -= n; } @@ -525,39 +543,21 @@ using string_view = basic_string_view; template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; -/** - \rst - Returns a string view of `s`. In order to add custom string type support to - {fmt} provide an overload of `to_string_view` for it in the same namespace as - the type for the argument-dependent lookup to work. - - **Example**:: - - namespace my_ns { - inline string_view to_string_view(const my_string& s) { - return {s.data(), s.length()}; - } - } - std::string message = fmt::format(my_string("The answer is {}"), 42); - \endrst - */ +// Returns a string view of `s`. template ::value)> FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { return s; } - template inline auto to_string_view(const std::basic_string& s) -> basic_string_view { return s; } - template constexpr auto to_string_view(basic_string_view s) -> basic_string_view { return s; } - template >::value)> inline auto to_string_view(detail::std_string_view s) @@ -581,7 +581,7 @@ constexpr auto to_string_view(const S& s) FMT_BEGIN_DETAIL_NAMESPACE void to_string_view(...); -using fmt::v7::to_string_view; +using fmt::to_string_view; // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in @@ -608,6 +608,8 @@ FMT_INLINE void check_format_string(const S&) { template ::value)> void check_format_string(S); +FMT_NORETURN FMT_API void throw_format_error(const char* message); + struct error_handler { constexpr error_handler() = default; constexpr error_handler(const error_handler&) = default; @@ -624,7 +626,7 @@ template using char_t = typename detail::char_t_impl::type; \rst Parsing context consisting of a format string range being parsed and an argument counter for automatic indexing. - You can use the ```format_parse_context`` type alias for ``char`` instead. + You can use the ``format_parse_context`` type alias for ``char`` instead. \endrst */ template @@ -722,6 +724,22 @@ class appender; FMT_BEGIN_DETAIL_NAMESPACE +template +constexpr auto has_const_formatter_impl(T*) + -> decltype(typename Context::template formatter_type().format( + std::declval(), std::declval()), + true) { + return true; +} +template +constexpr auto has_const_formatter_impl(...) -> bool { + return false; +} +template +constexpr auto has_const_formatter() -> bool { + return has_const_formatter_impl(static_cast(nullptr)); +} + // Extracts a reference to the container from back_insert_iterator. template inline auto get_container(std::back_insert_iterator it) @@ -741,13 +759,13 @@ FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) return out; } -template ::value)> -FMT_CONSTEXPR auto copy_str(const Char* begin, const Char* end, Char* out) - -> Char* { - if (is_constant_evaluated()) - return copy_str(begin, end, out); +template , U>::value&& is_char::value)> +FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { + if (is_constant_evaluated()) return copy_str(begin, end, out); auto size = to_unsigned(end - begin); - memcpy(out, begin, size); + memcpy(out, begin, size * sizeof(U)); return out + size; } @@ -768,22 +786,22 @@ template class buffer { FMT_MSC_WARNING(suppress : 26495) buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} - buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT - : ptr_(p), - size_(sz), - capacity_(cap) {} + FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, + size_t cap = 0) FMT_NOEXCEPT : ptr_(p), + size_(sz), + capacity_(cap) {} - ~buffer() = default; + FMT_CONSTEXPR20 ~buffer() = default; buffer(buffer&&) = default; /** Sets the buffer data and capacity. */ - void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { ptr_ = buf_data; capacity_ = buf_capacity; } /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual void grow(size_t capacity) = 0; + virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; public: using value_type = T; @@ -799,23 +817,23 @@ template class buffer { auto end() const FMT_NOEXCEPT -> const T* { return ptr_ + size_; } /** Returns the size of this buffer. */ - auto size() const FMT_NOEXCEPT -> size_t { return size_; } + constexpr auto size() const FMT_NOEXCEPT -> size_t { return size_; } /** Returns the capacity of this buffer. */ - auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; } + constexpr auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; } /** Returns a pointer to the buffer data. */ - auto data() FMT_NOEXCEPT -> T* { return ptr_; } + FMT_CONSTEXPR auto data() FMT_NOEXCEPT -> T* { return ptr_; } /** Returns a pointer to the buffer data. */ - auto data() const FMT_NOEXCEPT -> const T* { return ptr_; } + FMT_CONSTEXPR auto data() const FMT_NOEXCEPT -> const T* { return ptr_; } /** Clears this buffer. */ void clear() { size_ = 0; } // Tries resizing the buffer to contain *count* elements. If T is a POD type // the new elements may not be initialized. - void try_resize(size_t count) { + FMT_CONSTEXPR20 void try_resize(size_t count) { try_reserve(count); size_ = count <= capacity_ ? count : capacity_; } @@ -824,11 +842,11 @@ template class buffer { // capacity by a smaller amount than requested but guarantees there is space // for at least one additional element either by increasing the capacity or by // flushing the buffer if it is full. - void try_reserve(size_t new_capacity) { + FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) { if (new_capacity > capacity_) grow(new_capacity); } - void push_back(const T& value) { + FMT_CONSTEXPR20 void push_back(const T& value) { try_reserve(size_ + 1); ptr_[size_++] = value; } @@ -836,8 +854,11 @@ template class buffer { /** Appends data to the end of the buffer. */ template void append(const U* begin, const U* end); - template auto operator[](I index) -> T& { return ptr_[index]; } - template auto operator[](I index) const -> const T& { + template FMT_CONSTEXPR auto operator[](I index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](I index) const -> const T& { return ptr_[index]; } }; @@ -872,7 +893,7 @@ class iterator_buffer final : public Traits, public buffer { T data_[buffer_size]; protected: - void grow(size_t) final FMT_OVERRIDE { + FMT_CONSTEXPR20 void grow(size_t) override { if (this->size() == buffer_size) flush(); } @@ -896,9 +917,55 @@ class iterator_buffer final : public Traits, public buffer { auto count() const -> size_t { return Traits::count() + this->size(); } }; +template +class iterator_buffer final + : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() == this->capacity()) flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) + : fixed_buffer_traits(other), + buffer(std::move(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + template class iterator_buffer final : public buffer { protected: - void grow(size_t) final FMT_OVERRIDE {} + FMT_CONSTEXPR20 void grow(size_t) override {} public: explicit iterator_buffer(T* out, size_t = 0) : buffer(out, 0, ~size_t()) {} @@ -916,7 +983,7 @@ class iterator_buffer, Container& container_; protected: - void grow(size_t capacity) final FMT_OVERRIDE { + FMT_CONSTEXPR20 void grow(size_t capacity) override { container_.resize(capacity); this->set(&container_[0], capacity); } @@ -939,7 +1006,7 @@ template class counting_buffer final : public buffer { size_t count_ = 0; protected: - void grow(size_t) final FMT_OVERRIDE { + FMT_CONSTEXPR20 void grow(size_t) override { if (this->size() != buffer_size) return; count_ += this->size(); this->clear(); @@ -1056,6 +1123,11 @@ template constexpr auto count_named_args() -> size_t { return count::value...>(); } +template +constexpr auto count_statically_named_args() -> size_t { + return count::value...>(); +} + enum class type { none_type, // Integer types should go first, @@ -1111,6 +1183,11 @@ constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } +struct unformattable {}; +struct unformattable_char : unformattable {}; +struct unformattable_const : unformattable {}; +struct unformattable_pointer : unformattable {}; + template struct string_value { const Char* data; size_t size; @@ -1123,8 +1200,8 @@ template struct named_arg_value { template struct custom_value { using parse_context = typename Context::parse_context_type; - const void* value; - void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); }; // A formatting argument value. @@ -1158,8 +1235,8 @@ template class value { constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} FMT_INLINE value(int128_t val) : int128_value(val) {} FMT_INLINE value(uint128_t val) : uint128_value(val) {} - FMT_INLINE value(float val) : float_value(val) {} - FMT_INLINE value(double val) : double_value(val) {} + constexpr FMT_INLINE value(float val) : float_value(val) {} + constexpr FMT_INLINE value(double val) : double_value(val) {} FMT_INLINE value(long double val) : long_double_value(val) {} constexpr FMT_INLINE value(bool val) : bool_value(val) {} constexpr FMT_INLINE value(char_type val) : char_value(val) {} @@ -1175,26 +1252,34 @@ template class value { FMT_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} - template FMT_CONSTEXPR FMT_INLINE value(const T& val) { - custom.value = &val; + template FMT_CONSTEXPR FMT_INLINE value(T& val) { + using value_type = remove_cvref_t; + custom.value = const_cast(&val); // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. custom.format = format_custom_arg< - T, conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>>; + value_type, + conditional_t::value, + typename Context::template formatter_type, + fallback_formatter>>; } + value(unformattable); + value(unformattable_char); + value(unformattable_const); + value(unformattable_pointer); private: // Formats an argument of a custom type, such as a user-defined class. template - static void format_custom_arg(const void* arg, + static void format_custom_arg(void* arg, typename Context::parse_context_type& parse_ctx, Context& ctx) { - Formatter f; + auto f = Formatter(); parse_ctx.advance_to(f.parse(parse_ctx)); - ctx.advance_to(f.format(*static_cast(arg), ctx)); + using qualified_type = + conditional_t(), const T, T>; + ctx.advance_to(f.format(*static_cast(arg), ctx)); } }; @@ -1207,9 +1292,9 @@ enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t; using ulong_type = conditional_t; -struct unformattable {}; - // Maps formatting arguments to core types. +// arg_mapper reports errors by returning unformattable instead of using +// static_assert because it's used in the is_formattable trait. template struct arg_mapper { using char_type = typename Context::char_type; @@ -1236,13 +1321,22 @@ template struct arg_mapper { FMT_CONSTEXPR FMT_INLINE auto map(uint128_t val) -> uint128_t { return val; } FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; } - template ::value)> + template ::value || + std::is_same::value)> FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type { - static_assert( - std::is_same::value || std::is_same::value, - "mixing character types is disallowed"); return val; } + template ::value || +#ifdef __cpp_char8_t + std::is_same::value || +#endif + std::is_same::value || + std::is_same::value) && + !std::is_same::value, + int> = 0> + FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char { + return {}; + } FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } @@ -1256,13 +1350,19 @@ template struct arg_mapper { FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { return val; } - template ::value)> + template ::value && !std::is_pointer::value && + std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> basic_string_view { - static_assert(std::is_same>::value, - "mixing character types is disallowed"); return to_string_view(val); } + template ::value && !std::is_pointer::value && + !std::is_same>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { + return {}; + } template , T>::value && @@ -1283,21 +1383,25 @@ template struct arg_mapper { -> basic_string_view { return std_string_view(val); } - FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) -> const char* { - static_assert(std::is_same::value, "invalid string type"); - return reinterpret_cast(val); + + using cstring_result = conditional_t::value, + const char*, unformattable_pointer>; + + FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) + -> cstring_result { + return map(reinterpret_cast(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) -> const char* { - static_assert(std::is_same::value, "invalid string type"); - return reinterpret_cast(val); + FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) + -> cstring_result { + return map(reinterpret_cast(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) -> const char* { - const auto* const_val = val; - return map(const_val); + FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) + -> cstring_result { + return map(reinterpret_cast(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) -> const char* { - const auto* const_val = val; - return map(const_val); + FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) + -> cstring_result { + return map(reinterpret_cast(val)); } FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } @@ -1310,37 +1414,69 @@ template struct arg_mapper { // We use SFINAE instead of a const T* parameter to avoid conflicting with // the C array overload. - template - FMT_CONSTEXPR auto map(T) -> enable_if_t::value, int> { - // Formatting of arbitrary pointers is disallowed. If you want to output - // a pointer cast it to "void *" or "const void *". In particular, this - // forbids formatting of "[const] volatile char *" which is printed as bool - // by iostreams. - static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); - return 0; + template < + typename T, + FMT_ENABLE_IF( + std::is_member_pointer::value || + std::is_function::type>::value || + (std::is_convertible::value && + !std::is_convertible::value))> + FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { + return {}; } - template + template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { return values; } template ::value && - !has_formatter::value && - !has_fallback_formatter::value)> + FMT_ENABLE_IF( + std::is_enum::value&& std::is_convertible::value && + !has_formatter::value && + !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(std::declval().map( static_cast::type>(val))) { return map(static_cast::type>(val)); } - template ::value && !is_char::value && - (has_formatter::value || - has_fallback_formatter::value))> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> const T& { + + FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned { + return map(static_cast(val)); + } + + template > + struct formattable + : bool_constant() || + !std::is_const>::value || + has_fallback_formatter::value> {}; + +#if FMT_MSC_VER != 0 && FMT_MSC_VER < 1910 + // Workaround a bug in MSVC. + template FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { return val; } +#else + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + return val; + } + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { + return {}; + } +#endif + + template , + FMT_ENABLE_IF(!is_string::value && !is_char::value && + !std::is_array::value && + (has_formatter::value || + has_fallback_formatter::value))> + FMT_CONSTEXPR FMT_INLINE auto map(T&& val) + -> decltype(this->do_map(std::forward(val))) { + return do_map(std::forward(val)); + } template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) @@ -1377,19 +1513,12 @@ class appender : public std::back_insert_iterator> { public: using std::back_insert_iterator>::back_insert_iterator; - appender(base it) : base(it) {} + appender(base it) FMT_NOEXCEPT : base(it) {} using _Unchecked_type = appender; // Mark iterator as checked. - auto operator++() -> appender& { - base::operator++(); - return *this; - } + auto operator++() FMT_NOEXCEPT -> appender& { return *this; } - auto operator++(int) -> appender { - auto tmp = *this; - ++*this; - return tmp; - } + auto operator++(int) FMT_NOEXCEPT -> appender { return *this; } }; // A formatting argument. It is a trivially copyable/constructible type to @@ -1573,10 +1702,30 @@ FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg { // another (not recommended). template -FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value { - const auto& arg = arg_mapper().map(val); +FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { + const auto& arg = arg_mapper().map(std::forward(val)); + + constexpr bool formattable_char = + !std::is_same::value; + static_assert(formattable_char, "Mixing character types is disallowed."); + + constexpr bool formattable_const = + !std::is_same::value; + static_assert(formattable_const, "Cannot format a const argument."); + + // Formatting of arbitrary pointers is disallowed. If you want to output + // a pointer cast it to "void *" or "const void *". In particular, this + // forbids formatting of "[const] volatile char *" which is printed as bool + // by iostreams. + constexpr bool formattable_pointer = + !std::is_same::value; + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); + + constexpr bool formattable = + !std::is_same::value; static_assert( - !std::is_same::value, + formattable, "Cannot format an argument. To make type T formattable provide a " "formatter specialization: https://fmt.dev/latest/api.html#udt"); return {arg}; @@ -1654,9 +1803,9 @@ using format_context = buffer_context; template using is_formattable = bool_constant< - !std::is_same>().map( - std::declval())), - detail::unformattable>::value && + !std::is_base_of>().map( + std::declval()))>::value && !detail::has_fallback_formatter::value>; /** @@ -1695,14 +1844,16 @@ class format_arg_store : 0); public: - FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args) + template + FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), #endif data_{detail::make_arg< is_packed, Context, - detail::mapped_type_constant::value>(args)...} { + detail::mapped_type_constant, Context>::value>( + std::forward(args))...} { detail::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1716,9 +1867,9 @@ class format_arg_store \endrst */ template -constexpr auto make_format_args(const Args&... args) - -> format_arg_store { - return {args...}; +constexpr auto make_format_args(Args&&... args) + -> format_arg_store...> { + return {std::forward(args)...}; } /** @@ -1877,8 +2028,6 @@ using sign_t = sign::type; FMT_BEGIN_DETAIL_NAMESPACE -void throw_format_error(const char* message); - // Workaround an array initialization issue in gcc 4.8. template struct fill_t { private: @@ -1904,11 +2053,33 @@ template struct fill_t { }; FMT_END_DETAIL_NAMESPACE +enum class presentation_type : unsigned char { + none, + // Integer types should go first, + dec, // 'd' + oct, // 'o' + hex_lower, // 'x' + hex_upper, // 'X' + bin_lower, // 'b' + bin_upper, // 'B' + hexfloat_lower, // 'a' + hexfloat_upper, // 'A' + exp_lower, // 'e' + exp_upper, // 'E' + fixed_lower, // 'f' + fixed_upper, // 'F' + general_lower, // 'g' + general_upper, // 'G' + chr, // 'c' + string, // 's' + pointer // 'p' +}; + // Format specifiers for built-in and string types. template struct basic_format_specs { int width; int precision; - char type; + presentation_type type; align_t align : 4; sign_t sign : 3; bool alt : 1; // Alternate form ('#'). @@ -1918,7 +2089,7 @@ template struct basic_format_specs { constexpr basic_format_specs() : width(0), precision(-1), - type(0), + type(presentation_type::none), align(align::none), sign(sign::none), alt(false), @@ -1998,9 +2169,7 @@ template class specs_setter { } FMT_CONSTEXPR void end_precision() {} - FMT_CONSTEXPR void on_type(Char type) { - specs_.type = static_cast(type); - } + FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } }; // Format spec handler that saves references to arguments representing dynamic @@ -2074,8 +2243,8 @@ constexpr auto to_ascii(Char value) -> template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { if (const_check(sizeof(Char) != 1)) return 1; - constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0}; + auto lengths = + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4"; int len = lengths[static_cast(*begin) >> 3]; // Compute the pointer to the next character early so that the next @@ -2282,6 +2451,48 @@ FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, return begin; } +template +FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { + switch (to_ascii(type)) { + case 'd': + return presentation_type::dec; + case 'o': + return presentation_type::oct; + case 'x': + return presentation_type::hex_lower; + case 'X': + return presentation_type::hex_upper; + case 'b': + return presentation_type::bin_lower; + case 'B': + return presentation_type::bin_upper; + case 'a': + return presentation_type::hexfloat_lower; + case 'A': + return presentation_type::hexfloat_upper; + case 'e': + return presentation_type::exp_lower; + case 'E': + return presentation_type::exp_upper; + case 'f': + return presentation_type::fixed_lower; + case 'F': + return presentation_type::fixed_upper; + case 'g': + return presentation_type::general_lower; + case 'G': + return presentation_type::general_upper; + case 'c': + return presentation_type::chr; + case 's': + return presentation_type::string; + case 'p': + return presentation_type::pointer; + default: + return presentation_type::none; + } +} + // Parses standard format specifiers and sends notifications about parsed // components to handler. template @@ -2289,9 +2500,12 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, const Char* end, SpecHandler&& handler) -> const Char* { - if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && + if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') { - handler.on_type(*begin++); + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); return begin; } @@ -2345,7 +2559,12 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, } // Parse type. - if (begin != end && *begin != '}') handler.on_type(*begin++); + if (begin != end && *begin != '}') { + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); + } return begin; } @@ -2392,7 +2611,7 @@ FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, template FMT_CONSTEXPR FMT_INLINE void parse_format_string( basic_string_view format_str, Handler&& handler) { - // this is most likely a name-lookup defect in msvc's modules implementation + // Workaround a name-lookup bug in MSVC's modules implementation. using detail::find; auto begin = format_str.data(); @@ -2420,7 +2639,7 @@ FMT_CONSTEXPR FMT_INLINE void parse_format_string( if (pbegin == pend) return; for (;;) { const Char* p = nullptr; - if (!find(pbegin, pend, '}', p)) + if (!find(pbegin, pend, Char('}'), p)) return handler_.on_text(pbegin, pend); ++p; if (p == pend || *p != '}') @@ -2435,7 +2654,7 @@ FMT_CONSTEXPR FMT_INLINE void parse_format_string( // Doing two passes with memchr (one for '{' and another for '}') is up to // 2.5x faster than the naive one-pass implementation on big format strings. const Char* p = begin; - if (*begin != '{' && !find(begin + 1, end, '{', p)) + if (*begin != '{' && !find(begin + 1, end, Char('{'), p)) return write(begin, end); write(begin, p); begin = parse_replacement_field(p, end, handler); @@ -2487,28 +2706,18 @@ class compile_parse_context }; template -FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) { - switch (spec) { - case 0: - case 'd': - case 'x': - case 'X': - case 'b': - case 'B': - case 'o': - case 'c': - break; - default: +FMT_CONSTEXPR void check_int_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type > presentation_type::bin_upper && type != presentation_type::chr) eh.on_error("invalid type specifier"); - break; - } } // Checks char specs and returns true if the type spec is char (and not int). template FMT_CONSTEXPR auto check_char_specs(const basic_format_specs& specs, ErrorHandler&& eh = {}) -> bool { - if (specs.type && specs.type != 'c') { + if (specs.type != presentation_type::none && + specs.type != presentation_type::chr) { check_int_type_spec(specs.type, eh); return false; } @@ -2532,7 +2741,7 @@ struct float_specs { bool upper : 1; bool locale : 1; bool binary32 : 1; - bool use_grisu : 1; + bool fallback : 1; bool showpoint : 1; }; @@ -2544,33 +2753,33 @@ FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, result.showpoint = specs.alt; result.locale = specs.localized; switch (specs.type) { - case 0: + case presentation_type::none: result.format = float_format::general; break; - case 'G': + case presentation_type::general_upper: result.upper = true; FMT_FALLTHROUGH; - case 'g': + case presentation_type::general_lower: result.format = float_format::general; break; - case 'E': + case presentation_type::exp_upper: result.upper = true; FMT_FALLTHROUGH; - case 'e': + case presentation_type::exp_lower: result.format = float_format::exp; result.showpoint |= specs.precision != 0; break; - case 'F': + case presentation_type::fixed_upper: result.upper = true; FMT_FALLTHROUGH; - case 'f': + case presentation_type::fixed_lower: result.format = float_format::fixed; result.showpoint |= specs.precision != 0; break; - case 'A': + case presentation_type::hexfloat_upper: result.upper = true; FMT_FALLTHROUGH; - case 'a': + case presentation_type::hexfloat_lower: result.format = float_format::hex; break; default: @@ -2580,22 +2789,27 @@ FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, return result; } -template -FMT_CONSTEXPR auto check_cstring_type_spec(Char spec, ErrorHandler&& eh = {}) - -> bool { - if (spec == 0 || spec == 's') return true; - if (spec != 'p') eh.on_error("invalid type specifier"); +template +FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, + ErrorHandler&& eh = {}) -> bool { + if (type == presentation_type::none || type == presentation_type::string) + return true; + if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); return false; } -template -FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh) { - if (spec != 0 && spec != 's') eh.on_error("invalid type specifier"); +template +FMT_CONSTEXPR void check_string_type_spec(presentation_type type, + ErrorHandler&& eh = {}) { + if (type != presentation_type::none && type != presentation_type::string) + eh.on_error("invalid type specifier"); } -template -FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) { - if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier"); +template +FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type != presentation_type::none && type != presentation_type::pointer) + eh.on_error("invalid type specifier"); } // A parse_format_specs handler that checks if specifiers are consistent with @@ -2731,14 +2945,14 @@ void check_format_string(S format_str) { remove_cvref_t...>; FMT_CONSTEXPR bool invalid_format = (parse_format_string(s, checker(s, {})), true); - (void)invalid_format; + ignore_unused(invalid_format); } template void vformat_to( buffer& buf, basic_string_view fmt, basic_format_args)> args, - detail::locale_ref loc = {}); + locale_ref loc = {}); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 @@ -2773,7 +2987,10 @@ struct formatter struct basic_runtime { basic_string_view str; }; +/** A compile-time format string. */ template class basic_format_string { private: basic_string_view str_; @@ -2836,14 +3054,15 @@ template class basic_format_string { template >::value)> - FMT_CONSTEVAL basic_format_string(const S& s) : str_(s) { + FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) { static_assert( detail::count< (std::is_base_of>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); #ifdef FMT_HAS_CONSTEVAL - if constexpr (detail::count_named_args() == 0) { + if constexpr (detail::count_named_args() == + detail::count_statically_named_args()) { using checker = detail::format_string_checker...>; detail::parse_format_string(str_, checker(s, {})); @@ -2866,7 +3085,16 @@ template auto runtime(const S& s) -> basic_string_view> { #else template using format_string = basic_format_string...>; -// Creates a runtime format string. +/** + \rst + Creates a runtime format string. + + **Example**:: + + // Check format string at runtime instead of compile-time. + fmt::print(fmt::runtime("{:d}"), "I am not a number"); + \endrst + */ template auto runtime(const S& s) -> basic_runtime> { return {{s}}; } @@ -2882,11 +3110,12 @@ FMT_API auto vformat(string_view fmt, format_args args) -> std::string; **Example**:: #include - std::string message = fmt::format("The answer is {}", 42); + std::string message = fmt::format("The answer is {}.", 42); \endrst */ template -FMT_INLINE auto format(format_string fmt, T&&... args) -> std::string { +FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) + -> std::string { return vformat(fmt, fmt::make_format_args(args...)); } @@ -2896,7 +3125,7 @@ template OutputIt { using detail::get_buffer; auto&& buf = get_buffer(out); - detail::vformat_to(buf, string_view(fmt), args); + detail::vformat_to(buf, fmt, args, {}); return detail::get_iterator(buf); } @@ -2904,7 +3133,7 @@ auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { \rst Formats ``args`` according to specifications in ``fmt``, writes the result to the output iterator ``out`` and returns the iterator past the end of the output - range. + range. `format_to` does not append a terminating null character. **Example**:: @@ -2930,10 +3159,9 @@ template ::value)> auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) -> format_to_n_result { - using buffer = - detail::iterator_buffer; - auto buf = buffer(out, n); - detail::vformat_to(buf, fmt, args); + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); return {buf.out(), buf.count()}; } @@ -2942,20 +3170,22 @@ auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) Formats ``args`` according to specifications in ``fmt``, writes up to ``n`` characters of the result to the output iterator ``out`` and returns the total (not truncated) output size and the iterator past the end of the output range. + `format_to_n` does not append a terminating null character. \endrst */ template ::value)> FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, - const T&... args) -> format_to_n_result { + T&&... args) -> format_to_n_result { return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); } /** Returns the number of chars in the output of ``format(fmt, args...)``. */ template -FMT_INLINE auto formatted_size(format_string fmt, T&&... args) -> size_t { +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...)); + detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); return buf.count(); } diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index a802aea5..2c51c50a 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -40,6 +40,10 @@ FMT_FUNC void assert_fail(const char* file, int line, const char* message) { std::terminate(); } +FMT_FUNC void throw_format_error(const char* message) { + FMT_THROW(format_error(message)); +} + #ifndef _MSC_VER # define FMT_SNPRINTF snprintf #else // _MSC_VER @@ -145,141 +149,13 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; } -#if __cplusplus < 201703L -template constexpr const char basic_data::digits[][2]; -template constexpr const char basic_data::hex_digits[]; -template constexpr const char basic_data::signs[]; -template constexpr const unsigned basic_data::prefixes[]; -template constexpr const char basic_data::left_padding_shifts[]; -template -constexpr const char basic_data::right_padding_shifts[]; -#endif +// log10(2) = 0x0.4d104d427de7fbcc... +static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc; -template struct bits { - static FMT_CONSTEXPR_DECL const int value = - static_cast(sizeof(T) * std::numeric_limits::digits); -}; - -class fp; -template fp normalize(fp value); - -// Lower (upper) boundary is a value half way between a floating-point value -// and its predecessor (successor). Boundaries have the same exponent as the -// value so only significands are stored. -struct boundaries { - uint64_t lower; - uint64_t upper; -}; - -// A handmade floating-point number f * pow(2, e). -class fp { - private: - using significand_type = uint64_t; - - template - using is_supported_float = bool_constant; - - public: - significand_type f; - int e; - - // All sizes are in bits. - // Subtract 1 to account for an implicit most significant bit in the - // normalized form. - static FMT_CONSTEXPR_DECL const int double_significand_size = - std::numeric_limits::digits - 1; - static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = - 1ULL << double_significand_size; - static FMT_CONSTEXPR_DECL const int significand_size = - bits::value; - - fp() : f(0), e(0) {} - fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} - - // Constructs fp from an IEEE754 double. It is a template to prevent compile - // errors on platforms where double is not IEEE754. - template explicit fp(Double d) { assign(d); } - - // Assigns d to this and return true iff predecessor is closer than successor. - template ::value)> - bool assign(Float d) { - // Assume float is in the format [sign][exponent][significand]. - using limits = std::numeric_limits; - const int float_significand_size = limits::digits - 1; - const int exponent_size = - bits::value - float_significand_size - 1; // -1 for sign - const uint64_t float_implicit_bit = 1ULL << float_significand_size; - const uint64_t significand_mask = float_implicit_bit - 1; - const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask; - const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; - constexpr bool is_double = sizeof(Float) == sizeof(uint64_t); - auto u = bit_cast>(d); - f = u & significand_mask; - int biased_e = - static_cast((u & exponent_mask) >> float_significand_size); - // Predecessor is closer if d is a normalized power of 2 (f == 0) other than - // the smallest normalized number (biased_e > 1). - bool is_predecessor_closer = f == 0 && biased_e > 1; - if (biased_e != 0) - f += float_implicit_bit; - else - biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = biased_e - exponent_bias - float_significand_size; - return is_predecessor_closer; - } - - template ::value)> - bool assign(Float) { - *this = fp(); - return false; - } -}; - -// Normalizes the value converted from double and multiplied by (1 << SHIFT). -template fp normalize(fp value) { - // Handle subnormals. - 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. - const auto offset = - fp::significand_size - fp::double_significand_size - SHIFT - 1; - value.f <<= offset; - value.e -= offset; - return value; -} - -inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } - -// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. -inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { -#if FMT_USE_INT128 - auto product = static_cast<__uint128_t>(lhs) * rhs; - auto f = static_cast(product >> 64); - return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; -#else - // Multiply 32-bit parts of significands. - uint64_t mask = (1ULL << 32) - 1; - uint64_t a = lhs >> 32, b = lhs & mask; - uint64_t c = rhs >> 32, d = rhs & mask; - uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; - // Compute mid 64-bit of result and round. - uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); - return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); -#endif -} - -inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; } - -// 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`. -inline fp get_cached_power(int min_exponent, int& pow10_exponent) { +template struct basic_impl_data { // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. // These are generated by support/compute-powers.py. - static constexpr const uint64_t pow10_significands[] = { + static constexpr uint64_t pow10_significands[87] = { 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, @@ -311,9 +187,13 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) { 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, }; +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnarrowing" +#endif // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding // to significands above. - static constexpr const int16_t pow10_exponents[] = { + static constexpr int16_t pow10_exponents[87] = { -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, @@ -322,11 +202,137 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) { 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +# pragma GCC diagnostic pop +#endif + static constexpr uint64_t power_of_10_64[20] = { + 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; +}; + +// This is a struct rather than an alias to avoid shadowing warnings in gcc. +struct impl_data : basic_impl_data<> {}; + +#if __cplusplus < 201703L +template +constexpr uint64_t basic_impl_data::pow10_significands[]; +template constexpr int16_t basic_impl_data::pow10_exponents[]; +template constexpr uint64_t basic_impl_data::power_of_10_64[]; +#endif + +template struct bits { + static FMT_CONSTEXPR_DECL const int value = + static_cast(sizeof(T) * std::numeric_limits::digits); +}; + +// Returns the number of significand bits in Float excluding the implicit bit. +template constexpr int num_significand_bits() { + // Subtract 1 to account for an implicit most significant bit in the + // normalized form. + return std::numeric_limits::digits - 1; +} + +// A floating-point number f * pow(2, e). +struct fp { + uint64_t f; + int e; + + static constexpr const int num_significand_bits = bits::value; + + constexpr fp() : f(0), e(0) {} + constexpr fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 floating-point number. It is a template to + // prevent compile errors on systems where n is not IEEE754. + template explicit FMT_CONSTEXPR fp(Float n) { assign(n); } + + template + using is_supported = bool_constant; + + // Assigns d to this and return true iff predecessor is closer than successor. + template ::value)> + FMT_CONSTEXPR bool assign(Float n) { + // Assume float is in the format [sign][exponent][significand]. + const int num_float_significand_bits = + detail::num_significand_bits(); + const uint64_t implicit_bit = 1ULL << num_float_significand_bits; + const uint64_t significand_mask = implicit_bit - 1; + constexpr bool is_double = sizeof(Float) == sizeof(uint64_t); + auto u = bit_cast>(n); + f = u & significand_mask; + const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask; + int biased_e = + static_cast((u & exponent_mask) >> num_float_significand_bits); + // The predecessor is closer if n is a normalized power of 2 (f == 0) other + // than the smallest normalized number (biased_e > 1). + bool is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e != 0) + f += implicit_bit; + else + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + const int exponent_bias = std::numeric_limits::max_exponent - 1; + e = biased_e - exponent_bias - num_float_significand_bits; + return is_predecessor_closer; + } + + template ::value)> + bool assign(Float) { + FMT_ASSERT(false, ""); + return false; + } +}; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template FMT_CONSTEXPR fp normalize(fp value) { + // Handle subnormals. + const uint64_t implicit_bit = 1ULL << num_significand_bits(); + const auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = + fp::num_significand_bits - num_significand_bits() - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; +} + +inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } + +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast(product >> 64); + return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); +#endif +} + +FMT_CONSTEXPR inline fp operator*(fp x, fp y) { + return {multiply(x.f, y.f), x.e + y.e + 64}; +} + +// 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_CONSTEXPR inline fp get_cached_power(int min_exponent, + int& pow10_exponent) { const int shift = 32; - const auto significand = static_cast(data::log10_2_significand); + const auto significand = static_cast(log10_2_significand); int index = static_cast( - ((min_exponent + fp::significand_size - 1) * (significand >> shift) + + ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) + ((int64_t(1) << shift) - 1)) // ceil >> 32 // arithmetic shift ); @@ -336,7 +342,8 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) { const int dec_exp_step = 8; index = (index - first_dec_exp - 1) / dec_exp_step + 1; pow10_exponent = first_dec_exp + index * dec_exp_step; - return {pow10_significands[index], pow10_exponents[index]}; + return {impl_data::pow10_significands[index], + impl_data::pow10_exponents[index]}; } // A simple accumulator to hold the sums of terms in bigint::square if uint128_t @@ -345,14 +352,16 @@ struct accumulator { uint64_t lower; uint64_t upper; - accumulator() : lower(0), upper(0) {} - explicit operator uint32_t() const { return static_cast(lower); } + constexpr accumulator() : lower(0), upper(0) {} + constexpr explicit operator uint32_t() const { + return static_cast(lower); + } - void operator+=(uint64_t n) { + FMT_CONSTEXPR void operator+=(uint64_t n) { lower += n; if (lower < n) ++upper; } - void operator>>=(int shift) { + FMT_CONSTEXPR void operator>>=(int shift) { FMT_ASSERT(shift == 32, ""); (void)shift; lower = (upper << 32) | (lower >> 32); @@ -370,27 +379,31 @@ class bigint { basic_memory_buffer bigits_; int exp_; - bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } - bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } + FMT_CONSTEXPR20 bigit operator[](int index) const { + return bigits_[to_unsigned(index)]; + } + FMT_CONSTEXPR20 bigit& operator[](int index) { + return bigits_[to_unsigned(index)]; + } static FMT_CONSTEXPR_DECL const int bigit_bits = bits::value; friend struct formatter; - void subtract_bigits(int index, bigit other, bigit& borrow) { + FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) { auto result = static_cast((*this)[index]) - other - borrow; (*this)[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } - void remove_leading_zeros() { + FMT_CONSTEXPR20 void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; bigits_.resize(to_unsigned(num_bigits + 1)); } // Computes *this -= other assuming aligned bigints and *this >= other. - void subtract_aligned(const bigint& other) { + FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) { FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; @@ -401,7 +414,7 @@ class bigint { remove_leading_zeros(); } - void multiply(uint32_t value) { + FMT_CONSTEXPR20 void multiply(uint32_t value) { const double_bigit wide_value = value; bigit carry = 0; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { @@ -412,7 +425,7 @@ class bigint { if (carry != 0) bigits_.push_back(carry); } - void multiply(uint64_t value) { + FMT_CONSTEXPR20 void multiply(uint64_t value) { const bigit mask = ~bigit(0); const double_bigit lower = value & mask; const double_bigit upper = value >> bigit_bits; @@ -430,14 +443,16 @@ class bigint { } public: - bigint() : exp_(0) {} + FMT_CONSTEXPR20 bigint() : exp_(0) {} explicit bigint(uint64_t n) { assign(n); } - ~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); } + FMT_CONSTEXPR20 ~bigint() { + FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); + } bigint(const bigint&) = delete; void operator=(const bigint&) = delete; - void assign(const bigint& other) { + FMT_CONSTEXPR20 void assign(const bigint& other) { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); @@ -445,7 +460,7 @@ class bigint { exp_ = other.exp_; } - void assign(uint64_t n) { + FMT_CONSTEXPR20 void assign(uint64_t n) { size_t num_bigits = 0; do { bigits_[num_bigits++] = n & ~bigit(0); @@ -455,9 +470,11 @@ class bigint { exp_ = 0; } - int num_bigits() const { return static_cast(bigits_.size()) + exp_; } + FMT_CONSTEXPR20 int num_bigits() const { + return static_cast(bigits_.size()) + exp_; + } - FMT_NOINLINE bigint& operator<<=(int shift) { + FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) { FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; @@ -472,13 +489,13 @@ class bigint { return *this; } - template bigint& operator*=(Int value) { + template FMT_CONSTEXPR20 bigint& operator*=(Int value) { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t(value)); return *this; } - friend int compare(const bigint& lhs, const bigint& rhs) { + friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) { int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); if (num_lhs_bigits != num_rhs_bigits) return num_lhs_bigits > num_rhs_bigits ? 1 : -1; @@ -495,8 +512,8 @@ class bigint { } // Returns compare(lhs1 + lhs2, rhs). - friend int add_compare(const bigint& lhs1, const bigint& lhs2, - const bigint& rhs) { + friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) { int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits()); int num_rhs_bigits = rhs.num_bigits(); if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; @@ -519,7 +536,7 @@ class bigint { } // Assigns pow(10, exp) to this bigint. - void assign_pow10(int exp) { + FMT_CONSTEXPR20 void assign_pow10(int exp) { FMT_ASSERT(exp >= 0, ""); if (exp == 0) return assign(1); // Find the top bit. @@ -538,7 +555,7 @@ class bigint { *this <<= exp; // Multiply by pow(2, exp) by shifting. } - void square() { + FMT_CONSTEXPR20 void square() { int num_bigits = static_cast(bigits_.size()); int num_result_bigits = 2 * num_bigits; basic_memory_buffer n(std::move(bigits_)); @@ -563,14 +580,13 @@ class bigint { (*this)[bigit_index] = static_cast(sum); sum >>= bits::value; } - --num_result_bigits; remove_leading_zeros(); exp_ *= 2; } // If this bigint has a bigger exponent than other, adds trailing zero to make // exponents equal. This simplifies some operations such as subtraction. - void align(const bigint& other) { + FMT_CONSTEXPR20 void align(const bigint& other) { int exp_difference = exp_ - other.exp_; if (exp_difference <= 0) return; int num_bigits = static_cast(bigits_.size()); @@ -583,7 +599,7 @@ class bigint { // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. - int divmod_assign(const bigint& divisor) { + FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); @@ -603,8 +619,9 @@ enum class round_direction { unknown, up, down }; // some number v and the error, returns whether v should be rounded up, down, or // whether the rounding direction can't be determined due to error. // error should be less than divisor / 2. -inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder, - uint64_t error) { +FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor, + uint64_t remainder, + uint64_t error) { FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. @@ -627,19 +644,52 @@ enum result { }; } -inline uint64_t power_of_10_64(int exp) { - static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1), - FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; - return data[exp]; -} +struct gen_digits_handler { + char* buf; + int size; + int precision; + int exp10; + bool fixed; + + FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor, + uint64_t remainder, uint64_t error, + bool integral) { + FMT_ASSERT(remainder < divisor, ""); + buf[size++] = digit; + if (!integral && error >= remainder) return digits::error; + if (size < precision) return digits::more; + if (!integral) { + // Check if error * 2 < divisor with overflow prevention. + // The check is not needed for the integral part because error = 1 + // and divisor > (1 << 32) there. + if (error >= divisor || error >= divisor - error) return digits::error; + } else { + FMT_ASSERT(error == 1 && divisor > 2, ""); + } + auto dir = get_round_direction(divisor, remainder, error); + if (dir != round_direction::up) + return dir == round_direction::down ? digits::done : digits::error; + ++buf[size - 1]; + for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[size++] = '0'; + else + ++exp10; + } + return digits::done; + } +}; // Generates output using the Grisu digit-gen algorithm. // error: the size of the region (lower, upper) outside of which numbers // definitely do not round to value (Delta in Grisu3). -template -FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, - Handler& handler) { +FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits( + fp value, uint64_t error, int& exp, gen_digits_handler& handler) { const fp one(1ULL << -value.e, value.e); // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be // zero because it contains a product of two 64-bit numbers with MSB set (due @@ -650,10 +700,28 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, // The fractional part of scaled value (p2 in Grisu) c = value % one. uint64_t fractional = value.f & (one.f - 1); exp = count_digits(integral); // kappa in Grisu. - // Divide by 10 to prevent overflow. - auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e, - value.f / 10, error * 10, exp); - if (result != digits::more) return result; + // Non-fixed formats require at least one digit and no precision adjustment. + if (handler.fixed) { + // Adjust fixed precision by exponent because it is relative to decimal + // point. + int precision_offset = exp + handler.exp10; + if (precision_offset > 0 && + handler.precision > max_value() - precision_offset) { + FMT_THROW(format_error("number is too big")); + } + handler.precision += precision_offset; + // Check if precision is satisfied just by leading zeros, e.g. + // format("{:.2f}", 0.001) gives "0.00" without generating any digits. + if (handler.precision <= 0) { + if (handler.precision < 0) return digits::done; + // Divide by 10 to prevent overflow. + uint64_t divisor = impl_data::power_of_10_64[exp - 1] << -one.e; + auto dir = get_round_direction(divisor, value.f / 10, error * 10); + if (dir == round_direction::unknown) return digits::error; + handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0'; + return digits::done; + } + } // Generate digits for the integral part. This can produce up to 10 digits. do { uint32_t digit = 0; @@ -700,9 +768,9 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, } --exp; auto remainder = (static_cast(integral) << -one.e) + fractional; - result = handler.on_digit(static_cast('0' + digit), - power_of_10_64(exp) << -one.e, remainder, error, - exp, true); + auto result = handler.on_digit(static_cast('0' + digit), + impl_data::power_of_10_64[exp] << -one.e, + remainder, error, true); if (result != digits::more) return result; } while (exp > 0); // Generate digits for the fractional part. @@ -712,69 +780,11 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, char digit = static_cast('0' + (fractional >> -one.e)); fractional &= one.f - 1; --exp; - result = handler.on_digit(digit, one.f, fractional, error, exp, false); + auto result = handler.on_digit(digit, one.f, fractional, error, false); if (result != digits::more) return result; } } -// The fixed precision digit handler. -struct fixed_handler { - char* buf; - int size; - int precision; - int exp10; - bool fixed; - - digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error, - int& exp) { - // Non-fixed formats require at least one digit and no precision adjustment. - if (!fixed) return digits::more; - // Adjust fixed precision by exponent because it is relative to decimal - // point. - precision += exp + exp10; - // Check if precision is satisfied just by leading zeros, e.g. - // format("{:.2f}", 0.001) gives "0.00" without generating any digits. - if (precision > 0) return digits::more; - if (precision < 0) return digits::done; - auto dir = get_round_direction(divisor, remainder, error); - if (dir == round_direction::unknown) return digits::error; - buf[size++] = dir == round_direction::up ? '1' : '0'; - return digits::done; - } - - digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, - uint64_t error, int, bool integral) { - FMT_ASSERT(remainder < divisor, ""); - buf[size++] = digit; - if (!integral && error >= remainder) return digits::error; - if (size < precision) return digits::more; - if (!integral) { - // Check if error * 2 < divisor with overflow prevention. - // The check is not needed for the integral part because error = 1 - // and divisor > (1 << 32) there. - if (error >= divisor || error >= divisor - error) return digits::error; - } else { - FMT_ASSERT(error == 1 && divisor > 2, ""); - } - auto dir = get_round_direction(divisor, remainder, error); - if (dir != round_direction::up) - return dir == round_direction::down ? digits::done : digits::error; - ++buf[size - 1]; - for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] > '9') { - buf[0] = '1'; - if (fixed) - buf[size++] = '0'; - else - ++exp10; - } - return digits::done; - } -}; - // A 128-bit integer type used internally, struct uint128_wrapper { uint128_wrapper() = default; @@ -898,8 +908,7 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT { inline int floor_log10_pow2(int e) FMT_NOEXCEPT { FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); const int shift = 22; - return (e * static_cast(data::log10_2_significand >> (64 - shift))) >> - shift; + return (e * static_cast(log10_2_significand >> (64 - shift))) >> shift; } // Various fast log computations. @@ -917,8 +926,7 @@ inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT { FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375; const int shift_amount = 22; - return (e * static_cast(data::log10_2_significand >> - (64 - shift_amount)) - + return (e * static_cast(log10_2_significand >> (64 - shift_amount)) - static_cast(log10_4_over_3_fractional_digits >> (64 - shift_amount))) >> shift_amount; @@ -1043,7 +1051,7 @@ template <> struct cache_accessor { static uint64_t get_cached_power(int k) FMT_NOEXCEPT { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); - constexpr const uint64_t pow10_significands[] = { + static constexpr const uint64_t pow10_significands[] = { 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, @@ -2211,24 +2219,21 @@ small_divisor_case_label: } } // namespace dragonbox -// Formats value using a variation of the Fixed-Precision Positive -// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: +// Formats a floating-point number using a variation of the Fixed-Precision +// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // https://fmt.dev/papers/p372-steele.pdf. -template -void fallback_format(Double d, int num_digits, bool binary32, buffer& buf, - int& exp10) { +FMT_CONSTEXPR20 inline void format_dragon(fp value, bool is_predecessor_closer, + int num_digits, buffer& buf, + int& exp10) { bigint numerator; // 2 * R in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2. // lower and upper are differences between value and corresponding boundaries. bigint lower; // (M^- in (FPP)^2). bigint upper_store; // upper's value if different from lower. bigint* upper = nullptr; // (M^+ in (FPP)^2). - fp value; // Shift numerator and denominator by an extra bit or two (if lower boundary // is closer) to make lower and upper integers. This eliminates multiplication // by 2 during later computations. - const bool is_predecessor_closer = - binary32 ? value.assign(static_cast(d)) : value.assign(d); int shift = is_predecessor_closer ? 2 : 1; uint64_t significand = value.f << shift; if (value.e >= 0) { @@ -2298,9 +2303,9 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer& buf, // Generate the given number of digits. exp10 -= num_digits - 1; if (num_digits == 0) { - buf.try_resize(1); denominator *= 10; - buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + buf.push_back(digit); return; } buf.try_resize(to_unsigned(num_digits)); @@ -2331,9 +2336,12 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer& buf, buf[num_digits - 1] = static_cast('0' + digit); } -template -int format_float(T value, int precision, float_specs specs, buffer& buf) { - static_assert(!std::is_same::value, ""); +template +FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision, + float_specs specs, + buffer& buf) { + // float is passed as double to reduce the number of instantiations. + static_assert(!std::is_same::value, ""); FMT_ASSERT(value >= 0, "value is negative"); const bool fixed = specs.format == float_format::fixed; @@ -2343,13 +2351,13 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { return 0; } buf.try_resize(to_unsigned(precision)); - std::uninitialized_fill_n(buf.data(), precision, '0'); + fill_n(buf.data(), precision, '0'); return -precision; } - if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); + if (specs.fallback) return snprintf_float(value, precision, specs, buf); - if (precision < 0) { + if (!is_constant_evaluated() && precision < 0) { // Use Dragonbox for the shortest format. if (specs.binary32) { auto dec = dragonbox::to_decimal(static_cast(value)); @@ -2361,26 +2369,37 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { return dec.exponent; } - // Use Grisu + Dragon4 for the given precision: - // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. int exp = 0; - const int min_exp = -60; // alpha in Grisu. - int cached_exp10 = 0; // K in Grisu. - 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; - // Limit precision to the maximum possible number of significant digits in an - // IEEE754 double because we don't need to generate zeros. - const int max_double_digits = 767; - if (precision > max_double_digits) precision = max_double_digits; - fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) { - exp += handler.size - cached_exp10 - 1; - fallback_format(value, handler.precision, specs.binary32, buf, exp); - } else { - exp += handler.exp10; - buf.try_resize(to_unsigned(handler.size)); + bool use_dragon = true; + if (is_fast_float()) { + // Use Grisu + Dragon4 for the given precision: + // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. + const int min_exp = -60; // alpha in Grisu. + int cached_exp10 = 0; // K in Grisu. + fp normalized = normalize(fp(value)); + const auto cached_pow = get_cached_power( + min_exp - (normalized.e + fp::num_significand_bits), cached_exp10); + normalized = normalized * cached_pow; + gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; + if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error && + !is_constant_evaluated()) { + exp += handler.exp10; + buf.try_resize(to_unsigned(handler.size)); + use_dragon = false; + } else { + exp += handler.size - cached_exp10 - 1; + precision = handler.precision; + } + } + if (use_dragon) { + auto f = fp(); + bool is_predecessor_closer = + specs.binary32 ? f.assign(static_cast(value)) : f.assign(value); + // Limit precision to the maximum possible number of significant digits in + // an IEEE754 double because we don't need to generate zeros. + const int max_double_digits = 767; + if (precision > max_double_digits) precision = max_double_digits; + format_dragon(f, is_predecessor_closer, precision, buf, exp); } if (!fixed && !specs.showpoint) { // Remove trailing zeros. @@ -2392,7 +2411,7 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { buf.try_resize(num_digits); } return exp; -} // namespace detail +} template int snprintf_float(T value, int precision, float_specs specs, @@ -2526,8 +2545,8 @@ template <> struct formatter { }; FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { - for_each_codepoint(s, [this](uint32_t cp, int error) { - if (error != 0) FMT_THROW(std::runtime_error("invalid utf8")); + for_each_codepoint(s, [this](uint32_t cp, string_view) { + if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8")); if (cp <= 0xFFFF) { buffer_.push_back(static_cast(cp)); } else { @@ -2535,6 +2554,7 @@ FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { buffer_.push_back(static_cast(0xD800 + (cp >> 10))); buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); } + return true; }); buffer_.push_back(0); } @@ -2550,15 +2570,17 @@ FMT_FUNC void format_system_error(detail::buffer& out, int error_code, format_error_code(out, error_code, message); } -FMT_FUNC void detail::error_handler::on_error(const char* message) { - FMT_THROW(format_error(message)); -} - FMT_FUNC void report_system_error(int error_code, const char* message) FMT_NOEXCEPT { report_error(format_system_error, error_code, message); } +// DEPRECATED! +// This function is defined here and not inline for ABI compatiblity. +FMT_FUNC void detail::error_handler::on_error(const char* message) { + throw_format_error(message); +} + FMT_FUNC std::string vformat(string_view fmt, format_args args) { // Don't optimize the "{}" case to keep the binary size small and because it // can be better optimized in fmt::format anyway. diff --git a/include/fmt/format.h b/include/fmt/format.h index 03ae1c96..ee69651c 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -41,14 +41,16 @@ #include // std::system_error #include // std::swap +#ifdef __cpp_lib_bit_cast +# include // std::bitcast +#endif + #include "core.h" -#ifdef __INTEL_COMPILER -# define FMT_ICC_VERSION __INTEL_COMPILER -#elif defined(__ICL) -# define FMT_ICC_VERSION __ICL +#if FMT_GCC_VERSION +# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) #else -# define FMT_ICC_VERSION 0 +# define FMT_GCC_VISIBILITY_HIDDEN #endif #ifdef __NVCC__ @@ -108,17 +110,11 @@ FMT_END_NAMESPACE # define FMT_CATCH(x) if (false) #endif -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 -# define FMT_DEPRECATED [[deprecated]] +#ifndef FMT_MAYBE_UNUSED +# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) +# define FMT_MAYBE_UNUSED [[maybe_unused]] # else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VER -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif +# define FMT_MAYBE_UNUSED # endif #endif @@ -149,18 +145,25 @@ FMT_END_NAMESPACE #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 -# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +// https://github.com/fmtlib/fmt/issues/519. +#if !FMT_MSC_VER +# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif #endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER -# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) -#endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz)) -# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) -#endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll)) -# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) + +// __builtin_ctz is broken in Intel Compiler Classic on Windows: +// https://github.com/fmtlib/fmt/issues/2510. +#ifndef __ICL +# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) +# endif #endif #if FMT_MSC_VER @@ -175,7 +178,6 @@ FMT_BEGIN_NAMESPACE namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # if !defined(__clang__) -# pragma managed(push, off) # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # if defined(_WIN64) @@ -237,40 +239,75 @@ inline auto ctzll(uint64_t x) -> int { return static_cast(r); } # define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) -# if !defined(__clang__) -# pragma managed(pop) -# endif } // namespace detail FMT_END_NAMESPACE #endif +#ifdef FMT_HEADER_ONLY +# define FMT_HEADER_ONLY_CONSTEXPR20 FMT_CONSTEXPR20 +#else +# define FMT_HEADER_ONLY_CONSTEXPR20 +#endif + FMT_BEGIN_NAMESPACE namespace detail { -#if __cplusplus >= 202002L || \ - (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) -# define FMT_CONSTEXPR20 constexpr -#else -# define FMT_CONSTEXPR20 -#endif +template class formatbuf : public Streambuf { + private: + using char_type = typename Streambuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename Streambuf::int_type; + using traits_type = typename Streambuf::traits_type; -// An equivalent of `*reinterpret_cast(&source)` that doesn't have -// undefined behavior (e.g. due to type aliasing). -// Example: uint64_t d = bit_cast(2.718); -template -inline auto bit_cast(const Source& source) -> Dest { - static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); - Dest dest; - std::memcpy(&dest, &source, sizeof(dest)); - return dest; + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +// Implementation of std::bit_cast for pre-C++20. +template +FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { + static_assert(sizeof(To) == sizeof(From), "size mismatch"); +#ifdef __cpp_lib_bit_cast + if (is_constant_evaluated()) return std::bit_cast(from); +#endif + auto to = To(); + std::memcpy(&to, &from, sizeof(to)); + return to; } inline auto is_big_endian() -> bool { - const auto u = 1u; +#ifdef _WIN32 + return false; +#elif defined(__BIG_ENDIAN__) + return true; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; +#else struct bytes { - char data[sizeof(u)]; + char data[sizeof(int)]; }; - return bit_cast(u).data[0] == 0; + return bit_cast(1).data[0] == 0; +#endif } // A fallback implementation of uintptr_t for systems that lack it. @@ -280,7 +317,7 @@ struct fallback_uintptr { fallback_uintptr() = default; explicit fallback_uintptr(const void* p) { *this = bit_cast(p); - if (is_big_endian()) { + if (const_check(is_big_endian())) { for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j) std::swap(value[i], value[j]); } @@ -339,12 +376,15 @@ inline auto get_data(Container& c) -> typename Container::value_type* { #if defined(_SECURE_SCL) && _SECURE_SCL // Make a checked iterator to avoid MSVC warnings. template using checked_ptr = stdext::checked_array_iterator; -template auto make_checked(T* p, size_t size) -> checked_ptr { +template +constexpr auto make_checked(T* p, size_t size) -> checked_ptr { return {p, size}; } #else template using checked_ptr = T*; -template inline auto make_checked(T* p, size_t) -> T* { return p; } +template constexpr auto make_checked(T* p, size_t) -> T* { + return p; +} #endif // Attempts to reserve space for n extra characters in the output range. @@ -480,27 +520,38 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) return next; } +constexpr uint32_t invalid_code_point = ~uint32_t(); + +// Invokes f(cp, sv) for every code point cp in s with sv being the string view +// corresponding to the code point. cp is invalid_code_point on error. template FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { - auto decode = [f](const char* p) { + auto decode = [f](const char* buf_ptr, const char* ptr) { auto cp = uint32_t(); auto error = 0; - p = utf8_decode(p, &cp, &error); - f(cp, error); - return p; + auto end = utf8_decode(buf_ptr, &cp, &error); + bool result = f(error ? invalid_code_point : cp, + string_view(ptr, to_unsigned(end - buf_ptr))); + return result ? end : nullptr; }; auto p = s.data(); const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. if (s.size() >= block_size) { - for (auto end = p + s.size() - block_size + 1; p < end;) p = decode(p); + for (auto end = p + s.size() - block_size + 1; p < end;) { + p = decode(p, p); + if (!p) return; + } } if (auto num_chars_left = s.data() + s.size() - p) { char buf[2 * block_size - 1] = {}; copy_str(p, p + num_chars_left, buf); - p = buf; + const char* buf_ptr = buf; do { - p = decode(p); - } while (p - buf < num_chars_left); + auto end = decode(buf_ptr, p); + if (!end) return; + p += end - buf_ptr; + buf_ptr = end; + } while (buf_ptr - buf < num_chars_left); } } @@ -515,14 +566,14 @@ FMT_CONSTEXPR inline size_t compute_width(string_view s) { // It is not a lambda for compatibility with C++14. struct count_code_points { size_t* count; - FMT_CONSTEXPR void operator()(uint32_t cp, int error) const { + FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { *count += detail::to_unsigned( 1 + - (error == 0 && cp >= 0x1100 && + (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 == 0x2329 || // LEFT-POINTING ANGLE BRACKET + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs @@ -536,6 +587,7 @@ FMT_CONSTEXPR inline size_t compute_width(string_view s) { (cp >= 0x1f300 && cp <= 0x1f64f) || // Supplemental Symbols and Pictographs: (cp >= 0x1f900 && cp <= 0x1f9ff)))); + return true; } }; for_each_codepoint(s, count_code_points{&num_code_points}); @@ -564,9 +616,10 @@ inline auto code_point_index(basic_string_view s, size_t n) return s.size(); } -template -using is_fast_float = bool_constant::is_iec559 && - sizeof(T) <= sizeof(double)>; +template ::value> +struct is_fast_float : bool_constant::is_iec559 && + sizeof(T) <= sizeof(double)> {}; +template struct is_fast_float : std::false_type {}; #ifndef FMT_USE_FULL_CACHE_DRAGONBOX # define FMT_USE_FULL_CACHE_DRAGONBOX 0 @@ -603,12 +656,12 @@ enum { inline_buffer_size = 500 }; A dynamically growing memory buffer for trivially copyable/constructible types with the first ``SIZE`` elements stored in the object itself. - You can use the ```memory_buffer`` type alias for ``char`` instead. + You can use the ``memory_buffer`` type alias for ``char`` instead. **Example**:: - fmt::memory_buffer out; - format_to(out, "The answer is {}.", 42); + auto out = fmt::memory_buffer(); + format_to(std::back_inserter(out), "The answer is {}.", 42); This will append the following output to the ``out`` object: @@ -629,34 +682,43 @@ class basic_memory_buffer final : public detail::buffer { Allocator alloc_; // Deallocate memory allocated by the buffer. - void deallocate() { + FMT_CONSTEXPR20 void deallocate() { T* data = this->data(); if (data != store_) alloc_.deallocate(data, this->capacity()); } protected: - void grow(size_t size) final FMT_OVERRIDE; + FMT_CONSTEXPR20 void grow(size_t size) override; public: using value_type = T; using const_reference = const T&; - explicit basic_memory_buffer(const Allocator& alloc = Allocator()) + FMT_CONSTEXPR20 explicit basic_memory_buffer( + const Allocator& alloc = Allocator()) : alloc_(alloc) { this->set(store_, SIZE); + if (detail::is_constant_evaluated()) { + detail::fill_n(store_, SIZE, T{}); + } } - ~basic_memory_buffer() { deallocate(); } + FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } private: // Move data from other to this buffer. - void move(basic_memory_buffer& other) { + FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { alloc_ = std::move(other.alloc_); T* data = other.data(); size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); - std::uninitialized_copy(other.store_, other.store_ + size, - detail::make_checked(store_, capacity)); + if (detail::is_constant_evaluated()) { + detail::copy_str(other.store_, other.store_ + size, + detail::make_checked(store_, capacity)); + } else { + std::uninitialized_copy(other.store_, other.store_ + size, + detail::make_checked(store_, capacity)); + } } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called @@ -673,7 +735,10 @@ class basic_memory_buffer final : public detail::buffer { of the other object to it. \endrst */ - basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); } + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) + FMT_NOEXCEPT { + move(other); + } /** \rst @@ -695,7 +760,7 @@ class basic_memory_buffer final : public detail::buffer { Resizes the buffer to contain *count* elements. If T is a POD type new elements may not be initialized. */ - void resize(size_t count) { this->try_resize(count); } + FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); } /** Increases the buffer capacity to *new_capacity*. */ void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } @@ -709,7 +774,8 @@ class basic_memory_buffer final : public detail::buffer { }; template -void basic_memory_buffer::grow(size_t size) { +FMT_CONSTEXPR20 void basic_memory_buffer::grow( + size_t size) { #ifdef FMT_FUZZ if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); #endif @@ -754,7 +820,7 @@ class FMT_API format_error : public std::runtime_error { format_error& operator=(const format_error&) = default; format_error(format_error&&) = default; format_error& operator=(format_error&&) = default; - ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT; + ~format_error() FMT_NOEXCEPT override FMT_MSC_DEFAULT; }; /** @@ -807,10 +873,6 @@ constexpr auto compile_string_to_view(detail::std_string_view s) FMT_BEGIN_DETAIL_NAMESPACE -inline void throw_format_error(const char* message) { - FMT_THROW(format_error(message)); -} - template struct is_integral : std::is_integral {}; template <> struct is_integral : std::true_type {}; template <> struct is_integral : std::true_type {}; @@ -853,46 +915,23 @@ using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; (factor)*1000000, (factor)*10000000, (factor)*100000000, \ (factor)*1000000000 -// Static data is placed in this class template for the header-only config. -template struct basic_data { - // log10(2) = 0x0.4d104d427de7fbcc... - static const uint64_t log10_2_significand = 0x4d104d427de7fbcc; +// Converts value in the range [0, 100) to a string. +constexpr const char* digits2(size_t value) { + // GCC generates slightly better code when value is pointer-size. + return &"0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"[value * 2]; +} - // GCC generates slightly better code for pairs than chars. - FMT_API static constexpr const char digits[][2] = { - {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, - {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, - {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, - {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, - {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, - {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, - {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, - {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, - {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, - {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, - {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, - {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, - {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, - {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, - {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, - {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, - {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; - - FMT_API static constexpr const char hex_digits[] = "0123456789abcdef"; - FMT_API static constexpr const char signs[] = {0, '-', '+', ' '}; - FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', - 0x1000000u | ' '}; - FMT_API static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0}; - FMT_API static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0}; -}; - -#ifdef FMT_SHARED -// Required for -flto, -fivisibility=hidden and -shared to work -extern template struct basic_data; +// Sign is a template parameter to workaround a bug in gcc 4.8. +template constexpr Char sign(Sign s) { +#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 + static_assert(std::is_same::value, ""); #endif - -// This is a struct rather than an alias to avoid shadowing warnings in gcc. -struct data : basic_data<> {}; + return static_cast("\0-+ "[s]); +} template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { int count = 1; @@ -914,23 +953,33 @@ FMT_CONSTEXPR inline auto count_digits(uint128_t n) -> int { } #endif +#ifdef FMT_BUILTIN_CLZLL +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +inline auto do_count_digits(uint64_t n) -> int { + // This has comparable performance to the version by Kendall Willets + // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) + // but uses smaller tables. + // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). + static constexpr uint8_t bsr2log10[] = { + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; + auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; + static constexpr const uint64_t zero_or_powers_of_10[] = { + 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return t - (n < zero_or_powers_of_10[t]); +} +#endif + // Returns the number of decimal digits in n. Leading zeros are not counted // except for n == 0 in which case count_digits returns 1. FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL if (!is_constant_evaluated()) { - // https://github.com/fmtlib/format-benchmark/blob/master/digits10 - // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). - constexpr uint16_t bsr2log10[] = { - 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, - 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, - 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, - 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; - auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; - constexpr const uint64_t zero_or_powers_of_10[] = { - 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; - return t - (n < zero_or_powers_of_10[t]); + return do_count_digits(n); } #endif return count_digits_fallback(n); @@ -943,21 +992,25 @@ FMT_CONSTEXPR auto count_digits(UInt n) -> int { if (num_bits() == 32) return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; #endif - int num_digits = 0; - do { - ++num_digits; - } while ((n >>= BITS) != 0); - return num_digits; + // Lambda avoids unreachable code warnings from NVHPC. + return [](UInt m) { + int num_digits = 0; + do { + ++num_digits; + } while ((m >>= BITS) != 0); + return num_digits; + }(n); } template <> auto count_digits<4>(detail::fallback_uintptr n) -> int; +#ifdef FMT_BUILTIN_CLZ // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. -FMT_INLINE uint64_t count_digits_inc(int n) { - // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. - // This increments the upper 32 bits (log10(T) - 1) when >= T is added. -#define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) +FMT_INLINE auto do_count_digits(uint32_t n) -> int { +// An optimization by Kendall Willets from https://bit.ly/3uOIQrB. +// This increments the upper 32 bits (log10(T) - 1) when >= T is added. +# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 @@ -971,15 +1024,16 @@ FMT_INLINE uint64_t count_digits_inc(int n) { FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M FMT_INC(1000000000), FMT_INC(1000000000) // 4B }; - return table[n]; + auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; + return static_cast((n + inc) >> 32); } +#endif // Optional version of count_digits for better performance on 32-bit platforms. FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { #ifdef FMT_BUILTIN_CLZ if (!is_constant_evaluated()) { - auto inc = count_digits_inc(FMT_BUILTIN_CLZ(n | 1) ^ 31); - return static_cast((n + inc) >> 32); + return do_count_digits(n); } #endif return count_digits_fallback(n); @@ -1023,18 +1077,22 @@ template <> inline auto decimal_point(locale_ref loc) -> wchar_t { // Compares two characters for equality. template auto equal2(const Char* lhs, const char* rhs) -> bool { - return lhs[0] == rhs[0] && lhs[1] == rhs[1]; + return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); } inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } // Copies two characters from src to dst. -template void copy2(Char* dst, const char* src) { +template +FMT_CONSTEXPR20 FMT_INLINE void copy2(Char* dst, const char* src) { + if (!is_constant_evaluated() && sizeof(Char) == sizeof(char)) { + memcpy(dst, src, 2); + return; + } *dst++ = static_cast(*src++); *dst = static_cast(*src); } -FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } template struct format_decimal_result { Iterator begin; @@ -1050,20 +1108,12 @@ FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) FMT_ASSERT(size >= count_digits(value), "invalid digit count"); out += size; Char* end = out; - if (is_constant_evaluated()) { - while (value >= 10) { - *--out = static_cast('0' + value % 10); - value /= 10; - } - *--out = static_cast('0' + value); - return {out, end}; - } while (value >= 100) { // Integer division is slow so do it for a group of two 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. out -= 2; - copy2(out, data::digits[value % 100]); + copy2(out, digits2(static_cast(value % 100))); value /= 100; } if (value < 10) { @@ -1071,7 +1121,7 @@ FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) return {out, end}; } out -= 2; - copy2(out, data::digits[value]); + copy2(out, digits2(static_cast(value))); return {out, end}; } @@ -1091,7 +1141,7 @@ FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, buffer += num_digits; Char* end = buffer; do { - const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits; + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; unsigned digit = (value & ((1 << BASE_BITS) - 1)); *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]); @@ -1114,7 +1164,7 @@ auto format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, auto p = buffer; for (int i = 0; i < char_digits; ++i) { unsigned digit = (value & ((1 << BASE_BITS) - 1)); - *--p = static_cast(data::hex_digits[digit]); + *--p = static_cast("0123456789abcdef"[digit]); value >>= BASE_BITS; } } @@ -1224,7 +1274,7 @@ constexpr auto exponent_mask() -> // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. template -auto write_exponent(int exp, It it) -> It { +FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { *it++ = static_cast('-'); @@ -1233,28 +1283,31 @@ auto write_exponent(int exp, It it) -> It { *it++ = static_cast('+'); } if (exp >= 100) { - const char* top = data::digits[exp / 100]; + const char* top = digits2(to_unsigned(exp / 100)); if (exp >= 1000) *it++ = static_cast(top[0]); *it++ = static_cast(top[1]); exp %= 100; } - const char* d = data::digits[exp]; + const char* d = digits2(to_unsigned(exp)); *it++ = static_cast(d[0]); *it++ = static_cast(d[1]); return it; } template -auto format_float(T value, int precision, float_specs specs, buffer& buf) - -> int; +FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision, + float_specs specs, + buffer& buf) -> int; // Formats a floating-point number with snprintf. template auto snprintf_float(T value, int precision, float_specs specs, buffer& buf) -> int; -template auto promote_float(T value) -> T { return value; } -inline auto promote_float(float value) -> double { +template constexpr auto promote_float(T value) -> T { + return value; +} +constexpr auto promote_float(float value) -> double { return static_cast(value); } @@ -1280,8 +1333,9 @@ FMT_CONSTEXPR auto write_padded(OutputIt out, static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; - auto* shifts = align == align::left ? data::left_padding_shifts - : data::right_padding_shifts; + // Shifts are encoded as string literals because static constexpr is not + // supported in constexpr functions. + auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; size_t left_padding = padding >> shifts[specs.align]; size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); @@ -1391,56 +1445,91 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, }); } +template class digit_grouping { + private: + thousands_sep_result sep_; + + struct next_state { + std::string::const_iterator group; + int pos; + }; + next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + + // Returns the next digit group separator position. + int next(next_state& state) const { + if (!sep_.thousands_sep) return max_value(); + if (state.group == sep_.grouping.end()) + return state.pos += sep_.grouping.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; + } + + public: + explicit digit_grouping(locale_ref loc, bool localized = true) { + if (localized) + sep_ = thousands_sep(loc); + else + sep_.thousands_sep = Char(); + } + explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} + + Char separator() const { return sep_.thousands_sep; } + + int count_separators(int num_digits) const { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and write the output to out. + template + Out apply(Out out, basic_string_view digits) const { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); + } + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + *out++ = separator(); + --sep_index; + } + *out++ = static_cast(digits[to_unsigned(i)]); + } + return out; + } +}; + +template +auto write_int_localized(OutputIt out, UInt value, unsigned prefix, + const basic_format_specs& specs, + const digit_grouping& grouping) -> OutputIt { + static_assert(std::is_same, UInt>::value, ""); + int num_digits = count_digits(value); + char digits[40]; + format_decimal(digits, value, num_digits); + unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + + grouping.count_separators(num_digits)); + return write_padded( + out, specs, size, size, [&](reserve_iterator it) { + if (prefix != 0) *it++ = static_cast(prefix); + return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); + }); +} + template auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, const basic_format_specs& specs, locale_ref loc) -> bool { - static_assert(std::is_same, UInt>::value, ""); - const auto sep_size = 1; - auto ts = thousands_sep(loc); - if (!ts.thousands_sep) return false; - int num_digits = count_digits(value); - int size = num_digits, n = num_digits; - const std::string& groups = ts.grouping; - std::string::const_iterator group = groups.cbegin(); - while (group != groups.cend() && n > *group && *group > 0 && - *group != max_value()) { - size += sep_size; - n -= *group; - ++group; - } - if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); - char digits[40]; - format_decimal(digits, value, num_digits); - basic_memory_buffer buffer; - if (prefix != 0) ++size; - const auto usize = to_unsigned(size); - buffer.resize(usize); - basic_string_view s(&ts.thousands_sep, sep_size); - // Index of a decimal digit with the least significant digit having index 0. - int digit_index = 0; - group = groups.cbegin(); - auto p = buffer.data() + size - 1; - for (int i = num_digits - 1; i > 0; --i) { - *p-- = static_cast(digits[i]); - if (*group <= 0 || ++digit_index % *group != 0 || - *group == max_value()) - continue; - if (group + 1 != groups.cend()) { - digit_index = 0; - ++group; - } - std::uninitialized_copy(s.data(), s.data() + s.size(), - make_checked(p, s.size())); - p -= s.size(); - } - *p-- = static_cast(*digits); - if (prefix != 0) *p = static_cast(prefix); - auto data = buffer.data(); - out = write_padded( - out, specs, usize, usize, [=](reserve_iterator it) { - return copy_str(data, data + size, it); - }); + auto grouping = digit_grouping(loc); + out = write_int_localized(out, value, prefix, specs, grouping); return true; } @@ -1463,7 +1552,9 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) prefix = 0x01000000 | '-'; abs_value = 0 - abs_value; } else { - prefix = data::prefixes[sign]; + constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + prefix = prefixes[sign]; } return {abs_value, prefix}; } @@ -1475,10 +1566,9 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; - auto utype = static_cast(specs.type); switch (specs.type) { - case 0: - case 'd': { + case presentation_type::none: + case presentation_type::dec: { if (specs.localized && write_int_localized(out, static_cast>(abs_value), prefix, specs, loc)) { @@ -1490,52 +1580,61 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, return format_decimal(it, abs_value, num_digits).end; }); } - case 'x': - case 'X': { - if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); - bool upper = specs.type != 'x'; + case presentation_type::hex_lower: + case presentation_type::hex_upper: { + bool upper = specs.type == presentation_type::hex_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); int num_digits = count_digits<4>(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<4, Char>(it, abs_value, num_digits, upper); }); } - case 'b': - case 'B': { - if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); + case presentation_type::bin_lower: + case presentation_type::bin_upper: { + bool upper = specs.type == presentation_type::bin_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); int num_digits = count_digits<1>(abs_value); return write_int(out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<1, Char>(it, abs_value, num_digits); }); } - case 'o': { + case presentation_type::oct: { int num_digits = count_digits<3>(abs_value); - 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. + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt && specs.precision <= num_digits && abs_value != 0) prefix_append(prefix, '0'); - } return write_int(out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_uint<3, Char>(it, abs_value, num_digits); }); } - case 'c': + case presentation_type::chr: return write_char(out, static_cast(abs_value), specs); default: - FMT_THROW(format_error("invalid type specifier")); + throw_format_error("invalid type specifier"); } return out; } +template +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( + OutputIt out, write_int_arg arg, const basic_format_specs& specs, + locale_ref loc) -> OutputIt { + return write_int(out, arg, specs, loc); +} template ::value && !std::is_same::value && std::is_same>::value)> -FMT_CONSTEXPR auto write(OutputIt out, T value, - const basic_format_specs& specs, locale_ref loc) - -> OutputIt { - return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); +FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, + const basic_format_specs& specs, + locale_ref loc) -> OutputIt { + return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, + loc); } // An inlined version of write used in format string compilation. template > s, const basic_format_specs& specs, locale_ref) -> OutputIt { + check_string_type_spec(specs.type); return write(out, s, specs); } template @@ -1579,8 +1679,9 @@ FMT_CONSTEXPR auto write(OutputIt out, const Char* s, } template -auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs specs, - const float_specs& fspecs) -> OutputIt { +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isinf, + basic_format_specs specs, + const float_specs& fspecs) -> OutputIt { auto str = isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); constexpr size_t str_size = 3; @@ -1591,7 +1692,7 @@ auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs specs, specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); if (is_zero_fill) specs.fill[0] = static_cast(' '); return write_padded(out, specs, size, [=](reserve_iterator it) { - if (sign) *it++ = static_cast(data::signs[sign]); + if (sign) *it++ = detail::sign(sign); return copy_str(str, str + str_size, it); }); } @@ -1603,7 +1704,7 @@ struct big_decimal_fp { int exponent; }; -inline auto get_significand_size(const big_decimal_fp& fp) -> int { +constexpr auto get_significand_size(const big_decimal_fp& fp) -> int { return fp.significand_size; } template @@ -1612,8 +1713,8 @@ inline auto get_significand_size(const dragonbox::decimal_fp& fp) -> int { } template -inline auto write_significand(OutputIt out, const char* significand, - int& significand_size) -> OutputIt { +constexpr auto write_significand(OutputIt out, const char* significand, + int significand_size) -> OutputIt { return copy_str(significand, significand + significand_size, out); } template @@ -1621,6 +1722,19 @@ inline auto write_significand(OutputIt out, UInt significand, int significand_size) -> OutputIt { return format_decimal(out, significand, significand_size).end; } +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int exponent, + const Grouping& grouping) -> OutputIt { + if (!grouping.separator()) { + out = write_significand(out, significand, significand_size); + return detail::fill_n(out, exponent, static_cast('0')); + } + auto buffer = memory_buffer(); + write_significand(appender(buffer), significand, significand_size); + detail::fill_n(appender(buffer), exponent, '0'); + return grouping.apply(out, string_view(buffer.data(), buffer.size())); +} template ::value)> @@ -1628,14 +1742,20 @@ inline auto write_significand(Char* out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> Char* { if (!decimal_point) return format_decimal(out, significand, significand_size).end; - auto end = format_decimal(out + 1, significand, significand_size).end; - if (integral_size == 1) { - out[0] = out[1]; - } else { - std::uninitialized_copy_n(out + 1, integral_size, - make_checked(out, to_unsigned(integral_size))); + out += significand_size + 1; + Char* end = out; + int floating_size = significand_size - integral_size; + for (int i = floating_size / 2; i > 0; --i) { + out -= 2; + copy2(out, digits2(significand % 100)); + significand /= 100; } - out[integral_size] = decimal_point; + if (floating_size % 2 != 0) { + *--out = static_cast('0' + significand % 10); + significand /= 10; + } + *--out = decimal_point; + format_decimal(out - integral_size, significand, integral_size); return end; } @@ -1652,9 +1772,9 @@ inline auto write_significand(OutputIt out, UInt significand, } template -inline auto write_significand(OutputIt out, const char* significand, - int significand_size, int integral_size, - Char decimal_point) -> OutputIt { +FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { out = detail::copy_str_noinline(significand, significand + integral_size, out); if (!decimal_point) return out; @@ -1663,17 +1783,40 @@ inline auto write_significand(OutputIt out, const char* significand, significand + significand_size, out); } -template -auto write_float(OutputIt out, const DecimalFP& fp, - const basic_format_specs& specs, float_specs fspecs, - Char decimal_point) -> OutputIt { +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int integral_size, + Char decimal_point, + const Grouping& grouping) -> OutputIt { + if (!grouping.separator()) { + return write_significand(out, significand, significand_size, integral_size, + decimal_point); + } + auto buffer = basic_memory_buffer(); + write_significand(buffer_appender(buffer), significand, + significand_size, integral_size, decimal_point); + grouping.apply( + out, basic_string_view(buffer.data(), to_unsigned(integral_size))); + return detail::copy_str_noinline(buffer.data() + integral_size, + buffer.end(), out); +} + +template > +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, + const basic_format_specs& specs, + float_specs fspecs, locale_ref loc) + -> OutputIt { auto significand = fp.significand; int significand_size = get_significand_size(fp); - static const Char zero = static_cast('0'); + constexpr Char zero = static_cast('0'); auto sign = fspecs.sign; size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); using iterator = reserve_iterator; + Char decimal_point = + fspecs.locale ? detail::decimal_point(loc) : static_cast('.'); + int output_exp = fp.exponent + significand_size - 1; auto use_exp_format = [=]() { if (fspecs.format == float_format::exp) return true; @@ -1700,7 +1843,7 @@ auto write_float(OutputIt out, const DecimalFP& fp, size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); char exp_char = fspecs.upper ? 'E' : 'e'; auto write = [=](iterator it) { - if (sign) *it++ = static_cast(data::signs[sign]); + if (sign) *it++ = detail::sign(sign); // Insert a decimal point after the first digit and add an exponent. it = write_significand(it, significand, significand_size, 1, decimal_point); @@ -1725,10 +1868,12 @@ auto write_float(OutputIt out, const DecimalFP& fp, if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; } + auto grouping = Grouping(loc, fspecs.locale); + size += to_unsigned(grouping.count_separators(significand_size)); return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = static_cast(data::signs[sign]); - it = write_significand(it, significand, significand_size); - it = detail::fill_n(it, fp.exponent, zero); + if (sign) *it++ = detail::sign(sign); + it = write_significand(it, significand, significand_size, + fp.exponent, grouping); if (!fspecs.showpoint) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; @@ -1737,10 +1882,12 @@ auto write_float(OutputIt out, const DecimalFP& fp, // 1234e-2 -> 12.34[0+] int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); + auto grouping = Grouping(loc, fspecs.locale); + size += to_unsigned(grouping.count_separators(significand_size)); return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = static_cast(data::signs[sign]); + if (sign) *it++ = detail::sign(sign); it = write_significand(it, significand, significand_size, exp, - decimal_point); + decimal_point, grouping); return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } @@ -1753,7 +1900,7 @@ auto write_float(OutputIt out, const DecimalFP& fp, bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = static_cast(data::signs[sign]); + if (sign) *it++ = detail::sign(sign); *it++ = zero; if (!pointy) return it; *it++ = decimal_point; @@ -1762,26 +1909,97 @@ auto write_float(OutputIt out, const DecimalFP& fp, }); } +template class fallback_digit_grouping { + public: + constexpr fallback_digit_grouping(locale_ref, bool) {} + + constexpr Char separator() const { return Char(); } + + constexpr int count_separators(int) const { return 0; } + + template + constexpr Out apply(Out out, basic_string_view) const { + return out; + } +}; + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& fp, + const basic_format_specs& specs, + float_specs fspecs, locale_ref loc) + -> OutputIt { + if (is_constant_evaluated()) { + return do_write_float>(out, fp, specs, fspecs, + loc); + } else { + return do_write_float(out, fp, specs, fspecs, loc); + } +} + +template ::value)> +FMT_CONSTEXPR20 bool isinf(T value) { + if (is_constant_evaluated()) { +#if defined(__cpp_if_constexpr) + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + constexpr auto significand_bits = + dragonbox::float_info::significand_bits; + return (bits & exponent_mask()) && + !(bits & ((uint64_t(1) << significand_bits) - 1)); + } +#endif + } + return std::isinf(value); +} + +template ::value)> +FMT_CONSTEXPR20 bool isfinite(T value) { + if (is_constant_evaluated()) { +#if defined(__cpp_if_constexpr) + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits & exponent_mask()) != exponent_mask(); + } +#endif + } + return std::isfinite(value); +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { + if (is_constant_evaluated()) { +#ifdef __cpp_if_constexpr + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits & (uint64_t(1) << (num_bits() - 1))) != 0; + } +#endif + } + return std::signbit(value); +} + template ::value)> -auto write(OutputIt out, T value, basic_format_specs specs, - locale_ref loc = {}) -> OutputIt { +FMT_CONSTEXPR20 auto write(OutputIt out, T value, + basic_format_specs specs, locale_ref loc = {}) + -> OutputIt { if (const_check(!is_supported_floating_point(value))) return out; float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; - if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. + if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. fspecs.sign = sign::minus; value = -value; } else if (fspecs.sign == sign::minus) { fspecs.sign = sign::none; } - if (!std::isfinite(value)) - return write_nonfinite(out, std::isinf(value), specs, fspecs); + if (!detail::isfinite(value)) + return write_nonfinite(out, detail::isinf(value), specs, fspecs); if (specs.align == align::numeric && fspecs.sign) { auto it = reserve(out, 1); - *it++ = static_cast(data::signs[fspecs.sign]); + *it++ = detail::sign(fspecs.sign); out = base_iterator(out, it); fspecs.sign = sign::none; if (specs.width != 0) --specs.width; @@ -1789,31 +2007,35 @@ auto write(OutputIt out, T value, basic_format_specs specs, memory_buffer buffer; if (fspecs.format == float_format::hex) { - if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); + if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); snprintf_float(promote_float(value), specs.precision, fspecs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } - int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; + int precision = specs.precision >= 0 || specs.type == presentation_type::none + ? specs.precision + : 6; if (fspecs.format == float_format::exp) { if (precision == max_value()) - FMT_THROW(format_error("number is too big")); + throw_format_error("number is too big"); else ++precision; } if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = is_fast_float(); + if (!is_fast_float()) fspecs.fallback = true; int exp = format_float(promote_float(value), precision, fspecs, buffer); fspecs.precision = precision; - Char point = - fspecs.locale ? decimal_point(loc) : static_cast('.'); auto fp = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; - return write_float(out, fp, specs, fspecs, point); + return write_float(out, fp, specs, fspecs, loc); } template ::value)> -auto write(OutputIt out, T value) -> OutputIt { +FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { + if (is_constant_evaluated()) { + return write(out, value, basic_format_specs()); + } + if (const_check(!is_supported_floating_point(value))) return out; using floaty = conditional_t::value, double, T>; @@ -1821,19 +2043,18 @@ auto write(OutputIt out, T value) -> OutputIt { auto bits = bit_cast(value); auto fspecs = float_specs(); - auto sign_bit = bits & (uint(1) << (num_bits() - 1)); - if (sign_bit != 0) { + if (detail::signbit(value)) { fspecs.sign = sign::minus; value = -value; } - static const auto specs = basic_format_specs(); + constexpr auto specs = basic_format_specs(); uint mask = exponent_mask(); if ((bits & mask) == mask) return write_nonfinite(out, std::isinf(value), specs, fspecs); auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, fspecs, static_cast('.')); + return write_float(out, dec, specs, fspecs, {}); } template OutputIt { return base_iterator(out, it); } -// FMT_ENABLE_IF() condition separated to workaround MSVC bug +// FMT_ENABLE_IF() condition separated to workaround an MSVC bug. template < typename Char, typename OutputIt, typename T, bool check = @@ -1904,7 +2125,8 @@ template & specs = {}, locale_ref = {}) -> OutputIt { - return specs.type && specs.type != 's' + return specs.type != presentation_type::none && + specs.type != presentation_type::string ? write(out, value ? 1 : 0, specs, {}) : write_bytes(out, value ? "true" : "false", specs); } @@ -1920,10 +2142,9 @@ template FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) -> OutputIt { if (!value) { - FMT_THROW(format_error("string pointer is null")); + throw_format_error("string pointer is null"); } else { - auto length = std::char_traits::length(value); - out = write(out, basic_string_view(value, length)); + out = write(out, basic_string_view(value)); } return out; } @@ -1937,18 +2158,28 @@ auto write(OutputIt out, const T* value, return write_ptr(out, to_uintptr(value), &specs); } -template -FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> - typename std::enable_if< - mapped_type_constant>::value == - type::custom_type, - OutputIt>::type { - using context_type = basic_format_context; +// A write overload that handles implicit conversions. +template > +FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t< + std::is_class::value && !is_string::value && + !std::is_same::value && + !std::is_same().map(value))>::value, + OutputIt> { + return write(out, arg_mapper().map(value)); +} + +template > +FMT_CONSTEXPR auto write(OutputIt out, const T& value) + -> enable_if_t::value == type::custom_type, + OutputIt> { using formatter_type = - conditional_t::value, - typename context_type::template formatter_type, + conditional_t::value, + typename Context::template formatter_type, fallback_formatter>; - context_type ctx(out, {}, {}); + auto ctx = Context(out, {}, {}); return formatter_type().format(value, ctx); } @@ -2365,6 +2596,7 @@ FMT_FORMAT_AS(unsigned long, unsigned long long); FMT_FORMAT_AS(Char*, const Char*); FMT_FORMAT_AS(std::basic_string, basic_string_view); FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(detail::byte, unsigned char); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); template @@ -2490,6 +2722,52 @@ template <> struct formatter { } }; +// group_digits_view is not derived from view because it copies the argument. +template struct group_digits_view { T value; }; + +/** + \rst + Returns a view that formats an integer value using ',' as a locale-independent + thousands separator. + + **Example**:: + + fmt::print("{}", fmt::group_digits(12345)); + // Output: "12,345" + \endrst + */ +template auto group_digits(T value) -> group_digits_view { + return {value}; +} + +template struct formatter> : formatter { + private: + detail::dynamic_format_specs specs_; + + public: + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + using handler_type = detail::dynamic_specs_handler; + detail::specs_checker handler(handler_type(specs_, ctx), + detail::type::int_type); + auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); + detail::check_string_type_spec(specs_.type, ctx.error_handler()); + return it; + } + + template + auto format(group_digits_view t, FormatContext& ctx) + -> decltype(ctx.out()) { + detail::handle_dynamic_spec(specs_.width, + specs_.width_ref, ctx); + detail::handle_dynamic_spec( + specs_.precision, specs_.precision_ref, ctx); + return detail::write_int_localized( + ctx.out(), static_cast>(t.value), 0, specs_, + detail::digit_grouping({"\3", ','})); + } +}; + template struct join_view : detail::view { It begin; @@ -2506,7 +2784,12 @@ using arg_join FMT_DEPRECATED_ALIAS = join_view; template struct formatter, Char> { private: - using value_type = typename std::iterator_traits::value_type; + using value_type = +#ifdef __cpp_lib_ranges + std::iter_value_t; +#else + typename std::iterator_traits::value_type; +#endif using context = buffer_context; using mapper = detail::arg_mapper; @@ -2540,11 +2823,13 @@ struct formatter, Char> { auto it = value.begin; auto out = ctx.out(); if (it != value.end) { - out = value_formatter_.format(map(*it++), ctx); + out = value_formatter_.format(map(*it), ctx); + ++it; while (it != value.end) { out = detail::copy_str(value.sep.begin(), value.sep.end(), out); ctx.advance_to(out); - out = value_formatter_.format(map(*it++), ctx); + out = value_formatter_.format(map(*it), ctx); + ++it; } } return out; @@ -2552,8 +2837,8 @@ struct formatter, Char> { }; /** - Returns an object that formats the iterator range `[begin, end)` with - elements separated by `sep`. + Returns a view that formats the iterator range `[begin, end)` with elements + separated by `sep`. */ template auto join(It begin, Sentinel end, string_view sep) -> join_view { @@ -2562,7 +2847,7 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view { /** \rst - Returns an object that formats `range` with elements separated by `sep`. + Returns a view that formats `range` with elements separated by `sep`. **Example**:: @@ -2601,7 +2886,7 @@ inline auto to_string(const T& value) -> std::string { } template ::value)> -inline auto to_string(T value) -> std::string { +FMT_NODISCARD inline auto to_string(T value) -> std::string { // The buffer should be large enough to store the number including the sign // or "false" for bool. constexpr int max_size = detail::digits10() + 2; @@ -2611,7 +2896,7 @@ inline auto to_string(T value) -> std::string { } template -auto to_string(const basic_memory_buffer& buf) +FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) -> std::basic_string { auto size = buf.size(); detail::assume(size < std::basic_string().max_size()); @@ -2621,9 +2906,10 @@ auto to_string(const basic_memory_buffer& buf) FMT_BEGIN_DETAIL_NAMESPACE template -void vformat_to(buffer& buf, basic_string_view fmt, - basic_format_args>> args, - locale_ref loc) { +void vformat_to( + buffer& buf, basic_string_view fmt, + basic_format_args)> args, + locale_ref loc) { // workaround for msvc bug regarding name-lookup in module // link names into function scope using detail::arg_formatter; @@ -2703,10 +2989,6 @@ void vformat_to(buffer& buf, basic_string_view fmt, } #ifndef FMT_HEADER_ONLY -extern template void vformat_to(detail::buffer&, string_view, - basic_format_args, - detail::locale_ref); - extern template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; extern template FMT_API auto thousands_sep_impl(locale_ref) @@ -2730,6 +3012,8 @@ extern template auto snprintf_float(long double value, #endif // FMT_HEADER_ONLY FMT_END_DETAIL_NAMESPACE + +#if FMT_USE_USER_DEFINED_LITERALS inline namespace literals { /** \rst @@ -2741,34 +3025,27 @@ inline namespace literals { fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); \endrst */ -#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS template constexpr auto operator""_a() -> detail::udl_arg, sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> { return {}; } -#else +# else constexpr auto operator"" _a(const char* s, size_t) -> detail::udl_arg { return {s}; } -#endif +# endif -/** - \rst - User-defined literal equivalent of :func:`fmt::format`. - - **Example**:: - - using namespace fmt::literals; - std::string message = "The answer is {}"_format(42); - \endrst - */ -constexpr auto operator"" _format(const char* s, size_t n) +// DEPRECATED! +// User-defined literal equivalent of fmt::format. +FMT_DEPRECATED constexpr auto operator"" _format(const char* s, size_t n) -> detail::udl_formatter { return {{s, n}}; } } // namespace literals +#endif // FMT_USE_USER_DEFINED_LITERALS template ::value)> inline auto vformat(const Locale& loc, string_view fmt, format_args args) diff --git a/include/fmt/os.h b/include/fmt/os.h index c447831e..b64f8bbf 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -21,17 +21,20 @@ #include "format.h" +#ifndef FMT_USE_FCNTL // UWP doesn't provide _pipe. -#if FMT_HAS_INCLUDE("winapifamily.h") -# include -#endif -#if (FMT_HAS_INCLUDE() || defined(__APPLE__) || \ - defined(__linux__)) && \ - (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) -# include // for O_RDONLY -# define FMT_USE_FCNTL 1 -#else -# define FMT_USE_FCNTL 0 +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if (FMT_HAS_INCLUDE() || defined(__APPLE__) || \ + defined(__linux__)) && \ + (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# include // for O_RDONLY +# define FMT_USE_FCNTL 1 +# else +# define FMT_USE_FCNTL 0 +# endif #endif #ifndef FMT_POSIX @@ -390,23 +393,26 @@ struct ostream_params { : ostream_params(params...) { this->buffer_size = bs.value; } + +// Intel has a bug that results in failure to deduce a constructor +// for empty parameter packs. +# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000 + ostream_params(int new_oflag) : oflag(new_oflag) {} + ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {} +# endif }; FMT_END_DETAIL_NAMESPACE -static constexpr detail::buffer_size buffer_size; +// Added {} below to work around default constructor error known to +// occur in Xcode versions 7.2.1 and 8.2.1. +constexpr detail::buffer_size buffer_size{}; /** A fast output stream which is not thread-safe. */ class FMT_API ostream final : private detail::buffer { private: file file_; - void flush() { - if (size() == 0) return; - file_.write(data(), size()); - clear(); - } - void grow(size_t) override; ostream(cstring_view path, const detail::ostream_params& params) @@ -426,6 +432,12 @@ class FMT_API ostream final : private detail::buffer { delete[] data(); } + void flush() { + if (size() == 0) return; + file_.write(data(), size()); + clear(); + } + template friend ostream output_file(cstring_view path, T... params); @@ -500,7 +512,7 @@ class locale { // Converts string to floating-point number and advances str past the end // of the parsed input. - double strtod(const char*& str) const { + FMT_DEPRECATED double strtod(const char*& str) const { char* end = nullptr; double result = strtod_l(str, &end, locale_); str = end; diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index d66248a6..3d716ece 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -14,73 +14,20 @@ FMT_BEGIN_NAMESPACE -template class basic_printf_parse_context; template class basic_printf_context; namespace detail { -template class formatbuf : public std::basic_streambuf { - private: - using int_type = typename std::basic_streambuf::int_type; - using traits_type = typename std::basic_streambuf::traits_type; - - buffer& buffer_; - - public: - formatbuf(buffer& buf) : buffer_(buf) {} - - protected: - // The put-area is actually always empty. This makes the implementation - // simpler and has the advantage that the streambuf and the buffer are always - // in sync and sputc never writes into uninitialized memory. The obvious - // disadvantage is that each call to sputc always results in a (virtual) call - // to overflow. There is no disadvantage here for sputn since this always - // results in a call to xsputn. - - int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast(ch)); - return ch; - } - - std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE { - buffer_.append(s, s + count); - return count; - } -}; - -struct converter { - template ::value)> converter(T); -}; - -template struct test_stream : std::basic_ostream { - private: - void_t<> operator<<(converter); -}; - -// Hide insertion operators for built-in types. -template -void_t<> operator<<(std::basic_ostream&, Char); -template -void_t<> operator<<(std::basic_ostream&, char); -template -void_t<> operator<<(std::basic_ostream&, char); -template -void_t<> operator<<(std::basic_ostream&, signed char); -template -void_t<> operator<<(std::basic_ostream&, unsigned char); - -// Checks if T has a user-defined operator<< (e.g. not a member of -// std::ostream). -template class is_streamable { +// Checks if T has a user-defined operator<<. +template +class is_streamable { private: template - static bool_constant&>() - << std::declval()), - void_t<>>::value> - test(int); + static auto test(int) + -> bool_constant&>() + << std::declval()) != 0>; - template static std::false_type test(...); + template static auto test(...) -> std::false_type; using result = decltype(test(0)); @@ -90,7 +37,21 @@ template class is_streamable { static const bool value = result::value; }; +// Formatting of built-in types and arrays is intentionally disabled because +// it's handled by standard (non-ostream) formatters. +template +struct is_streamable< + T, Char, + enable_if_t< + std::is_arithmetic::value || std::is_array::value || + std::is_pointer::value || std::is_same::value || + std::is_same>::value || + std::is_same>::value || + (std::is_convertible::value && !std::is_enum::value)>> + : std::false_type {}; + // Write the content of buf to os. +// It is a separate function rather than a part of vprint to simplify testing. template void write_buffer(std::basic_ostream& os, buffer& buf) { const Char* buf_data = buf.data(); @@ -108,8 +69,8 @@ void write_buffer(std::basic_ostream& os, buffer& buf) { template void format_value(buffer& buf, const T& value, locale_ref loc = locale_ref()) { - formatbuf format_buf(buf); - std::basic_ostream output(&format_buf); + auto&& format_buf = formatbuf>(buf); + auto&& output = std::basic_ostream(&format_buf); #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) if (loc) output.imbue(loc.get()); #endif @@ -122,29 +83,22 @@ void format_value(buffer& buf, const T& value, template struct fallback_formatter::value>> : private formatter, Char> { - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { - return formatter, Char>::parse(ctx); - } - template >::value)> - auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } + using formatter, Char>::parse; template auto format(const T& value, basic_format_context& ctx) -> OutputIt { - basic_memory_buffer buffer; + auto buffer = basic_memory_buffer(); format_value(buffer, value, ctx.locale()); - basic_string_view str(buffer.data(), buffer.size()); - return formatter, Char>::format(str, ctx); + return formatter, Char>::format( + {buffer.data(), buffer.size()}, ctx); } + + // DEPRECATED! template auto format(const T& value, basic_printf_context& ctx) -> OutputIt { - basic_memory_buffer buffer; + auto buffer = basic_memory_buffer(); format_value(buffer, value, ctx.locale()); return std::copy(buffer.begin(), buffer.end(), ctx.out()); } @@ -155,7 +109,7 @@ FMT_MODULE_EXPORT template void vprint(std::basic_ostream& os, basic_string_view format_str, basic_format_args>> args) { - basic_memory_buffer buffer; + auto buffer = basic_memory_buffer(); detail::vformat_to(buffer, format_str, args); detail::write_buffer(os, buffer); } diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 3a3cd152..19d550f6 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -233,7 +233,7 @@ class printf_arg_formatter : public arg_formatter { OutputIt write_null_pointer(bool is_string = false) { auto s = this->specs; - s.type = 0; + s.type = presentation_type::none; return write_bytes(this->out, is_string ? "(null)" : "(nil)", s); } @@ -249,8 +249,10 @@ class printf_arg_formatter : public arg_formatter { // std::is_same instead. if (std::is_same::value) { format_specs fmt_specs = this->specs; - if (fmt_specs.type && fmt_specs.type != 'c') + if (fmt_specs.type != presentation_type::none && + fmt_specs.type != presentation_type::chr) { return (*this)(static_cast(value)); + } fmt_specs.sign = sign::none; fmt_specs.alt = false; fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. @@ -271,13 +273,13 @@ class printf_arg_formatter : public arg_formatter { /** Formats a null-terminated C string. */ OutputIt operator()(const char* value) { if (value) return base::operator()(value); - return write_null_pointer(this->specs.type != 'p'); + return write_null_pointer(this->specs.type != presentation_type::pointer); } /** Formats a null-terminated wide C string. */ OutputIt operator()(const wchar_t* value) { if (value) return base::operator()(value); - return write_null_pointer(this->specs.type != 'p'); + return write_null_pointer(this->specs.type != presentation_type::pointer); } OutputIt operator()(basic_string_view value) { @@ -490,13 +492,13 @@ void vprintf(buffer& buf, basic_string_view format, // Parse type. if (it == end) FMT_THROW(format_error("invalid format string")); - specs.type = static_cast(*it++); + char type = static_cast(*it++); if (arg.is_integral()) { // Normalize type. - switch (specs.type) { + switch (type) { case 'i': case 'u': - specs.type = 'd'; + type = 'd'; break; case 'c': visit_format_arg( @@ -505,6 +507,9 @@ void vprintf(buffer& buf, basic_string_view format, break; } } + specs.type = parse_presentation_type(type); + if (specs.type == presentation_type::none) + parse_ctx.on_error("invalid type specifier"); start = it; diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 52961389..eb9fb8a9 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -13,37 +13,13 @@ #define FMT_RANGES_H_ #include +#include #include #include "format.h" FMT_BEGIN_NAMESPACE -template struct formatting_range { -#ifdef FMT_DEPRECATED_BRACED_RANGES - Char prefix = '{'; - Char postfix = '}'; -#else - Char prefix = '['; - Char postfix = ']'; -#endif - - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } -}; - -template struct formatting_tuple { - Char prefix = '('; - Char postfix = ')'; - - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } -}; - namespace detail { template @@ -71,7 +47,7 @@ OutputIterator copy(wchar_t ch, OutputIterator out) { return out; } -/// Return true value if T has std::string interface, like std::string_view. +// Returns true if T has a std::string-like interface, like std::string_view. template class is_std_string_like { template static auto check(U* p) @@ -80,12 +56,40 @@ template class is_std_string_like { public: static FMT_CONSTEXPR_DECL const bool value = - is_string::value || !std::is_void(nullptr))>::value; + is_string::value || + std::is_convertible>::value || + !std::is_void(nullptr))>::value; }; template struct is_std_string_like> : std::true_type {}; +template class is_map { + template static auto check(U*) -> typename U::mapped_type; + template static void check(...); + + public: +#ifdef FMT_FORMAT_MAP_AS_LIST + static FMT_CONSTEXPR_DECL const bool value = false; +#else + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value; +#endif +}; + +template class is_set { + template static auto check(U*) -> typename U::key_type; + template static void check(...); + + public: +#ifdef FMT_FORMAT_SET_AS_LIST + static FMT_CONSTEXPR_DECL const bool value = false; +#else + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value && !is_map::value; +#endif +}; + template struct conditional_helper {}; template struct is_range_ : std::false_type {}; @@ -143,16 +147,16 @@ struct has_mutable_begin_end : std::false_type {}; template struct has_const_begin_end< - T, void_t&>())), - decltype(detail::range_begin( - std::declval&>()))>> + T, + void_t< + decltype(detail::range_begin(std::declval&>())), + decltype(detail::range_end(std::declval&>()))>> : std::true_type {}; template struct has_mutable_begin_end< T, void_t())), - decltype(detail::range_begin(std::declval())), + decltype(detail::range_end(std::declval())), enable_if_t::value>>> : std::true_type {}; @@ -160,34 +164,10 @@ template struct is_range_ : std::integral_constant::value || has_mutable_begin_end::value)> {}; - -template struct range_to_view; -template -struct range_to_view::value>> { - struct view_t { - const T* m_range_ptr; - - auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr)); - auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr)); - }; - static auto view(const T& range) -> view_t { return {&range}; } -}; - -template -struct range_to_view::value && - has_mutable_begin_end::value>> { - struct view_t { - T m_range_copy; - - auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy)); - auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy)); - }; - static auto view(const T& range) -> view_t { return {range}; } -}; # undef FMT_DECLTYPE_RETURN #endif -/// tuple_size and tuple_element check. +// tuple_size and tuple_element check. template class is_tuple_like_ { template static auto check(U* p) -> decltype(std::tuple_size::value, int()); @@ -251,16 +231,295 @@ template OutputIt write_delimiter(OutputIt out) { return out; } -template < - typename Char, typename OutputIt, typename Arg, - FMT_ENABLE_IF(is_std_string_like::type>::value)> -OutputIt write_range_entry(OutputIt out, const Arg& v) { +struct singleton { + unsigned char upper; + unsigned char lower_count; +}; + +inline auto is_printable(uint16_t x, const singleton* singletons, + size_t singletons_size, + const unsigned char* singleton_lowers, + const unsigned char* normal, size_t normal_size) + -> bool { + auto upper = x >> 8; + auto lower_start = 0; + for (size_t i = 0; i < singletons_size; ++i) { + auto s = singletons[i]; + auto lower_end = lower_start + s.lower_count; + if (upper < s.upper) break; + if (upper == s.upper) { + for (auto j = lower_start; j < lower_end; ++j) { + if (singleton_lowers[j] == (x & 0xff)) return false; + } + } + lower_start = lower_end; + } + + auto xsigned = static_cast(x); + auto current = true; + for (size_t i = 0; i < normal_size; ++i) { + auto v = static_cast(normal[i]); + auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; + xsigned -= len; + if (xsigned < 0) break; + current = !current; + } + return current; +} + +// Returns true iff the code point cp is printable. +// This code is generated by support/printable.py. +inline auto is_printable(uint32_t cp) -> bool { + static constexpr singleton singletons0[] = { + {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, + {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, + {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, + {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, + {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, + {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, + {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, + }; + static constexpr unsigned char singletons0_lower[] = { + 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, + 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, + 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, + 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, + 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, + 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, + 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, + 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, + 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, + 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, + 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, + 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, + 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, + 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, + 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, + 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, + 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, + 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, + 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, + 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, + 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, + 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, + 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, + 0xfe, 0xff, + }; + static constexpr singleton singletons1[] = { + {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, + {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, + {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, + {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, + {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, + {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, + {0xfa, 2}, {0xfb, 1}, + }; + static constexpr unsigned char singletons1_lower[] = { + 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, + 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, + 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, + 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, + 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, + 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, + 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, + 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, + 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, + 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, + 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, + 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, + 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, + 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, + }; + static constexpr unsigned char normal0[] = { + 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, + 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, + 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, + 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, + 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, + 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, + 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, + 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, + 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, + 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, + 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, + 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, + 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, + 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, + 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, + 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, + 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, + 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, + 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, + 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, + 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, + 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, + 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, + 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, + 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, + 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, + }; + static constexpr unsigned char normal1[] = { + 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, + 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, + 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, + 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, + 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, + 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, + 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, + 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, + 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, + 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, + 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, + 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, + 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, + 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, + 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, + 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, + 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, + 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, + 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, + 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, + 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, + 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, + 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, + 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, + 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, + 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, + 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, + 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, + 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, + 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, + 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, + 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, + 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, + 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, + 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, + }; + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + } + if (0x2a6de <= cp && cp < 0x2a700) return false; + if (0x2b735 <= cp && cp < 0x2b740) return false; + if (0x2b81e <= cp && cp < 0x2b820) return false; + if (0x2cea2 <= cp && cp < 0x2ceb0) return false; + if (0x2ebe1 <= cp && cp < 0x2f800) return false; + if (0x2fa1e <= cp && cp < 0x30000) return false; + if (0x3134b <= cp && cp < 0xe0100) return false; + if (0xe01f0 <= cp && cp < 0x110000) return false; + return cp < 0x110000; +} + +inline auto needs_escape(uint32_t cp) -> bool { + return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' || + !is_printable(cp); +} + +template struct find_escape_result { + const Char* begin; + const Char* end; + uint32_t cp; +}; + +template +auto find_escape(const Char* begin, const Char* end) + -> find_escape_result { + for (; begin != end; ++begin) { + auto cp = static_cast::type>(*begin); + if (sizeof(Char) == 1 && cp >= 0x80) continue; + if (needs_escape(cp)) return {begin, begin + 1, cp}; + } + return {begin, nullptr, 0}; +} + +inline auto find_escape(const char* begin, const char* end) + -> find_escape_result { + if (!is_utf8()) return find_escape(begin, end); + auto result = find_escape_result{end, nullptr, 0}; + for_each_codepoint(string_view(begin, to_unsigned(end - begin)), + [&](uint32_t cp, string_view sv) { + if (needs_escape(cp)) { + result = {sv.begin(), sv.end(), cp}; + return false; + } + return true; + }); + return result; +} + +template +auto write_range_entry(OutputIt out, basic_string_view str) -> OutputIt { *out++ = '"'; - out = write(out, v); + auto begin = str.begin(), end = str.end(); + do { + auto escape = find_escape(begin, end); + out = copy_str(begin, escape.begin, out); + begin = escape.end; + if (!begin) break; + auto c = static_cast(escape.cp); + switch (escape.cp) { + case '\n': + *out++ = '\\'; + c = 'n'; + break; + case '\r': + *out++ = '\\'; + c = 'r'; + break; + case '\t': + *out++ = '\\'; + c = 't'; + break; + case '"': + FMT_FALLTHROUGH; + case '\\': + *out++ = '\\'; + break; + default: + if (is_utf8()) { + if (escape.cp < 0x100) { + out = format_to(out, "\\x{:02x}", escape.cp); + continue; + } + if (escape.cp < 0x10000) { + out = format_to(out, "\\u{:04x}", escape.cp); + continue; + } + if (escape.cp < 0x110000) { + out = format_to(out, "\\U{:08x}", escape.cp); + continue; + } + } + for (Char escape_char : basic_string_view( + escape.begin, to_unsigned(escape.end - escape.begin))) { + out = format_to( + out, "\\x{:02x}", + static_cast::type>(escape_char)); + } + continue; + } + *out++ = c; + } while (begin != end); *out++ = '"'; return out; } +template >::value)> +inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt { + auto sv = std_string_view(str); + return write_range_entry(out, basic_string_view(sv)); +} + template ::value)> OutputIt write_range_entry(OutputIt out, const Arg v) { @@ -288,43 +547,37 @@ template struct is_tuple_like { template struct formatter::value>> { private: - // C++11 generic lambda for format() + // C++11 generic lambda for format(). template struct format_each { template void operator()(const T& v) { if (i > 0) out = detail::write_delimiter(out); out = detail::write_range_entry(out, v); ++i; } - formatting_tuple& formatting; - size_t& i; - typename std::add_lvalue_reference< - decltype(std::declval().out())>::type out; + int i; + typename FormatContext::iterator& out; }; public: - formatting_tuple formatting; - template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return formatting.parse(ctx); + return ctx.begin(); } template auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { auto out = ctx.out(); - size_t i = 0; - - detail::copy(formatting.prefix, out); - detail::for_each(values, format_each{formatting, i, out}); - detail::copy(formatting.postfix, out); - - return ctx.out(); + *out++ = '('; + detail::for_each(values, format_each{0, out}); + *out++ = ')'; + return out; } }; template struct is_range { static FMT_CONSTEXPR_DECL const bool value = detail::is_range_::value && !detail::is_std_string_like::value && + !detail::is_map::value && !std::is_convertible>::value && !std::is_constructible, T>::value; }; @@ -334,32 +587,80 @@ struct formatter< T, Char, enable_if_t< fmt::is_range::value -// Workaround a bug in MSVC 2017 and earlier. -#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 - && (has_formatter, format_context>::value || +// Workaround a bug in MSVC 2019 and earlier. +#if !FMT_MSC_VER + && (is_formattable, Char>::value || detail::has_fallback_formatter, Char>::value) #endif >> { - formatting_range formatting; - template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return formatting.parse(ctx); + return ctx.begin(); } - template - typename FormatContext::iterator format(const T& values, FormatContext& ctx) { - auto out = detail::copy(formatting.prefix, ctx.out()); - size_t i = 0; - auto view = detail::range_to_view::view(values); - auto it = view.begin(); - auto end = view.end(); + template < + typename FormatContext, typename U, + FMT_ENABLE_IF( + std::is_same::value, + const T, T>>::value)> + auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) { +#ifdef FMT_DEPRECATED_BRACED_RANGES + Char prefix = '{'; + Char postfix = '}'; +#else + Char prefix = detail::is_set::value ? '{' : '['; + Char postfix = detail::is_set::value ? '}' : ']'; +#endif + auto out = ctx.out(); + *out++ = prefix; + int i = 0; + auto it = std::begin(range); + auto end = std::end(range); for (; it != end; ++it) { if (i > 0) out = detail::write_delimiter(out); out = detail::write_range_entry(out, *it); ++i; } - return detail::copy(formatting.postfix, out); + *out++ = postfix; + return out; + } +}; + +template +struct formatter< + T, Char, + enable_if_t< + detail::is_map::value +// Workaround a bug in MSVC 2019 and earlier. +#if !FMT_MSC_VER + && (is_formattable, Char>::value || + detail::has_fallback_formatter, Char>::value) +#endif + >> { + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template < + typename FormatContext, typename U, + FMT_ENABLE_IF( + std::is_same::value, + const T, T>>::value)> + auto format(U& map, FormatContext& ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + *out++ = '{'; + int i = 0; + for (const auto& item : map) { + if (i > 0) out = detail::write_delimiter(out); + out = detail::write_range_entry(out, item.first); + *out++ = ':'; + *out++ = ' '; + out = detail::write_range_entry(out, item.second); + ++i; + } + *out++ = '}'; + return out; } }; @@ -374,45 +675,70 @@ template struct tuple_join_view : detail::view { template using tuple_arg_join = tuple_join_view; +// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers +// support in tuple_join. It is disabled by default because of issues with +// the dynamic width and precision. +#ifndef FMT_TUPLE_JOIN_SPECIFIERS +# define FMT_TUPLE_JOIN_SPECIFIERS 0 +#endif + template struct formatter, Char> { template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + return do_parse(ctx, std::integral_constant()); } template - auto format(const tuple_join_view& value, FormatContext& ctx) -> - typename FormatContext::iterator { - return format(value, ctx, detail::make_index_sequence{}); + auto format(const tuple_join_view& value, + FormatContext& ctx) const -> typename FormatContext::iterator { + return do_format(value, ctx, + std::integral_constant()); } private: - template - auto format(const tuple_join_view& value, FormatContext& ctx, - detail::index_sequence) -> - typename FormatContext::iterator { - return format_args(value, ctx, std::get(value.tuple)...); + std::tuple::type, Char>...> formatters_; + + template + FMT_CONSTEXPR auto do_parse(ParseContext& ctx, + std::integral_constant) + -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + FMT_CONSTEXPR auto do_parse(ParseContext& ctx, + std::integral_constant) + -> decltype(ctx.begin()) { + auto end = ctx.begin(); +#if FMT_TUPLE_JOIN_SPECIFIERS + end = std::get(formatters_).parse(ctx); + if (N > 1) { + auto end1 = do_parse(ctx, std::integral_constant()); + if (end != end1) + FMT_THROW(format_error("incompatible format specs for tuple elements")); + } +#endif + return end; } template - auto format_args(const tuple_join_view&, FormatContext& ctx) -> + auto do_format(const tuple_join_view&, FormatContext& ctx, + std::integral_constant) const -> typename FormatContext::iterator { - // 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 - auto format_args(const tuple_join_view& value, FormatContext& ctx, - const Arg& arg, const Args&... args) -> + template + auto do_format(const tuple_join_view& value, FormatContext& ctx, + std::integral_constant) const -> typename FormatContext::iterator { - using base = formatter::type, Char>; - auto out = base().format(arg, ctx); - if (sizeof...(Args) > 0) { + auto out = std::get(formatters_) + .format(std::get(value.tuple), ctx); + if (N > 1) { out = std::copy(value.sep.begin(), value.sep.end(), out); ctx.advance_to(out); - return format_args(value, ctx, args...); + return do_format(value, ctx, std::integral_constant()); } return out; } diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 74c69045..55825077 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -5,8 +5,8 @@ // // For the license information refer to format.h. -#ifndef FMT_WCHAR_H_ -#define FMT_WCHAR_H_ +#ifndef FMT_XCHAR_H_ +#define FMT_XCHAR_H_ #include #include @@ -142,7 +142,7 @@ FMT_DEPRECATED auto format_to(basic_memory_buffer& buf, const S& format_str, Args&&... args) -> typename buffer_context::iterator { const auto& vargs = fmt::make_args_checked(format_str, args...); - detail::vformat_to(buf, to_string_view(format_str), vargs); + detail::vformat_to(buf, to_string_view(format_str), vargs, {}); return detail::buffer_appender(buf); } @@ -217,11 +217,11 @@ inline void vprint(wstring_view fmt, wformat_args args) { template void print(std::FILE* f, wformat_string fmt, T&&... args) { - return vprint(f, wstring_view(fmt), make_wformat_args(args...)); + return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); } template void print(wformat_string fmt, T&&... args) { - return vprint(wstring_view(fmt), make_wformat_args(args...)); + return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); } /** @@ -233,4 +233,4 @@ template inline auto to_wstring(const T& value) -> std::wstring { FMT_MODULE_EXPORT_END FMT_END_NAMESPACE -#endif // FMT_WCHAR_H_ +#endif // FMT_XCHAR_H_ diff --git a/src/fmt.cc b/src/fmt.cc index d0d6e7fb..80e77e26 100644 --- a/src/fmt.cc +++ b/src/fmt.cc @@ -79,7 +79,6 @@ export module fmt; #define FMT_END_DETAIL_NAMESPACE \ } \ export { - // all library-provided declarations and definitions // must be in the module purview to be exported #include "fmt/args.h" diff --git a/src/format.cc b/src/format.cc index 187dcb73..ecb8cc79 100644 --- a/src/format.cc +++ b/src/format.cc @@ -10,6 +10,52 @@ FMT_BEGIN_NAMESPACE namespace detail { +// DEPRECATED! +template struct basic_data { + FMT_API static constexpr const char digits[100][2] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, + {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, + {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, + {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, + {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, + {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, + {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, + {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, + {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, + {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, + {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, + {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, + {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, + {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; + FMT_API static constexpr const char hex_digits[] = "0123456789abcdef"; + FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '}; + FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1, + 0}; + FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1, + 0}; + FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; +}; + +#ifdef FMT_SHARED +// Required for -flto, -fivisibility=hidden and -shared to work +extern template struct basic_data; +#endif + +#if __cplusplus < 201703L +// DEPRECATED! These are here only for ABI compatiblity. +template constexpr const char basic_data::digits[][2]; +template constexpr const char basic_data::hex_digits[]; +template constexpr const char basic_data::signs[]; +template constexpr const char basic_data::left_padding_shifts[]; +template +constexpr const char basic_data::right_padding_shifts[]; +template constexpr const unsigned basic_data::prefixes[]; +#endif + template int format_float(char* buf, std::size_t size, const char* format, int precision, T value) { @@ -47,6 +93,9 @@ template FMT_API char detail::decimal_point_impl(locale_ref); template FMT_API void detail::buffer::append(const char*, const char*); +// DEPRECATED! +// There is no correspondent extern template in format.h because of +// incompatibility between clang and gcc (#2377). template FMT_API void detail::vformat_to( detail::buffer&, string_view, basic_format_args, detail::locale_ref); diff --git a/src/os.cc b/src/os.cc index 934629d7..04b4dc50 100644 --- a/src/os.cc +++ b/src/os.cc @@ -26,19 +26,17 @@ # endif # include -# define O_CREAT _O_CREAT -# define O_TRUNC _O_TRUNC - # ifndef S_IRUSR # define S_IRUSR _S_IREAD # endif - # ifndef S_IWUSR # define S_IWUSR _S_IWRITE # endif - -# ifdef __MINGW32__ -# define _SH_DENYNO 0x40 +# ifndef S_IRGRP +# define S_IRGRP 0 +# endif +# ifndef S_IROTH +# define S_IROTH 0 # endif # endif // _WIN32 #endif // FMT_USE_FCNTL @@ -213,7 +211,10 @@ int buffered_file::fileno() const { #if FMT_USE_FCNTL file::file(cstring_view path, int oflag) { - int mode = S_IRUSR | S_IWUSR; +# ifdef _WIN32 + using mode_t = int; +# endif + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; # if defined(_WIN32) && !defined(__MINGW32__) fd_ = -1; FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); diff --git a/support/bazel/.bazelrc b/support/bazel/.bazelrc new file mode 100644 index 00000000..583cbbd2 --- /dev/null +++ b/support/bazel/.bazelrc @@ -0,0 +1 @@ +build --symlink_prefix=/ # Out of source build diff --git a/support/bazel/.bazelversion b/support/bazel/.bazelversion new file mode 100644 index 00000000..fae6e3d0 --- /dev/null +++ b/support/bazel/.bazelversion @@ -0,0 +1 @@ +4.2.1 diff --git a/support/bazel/BUILD.bazel b/support/bazel/BUILD.bazel new file mode 100644 index 00000000..3380bbca --- /dev/null +++ b/support/bazel/BUILD.bazel @@ -0,0 +1,29 @@ +cc_library( + name = "fmt", + srcs = [ + #"src/fmt.cc", # No C++ module support + "src/format.cc", + "src/os.cc", + ], + hdrs = [ + "include/fmt/args.h", + "include/fmt/chrono.h", + "include/fmt/color.h", + "include/fmt/compile.h", + "include/fmt/core.h", + "include/fmt/format.h", + "include/fmt/format-inl.h", + "include/fmt/locale.h", + "include/fmt/os.h", + "include/fmt/ostream.h", + "include/fmt/printf.h", + "include/fmt/ranges.h", + "include/fmt/xchar.h", + ], + includes = [ + "include", + "src", + ], + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) diff --git a/support/bazel/README.md b/support/bazel/README.md new file mode 100644 index 00000000..44af620c --- /dev/null +++ b/support/bazel/README.md @@ -0,0 +1,73 @@ +# Bazel support + +To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}). + +## Using {fmt} as a dependency + +The following minimal example shows how to use {fmt} as a dependency within a Bazel project. + +The following file structure is assumed: + +``` +example +├── BUILD.bazel +├── main.cpp +└── WORKSPACE.bazel +``` + +*main.cpp*: + +```c++ +#include "fmt/core.h" + +int main() { + fmt::print("The answer is {}\n", 42); +} +``` + +The expected output of this example is `The answer is 42`. + +*WORKSPACE.bazel*: + +```python +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +git_repository( + name = "fmt", + branch = "master", + remote = "https://github.com/fmtlib/fmt", + patch_cmds = [ + "mv support/bazel/.bazelrc .bazelrc", + "mv support/bazel/.bazelversion .bazelversion", + "mv support/bazel/BUILD.bazel BUILD.bazel", + "mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel", + ], + # Windows-related patch commands are only needed in the case MSYS2 is not installed. + # More details about the installation process of MSYS2 on Windows systems can be found here: + # https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes + # Even if MSYS2 is installed the Windows related patch commands can still be used. + patch_cmds_win = [ + "Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc", + "Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion", + "Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel", + "Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel", + ], +) +``` + +In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace. + +*BUILD.bazel*: + +```python +cc_binary( + name = "Demo", + srcs = ["main.cpp"], + deps = ["@fmt"], +) +``` + +The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}. + +To execute the binary you can run `bazel run //:Demo`. + diff --git a/support/bazel/WORKSPACE.bazel b/support/bazel/WORKSPACE.bazel new file mode 100644 index 00000000..5be77811 --- /dev/null +++ b/support/bazel/WORKSPACE.bazel @@ -0,0 +1 @@ +workspace(name = "fmt") diff --git a/support/manage.py b/support/manage.py index 9f270669..bece4566 100755 --- a/support/manage.py +++ b/support/manage.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Manage site and releases. @@ -163,6 +163,13 @@ def update_site(env): if version.startswith('7.'): b.data = b.data.replace(', std::size_t', ', size_t') b.data = b.data.replace('join(It, It', 'join(It, Sentinel') + if version.startswith('7.1.'): + b.data = b.data.replace(', std::size_t', ', size_t') + b.data = b.data.replace('join(It, It', 'join(It, Sentinel') + b.data = b.data.replace( + 'fmt::format_to(OutputIt, const S&, Args&&...)', + 'fmt::format_to(OutputIt, const S&, Args&&...) -> ' + + 'typename std::enable_if::type') b.data = b.data.replace('aa long', 'a long') b.data = b.data.replace('serveral', 'several') if version.startswith('6.2.'): @@ -233,7 +240,7 @@ def release(args): # Update the version in the changelog. title_len = 0 for line in fileinput.input(changelog_path, inplace=True): - if line.decode('utf-8').startswith(version + ' - TBD'): + if line.startswith(version + ' - TBD'): line = version + ' - ' + datetime.date.today().isoformat() title_len = len(line) line += '\n' @@ -263,9 +270,9 @@ def release(args): # Create a release on GitHub. fmt_repo.push('origin', 'release') - params = {'access_token': os.getenv('FMT_TOKEN')} + auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', - params=params, + headers=auth_headers, data=json.dumps({'tag_name': version, 'target_commitish': 'release', 'body': changes, 'draft': True})) @@ -276,8 +283,8 @@ def release(args): package = 'fmt-{}.zip'.format(version) r = requests.post( '{}/{}/assets?name={}'.format(uploads_url, id, package), - headers={'Content-Type': 'application/zip'}, - params=params, data=open('build/fmt/' + package, 'rb')) + headers={'Content-Type': 'application/zip'} | auth_headers, + data=open('build/fmt/' + package, 'rb')) if r.status_code != 201: raise Exception('Failed to upload an asset ' + str(r)) diff --git a/support/printable.py b/support/printable.py new file mode 100755 index 00000000..7d23d3bb --- /dev/null +++ b/support/printable.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 + +# This script is based on +# https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py +# distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT. + +# This script uses the following Unicode tables: +# - UnicodeData.txt + + +from collections import namedtuple +import csv +import os +import subprocess + +NUM_CODEPOINTS=0x110000 + +def to_ranges(iter): + current = None + for i in iter: + if current is None or i != current[1] or i in (0x10000, 0x20000): + if current is not None: + yield tuple(current) + current = [i, i + 1] + else: + current[1] += 1 + if current is not None: + yield tuple(current) + +def get_escaped(codepoints): + for c in codepoints: + if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '): + yield c.value + +def get_file(f): + try: + return open(os.path.basename(f)) + except FileNotFoundError: + subprocess.run(["curl", "-O", f], check=True) + return open(os.path.basename(f)) + +Codepoint = namedtuple('Codepoint', 'value class_') + +def get_codepoints(f): + r = csv.reader(f, delimiter=";") + prev_codepoint = 0 + class_first = None + for row in r: + codepoint = int(row[0], 16) + name = row[1] + class_ = row[2] + + if class_first is not None: + if not name.endswith("Last>"): + raise ValueError("Missing Last after First") + + for c in range(prev_codepoint + 1, codepoint): + yield Codepoint(c, class_first) + + class_first = None + if name.endswith("First>"): + class_first = class_ + + yield Codepoint(codepoint, class_) + prev_codepoint = codepoint + + if class_first is not None: + raise ValueError("Missing Last after First") + + for c in range(prev_codepoint + 1, NUM_CODEPOINTS): + yield Codepoint(c, None) + +def compress_singletons(singletons): + uppers = [] # (upper, # items in lowers) + lowers = [] + + for i in singletons: + upper = i >> 8 + lower = i & 0xff + if len(uppers) == 0 or uppers[-1][0] != upper: + uppers.append((upper, 1)) + else: + upper, count = uppers[-1] + uppers[-1] = upper, count + 1 + lowers.append(lower) + + return uppers, lowers + +def compress_normal(normal): + # lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f + # lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff + compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)] + + prev_start = 0 + for start, count in normal: + truelen = start - prev_start + falselen = count + prev_start = start + count + + assert truelen < 0x8000 and falselen < 0x8000 + entry = [] + if truelen > 0x7f: + entry.append(0x80 | (truelen >> 8)) + entry.append(truelen & 0xff) + else: + entry.append(truelen & 0x7f) + if falselen > 0x7f: + entry.append(0x80 | (falselen >> 8)) + entry.append(falselen & 0xff) + else: + entry.append(falselen & 0x7f) + + compressed.append(entry) + + return compressed + +def print_singletons(uppers, lowers, uppersname, lowersname): + print(" static constexpr singleton {}[] = {{".format(uppersname)) + for u, c in uppers: + print(" {{{:#04x}, {}}},".format(u, c)) + print(" };") + print(" static constexpr unsigned char {}[] = {{".format(lowersname)) + for i in range(0, len(lowers), 8): + print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8]))) + print(" };") + +def print_normal(normal, normalname): + print(" static constexpr unsigned char {}[] = {{".format(normalname)) + for v in normal: + print(" {}".format(" ".join("{:#04x},".format(i) for i in v))) + print(" };") + +def main(): + file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt") + + codepoints = get_codepoints(file) + + CUTOFF=0x10000 + singletons0 = [] + singletons1 = [] + normal0 = [] + normal1 = [] + extra = [] + + for a, b in to_ranges(get_escaped(codepoints)): + if a > 2 * CUTOFF: + extra.append((a, b - a)) + elif a == b - 1: + if a & CUTOFF: + singletons1.append(a & ~CUTOFF) + else: + singletons0.append(a) + elif a == b - 2: + if a & CUTOFF: + singletons1.append(a & ~CUTOFF) + singletons1.append((a + 1) & ~CUTOFF) + else: + singletons0.append(a) + singletons0.append(a + 1) + else: + if a >= 2 * CUTOFF: + extra.append((a, b - a)) + elif a & CUTOFF: + normal1.append((a & ~CUTOFF, b - a)) + else: + normal0.append((a, b - a)) + + singletons0u, singletons0l = compress_singletons(singletons0) + singletons1u, singletons1l = compress_singletons(singletons1) + normal0 = compress_normal(normal0) + normal1 = compress_normal(normal1) + + print("""\ +inline auto is_printable(uint32_t cp) -> bool {\ +""") + print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower') + print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower') + print_normal(normal0, 'normal0') + print_normal(normal1, 'normal1') + print("""\ + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + }\ +""") + for a, b in extra: + print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b)) + print("""\ + return cp < 0x{:x}; +}}\ +""".format(NUM_CODEPOINTS)) + +if __name__ == '__main__': + main() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dcf3f4ac..7ed8d5f7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,24 +8,21 @@ target_link_libraries(test-main gtest) include(CheckCXXCompilerFlag) -# Workaround GTest bug https://github.com/google/googletest/issues/705. -check_cxx_compiler_flag( - -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) -if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) - target_compile_options(test-main PUBLIC -fno-delete-null-pointer-checks) -endif () - -# Use less strict pedantic flags for the tests because GMock doesn't compile -# cleanly with -pedantic and -std=c++98. -if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) - #set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros) -endif () - function(add_fmt_executable name) add_executable(${name} ${ARGN}) if (MINGW) target_link_libraries(${name} -static-libgcc -static-libstdc++) endif () + # (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443 + # Bogus -Wstringop-overflow warning + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100395 + # [10 Regression] spurious -Wstringop-overflow writing to a trailing array plus offset + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353 + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND + NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) + target_link_libraries(${name} -Wno-stringop-overflow) + endif () endfunction() # Adds a test. @@ -41,7 +38,7 @@ function(add_fmt_test name) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) endif () elseif (ADD_FMT_TEST_MODULE) - set(libs test-main test-module) + set(libs gtest test-module) set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module) else () set(libs test-main fmt) @@ -74,8 +71,13 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS)) endif () add_fmt_test(ostream-test) add_fmt_test(compile-test) +add_fmt_test(compile-fp-test HEADER_ONLY) +if (MSVC) + # Without this option, MSVC returns 199711L for the __cplusplus macro. + target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus) +endif() add_fmt_test(printf-test) -add_fmt_test(ranges-test) +add_fmt_test(ranges-test ranges-odr-test.cc) add_fmt_test(scan-test) add_fmt_test(unicode-test HEADER_ONLY) if (MSVC) @@ -97,10 +99,12 @@ if (FMT_CAN_MODULE) $) enable_module(test-module) - add_fmt_test(module-test MODULE) + add_fmt_test(module-test MODULE test-main.cc) if (MSVC) - target_compile_options(test-module PRIVATE /utf-8) - target_compile_options(module-test PRIVATE /utf-8) + target_compile_options(test-module PRIVATE /utf-8 /Zc:__cplusplus + /Zc:externConstexpr /Zc:inline) + target_compile_options(module-test PRIVATE /utf-8 /Zc:__cplusplus + /Zc:externConstexpr /Zc:inline) endif () endif () @@ -133,20 +137,14 @@ endif () message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}") -if (FMT_PEDANTIC AND CXX_STANDARD LESS 20) - # MSVC fails to compile GMock when C++17 is enabled. - if (FMT_HAS_VARIANT AND NOT MSVC) - add_fmt_test(std-format-test) - set_property(TARGET std-format-test PROPERTY CXX_STANDARD 17) - endif () - +if (FMT_PEDANTIC) # Test that the library can be compiled with exceptions disabled. # -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822. if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG) endif () if (HAVE_FNO_EXCEPTIONS_FLAG) - add_library(noexception-test ../src/format.cc) + add_library(noexception-test ../src/format.cc noexception-test.cc) target_include_directories( noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include) target_compile_options(noexception-test PRIVATE -fno-exceptions) @@ -161,7 +159,11 @@ if (FMT_PEDANTIC AND CXX_STANDARD LESS 20) nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include) target_compile_definitions( nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1) +endif () +# These tests are disabled on Windows because they take too long. +if (FMT_PEDANTIC AND NOT WIN32) + # Test if incorrect API usages produce compilation error. add_test(compile-error-test ${CMAKE_CTEST_COMMAND} --build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test" @@ -170,14 +172,11 @@ if (FMT_PEDANTIC AND CXX_STANDARD LESS 20) --build-makeprogram ${CMAKE_MAKE_PROGRAM} --build-options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" - "-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}" - "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}" + "-DFMT_DIR=${CMAKE_SOURCE_DIR}" "-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}") -endif () -# These tests are disabled on Windows because they take too long. -if (FMT_PEDANTIC AND NOT WIN32) # Test if the targets are found from the build directory. add_test(find-package-test ${CMAKE_CTEST_COMMAND} -C ${CMAKE_BUILD_TYPE} diff --git a/test/args-test.cc b/test/args-test.cc index a7421bc5..aa01fa4e 100644 --- a/test/args-test.cc +++ b/test/args-test.cc @@ -7,10 +7,12 @@ #include "fmt/args.h" +#include + #include "gtest/gtest.h" TEST(args_test, basic) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(42); store.push_back("abc1"); store.push_back(1.5f); @@ -19,7 +21,7 @@ TEST(args_test, basic) { TEST(args_test, strings_and_refs) { // Unfortunately the tests are compiled with old ABI so strings use COW. - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char str[] = "1234567890"; store.push_back(str); store.push_back(std::cref(str)); @@ -48,7 +50,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, custom_format) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto c = custom_type(); store.push_back(c); ++c.i; @@ -77,7 +79,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, to_string_and_formatter) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto s = to_stringable(); store.push_back(s); store.push_back(std::cref(s)); @@ -85,13 +87,13 @@ TEST(args_test, to_string_and_formatter) { } TEST(args_test, named_int) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("a1", 42)); EXPECT_EQ("42", fmt::vformat("{a1}", store)); } TEST(args_test, named_strings) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char str[] = "1234567890"; store.push_back(fmt::arg("a1", str)); store.push_back(fmt::arg("a2", std::cref(str))); @@ -100,7 +102,7 @@ TEST(args_test, named_strings) { } TEST(args_test, named_arg_by_ref) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char band[] = "Rolling Stones"; store.push_back(fmt::arg("band", std::cref(band))); band[9] = 'c'; // Changing band affects the output. @@ -108,7 +110,7 @@ TEST(args_test, named_arg_by_ref) { } TEST(args_test, named_custom_format) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto c = custom_type(); store.push_back(fmt::arg("c1", c)); ++c.i; @@ -121,7 +123,7 @@ TEST(args_test, named_custom_format) { } TEST(args_test, clear) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(42); auto result = fmt::vformat("{}", store); @@ -138,7 +140,7 @@ TEST(args_test, clear) { } TEST(args_test, reserve) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.reserve(2, 1); store.push_back(1.5f); store.push_back(fmt::arg("a1", 42)); @@ -163,7 +165,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, throw_on_copy) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(std::string("foo")); try { store.push_back(copy_throwable()); @@ -171,3 +173,14 @@ TEST(args_test, throw_on_copy) { } EXPECT_EQ(fmt::vformat("{}", store), "foo"); } + +TEST(args_test, move_constructor) { + using store_type = fmt::dynamic_format_arg_store; + auto store = std::unique_ptr(new store_type()); + store->push_back(42); + store->push_back(std::string("foo")); + store->push_back(fmt::arg("a1", "foo")); + auto moved_store = std::move(*store); + store.reset(); + EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo"); +} diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 113c0fda..42360e5f 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -7,6 +7,9 @@ #include "fmt/chrono.h" +#include +#include + #include "gtest-extra.h" // EXPECT_THROW_MSG #include "util.h" // get_locale @@ -38,6 +41,36 @@ auto make_second(int s) -> std::tm { return time; } +std::string system_strftime(const std::string& format, const std::tm* timeptr, + std::locale* locptr = nullptr) { + auto loc = locptr ? *locptr : std::locale::classic(); + auto& facet = std::use_facet>(loc); + std::ostringstream os; + os.imbue(loc); + facet.put(os, os, ' ', timeptr, format.c_str(), + format.c_str() + format.size()); +#ifdef _WIN32 + // Workaround a bug in older versions of Universal CRT. + auto str = os.str(); + if (str == "-0000") str = "+0000"; + return str; +#else + return os.str(); +#endif +} + +FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min, + int sec) { + auto tm = std::tm(); + tm.tm_sec = sec; + tm.tm_min = min; + tm.tm_hour = hour; + tm.tm_mday = mday; + tm.tm_mon = mon - 1; + tm.tm_year = year - 1900; + return tm; +} + TEST(chrono_test, format_tm) { auto tm = std::tm(); tm.tm_year = 116; @@ -48,14 +81,132 @@ TEST(chrono_test, format_tm) { tm.tm_sec = 33; EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), "The date is 2016-04-25 11:22:33."); + EXPECT_EQ(fmt::format("{:%Y}", tm), "2016"); + EXPECT_EQ(fmt::format("{:%C}", tm), "20"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); + EXPECT_EQ(fmt::format("{:%e}", tm), "25"); + EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/16"); + EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25"); + EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33"); + + // Short year + tm.tm_year = 999 - 1900; + tm.tm_mon = 0; // for %G + tm.tm_mday = 2; // for %G + tm.tm_wday = 3; // for %G + tm.tm_yday = 1; // for %G + EXPECT_EQ(fmt::format("{:%Y}", tm), "0999"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), "0999"); + EXPECT_EQ(fmt::format("{:%G}", tm), "0999"); + + tm.tm_year = 27 - 1900; + EXPECT_EQ(fmt::format("{:%Y}", tm), "0027"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), "0027"); + + // Overflow year + tm.tm_year = 2147483647; + EXPECT_EQ(fmt::format("{:%Y}", tm), "2147485547"); + + tm.tm_year = -2147483648; + EXPECT_EQ(fmt::format("{:%Y}", tm), "-2147481748"); + + // for week on the year + // https://www.cl.cam.ac.uk/~mgk25/iso-time.html + std::vector tm_list = { + make_tm(1975, 12, 29, 12, 14, 16), // W01 + make_tm(1977, 1, 2, 12, 14, 16), // W53 + make_tm(1999, 12, 27, 12, 14, 16), // W52 + make_tm(1999, 12, 31, 12, 14, 16), // W52 + make_tm(2000, 1, 1, 12, 14, 16), // W52 + make_tm(2000, 1, 2, 12, 14, 16), // W52 + make_tm(2000, 1, 3, 12, 14, 16) // W1 + }; + const std::string iso_week_spec = "%Y-%m-%d: %G %g %V"; + for (auto ctm : tm_list) { + // Calculate tm_yday, tm_wday, etc. + std::time_t t = std::mktime(&ctm); + tm = *std::localtime(&t); + + auto fmt_spec = fmt::format("{{:{}}}", iso_week_spec); + EXPECT_EQ(system_strftime(iso_week_spec, &tm), + fmt::format(fmt::runtime(fmt_spec), tm)); + } + + // Every day from 1970-01-01 + std::time_t time_now = std::time(nullptr); + for (std::time_t t = 6 * 3600; t < time_now; t += 86400) { + tm = *std::localtime(&t); + + auto fmt_spec = fmt::format("{{:{}}}", iso_week_spec); + EXPECT_EQ(system_strftime(iso_week_spec, &tm), + fmt::format(fmt::runtime(fmt_spec), tm)); + } } +// MSVC: +// minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(971) : Assertion failed: +// timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099 +#ifndef _WIN32 +TEST(chrono_test, format_tm_future) { + auto tm = std::tm(); + tm.tm_year = 10445; // 10000+ years + tm.tm_mon = 3; + tm.tm_mday = 25; + tm.tm_hour = 11; + tm.tm_min = 22; + tm.tm_sec = 33; + EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), + "The date is 12345-04-25 11:22:33."); + EXPECT_EQ(fmt::format("{:%Y}", tm), "12345"); + EXPECT_EQ(fmt::format("{:%C}", tm), "123"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); + EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/45"); + EXPECT_EQ(fmt::format("{:%F}", tm), "12345-04-25"); + EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33"); +} + +TEST(chrono_test, format_tm_past) { + auto tm = std::tm(); + tm.tm_year = -2001; + tm.tm_mon = 3; + tm.tm_mday = 25; + tm.tm_hour = 11; + tm.tm_min = 22; + tm.tm_sec = 33; + EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), + "The date is -101-04-25 11:22:33."); + EXPECT_EQ(fmt::format("{:%Y}", tm), "-101"); + + // macOS %C - "-1" + // Linux %C - "-2" + // fmt %C - "-1" + EXPECT_EQ(fmt::format("{:%C}", tm), "-1"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); + + // macOS %D - "04/25/01" (%y) + // Linux %D - "04/25/99" (%y) + // fmt %D - "04/25/01" (%y) + EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/01"); + + EXPECT_EQ(fmt::format("{:%F}", tm), "-101-04-25"); + EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33"); + + tm.tm_year = -1901; // -1 + EXPECT_EQ(fmt::format("{:%Y}", tm), "-001"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); + + tm.tm_year = -1911; // -11 + EXPECT_EQ(fmt::format("{:%Y}", tm), "-011"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); +} +#endif + TEST(chrono_test, grow_buffer) { auto s = std::string("{:"); for (int i = 0; i < 30; ++i) s += "%c"; s += "}\n"; auto t = std::time(nullptr); - fmt::format(fmt::runtime(s), *std::localtime(&t)); + (void)fmt::format(fmt::runtime(s), *std::localtime(&t)); } TEST(chrono_test, format_to_empty_container) { @@ -88,22 +239,45 @@ TEST(chrono_test, gmtime) { EXPECT_TRUE(equal(tm, fmt::gmtime(t))); } -template auto strftime(TimePoint tp) -> std::string { +template auto strftime_full(TimePoint tp) -> std::string { auto t = std::chrono::system_clock::to_time_t(tp); auto tm = *std::localtime(&t); - char output[256] = {}; - std::strftime(output, sizeof(output), "%Y-%m-%d %H:%M:%S", &tm); - return output; + return system_strftime("%Y-%m-%d %H:%M:%S", &tm); } TEST(chrono_test, time_point) { auto t1 = std::chrono::system_clock::now(); - EXPECT_EQ(strftime(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); - EXPECT_EQ(strftime(t1), fmt::format("{}", t1)); + EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); + EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1)); using time_point = std::chrono::time_point; auto t2 = time_point(std::chrono::seconds(42)); - EXPECT_EQ(strftime(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); + EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); + + std::vector spec_list = { + "%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", + "%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U", + "%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e", + "%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH", + "%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X", + "%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"}; + spec_list.push_back("%Y-%m-%d %H:%M:%S"); +#ifndef _WIN32 + // Disabled on Windows because these formats are not consistent among + // platforms. + spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"}); +#endif + + for (const auto& spec : spec_list) { + auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::localtime(&t); + + auto sys_output = system_strftime(spec, &tm); + + auto fmt_spec = fmt::format("{{:{}}}", spec); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1)); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); + } } #ifndef FMT_STATIC_THOUSANDS_SEPARATOR @@ -197,41 +371,41 @@ TEST(chrono_test, format_specs) { TEST(chrono_test, invalid_specs) { auto sec = std::chrono::seconds(0); - EXPECT_THROW_MSG(fmt::format(runtime("{:%a}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%a}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%A}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%A}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%c}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%c}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%x}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%x}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%Ex}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Ex}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%X}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%X}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%EX}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%EX}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%D}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%D}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%F}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%F}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%Ec}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Ec}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%w}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%w}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%u}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%u}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%b}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%b}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%B}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%B}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%z}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%z}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%Z}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Z}"), sec), fmt::format_error, "no date"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%Eq}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Eq}"), sec), fmt::format_error, "invalid format"); - EXPECT_THROW_MSG(fmt::format(runtime("{:%Oq}"), sec), fmt::format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error, "invalid format"); } @@ -279,20 +453,33 @@ TEST(chrono_test, format_default_fp) { } TEST(chrono_test, format_precision) { - EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), std::chrono::seconds(42)), - fmt::format_error, - "precision not allowed for this argument type"); + EXPECT_THROW_MSG( + (void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)), + fmt::format_error, "precision not allowed for this argument type"); + EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234))); EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2)); + + EXPECT_EQ("13ms", fmt::format("{:.0}", dms(12.56))); + EXPECT_EQ("12.6ms", fmt::format("{:.1}", dms(12.56))); + EXPECT_EQ("12.56ms", fmt::format("{:.2}", dms(12.56))); } TEST(chrono_test, format_full_specs) { + EXPECT_EQ("1ms ", fmt::format("{:6.0}", dms(1.234))); EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234))); EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2)); EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1)); EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8)); EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3)); EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234))); + + EXPECT_EQ("13ms ", fmt::format("{:6.0}", dms(12.56))); + EXPECT_EQ(" 13ms", fmt::format("{:>8.{}}", dms(12.56), 0)); + EXPECT_EQ(" 13ms ", fmt::format("{:^{}.{}}", dms(12.56), 6, 0)); + EXPECT_EQ(" 13ms ", fmt::format("{0:^{2}.{1}}", dms(12.56), 0, 8)); + EXPECT_EQ("==13ms===", fmt::format("{:=^{}.{}}", dms(12.56), 9, 0)); + EXPECT_EQ("***13ms***", fmt::format("{:*^10.0}", dms(12.56))); } TEST(chrono_test, format_simple_q) { @@ -306,29 +493,37 @@ TEST(chrono_test, format_simple_q) { } TEST(chrono_test, format_precision_q) { - EXPECT_THROW_MSG(fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)), - fmt::format_error, - "precision not allowed for this argument type"); + EXPECT_THROW_MSG( + (void)fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)), + fmt::format_error, "precision not allowed for this argument type"); EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234))); EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2)); } TEST(chrono_test, format_full_specs_q) { + EXPECT_EQ("1 ms ", fmt::format("{:7.0%Q %q}", dms(1.234))); EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234))); EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2)); EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1)); EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9)); EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3)); EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234))); + + EXPECT_EQ("13 ms ", fmt::format("{:7.0%Q %q}", dms(12.56))); + EXPECT_EQ(" 13 ms", fmt::format("{:>8.{}%Q %q}", dms(12.56), 0)); + EXPECT_EQ(" 13 ms ", fmt::format("{:^{}.{}%Q %q}", dms(12.56), 8, 0)); + EXPECT_EQ(" 13 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(12.56), 0, 9)); + EXPECT_EQ("==13 ms==", fmt::format("{:=^{}.{}%Q %q}", dms(12.56), 9, 0)); + EXPECT_EQ("***13 ms***", fmt::format("{:*^11.0%Q %q}", dms(12.56))); } TEST(chrono_test, invalid_width_id) { - EXPECT_THROW(fmt::format(runtime("{:{o}"), std::chrono::seconds(0)), + EXPECT_THROW((void)fmt::format(runtime("{:{o}"), std::chrono::seconds(0)), fmt::format_error); } TEST(chrono_test, invalid_colons) { - EXPECT_THROW(fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)), + EXPECT_THROW((void)fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)), fmt::format_error); } @@ -348,15 +543,12 @@ TEST(chrono_test, negative_durations) { } TEST(chrono_test, special_durations) { - EXPECT_EQ( - "40.", - fmt::format("{:%S}", std::chrono::duration(1e20)).substr(0, 3)); + auto value = fmt::format("{:%S}", std::chrono::duration(1e20)); + EXPECT_EQ(value, "40"); auto nan = std::numeric_limits::quiet_NaN(); EXPECT_EQ( "nan nan nan nan nan:nan nan", fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration(nan))); - fmt::format("{:%S}", - std::chrono::duration(1.79400457e+31f)); EXPECT_EQ(fmt::format("{}", std::chrono::duration(1)), "1Es"); EXPECT_EQ(fmt::format("{}", std::chrono::duration(1)), @@ -375,11 +567,59 @@ TEST(chrono_test, weekday) { auto loc = get_locale("ru_RU.UTF-8"); std::locale::global(loc); auto mon = fmt::weekday(1); + + auto tm = std::tm(); + tm.tm_wday = static_cast(mon.c_encoding()); + EXPECT_EQ(fmt::format("{}", mon), "Mon"); + EXPECT_EQ(fmt::format("{:%a}", tm), "Mon"); + if (loc != std::locale::classic()) { EXPECT_THAT((std::vector{"пн", "Пн", "пнд", "Пнд"}), Contains(fmt::format(loc, "{:L}", mon))); + EXPECT_THAT((std::vector{"пн", "Пн", "пнд", "Пнд"}), + Contains(fmt::format(loc, "{:%a}", tm))); } } +TEST(chrono_test, cpp20_duration_subsecond_support) { + using attoseconds = std::chrono::duration; + // Check that 18 digits of subsecond precision are supported. + EXPECT_EQ(fmt::format("{:%S}", attoseconds{999999999999999999}), + "00.999999999999999999"); + EXPECT_EQ(fmt::format("{:%S}", attoseconds{673231113420148734}), + "00.673231113420148734"); + EXPECT_EQ(fmt::format("{:%S}", attoseconds{-673231113420148734}), + "-00.673231113420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{13420148734}), + "13.420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}), + "-13.420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234"); + { + // Check that {:%H:%M:%S} is equivalent to {:%T}. + auto dur = std::chrono::milliseconds{3601234}; + auto formatted_dur = fmt::format("{:%T}", dur); + EXPECT_EQ(formatted_dur, "01:00:01.234"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + } + using nanoseconds_dbl = std::chrono::duration; + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789"); + // Verify that only the seconds part is extracted and printed. + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789"); + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000"); + { + // Now the hour is printed, and we also test if negative doubles work. + auto dur = nanoseconds_dbl{-99123456789}; + auto formatted_dur = fmt::format("{:%T}", dur); + EXPECT_EQ(formatted_dur, "-00:01:39.123456789"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + } + // Check that durations with precision greater than std::chrono::seconds have + // fixed precision, and print zeros even if there is no fractional part. + EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), + "07.000000"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR diff --git a/test/color-test.cc b/test/color-test.cc index e2408ca1..af8f1494 100644 --- a/test/color-test.cc +++ b/test/color-test.cc @@ -20,10 +20,16 @@ TEST(color_test, format) { fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"), "\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m"); EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::faint, "faint"), "\x1b[2mfaint\x1b[0m"); EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"), "\x1b[3mitalic\x1b[0m"); EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"), "\x1b[4munderline\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::blink, "blink"), "\x1b[5mblink\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::reverse, "reverse"), + "\x1b[7mreverse\x1b[0m"); + EXPECT_EQ(fmt::format(fmt::emphasis::conceal, "conceal"), + "\x1b[8mconceal\x1b[0m"); EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"), "\x1b[9mstrikethrough\x1b[0m"); EXPECT_EQ( diff --git a/test/compile-error-test/CMakeLists.txt b/test/compile-error-test/CMakeLists.txt index 8202f279..db7a9429 100644 --- a/test/compile-error-test/CMakeLists.txt +++ b/test/compile-error-test/CMakeLists.txt @@ -1,71 +1,158 @@ # Test if compile errors are produced where necessary. cmake_minimum_required(VERSION 3.1...3.18) +project(compile-error-test CXX) -include(CheckCXXSourceCompiles) -include(CheckCXXCompilerFlag) +set(fmt_headers " + #include + #include +") -set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../../include) -set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG} ${PEDANTIC_COMPILE_FLAGS}) +set(error_test_names "") +set(non_error_test_content "") -function (generate_source result fragment) - set(${result} " - #define FMT_HEADER_ONLY 1 - #include \"fmt/format.h\" - int main() { - ${fragment} - } - " PARENT_SCOPE) +# For error tests (we expect them to produce compilation error): +# * adds a name of test into `error_test_names` list +# * generates a single source file (with the same name) for each test +# For non-error tests (we expect them to compile successfully): +# * adds a code segment as separate function to `non_error_test_content` +function (expect_compile name code_fragment) + cmake_parse_arguments(EXPECT_COMPILE "ERROR" "" "" ${ARGN}) + string(MAKE_C_IDENTIFIER "${name}" test_name) + + if (EXPECT_COMPILE_ERROR) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/${test_name}.cc" " + ${fmt_headers} + void ${test_name}() { + ${code_fragment} + } + ") + set(error_test_names_copy "${error_test_names}") + list(APPEND error_test_names_copy "${test_name}") + set(error_test_names "${error_test_names_copy}" PARENT_SCOPE) + else() + set(non_error_test_content " + ${non_error_test_content} + void ${test_name}() { + ${code_fragment} + }" PARENT_SCOPE) + endif() endfunction () -function (expect_compile code) - generate_source(source "${code}") - check_cxx_source_compiles("${source}" compiles) - if (NOT compiles) - set(error_msg "Compile error for: ${code}") - endif () - # Unset the CMake cache variable compiles. Otherwise the compile test will - # just use cached information next time it runs. - unset(compiles CACHE) - if (error_msg) - message(FATAL_ERROR ${error_msg}) +# Generates a source file for non-error test with `non_error_test_content` and +# CMake project file with all error and single non-error test targets. +function (run_tests) + set(cmake_targets "") + foreach(test_name IN LISTS error_test_names) + set(cmake_targets " + ${cmake_targets} + add_library(test-${test_name} ${test_name}.cc) + target_link_libraries(test-${test_name} PRIVATE fmt::fmt) + ") + endforeach() + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/non_error_test.cc" " + ${fmt_headers} + ${non_error_test_content} + ") + set(cmake_targets " + ${cmake_targets} + add_library(non-error-test non_error_test.cc) + target_link_libraries(non-error-test PRIVATE fmt::fmt) + ") + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" " + cmake_minimum_required(VERSION 3.1...3.18) + project(tests CXX) + add_subdirectory(${FMT_DIR} fmt) + ${cmake_targets} + ") + + set(build_directory "${CMAKE_CURRENT_BINARY_DIR}/test/build") + file(MAKE_DIRECTORY "${build_directory}") + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCMAKE_GENERATOR=${CMAKE_GENERATOR}" + "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}" + "-DFMT_DIR=${FMT_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}/test" + WORKING_DIRECTORY "${build_directory}" + RESULT_VARIABLE result_var + OUTPUT_VARIABLE output_var + ERROR_VARIABLE output_var) + if (NOT result_var EQUAL 0) + message(FATAL_ERROR "Unable to configure:\n${output_var}") + endif() + + foreach(test_name IN LISTS error_test_names) + execute_process( + COMMAND + "${CMAKE_COMMAND}" --build "${build_directory}" --target "test-${test_name}" + WORKING_DIRECTORY "${build_directory}" + RESULT_VARIABLE result_var + OUTPUT_VARIABLE output_var + ERROR_QUIET) + if (result_var EQUAL 0) + message(SEND_ERROR "No compile error for \"${test_name}\":\n${output_var}") + endif () + endforeach() + + execute_process( + COMMAND + "${CMAKE_COMMAND}" --build "${build_directory}" --target "non-error-test" + WORKING_DIRECTORY "${build_directory}" + RESULT_VARIABLE result_var + OUTPUT_VARIABLE output_var + ERROR_VARIABLE output_var) + if (NOT result_var EQUAL 0) + message(SEND_ERROR "Compile error for combined non-error test:\n${output_var}") endif () endfunction () -function (expect_compile_error code) - generate_source(source "${code}") - check_cxx_source_compiles("${source}" compiles) - if (compiles) - set(error_msg "No compile error for: ${code}") - endif () - # Unset the CMake cache variable compiles. Otherwise the compile test will - # just use cached information next time it runs. - unset(compiles CACHE) - if (error_msg) - message(FATAL_ERROR ${error_msg}) - endif () -endfunction () # check if the source file skeleton compiles -expect_compile("") +expect_compile(check "") +expect_compile(check-error "compilation_error" ERROR) # Formatting a wide character with a narrow format string is forbidden. -expect_compile_error("fmt::format(\"{}\", L'a');") +expect_compile(wide-character-narrow-format-string "fmt::format(L\"{}\", L'a');") +expect_compile(wide-character-narrow-format-string-error "fmt::format(\"{}\", L'a');" ERROR) # Formatting a wide string with a narrow format string is forbidden. -expect_compile_error("fmt::format(\"{}\", L\"foo\");") +expect_compile(wide-string-narrow-format-string "fmt::format(L\"{}\", L\"foo\");") +expect_compile(wide-string-narrow-format-string-error "fmt::format(\"{}\", L\"foo\");" ERROR) # Formatting a narrow string with a wide format string is forbidden because # mixing UTF-8 with UTF-16/32 can result in an invalid output. -expect_compile_error("fmt::format(L\"{}\", \"foo\");") +expect_compile(narrow-string-wide-format-string "fmt::format(L\"{}\", L\"foo\");") +expect_compile(narrow-string-wide-format-string-error "fmt::format(L\"{}\", \"foo\");" ERROR) -# Formatting a wide string with a narrow format string is forbidden. -expect_compile_error(" +expect_compile(cast-to-string " + struct S { + operator std::string() const { return std::string(); } + }; + fmt::format(\"{}\", std::string(S())); +") +expect_compile(cast-to-string-error " struct S { operator std::string() const { return std::string(); } }; fmt::format(\"{}\", S()); +" ERROR) + +# Formatting a function +expect_compile(format-function " + void (*f)(); + fmt::format(\"{}\", fmt::ptr(f)); ") +expect_compile(format-function-error " + void (*f)(); + fmt::format(\"{}\", f); +" ERROR) # Make sure that compiler features detected in the header # match the features detected in CMake. @@ -74,6 +161,43 @@ if (SUPPORTS_USER_DEFINED_LITERALS) else () set(supports_udl 0) endif () -expect_compile("#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl} - # error - #endif") +expect_compile(udl-check " + #if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl} + # error + #endif +") + +if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) + # Compile-time argument type check + expect_compile(format-string-number-spec " + #ifdef FMT_HAS_CONSTEVAL + fmt::format(\"{:d}\", 42); + #endif + ") + expect_compile(format-string-number-spec-error " + #ifdef FMT_HAS_CONSTEVAL + fmt::format(\"{:d}\", \"I am not a number\"); + #else + #error + #endif + " ERROR) + + # Compile-time argument name check + expect_compile(format-string-name " + #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + using namespace fmt::literals; + fmt::print(\"{foo}\", \"foo\"_a=42); + #endif + ") + expect_compile(format-string-name-error " + #if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + using namespace fmt::literals; + fmt::print(\"{foo}\", \"bar\"_a=42); + #else + #error + #endif + " ERROR) +endif () + +# Run all tests +run_tests() diff --git a/test/compile-fp-test.cc b/test/compile-fp-test.cc new file mode 100644 index 00000000..afedc26d --- /dev/null +++ b/test/compile-fp-test.cc @@ -0,0 +1,62 @@ +// Formatting library for C++ - formatting library tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/compile.h" +#include "gmock/gmock.h" + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \ + defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \ + defined(__cpp_constexpr_dynamic_alloc) && \ + __cpp_constexpr_dynamic_alloc >= 201907 && __cplusplus >= 202002L +template struct test_string { + template constexpr bool operator==(const T& rhs) const noexcept { + return fmt::basic_string_view(rhs).compare(buffer) == 0; + } + Char buffer[max_string_length]{}; +}; + +template +consteval auto test_format(auto format, const Args&... args) { + test_string string{}; + fmt::format_to(string.buffer, format, args...); + return string; +} + +TEST(compile_time_formatting_test, floating_point) { + EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f)); + EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f)); + + EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0)); + EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0)); + EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0)); + EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65)); + EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65)); + EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65)); + EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6)); + EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65)); + EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65)); + + EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65)); + EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65)); + EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65)); + EXPECT_EQ("9223372036854775808.000000", + test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0)); + + constexpr double nan = std::numeric_limits::quiet_NaN(); + EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan)); + EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan)); + if (std::signbit(-nan)) + EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan)); + else + fmt::print("Warning: compiler doesn't handle negative NaN correctly"); + + constexpr double inf = std::numeric_limits::infinity(); + EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf)); + EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf)); + EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf)); +} +#endif diff --git a/test/compile-test.cc b/test/compile-test.cc index fe2ca110..1765961b 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -59,7 +59,24 @@ TEST(compile_test, compile_fallback) { EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42)); } -#ifdef __cpp_if_constexpr +struct type_with_get { + template friend void get(type_with_get); +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter : formatter { + template + auto format(type_with_get, FormatContext& ctx) -> decltype(ctx.out()) { + return formatter::format(42, ctx); + } +}; +FMT_END_NAMESPACE + +TEST(compile_test, compile_type_with_get) { + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), type_with_get())); +} + +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) struct test_formattable {}; FMT_BEGIN_NAMESPACE diff --git a/test/core-test.cc b/test/core-test.cc index ad1bc38a..b2f2097e 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -151,7 +151,9 @@ TEST(buffer_test, indestructible) { template struct mock_buffer final : buffer { MOCK_METHOD1(do_grow, size_t(size_t capacity)); - void grow(size_t capacity) { this->set(this->data(), do_grow(capacity)); } + void grow(size_t capacity) override { + this->set(this->data(), do_grow(capacity)); + } mock_buffer(T* data = nullptr, size_t buf_capacity = 0) { this->set(data, buf_capacity); @@ -406,15 +408,7 @@ TYPED_TEST(numeric_arg_test, make_and_visit) { CHECK_ARG_SIMPLE(std::numeric_limits::max()); } -namespace fmt { -template <> struct is_char : std::true_type {}; -} // namespace fmt - -TEST(arg_test, char_arg) { - CHECK_ARG(char, 'a', 'a'); - CHECK_ARG(wchar_t, L'a', 'a'); - CHECK_ARG(wchar_t, L'a', L'a'); -} +TEST(arg_test, char_arg) { CHECK_ARG(char, 'a', 'a'); } TEST(arg_test, string_arg) { char str_data[] = "test"; @@ -452,7 +446,7 @@ struct check_custom { struct test_buffer final : fmt::detail::buffer { char data[10]; test_buffer() : fmt::detail::buffer(data, 0, 10) {} - void grow(size_t) {} + void grow(size_t) override {} } buffer; auto parse_ctx = fmt::format_parse_context(""); auto ctx = fmt::format_context(fmt::detail::buffer_appender(buffer), @@ -530,7 +524,7 @@ struct test_format_specs_handler { fmt::detail::arg_ref width_ref; int precision = 0; fmt::detail::arg_ref precision_ref; - char type = 0; + fmt::presentation_type type = fmt::presentation_type::none; // Workaround for MSVC2017 bug that results in "expression did not evaluate // to a constant" with compiler-generated copy ctor. @@ -556,14 +550,14 @@ struct test_format_specs_handler { constexpr void on_dynamic_precision(string_view) {} constexpr void end_precision() {} - constexpr void on_type(char t) { type = t; } + constexpr void on_type(fmt::presentation_type t) { type = t; } constexpr void on_error(const char*) { res = error; } }; template constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) { auto h = test_format_specs_handler(); - fmt::detail::parse_format_specs(s, s + N, h); + fmt::detail::parse_format_specs(s, s + N - 1, h); return h; } @@ -581,7 +575,7 @@ TEST(core_test, constexpr_parse_format_specs) { static_assert(parse_test_specs("{42}").width_ref.val.index == 42, ""); static_assert(parse_test_specs(".42").precision == 42, ""); static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, ""); - static_assert(parse_test_specs("d").type == 'd', ""); + static_assert(parse_test_specs("d").type == fmt::presentation_type::dec, ""); static_assert(parse_test_specs("{<").res == handler::error, ""); } @@ -603,7 +597,7 @@ constexpr fmt::detail::dynamic_format_specs parse_dynamic_specs( auto specs = fmt::detail::dynamic_format_specs(); auto ctx = test_parse_context(); auto h = fmt::detail::dynamic_specs_handler(specs, ctx); - parse_format_specs(s, s + N, h); + parse_format_specs(s, s + N - 1, h); return specs; } @@ -621,14 +615,15 @@ TEST(format_test, constexpr_dynamic_specs_handler) { static_assert(parse_dynamic_specs(".42").precision == 42, ""); static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, ""); static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, ""); - static_assert(parse_dynamic_specs("d").type == 'd', ""); + static_assert(parse_dynamic_specs("d").type == fmt::presentation_type::dec, + ""); } template constexpr test_format_specs_handler check_specs(const char (&s)[N]) { fmt::detail::specs_checker checker( test_format_specs_handler(), fmt::detail::type::double_type); - parse_format_specs(s, s + N, checker); + parse_format_specs(s, s + N - 1, checker); return checker; } @@ -645,7 +640,7 @@ TEST(format_test, constexpr_specs_checker) { static_assert(check_specs("{42}").width_ref.val.index == 42, ""); static_assert(check_specs(".42").precision == 42, ""); static_assert(check_specs(".{42}").precision_ref.val.index == 42, ""); - static_assert(check_specs("d").type == 'd', ""); + static_assert(check_specs("d").type == fmt::presentation_type::dec, ""); static_assert(check_specs("{<").res == handler::error, ""); } @@ -709,10 +704,80 @@ TEST(core_test, has_formatter) { ""); } +struct const_formattable {}; +struct nonconst_formattable {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + auto format(const const_formattable&, format_context& ctx) + -> decltype(ctx.out()) { + auto test = string_view("test"); + return std::copy_n(test.data(), test.size(), ctx.out()); + } +}; + +template <> struct formatter { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + auto format(nonconst_formattable&, format_context& ctx) + -> decltype(ctx.out()) { + auto test = string_view("test"); + return std::copy_n(test.data(), test.size(), ctx.out()); + } +}; +FMT_END_NAMESPACE + +struct convertible_to_pointer { + operator const int*() const { return nullptr; } +}; + +enum class test_scoped_enum {}; + TEST(core_test, is_formattable) { +#if 0 + // This should be enabled once corresponding map overloads are gone. + static_assert(fmt::is_formattable::value, ""); + static_assert(fmt::is_formattable::value, ""); + static_assert(fmt::is_formattable::value, ""); + static_assert(fmt::is_formattable::value, ""); +#endif + static_assert(!fmt::is_formattable::value, ""); +#ifdef __cpp_char8_t + static_assert(!fmt::is_formattable::value, ""); +#endif + static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable>::value, + ""); static_assert(fmt::is_formattable::value, ""); static_assert(!fmt::is_formattable::value, ""); static_assert(fmt::is_formattable::value, ""); + + static_assert(fmt::is_formattable::value, ""); + static_assert(fmt::is_formattable::value, ""); + + static_assert(fmt::is_formattable::value, ""); +#if !FMT_MSC_VER || FMT_MSC_VER >= 1910 + static_assert(!fmt::is_formattable::value, ""); +#endif + + static_assert(!fmt::is_formattable::value, ""); + + static_assert(!fmt::is_formattable::value, ""); + + struct s; + + static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); } TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } @@ -838,10 +903,21 @@ TEST(core_test, adl) { if (fmt::detail::const_check(true)) return; auto s = adl_test::string(); char buf[10]; - fmt::format("{}", s); + (void)fmt::format("{}", s); fmt::format_to(buf, "{}", s); fmt::format_to_n(buf, 10, "{}", s); - fmt::formatted_size("{}", s); + (void)fmt::formatted_size("{}", s); fmt::print("{}", s); fmt::print(stdout, "{}", s); } + +TEST(core_test, has_const_formatter) { + EXPECT_TRUE((fmt::detail::has_const_formatter())); + EXPECT_FALSE((fmt::detail::has_const_formatter())); +} + +TEST(core_test, format_nonconst) { + EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test"); +} diff --git a/test/enforce-checks-test.cc b/test/enforce-checks-test.cc index 5e63e163..c77cb142 100644 --- a/test/enforce-checks-test.cc +++ b/test/enforce-checks-test.cc @@ -17,12 +17,12 @@ // Exercise the API to verify that everything we expect to can compile. void test_format_api() { - fmt::format(FMT_STRING("{}"), 42); - fmt::format(FMT_STRING(L"{}"), 42); - fmt::format(FMT_STRING("noop")); + (void)fmt::format(FMT_STRING("{}"), 42); + (void)fmt::format(FMT_STRING(L"{}"), 42); + (void)fmt::format(FMT_STRING("noop")); - fmt::to_string(42); - fmt::to_wstring(42); + (void)fmt::to_string(42); + (void)fmt::to_wstring(42); std::vector out; fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42); @@ -35,13 +35,14 @@ void test_format_api() { } void test_chrono() { - fmt::format(FMT_STRING("{}"), std::chrono::seconds(42)); - fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42)); + (void)fmt::format(FMT_STRING("{}"), std::chrono::seconds(42)); + (void)fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42)); } void test_text_style() { fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)"); - fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)"); + (void)fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), + "rgb(255,20,30)"); fmt::text_style ts = fg(fmt::rgb(255, 20, 30)); std::string out; @@ -51,7 +52,7 @@ void test_text_style() { void test_range() { std::vector hello = {'h', 'e', 'l', 'l', 'o'}; - fmt::format(FMT_STRING("{}"), hello); + (void)fmt::format(FMT_STRING("{}"), hello); } int main() { diff --git a/test/find-package-test/main.cc b/test/find-package-test/main.cc index f39f377c..911e0e8d 100644 --- a/test/find-package-test/main.cc +++ b/test/find-package-test/main.cc @@ -1,6 +1,5 @@ #include "fmt/format.h" int main(int argc, char** argv) { - for(int i = 0; i < argc; ++i) - fmt::print("{}: {}\n", i, argv[i]); + for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]); } diff --git a/test/format b/test/format deleted file mode 100644 index a6c9e7e9..00000000 --- a/test/format +++ /dev/null @@ -1,856 +0,0 @@ -// Formatting library for C++ - the standard API implementation -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_FORMAT_ -#define FMT_FORMAT_ - -#include -#include -#include -#include "fmt/format.h" - -// This implementation verifies the correctness of the standard API proposed in -// P0645 Text Formatting and is optimized for copy-pasting from the paper, not -// for efficiency or readability. An efficient implementation should not use -// std::variant and should store packed argument type tags separately from -// values in basic_format_args for small number of arguments. - -namespace std { -template -constexpr bool Integral = is_integral_v; - -template - using iter_difference_t = ptrdiff_t; -} - -// https://fmt.dev/Text%20Formatting.html#format.syn -namespace std { - // [format.error], class format_error - class format_error; - - // [format.formatter], formatter - template class basic_format_parse_context; - using format_parse_context = basic_format_parse_context; - using wformat_parse_context = basic_format_parse_context; - - template class basic_format_context; - using format_context = basic_format_context< - /* unspecified */ fmt::detail::buffer_appender, char>; - using wformat_context = basic_format_context< - /* unspecified */ fmt::detail::buffer_appender, wchar_t>; - - template struct formatter { - formatter() = delete; - }; - - // [format.arguments], arguments - template class basic_format_arg; - - template - /* see below */ auto visit_format_arg(Visitor&& vis, basic_format_arg arg); - - template struct format_arg_store; // exposition only - - template class basic_format_args; - using format_args = basic_format_args; - using wformat_args = basic_format_args; - - template - using format_args_t = basic_format_args>; - - template - format_arg_store - make_format_args(const Args&... args); - template - format_arg_store - make_wformat_args(const Args&... args); - - // [format.functions], formatting functions - template - string format(string_view fmt, const Args&... args); - template - wstring format(wstring_view fmt, const Args&... args); - - string vformat(string_view fmt, format_args args); - wstring vformat(wstring_view fmt, wformat_args args); - - template - Out format_to(Out out, string_view fmt, const Args&... args); - template - Out format_to(Out out, wstring_view fmt, const Args&... args); - - template - Out vformat_to(Out out, string_view fmt, format_args_t, char> args); - template - Out vformat_to(Out out, wstring_view fmt, format_args_t, wchar_t> args); - - template - struct format_to_n_result { - Out out; - iter_difference_t size; - }; - - template - format_to_n_result format_to_n(Out out, iter_difference_t n, - string_view fmt, const Args&... args); - template - format_to_n_result format_to_n(Out out, iter_difference_t n, - wstring_view fmt, const Args&... args); - - template - size_t formatted_size(string_view fmt, const Args&... args); - template - size_t formatted_size(wstring_view fmt, const Args&... args); -} - -// https://fmt.dev/Text%20Formatting.html#format.error -namespace std { - class format_error : public runtime_error { - public: - explicit format_error(const string& what_arg) : runtime_error(what_arg) {} - explicit format_error(const char* what_arg) : runtime_error(what_arg) {} - }; -} - -namespace std { -namespace detail { -struct error_handler { - // This function is intentionally not constexpr to give a compile-time error. - void on_error(const char* message) { - throw std::format_error(message); - } -}; -} -} - -// https://fmt.dev/Text%20Formatting.html#format.parse_context -namespace std { - template - class basic_format_parse_context { - public: - using char_type = charT; - using const_iterator = typename basic_string_view::const_iterator; - using iterator = const_iterator; - - private: - iterator begin_; // exposition only - iterator end_; // exposition only - enum indexing { unknown, manual, automatic }; // exposition only - indexing indexing_; // exposition only - size_t next_arg_id_; // exposition only - size_t num_args_; // exposition only - - public: - explicit constexpr basic_format_parse_context(basic_string_view fmt, - size_t num_args = 0) noexcept; - basic_format_parse_context(const basic_format_parse_context&) = delete; - basic_format_parse_context& operator=(const basic_format_parse_context&) = delete; - - constexpr const_iterator begin() const noexcept; - constexpr const_iterator end() const noexcept; - constexpr void advance_to(const_iterator it); - - constexpr size_t next_arg_id(); - constexpr void check_arg_id(size_t id); - - // Implementation detail: - constexpr void check_arg_id(fmt::string_view) {} - detail::error_handler error_handler() const { return {}; } - void on_error(const char* msg) { error_handler().on_error(msg); } - }; -} - -namespace std { -template -/* explicit */ constexpr basic_format_parse_context:: - basic_format_parse_context(basic_string_view fmt, - size_t num_args) noexcept -: begin_(fmt.begin()), end_(fmt.end()), indexing_(unknown), next_arg_id_(0), num_args_(num_args) {} - -template -constexpr typename basic_format_parse_context::const_iterator basic_format_parse_context::begin() const noexcept { return begin_; } - -template -constexpr typename basic_format_parse_context::const_iterator basic_format_parse_context::end() const noexcept { return end_; } - -template -constexpr void basic_format_parse_context::advance_to(typename basic_format_parse_context::iterator it) { begin_ = it; } - -template -constexpr size_t basic_format_parse_context::next_arg_id() { - if (indexing_ == manual) - throw format_error("manual to automatic indexing"); - if (indexing_ == unknown) - indexing_ = automatic; - return next_arg_id_++; -} - -template -constexpr void basic_format_parse_context::check_arg_id(size_t id) { - // clang doesn't support __builtin_is_constant_evaluated yet - //if (!(!__builtin_is_constant_evaluated() || id < num_args_)) - // throw format_error(invalid index is out of range"); - if (indexing_ == automatic) - throw format_error("automatic to manual indexing"); - if (indexing_ == unknown) - indexing_ = manual; -} -} - -// https://fmt.dev/Text%20Formatting.html#format.context -namespace std { - template - class basic_format_context { - basic_format_args args_; // exposition only - Out out_; // exposition only - - public: - using iterator = Out; - using char_type = charT; - template using formatter_type = formatter; - - basic_format_arg arg(size_t id) const; - - iterator out(); - void advance_to(iterator it); - - // Implementation details: - using format_arg = basic_format_arg; - basic_format_context(Out out, basic_format_args args, fmt::detail::locale_ref) - : args_(args), out_(out) {} - detail::error_handler error_handler() const { return {}; } - basic_format_arg arg(fmt::basic_string_view) const { - return {}; // unused: named arguments are not supported yet - } - void on_error(const char* msg) { error_handler().on_error(msg); } - }; -} - -namespace std { -template -basic_format_arg> basic_format_context::arg(size_t id) const { return args_.get(id); } - -template -typename basic_format_context::iterator basic_format_context::out() { return out_; } - -template -void basic_format_context::advance_to(typename basic_format_context::iterator it) { out_ = it; } -} - -namespace std { -namespace detail { -template -constexpr bool is_standard_integer_v = - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v; - -template -constexpr bool is_standard_unsigned_integer_v = - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v; - -template struct formatter; -} -} - -// https://fmt.dev/Text%20Formatting.html#format.arg -namespace std { - template - class basic_format_arg { - public: - class handle; - - private: - using char_type = typename Context::char_type; // exposition only - - variant, - const void*, handle> value; // exposition only - - template || - std::is_same_v || - (std::is_same_v && std::is_same_v) || - detail::is_standard_integer_v || - detail::is_standard_unsigned_integer_v || - sizeof(typename Context::template formatter_type().format(declval(), declval())) != 0 - >> explicit basic_format_arg(const T& v) noexcept; // exposition only - explicit basic_format_arg(float n) noexcept; // exposition only - explicit basic_format_arg(double n) noexcept; // exposition only - explicit basic_format_arg(long double n) noexcept; // exposition only - explicit basic_format_arg(const char_type* s); // exposition only - - template - explicit basic_format_arg( - basic_string_view s) noexcept; // exposition only - - template - explicit basic_format_arg( - const basic_string& s) noexcept; // exposition only - - explicit basic_format_arg(nullptr_t) noexcept; // exposition only - - template>> - explicit basic_format_arg(const T* p) noexcept; // exposition only - - // Fails due to a bug in clang - //template - // friend auto visit_format_arg(Visitor&& vis, - // basic_format_arg arg); // exposition only - - friend auto get_value(basic_format_arg arg) { - return arg.value; - } - - template friend struct detail::formatter; - - template - friend format_arg_store - make_format_args(const Args&... args); // exposition only - - public: - basic_format_arg() noexcept; - - explicit operator bool() const noexcept; - }; -} - -namespace std { -template -basic_format_arg::basic_format_arg() noexcept {} - -template -template /* explicit */ basic_format_arg::basic_format_arg(const T& v) noexcept { - if constexpr (std::is_same_v || std::is_same_v) - value = v; - else if constexpr (std::is_same_v && std::is_same_v) - value = static_cast(v); - else if constexpr (detail::is_standard_integer_v && sizeof(T) <= sizeof(int)) - value = static_cast(v); - else if constexpr (detail::is_standard_unsigned_integer_v && sizeof(T) <= sizeof(unsigned)) - value = static_cast(v); - else if constexpr (detail::is_standard_integer_v) - value = static_cast(v); - else if constexpr (detail::is_standard_unsigned_integer_v) - value = static_cast(v); - else if constexpr (sizeof(typename Context::template formatter_type().format(declval(), declval())) != 0) - value = handle(v); -} - -template -/* explicit */ basic_format_arg::basic_format_arg(float n) noexcept - : value(static_cast(n)) {} - -template -/* explicit */ basic_format_arg::basic_format_arg(double n) noexcept - : value(n) {} - -template -/* explicit */ basic_format_arg::basic_format_arg(long double n) noexcept - : value(n) {} - -template -/* explicit */ basic_format_arg::basic_format_arg(const typename basic_format_arg::char_type* s) - : value(s) { - assert(s != nullptr); -} - -template -template -/* explicit */ basic_format_arg::basic_format_arg(basic_string_view s) noexcept - : value(s) {} - -template -template -/* explicit */ basic_format_arg::basic_format_arg( - const basic_string& s) noexcept - : value(basic_string_view(s.data(), s.size())) {} - - -template -/* explicit */ basic_format_arg::basic_format_arg(nullptr_t) noexcept - : value(static_cast(nullptr)) {} - -template -template /* explicit */ basic_format_arg::basic_format_arg(const T* p) noexcept - : value(p) {} - -template -/* explicit */ basic_format_arg::operator bool() const noexcept { - return !holds_alternative(value); -} -} - -namespace std { - template - class basic_format_arg::handle { - const void* ptr_; // exposition only - void (*format_)(basic_format_parse_context&, - Context&, const void*); // exposition only - - template explicit handle(const T& val) noexcept; // exposition only - - friend class basic_format_arg; // exposition only - - public: - void format(basic_format_parse_context&, Context& ctx) const; - }; -} - -namespace std { -template -template /* explicit */ basic_format_arg::handle::handle(const T& val) noexcept - : ptr_(&val), format_([](basic_format_parse_context& parse_ctx, Context& format_ctx, const void* ptr) { - typename Context::template formatter_type f; - parse_ctx.advance_to(f.parse(parse_ctx)); - format_ctx.advance_to(f.format(*static_cast(ptr), format_ctx)); - }) {} - -template -void basic_format_arg::handle::format(basic_format_parse_context& parse_ctx, Context& format_ctx) const { - format_(parse_ctx, format_ctx, ptr_); -} - -// https://fmt.dev/Text%20Formatting.html#format.visit -template - auto visit_format_arg(Visitor&& vis, basic_format_arg arg) { - return visit(vis, get_value(arg)); - } -} - -// https://fmt.dev/Text%20Formatting.html#format.store -namespace std { - template - struct format_arg_store { // exposition only - array, sizeof...(Args)> args; - }; -} - -// https://fmt.dev/Text%20Formatting.html#format.basic_args -namespace std { - template - class basic_format_args { - size_t size_; // exposition only - const basic_format_arg* data_; // exposition only - - public: - basic_format_args() noexcept; - - template - basic_format_args(const format_arg_store& store) noexcept; - - basic_format_arg get(size_t i) const noexcept; - }; -} - -namespace std { - -template -basic_format_args::basic_format_args() noexcept : size_(0) {} - -template -template - basic_format_args::basic_format_args(const format_arg_store& store) noexcept - : size_(sizeof...(Args)), data_(store.args.data()) {} - -template -basic_format_arg basic_format_args::get(size_t i) const noexcept { - return i < size_ ? data_[i] : basic_format_arg(); -} -} - -namespace std { -// https://fmt.dev/Text%20Formatting.html#format.make_args -template - format_arg_store make_format_args(const Args&... args) { - return {basic_format_arg(args)...}; - } - -// https://fmt.dev/Text%20Formatting.html#format.make_wargs -template - format_arg_store make_wformat_args(const Args&... args) { - return make_format_args(args...); - } -} - -namespace std { -namespace detail { - -template -class arg_formatter - : public fmt::detail::arg_formatter_base { - private: - using char_type = Char; - using base = fmt::detail::arg_formatter_base; - using format_context = std::basic_format_context; - using parse_context = basic_format_parse_context; - - parse_context* parse_ctx_; - format_context& ctx_; - - public: - using iterator = OutputIt; - using format_specs = typename base::format_specs; - - /** - \rst - Constructs an argument formatter object. - *ctx* is a reference to the formatting context, - *spec* contains format specifier information for standard argument types. - \endrst - */ - arg_formatter(format_context& ctx, parse_context* parse_ctx = nullptr, fmt::format_specs* spec = nullptr) - : base(ctx.out(), spec, {}), parse_ctx_(parse_ctx), ctx_(ctx) {} - - using base::operator(); - - /** Formats an argument of a user-defined type. */ - iterator operator()(typename std::basic_format_arg::handle handle) { - handle.format(*parse_ctx_, ctx_); - return this->out(); - } - - iterator operator()(monostate) { - throw format_error(""); - } -}; - -template -inline fmt::detail::type get_type(basic_format_arg arg) { - return visit_format_arg([&] (auto val) { - using char_type = typename Context::char_type; - using T = decltype(val); - if (std::is_same_v) - return fmt::detail::type::none_type; - if (std::is_same_v) - return fmt::detail::type::bool_type; - if (std::is_same_v) - return fmt::detail::type::char_type; - if (std::is_same_v) - return fmt::detail::type::int_type; - if (std::is_same_v) - return fmt::detail::type::uint_type; - if (std::is_same_v) - return fmt::detail::type::long_long_type; - if (std::is_same_v) - return fmt::detail::type::ulong_long_type; - if (std::is_same_v) - return fmt::detail::type::double_type; - if (std::is_same_v) - return fmt::detail::type::long_double_type; - if (std::is_same_v) - return fmt::detail::type::cstring_type; - if (std::is_same_v>) - return fmt::detail::type::string_type; - if (std::is_same_v) - return fmt::detail::type::pointer_type; - assert(get_value(arg).index() == 12); - return fmt::detail::type::custom_type; - }, arg); -} - -template -class custom_formatter { - private: - using parse_context = basic_format_parse_context; - parse_context& parse_ctx_; - Context& format_ctx_; - - public: - custom_formatter(parse_context& parse_ctx, Context& ctx) : parse_ctx_(parse_ctx), format_ctx_(ctx) {} - - bool operator()(typename basic_format_arg::handle h) const { - h.format(parse_ctx_, format_ctx_); - return true; - } - - template bool operator()(T) const { return false; } -}; - -template -struct format_handler : detail::error_handler { - using iterator = typename ArgFormatter::iterator; - - format_handler(iterator out, basic_string_view str, - basic_format_args format_args, - fmt::detail::locale_ref loc) - : parse_ctx(str), context(out, format_args, loc) {} - - void on_text(const Char* begin, const Char* end) { - auto size = fmt::detail::to_unsigned(end - begin); - auto out = context.out(); - auto&& it = fmt::detail::reserve(out, size); - it = std::copy_n(begin, size, it); - context.advance_to(out); - } - - int on_arg_id() { return parse_ctx.next_arg_id(); } - int on_arg_id(unsigned id) { return parse_ctx.check_arg_id(id), id; } - int on_arg_id(fmt::basic_string_view) { return 0; } - - void on_replacement_field(int id, const Char* p) { - auto arg = context.arg(id); - parse_ctx.advance_to(parse_ctx.begin() + (p - &*parse_ctx.begin())); - custom_formatter f(parse_ctx, context); - if (!visit_format_arg(f, arg)) - context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx), arg)); - } - - const Char* on_format_specs(int id, const Char* begin, const Char* end) { - auto arg = context.arg(id); - parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin())); - custom_formatter f(parse_ctx, context); - if (visit_format_arg(f, arg)) return &*parse_ctx.begin(); - fmt::basic_format_specs specs; - using fmt::detail::specs_handler; - using parse_context = basic_format_parse_context; - fmt::detail::specs_checker> handler( - specs_handler(specs, parse_ctx, context), get_type(arg)); - begin = parse_format_specs(begin, end, handler); - if (begin == end || *begin != '}') on_error("missing '}' in format string"); - parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin())); - context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx, &specs), arg)); - return begin; - } - - basic_format_parse_context parse_ctx; - Context context; -}; - -template -struct formatter { - // Parses format specifiers stopping either at the end of the range or at the - // terminating '}'. - template - FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext& ctx) { - namespace detail = fmt::detail; - typedef detail::dynamic_specs_handler handler_type; - auto type = detail::mapped_type_constant>::value; - detail::specs_checker handler(handler_type(specs_, ctx), - type); - auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); - auto type_spec = specs_.type; - auto eh = ctx.error_handler(); - switch (type) { - case detail::type::none_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case detail::type::int_type: - case detail::type::uint_type: - case detail::type::long_long_type: - case detail::type::ulong_long_type: - case detail::type::bool_type: - handle_int_type_spec(type_spec, - detail::int_type_checker(eh)); - break; - case detail::type::char_type: - handle_char_specs( - &specs_, detail::char_specs_checker(type_spec, eh)); - break; - case detail::type::double_type: - case detail::type::long_double_type: - detail::parse_float_type_spec(specs_, eh); - break; - case detail::type::cstring_type: - detail::handle_cstring_type_spec( - type_spec, detail::cstring_type_checker(eh)); - break; - case detail::type::string_type: - detail::check_string_type_spec(type_spec, eh); - break; - case detail::type::pointer_type: - detail::check_pointer_type_spec(type_spec, eh); - break; - case detail::type::custom_type: - // Custom format specifiers should be checked in parse functions of - // formatter specializations. - break; - } - return it; - } - - template - auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) { - fmt::detail::handle_dynamic_spec( - specs_.width, specs_.width_ref, ctx); - fmt::detail::handle_dynamic_spec( - specs_.precision, specs_.precision_ref, ctx); - using af = arg_formatter; - return visit_format_arg(af(ctx, nullptr, &specs_), - basic_format_arg(val)); - } - - private: - fmt::detail::dynamic_format_specs specs_; -}; -} // namespace detail - -// https://fmt.dev/Text%20Formatting.html#format.functions -template - string format(string_view fmt, const Args&... args) { - return vformat(fmt, make_format_args(args...)); - } - -template - wstring format(wstring_view fmt, const Args&... args) { - return vformat(fmt, make_wformat_args(args...)); - } - -string vformat(string_view fmt, format_args args) { - fmt::memory_buffer mbuf; - fmt::detail::buffer& buf = mbuf; - using af = detail::arg_formatter; - detail::format_handler - h(fmt::detail::buffer_appender(buf), fmt, args, {}); - fmt::detail::parse_format_string(fmt::to_string_view(fmt), h); - return to_string(mbuf); -} - -wstring vformat(wstring_view fmt, wformat_args args); - -template - Out format_to(Out out, string_view fmt, const Args&... args) { - using context = basic_format_context; - return vformat_to(out, fmt, make_format_args(args...)); - } - -template - Out format_to(Out out, wstring_view fmt, const Args&... args) { - using context = basic_format_context; - return vformat_to(out, fmt, make_format_args(args...)); - } - -template - Out vformat_to(Out out, string_view fmt, format_args_t, char> args) { - using af = detail::arg_formatter; - detail::format_handler> - h(out, fmt, args, {}); - fmt::detail::parse_format_string(fmt::to_string_view(fmt), h); - return h.context.out(); - } - -template - Out vformat_to(Out out, wstring_view fmt, format_args_t, wchar_t> args); - -template - format_to_n_result format_to_n(Out out, iter_difference_t n, - string_view fmt, const Args&... args); -template - format_to_n_result format_to_n(Out out, iter_difference_t n, - wstring_view fmt, const Args&... args); - -template - size_t formatted_size(string_view fmt, const Args&... args); -template - size_t formatted_size(wstring_view fmt, const Args&... args); - -#define charT char - -template<> struct formatter : detail::formatter {}; - -template<> struct formatter; - -template<> struct formatter : detail::formatter {}; - -template<> struct formatter : detail::formatter {}; - -template struct formatter - : detail::formatter, charT> {}; - -template - struct formatter, charT> - : detail::formatter, charT> {}; - -template - struct formatter, charT> - : detail::formatter, charT> {}; - -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; - -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter - : detail::formatter, charT> {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter - : detail::formatter, charT> {}; -template <> struct formatter : detail::formatter {}; - -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; - -#undef charT - -#define charT wchar_t - -template<> struct formatter : detail::formatter {}; - -template<> struct formatter : detail::formatter {}; - -template<> struct formatter : detail::formatter {}; - -template<> struct formatter : detail::formatter {}; - -template struct formatter - : detail::formatter, charT> {}; - -template - struct formatter, charT> - : detail::formatter, charT> {}; - -template - struct formatter, charT> - : detail::formatter, charT> {}; - -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; - -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter - : detail::formatter, charT> {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter - : detail::formatter, charT> {}; -template <> struct formatter : detail::formatter {}; - -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; -template <> struct formatter : detail::formatter {}; - -#undef charT - - template<> struct formatter { - formatter() = delete; - }; -} - -#endif // FMT_FORMAT_ diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index d3333cd2..a012306f 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -278,25 +278,22 @@ TEST(fp_test, get_round_direction) { } TEST(fp_test, fixed_handler) { - struct handler : fmt::detail::fixed_handler { + struct handler : fmt::detail::gen_digits_handler { char buffer[10]; - handler(int prec = 0) : fmt::detail::fixed_handler() { + handler(int prec = 0) : fmt::detail::gen_digits_handler() { buf = buffer; precision = prec; } }; - int exp = 0; - handler().on_digit('0', 100, 99, 0, exp, false); - EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false), - assertion_failure); + handler().on_digit('0', 100, 99, 0, false); + EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure); namespace digits = fmt::detail::digits; - EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error); + EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error); // Check that divisor - error doesn't overflow. - EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error); + EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error); // Check that 2 * error doesn't overflow. uint64_t max = max_value(); - EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false), - digits::error); + EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error); } TEST(fp_test, grisu_format_compiles_with_on_ieee_double) { diff --git a/test/format-test.cc b/test/format-test.cc index 892556f1..a8592ef0 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -370,11 +370,11 @@ TEST(format_test, escape) { } TEST(format_test, unmatched_braces) { - EXPECT_THROW_MSG(fmt::format(runtime("{")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{")), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("}")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("}")), format_error, "unmatched '}' in format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0{}")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0{}")), format_error, "invalid format string"); } @@ -391,30 +391,30 @@ TEST(format_test, args_in_different_positions) { } TEST(format_test, arg_errors) { - EXPECT_THROW_MSG(fmt::format(runtime("{")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{")), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{?}")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{?}")), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0")), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0}")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0}")), format_error, "argument not found"); - EXPECT_THROW_MSG(fmt::format(runtime("{00}"), 42), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{00}"), 42), format_error, "invalid format string"); char format_str[buffer_size]; safe_sprintf(format_str, "{%u", INT_MAX); - EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, "invalid format string"); safe_sprintf(format_str, "{%u}", INT_MAX); - EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, "argument not found"); safe_sprintf(format_str, "{%u", INT_MAX + 1u); - EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, "invalid format string"); safe_sprintf(format_str, "{%u}", INT_MAX + 1u); - EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, "argument not found"); } @@ -458,24 +458,26 @@ TEST(format_test, named_arg) { fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0), fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0), fmt::arg("o", 0), fmt::arg("p", 0))); - EXPECT_THROW_MSG(fmt::format(runtime("{a}")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{a}")), format_error, "argument not found"); - EXPECT_THROW_MSG(fmt::format(runtime("{a}"), 42), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{a}"), 42), format_error, "argument not found"); } TEST(format_test, auto_arg_index) { EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c')); - EXPECT_THROW_MSG(fmt::format(runtime("{0}{}"), 'a', 'b'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0}{}"), 'a', 'b'), format_error, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG(fmt::format(runtime("{}{0}"), 'a', 'b'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{}{0}"), 'a', 'b'), format_error, "cannot switch from automatic to manual argument indexing"); EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2)); - EXPECT_THROW_MSG(fmt::format(runtime("{0}:.{}"), 1.2345, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0}:.{}"), 1.2345, 2), + format_error, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG(fmt::format(runtime("{:.{0}}"), 1.2345, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{0}}"), 1.2345, 2), + format_error, "cannot switch from automatic to manual argument indexing"); - EXPECT_THROW_MSG(fmt::format(runtime("{}")), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{}")), format_error, "argument not found"); } @@ -533,9 +535,9 @@ TEST(format_test, center_align) { } TEST(format_test, fill) { - EXPECT_THROW_MSG(fmt::format(runtime("{0:{<5}"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{<5}"), 'c'), format_error, "invalid fill character '{'"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{<5}}"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{<5}}"), 'c'), format_error, "invalid fill character '{'"); EXPECT_EQ("**42", fmt::format("{0:*>4}", 42)); EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42)); @@ -554,80 +556,83 @@ TEST(format_test, fill) { EXPECT_EQ(std::string("\0\0\0*", 4), fmt::format(string_view("{:\0>4}", 6), '*')); EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42)); - EXPECT_THROW_MSG(fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0), - format_error, "missing '}' in format string"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0), + format_error, "invalid type specifier"); } TEST(format_test, plus_sign) { EXPECT_EQ("+42", fmt::format("{0:+}", 42)); EXPECT_EQ("-42", fmt::format("{0:+}", -42)); EXPECT_EQ("+42", fmt::format("{0:+}", 42)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42u), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42u), format_error, "format specifier requires signed argument"); EXPECT_EQ("+42", fmt::format("{0:+}", 42l)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42ul), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ul), format_error, "format specifier requires signed argument"); EXPECT_EQ("+42", fmt::format("{0:+}", 42ll)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42ull), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ull), format_error, "format specifier requires signed argument"); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0)); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+"), 'c'), format_error, "missing '}' in format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 'c'), format_error, "invalid format specifier for char"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), "abc"), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), "abc"), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), reinterpret_cast(0x42)), - format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG( + (void)fmt::format(runtime("{0:+}"), reinterpret_cast(0x42)), + format_error, "format specifier requires numeric argument"); } TEST(format_test, minus_sign) { EXPECT_EQ("42", fmt::format("{0:-}", 42)); EXPECT_EQ("-42", fmt::format("{0:-}", -42)); EXPECT_EQ("42", fmt::format("{0:-}", 42)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42u), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42u), format_error, "format specifier requires signed argument"); EXPECT_EQ("42", fmt::format("{0:-}", 42l)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42ul), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ul), format_error, "format specifier requires signed argument"); EXPECT_EQ("42", fmt::format("{0:-}", 42ll)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42ull), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ull), format_error, "format specifier requires signed argument"); EXPECT_EQ("42", fmt::format("{0:-}", 42.0)); EXPECT_EQ("42", fmt::format("{0:-}", 42.0l)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-"), 'c'), format_error, "missing '}' in format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 'c'), format_error, "invalid format specifier for char"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), "abc"), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), "abc"), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), reinterpret_cast(0x42)), - format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG( + (void)fmt::format(runtime("{0:-}"), reinterpret_cast(0x42)), + format_error, "format specifier requires numeric argument"); } TEST(format_test, space_sign) { EXPECT_EQ(" 42", fmt::format("{0: }", 42)); EXPECT_EQ("-42", fmt::format("{0: }", -42)); EXPECT_EQ(" 42", fmt::format("{0: }", 42)); - EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42u), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42u), format_error, "format specifier requires signed argument"); EXPECT_EQ(" 42", fmt::format("{0: }", 42l)); - EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42ul), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ul), format_error, "format specifier requires signed argument"); EXPECT_EQ(" 42", fmt::format("{0: }", 42ll)); - EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42ull), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ull), format_error, "format specifier requires signed argument"); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0)); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l)); - EXPECT_THROW_MSG(fmt::format(runtime("{0: "), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0: "), 'c'), format_error, "missing '}' in format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 'c'), format_error, "invalid format specifier for char"); - EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), "abc"), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), "abc"), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), reinterpret_cast(0x42)), - format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG( + (void)fmt::format(runtime("{0: }"), reinterpret_cast(0x42)), + format_error, "format specifier requires numeric argument"); } TEST(format_test, hash_flag) { @@ -670,14 +675,15 @@ TEST(format_test, hash_flag) { EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01)); EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5)); EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:#"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error, "missing '}' in format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error, "invalid format specifier for char"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), "abc"), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), reinterpret_cast(0x42)), - format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG( + (void)fmt::format(runtime("{0:#}"), reinterpret_cast(0x42)), + format_error, "format specifier requires numeric argument"); } TEST(format_test, zero_flag) { @@ -690,14 +696,14 @@ TEST(format_test, zero_flag) { EXPECT_EQ("00042", fmt::format("{0:05}", 42ull)); EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0)); EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:0"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error, "missing '}' in format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), 'c'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error, "invalid format specifier for char"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), "abc"), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error, "format specifier requires numeric argument"); EXPECT_THROW_MSG( - fmt::format(runtime("{0:05}"), reinterpret_cast(0x42)), + (void)fmt::format(runtime("{0:05}"), reinterpret_cast(0x42)), format_error, "format specifier requires numeric argument"); } @@ -705,19 +711,19 @@ TEST(format_test, width) { char format_str[buffer_size]; safe_sprintf(format_str, "{0:%u", UINT_MAX); increment(format_str + 3); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); size_t size = std::strlen(format_str); format_str[size] = '}'; format_str[size + 1] = 0; - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); safe_sprintf(format_str, "{0:%u", INT_MAX + 1u); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); EXPECT_EQ(" -42", fmt::format("{0:4}", -42)); EXPECT_EQ(" 42", fmt::format("{0:5}", 42u)); @@ -742,47 +748,47 @@ TEST(format_test, runtime_width) { char format_str[buffer_size]; safe_sprintf(format_str, "{0:{%u", UINT_MAX); increment(format_str + 4); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "invalid format string"); size_t size = std::strlen(format_str); format_str[size] = '}'; format_str[size + 1] = 0; - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "argument not found"); format_str[size + 1] = '}'; format_str[size + 2] = 0; - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "argument not found"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{"), 0), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{}"), 0), format_error, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{?}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{?}}"), 0), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0), format_error, "argument not found"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{0:}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{0:}}"), 0), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, -1), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error, "negative width"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)), format_error, "number is too big"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, -1l), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error, "negative width"); if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { long value = INT_MAX; - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (value + 1)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)), format_error, "number is too big"); } - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)), format_error, "number is too big"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, '0'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error, "width is not integer"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error, "width is not integer"); EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4)); @@ -803,53 +809,53 @@ TEST(format_test, precision) { char format_str[buffer_size]; safe_sprintf(format_str, "{0:.%u", UINT_MAX); increment(format_str + 4); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); size_t size = std::strlen(format_str); format_str[size] = '}'; format_str[size + 1] = 0; - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "number is too big"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:."), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0), format_error, "missing precision specifier"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0), format_error, "missing precision specifier"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42u), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42u), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42u), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42u), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42l), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42l), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42l), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42l), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ul), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ul), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ul), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ul), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ll), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ll), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ll), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ll), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ull), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ull), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ull), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ull), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:3.0}"), 'x'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.0}"), 'x'), format_error, "precision not allowed for this argument type"); EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345)); EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l)); @@ -866,6 +872,23 @@ TEST(format_test, precision) { "117102665855668676818703956031062493194527159149245532930545654440112748" "012970999954193198940908041656332452475714786901472678015935523861155013" "480352649347201937902681071074917033322268447533357208324319361e-324"); + EXPECT_EQ( + fmt::format("{:.1074f}", 1.1125369292536e-308), + "0.0000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000111253692925360019747947051741965785554081512200979" + "355021686109411883779182127659725163430929750364498219730822952552570601" + "152163505899912777129583674906301179059298598412303893909188340988729019" + "014361467448914817838555156840459458527907308695109202499990850735085304" + "478476991912072201449236975063640913461919914396877093174125167509869762" + "482369631100360266123742648159508919592746619553246586039571522788247697" + "156360766271842991667238355464496455107749716934387136380536472531224398" + "559833794807213172371254492216255558078524900147957309382830827524104234" + "530961756787819847850302379672357738807808384667004752163416921762619527" + "462847642037420991432005657440259928195996762610375541867198059294212446" + "81962777939941034720757232455434770912461317493580281734466552734375"); std::string outputs[] = { "-0X1.41FE3FFE71C9E000000000000000000000000000000000000000000000000000000" @@ -903,13 +926,16 @@ TEST(format_test, precision) { EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34)); EXPECT_THROW_MSG( - fmt::format(runtime("{0:.2}"), reinterpret_cast(0xcafe)), + (void)fmt::format(runtime("{0:.2}"), reinterpret_cast(0xcafe)), format_error, "precision not allowed for this argument type"); EXPECT_THROW_MSG( - fmt::format(runtime("{0:.2f}"), reinterpret_cast(0xcafe)), + (void)fmt::format(runtime("{0:.2f}"), reinterpret_cast(0xcafe)), format_error, "precision not allowed for this argument type"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0, + fmt::detail::max_value()), + format_error, "number is too big"); EXPECT_THROW_MSG( - fmt::format(runtime("{:.{}e}"), 42.0, fmt::detail::max_value()), + (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304), format_error, "number is too big"); EXPECT_EQ("st", fmt::format("{0:.2}", "str")); @@ -919,86 +945,97 @@ TEST(format_test, runtime_precision) { char format_str[buffer_size]; safe_sprintf(format_str, "{0:.{%u", UINT_MAX); increment(format_str + 5); - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "invalid format string"); size_t size = std::strlen(format_str); format_str[size] = '}'; format_str[size + 1] = 0; - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "argument not found"); format_str[size + 1] = '}'; format_str[size + 2] = 0; - EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, "argument not found"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0), format_error, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{?}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}"), 0, 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}"), 0, 0), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0), format_error, "argument not found"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{0:}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0), format_error, "invalid format string"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, -1), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1), format_error, "negative precision"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)), format_error, "number is too big"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error, "negative precision"); if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { long value = INT_MAX; - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (value + 1)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (value + 1)), format_error, "number is too big"); } - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)), format_error, "number is too big"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error, "precision is not integer"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error, "precision is not integer"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42u, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42u, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42l, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42l, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ul, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ul, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ul, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ul, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ll, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ll, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ll, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ll, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ull, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ull, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ull, 2), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ull, 2), + format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:3.{1}}"), 'x', 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.{1}}"), 'x', 0), + format_error, "precision not allowed for this argument type"); EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2)); EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l)); - EXPECT_THROW_MSG( - fmt::format(runtime("{0:.{1}}"), reinterpret_cast(0xcafe), 2), - format_error, "precision not allowed for this argument type"); - EXPECT_THROW_MSG( - fmt::format(runtime("{0:.{1}f}"), reinterpret_cast(0xcafe), 2), - format_error, "precision not allowed for this argument type"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), + reinterpret_cast(0xcafe), 2), + format_error, + "precision not allowed for this argument type"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), + reinterpret_cast(0xcafe), 2), + format_error, + "precision not allowed for this argument type"); EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2)); } @@ -1029,15 +1066,15 @@ void check_unknown_types(const T& value, const char* types, const char*) { if (std::strchr(types, c) || std::strchr(special, c) || !c) continue; safe_sprintf(format_str, "{0:10%c}", c); const char* message = "invalid type specifier"; - EXPECT_THROW_MSG(fmt::format(runtime(format_str), value), format_error, - message) + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), value), + format_error, message) << format_str << " " << message; } } TEST(format_test, format_int) { - EXPECT_THROW_MSG(fmt::format(runtime("{0:v"), 42), format_error, - "missing '}' in format string"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:v"), 42), format_error, + "invalid type specifier"); check_unknown_types(42, "bBdoxXnLc", "integer"); EXPECT_EQ("x", fmt::format("{:c}", static_cast('x'))); } @@ -1345,26 +1382,10 @@ TEST(format_test, format_cstring) { char nonconst[] = "nonconst"; EXPECT_EQ("nonconst", fmt::format("{0}", nonconst)); EXPECT_THROW_MSG( - fmt::format(runtime("{0}"), static_cast(nullptr)), + (void)fmt::format(runtime("{0}"), static_cast(nullptr)), format_error, "string pointer is null"); } -TEST(format_test, format_schar_string) { - signed char str[] = "test"; - EXPECT_EQ("test", fmt::format("{0:s}", str)); - const signed char* const_str = str; - EXPECT_EQ("test", fmt::format("{0:s}", const_str)); -} - -TEST(format_test, format_uchar_string) { - unsigned char str[] = "test"; - EXPECT_EQ("test", fmt::format("{0:s}", str)); - const unsigned char* const_str = str; - EXPECT_EQ("test", fmt::format("{0:s}", const_str)); - unsigned char* ptr = str; - EXPECT_EQ("test", fmt::format("{0:s}", ptr)); -} - void function_pointer_test(int, double, std::string) {} TEST(format_test, format_pointer) { @@ -1390,6 +1411,8 @@ TEST(format_test, format_pointer) { TEST(format_test, format_string) { EXPECT_EQ("test", fmt::format("{0}", std::string("test"))); + EXPECT_THROW((void)fmt::format(fmt::runtime("{:x}"), std::string("test")), + fmt::format_error); } TEST(format_test, format_string_view) { @@ -1479,7 +1502,7 @@ template <> struct formatter : formatter { FMT_END_NAMESPACE TEST(format_test, format_custom) { - EXPECT_THROW_MSG(fmt::format(runtime("{:s}"), date(2012, 12, 9)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{:s}"), date(2012, 12, 9)), format_error, "unknown format specifier"); EXPECT_EQ("42", fmt::format("{0}", Answer())); EXPECT_EQ("0042", fmt::format("{:04}", Answer())); @@ -1556,8 +1579,9 @@ TEST(format_test, format_examples) { fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}", 42)); EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42)); - EXPECT_THROW_MSG(fmt::format(runtime("The answer is {:d}"), "forty-two"), - format_error, "invalid type specifier"); + EXPECT_THROW_MSG( + (void)fmt::format(runtime("The answer is {:d}"), "forty-two"), + format_error, "invalid type specifier"); EXPECT_WRITE( stdout, fmt::print("{}", std::numeric_limits::infinity()), "inf"); @@ -1593,6 +1617,11 @@ TEST(format_test, bytes) { EXPECT_EQ(10, s.size()); } +TEST(format_test, group_digits_view) { + EXPECT_EQ(fmt::format("{}", fmt::group_digits(10000000)), "10,000,000"); + EXPECT_EQ(fmt::format("{:8}", fmt::group_digits(1000)), " 1,000"); +} + enum test_enum { foo, bar }; TEST(format_test, join) { @@ -1716,6 +1745,21 @@ TEST(format_test, custom_format_compile_time_string) { using namespace fmt::literals; +# if FMT_GCC_VERSION +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1 +# elif FMT_CLANG_VERSION && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1 +# endif +# endif +# ifndef FMT_CHECK_DEPRECATED_UDL_FORMAT +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 0 +# endif + +# if FMT_CHECK_DEPRECATED_UDL_FORMAT +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" + TEST(format_test, format_udl) { EXPECT_EQ("{}c{}"_format("ab", 1), fmt::format("{}c{}", "ab", 1)); EXPECT_EQ("foo"_format(), "foo"); @@ -1723,6 +1767,9 @@ TEST(format_test, format_udl) { EXPECT_EQ("{}"_format(date(2015, 10, 21)), "2015-10-21"); } +# pragma GCC diagnostic pop +# endif + TEST(format_test, named_arg_udl) { auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra", "second"_a = "cad", "third"_a = 99); @@ -1775,21 +1822,21 @@ TEST(format_test, dynamic_formatter) { EXPECT_EQ("42", fmt::format("{:d}", num)); EXPECT_EQ("foo", fmt::format("{:s}", str)); EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4)); - EXPECT_THROW_MSG(fmt::format(runtime("{0:{}}"), num), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{}}"), num), format_error, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG(fmt::format(runtime("{:{0}}"), num), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:{0}}"), num), format_error, "cannot switch from automatic to manual argument indexing"); - EXPECT_THROW_MSG(fmt::format(runtime("{:+}"), str), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:+}"), str), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{:-}"), str), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:-}"), str), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{: }"), str), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{: }"), str), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{:#}"), str), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:#}"), str), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{:0}"), str), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:0}"), str), format_error, "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), num), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2}"), num), format_error, "precision not allowed for this argument type"); } @@ -1813,13 +1860,20 @@ struct formatter : formatter { }; FMT_END_NAMESPACE -TEST(format_test, to_string) { - EXPECT_EQ("42", fmt::to_string(42)); - EXPECT_EQ("0x1234", fmt::to_string(reinterpret_cast(0x1234))); - EXPECT_EQ("foo", fmt::to_string(adl_test::fmt::detail::foo())); +struct convertible_to_int { + operator int() const { return value; } - enum test_enum2 : unsigned char { test_value }; - EXPECT_EQ("0", fmt::to_string(test_value)); + int value = 42; +}; + +TEST(format_test, to_string) { + EXPECT_EQ(fmt::to_string(42), "42"); + EXPECT_EQ(fmt::to_string(reinterpret_cast(0x1234)), "0x1234"); + EXPECT_EQ(fmt::to_string(adl_test::fmt::detail::foo()), "foo"); + EXPECT_EQ(fmt::to_string(convertible_to_int()), "42"); + + enum foo : unsigned char { zero }; + EXPECT_EQ(fmt::to_string(zero), "0"); } TEST(format_test, output_iterators) { diff --git a/test/fuzzing/CMakeLists.txt b/test/fuzzing/CMakeLists.txt index 2f716d83..0280c5cd 100644 --- a/test/fuzzing/CMakeLists.txt +++ b/test/fuzzing/CMakeLists.txt @@ -25,6 +25,6 @@ function(add_fuzzer source) target_compile_features(${name} PRIVATE cxx_generic_lambdas) endfunction() -foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc) +foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc) add_fuzzer(${source}) endforeach () diff --git a/test/fuzzing/build.sh b/test/fuzzing/build.sh index 28c50633..4497b62c 100755 --- a/test/fuzzing/build.sh +++ b/test/fuzzing/build.sh @@ -22,6 +22,8 @@ here=$(pwd) CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17" +CLANG=clang++-11 + # For performance analysis of the fuzzers. builddir=$here/build-fuzzers-perfanalysis mkdir -p $builddir @@ -37,7 +39,7 @@ cmake --build $builddir builddir=$here/build-fuzzers-ossfuzz mkdir -p $builddir cd $builddir -CXX="clang++" \ +CXX=$CLANG \ CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \ cmake $CMAKEFLAGSALL \ -DFMT_FUZZ_LINKMAIN=Off \ @@ -50,7 +52,7 @@ cmake --build $builddir builddir=$here/build-fuzzers-libfuzzer mkdir -p $builddir cd $builddir -CXX="clang++" \ +CXX=$CLANG \ CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \ cmake $CMAKEFLAGSALL \ -DFMT_FUZZ_LINKMAIN=Off \ @@ -62,7 +64,7 @@ cmake --build $builddir builddir=$here/build-fuzzers-fast mkdir -p $builddir cd $builddir -CXX="clang++" \ +CXX=$CLANG \ CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \ cmake $CMAKEFLAGSALL \ -DFMT_FUZZ_LINKMAIN=Off \ diff --git a/test/fuzzing/chrono-duration.cc b/test/fuzzing/chrono-duration.cc index fdad9894..d66068d9 100644 --- a/test/fuzzing/chrono-duration.cc +++ b/test/fuzzing/chrono-duration.cc @@ -1,9 +1,10 @@ // Copyright (c) 2019, Paul Dreik // For the license information refer to format.h. -#include #include +#include + #include "fuzzer-common.h" template @@ -13,8 +14,8 @@ void invoke_inner(fmt::string_view format_str, Rep rep) { #if FMT_FUZZ_FORMAT_TO_STRING std::string message = fmt::format(format_str, value); #else - fmt::memory_buffer buf; - fmt::format_to(buf, format_str, value); + auto buf = fmt::memory_buffer(); + fmt::format_to(std::back_inserter(buf), format_str, value); #endif } catch (std::exception&) { } @@ -31,7 +32,7 @@ void invoke_outer(const uint8_t* data, size_t size, int period) { data += fixed_size; size -= fixed_size; - // data is already allocated separately in libFuzzer so reading past the end + // data is already allocated separately in libFuzzer so reading past the end // will most likely be detected anyway. const auto format_str = fmt::string_view(as_chars(data), size); @@ -86,7 +87,7 @@ void invoke_outer(const uint8_t* data, size_t size, int period) { } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size <= 4) return 0; + if (size <= 4) return 0; const auto representation = data[0]; const auto period = data[1]; diff --git a/test/fuzzing/chrono-timepoint.cc b/test/fuzzing/chrono-timepoint.cc new file mode 100644 index 00000000..8a1b24d2 --- /dev/null +++ b/test/fuzzing/chrono-timepoint.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2021, Paul Dreik +// For license information refer to format.h. +#include + +#include "fuzzer-common.h" + +/* + * a fuzzer for the chrono timepoints formatters + * C is a clock (std::chrono::system_clock etc) + */ +template void doit(const uint8_t* data, size_t size) { + using Rep = typename C::time_point::rep; + constexpr auto N = sizeof(Rep); + if (size < N) return; + + const auto x = assign_from_buf(data); + typename C::duration dur{x}; + typename C::time_point timepoint{dur}; + data += N; + size -= N; + data_to_string format_str(data, size); + + std::string message = fmt::format(format_str.get(), timepoint); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + try { + doit(data, size); + } catch (...) { + } + return 0; +} diff --git a/test/fuzzing/float.cc b/test/fuzzing/float.cc index 073e4bcd..d4c9e4f5 100644 --- a/test/fuzzing/float.cc +++ b/test/fuzzing/float.cc @@ -1,17 +1,18 @@ // A fuzzer for floating-point formatter. // For the license information refer to format.h. +#include + #include #include -#include #include -#include +#include #include "fuzzer-common.h" void check_round_trip(fmt::string_view format_str, double value) { auto buffer = fmt::memory_buffer(); - fmt::format_to(buffer, format_str, value); + fmt::format_to(std::back_inserter(buffer), format_str, value); if (std::isnan(value)) { auto nan = std::signbit(value) ? "-nan" : "nan"; @@ -24,8 +25,7 @@ void check_round_trip(fmt::string_view format_str, double value) { char* ptr = nullptr; if (std::strtod(buffer.data(), &ptr) != value) throw std::runtime_error("round trip failure"); - if (ptr + 1 != buffer.end()) - throw std::runtime_error("unparsed output"); + if (ptr + 1 != buffer.end()) throw std::runtime_error("unparsed output"); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { diff --git a/test/fuzzing/fuzzer-common.h b/test/fuzzing/fuzzer-common.h index 635a5d99..c0a24672 100644 --- a/test/fuzzing/fuzzer-common.h +++ b/test/fuzzing/fuzzer-common.h @@ -4,12 +4,12 @@ #ifndef FUZZER_COMMON_H #define FUZZER_COMMON_H -#include // std::uint8_t -#include // memcpy -#include - #include +#include // std::uint8_t +#include // memcpy +#include + // One can format to either a string, or a buffer. The latter is faster, but // one may be interested in formatting to a string instead to verify it works // as intended. To avoid a combinatoric explosion, select this at compile time @@ -56,7 +56,9 @@ struct data_to_string { data_to_string(const uint8_t* data, size_t size, bool add_terminator = false) : buffer(size + (add_terminator ? 1 : 0)) { - std::memcpy(buffer.data(), data, size); + if (size) { + std::memcpy(buffer.data(), data, size); + } } fmt::string_view get() const { return {buffer.data(), buffer.size()}; } diff --git a/test/fuzzing/named-arg.cc b/test/fuzzing/named-arg.cc index ffd8e903..3bee1ae3 100644 --- a/test/fuzzing/named-arg.cc +++ b/test/fuzzing/named-arg.cc @@ -1,10 +1,11 @@ // Copyright (c) 2019, Paul Dreik // For the license information refer to format.h. +#include + #include #include #include -#include #include "fuzzer-common.h" @@ -25,10 +26,11 @@ void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) { try { #if FMT_FUZZ_FORMAT_TO_STRING std::string message = - fmt::format(format_str.get(), fmt::arg(arg_name.data(), value)); + fmt::format(format_str.get(), fmt::arg(arg_name.data(), value)); #else fmt::memory_buffer out; - fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value)); + fmt::format_to(std::back_inserter(out), format_str.get(), + fmt::arg(arg_name.data(), value)); #endif } catch (std::exception&) { } diff --git a/test/fuzzing/one-arg.cc b/test/fuzzing/one-arg.cc index df173432..90cec716 100644 --- a/test/fuzzing/one-arg.cc +++ b/test/fuzzing/one-arg.cc @@ -1,17 +1,18 @@ // Copyright (c) 2019, Paul Dreik // For the license information refer to format.h. +#include + #include #include -#include #include "fuzzer-common.h" -template -const T* from_repr(const Repr& r) { return &r; } +template const T* from_repr(const Repr& r) { + return &r; +} -template <> -const std::tm* from_repr(const std::time_t& t) { +template <> const std::tm* from_repr(const std::time_t& t) { return std::localtime(&t); } diff --git a/test/fuzzing/two-args.cc b/test/fuzzing/two-args.cc index 4d7d3453..979320c2 100644 --- a/test/fuzzing/two-args.cc +++ b/test/fuzzing/two-args.cc @@ -1,10 +1,11 @@ // Copyright (c) 2019, Paul Dreik // For the license information refer to format.h. +#include + #include #include #include -#include #include "fuzzer-common.h" diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index dc542184..1d48a173 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -32,11 +32,10 @@ output_redirect::~output_redirect() FMT_NOEXCEPT { } void output_redirect::flush() { -# if EOF != -1 -# error "FMT_RETRY assumes return value of -1 indicating failure" -# endif int result = 0; - FMT_RETRY(result, fflush(file_)); + do { + result = fflush(file_); + } while (result == EOF && errno == EINTR); if (result != 0) throw fmt::system_error(errno, "cannot flush stream"); } diff --git a/test/gtest-extra.h b/test/gtest-extra.h index bbda1c01..df2b1d8c 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -12,7 +12,12 @@ #include -#include "fmt/os.h" +#ifdef FMT_MODULE_TEST +import fmt; +#else +# include "fmt/os.h" +#endif // FMG_MODULE_TEST + #include "gmock/gmock.h" #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ @@ -129,6 +134,7 @@ class suppress_assert { ~suppress_assert() { _set_invalid_parameter_handler(original_handler_); _CrtSetReportMode(_CRT_ASSERT, original_report_mode_); + (void)original_report_mode_; } }; diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 1282d62a..0cc4e1aa 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,6 +17,13 @@ else () target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) endif () +# Workaround GTest bug https://github.com/google/googletest/issues/705. +check_cxx_compiler_flag( + -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) +if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) + target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks) +endif () + if (MSVC) # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) diff --git a/test/header-only-test.cc b/test/header-only-test.cc index f0c3a7fa..570f09a5 100644 --- a/test/header-only-test.cc +++ b/test/header-only-test.cc @@ -1,7 +1,11 @@ // Header-only configuration test #include "fmt/core.h" +#include "fmt/ostream.h" +#include "gtest/gtest.h" #ifndef FMT_HEADER_ONLY # error "Not in the header-only mode." #endif + +TEST(header_only_test, format) { EXPECT_EQ(fmt::format("foo"), "foo"); } diff --git a/test/module-test.cc b/test/module-test.cc index 69b6099a..39c83983 100644 --- a/test/module-test.cc +++ b/test/module-test.cc @@ -15,6 +15,8 @@ # define FMT_HIDE_MODULE_BUGS #endif +#define FMT_MODULE_TEST + #include #include #include @@ -35,18 +37,30 @@ # define FMT_USE_FCNTL 0 #endif #define FMT_NOEXCEPT noexcept +#if defined(_WIN32) && !defined(__MINGW32__) +# define FMT_POSIX(call) _##call +#else +# define FMT_POSIX(call) call +#endif +#define FMT_OS_H_ // don't pull in os.h directly or indirectly import fmt; // check for macros leaking from BMI static bool macro_leaked = -#if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H) +#if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H_) true; #else false; #endif -#include "gtest-extra.h" +// Include sources to pick up functions and classes from the module rather than +// from the non-modular library which is baked into the 'test-main' library. +// This averts linker problems: +// - strong ownership model: missing linker symbols +// - weak ownership model: duplicate linker symbols +#include "gtest-extra.cc" +#include "util.cc" // an implicitly exported namespace must be visible [module.interface]/2.2 TEST(module_test, namespace) { @@ -62,8 +76,10 @@ bool oops_detail_namespace_is_visible; namespace fmt { bool namespace_detail_invisible() { #if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ - _MSC_FULL_VER <= 192930129 - // bug in msvc up to 16.11-pre1: + ((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \ + (_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704)) + // bug in msvc up to 16.11.5 / 17.0-pre5: + // the namespace is visible even when it is neither // implicitly nor explicitly exported return true; @@ -83,8 +99,8 @@ TEST(module_test, detail_namespace) { // macros must not be imported from a *named* module [cpp.import]/5.1 TEST(module_test, macros) { #if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ - _MSC_FULL_VER <= 192930129 - // bug in msvc up to 16.11-pre1: + _MSC_FULL_VER <= 192930130 + // bug in msvc up to 16.11-pre2: // include-guard macros leak from BMI // and even worse: they cannot be #undef-ined macro_leaked = false; @@ -442,8 +458,7 @@ TEST(module_test, time_duration) { } TEST(module_test, weekday) { - EXPECT_EQ("Monday", - std::format(std::locale::classic(), "{:%A}", fmt::weekday(1))); + EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1))); } TEST(module_test, to_string_view) { diff --git a/test/noexception-test.cc b/test/noexception-test.cc new file mode 100644 index 00000000..bf3472fd --- /dev/null +++ b/test/noexception-test.cc @@ -0,0 +1,18 @@ +// Formatting library for C++ - Noexception tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/args.h" +#include "fmt/chrono.h" +#include "fmt/color.h" +#include "fmt/compile.h" +#include "fmt/core.h" +#include "fmt/format.h" +#include "fmt/os.h" +#include "fmt/ostream.h" +#include "fmt/printf.h" +#include "fmt/ranges.h" +#include "fmt/xchar.h" diff --git a/test/os-test.cc b/test/os-test.cc index a34f96e6..5b5ef76e 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -41,9 +41,9 @@ TEST(util_test, utf16_to_utf8_empty_string) { } template -void check_utf_conversion_error( - const char* message, - fmt::basic_string_view str = fmt::basic_string_view(0, 1)) { +void check_utf_conversion_error(const char* message, + fmt::basic_string_view str = + fmt::basic_string_view(nullptr, 1)) { fmt::memory_buffer out; fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message); auto error = std::system_error(std::error_code()); @@ -63,7 +63,7 @@ TEST(util_test, utf16_to_utf8_error) { TEST(util_test, utf16_to_utf8_convert) { fmt::detail::utf16_to_utf8 u; - EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(0, 1))); + EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(nullptr, 1))); EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(L"foo", INT_MAX + 1u))); } @@ -81,12 +81,12 @@ TEST(os_test, format_std_error_code) { } TEST(os_test, format_windows_error) { - LPWSTR message = 0; + LPWSTR message = nullptr; auto result = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - 0, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message), 0, 0); + nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, nullptr); fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2)); LocalFree(message); fmt::memory_buffer actual_message; @@ -97,17 +97,17 @@ TEST(os_test, format_windows_error) { } TEST(os_test, format_long_windows_error) { - LPWSTR message = 0; + LPWSTR message = nullptr; // this error code is not available on all Windows platforms and // Windows SDKs, so do not fail the test if the error string cannot // be retrieved. int provisioning_not_allowed = 0x80284013L; // TBS_E_PROVISIONING_NOT_ALLOWED - auto result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - 0, static_cast(provisioning_not_allowed), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message), 0, 0); + auto result = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, static_cast(provisioning_not_allowed), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), 0, nullptr); if (result == 0) { LocalFree(message); return; @@ -336,6 +336,14 @@ TEST(ostream_test, truncate) { EXPECT_EQ("foo", read(in, 4)); } +TEST(ostream_test, flush) { + auto out = fmt::output_file("test-file"); + out.print("x"); + out.flush(); + auto in = fmt::file("test-file", file::RDONLY); + EXPECT_READ(in, "x"); +} + TEST(file_test, default_ctor) { file f; EXPECT_EQ(-1, f.descriptor()); @@ -540,13 +548,4 @@ TEST(file_test, fdopen) { int read_fd = read_end.descriptor(); EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get()))); } - -# ifdef FMT_LOCALE -TEST(locale_test, strtod) { - fmt::locale loc; - const char *start = "4.2", *ptr = start; - EXPECT_EQ(4.2, loc.strtod(ptr)); - EXPECT_EQ(start + 3, ptr); -} -# endif #endif // FMT_USE_FCNTL diff --git a/test/ostream-test.cc b/test/ostream-test.cc index 19105f45..f81039e5 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -23,6 +23,7 @@ template <> struct formatter : formatter { #include +#include "fmt/compile.h" #include "fmt/ostream.h" #include "fmt/ranges.h" #include "gmock/gmock.h" @@ -69,16 +70,16 @@ TEST(ostream_test, format_specs) { EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def"))); EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def"))); EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def"))); - EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), test_string()), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), test_string()), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), test_string()), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), test_string()), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), test_string()), format_error, - "format specifier requires numeric argument"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()), + format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()), + format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()), + format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()), + format_error, "format specifier requires numeric argument"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()), + format_error, "format specifier requires numeric argument"); EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test"))); EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13)); EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test"))); @@ -115,12 +116,12 @@ TEST(ostream_test, write_to_ostream_max_size) { struct test_buffer final : fmt::detail::buffer { explicit test_buffer(size_t size) : fmt::detail::buffer(nullptr, size, size) {} - void grow(size_t) {} + void grow(size_t) override {} } buffer(max_size); struct mock_streambuf : std::streambuf { MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n)); - std::streamsize xsputn(const char* s, std::streamsize n) { + std::streamsize xsputn(const char* s, std::streamsize n) override { const void* v = s; return xsputn(v, n); } @@ -257,7 +258,8 @@ std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) { } TEST(ostream_test, format_convertible_to_bool) { - EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool())); + // operator<< is intentionally not used because of potential ODR violations. + EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true"); } struct copyfmt_test {}; @@ -280,3 +282,20 @@ TEST(ostream_test, range) { auto strs = std::vector{test_string("foo"), test_string("bar")}; EXPECT_EQ("[foo, bar]", fmt::format("{}", strs)); } + +struct abstract { + virtual ~abstract() = default; + virtual void f() = 0; + friend std::ostream& operator<<(std::ostream& os, const abstract&) { + return os; + } +}; + +void format_abstract_compiles(const abstract& a) { + fmt::format(FMT_COMPILE("{}"), a); +} + +TEST(ostream_test, is_formattable) { + EXPECT_TRUE(fmt::is_formattable()); + EXPECT_TRUE(fmt::is_formattable>()); +} diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index 191d7aef..255e216c 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -467,9 +467,6 @@ struct locale_mock { MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale, locale_type base)); MOCK_METHOD1(freelocale, void(locale_type locale)); - - MOCK_METHOD3(strtod_l, - double(const char* nptr, char** endptr, locale_type locale)); } * locale_mock::instance; # ifdef _MSC_VER @@ -487,10 +484,6 @@ _locale_t _create_locale(int category, const char* locale) { void _free_locale(_locale_t locale) { locale_mock::instance->freelocale(locale); } - -double _strtod_l(const char* nptr, char** endptr, _locale_t locale) { - return locale_mock::instance->strtod_l(nptr, endptr, locale); -} # ifdef __clang__ # pragma clang diagnostic pop # endif @@ -516,11 +509,6 @@ FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW { return FreeLocaleResult(); } -double strtod_l(const char* nptr, char** endptr, - locale_type locale) FMT_LOCALE_THROW { - return locale_mock::instance->strtod_l(nptr, endptr, locale); -} - # undef FMT_LOCALE_THROW # ifndef _WIN32 @@ -549,18 +537,4 @@ TEST(locale_test, locale) { EXPECT_EQ(impl, loc.get()); } -TEST(locale_test, strtod) { - scoped_mock mock; - EXPECT_CALL(mock, newlocale(_, _, _)) - .WillOnce(Return(reinterpret_cast(42))); - EXPECT_CALL(mock, freelocale(_)); - fmt::locale loc; - const char* str = "4.2"; - char end = 'x'; - EXPECT_CALL(mock, strtod_l(str, _, loc.get())) - .WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777))); - EXPECT_EQ(777, loc.strtod(str)); - EXPECT_EQ(&end, str); -} - #endif // FMT_LOCALE diff --git a/test/printf-test.cc b/test/printf-test.cc index e22e0b9d..0bb9ccda 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -481,12 +481,6 @@ TEST(printf_test, string) { EXPECT_PRINTF(L" (null)", L"%10s", null_wstr); } -TEST(printf_test, uchar_string) { - unsigned char str[] = "test"; - unsigned char* pstr = str; - EXPECT_EQ("test", fmt::sprintf("%s", pstr)); -} - TEST(printf_test, pointer) { int n; void* p = &n; diff --git a/test/ranges-odr-test.cc b/test/ranges-odr-test.cc new file mode 100644 index 00000000..031354d3 --- /dev/null +++ b/test/ranges-odr-test.cc @@ -0,0 +1,17 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include + +#include "fmt/ranges.h" +#include "gtest/gtest.h" + +// call fmt::format from another translation unit to test ODR +TEST(ranges_odr_test, format_vector) { + auto v = std::vector{1, 2, 3, 5, 7, 11}; + EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]"); +} diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 4932fe21..63cb8c8b 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -55,7 +55,12 @@ TEST(ranges_test, format_vector2) { TEST(ranges_test, format_map) { auto m = std::map{{"one", 1}, {"two", 2}}; - EXPECT_EQ(fmt::format("{}", m), "[(\"one\", 1), (\"two\", 2)]"); + EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}"); +} + +TEST(ranges_test, format_set) { + EXPECT_EQ(fmt::format("{}", std::set{"one", "two"}), + "{\"one\", \"two\"}"); } TEST(ranges_test, format_pair) { @@ -190,7 +195,14 @@ TEST(ranges_test, range) { EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]"); } -#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 +enum test_enum { foo }; + +TEST(ranges_test, enum_range) { + auto v = std::vector{test_enum::foo}; + EXPECT_EQ(fmt::format("{}", v), "[0]"); +} + +#if !FMT_MSC_VER struct unformattable {}; TEST(ranges_test, unformattable_range) { @@ -217,6 +229,21 @@ TEST(ranges_test, join_tuple) { // Single element tuple. auto t4 = std::tuple(4.0f); EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); + +# if FMT_TUPLE_JOIN_SPECIFIERS + // Specs applied to each element. + auto t5 = std::tuple(-3, 100, 1); + EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01"); + + auto t6 = std::tuple(3, 3.14, 3.1415); + EXPECT_EQ(fmt::format("{:5.5f}", fmt::join(t6, ", ")), + "3.00000, 3.14000, 3.14150"); + + // Testing lvalue tuple args. + int y = -1; + auto t7 = std::tuple(3, y, y); + EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01"); +# endif } TEST(ranges_test, join_initializer_list) { @@ -236,6 +263,36 @@ struct zstring { zstring_sentinel end() const { return {}; } }; +# ifdef __cpp_lib_ranges +struct cpp20_only_range { + struct iterator { + int val = 0; + + using value_type = int; + using difference_type = std::ptrdiff_t; + using iterator_concept = std::input_iterator_tag; + + iterator() = default; + iterator(int i) : val(i) {} + int operator*() const { return val; } + iterator& operator++() { + ++val; + return *this; + } + void operator++(int) { ++*this; } + bool operator==(const iterator& rhs) const { return val == rhs.val; } + }; + + int lo; + int hi; + + iterator begin() const { return iterator(lo); } + iterator end() const { return iterator(hi); } +}; + +static_assert(std::input_iterator); +# endif + TEST(ranges_test, join_sentinel) { auto hello = zstring{"hello"}; EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']"); @@ -260,5 +317,47 @@ TEST(ranges_test, join_range) { const auto z = std::vector(3u, 0); EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0"); + +# ifdef __cpp_lib_ranges + EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}), + "[0, 1, 2, 3, 4]"); + EXPECT_EQ( + fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")), + "0,1,2,3,4"); +# endif } #endif // FMT_RANGES_TEST_ENABLE_JOIN + +TEST(ranges_test, is_printable) { + using fmt::detail::is_printable; + EXPECT_TRUE(is_printable(0x0323)); + EXPECT_FALSE(is_printable(0x0378)); + EXPECT_FALSE(is_printable(0x110000)); +} + +TEST(ranges_test, escape_string) { + using vec = std::vector; + EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]"); + EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]"); + EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]"); + EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]"); + + if (fmt::detail::is_utf8()) { + EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]"); + // Unassigned Unicode code points. + EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]"); + EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}), + "[\"\\xf4\\x8f\\xbf\\xc0\"]"); + } +} + +#ifdef FMT_USE_STRING_VIEW +struct convertible_to_string_view { + operator std::string_view() const { return "foo"; } +}; + +TEST(ranges_test, escape_convertible_to_string_view) { + EXPECT_EQ(fmt::format("{}", std::vector(1)), + "[\"foo\"]"); +} +#endif // FMT_USE_STRING_VIEW diff --git a/test/std-format-test.cc b/test/std-format-test.cc deleted file mode 100644 index c67a2a03..00000000 --- a/test/std-format-test.cc +++ /dev/null @@ -1,161 +0,0 @@ -#include - -#include "gtest/gtest.h" - -TEST(std_format_test, escaping) { - using namespace std; - string s = format("{0}-{{", 8); // s == "8-{" - EXPECT_EQ(s, "8-{"); -} - -TEST(std_format_test, indexing) { - using namespace std; - string s0 = format("{} to {}", "a", "b"); // OK: automatic indexing - string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing - EXPECT_EQ(s0, "a to b"); - EXPECT_EQ(s1, "b to a"); - // Error: mixing automatic and manual indexing - EXPECT_THROW(string s2 = format("{0} to {}", "a", "b"), std::format_error); - // Error: mixing automatic and manual indexing - EXPECT_THROW(string s3 = format("{} to {1}", "a", "b"), std::format_error); -} - -TEST(std_format_test, alignment) { - using namespace std; - char c = 120; - string s0 = format("{:6}", 42); // s0 == " 42" - string s1 = format("{:6}", 'x'); // s1 == "x " - string s2 = format("{:*<6}", 'x'); // s2 == "x*****" - string s3 = format("{:*>6}", 'x'); // s3 == "*****x" - string s4 = format("{:*^6}", 'x'); // s4 == "**x***" - // 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("{: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, "true "); -} - -TEST(std_format_test, float) { - using namespace std; - double inf = numeric_limits::infinity(); - double nan = numeric_limits::quiet_NaN(); - string s0 = format("{0:} {0:+} {0:-} {0: }", 1); // s0 == "1 +1 1 1" - string s1 = format("{0:} {0:+} {0:-} {0: }", -1); // s1 == "-1 -1 -1 -1" - string s2 = - format("{0:} {0:+} {0:-} {0: }", inf); // s2 == "inf +inf inf inf" - string s3 = - format("{0:} {0:+} {0:-} {0: }", nan); // s3 == "nan +nan nan nan" - EXPECT_EQ(s0, "1 +1 1 1"); - EXPECT_EQ(s1, "-1 -1 -1 -1"); - EXPECT_EQ(s2, "inf +inf inf inf"); - EXPECT_EQ(s3, "nan +nan nan nan"); -} - -TEST(std_format_test, int) { - using namespace std; - string s0 = format("{}", 42); // s0 == "42" - string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a" - string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A" - string s3 = format("{:L}", 1234); // s3 == "1234" (depends on the locale) - EXPECT_EQ(s0, "42"); - EXPECT_EQ(s1, "101010 42 52 2a"); - EXPECT_EQ(s2, "0x2a 0X2A"); - EXPECT_EQ(s3, "1234"); -} - -#include - -enum color { red, green, blue }; - -const char* color_names[] = {"red", "green", "blue"}; - -template <> struct std::formatter : std::formatter { - auto format(color c, format_context& ctx) { - return formatter::format(color_names[c], ctx); - } -}; - -struct err {}; - -TEST(std_format_test, formatter) { - std::string s0 = std::format("{}", 42); // OK: library-provided formatter - // std::string s1 = std::format("{}", L"foo"); // Ill-formed: disabled - // formatter - std::string s2 = std::format("{}", red); // OK: user-provided formatter - // std::string s3 = std::format("{}", err{}); // Ill-formed: disabled - // formatter - EXPECT_EQ(s0, "42"); - EXPECT_EQ(s2, "red"); -} - -struct S { - int value; -}; - -template <> struct std::formatter { - size_t width_arg_id = 0; - - // Parses a width argument id in the format { }. - constexpr auto parse(format_parse_context& ctx) { - constexpr auto is_ascii_digit = [](const char c) { - return c >= '0' && c <= '9'; - }; - - auto iter = ctx.begin(); - // auto get_char = [&]() { return iter != ctx.end() ? *iter : 0; }; - auto get_char = [&]() { return iter != ctx.end() ? *iter : '\0'; }; - if (get_char() != '{') return iter; - ++iter; - char c = get_char(); - if (!is_ascii_digit(c) || (++iter, get_char()) != '}') - throw format_error("invalid format"); - width_arg_id = fmt::detail::to_unsigned(c - '0'); - ctx.check_arg_id(width_arg_id); - return ++iter; - } - - // Formats S with width given by the argument width_arg_id. - auto format(S s, format_context& ctx) { - int width = visit_format_arg( - [](auto value) -> int { - using type = decltype(value); - if constexpr (!is_integral_v || is_same_v) - throw format_error("width is not integral"); - // else if (value < 0 || value > numeric_limits::max()) - else if (fmt::detail::is_negative(value) || - value > numeric_limits::max()) - throw format_error("invalid width"); - else - return static_cast(value); - }, - ctx.arg(width_arg_id)); - return format_to(ctx.out(), "{0:{1}}", s.value, width); - } -}; - -TEST(std_format_test, parsing) { - std::string s = std::format("{0:{1}}", S{42}, 10); // s == " 42" - EXPECT_EQ(s, " 42"); -} - -#if FMT_USE_INT128 -template <> struct std::formatter<__int128_t> : std::formatter { - auto format(__int128_t n, format_context& ctx) { - // Format as a long long since we only want to check if it is possible to - // specialize formatter for __int128_t. - return formatter::format(static_cast(n), ctx); - } -}; - -TEST(std_format_test, int128) { - __int128_t n = 42; - auto s = std::format("{}", n); - EXPECT_EQ(s, "42"); -} -#endif // FMT_USE_INT128 diff --git a/test/test-main.cc b/test/test-main.cc index b69cc500..268ed086 100644 --- a/test/test-main.cc +++ b/test/test-main.cc @@ -35,6 +35,7 @@ int main(int argc, char** argv) { _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); try { testing::InitGoogleTest(&argc, argv); + testing::FLAGS_gtest_death_test_style = "threadsafe"; return RUN_ALL_TESTS(); } catch (...) { // Catch all exceptions to make Coverity happy. diff --git a/test/unicode-test.cc b/test/unicode-test.cc index 73cac58c..63c828dd 100644 --- a/test/unicode-test.cc +++ b/test/unicode-test.cc @@ -18,7 +18,7 @@ using testing::Contains; TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); } TEST(unicode_test, legacy_locale) { - auto loc = get_locale("ru_RU.CP1251", "Russian.1251"); + auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251"); if (loc == std::locale::classic()) return; auto s = std::string(); diff --git a/test/util.h b/test/util.h index 8a5cbcc1..2e58ad95 100644 --- a/test/util.h +++ b/test/util.h @@ -10,7 +10,11 @@ #include #include -#include "fmt/os.h" +#ifdef FMT_MODULE_TEST +import fmt; +#else +# include "fmt/os.h" +#endif // FMT_MODULE_TEST #ifdef _MSC_VER # define FMT_VSNPRINTF vsprintf_s @@ -34,7 +38,7 @@ fmt::buffered_file open_buffered_file(FILE** fp = nullptr); inline FILE* safe_fopen(const char* filename, const char* mode) { #if defined(_WIN32) && !defined(__MINGW32__) // Fix MSVC warning about "unsafe" fopen. - FILE* f = 0; + FILE* f = nullptr; errno = fopen_s(&f, filename, mode); return f; #else diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 78ecb2c7..346cd212 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -8,14 +8,18 @@ #include "fmt/xchar.h" #include +#include +#include #include "fmt/chrono.h" #include "fmt/color.h" #include "fmt/ostream.h" #include "fmt/ranges.h" -#include "gtest/gtest.h" +#include "gtest-extra.h" // Contains +#include "util.h" // get_locale using fmt::detail::max_value; +using testing::Contains; namespace test_ns { template class test_string { @@ -87,6 +91,10 @@ TEST(xchar_test, format) { EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); } +TEST(xchar_test, is_formattable) { + static_assert(!fmt::is_formattable::value, ""); +} + TEST(xchar_test, compile_time_string) { #if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); @@ -257,6 +265,55 @@ TEST(xchar_test, chrono) { EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), "The date is 2016-04-25 11:22:33."); EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); + EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25"); + EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33"); +} + +std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr, + std::locale* locptr = nullptr) { + auto loc = locptr ? *locptr : std::locale::classic(); + auto& facet = std::use_facet>(loc); + std::wostringstream os; + os.imbue(loc); + facet.put(os, os, L' ', timeptr, format.c_str(), + format.c_str() + format.size()); +#ifdef _WIN32 + // Workaround a bug in older versions of Universal CRT. + auto str = os.str(); + if (str == L"-0000") str = L"+0000"; + return str; +#else + return os.str(); +#endif +} + +TEST(chrono_test, time_point) { + auto t1 = std::chrono::system_clock::now(); + + std::vector spec_list = { + L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C", + L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U", + L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e", + L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH", + L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X", + L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"}; + spec_list.push_back(L"%Y-%m-%d %H:%M:%S"); +#ifndef _WIN32 + // Disabled on Windows, because these formats is not consistent among + // platforms. + spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"}); +#endif + + for (const auto& spec : spec_list) { + auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::localtime(&t); + + auto sys_output = system_wcsftime(spec, &tm); + + auto fmt_spec = fmt::format(L"{{:{}}}", spec); + EXPECT_EQ(sys_output, fmt::format(fmt_spec, t1)); + EXPECT_EQ(sys_output, fmt::format(fmt_spec, tm)); + } } TEST(xchar_test, color) { @@ -301,10 +358,12 @@ template struct small_grouping : std::numpunct { Char do_thousands_sep() const override { return ','; } }; -TEST(locale_test, double_decimal_point) { +TEST(locale_test, localized_double) { auto loc = std::locale(std::locale(), new numpunct()); EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23)); EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23)); + EXPECT_EQ("1~234?5", fmt::format(loc, "{:L}", 1234.5)); + EXPECT_EQ("12~000", fmt::format(loc, "{:L}", 12000.0)); } TEST(locale_test, format) { @@ -403,7 +462,7 @@ template struct formatter, charT> { specs_.precision, specs_.precision_ref, ctx); auto specs = std::string(); if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision); - if (specs_.type) specs += specs_.type; + if (specs_.type == presentation_type::fixed_lower) specs += 'f'; auto real = fmt::format(ctx.locale().template get(), fmt::runtime("{:" + specs + "}"), c.real()); auto imag = fmt::format(ctx.locale().template get(), @@ -424,4 +483,20 @@ TEST(locale_test, complex) { EXPECT_EQ(fmt::format("{:8}", std::complex(1, 2)), " (1+2i)"); } +TEST(locale_test, chrono_weekday) { + auto loc = get_locale("ru_RU.UTF-8", "Russian_Russia.1251"); + auto loc_old = std::locale::global(loc); + auto mon = fmt::weekday(1); + EXPECT_EQ(fmt::format(L"{}", mon), L"Mon"); + if (loc != std::locale::classic()) { + // {L"\x43F\x43D", L"\x41F\x43D", L"\x43F\x43D\x434", L"\x41F\x43D\x434"} + // {L"пн", L"Пн", L"пнд", L"Пнд"} + EXPECT_THAT( + (std::vector{L"\x43F\x43D", L"\x41F\x43D", + L"\x43F\x43D\x434", L"\x41F\x43D\x434"}), + Contains(fmt::format(loc, L"{:L}", mon))); + } + std::locale::global(loc_old); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR