From 6633089a44b13022ddb37a44229c9d9a88a9096f Mon Sep 17 00:00:00 2001 From: Merry Date: Tue, 15 Feb 2022 11:15:34 +0000 Subject: [PATCH] Squashed 'externals/fmt/' changes from 9e8b86fd2..b6f4ceaed b6f4ceaed Update version 15f812dae Update changelog 6884aab49 Update changelog 88ec4e706 Bump version dd3d2490e Update changelog 739055ae7 Fix apidocs dbbd711f4 Suppress a warning 98cbb6a43 Fix ABI compatiblity issue 214cf13f1 Fix endianness bug in write_digit2_separated (#2699) 17a5c808d Restore FMT_API on error_handler::on_error() (#2696) fc1783fcc Avoid undefined symbols with mingw-w64 (#2692) 1b193e7b3 Deprecate more 8e59744b8 Switch to new github auth mechanism 7081a6aa3 Update version 64dc8fbad Bump version fc8e3de7d Fix manage.py script 57bee9fcd Fix formating dce52e491 Update changelog 9405a4724 Update changelog 495b8bf12 Update changelog e221166fa Update changelog 035cab8da Update changelog 89c6ed12b Clarify in comments (for now) deprecated map functions e462da828 Add some noexcept (#2684) 79c66d66b Update changelog 5d37f705f Update changelog 6bb370cec Update changelog bb6920157 Fix tuple join 4fac7daae Cleanup bit_cast 3617c2795 Update changelog 9c0c1bcdb Simplify tuple formatting 187e8db1b Update changelog c7f88180f add tests for format string compile-time checks 8a2c3fb88 add reverse tests to compile-error-test 1164eda5a disable compile-error-test on Windows 4482f6f1f rewrite compile-error-test to use non-header-only library 796662a61 Escape range items convertible to std::string_view 33ee4cc51 Improve noexception test 3bbf2c673 Fix throw with exceptions disabled 074c9c52e Update changelog 3110ec5a2 Update changelog 3014b3d77 Clarify that C strings must be null-terminated eab2ea9fc Replace an assert with an exception 21ed92a6e Update changelog 04111dd1e Fix issue #2670 (#2671) 817788fbf remove incorrect C++20 check from test/CMakeLists.txt (#2663) 4511030af Minor code style tweaks for consistency 7812813a3 Don't explicitly delete copy ctor of dynamic_format_arg_store (#2664) 664cd6067 Remove std-format-test 784e2a7b4 Fix an overflow when formatting very large durations fc2a376d8 Remove two expressions which had no effect (reported by LGTM) c5aafd8f9 expose headers as SYSTEM depending on special configuration option eaddd1e3c Fix handling of byte 2d4457758 Try fixing byte regression e46392ea2 deprecate _format UDL in code using FMT_DEPRECATED c882790a2 Add a set formatter 121002d70 Add a map formatter be51ee1ce Disable broken copy ctor of dynamic_format_arg_store 659de779e Fix a UB in parse_format_specs when begin is null 51b14b6c0 remove commented out lines 223a0fa55 move gtest-specific check into gtest/CMakeLists.txt ef72b471f enable named arguments check in compile-time checks (#2649) 82246b876 fix throw with exceptions disabled (#2647) 35f60377a Update ChangeLog.rst 3a951a66c Avoid qualifying by inline namespace. Fixes #2642. (#2643) e0136fc8b Qualify calls to make_wformat_args. Fixes #2639. (#2641) ac1b5f3da Refactor problematic trailing returns in arg_mapper fd62fba98 Don't convert scoped enums to integers c652f8243 Make header guard consistent with header name a9c7b9b8f Clarify that _format is deprecated e4f0564aa Disable is_streamable for string[_view] 91533d3c3 Minor tweaks to chrono subsecond formatting 0bbc9708f Implement c++20 std::chrono::duration subsecond formatting (#2623) 9d5b9defd Enable tzset only on Windows desktop app (#2633) 215f21a03 Detect overflow on large precision c240d98ff Optimize tm formatting (Non C-locales and %Z) (#2617) 6ab73113f Mark grow as FMT_CONSTEXPR20 (#2630) 713c7c7c6 Cleanup os.cc 9b1807a8a fix int -> uint warning (#2611) ec3b097cb [doc] FMT_STRING supports C++14 and no-op in C++11 (#2620) c472a2781 Fix handling of very large precision in fixed format 201971e29 Make MSVC use [[nodiscard]] (#2615) acad8cfab Reformat all source code; no functional changes 491ba2dda Annotate fmt::format and fmt::formatted_size as [[nodiscard]] 5abe9e826 Add platform-specific 'z' formatter be3a3a5ae Use predefined formats for C-locale a3ab36c80 Formatting of function pointers, member function pointers, member object pointers... (#2610) 19cac63fe Broken link in README.rst 43419a4ad Workaround a bug in gcc c089f7d49 Simplify std::tm formatter aa5517f6b Reuse tm_writer in chrono_formatter 50140be7a Reuse tm_writer in weekday formatter 8b8945499 Improve consistency 5380ff4d8 Detect types convertible to unformattable pointers 094b66e81 changed locale retrieval way to a fancy one b69ae4854 Reorder classes (#2591) 0b843af56 sped up chrono.h formatting for cases without providing locale (#2576) 12b1d8b14 Fix precision 0 with std::chrono::duration and added additional tests. (#2588) e67f92c55 Cleanup warnings with nvhpc/21.9. (#2582) 812733cc9 const qualify format function for systen_clock 028f22775 Handle implicit conversions in write 5b0aa638c Minor grammar fix 6eaceb5f7 Fix incompatible between docutils 1.18.0 and sphinx 3.3.0 (#2575) 0697c5edb FMT_USE_FCNTL can be predefined (#2573) 1031eedf2 Replacing strftime with std::time_put (#2550) 90034e4c4 Add FMT_ASSERT and validation of values of struct tm members (#2564) df40e9467 Upgrade `module-test` to msvc 16.11.5 and 17.0-pre5 (#2558) e6d5059cb Simplify js tag in basic-bootstrap theme (#2562) 3b6e409cd Enable `consteval` for msvc 17.0-pre5 (#2559) 249f03bbb do not detect LLVM based IBMXL compiler (on ppc) as clang (#2555) 7463c8320 Fix overflow for very bigger years (>2*10^9) (#2551) 1266c2b60 Fix handling of exotic character types 684e2fdc9 Minor cleanup a1d586302 Minor cleanup 7a604cdd9 Cleanup aeb54b0dd Fix bug on '%Y' and '%C' formats with negative years Requested changes f88c020fc Generalization of strftime/wcsftime function calls in tests 2eeddba75 Renaming, splitting of functions 275454608 Fix errors in ISO week-base-year formatter 218cecb6d Fix error in test e9f4453b0 Fix Microsoft Visual Studio 14.0 build 27c3674ce Improve performance 5dc3dd3d4 New tests f8542cd98 Unified formatters for std::chrono::time_point and std::tm 4707373d3 Fix year formatter 79c00ad8f Improve ISO week-base-year formatter fbaaa5906 Improve week of the year formatter cde44ddb7 Improve year formatter b04601b91 Switch from std::strftime/std::wcsftime to internal implementation for locale independent formats d3d30a46f New tests 7911d8d3f Add format spec checker fbbfc3b03 Reorder formatters 509eac957 Workarounds for implementation-defined std::strftime behavior 85b38190d New tests for all C++11 std::strftime format specifiers 7aca36bca Extending fmt::join to support C++20-only ranges. (#2549) f5371a75f locale.h -> format.h febdef43f fix: add workaround for intel parameter pack bug f56756986 fix: check to make sure both 'if constexpr' and return type deduction are available dcd282bb2 Namespace qualify calls to get 9c14474d3 Include `` when using `std::bit_cast` 1e96e0176 Fix compiler flag check (#2540) 7e4bc9451 Speeding up write_significand() (#2499) 26c1ca4c3 Replaced default spec with equivalent one, which is potentially more optimizable (#2537) 1e865b353 Fix docs 4a85db1ce Change default open mode to -rw-r--r-- (#2530) 0a985fd4c Move size_ initialization to initializer list (#2529) 012cc709d Workaround gcc _Pragma bug 59884 d6590e3bd Fix compiler check 134aec40f Fix search in docs 48a476ae0 Update example (#2522) 023c2018f Don't use strlen in constexpr 800d4c8ac Refactor Windows workarounds 32865aeaa changed detection of Intel Compiler Classic to distinguish MS-Windows (#2510) 7b339795a Describe a better approach of how to use {fmt} as a depency in a Bazel project (#2516) ae9bbe116 Suppress warning C4127 in chrono.h (conditional expression is constant) (#2518) 927dbd134 Misplaced comma in README.rst (#2515) 2a9a77dd8 Remove misplaced comment 1aee4bc90 Refactor FP formatting e1bd6cc91 Refactor FP formatting 027fcaf05 Replace use_grisu with fallback since Grisu is only one of multiple implemented algorithms 716d69f27 Refactor FP formatting ff7e73af6 Always run grisu_gen_digits before fallback_format 2976e31ac Refactor format_float 807ee5ec3 Disable consteval in Apple clang d9a731d48 Add basic support for Bazel (#2505) 9c57357e0 Add `static` to a table (#2509) 2742611ca Fix formatting 5092b198b Document group_digits b4d9d82e1 make FP formatting available to be used at compile-time (#2426) d9fd695ac Fix wchar_t tm formatting 92614ecbf Optimize %T in tm formatting aaeca12d8 Move FMT_MAYBE_UNUSED to format.h where it is used 3d0c7ae38 Move data to format.cc 04e3a79f7 Use memcpy in more cases in copy2 e47e99bb0 Simplify format_decimal (#2498) 9b6b0e403 Remove data 4d1c6034e Deprecate basic_data a3348eccd Deprecate most of basic_data 3a0448148 Remove data::hex_digits ad77331c0 Move log10_2_significand to format-inl.h d9ebc4e82 Add a function to get sign char c00eb4f4c Add missing inline 25af02f21 positive -> nonnegative (#2493) 67cb2dad3 Optimize %F in tm formatting 1aa98f8b9 Eliminate double copying in vformat_to_n (#2489) a58c13382 Improve code_point_length codegen on older gcc aeee70a81 Remove unnecessary cast c771ba361 Fix build for the clang-10 / libstdc++-9 couple (#2491) ab6e2272c Clarify shifts encoding e4728409e Use throw_format_error in more places to reduce bloat e3ebf366a Inline padding shifts 894faf3fe Refactor presentation types 4eb97fa4e Reduce code bloat 6b55c8325 is_const_formattable -> has_const_formatter 2fe94ad7e Make specifiers support in tuple_join an opt-in 3940de595 thousands -> group_digits c4d0f96a6 Implement format specs in fmt::thousands 3b9c44268 Implement thousands separators without locales 08f98c7fa Simplify get_arg_index_by_name a151f955a Remove FMT_OVERRIDE 42a225cbd Remove redundand final bf20d1990 Simplify the core API fc0884037 Move FMT_GCC_VISIBILITY_HIDDEN to format.h 1aeed2dbc Require inline namespaces 799bea473 Remove FMT_HAS_GXX_CXX11 60cd5ea3f Add support for more formattable types in ranges 4fd9a00f3 Simplify ostream interface 568156389 Cleanup ostream interface 20931baf1 Disable fallback_formatter for arrays d58d19ba3 Fix an odr violation in ranges.h (#2483) ee0659f8b Fix formatting of abstract classes via ostream 8029bf955 Fix copy_str performance (#2477) 2520f410c Workaround for #2478 (#2482) ee63f5f04 Workaround to MSVC bug (#2474) (#2476) 1aaf72fb6 Add an example to fmt::runtime c1313c205 Clarify that format_to[_n] do not append a terminating null cb0f177c3 Improve docs 71677e520 Improve docs 4db572352 add fuzzers for chrono timepoint and localtime,gmtime (#2469) dc7f3ef2b Fix header name 419ba86a9 Improve docs 6a5b4d5fa Document format_string 2599163b8 Document format_string 8ef22f774 Update docs c0c4d1ada Update docs 729a44e67 Depreate strtod and remove problematic tests 74c111896 Apply force inline 596508a92 Cleanup 043e3b342 Remove static_assert from arg_mapper 8b0cb944d Fix error reporting when mixing character types 117fc6707 CI: replace g++ C++20 build to test FP formatting at compile-time c79a3841e make detail::fp and detail::bigit constexpr 5888de9f3 make detail::make_checked() constexpr 04b4b69b1 make detail::bit_cast() constexpr with C++20 fd34a3d24 make detail::basic_memory_buffer constexpr with C++20 6d597e39c Fix overload ambiguity in arg_mapper b9ce56d93 Improve comments f889e52a1 Improve error reporting 34caecd6b Use consistent initialization style a44c8f651 reimplement `formatter` (#2457) 4b8bda25c Fix 2462 6b5e6119e set clang in one place 7af1dc1d2 fix UB in fuzzer common (memcpy on nullptr) e77686f7a clang format 2207ea0b3 More escaping a212ff757 Escape invalid code points a76031e11 check -> is_printable a7f280765 Improve naming 07d033ecb Fix is_printable cdb4299ac Add Unicode support to is_printable 7df2c82a8 Rewrite printable.py codegen to emit C++ 6cf90d7ce Add script license and fix python version 2f1ad8ed3 Add printable codegen from Rust 371d8e2ee Escape Unicode 6397095ca More escaping f69a57253 Don't overescape wide strings 11b07a56b We should escape b559cfd4c Implement basic escaping 11d49491c Handle global locale 6ea6cf946 Add decimal separator support to float 9730a2af0 Update ChangeLog.rst c2ed5f686 Update ChangeLog.rst 7b66e72e2 Use builtin intrinsics on intel (#2450) d57b2a652 Suppress a warning bdfbd794e Cleanup begin/end usage 111de881f Don't copy non-const-iterable ranges d6e882ed8 Undo the move because the doc is not a GH template f488eed10 Resolve default constructor error in Xcode 7.2.1 and 8.2.1 652c3653b Move CONTRIBUTING.md to .github fb19faa31 Improves README with svg badge (#2446) 07211701f Disable the -Wstringop-overflow warning from GCC 7 (#2442) bba0a9d96 Make flush public f1794a885 Switch to threadsafe death test style 0544a2279 Exclude fallback functions when FMT_BUILTIN_CLZ(LL) is not defined (#2434) 5c222f056 Add support for nonconst formattable types 3def950b8 Set FMT_CAN_MODULE=OFF for MSVC 19.29.30035+ 63fe2d5bd Add copy constructor for dynamic_format_arg_store, and test 561834650 Improve digit count f20f50368 Replace `throw` with `FMT_THROW` (#2427) 00235d8a9 fix module test odr violations (#2414) 2038bf618 Update format_to usage e41ac1f87 Don't use deprecated API in docs 8465869d7 Move ignore_unused to detail 3d53d1539 Warning removals in test code (#2399) 20e4ef8b4 Pass significand_size by value c4a3c2342 Refactor locale handling 7a0d30175 Update README.rst f2b03facd Include test 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. (#2356) 02ad5e11d Add faint, blink, reverse and conceal to the emphases (#2394) d141cdbeb Update version cfc05e05f Bump version 8ea312633 Update changelog e461f3dbb Minor consitency and comment tweaks 54014e42e silence warning C4100 on MSVC 2019 when exceptions are disabled (#2397) 3e7a29cc9 Workaround clang/gcc incompatibility 00a57a9f8 Update changelog 1d7384530 Add missing presentation type checks for std::string (#2402) 889bbf27a Fix missing std::get overload in MSVC (#2407) 5f8473914 Remove outdated apidoc 785908ee3 Fix warnings fbb70eec5 suppress unused variable warnings (#2381) 002bb759f Remove unneeded `num_result_bigits` decrement a3f762c5a [doc] Minor: fix ``code``. c3c27e5ab Fix MSVC warning C4819 c6b1f181a Fix docs 94564b058 Fix docs 0fc73a2a8 Merge branch 'master' of github.com:fmtlib/fmt 3156fcf5f Switch to older breathe version f85fb9fdf Adjust definition for FMT_HAS_INCLUDE 0bc3d664e Fix docs e5c46e13e Fix docs: breathe 18 and earlier corrupts trailing return type 49a3b58c8 Specify size for static data arrays d0c8d45a2 apt update before install c9a10631c format: do not use udl_{arg,formatter} return types when UDL is not in use 3bd806f12 Eliminate intel compiler warning fd16bcb20 Fix bug in cmake join function 5221242f6 Instruct msvc to report the _true_ value in `__cplusplus` and force _full_ C++ conformance 31a5f0d39 Bump version in inline namespace 102a4d492 Bump version in inline namespace f68508b6c Update ChangeLog.rst git-subtree-dir: externals/fmt git-subtree-split: b6f4ceaed0a0a24ccf575fab6c56dd50ccf6f1a9 --- .github/workflows/doc.yml | 1 + .github/workflows/linux.yml | 3 +- CMakeLists.txt | 17 +- ChangeLog.rst | 446 ++++++++- README.rst | 12 +- doc/api.rst | 46 +- doc/basic-bootstrap/layout.html | 4 +- doc/build.py | 9 +- doc/syntax.rst | 29 +- include/fmt/args.h | 2 + include/fmt/chrono.h | 1181 +++++++++++++++++++----- include/fmt/color.h | 37 +- include/fmt/compile.h | 25 +- include/fmt/core.h | 766 +++++++++------ include/fmt/format-inl.h | 594 ++++++------ include/fmt/format.h | 935 ++++++++++++------- include/fmt/os.h | 48 +- include/fmt/ostream.h | 108 +-- include/fmt/printf.h | 19 +- include/fmt/ranges.h | 546 ++++++++--- include/fmt/xchar.h | 12 +- src/fmt.cc | 1 - src/format.cc | 49 + src/os.cc | 17 +- support/bazel/.bazelrc | 1 + support/bazel/.bazelversion | 1 + support/bazel/BUILD.bazel | 29 + support/bazel/README.md | 73 ++ support/bazel/WORKSPACE.bazel | 1 + support/manage.py | 19 +- support/printable.py | 201 ++++ test/CMakeLists.txt | 61 +- test/args-test.cc | 35 +- test/chrono-test.cc | 318 ++++++- test/color-test.cc | 6 + test/compile-error-test/CMakeLists.txt | 214 ++++- test/compile-fp-test.cc | 62 ++ test/compile-test.cc | 19 +- test/core-test.cc | 118 ++- test/enforce-checks-test.cc | 19 +- test/find-package-test/main.cc | 3 +- test/format | 856 ----------------- test/format-impl-test.cc | 17 +- test/format-test.cc | 396 ++++---- test/fuzzing/CMakeLists.txt | 2 +- test/fuzzing/build.sh | 8 +- test/fuzzing/chrono-duration.cc | 11 +- test/fuzzing/chrono-timepoint.cc | 32 + test/fuzzing/float.cc | 10 +- test/fuzzing/fuzzer-common.h | 12 +- test/fuzzing/named-arg.cc | 8 +- test/fuzzing/one-arg.cc | 11 +- test/fuzzing/two-args.cc | 3 +- test/gtest-extra.cc | 7 +- test/gtest-extra.h | 8 +- test/gtest/CMakeLists.txt | 7 + test/header-only-test.cc | 4 + test/module-test.cc | 31 +- test/noexception-test.cc | 18 + test/os-test.cc | 45 +- test/ostream-test.cc | 45 +- test/posix-mock-test.cc | 26 - test/printf-test.cc | 6 - test/ranges-odr-test.cc | 17 + test/ranges-test.cc | 103 ++- test/std-format-test.cc | 161 ---- test/test-main.cc | 1 + test/unicode-test.cc | 2 +- test/util.h | 8 +- test/xchar-test.cc | 81 +- 70 files changed, 5141 insertions(+), 2882 deletions(-) create mode 100644 support/bazel/.bazelrc create mode 100644 support/bazel/.bazelversion create mode 100644 support/bazel/BUILD.bazel create mode 100644 support/bazel/README.md create mode 100644 support/bazel/WORKSPACE.bazel create mode 100755 support/printable.py create mode 100644 test/compile-fp-test.cc delete mode 100644 test/format create mode 100644 test/fuzzing/chrono-timepoint.cc create mode 100644 test/noexception-test.cc create mode 100644 test/ranges-odr-test.cc delete mode 100644 test/std-format-test.cc 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