From 3e6176d6f61247c57d4a657a490d983322a3dc90 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 23 Jun 2021 03:39:40 -0400 Subject: [PATCH] Squashed 'externals/fmt/' changes from cd4af11e..9e8b86fd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 9e8b86fd Update version 92fec0f0 Bump version 4749cc93 Update changelog 78a0ba0a Improve conversion of paragraphs 7a39837d Use a working breathe version 55b6e92d Fix docs 69dc3a85 Fix docs 27f4cdd5 Update changelog 70d61a0a Update changelog 427b5340 Add no_value state to value e421d527 Simplify error handling in parse_nonnegative_int a59678f3 Fix chrono_test.locale c98254c3 Install locales into CI c123a728 Fix set locale error in chrono formatter 3c8fad12 Optimize parse_nonnegative_int f28cf330 adding a default format for std::chrono::time_point xchar-test 206000a0 Workaround pathological conversion (#2343) 76ee4904 Move wchar/custom char overloads to xchar.h e77b22d6 Deprecate memory buffer overload of format_to 07039f4b Update README.rst 4678192c Remove bsr2log10 7c3d3dfa Update thousands_sep_impl signature ef826b86 Fix docs 5223f552 Remove FMT_ALWAYS_INLINE cfde93af Add FMT_STATIC_CONSTEXPR 986a5a6c Fixed join_view formatter for wchar_t 7c8b35ff fix MSVC Win32 count_digits 3eeb084e Optimize count_digits 2ac0bfe5 Improve handling of thousands separator 024741b4 CI: set up multi-thread build for all platforms f4c95f6d Improve handling of thousands separator d4fbeacc Fix docs build 0eef389d Code style e27b1ce5 Fix docs 9f8b6dac Fix wheel installation 6060bcfc Fix docs ff967346 Fix docs 1085cc21 Fix docs 11addaa1 Update docs 760ca5cc Update docs 290d3f8b Cleanup ranges API aa09e0f5 Update docs d142579e Cleanup the format API f286139d Fix "undefined reference to `fmt::v7::detail::basic_data::digits'" 7b9d69b8 Add xchar.h to docs cbd861f1 Update docs faf972f0 Update docs 622d1c04 Update changelog 634c9487 Update changelog a04e3a2d Comment 87876d54 Cleanup the printf implementation d338d663 Cleanup the printf implementation 272660e7 Remove deprecated printf functions 5a95c5ae Update changelog 70e67ae0 Re-enable module testing Prepare for compilation with gcc (modules branch). ad972589 Merge branch 'master' of github.com:fmtlib/fmt ed2a6377 Workaround msvc constexpr issues 99768695 fix custom types formatting at compile-time, add test 8c1b22ba Workaround a gcc 9.1 bug (#2334) 2dba1cfa Update changelog d7ba6c3e Use qualified name-lookup in module. (#2324) bf9904ee Workaround msvc bugs 577bce90 Apply clang-format ba4c7f19 Swap parameter order to match #2327 (#2329) e9e89b35 Update ChangeLog.rst 9bb406d7 Update changelog 11a14db2 Update format_to taking a buffer and remove undocumented vformat_to overload 832ec098 Fix argument order in locale overload of vformat_to (#2327) 486a80e8 Move wchar_t overloads to xchar.h 19d45f4b Update changelog 5a2b88f6 Reduce binary size 00a39ad5 Enable `Char` types other than `char` (#2323) ff37e416 wchar.h -> xchar.h because it handles other code unit types too 0901176f arg_join -> join_view a9a90181 Move wmemory_buffer to wchar.h 4a7801c3 Update changelog 517578f8 Update changelog 85442ed0 Update changelog 6a12b13a Update changelog 1cfe3c73 Update ChangeLog.rst c0601479 Update changelog 6fe04871 Update changelog 9d67988a FMT_DEPRECATED_WCHAR -> FMT_DEPRECATED_INCLUDE_WCHAR 765b451e Update changelog 17c993c7 Fixed compilation with CMake < 3.7 (#2321) dde69373 Update changelog 272b0f36 More module tests (#2309) 126c8cb4 Export os.h API, too (#2318) 98b9ff47 Align hex floats right as default (#2317) ece4b4b3 Update changelog a70a4ae0 Ignore zero-padding for non-finite floating points (#2310) 7612f18d Update changelog b9f2c276 Update changelog 4e21baff Simplify get_units 683ef11a Update changelog ca466374 qualify make_format_args (#2315) 5a2a1856 Make buffers non-movable ee52a6dc add `fmt::print()` overload to support compiled format (#2304) 82607efb Fixed int conversion warning (#2313) 35a2c2a7 Refactor chrono formatting b955e7a6 Refactor chrono formatting 883d9595 Support alternative locale names in tests 1f308a3c Update integer presentation types documentation. 1cd9899c Add initial support for weekday formatting 069131dc Add unicode-test dd8f38fc Cleanup printf API a216f256 Remove undocumented and obsolete vprintf overload 0c092639 Add is_exotic_char trait bc13c6de Update README.rst 8ec0b9e3 Do *not* export namespace `detail` b99c2bd3 Remove deprecated `locale.h` from module interface unit c04a2439 Update changelog b099a56f Update changelog 703005c8 Deprecate locale.h 51f01786 Cleanup the format API 5d59dcf6 Remove deprecated aliases / undeprecate has_formatter c242dd40 Move cerrno include to where it is used 2216e0b7 Update changelog 1c83a49b Simplify buffer extraction 2617384d Improve buffer extraction 34b8acae More wchar_t-specific API to wchar.h 6326c189 Improve code style consistency 5c4b0c86 Add missing Allocator template argument for basic_memory_buffer in format_to 00149c0b Move detail::null to chrono where it is used c5c968cb Improve binary size 128cbdeb cmake: hide private symbols by default 18af1dc4 Fix binary size regression caused by b268f88 d1e6f0f8 Fix binary size regression caused by b268f88 5a0d99fa Add a test for the module 6e2e6b79 Restore support for `wchar_t` overloads in module 24b677d0 Improve symbol sizes 63271a51 Fix ADL issues 61b4c923 Reduce code bloat 2a2e4c58 addressing nits. be48f4d6 Avoid unwanted sign extensions from MSVC in is_utf8. 13e65293 export missed symbols 71fb1138 fix compile error on msvc preview 4 (16.10) involving lookup clash /w STL 08d22503 Remove outdated comments 56f518a9 Update signatures b7f29337 Update signatures 7483dfc6 Update signatures 95c358f7 Improve separation between code unit types 39c3c4ec Simplify the core API e9c1c415 Improve compile-time checks 21d93bfd Move generic format functions to format.h 9a92eb41 Move more wchar overloads to wchar.h 0dd91e20 Add wchar.h for wide char overloads ce14eafc Simplify format string checks 8d70c0ed Refactor the format API 813ac495 More API cleanups 4ab01fb1 Cleanup printf API d5036b11 Remove deprecated APIs 25819462 Cleanup the core API b35db4e0 Improve handling of 128-bit ints d35f1ad5 Cleanup core 8f1902c0 Move format string checks to core.h 6469b903 Silence msvc warning about an unused named parameter 7d4c92fb Update ChangeLog.rst 0763d8ca Fix Visual Studio warning 5466373a Do *not* export namespace `detail` 588bdb54 Simplify get_arg_index_by_name 54f22a3e add support for statically named arguments with FMT_STRING ea94d6d9 Prevent ambiguity in name lookup 57280762 Move specs checker to core.h ced30375 Move dynamic specs to core.h dd2bc998 Move specs to core.h 08da1adc Remove unused headers 3be0cc20 Fix handling of 128-bit ints 9648bdce add missing header d1aebdbd Inline format_to 8f0fadfa Cleanup docs 02896dab Avoid use after move (#2278) 0036a1d1 Fix issue #2274. 2a9b3146 Replace fmt::error_code to std::error_code 2165bef4 Update README.rst 48629308 Optimize format string compilation 3207a8bb Get rid of unnecessary recursion to enable inlining 6214f15a Optimize standard formatter specialization cd2c78fb Use write directly in formatter specializations 4211d865 Add a formatter specialization for std::error_code. 39f28424 Cleanup tests 84feeb0f Remove redundant comments and put common case check first 2665afb5 Cleanup add-subdirectory-test d0abe7c2 Make chrono formatting locale-independent by default 50fb0b5e Fix formatting 16f2ef91 Replace fmt::system_error with std::system_error 4b885c86 Replace windows_error with system_error 5238055f Move esoteric char type support to format.h 9ac088f3 Add fmtlog to projects 849c9f61 Move is_name_start to core 23892caf Move more parsing to core 8e6390c3 Move FMT_STRING to core 51a33713 Move parsing to core 9c3af11a Cleanup tests 9d7b53cb Remove redundant formatter specialization for byte f0095ccd Add support for ranges of types without formatters to join (#2262) 4f0eadfc Exclude fallback from is_formattable 400b953f Use [] instead of {} in ranges for consistency with Python format 38bcc04a Drop range limit and cleanup tests c738c343 Cleanup tests ed7c4320 Cleanup tests 9155e2de Cleanup tests 38127d9e Cleanup tests c9c0e507 Cleanup tests ccf4ccde Cleanup tests and format string compilation e96a92f8 Cleanup tests and format string compilation fd43e4dc gtest: fix std::is_trivially_copy_constructible for GCC 4.8 & 4.9 properly 3d51ccda gtest: remove obsolete `GTEST_LANG_CXX11` compile definition setting 833377ff gtest: add `.clang-format` file into `test/gtest` directory to prevent formatting there 53ca0cbe gtest: move GTest/GMock files to separate directory, update GTest/GMock usages 342973b3 Make wchar_t overloads usable in module Bring ''detail::find()' into scope. 355be4b1 Make FMT_COMPILE fallback on runtime without if constexpr (#2261) 0cd0fb91 C++17: std::char_traits<>::{compare,length} is constexpr - v2 d1a6e560 Keep defaulted destructors inline applies to exception classes in case of msvc only 84a36b99 Move data to functions ab7c33ed Suppress checked iterator warnings 77258f60 fix FMT_CONSTEXPR_CHAR_TRAITS check for MSVC d23e315e CI windows: add MSVC C++20 build f085c3d7 use proper check for non-type template parameters 69bdc20a Workaround missing std::system on iOS, take 2 847aac43 Follow naming conventions in tests 39818e79 Cleanup core-test 0e6f989b __THROW warning fix for e2k (#2253) 1678ed62 simplify field::format() and spec_field::format(), fix typo ca821982 use named arg with static name in compile-time API ce6e7d86 use fixed_string to create named arg class with static name for _a literal fc56af14 move fixed_string from compile.h to format.h bb006f97 Replace TYPED_TEST_CASE with TYPED_TEST_SUITE 6956b10b Fix gcc 4.8 build b4f9a058 Update gtest 8f9ddf45 Remove deprecated posix.h dacd1356 Add module interface unit d3c523e0 Export printf-related contexts from printf.h 2c25df08 Export replacement type_traits, too 553022dc Don't use std::system on iOS (#2248) 8a040d18 Cleanup core-test 064cac2b Bump version 5b2c740a Remove deprecated APIs b9ab5c88 Remove printf.h dependency on ostream.h c47f2112 Simplify data handling 54d3b171 Move more data out of basic_data 128f007b C++17: std::char_traits<>::{compare,length} is constexpr. (#2246) 841aad95 Move data out of basic_data 1d4199f4 fix udl_compiled_string with non-byte chars (e.g. wchar) (#2242) c5d4fcb1 Appending a space to guarantee non-empty strftime() result. (#2244) 62714062 Fix a warning (#2233) 52bd62c7 Create separate dllexport marking points for clang and msvc. (#2229) f4bbc54c Tag official API for module export (#2235) d8910af8 Use qualified name lookup rather than ADL. (#2239) 92601141 Ranges wide strings support (#2236) 24c97515 Try to suppress MVSC warn of narrowing (#2230) a1c6bfd7 Add a link to llvm diff 42eccac4 Fix clang warning about ignoring __declspec(dllexport) on basic_data template instantitation definition (#2220) aec50434 Update README.rst 0b411454 Update README.rst 00f3d16b Update docs 99c2f7a3 Allow including fmt/core.h in the header-only mode b4415323 CI linux: add clang++-11 C++20 (with LLVM libc++) build 1dbadb65 CI linux: add clang++-11 C++20 build 09dbad47 CI linux: add missing build_type e2facffe CI linux: remove excessive clang++-9 include 273d8865 Suppress redef warning of _CRT_SECURE_NO_WARNINGS if any. (#2218) 5a8bf1f6 Workaround hexfloat inconsistency on windows (#2205) 78776ee4 Fix a conditional expression is constant warning #2210 (#2211) 266107f5 constexpr uint128_wrapper (#2215) 2e0d64cf specify size for `prefixes` static data 95da4847 Fix a link 06b3a100 Add support for time points with arbitrary durations (#2208) dac42f52 Inline fallback is_constant_evaluated 7c43f8b8 Don't use strlen at compile time (#2205) c62e4c30 Make buffer_appender default-constructible when back_insert_iterator is 0d6b70d9 Install gcc 8 15c10b0c Add speech synthesis support 308510eb "Use" `fwrite` result (workaround for `warn_unused_result`) afe23e7f Don't call fileno on NULL file in tests (#2196) b49af043 Remove noexcept from file's move assignment 14848875 Fix: fmt::ostream cannot be moved while holding buffered data #2197 (#2198) 7d8c3401 Update pull_request_template.md b966afcc Remove formattable ec5315a9 Use strlen when possible in fallback basic_string_view 4f8778ba Inline basic_format_args's ctor e2d87548 user-defined constructor f7151d38 Extra flag to prevent Intel compiler with Clang front-end warning of 'unknown attribute no_sanitize' 0fb8ef8f Inline trivial argument handling functions 1b23e25f Simplify formattability check 35c71ff5 Only use -Og with optimizations disabled 243d8beb Enable minimal optimizations in debug mode 9b34681d Work around xl compiler bug when nvcc preprocesses this file (#2190) 4dc7170d Fix C++17 builds: (#2192) 9cb347b4 Simplify argument formatters 0f85a468 add default cases (#2186) 417e1cee Stop using deprecated UDL templates f7e900e1 Simplify UDL definitions d9661c8f Mark grouping as deprecated 14a2a64d Fix handling of formattable types with to_string_view (#2181) 6ae402fd Fix handling of types with to_string_view and formatter specialization (#2180) a6408a3b Add args-test 1147782c Fix an ambiguous call to check caused by ADL (#2184) 2f3f3862 Fix harmless MSVS warning about using undefined _MANAGED symbol (#2183) d0bded59 Fix MSVC /clr builds (#2179) 8308f52c Fix dynamic_format_arg_store::push_back comment 6151d0dc Fix the comment 5a1127b7 Don't wrap named arg in cref and clarify docs b8ff3c18 optimize append (#2164) c8d8b882 fix GCC 7,8,9 warning about unused but set parameter (#2177) d2810187 Document ostream support limitation bac14ef9 Simplify integer spec checking 8f9db3fc Make ubsan happy on empty format specs (#2175) af567538 Bitpack integral prefixes cdf877d4 Workaround missed optimization opportunity eef4ba9c Optimize integer formatting without padding a1ea8a82 Unbloat my heart a457e163 Simplify integer formatter 05bc87a6 Optimize padding 605b6037 Optimize count_digits for powers of 2 85ba2716 Implement 128-bit count_digits in terms of count_digits_fallback d9835737 spec -> specs f9e0e904 Apply clang-format 60f5d244 Simplify arg_formatter 30e1302e Simplify on_format_specs 87c5cd46 Optimize parsing of argument ids 6a9016ea fix `formatted_size` with "compiled format" as argument (#2161) 6e1fc017 Move detail::truncating_iterator to fmt/compile.h e718ec3e Make truncating_iterator an output_iterator (#2158) 772aeca3 Don't include . (#2148) (#2152) 684b5b0e Fix fallback to runtime API from compile-time API (#2143) d8b92543 use simplified `void_t` for all compilers other than gcc 4.x (#2160) 835b910e Add an is_formattable trait 57887403 Revert "Optimize handling of integer constants" (#2147) 640acba8 Print x.what() of FMT_THROW when exception is disabled (#2145) d8e1c9f1 fix `fmt::get` for some GCC versions and legacy Clang (#2144) 2797588b Optimize handling of integer constants e8eff3b8 Fix FMT_STATIC_THOUSANDS_SEPARATOR (#2142) ab0f7d7f use const& for arguments 29cc8282 update chrono duration formatter (constness), use it in compile-test for specs checks 3f69af3a update wording in the error inside `arg_id_handler`, use `FMT_ASSERT` instead of `throw` 499047e1 fix incorrect indexing mode for named args, update tests 78c67157 prepare tests, fix incorrect handling of named args with simple `{}` replacement fields b31bc2dc simplify `try_format_argument()`, make `manual_indexing_id()` a variable 95e1aa2d add support for manual indexing and named fields, add tests 7e72673d Improve width estimation (#2033) 13b117b5 Improve code point computation ee0fed63 Fix handling of the + flag with locales (#2133) c5979d56 Fix fmt::localtime formatting not working in wide-char string contexts e6ef927e fmt::ptr: Support function pointers (#2131) 58aa0457 Fix ordering of install commands for CMake (#2122) 1980ca8c fix #2118: FMT_COMPILE did not work with tm formatter (#2119) 2a25e2bf Make ranges-test available with C++11 (#2114) b0b56b43 fix #2116 (FMT_COMPILE requires exceptions enabled) (#2117) 373262f9 Update docs ce519e93 Fix exception propagation from iterators (#2097) acef0bb5 use gcc-10.2 instead of gcc-10.1 on CI, also fix one problem (#2110) 8bf28e6b Add support for s format specifier to bool (#2094) (#2109) 9c418bc4 Update README.rst 456efa46 add missing detail namespace (#2107) 80dc7cce Fixed format.h(1465): warning C4702: unreachable code (#2106) 7fd535c6 Cleanup 'L' handling b4b8917c Update docs e4f2cf45 Make 'L' a modifier 6972b5f3 Add build variable: FMT_MASTER_PROJECT (#2100) ac352081 Install fmt/args.h (#2096) 532e846b Fix width computation in float formatter f8c2f848 Fix handling of width when formatting int as char 0fe0b15e Fix handling of # in width computation 061e364b Document output_file 018688da Correct a typo on syntax.rst (documentation) (#2081) 9ec5592b Fix writing to stdout when redirected to NUL on Windows (#2080) cdc5ef67 Remove fallback to inline specifier from FMT_CONSTEXPR(20) macro (#2075) c9dd1eb9 Don't change charset d09b5c14 Fix std::byte formatting with compile-time API (#2072) bbd6ed5b Add support of most format_specs for formatting at compile-time (#2056) a750bf3a Update api.rst 1256541d Fix formatting 4fa4c924 Add tests for FMT_ENFORCE_COMPILE_STRING, fix several errors (#2038) aa89e380 add cwchar to format.h for std::fputws (#2073) 5a37e182 Disable warning about format string (#2067) fa43fd14 Forward arguments to work with views (#2068) 3551f5d1 Workaround a gcc 10 -Warray-bounds bug (#2065) e7376726 Remove an old mingw workaround (#2059) 25a41b80 Fix a link to Android.mk (#2057) 9293f707 Suppress gcc warning on privates-only class (#2053) c20874c2 Reenable support for fallback formatter in join (#2040) (#2050) 5de0bc1d Add UDL as replacement for FMT_COMPILE (#2043) a6fafe2f docs: use https for some links (#2051) 33f9a6d3 Fix handling of enums in to_string (#2036) aabe0a84 simplify tests by reordering arguments of `EXPECT_EQ` (#2044) 1f4a76d2 Add a missing include (#2047) 4a6eadbd Make std::byte formattabe (#1981) 683a7450 fix formatting with empty compiled format string (#2042) f43416e1 Add a link to contents from index 5a493560 Move some code from core.h to format.h where it is used 9ed0a981 Fix docs build dac753b8 Basics of formatting at compile-time based on compile-time API (#2019) 119f7dc3 Truncate file by default 22a68d16 Don't emit trailing zeros by default d0110b7e Update README.rst 3f4839ce Merge branch 'release' of github.com:fmtlib/fmt 7bdf0628 Update version fc135511 Update changelog 926233bd Fix test 0683fa7d Bump version 6ce207b9 Fix formatting 07b1c1a1 Update changelog 58992761 Reintroduce ostream support to range formatters (#2014) b8957f50 Fix an overflow in format_to_n (#2029) df66516e Workaround an issue with mixing std versions in gcc (#2017) a57baa69 Fix more linkage errors (#2011) 85534a13 Fix linkage errors when linking with a shared library (#2011) a2fa5d62 Update changelog cd300368 Fix more linkage errors (#2011) d1ef29d6 Fix initialization of iterator_buffer (#1996) 5f41bb0f clang-format a58a6b27 Add a newline a036cc97 Reintroduce ostream support to range formatters (#2014) 38c7def4 Update clang version to 3.4 since there are ICEs on earlier ones 55336413 :new: [CI] Test with C++14 in Windows 2019 (#2020) 55dfdd92 Update README.rst 2c734c9b Fix an overflow in format_to_n (#2029) 6cdd1be9 Update build.gradle for latest AGP (#2026) bcc20b29 Implement compile-time checks by default befd7d4a Always use FMT_STRING internally where possible [Issue #2002] (#2006) f8640d40 Add more standards f81c14aa Workaround an issue with mixing std versions in gcc (#2017) 5555651c Fix more linkage errors (#2011) b268f881 detail::write in one more place relevant to printf with long argument… (#2016) aa9b09a9 :bug: Cannot call non-constexpr function in constexpr context (#2010) 986fa004 Printf get container (#1982) 7abc3c01 Suppress a useless warning (#2004) 6d14f781 Fix linkage errors when linking with a shared library (#2011) 9534b9fe Refactor warning suppression 60dc2735 Simplify on_text b5dac0f0 Reduce usage (#1998) a07627b1 :bug: Implicit sign conversion warning in clang in c++17 and 20 modes (#2009) 1b8f499e :wrench: Silence useless cast warnings (#2008) f428d286 Update README.rst beb248b6 Optimize handling of large format strings 1936dddc fix gcc warning of missing override (#2001) 14f6bd0f Move one more headers to args.h e01d26e1 Optimize includes e528d919 Merge branch 'master' of github.com:fmtlib/fmt 48816772 Update signatures 3302fd10 use memchr for searching for '%' in printf format string (#1984) 4c2d6372 Update signatures beaff396 Update signatures ffa0a083 Use newer versions of Sphinx and Breathe 038057eb Document contexts 5bedcb66 Fix initialization of iterator_buffer (#1996) 2435ea41 Workaround MSVC mess 8c6215f5 Fix fmt/color.h 10ebe6cb Document color 1ac50fcb Suppress more bogus warnings e098be8e Fix warning filtering 8cf0afaf Improve docs e29f93e8 Suppress more bogus warnings 4e8d000f Suppress more bogus warnings 7787792e Fix re usage 6ee5e507 Fix imports 06ee32d1 Filter useless doxygen warnings 86bb7fe6 Add a missing import 959a9f5c Merge branch 'master' of github.com:fmtlib/fmt 4f7df299 Improve docs b3ab0bc7 :art: [CI] Specify the exact version of clang to use (#1991) 701ed6c8 Install deps in github actions instead of script 8f2131cf Document chrono 32c4af8f Document chrono 295a60ec Document chrono a4fae96c Document chrono 263bb0e6 Document chrono 0506b328 Document chrono 4e426c19 Document chrono 9795d873 Update docs 2eb0be0b Remove debug code and fix bot contact cd955798 Move less installation to actions 98639d0f Debug doc build ab5e0632 Debug doc build b123129f Dump the content of html dir 81d2b986 Print less command 7a0b1d57 Add key 9f0617cb Fix branch ref 75b07598 Chrono docs dfbb6975 Remove travis config 5b3052f9 Switch doc build to github actions 506ff320 Fix build failure when not using fcntl with -Werror (#1990) a30b279b Apply clang-format and tweak comments 6a2495c8 -Wattributes visibility warning with some GCC versions (#1975) cba5970c Remove migrated build configs 689081d8 Merge branch 'release' of github.com:fmtlib/fmt cc09f1a6 Update version e4eb242c Update changelog and bump version ce98e0c6 Fix fallback float formatter at assymetric bounds (#1976) 49544ea9 Fuzz fallback formatter 6b7bfed4 Fix fallback float formatter at assymetric bounds (#1976) bcab36da Update CI config 1689e73e Move PR template 0103408a Update CI config 38a16ecb Move build config to github actions 205eb3a8 Update CI config fe61b8c6 Update CI config 867b15d7 Update CI config 98cb9f99 Update CI config 95077d60 Update CI config bc49f094 Update CI config cef6dfb4 Update CI config c8703ba4 Update CI config ab4405be Update README.rst 78a55e28 Update CI config d0a2494a Update cmake.yml 89d009ba Update cmake.yml 1f4ff47b Create cmake.yml eb52ac7a :new: Enable -Wshadow in pedantic mode e904e891 :art: :bug: Rename all shadowed types and variables 771292c3 Remove sizeof from unused variable silencer (#1974) 86bf6045 Merge branch 'release' of github.com:fmtlib/fmt 5f7f7b95 Update version 5d3f0741 Update changelog and bump version 563cbb6c Add a macro to workaround clang/gcc ABI incompatibility on ARM 425778aa Fix ABI compatibility (#1961) 69a84198 Remove accidental parenthesis (#1968) 5c045049 Removed [-Wsign-conversion] warning in GCC 556a1cfb Instantiate to_decimal to make gcc lto happy (#1955) 28a8eae8 Cleanup 236fea1f Workaround bugs in gcc 8 e50ced88 Add a macro to workaround clang/gcc ABI incompatibility on ARM 112755cf Remove FMT_SAFEBUFFERS (#1966) 4081b2fe Fix ABI compatibility (#1961) 2d9311e8 Remove accidental parenthesis (#1968) b3a4f28a Fix implicit signedness conversion warning (#1963) 97c88732 Allocator::max_size support in basic_memory_buffer (#1960) bb68f608 Removed [-Wsign-conversion] warning in GCC f4ca065c Range support cb224eca Instantiate to_decimal to make gcc lto happy (#1955) 7977c2b4 Cleanup e54eb676 Workaround bugs in gcc 8 4fe0b111 Update version df4bd60f Bump version 764fb35e Always install the required version of breathe e1bdc0ec Use the correct version of sphinx 39bde329 Tweak markup 204d299a Tweak markup e0995b1c Update readme 4af178bd Remove outdated build config aa41dc02 Remove unused script 6a77ea3c Tweak markup 62c72059 Update changelog c10e3f7f Update changelog e542e695 Update changelog 530cf316 Point to the release, not dev documentation 740385d6 Update changelog cd465111 Update changelog 46291be3 Update changelog 90071c1d Update ChangeLog.rst 25293d7a Update ChangeLog.rst 5024742f Update ChangeLog.rst 0452a4e7 Update changelog 8de96817 Woraround bugs in gcc 8 47e16767 Simplify arg formatter f0a42346 Move parsing optimization one level up 86287b8d Optimize common case in parse_format_specs 8924211f Update README.rst 525e7649 Update CONTRIBUTING.md 0ecb3d18 Optimize alignment parsing 97553078 Optimize format_uint 7446818f Simplify vformat_to 280b5612 Add option to force usage of inline namespaces e57ec7d5 Merge vformat_to overloads 2a3f4de3 Remove iterator_category 27fdb4ea Unshadow floaty 297e0bad Apply clang-format e3b4c22e Simplify is_output_iterator da8278e1 Update changelog and bump version 17fba753 added position independent documentation (#1939) 71e705a2 Update README.rst 74654c8c Fix compilation for systems without fcntl.h (#1942) f468b203 Avoid conversion from long long to size_t (#1935) 20d4f2e8 Fix handling of weird character types when parsing sign (#1932) 08370c39 Update README.rst bd3c7925 Fix float fuzzer 8d3fd86d Merge branch 'master' of github.com:fmtlib/fmt 40347157 Update README.rst 37d738fa Update README.rst 271eff14 Make classes derived from buffer final to silence the virtual destructor warning. (#1937) 010efc31 Add float fuzzer and cleanup 811c8b58 Add float fuzzer and cleanup 82c4e223 Cleanup fuzzing 63e40c96 Fix naming of fuzzers 2f448ed5 Fix fuzzer timeouts af283059 Cleanup 48ea8193 Explain why assert-test is a separate test 1d112bdd Remove old test 5eb292a6 Update README.rst 7e56b6b6 Fix coding style and remove duplicate fuzzer 41d97e1e Fix a UB on ridiculously large precision 01c37e0a Added check for `-mbig-obj` and ref qualifier check (#1929) a5e7e7db Fix handling of thousand separator (#1927) bf19051a Optimize floating point formatting 3c13a88b Optimize floating point formatting f6d75c53 Refactor write_float e9c0b2d6 Merge write_float overloads 7eddbfed Cleanup exponent handling in write_float b347b302 Update dynamic_formatter comment (#1923) 3541880e Fix integer overflow when using max int precision 7b50dc0b Don't exclude all detail symbols from docs 28052431 Fix the doc config 34f22e88 Cleanup CMake config a18b3fbb Fix fixed precision handling when rounding (#1917) 72770357 Fix long lines in usage.md 7612c1ea Add reference to lhelper package manager in usage b91d39f2 Get rid of float_writer b4b64b9c Refactor float formatting 712abe40 Workaround a bug in gcc 7.5 (#1912) af8a180a Make GetCachedPower test more precise a581e9e5 Fix warning C4018: '<=': signed/unsigned mismatch (#1908) 05a28312 Update docs 4d0aa4d8 Update link 575f4018 Simplify FP formatting and follow coding conventions 6f3536f9 Move zero-check to an earlier branch (#1906) 90ef46df Fix dragonbox integration 3ae88147 Fix declaration 64179525 Improve dragonbox integration 79694d42 Fix WriteConsole signature 51f2e2ca Move nan test to where it belongs 68555fdb Make format-test not depend on color.h 63e0c354 Make dragonbox::to_decimal available in format.h 2213a711 Update README.rst 79ba37f3 Update README.rst a905d8f7 Merge grisu-test into format-test 762c33a9 Simplify windows handling (#1903) 253d6315 Remove dependency on windows.h (#1900) c156093f Fix carry in fallback_format 34179b33 Update format.h (#1898) 0651e459 Minor tweaks to get_cached_power 6c025520 Test that max_k is correctly defined 51f8d0cc Reuse log10_2_significand constant 1305cbeb Fix MSVC2019 error C2049 when compiling with /clr (#1897) 2d4fde3a Don't emit trailing zero for consistency with std::format 5fd89d50 Minor simplifications 605ce5e4 Simplify divisible_by_power_of_2 085171e7 Remove grisu_count_digits aa729bf2 Remove dead code aa2ddf9b Simplify Dragonbox integration c1654ce4 Simplify uint32_or_64_or_128_t definition 33712dc0 Combine pragmas e5942ac9 Tweak comments aae7a133 Remove unused pragmas 6bcde9aa https://github.com/fmtlib/fmt/pull/1882#issuecomment-696823912 (#1894) bb0db5e5 clang-format 16410056 Optimize copy_str for counting_iterator 2591ab91 MSVC optimizations for count_digits. (#1890) d5b8002d Update README.rst 821471e1 qkw: generalizing aliasing | using fmt library and it's features (#1888) 2e620ddb Small improvements that should have zero to negligible impact on the runtime (#1887) 2f7e0885 Disable range formatter if value type is not formattable (#1885) c46a8de4 Simplify test 2696dc92 add forgotten template argument to make_format_args which made some u… (#1877) 0016da7a Don't generate zeros and fix UB on huge precision ce3f7699 Merge intrinsic blocks 3b6248f6 Change formatting 2d9b1dd0 Fix sign mismatch 1f0600a2 Fix bug regarding FMT_SAFEBUFFERS 2ecdbb98 Fix a bug in ctzll 6f81ea15 Fix typo (and thus bug) 0c8ffe9b Implement Dragonbox (first version) 42699bf4 Fix msvc version of clz & clzll (#1880) bc51a8df Disable fallthrough attributes for the Intel compilers on Linux and MacOS (#1879) 45da432d fix compiler warnings in public header files d55e61f1 Improve FMT_ALWAYS_INLINE (#1878) 7e682752 Remove trailing zeros when using fallback formatter (#1873) 1d696dc2 Handle exotic character types in compilation f674434a Add format_to_n overload that accepts FMT_COMPILE (from #1767) (#1869) 5b5a5971 Fix handling of wide alignment f80ed64d Update README.rst 38139664 Simplify fallback format dce8e49b Handle float in fallback formatter 78b59443 Spelling f233b56c Don't generate insignificant digits 595902f8 Update test 4f2ee892 Use built-in FP formatter for any precision 58a044be Use built-in FP formatter for any precision efe3694f Macro tweak and clang-format 9f312fe8 Implement fallback FP formatting with given precision (#1526) fb289cf5 Fix coding conventions 86f0a704 Fix formatting bff4d18e Add color format_to overloads * Fix variable size basic_memory_buffer colorization * Fix an unused arguments warning on GCC that blocks the CI otherwise * Ref #1842 * Ref #1593 f19b8885 Fixed a warning in mingw32/mingw64 (#1860) f8e00a08 NOMINMAX not handled properly (#1855) 6cccdc24 Fix move constructor (#1844) 69902c17 Allow use of in Linux when __has_include is not available (#1848) 1edd38b9 Add append mode. (#1847) e66ba169 Added build2 usage instructions. (#1838) f39e6fb6 Add formatters for chrono::time_point (#1837) 77b627be Fix bogus MSVC warnings (#1825) 5dff01d3 Add complex tests d16d585e Update signatures c7e6d8af Fix usage of override (#1836) 92bff2fe Revert "Add missing includes" a0dcfbc5 Add ptr to docs 1651b2d4 Fix detail::write with fallback formatter (#1829) 06895a76 Add missing includes 92a448a0 Apply clang-format 6be65446 Fixing buffer_appender's ++ slicing (#1822) 951e0d23 CMakeLists.txt: Added Wundef warning to clang and gcc. (#1823) f9f02df7 CMakeLists.txt: Clang-warnings: removed -Wno-sign-conversion (#1817) 76e97dc4 Eliminate shadowed variable warnings on intel (#1816) e204df0e nvcc compiler should be EDG-based, but fails test (#1818) 1c8bb547 small changes to reduce clang-9 warnings (#1808) 4b69c787 fix: warning C4100: unreferenced formal parameter (#1814) fb0aeb82 fix: disabled UDL templates for PGI (#1811) (#1812) 54daa086 Add dynamic width support to FMT_COMPILE (#1809) 6fb7c6fb Workaround a bug in gcc10 (#1810) 16985fda Update README.rst 1378ddae Update README.rst 4fd95e4b Don't remove trailing zeros with # e06ae322 Avoid warnings on functions with external linkage that don't have declarations 7fc3d1f5 Add override to grow 065889a5 Use correct capacity in iterator_buffer (#1807) d0dd6786 Adding convenience append(range) 0e7cef06 Merge commit 'c13f79e0' e2c8c455 Update README.rst e4c954ff Update README.rst c13f79e0 Merge release branch d7921d64 Update README.rst 4a4fc225 Update changelog 61602a75 Remove -Wno-shadow 2f8fc29e Update README.rst 717b226b include/fmt/format.h: explicit cast to std::size_t for parameter to buffer.resize() in order to get rid of warning 'implicit conversion changes signedness:' in clang-8 (#1802) 2a69f567 Tweak buffer size ea769338 Simplify ostream 5413713c Remove unused function 57f46242 Increase the default buffer size 0b6e7cc6 Update README.rst e587adb4 Simplify count_digits 279d698e Fix handling of default alignmment with locale (#1801) 76cfb50b Test complex formatter 20829120 Optimize count_digits 8d9ab967 Cut a few cycles from count_digits 73434493 Simplify ostream_params 2a47a1e4 Update README.rst 7c4c5c79 Make buffer size configurable f0b84da5 Don't use 128 bit integers with clang-cl (#1800) a3dfd6f9 Workaround a bug in msvc 51d05521 Workaround broken numeric_limites, part 2 (#1787) 21c8b5c1 Report error on missing named argument (#1796) d82fdcc9 Fix handling of iterators in locale-specific formatting (#1782) 633213d9 Merge release branch e8f2580a Bump version 6cefe55a Update changelog 64e2da15 Update README.rst 1c8c810f Update README.rst c2399ccf Update README.rst a7c5db06 Update README.rst a4c22acd Update README.rst 0c1f4b5a Update README.rst 63b422ee Update README.rst 26e81a67 Update README.rst de5fc6af Update README.rst 9c2edfd1 Partially revert 638db5 because it breaks the doc build 810357c0 Document color 0a7032a4 Update README.rst 95d3abf9 Make format_to_n part of the core API 98626093 Correct the locale format specifier in api.rst (#1792) 47f8d7a3 Make formatted_size part of the core API 46a63b70 Update docs 430f393d Disabled __attribute__((deprecated)) usage for LCC (#1790) febffa4e Make join() handle non-const-only begin/end ranges (#1786) d69e2da2 Fix apidoc ce73ea37 Reorder functions d39d661b Workaround broken numeric_limits (#1725) c228bfe8 Improve docs 38ce19f7 Update README.rst d11849bc Add FMT_REDUCE_INT_INSTANTIATIONS flag (#1781) c08518a2 Move make_args_checked to the public API e2837084 Add a color section 9f0c0033 Simplify format string checks d615137c Improve handling of buffer iterator 26b47b6f Bump tested CMake version to 3.18 7a01c9c5 Update README.rst b17d5c4f Fix a regression in handling digit separators (#1782) eb90da2e Type erase output iterators 9d3cd0af Type erase output iterators 18024853 Fix compatibility with CMake 3.4 (#1779) f5d4215b Trying to clear ambiguous compile time claims (#1775) c26349f4 Improve error reporting f4b11ef6 Add a short anchor 0097cf11 Report unformattable type name more prominently 8fa20b47 dev -> latest a03bd3dd Autodetect MSVC static runtime (#1770) c108ee1d Clarify a comment a8074a86 Update README.rst 5f629548 Update README.rst bd903f96 Clarify precedence 16cac46a Improve handling of streamable and convertible to bool types (#1766) 415cd519 direct_buffered_file -> ostream e1bfb596 Fix handling of code units in compile ba8d98cb Cleanup direct_buffered_file 04a1f6e9 Improve handling of single code units in compile e4f57bfd Add an overload of write for buffer_appender d8704681 Make append work with fixed-size buffer e8ec09ae Cleanup core-test a2c4fed9 Double buffering no more 36406509 Add a fixed buffer 60c43e87 Apply clang-format b998e0f3 Reduce symbol sizes and simplify iterator use c5adfc51 Update README.rst c4ad94ce Update README.rst c1429651 Fix image source link 638db5ca Use Cmake to find Python and Sphinx-doc. c0905697 Update readme 1efdb2dd Simplify readme dc69afad Cleanup example 445f5d39 Break long lines 23063c34 Update readme f57b6257 Move PR template to the top level git-subtree-dir: externals/fmt git-subtree-split: 9e8b86fd2d9806672cc73133d21780dd182bfd24 --- .github/pull_request_template.md | 12 +- .github/workflows/doc.yml | 23 + .github/workflows/linux.yml | 78 + .github/workflows/macos.yml | 37 + .github/workflows/windows.yml | 60 + .gitignore | 1 + .travis.yml | 101 - CMakeLists.txt | 141 +- CONTRIBUTING.md | 3 + ChangeLog.rst | 1090 +- README.rst | 298 +- doc/CMakeLists.txt | 8 +- doc/api.rst | 289 +- doc/build.py | 113 +- doc/fmt.less | 5 + doc/index.rst | 72 +- doc/syntax.rst | 86 +- doc/usage.rst | 71 +- include/fmt/args.h | 232 + include/fmt/chrono.h | 495 +- include/fmt/color.h | 173 +- include/fmt/compile.h | 846 +- include/fmt/core.h | 2372 ++- include/fmt/format-inl.h | 2306 ++- include/fmt/format.h | 4168 ++-- include/fmt/locale.h | 80 +- include/fmt/os.h | 259 +- include/fmt/ostream.h | 32 +- include/fmt/posix.h | 2 - include/fmt/printf.h | 469 +- include/fmt/ranges.h | 315 +- include/fmt/xchar.h | 236 + src/fmt.cc | 100 + src/format.cc | 22 +- src/os.cc | 127 +- support/README | 2 - support/appveyor.yml | 10 - support/build-docs.py | 58 + support/build.gradle | 73 +- support/fmt.pro | 27 - support/rst2md.py | 2 +- support/travis-build.py | 119 - support/update-coverity-branch.py | 30 - test/CMakeLists.txt | 149 +- test/add-subdirectory-test/CMakeLists.txt | 16 +- test/add-subdirectory-test/main.cc | 5 +- test/args-test.cc | 173 + test/assert-test.cc | 15 +- test/chrono-test.cc | 272 +- test/color-test.cc | 58 +- test/compile-error-test/CMakeLists.txt | 2 +- test/compile-test.cc | 425 +- test/core-test.cc | 931 +- test/custom-formatter-test.cc | 58 - test/enforce-checks-test.cc | 62 + test/find-package-test/CMakeLists.txt | 2 +- test/format | 7 +- test/format-dyn-args-test.cc | 6 - test/format-impl-test.cc | 256 +- test/format-test.cc | 2757 ++- test/fuzzing/CMakeLists.txt | 42 +- test/fuzzing/README.md | 29 +- test/fuzzing/build.sh | 34 +- test/fuzzing/chrono-duration.cc | 135 + test/fuzzing/chrono_duration.cpp | 152 - test/fuzzing/float.cc | 39 + test/fuzzing/fuzzer-common.h | 75 + test/fuzzing/fuzzer_common.h | 67 - test/fuzzing/main.cc | 22 + test/fuzzing/main.cpp | 21 - test/fuzzing/named-arg.cc | 100 + test/fuzzing/named_arg.cpp | 128 - test/fuzzing/one-arg.cc | 91 + test/fuzzing/one_arg.cpp | 131 - test/fuzzing/sprintf.cpp | 116 - test/fuzzing/two-args.cc | 105 + test/fuzzing/two_args.cpp | 112 - test/gmock/gmock.h | 14204 -------------- test/grisu-test.cc | 75 - test/gtest-extra-test.cc | 124 +- test/gtest-extra.cc | 46 +- test/gtest-extra.h | 50 +- test/gtest/.clang-format | 3 + test/gtest/CMakeLists.txt | 31 + test/{ => gtest}/gmock-gtest-all.cc | 8820 ++++++--- test/gtest/gmock/gmock.h | 11645 ++++++++++++ test/gtest/gtest.h | 20065 -------------------- test/gtest/{ => gtest}/gtest-spi.h | 26 +- test/gtest/gtest/gtest.h | 12398 ++++++++++++ test/header-only-test.cc | 4 + test/header-only-test2.cc | 3 - test/locale-test.cc | 109 - test/mock-allocator.h | 12 +- test/module-test.cc | 565 + test/os-test.cc | 300 +- test/ostream-test.cc | 219 +- test/posix-mock-test.cc | 116 +- test/printf-test.cc | 226 +- test/ranges-test.cc | 267 +- test/scan-test.cc | 38 +- test/scan.h | 9 +- test/static-export-test/CMakeLists.txt | 30 + test/static-export-test/library.cc | 5 + test/static-export-test/main.cc | 6 + test/std-format-test.cc | 26 +- test/test-assert.h | 13 +- test/test-main.cc | 2 +- test/unicode-test.cc | 48 + test/util.cc | 46 +- test/util.h | 31 +- test/xchar-test.cc | 427 + 111 files changed, 44477 insertions(+), 47148 deletions(-) create mode 100644 .github/workflows/doc.yml create mode 100644 .github/workflows/linux.yml create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/windows.yml delete mode 100644 .travis.yml create mode 100644 include/fmt/args.h delete mode 100644 include/fmt/posix.h create mode 100644 include/fmt/xchar.h create mode 100644 src/fmt.cc create mode 100755 support/build-docs.py delete mode 100644 support/fmt.pro delete mode 100755 support/travis-build.py delete mode 100755 support/update-coverity-branch.py create mode 100644 test/args-test.cc delete mode 100644 test/custom-formatter-test.cc create mode 100644 test/enforce-checks-test.cc delete mode 100644 test/format-dyn-args-test.cc create mode 100644 test/fuzzing/chrono-duration.cc delete mode 100644 test/fuzzing/chrono_duration.cpp create mode 100644 test/fuzzing/float.cc create mode 100644 test/fuzzing/fuzzer-common.h delete mode 100644 test/fuzzing/fuzzer_common.h create mode 100644 test/fuzzing/main.cc delete mode 100644 test/fuzzing/main.cpp create mode 100644 test/fuzzing/named-arg.cc delete mode 100644 test/fuzzing/named_arg.cpp create mode 100644 test/fuzzing/one-arg.cc delete mode 100644 test/fuzzing/one_arg.cpp delete mode 100644 test/fuzzing/sprintf.cpp create mode 100644 test/fuzzing/two-args.cc delete mode 100644 test/fuzzing/two_args.cpp delete mode 100644 test/gmock/gmock.h delete mode 100644 test/grisu-test.cc create mode 100644 test/gtest/.clang-format create mode 100644 test/gtest/CMakeLists.txt rename test/{ => gtest}/gmock-gtest-all.cc (57%) create mode 100644 test/gtest/gmock/gmock.h delete mode 100644 test/gtest/gtest.h rename test/gtest/{ => gtest}/gtest-spi.h (94%) create mode 100644 test/gtest/gtest/gtest.h delete mode 100644 test/header-only-test2.cc delete mode 100644 test/locale-test.cc create mode 100644 test/module-test.cc create mode 100644 test/static-export-test/CMakeLists.txt create mode 100644 test/static-export-test/library.cc create mode 100644 test/static-export-test/main.cc create mode 100644 test/unicode-test.cc create mode 100644 test/xchar-test.cc diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 548e30f2..b70c27bf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ - - - - -I agree that my contributions are licensed under the {fmt} license, and agree to future changes to the licensing. + diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 00000000..402c5a0f --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,23 @@ +name: doc + +on: [push, pull_request] + +jobs: + build: + # Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken. + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + run: | + sudo apt install doxygen python3-virtualenv + sudo npm install -g less clean-css + cmake -E make_directory ${{runner.workspace}}/build + + - name: Build + working-directory: ${{runner.workspace}}/build + env: + KEY: ${{secrets.KEY}} + run: $GITHUB_WORKSPACE/support/build-docs.py diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 00000000..2c32fbcb --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,78 @@ +name: linux + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + cxx: [g++-4.8, g++-10, clang++-9] + build_type: [Debug, Release] + std: [11] + os: [ubuntu-18.04] + include: + - cxx: g++-4.8 + install: sudo apt install g++-4.8 + os: ubuntu-18.04 + - cxx: g++-8 + build_type: Debug + std: 14 + install: sudo apt install g++-8 + os: ubuntu-18.04 + - cxx: g++-10 + build_type: Debug + std: 17 + os: ubuntu-18.04 + - cxx: g++-10 + build_type: Debug + std: 20 + os: ubuntu-20.04 + - cxx: clang++-9 + build_type: Debug + fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON + std: 17 + os: ubuntu-18.04 + - cxx: clang++-11 + build_type: Debug + std: 20 + os: ubuntu-20.04 + - cxx: clang++-11 + build_type: Debug + std: 20 + cxxflags: -stdlib=libc++ + os: ubuntu-20.04 + install: sudo apt install libc++-11-dev libc++abi-11-dev + - shared: -DBUILD_SHARED_LIBS=ON + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + run: | + ${{matrix.install}} + sudo apt install locales-all + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + env: + CXX: ${{matrix.cxx}} + CXXFLAGS: ${{matrix.cxxflags}} + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + threads=`nproc` + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 00000000..e300a22b --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,37 @@ +name: macos + +on: [push, pull_request] + +jobs: + build: + runs-on: macos-10.15 + strategy: + matrix: + build_type: [Debug, Release] + include: + - shared: -DBUILD_SHARED_LIBS=ON + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + working-directory: ${{runner.workspace}}/build + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + threads=`sysctl -n hw.logicalcpu` + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 00000000..ece0372b --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,60 @@ +name: windows + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{matrix.os}} + strategy: + matrix: + # windows-2016 and windows-2019 have MSVC 2017 and 2019 installed + # respectively: https://github.com/actions/virtual-environments. + os: [windows-2016, windows-2019] + platform: [Win32, x64] + build_type: [Debug, Release] + standard: [11, 17, 20] + include: + - os: windows-2016 + platform: Win32 + build_type: Debug + shared: -DBUILD_SHARED_LIBS=ON + exclude: + - os: windows-2016 + platform: Win32 + - os: windows-2016 + standard: 17 + - os: windows-2016 + standard: 20 + - os: windows-2019 + standard: 11 + - os: windows-2019 + standard: 20 + platform: Win32 + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + # Use a bash shell for $GITHUB_WORKSPACE. + shell: bash + working-directory: ${{runner.workspace}}/build + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ + -A ${{matrix.platform}} \ + -DCMAKE_CXX_STANDARD=${{matrix.standard}} \ + $GITHUB_WORKSPACE + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + cmake --build . --config ${{matrix.build_type}} --parallel $threads + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} -V + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/.gitignore b/.gitignore index e95cbaa8..8a37cb98 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ gradle/ gradlew* local.properties build/ +support/.cxx bin/ /_CPack_Packages diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ff9d167b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,101 +0,0 @@ -language: cpp -dist: trusty -sudo: false - -os: linux - -git: - depth: 1 - -env: - global: - - secure: |- - a1eovNn4uol9won7ghr67eD3/59oeESN+G9bWE+ecI1V6yRseG9whniGhIpC/YfMW/Qz5I - 5sxSmFjaw9bxCISNwUIrL1O5x2AmRYTnFcXk4dFsUvlZg+WeF/aKyBYCNRM8C2ndbBmtAO - o1F2EwFbiso0EmtzhAPs19ujiVxkLn4= - -matrix: - include: - # Documentation - - env: BUILD=Doc - sudo: required - # g++ 6 on Linux with C++14 - - env: COMPILER=g++-6 BUILD=Debug STANDARD=14 - compiler: gcc - addons: - apt: - update: true - sources: - - ubuntu-toolchain-r-test - packages: - - g++-6 - - env: COMPILER=g++-6 BUILD=Release STANDARD=14 - compiler: gcc - addons: - apt: - update: true - sources: - - ubuntu-toolchain-r-test - packages: - - g++-6 - # g++ 8 on Linux with C++17 - - env: COMPILER=g++-8 BUILD=Debug STANDARD=17 - compiler: gcc - addons: - apt: - update: true - sources: - - ubuntu-toolchain-r-test - packages: - - g++-8 - - env: COMPILER=g++-8 BUILD=Release STANDARD=17 - compiler: gcc - addons: - apt: - update: true - sources: - - ubuntu-toolchain-r-test - packages: - - g++-8 - - # Apple clang on OS X with C++14 - - env: BUILD=Debug STANDARD=14 - compiler: clang - os: osx - - env: BUILD=Release STANDARD=14 - compiler: clang - os: osx - # clang 6.0 on Linux with C++14 (builds the fuzzers as well) - - env: COMPILER=clang++-6.0 BUILD=Debug STANDARD=14 ENABLE_FUZZING=1 - compiler: clang - addons: - apt: - update: true - packages: - - clang-6.0 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty - - llvm-toolchain-trusty-6.0 - # clang 4.0 on Linux with C++14 - - env: COMPILER=clang++-4.0 BUILD=Debug STANDARD=11 - compiler: clang - addons: - apt: - update: true - packages: - - clang-4.0 - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty - - llvm-toolchain-trusty-4.0 - # g++ 4.8 on Linux with C++11 - - env: COMPILER=g++-4.8 BUILD=Debug STANDARD=11 - compiler: gcc - -before_script: - - if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then export CXX=${COMPILER}; fi - - if [[ "${BUILD}" != "Doc" ]]; then ${CXX} --version; fi - -script: - - support/travis-build.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 977090f7..ae9c5692 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,18 @@ -cmake_minimum_required(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.1...3.18) -# Use newer policies if available, up to most recent tested version of CMake. -if(${CMAKE_VERSION} VERSION_LESS 3.11) +# Fallback for using newer policies on CMake <3.12. +if(${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.11) endif() # Determine if fmt is built as a subproject (using add_subdirectory) # or if it is the master project. -set(MASTER_PROJECT OFF) -if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) - set(MASTER_PROJECT ON) - message(STATUS "CMake version: ${CMAKE_VERSION}") +if (NOT DEFINED FMT_MASTER_PROJECT) + set(FMT_MASTER_PROJECT OFF) + if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(FMT_MASTER_PROJECT ON) + message(STATUS "CMake version: ${CMAKE_VERSION}") + endif () endif () # Joins arguments and places the results in ${result_var}. @@ -24,6 +24,17 @@ function(join result_var) set(${result_var} "${result}" PARENT_SCOPE) endfunction() +function(enable_module target) + if (MSVC) + set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) + target_compile_options(${target} + PRIVATE /interface /ifcOutput ${BMI} + INTERFACE /reference fmt=${BMI}) + endif () + set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) + set_source_files_properties(${BMI} PROPERTIES GENERATED ON) +endfunction() + include(CMakeParseArguments) # Sets a cache variable with a docstring joined from multiple arguments: @@ -46,7 +57,7 @@ endfunction() # Set the default CMAKE_BUILD_TYPE to Release. # This should be done before the project command since the latter can set # CMAKE_BUILD_TYPE itself (it does so for nmake). -if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) +if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") @@ -55,20 +66,36 @@ endif () project(FMT CXX) include(GNUInstallDirs) set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING - "Installation directory for include files, a relative path " - "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") + "Installation directory for include files, a relative path that " + "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.") option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) option(FMT_WERROR "Halt the compilation with an error on compiler warnings." OFF) # Options that control generation of various targets. -option(FMT_DOC "Generate the doc target." ${MASTER_PROJECT}) -option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT}) -option(FMT_TEST "Generate the test target." ${MASTER_PROJECT}) +option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) +option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT}) +option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) 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) + +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) +endif () +if (NOT FMT_CAN_MODULE) + set(FMT_MODULE OFF) + message(STATUS "Module support is disabled.") +endif () +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 () # Get version from core.h file(READ include/fmt/core.h core_h) @@ -104,24 +131,36 @@ if (${index} GREATER -1) endif () message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}") +if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) + set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING + "Preset for the export of private symbols") + set_property(CACHE CMAKE_CXX_VISIBILITY_PRESET PROPERTY STRINGS + hidden default) +endif () + +if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN) + set_verbose(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL + "Whether to add a compile flag to hide symbols of inline functions") +endif () + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic -Wold-style-cast -Wundef -Wredundant-decls -Wwrite-strings -Wpointer-arith -Wcast-qual -Wformat=2 -Wmissing-include-dirs - -Wcast-align -Wnon-virtual-dtor + -Wcast-align -Wctor-dtor-privacy -Wdisabled-optimization -Winvalid-pch -Woverloaded-virtual - -Wconversion -Wswitch-enum - -Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow) + -Wconversion -Wswitch-enum -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} -Wnoexcept + set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-dangling-else -Wno-unused-local-typedefs) endif () if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion -Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast - -Wvector-operation-performance -Wsized-deallocation) + -Wvector-operation-performance -Wsized-deallocation -Wshadow) endif () if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2 @@ -131,8 +170,9 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") endif () if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion - -Wno-sign-conversion -Wdeprecated -Wweak-vtables) + set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef + -Wdeprecated -Wweak-vtables -Wshadow + -Wno-gnu-zero-variadic-macro-arguments) check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING) if (HAS_NULLPTR_WARNING) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} @@ -146,7 +186,7 @@ if (MSVC) set(WERROR_FLAG /WX) endif () -if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") +if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") # If Microsoft SDK is installed create script run-msbuild.bat that # calls SetEnv.cmd to set up build environment and runs msbuild. # It is useful when building Visual Studio projects with the SDK @@ -185,9 +225,12 @@ function(add_headers VAR) endfunction() # Define the fmt library, its includes and the needed defines. -add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h - locale.h os.h ostream.h posix.h printf.h ranges.h) -if (FMT_OS) +add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h + format-inl.h locale.h os.h ostream.h printf.h ranges.h + xchar.h) +if (FMT_MODULE) + set(FMT_SOURCES src/fmt.cc) +elseif (FMT_OS) set(FMT_SOURCES src/format.cc src/os.cc) else() set(FMT_SOURCES src/format.cc) @@ -201,7 +244,10 @@ if (HAVE_STRTOD_L) endif () if (MINGW) - target_compile_options(fmt PUBLIC "-Wa,-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() endif () if (FMT_WERROR) @@ -210,6 +256,9 @@ endif () if (FMT_PEDANTIC) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) endif () +if (FMT_MODULE) + enable_module(fmt) +endif () target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) @@ -231,7 +280,8 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") endif () if (BUILD_SHARED_LIBS) - if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND NOT EMSCRIPTEN) + if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND + NOT EMSCRIPTEN) # Fix rpmlint warning: # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6. target_link_libraries(fmt -Wl,--as-needed) @@ -256,20 +306,22 @@ target_include_directories(fmt-header-only INTERFACE if (FMT_INSTALL) include(CMakePackageConfigHelpers) set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING - "Installation directory for cmake files, a relative path " - "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") + "Installation directory for cmake files, a relative path that " + "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute " + "path.") set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) set(targets_export_name fmt-targets) set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING - "Installation directory for libraries, a relative path " - "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") + "Installation directory for libraries, a relative path that " + "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.") set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH - "Installation directory for pkgconfig (.pc) files, a relative path " - "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") + "Installation directory for pkgconfig (.pc) files, a relative " + "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an " + "absolute path.") # Generate the version, config and target files into the build directory. write_basic_package_version_file( @@ -290,6 +342,13 @@ if (FMT_INSTALL) INSTALL_DESTINATION ${FMT_CMAKE_DIR}) set(INSTALL_TARGETS fmt fmt-header-only) + + # Install the library and headers. + install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} + LIBRARY DESTINATION ${FMT_LIB_DIR} + ARCHIVE DESTINATION ${FMT_LIB_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + # Use a namespace because CMake provides better diagnostics for namespaced # imported targets. export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt:: @@ -302,12 +361,6 @@ if (FMT_INSTALL) install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} NAMESPACE fmt::) - # Install the library and headers. - install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} - LIBRARY DESTINATION ${FMT_LIB_DIR} - ARCHIVE DESTINATION ${FMT_LIB_DIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - install(FILES $ DESTINATION ${FMT_LIB_DIR} OPTIONAL) install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt") @@ -326,11 +379,17 @@ endif () # Control fuzzing independent of the unit tests. if (FMT_FUZZ) add_subdirectory(test/fuzzing) + + # The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing + # mode and make fuzzing practically possible. It is similar to + # FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to + # avoid interfering with fuzzing of projects that use {fmt}. + # See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode. target_compile_definitions(fmt PUBLIC FMT_FUZZ) endif () set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore) -if (MASTER_PROJECT AND EXISTS ${gitignore}) +if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) # Get the list of ignored files from .gitignore. file (STRINGS ${gitignore} lines) list(REMOVE_ITEM lines /doc/html) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3532bd11..b82f1450 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,4 +14,7 @@ exceptions: * snake_case should be used instead of UpperCamelCase for function and type names +All documentation must adhere to the [Google Developer Documentation Style +Guide](https://developers.google.com/style). + Thanks for contributing! diff --git a/ChangeLog.rst b/ChangeLog.rst index 97a0a7da..2a6d7ffe 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,1075 @@ +8.0.0 - 2021-06-21 +------------------ + +* Enabled compile-time format string check by default. + For example (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + fmt::print("{:d}", "I am not a number"); + } + + gives a compile-time error on compilers with C++20 ``consteval`` support + (gcc 10+, clang 11+) because ``d`` is not a valid format specifier for a + string. + + To pass a runtime string wrap it in ``fmt::runtime``: + + .. code:: c++ + + fmt::print(fmt::runtime("{:d}"), "I am not a number"); + +* Added compile-time formatting + (`#2019 `_, + `#2044 `_, + `#2056 `_, + `#2072 `_, + `#2075 `_, + `#2078 `_, + `#2129 `_, + `#2326 `_). + For example (`godbolt `__): + + .. code:: c++ + + #include + + consteval auto compile_time_itoa(int value) -> std::array { + auto result = std::array(); + fmt::format_to(result.data(), FMT_COMPILE("{}"), value); + return result; + } + + constexpr auto answer = compile_time_itoa(42); + + Most of the formatting functionality is available at compile time with a + notable exception of floating-point numbers and pointers. + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Optimized handling of format specifiers during format string compilation. + For example, hexadecimal formatting (``"{:x}"``) is now 3-7x faster than + before when using ``format_to`` with format string compilation and a + stack-allocated buffer (`#1944 `_). + + Before (7.1.3):: + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + FMTCompileOld/0 15.5 ns 15.5 ns 43302898 + FMTCompileOld/42 16.6 ns 16.6 ns 43278267 + FMTCompileOld/273123 18.7 ns 18.6 ns 37035861 + FMTCompileOld/9223372036854775807 19.4 ns 19.4 ns 35243000 + ---------------------------------------------------------------------------- + + After (8.x):: + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + FMTCompileNew/0 1.99 ns 1.99 ns 360523686 + FMTCompileNew/42 2.33 ns 2.33 ns 279865664 + FMTCompileNew/273123 3.72 ns 3.71 ns 190230315 + FMTCompileNew/9223372036854775807 5.28 ns 5.26 ns 130711631 + ---------------------------------------------------------------------------- + + It is even faster than ``std::to_chars`` from libc++ compiled with clang on + macOS:: + + ---------------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------------- + ToChars/0 4.42 ns 4.41 ns 160196630 + ToChars/42 5.00 ns 4.98 ns 140735201 + ToChars/273123 7.26 ns 7.24 ns 95784130 + ToChars/9223372036854775807 8.77 ns 8.75 ns 75872534 + ---------------------------------------------------------------------------- + + In other cases, especially involving ``std::string`` construction, the + speed up is usually lower because handling format specifiers takes a smaller + fraction of the total time. + +* Added the ``_cf`` user-defined literal to represent a compiled format string. + It can be used instead of the ``FMT_COMPILE`` macro + (`#2043 `_, + `#2242 `_): + + .. code:: c++ + + #include + + using namespace fmt::literals; + auto s = fmt::format(FMT_COMPILE("{}"), 42); // 🙁 not modern + auto s = fmt::format("{}"_cf, 42); // 🙂 modern as hell + + It requires compiler support for class types in non-type template parameters + (a C++20 feature) which is available in GCC 9.3+. + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Format string compilation now requires ``format`` functions of ``formatter`` + specializations for user-defined types to be ``const``: + + .. code:: c++ + + template <> struct fmt::formatter: formatter { + template + auto format(my_type obj, FormatContext& ctx) const { // Note const here. + // ... + } + }; + +* Added UDL-based named argument support to format string compilation + (`#2243 `_, + `#2281 `_). For example: + + .. code:: c++ + + #include + + using namespace fmt::literals; + auto s = fmt::format(FMT_COMPILE("{answer}"), "answer"_a = 42); + + Here the argument named "answer" is resolved at compile time with no + runtime overhead. + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Added format string compilation support to ``fmt::print`` + (`#2280 `_, + `#2304 `_). + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Added initial support for compiling {fmt} as a C++20 module + (`#2235 `_, + `#2240 `_, + `#2260 `_, + `#2282 `_, + `#2283 `_, + `#2288 `_, + `#2298 `_, + `#2306 `_, + `#2307 `_, + `#2309 `_, + `#2318 `_, + `#2324 `_, + `#2332 `_, + `#2340 `_). + Thanks `@DanielaE (Daniela Engert) `_. + +* Made symbols private by default reducing shared library size + (`#2301 `_). For example there was + a ~15% reported reduction on one platform. + Thanks `@sergiud (Sergiu Deitsch) `_. + +* Optimized includes making the result of preprocessing ``fmt/format.h`` + ~20% smaller with libstdc++/C++20 and slightly improving build times + (`#1998 `_). + +* Added support of ranges with non-const ``begin`` / ``end`` + (`#1953 `_). + Thanks `@kitegi (sarah) `_. + +* Added support of ``std::byte`` and other formattable types to ``fmt::join`` + (`#1981 `_, + `#2040 `_, + `#2050 `_, + `#2262 `_). For example: + + .. code:: c++ + + #include + #include + #include + + int main() { + auto bytes = std::vector{std::byte(4), std::byte(2)}; + fmt::print("{}", fmt::join(bytes, "")); + } + + prints "42". + + Thanks `@kamibo (Camille Bordignon) `_. + +* Implemented the default format for ``std::chrono::system_clock`` + (`#2319 `_, + `#2345 `_). For example: + + .. code:: c++ + + #include + + int main() { + fmt::print("{}", std::chrono::system_clock::now()); + } + + prints "2021-06-18 15:22:00" (the output depends on the current date and + time). Thanks `@sunmy2019 `_. + +* Made more chrono specifiers locale independent by default. Use the ``'L'`` + specifier to get localized formatting. For example: + + .. code:: c++ + + #include + + int main() { + std::locale::global(std::locale("ru_RU.UTF-8")); + auto monday = std::chrono::weekday(1); + fmt::print("{}\n", monday); // prints "Mon" + fmt::print("{:L}\n", monday); // prints "пн" + } + +* Improved locale handling in chrono formatting + (`#2337 `_, + `#2349 `_, + `#2350 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Deprecated ``fmt/locale.h`` moving the formatting functions that take a + locale to ``fmt/format.h`` (``char``) and ``fmt/xchar`` (other overloads). + This doesn't introduce a dependency on ```` so there is virtually no + compile time effect. + +* Made parameter order in ``vformat_to`` consistent with ``format_to`` + (`#2327 `_). + +* Added support for time points with arbitrary durations + (`#2208 `_). For example: + + .. code:: c++ + + #include + + int main() { + using tp = std::chrono::time_point< + std::chrono::system_clock, std::chrono::seconds>; + fmt::print("{:%S}", tp(std::chrono::seconds(42))); + } + + prints "42". + +* Formatting floating-point numbers no longer produces trailing zeros by default + for consistency with ``std::format``. For example: + + .. code:: c++ + + #include + + int main() { + fmt::print("{0:.3}", 1.1); + } + + prints "1.1". Use the ``'#'`` specifier to keep trailing zeros. + +* Dropped a limit on the number of elements in a range and replaced ``{}`` with + ``[]`` as range delimiters for consistency with Python's ``str.format``. + +* The ``'L'`` specifier for locale-specific numeric formatting can now be + combined with presentation specifiers as in ``std::format``. For example: + + .. code:: c++ + + #include + #include + + int main() { + std::locale::global(std::locale("fr_FR.UTF-8")); + fmt::print("{0:.2Lf}", 0.42); + } + + prints "0,42". The deprecated ``'n'`` specifier has been removed. + +* Made the ``0`` specifier ignored for infinity and NaN + (`#2305 `_, + `#2310 `_). + Thanks `@Liedtke (Matthias Liedtke) `_. + +* Made the hexfloat formatting use the right alignment by default + (`#2308 `_, + `#2317 `_). + Thanks `@Liedtke (Matthias Liedtke) `_. + +* Removed the deprecated numeric alignment (``'='``). Use the ``'0'`` specifier + instead. + +* Removed the deprecated ``fmt/posix.h`` header that has been replaced with + ``fmt/os.h``. + +* Removed the deprecated ``format_to_n_context``, ``format_to_n_args`` and + ``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 + major release. + +* Fixed handling of the ``'+'`` specifier in localized formatting + (`#2133 `_). + +* Added support for the ``'s'`` format specifier that gives textual + representation of ``bool`` + (`#2094 `_, + `#2109 `_). For example: + + .. code:: c++ + + #include + + int main() { + fmt::print("{:s}", true); + } + + prints "true". + Thanks `@powercoderlol (Ivan Polyakov) `_. + +* Made ``fmt::ptr`` work with function pointers + (`#2131 `_). For example: + + .. code:: c++ + + #include + + int main() { + fmt::print("My main: {}\n", fmt::ptr(main)); + } + + Thanks `@mikecrowe (Mike Crowe) `_. + +* Fixed ``fmt::formatted_size`` with format string compilation + (`#2141 `_, + `#2161 `_). + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Fixed handling of empty format strings during format string compilation + (`#2042 `_): + + .. code:: c++ + + auto s = fmt::format(FMT_COMPILE("")); + + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Fixed handling of enums in ``fmt::to_string`` + (`#2036 `_). + +* Improved width computation + (`#2033 `_, + `#2091 `_). For example: + + .. code:: c++ + + #include + + int main() { + fmt::print("{:-<10}{}\n", "你好", "世界"); + fmt::print("{:-<10}{}\n", "hello", "world"); + } + + prints + + .. image:: https://user-images.githubusercontent.com/576385/ + 119840373-cea3ca80-beb9-11eb-91e0-54266c48e181.png + + on a modern terminal. + +* The experimental fast output stream (``fmt::ostream``) is now truncated by + default for consistency with ``fopen`` + (`#2018 `_). For example: + + .. code:: c++ + + #include + + int main() { + fmt::ostream out1 = fmt::output_file("guide"); + out1.print("Zaphod"); + out1.close(); + fmt::ostream out2 = fmt::output_file("guide"); + out2.print("Ford"); + } + + writes "Ford" to the file "guide". To preserve the old file content if any + pass ``fmt::file::WRONLY | fmt::file::CREATE`` flags to ``fmt::output_file``. + +* Fixed moving of ``fmt::ostream`` that holds buffered data + (`#2197 `_, + `#2198 `_). + Thanks `@vtta `_. + +* Replaced the ``fmt::system_error`` exception with a function of the same + name that constructs ``std::system_error`` + (`#2266 `_). + +* Replaced the ``fmt::windows_error`` exception with a function of the same + name that constructs ``std::system_error`` with the category returned by + ``fmt::system_category()`` + (`#2274 `_, + `#2275 `_). + The latter is similar to ``std::sytem_category`` but correctly handles UTF-8. + Thanks `@phprus (Vladislav Shchapov) `_. + +* Replaced ``fmt::error_code`` with ``std::error_code`` and made it formattable + (`#2269 `_, + `#2270 `_, + `#2273 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Added speech synthesis support + (`#2206 `_). + +* Made ``format_to`` work with a memory buffer that has a custom allocator + (`#2300 `_). + Thanks `@voxmea `_. + +* Added ``Allocator::max_size`` support to ``basic_memory_buffer``. + (`#1960 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Added wide string support to ``fmt::join`` + (`#2236 `_). + Thanks `@crbrz `_. + +* Made iterators passed to ``formatter`` specializations via a format context + satisfy C++20 ``std::output_iterator`` requirements + (`#2156 `_, + `#2158 `_, + `#2195 `_, + `#2204 `_). + Thanks `@randomnetcat (Jason Cobb) `_. + +* Optimized the ``printf`` implementation + (`#1982 `_, + `#1984 `_, + `#2016 `_, + `#2164 `_). + Thanks `@rimathia `_ and + `@moiwi `_. + +* Improved detection of ``constexpr`` ``char_traits`` + (`#2246 `_, + `#2257 `_). + Thanks `@phprus (Vladislav Shchapov) `_. + +* Fixed writing to ``stdout`` when it is redirected to ``NUL`` on Windows + (`#2080 `_). + +* Fixed exception propagation from iterators + (`#2097 `_). + +* Improved ``strftime`` error handling + (`#2238 `_, + `#2244 `_). + Thanks `@yumeyao `_. + +* Stopped using deprecated GCC UDL template extension. + +* Added ``fmt/args.h`` to the install target + (`#2096 `_). + +* Error messages are now passed to assert when exceptions are disabled + (`#2145 `_). + Thanks `@NobodyXu (Jiahao XU) `_. + +* Added the ``FMT_MASTER_PROJECT`` CMake option to control build and install + targets when {fmt} is included via ``add_subdirectory`` + (`#2098 `_, + `#2100 `_). + Thanks `@randomizedthinking `_. + +* Improved build configuration + (`#2026 `_, + `#2122 `_). + Thanks `@luncliff (Park DongHa) `_ and + `@ibaned (Dan Ibanez) `_. + +* Fixed various warnings and compilation issues + (`#1947 `_, + `#1959 `_, + `#1963 `_, + `#1965 `_, + `#1966 `_, + `#1974 `_, + `#1975 `_, + `#1990 `_, + `#2000 `_, + `#2001 `_, + `#2002 `_, + `#2004 `_, + `#2006 `_, + `#2009 `_, + `#2010 `_, + `#2038 `_, + `#2039 `_, + `#2047 `_, + `#2053 `_, + `#2059 `_, + `#2065 `_, + `#2067 `_, + `#2068 `_, + `#2073 `_, + `#2103 `_ + `#2105 `_ + `#2106 `_, + `#2107 `_, + `#2116 `_ + `#2117 `_, + `#2118 `_ + `#2119 `_, + `#2127 `_, + `#2128 `_, + `#2140 `_, + `#2142 `_, + `#2143 `_, + `#2144 `_, + `#2147 `_, + `#2148 `_, + `#2149 `_, + `#2152 `_, + `#2160 `_, + `#2170 `_, + `#2175 `_, + `#2176 `_, + `#2177 `_, + `#2178 `_, + `#2179 `_, + `#2180 `_, + `#2181 `_, + `#2183 `_, + `#2184 `_, + `#2185 `_, + `#2186 `_, + `#2187 `_, + `#2190 `_, + `#2192 `_, + `#2194 `_, + `#2205 `_, + `#2210 `_, + `#2211 `_, + `#2215 `_, + `#2216 `_, + `#2218 `_, + `#2220 `_, + `#2228 `_, + `#2229 `_, + `#2230 `_, + `#2233 `_, + `#2239 `_, + `#2248 `_, + `#2252 `_, + `#2253 `_, + `#2255 `_, + `#2261 `_, + `#2278 `_, + `#2284 `_, + `#2287 `_, + `#2289 `_, + `#2290 `_, + `#2293 `_, + `#2295 `_, + `#2296 `_, + `#2297 `_, + `#2311 `_, + `#2313 `_, + `#2315 `_, + `#2320 `_, + `#2321 `_, + `#2323 `_, + `#2328 `_, + `#2329 `_, + `#2333 `_, + `#2338 `_, + `#2341 `_). + Thanks `@darklukee `_, + `@fagg (Ashton Fagg) `_, + `@killerbot242 (Lieven de Cock) `_, + `@jgopel (Jonathan Gopel) `_, + `@yeswalrus (Walter Gray) `_, + `@Finkman `_, + `@HazardyKnusperkeks (Björn Schäpers) `_, + `@dkavolis (Daumantas Kavolis) `_ + `@concatime (Issam Maghni) `_, + `@chronoxor (Ivan Shynkarenka) `_, + `@summivox (Yin Zhong) `_, + `@yNeo `_, + `@Apache-HB (Elliot) `_, + `@alexezeder (Alexey Ochapov) `_, + `@toojays (John Steele Scott) `_, + `@Brainy0207 `_, + `@vadz (VZ) `_, + `@imsherlock (Ryan Sherlock) `_, + `@phprus (Vladislav Shchapov) `_, + `@white238 (Chris White) `_, + `@yafshar (Yaser Afshar) `_, + `@BillyDonahue (Billy Donahue) `_, + `@jstaahl `_, + `@denchat `_, + `@DanielaE (Daniela Engert) `_, + `@ilyakurdyukov (Ilya Kurdyukov) `_, + `@ilmai `_, + `@JessyDL (Jessy De Lannoit) `_, + `@sergiud (Sergiu Deitsch) `_, + `@mwinterb `_, + `@sven-herrmann `_, + `@jmelas (John Melas) `_, + `@twoixter (Jose Miguel Pérez) `_, + `@crbrz `_, + `@upsj (Tobias Ribizel) `_. + +* Improved documentation + (`#1986 `_, + `#2051 `_, + `#2057 `_, + `#2081 `_, + `#2084 `_, + `#2312 `_). + Thanks `@imba-tjd (谭九鼎) `_, + `@0x416c69 (AlιAѕѕaѕѕιN) `_, + `@mordante `_. + +* Continuous integration and test improvements + (`#1969 `_, + `#1991 `_, + `#2020 `_, + `#2110 `_, + `#2114 `_, + `#2196 `_, + `#2217 `_, + `#2247 `_, + `#2256 `_, + `#2336 `_, + `#2346 `_). + Thanks `@jgopel (Jonathan Gopel) `_, + `@alexezeder (Alexey Ochapov) `_ and + `@DanielaE (Daniela Engert) `_. + +7.1.3 - 2020-11-24 +------------------ + +* Fixed handling of buffer boundaries in ``format_to_n`` + (`#1996 `_, + `#2029 `_). + +* Fixed linkage errors when linking with a shared library + (`#2011 `_). + +* Reintroduced ostream support to range formatters + (`#2014 `_). + +* Worked around an issue with mixing std versions in gcc + (`#2017 `_). + +7.1.2 - 2020-11-04 +------------------ + +* Fixed floating point formatting with large precision + (`#1976 `_). + +7.1.1 - 2020-11-01 +------------------ + +* Fixed ABI compatibility with 7.0.x + (`#1961 `_). + +* Added the ``FMT_ARM_ABI_COMPATIBILITY`` macro to work around ABI + incompatibility between GCC and Clang on ARM + (`#1919 `_). + +* Worked around a SFINAE bug in GCC 8 + (`#1957 `_). + +* Fixed linkage errors when building with GCC's LTO + (`#1955 `_). + +* Fixed a compilation error when building without ``__builtin_clz`` or equivalent + (`#1968 `_). + Thanks `@tohammer (Tobias Hammer) `_. + +* Fixed a sign conversion warning + (`#1964 `_). + Thanks `@OptoCloud `_. + +7.1.0 - 2020-10-25 +------------------ + +* Switched from `Grisu3 + `_ + to `Dragonbox `_ for the default + floating-point formatting which gives the shortest decimal representation + with round-trip guarantee and correct rounding + (`#1882 `_, + `#1887 `_, + `#1894 `_). This makes {fmt} up to + 20-30x faster than common implementations of ``std::ostringstream`` and + ``sprintf`` on `dtoa-benchmark `_ + and faster than double-conversion and Ryū: + + .. image:: https://user-images.githubusercontent.com/576385/ + 95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png + + It is possible to get even better performance at the cost of larger binary + size by compiling with the ``FMT_USE_FULL_CACHE_DRAGONBOX`` macro set to 1. + + Thanks `@jk-jeon (Junekey Jeon) `_. + +* Added an experimental unsynchronized file output API which, together with + `format string compilation `_, + can give `5-9 times speed up compared to fprintf + `_ + on common platforms (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + auto f = fmt::output_file("guide"); + f.print("The answer is {}.", 42); + } + +* Added a formatter for ``std::chrono::time_point`` + (`#1819 `_, + `#1837 `_). For example + (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + auto now = std::chrono::system_clock::now(); + fmt::print("The time is {:%H:%M:%S}.\n", now); + } + + Thanks `@adamburgess (Adam Burgess) `_. + +* Added support for ranges with non-const ``begin``/``end`` to ``fmt::join`` + (`#1784 `_, + `#1786 `_). For example + (`godbolt `__): + + .. code:: c++ + + #include + #include + + int main() { + using std::literals::string_literals::operator""s; + auto strs = std::array{"a"s, "bb"s, "ccc"s}; + auto range = strs | ranges::views::filter( + [] (const std::string &x) { return x.size() != 2; } + ); + fmt::print("{}\n", fmt::join(range, "")); + } + + prints "accc". + + Thanks `@tonyelewis (Tony E Lewis) `_. + +* Added a ``memory_buffer::append`` overload that takes a range + (`#1806 `_). + Thanks `@BRevzin (Barry Revzin) `_. + +* Improved handling of single code units in ``FMT_COMPILE``. For example: + + .. code:: c++ + + #include + + char* f(char* buf) { + return fmt::format_to(buf, FMT_COMPILE("x{}"), 42); + } + + compiles to just (`godbolt `__): + + .. code:: asm + + _Z1fPc: + movb $120, (%rdi) + xorl %edx, %edx + cmpl $42, _ZN3fmt2v76detail10basic_dataIvE23zero_or_powers_of_10_32E+8(%rip) + movl $3, %eax + seta %dl + subl %edx, %eax + movzwl _ZN3fmt2v76detail10basic_dataIvE6digitsE+84(%rip), %edx + cltq + addq %rdi, %rax + movw %dx, -2(%rax) + ret + + Here a single ``mov`` instruction writes ``'x'`` (``$120``) to the output + buffer. + +* Added dynamic width support to format string compilation + (`#1809 `_). + +* Improved error reporting for unformattable types: now you'll get the type name + directly in the error message instead of the note: + + .. code:: c++ + + #include + + struct how_about_no {}; + + int main() { + fmt::print("{}", how_about_no()); + } + + Error (`godbolt `__): + + ``fmt/core.h:1438:3: error: static_assert failed due to requirement + 'fmt::v7::formattable()' "Cannot format an argument. + To make type T formattable provide a formatter specialization: + https://fmt.dev/latest/api.html#udt" + ...`` + +* Added the `make_args_checked `_ + function template that allows you to write formatting functions with + compile-time format string checks and avoid binary code bloat + (`godbolt `__): + + .. code:: c++ + + void vlog(const char* file, int line, fmt::string_view format, + fmt::format_args args) { + fmt::print("{}: {}: ", file, line); + fmt::vprint(format, args); + } + + template + void log(const char* file, int line, const S& format, Args&&... args) { + vlog(file, line, format, + fmt::make_args_checked(format, args...)); + } + + #define MY_LOG(format, ...) \ + log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) + + MY_LOG("invalid squishiness: {}", 42); + +* Replaced ``snprintf`` fallback with a faster internal IEEE 754 ``float`` and + ``double`` formatter for arbitrary precision. For example + (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + fmt::print("{:.500}\n", 4.9406564584124654E-324); + } + + prints + + ``4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208324319360923829e-324``. + +* Made ``format_to_n`` and ``formatted_size`` part of the `core API + `__ + (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + char buffer[10]; + auto result = fmt::format_to_n(buffer, sizeof(buffer), "{}", 42); + } + +* Added ``fmt::format_to_n`` overload with format string compilation + (`#1764 `_, + `#1767 `_, + `#1869 `_). For example + (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + char buffer[8]; + fmt::format_to_n(buffer, sizeof(buffer), FMT_COMPILE("{}"), 42); + } + + Thanks `@Kurkin (Dmitry Kurkin) `_, + `@alexezeder (Alexey Ochapov) `_. + +* Added ``fmt::format_to`` overload that take ``text_style`` + (`#1593 `_, + `#1842 `_, + `#1843 `_). For example + (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + std::string out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), + "The answer is {}.", 42); + } + + Thanks `@Naios (Denis Blank) `_. + +* Made the ``'#'`` specifier emit trailing zeros in addition to the decimal + point (`#1797 `_). For example + (`godbolt `__): + + .. code:: c++ + + #include + + int main() { + fmt::print("{:#.2g}", 0.5); + } + + prints ``0.50``. + +* Changed the default floating point format to not include ``.0`` for + consistency with ``std::format`` and ``std::to_chars`` + (`#1893 `_, + `#1943 `_). It is possible to get + the decimal point and trailing zero with the ``#`` specifier. + +* Fixed an issue with floating-point formatting that could result in addition of + a non-significant trailing zero in rare cases e.g. ``1.00e-34`` instead of + ``1.0e-34`` (`#1873 `_, + `#1917 `_). + +* Made ``fmt::to_string`` fallback on ``ostream`` insertion operator if + the ``formatter`` specialization is not provided + (`#1815 `_, + `#1829 `_). + Thanks `@alexezeder (Alexey Ochapov) `_. + +* Added support for the append mode to the experimental file API and + improved ``fcntl.h`` detection. + (`#1847 `_, + `#1848 `_). + Thanks `@t-wiser `_. + +* Fixed handling of types that have both an implicit conversion operator and + an overloaded ``ostream`` insertion operator + (`#1766 `_). + +* Fixed a slicing issue in an internal iterator type + (`#1822 `_). + Thanks `@BRevzin (Barry Revzin) `_. + +* Fixed an issue in locale-specific integer formatting + (`#1927 `_). + +* Fixed handling of exotic code unit types + (`#1870 `_, + `#1932 `_). + +* Improved ``FMT_ALWAYS_INLINE`` + (`#1878 `_). + Thanks `@jk-jeon (Junekey Jeon) `_. + +* Removed dependency on ``windows.h`` + (`#1900 `_). + Thanks `@bernd5 (Bernd Baumanns) `_. + +* Optimized counting of decimal digits on MSVC + (`#1890 `_). + Thanks `@mwinterb `_. + +* Improved documentation + (`#1772 `_, + `#1775 `_, + `#1792 `_, + `#1838 `_, + `#1888 `_, + `#1918 `_, + `#1939 `_). + Thanks `@leolchat (Léonard Gérard) `_, + `@pepsiman (Malcolm Parsons) `_, + `@Klaim (Joël Lamotte) `_, + `@ravijanjam (Ravi J) `_, + `@francesco-st `_, + `@udnaan (Adnan) `_. + +* Added the ``FMT_REDUCE_INT_INSTANTIATIONS`` CMake option that reduces the + binary code size at the cost of some integer formatting performance. This can + be useful for extremely memory-constrained embedded systems + (`#1778 `_, + `#1781 `_). + Thanks `@kammce (Khalil Estell) `_. + +* Added the ``FMT_USE_INLINE_NAMESPACES`` macro to control usage of inline + namespaces (`#1945 `_). + Thanks `@darklukee `_. + +* Improved build configuration + (`#1760 `_, + `#1770 `_, + `#1779 `_, + `#1783 `_, + `#1823 `_). + Thanks `@dvetutnev (Dmitriy Vetutnev) `_, + `@xvitaly (Vitaly Zaitsev) `_, + `@tambry (Raul Tambre) `_, + `@medithe `_, + `@martinwuehrer (Martin Wührer) `_. + +* Fixed various warnings and compilation issues + (`#1790 `_, + `#1802 `_, + `#1808 `_, + `#1810 `_, + `#1811 `_, + `#1812 `_, + `#1814 `_, + `#1816 `_, + `#1817 `_, + `#1818 `_, + `#1825 `_, + `#1836 `_, + `#1855 `_, + `#1856 `_, + `#1860 `_, + `#1877 `_, + `#1879 `_, + `#1880 `_, + `#1896 `_, + `#1897 `_, + `#1898 `_, + `#1904 `_, + `#1908 `_, + `#1911 `_, + `#1912 `_, + `#1928 `_, + `#1929 `_, + `#1935 `_ + `#1937 `_, + `#1942 `_, + `#1949 `_). + Thanks `@TheQwertiest `_, + `@medithe `_, + `@martinwuehrer (Martin Wührer) `_, + `@n16h7hunt3r `_, + `@Othereum (Seokjin Lee) `_, + `@gsjaardema (Greg Sjaardema) `_, + `@AlexanderLanin (Alexander Lanin) `_, + `@gcerretani (Giovanni Cerretani) `_, + `@chronoxor (Ivan Shynkarenka) `_, + `@noizefloor (Jan Schwers) `_, + `@akohlmey (Axel Kohlmeyer) `_, + `@jk-jeon (Junekey Jeon) `_, + `@rimathia `_, + `@rglarix (Riccardo Ghetta (larix)) `_, + `@moiwi `_, + `@heckad (Kazantcev Andrey) `_, + `@MarcDirven `_. + `@BartSiwek (Bart Siwek) `_, + `@darklukee `_. + 7.0.3 - 2020-08-06 ------------------ @@ -49,7 +1121,7 @@ `_. * Added a simpler and more efficient `format string compilation API - `_: + `_: .. code:: c++ @@ -188,7 +1260,7 @@ Thanks `@BRevzin (Barry Revzin) `_. -* Added support for named args, ``clear`` and ``reserve`` to +* Added support for named arguments, ``clear`` and ``reserve`` to ``dynamic_format_arg_store`` (`#1655 `_, `#1663 `_, @@ -1705,7 +2777,7 @@ * Implemented ``constexpr`` parsing of format strings and `compile-time format string checks - `_. For + `_. For example .. code:: c++ @@ -1766,7 +2838,7 @@ throw format_error("invalid specifier"); * Added `iterator support - `_: + `_: .. code:: c++ @@ -1777,7 +2849,7 @@ fmt::format_to(std::back_inserter(out), "{}", 42); * Added the `format_to_n - `_ + `_ function that restricts the output to the specified number of characters (`#298 `_): @@ -1788,7 +2860,7 @@ // out == "1234" (without terminating '\0') * Added the `formatted_size - `_ + `_ function for computing the output size: .. code:: c++ @@ -1798,7 +2870,7 @@ auto size = fmt::formatted_size("{}", 12345); // size == 5 * Improved compile times by reducing dependencies on standard headers and - providing a lightweight `core API `_: + providing a lightweight `core API `_: .. code:: c++ @@ -1810,7 +2882,7 @@ `_. * Added the `make_format_args - `_ + `_ function for capturing formatting arguments: .. code:: c++ @@ -1892,7 +2964,7 @@ fmt::format("{} {two}", 1, fmt::arg("two", 2)); * Removed the write API in favor of the `format API - `_ with compile-time handling of + `_ with compile-time handling of format strings. * Disallowed formatting of multibyte strings into a wide character target diff --git a/README.rst b/README.rst index 722a65eb..02c849c7 100644 --- a/README.rst +++ b/README.rst @@ -1,54 +1,69 @@ {fmt} ===== -.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master - :target: https://travis-ci.org/fmtlib/fmt +.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg + :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux + +.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg + :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos + +.. 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 :target: https://ci.appveyor.com/project/vitaut/fmt -.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg - :alt: fmt is continuously fuzzed att oss-fuzz +.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg + :alt: fmt is continuously fuzzed at oss-fuzz :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\ colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\ - Summary&q=proj%3Dlibfmt&can=1 + Summary&q=proj%3Dfmt&can=1 .. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg :alt: Ask questions at StackOverflow with the tag fmt :target: https://stackoverflow.com/questions/tagged/fmt -**{fmt}** is an open-source formatting library for C++. -It can be used as a safe and fast alternative to (s)printf and iostreams. +**{fmt}** is an open-source formatting library providing a fast and safe +alternative to C stdio and C++ iostreams. -`Documentation `__ +If you like this project, please consider donating to the BYSOL +Foundation that helps victims of political repressions in Belarus: +https://bysol.org/en/bs/general/. + +`Documentation `__ Q&A: ask questions on `StackOverflow with the tag fmt `_. +Try {fmt} in `Compiler Explorer `_. + Features -------- -* Simple `format API `_ with positional arguments +* Simple `format API `_ with positional arguments for localization * Implementation of `C++20 std::format `__ -* `Format string syntax `_ similar to the one - of Python's +* `Format string syntax `_ similar to Python's `format `_ +* Fast IEEE 754 floating-point formatter with correct rounding, shortness and + round-trip guarantees * Safe `printf implementation - `_ including - the POSIX extension for positional arguments -* Extensibility: support for user-defined types + `_ including the POSIX + extension for positional arguments +* Extensibility: `support for user-defined types + `_ * High performance: faster than common standard library implementations of - `printf `_, - iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ and - `Converting a hundred million integers to strings per second + ``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ + and `Converting a hundred million integers to strings per second `_ -* Small code size both in terms of source code (the minimum configuration - consists of just three header files, ``core.h``, ``format.h`` and - ``format-inl.h``) and compiled code. See `Compile time and code bloat`_ -* Reliability: the library has an extensive set of `unit tests - `_ and is continuously fuzzed +* Small code size both in terms of source code with the minimum configuration + consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``, + and compiled code; see `Compile time and code bloat`_ +* Reliability: the library has an extensive set of `tests + `_ and is `continuously fuzzed + `_ * Safety: the library is fully type safe, errors in format strings can be reported at compile time, automatic memory management prevents buffer overflow errors @@ -57,18 +72,17 @@ Features `_ * `Portability `_ with consistent output across platforms and support for older compilers -* Clean warning-free codebase even on high warning levels - (``-Wall -Wextra -pedantic``) +* Clean warning-free codebase even on high warning levels such as + ``-Wall -Wextra -pedantic`` * Locale-independence by default -* Support for wide strings * Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro -See the `documentation `_ for more details. +See the `documentation `_ for more details. Examples -------- -Print ``Hello, world!`` to ``stdout``: +**Print to stdout** (`run `_) .. code:: c++ @@ -78,100 +92,95 @@ Print ``Hello, world!`` to ``stdout``: fmt::print("Hello, world!\n"); } -Format a string: +**Format a string** (`run `_) .. code:: c++ std::string s = fmt::format("The answer is {}.", 42); // s == "The answer is 42." -Format a string using positional arguments: +**Format a string using positional arguments** (`run `_) .. code:: c++ std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); // s == "I'd rather be happy than right." -Print a chrono duration: +**Print chrono durations** (`run `_) .. code:: c++ #include int main() { - using namespace std::chrono_literals; - fmt::print("Elapsed time: {}", 42ms); + using namespace std::literals::chrono_literals; + fmt::print("Default format: {} {}\n", 42s, 100ms); + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); } -prints "Elapsed time: 42ms". +Output:: -Check a format string at compile time: + Default format: 42s 100ms + strftime-like format: 03:15:30 + +**Print a container** (`run `_) .. code:: c++ - // test.cc - #include - std::string s = format(FMT_STRING("{:d}"), "hello"); + #include + #include -gives a compile-time error because ``d`` is an invalid format specifier for a -string. - -Use {fmt} as a safe portable replacement for ``itoa`` -(`godbolt `_): - -.. code:: c++ - - fmt::memory_buffer buf; - format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10) - format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16) - // access the string with to_string(buf) or buf.data() - -Format objects of user-defined types via a simple `extension API -`_: - -.. code:: c++ - - #include - - struct date { - int year, month, day; - }; - - template <> - struct fmt::formatter { - constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } - - template - auto format(const date& d, FormatContext& ctx) { - return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day); - } - }; - - std::string s = fmt::format("The date is {}", date{2012, 12, 9}); - // s == "The date is 2012-12-9" - -Create your own functions similar to `format -`_ and -`print `_ -which take arbitrary arguments (`godbolt `_): - -.. code:: c++ - - // Prints formatted error message. - void vreport_error(const char* format, fmt::format_args args) { - fmt::print("Error: "); - fmt::vprint(format, args); - } - template - void report_error(const char* format, const Args & ... args) { - vreport_error(format, fmt::make_format_args(args...)); + int main() { + std::vector v = {1, 2, 3}; + fmt::print("{}\n", v); } - report_error("file not found: {}", path); +Output:: -Note that ``vreport_error`` is not parameterized on argument types which can -improve compile times and reduce code size compared to a fully parameterized -version. + [1, 2, 3] + +**Check a format string at compile time** + +.. code:: c++ + + std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number"); + +This gives a compile-time error because ``d`` is an invalid format specifier for +a string. + +**Write a file from a single thread** + +.. code:: c++ + + #include + + int main() { + auto out = fmt::output_file("guide.txt"); + out.print("Don't {}", "Panic"); + } + +This can be `5 to 9 times faster than fprintf +`_. + +**Print with colors and text styles** + +.. code:: c++ + + #include + + int main() { + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, + "Hello, {}!\n", "world"); + fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | + fmt::emphasis::underline, "Hello, {}!\n", "мир"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "Hello, {}!\n", "世界"); + } + +Output on a modern terminal: + +.. image:: https://user-images.githubusercontent.com/ + 576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png Benchmarks ---------- @@ -198,12 +207,14 @@ 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 10x faster than ``std::ostringstream`` and ``sprintf`` on +{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on floating-point formatting (`dtoa-benchmark `_) -and faster than `double-conversion `_: +and faster than `double-conversion `_ and +`ryu `_: -.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png - :target: https://fmt.dev/unknown_mac64_clang10.0.html +.. image:: https://user-images.githubusercontent.com/576385/ + 95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png + :target: https://fmt.dev/unknown_mac64_clang12.0.html Compile time and code bloat ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -278,34 +289,46 @@ Then you can run the speed test:: or the bloat test:: $ make bloat-test + +Migrating code +-------------- + +`clang-tidy-fmt `_ provides clang +tidy checks for converting occurrences of ``printf`` and ``fprintf`` to +``fmt::print``. Projects using this library --------------------------- -* `0 A.D. `_: A free, open-source, cross-platform +* `0 A.D. `_: a free, open-source, cross-platform real-time strategy game +* `2GIS `_: free business listings with a city map + * `AMPL/MP `_: - An open-source library for mathematical programming + an open-source library for mathematical programming * `Aseprite `_: - Animated sprite editor & pixel art tool + animated sprite editor & pixel art tool -* `AvioBook `_: A comprehensive aircraft +* `AvioBook `_: a comprehensive aircraft operations suite -* `Celestia `_: Real-time 3D visualization of space +* `Blizzard Battle.net `_: an online gaming platform + +* `Celestia `_: real-time 3D visualization of space -* `Ceph `_: A scalable distributed storage system +* `Ceph `_: a scalable distributed storage system -* `ccache `_: A compiler cache +* `ccache `_: a compiler cache -* `ClickHouse `_: analytical database management system +* `ClickHouse `_: analytical database + management system -* `CUAUV `_: Cornell University's autonomous underwater +* `CUAUV `_: Cornell University's autonomous underwater vehicle -* `Drake `_: A planning, control, and analysis toolbox +* `Drake `_: a planning, control, and analysis toolbox for nonlinear dynamical systems (MIT) * `Envoy `_: C++ L7 proxy and communication bus @@ -313,71 +336,82 @@ Projects using this library * `FiveM `_: a modification framework for GTA V +* `fmtlog `_: a performant fmtlib-style + logging library with latency in nanoseconds + * `Folly `_: Facebook open-source library +* `Grand Mountain Adventure + `_: + A beautiful open-world ski & snowboarding game + * `HarpyWar/pvpgn `_: Player vs Player Gaming Network with tweaks -* `KBEngine `_: An open-source MMOG server engine +* `KBEngine `_: an open-source MMOG server + engine -* `Keypirinha `_: A semantic launcher for Windows +* `Keypirinha `_: a semantic launcher for Windows -* `Kodi `_ (formerly xbmc): Home theater software +* `Kodi `_ (formerly xbmc): home theater software -* `Knuth `_: High-performance Bitcoin full-node +* `Knuth `_: high-performance Bitcoin full-node * `Microsoft Verona `_: - Research programming language for concurrent ownership + research programming language for concurrent ownership -* `MongoDB `_: Distributed document database +* `MongoDB `_: distributed document database -* `MongoDB Smasher `_: A small tool to +* `MongoDB Smasher `_: a small tool to generate randomized datasets -* `OpenSpace `_: An open-source +* `OpenSpace `_: an open-source astrovisualization framework * `PenUltima Online (POL) `_: - An MMO server, compatible with most Ultima Online clients + an MMO server, compatible with most Ultima Online clients -* `PyTorch `_: An open-source machine +* `PyTorch `_: an open-source machine learning library -* `quasardb `_: A distributed, high-performance, +* `quasardb `_: a distributed, high-performance, associative database + +* `Quill `_: asynchronous low-latency logging library -* `readpe `_: Read Portable Executable +* `QKW `_: generalizing aliasing to simplify + navigation, and executing complex multi-line terminal command sequences -* `redis-cerberus `_: A Redis cluster +* `redis-cerberus `_: a Redis cluster proxy -* `redpanda `_: A 10x faster Kafka® replacement +* `redpanda `_: a 10x faster Kafka® replacement for mission critical systems written in C++ -* `rpclib `_: A modern C++ msgpack-RPC server and client +* `rpclib `_: a modern C++ msgpack-RPC server and client library * `Salesforce Analytics Cloud `_: - Business intelligence software + business intelligence software -* `Scylla `_: A Cassandra-compatible NoSQL data store +* `Scylla `_: a Cassandra-compatible NoSQL data store that can handle 1 million transactions per second on a single server -* `Seastar `_: An advanced, open-source C++ +* `Seastar `_: an advanced, open-source C++ framework for high-performance server applications on modern hardware -* `spdlog `_: Super fast C++ logging library +* `spdlog `_: super fast C++ logging library -* `Stellar `_: Financial platform +* `Stellar `_: financial platform -* `Touch Surgery `_: Surgery simulator +* `Touch Surgery `_: surgery simulator -* `TrinityCore `_: Open-source +* `TrinityCore `_: open-source MMORPG framework -* `Windows Terminal `_: The new Windows - Terminal +* `Windows Terminal `_: the new Windows + terminal `More... `_ @@ -435,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/CMakeLists.txt b/doc/CMakeLists.txt index 108aa71e..06848450 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -4,10 +4,14 @@ if (NOT DOXYGEN) return () endif () +find_package(PythonInterp QUIET REQUIRED) + add_custom_target(doc - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py + ${FMT_VERSION} SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html) +include(GNUInstallDirs) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/ - DESTINATION share/doc/fmt OPTIONAL + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL PATTERN ".doctrees" EXCLUDE) diff --git a/doc/api.rst b/doc/api.rst index ca016895..e295f171 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -6,17 +6,18 @@ API Reference The {fmt} library API consists of the following parts: -* :ref:`fmt/core.h `: the core API providing argument handling - facilities and a lightweight subset of formatting functions -* :ref:`fmt/format.h `: the full format API providing compile-time - format string checks, wide string, output iterator and user-defined type - support -* :ref:`fmt/ranges.h `: additional formatting support for ranges - and tuples +* :ref:`fmt/core.h `: the core API providing main formatting functions + for ``char``/UTF-8 with compile-time checks and minimal dependencies +* :ref:`fmt/format.h `: the full format API providing additional + formatting functions and locale support +* :ref:`fmt/ranges.h `: formatting of ranges and tuples * :ref:`fmt/chrono.h `: date and time formatting * :ref:`fmt/compile.h `: format string compilation +* :ref:`fmt/color.h `: terminal color and text style +* :ref:`fmt/os.h `: system APIs * :ref:`fmt/ostream.h `: ``std::ostream`` support * :ref:`fmt/printf.h `: ``printf`` formatting +* :ref:`fmt/xchar.h `: optional ``wchar_t`` support All functions and types provided by the library reside in namespace ``fmt`` and macros have prefix ``FMT_``. @@ -26,34 +27,56 @@ macros have prefix ``FMT_``. Core API ======== -``fmt/core.h`` defines the core API which provides argument handling facilities -and a lightweight subset of formatting functions. In the header-only mode -include ``fmt/format.h`` instead of ``fmt/core.h``. +``fmt/core.h`` defines the core API which provides main formatting functions for +``char``/UTF-8 with compile-time checks. It has minimal include dependencies for +better compile times. This header is only beneficial when using {fmt} as a +library and not in the header-only mode. The following functions use :ref:`format string syntax ` similar to that of Python's `str.format -`_. -They take *format_str* and *args* as arguments. +`_. +They take *fmt* and *args* as arguments. -*format_str* is a format string that contains literal text and replacement +*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 *format_str* doesn't +arguments in the resulting string. A function taking *fmt* doesn't participate in an overload resolution if the latter is not a string. *args* is an argument list representing objects to be formatted. .. _format: -.. doxygenfunction:: format(const S&, Args&&...) -.. doxygenfunction:: vformat(const S&, basic_format_args>>) +.. doxygenfunction:: format(format_string fmt, T&&... args) -> std::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:: formatted_size(format_string fmt, T&&... args) -> size_t + +.. doxygenstruct:: fmt::format_to_n_result + :members: .. _print: -.. doxygenfunction:: print(const S&, Args&&...) -.. doxygenfunction:: vprint(string_view, format_args) +.. doxygenfunction:: fmt::print(format_string fmt, T&&... args) +.. doxygenfunction:: vprint(string_view fmt, format_args args) -.. doxygenfunction:: print(std::FILE *, const S&, Args&&...) -.. doxygenfunction:: vprint(std::FILE *, string_view, format_args) +.. doxygenfunction:: print(std::FILE *f, format_string fmt, T&&... args) +.. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args) + +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. + +.. doxygendefine:: FMT_STRING + +To force the use of compile-time checks, define the preprocessor variable +``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING`` +will fail to compile with regular strings. Runtime-checked +formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc. Named Arguments --------------- @@ -65,6 +88,35 @@ Named arguments are not supported in compile-time checks at the moment. Argument Lists -------------- +You can create your own formatting function with compile-time checks and small +binary footprint, for example (https://godbolt.org/z/oba4Mc): + +.. code:: c++ + + #include + + void vlog(const char* file, int line, fmt::string_view format, + fmt::format_args args) { + fmt::print("{}: {}: ", file, line); + fmt::vprint(format, args); + } + + template + void log(const char* file, int line, const S& format, Args&&... args) { + vlog(file, line, format, + fmt::make_args_checked(format, args...)); + } + + #define MY_LOG(format, ...) \ + log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) + + MY_LOG("invalid squishiness: {}", 42); + +Note that ``vlog`` is not parameterized on argument types which improves compile +times and reduces binary code size compared to a fully parameterized version. + +.. doxygenfunction:: fmt::make_args_checked(const S&, const remove_reference_t&...) + .. doxygenfunction:: fmt::make_format_args(const Args&...) .. doxygenclass:: fmt::format_arg_store @@ -76,11 +128,16 @@ Argument Lists .. doxygenclass:: fmt::basic_format_args :members: -.. doxygenstruct:: fmt::format_args +.. doxygentypedef:: fmt::format_args .. doxygenclass:: fmt::basic_format_arg :members: +.. doxygenclass:: fmt::basic_format_context + :members: + +.. doxygentypedef:: fmt::format_context + Compatibility ------------- @@ -88,12 +145,11 @@ Compatibility :members: .. doxygentypedef:: fmt::string_view -.. doxygentypedef:: fmt::wstring_view Locale ------ -All formatting is locale-independent by default. Use the ``'n'`` format +All formatting is locale-independent by default. Use the ``'L'`` format specifier to insert the appropriate number separator characters from the locale:: @@ -108,17 +164,10 @@ locale:: Format API ========== -``fmt/format.h`` defines the full format API providing compile-time format -string checks, wide string, output iterator and user-defined type support. +``fmt/format.h`` defines the full format API providing additional formatting +functions and locale support. -Compile-time Format String Checks ---------------------------------- - -Compile-time checks are supported for built-in and string types as well as -user-defined types with ``constexpr`` ``parse`` functions in their ``formatter`` -specializations. - -.. doxygendefine:: FMT_STRING +.. _udt: Formatting User-defined Types ----------------------------- @@ -130,14 +179,12 @@ template and implement ``parse`` and ``format`` methods:: struct point { double x, y; }; - template <> - struct fmt::formatter { + template <> struct fmt::formatter { // Presentation format: 'f' - fixed, 'e' - exponential. char presentation = 'f'; // Parses format specifications of the form ['f' | 'e']. - constexpr auto parse(format_parse_context& ctx) { - // auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11 + constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { // [ctx.begin(), ctx.end()) is a character range that contains a part of // the format string starting from the format specifications to be parsed, // e.g. in @@ -164,8 +211,7 @@ template and implement ``parse`` and ``format`` methods:: // Formats the point p using the parsed format specification (presentation) // stored in this formatter. template - auto format(const point& p, FormatContext& ctx) { - // auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11 + auto format(const point& p, FormatContext& ctx) -> decltype(ctx.out()) { // ctx.out() is an output iterator to write to. return format_to( ctx.out(), @@ -237,44 +283,36 @@ You can also write a formatter for a hierarchy of classes:: fmt::print("{}", a); // prints "B" } +If a type provides both a ``formatter`` specialization and an implicit +conversion to a formattable type, the specialization takes precedence over the +conversion. + .. doxygenclass:: fmt::basic_format_parse_context :members: -Output Iterator Support ------------------------ - -.. doxygenfunction:: fmt::format_to(OutputIt, const S&, Args&&...) -.. doxygenfunction:: fmt::format_to_n(OutputIt, size_t, const S&, const Args&...) -.. doxygenstruct:: fmt::format_to_n_result - :members: - Literal-based API ----------------- The following user-defined literals are defined in ``fmt/format.h``. -.. doxygenfunction:: operator""_format(const char *, size_t) +.. doxygenfunction:: operator""_format(const char *s, size_t n) -> detail::udl_formatter -.. doxygenfunction:: operator""_a(const char *, size_t) +.. doxygenfunction:: operator""_a(const char *s, size_t) -> detail::udl_arg Utilities --------- -.. doxygenstruct:: fmt::is_char +.. doxygenfunction:: fmt::ptr(T p) -> const void* +.. doxygenfunction:: fmt::ptr(const std::unique_ptr &p) -> const void* +.. doxygenfunction:: fmt::ptr(const std::shared_ptr &p) -> const void* -.. doxygentypedef:: fmt::char_t +.. doxygenfunction:: fmt::to_string(const T &value) -> std::string -.. doxygenfunction:: fmt::formatted_size(string_view, const Args&...) +.. doxygenfunction:: fmt::to_string_view(const Char *s) -> basic_string_view -.. doxygenfunction:: fmt::to_string(const T&) +.. doxygenfunction:: fmt::join(Range &&range, string_view sep) -> join_view, detail::sentinel_t> -.. doxygenfunction:: fmt::to_wstring(const T&) - -.. doxygenfunction:: fmt::to_string_view(const Char *) - -.. doxygenfunction:: fmt::join(const Range&, string_view) - -.. doxygenfunction:: fmt::join(It, Sentinel, string_view) +.. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view .. doxygenclass:: fmt::detail::buffer :members: @@ -286,20 +324,14 @@ Utilities System Errors ------------- -fmt does not use ``errno`` to communicate errors to the user, but it may call -system functions which set ``errno``. Users should not make any assumptions about -the value of ``errno`` being preserved by library functions. +{fmt} does not use ``errno`` to communicate errors to the user, but it may call +system functions which set ``errno``. Users should not make any assumptions +about the value of ``errno`` being preserved by library functions. -.. doxygenclass:: fmt::system_error - :members: +.. doxygenfunction:: fmt::system_error .. doxygenfunction:: fmt::format_system_error -.. doxygenclass:: fmt::windows_error - :members: - -.. _formatstrings: - Custom Allocators ----------------- @@ -330,10 +362,10 @@ allocator:: return vformat(alloc, format_str, fmt::make_format_args(args...)); } -The allocator will be used for the output container only. If you are using named -arguments, the container that stores pointers to them will be allocated using -the default allocator. Also floating-point formatting falls back on ``sprintf`` -which may do allocations. +The allocator will be used for the output container only. Formatting functions +normally don't do any allocations for built-in and string types except for +non-default floating-point formatting that occasionally falls back on +``sprintf``. .. _ranges-api: @@ -365,41 +397,84 @@ Using ``fmt::join``, you can separate tuple elements with a custom separator:: Date and Time Formatting ======================== -The library supports `strftime -`_-like date and time -formatting:: +``fmt/chrono.h`` provides formatters for + +* `std::chrono::duration `_ +* `std::chrono::time_point + `_ +* `std::tm `_ + +The format syntax is described in :ref:`chrono-specs`. + +**Example**:: #include - std::time_t t = std::time(nullptr); - // Prints "The date is 2016-04-29." (with the current date) - fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); + int main() { + std::time_t t = std::time(nullptr); -The format string syntax is described in the documentation of -`strftime `_. + // Prints "The date is 2020-11-07." (with the current date): + fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); + + using namespace std::literals::chrono_literals; + + // Prints "Default format: 42s 100ms": + fmt::print("Default format: {} {}\n", 42s, 100ms); + + // Prints "strftime-like format: 03:15:30": + fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); + } + +.. doxygenfunction:: localtime(std::time_t time) + +.. doxygenfunction:: gmtime(std::time_t time) .. _compile-api: Format string compilation ========================= -``fmt/compile.h`` provides format string compilation support. Format strings -are parsed at compile time and converted into efficient formatting code. This -supports arguments of built-in and string types as well as user-defined types -with ``constexpr`` ``parse`` functions in their ``formatter`` specializations. -Format string compilation can generate more binary code compared to the default -API and is only recommended in places where formatting is a performance -bottleneck. +``fmt/compile.h`` provides format string compilation support when using +``FMT_COMPILE``. Format strings are parsed, checked and converted into efficient +formatting code at compile-time. This supports arguments of built-in and string +types as well as user-defined types with ``constexpr`` ``parse`` functions in +their ``formatter`` specializations. Format string compilation can generate more +binary code compared to the default API and is only recommended in places where +formatting is a performance bottleneck. .. doxygendefine:: FMT_COMPILE +.. _color-api: + +Terminal color and text style +============================= + +``fmt/color.h`` provides support for terminal color and text style output. + +.. doxygenfunction:: print(const text_style &ts, const S &format_str, const Args&... args) + +.. doxygenfunction:: fg(detail::color_type) + +.. doxygenfunction:: bg(detail::color_type) + +.. _os-api: + +System APIs +=========== + +.. doxygenclass:: fmt::ostream + :members: + +.. doxygenfunction:: fmt::windows_error + :members: + .. _ostream-api: ``std::ostream`` Support ======================== ``fmt/ostream.h`` provides ``std::ostream`` support including formatting of -user-defined types that have overloaded ``operator<<``:: +user-defined types that have an overloaded insertion operator (``operator<<``):: #include @@ -416,7 +491,10 @@ user-defined types that have overloaded ``operator<<``:: std::string s = fmt::format("The date is {}", date(2012, 12, 9)); // s == "The date is 2012-12-9" -.. doxygenfunction:: print(std::basic_ostream&, const S&, Args&&...) +{fmt} only supports insertion operators that are defined in the same namespaces +as the types they format and can be found with the argument-dependent lookup. + +.. doxygenfunction:: print(std::basic_ostream &os, const S &format_str, Args&&... args) .. _printf-api: @@ -425,18 +503,32 @@ user-defined types that have overloaded ``operator<<``:: The header ``fmt/printf.h`` provides ``printf``-like formatting functionality. The following functions use `printf format string syntax -`_ with +`_ with the POSIX extension for positional arguments. Unlike their standard counterparts, the ``fmt`` functions are type-safe and throw an exception if an argument type doesn't match its format specification. -.. doxygenfunction:: printf(const S&, const Args&...) +.. doxygenfunction:: printf(const S &format_str, const T&... args) -.. doxygenfunction:: fprintf(std::FILE *, const S&, const Args&...) +.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int -.. doxygenfunction:: fprintf(std::basic_ostream&, const S&, const Args&...) +.. doxygenfunction:: sprintf(const S&, const T&...) -.. doxygenfunction:: sprintf(const S&, const Args&...) +.. _xchar-api: + +``wchar_t`` Support +=================== + +The optional header ``fmt/wchar_t.h`` provides support for ``wchar_t`` and +exotic character types. + +.. doxygenstruct:: fmt::is_char + +.. doxygentypedef:: fmt::wstring_view + +.. doxygentypedef:: fmt::wformat_context + +.. doxygenfunction:: fmt::to_wstring(const T &value) Compatibility with C++20 ``std::format`` ======================================== @@ -447,9 +539,6 @@ differences: * Names are defined in the ``fmt`` namespace instead of ``std`` to avoid collisions with standard library implementations. -* The ``'L'`` format specifier cannot be combined with presentation specifiers - yet. * Width calculation doesn't use grapheme clusterization. The latter has been implemented in a separate branch but hasn't been integrated yet. -* Chrono formatting doesn't support C++20 date types since they are not provided - by standard library implementations. +* Most C++20 chrono types are not supported yet. diff --git a/doc/build.py b/doc/build.py index 06e105aa..406bb8cf 100755 --- a/doc/build.py +++ b/doc/build.py @@ -1,63 +1,33 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Build the documentation. -from __future__ import print_function -import errno, os, shutil, sys, tempfile -from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE -from distutils.version import LooseVersion +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'] +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'] -def pip_install(package, commit=None, **kwargs): - "Install package using pip." - min_version = kwargs.get('min_version') - if min_version: - from pkg_resources import get_distribution, DistributionNotFound - try: - installed_version = get_distribution(os.path.basename(package)).version - if LooseVersion(installed_version) >= min_version: - print('{} {} already installed'.format(package, min_version)) - return - except DistributionNotFound: - pass - if commit: - package = 'git+https://github.com/{0}.git@{1}'.format(package, commit) - print('Installing {0}'.format(package)) - check_call(['pip', 'install', package]) +class Pip: + def __init__(self, venv_dir): + self.path = os.path.join(venv_dir, 'bin', 'pip') -def create_build_env(dirname='virtualenv'): + def install(self, package, commit=None): + "Install package using pip." + if commit: + package = 'git+https://github.com/{0}.git@{1}'.format(package, commit) + print('Installing {0}'.format(package)) + check_call([self.path, 'install', package]) + +def create_build_env(venv_dir='virtualenv'): # Create virtualenv. - if not os.path.exists(dirname): - check_call(['virtualenv', dirname]) - import sysconfig - scripts_dir = os.path.basename(sysconfig.get_path('scripts')) - activate_this_file = os.path.join(dirname, scripts_dir, 'activate_this.py') - with open(activate_this_file) as f: - exec(f.read(), dict(__file__=activate_this_file)) - # Import get_distribution after activating virtualenv to get info about - # the correct packages. - from pkg_resources import get_distribution, DistributionNotFound - # Upgrade pip because installation of sphinx with pip 1.1 available on Travis - # is broken (see #207) and it doesn't support the show command. - pip_version = get_distribution('pip').version - if LooseVersion(pip_version) < LooseVersion('1.5.4'): - print("Updating pip") - check_call(['pip', 'install', '--upgrade', 'pip']) - # Upgrade distribute because installation of sphinx with distribute 0.6.24 - # available on Travis is broken (see #207). - try: - distribute_version = get_distribution('distribute').version - if LooseVersion(distribute_version) <= LooseVersion('0.6.24'): - print("Updating distribute") - check_call(['pip', 'install', '--upgrade', 'distribute']) - except DistributionNotFound: - pass - # Install Sphinx and Breathe. - pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee', - min_version='1.4.1.dev20160531') - pip_install('michaeljones/breathe', - '129222318f7c8f865d2631e7da7b033567e7f56a', - min_version='4.2.0') + if not os.path.exists(venv_dir): + check_call(['python3', '-m', 'venv', venv_dir]) + # Install Sphinx and Breathe. Require the exact version of Sphinx which is + # compatible with Breathe. + pip = Pip(venv_dir) + pip.install('wheel') + pip.install('six') + pip.install('sphinx-doc/sphinx', 'v3.3.0') + pip.install('michaeljones/breathe', 'v4.16.0') def build_docs(version='dev', **kwargs): doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__))) @@ -66,16 +36,17 @@ def build_docs(version='dev', **kwargs): 'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt')) # Build docs. cmd = ['doxygen', '-'] - p = Popen(cmd, stdin=PIPE) + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) doxyxml_dir = os.path.join(work_dir, 'doxyxml') - p.communicate(input=r''' + out, _ = p.communicate(input=r''' PROJECT_NAME = fmt GENERATE_LATEX = NO GENERATE_MAN = NO GENERATE_RTF = NO CASE_SENSE_NAMES = NO - INPUT = {0}/core.h {0}/compile.h {0}/format.h {0}/os.h \ - {0}/ostream.h {0}/printf.h {0}/time.h + INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \ + {0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \ + {0}/xchar.h QUIET = YES JAVADOC_AUTOBRIEF = YES AUTOLINK_SUPPORT = NO @@ -86,6 +57,7 @@ def build_docs(version='dev', **kwargs): ALIASES += "endrst=\endverbatim" MACRO_EXPANSION = YES PREDEFINED = _WIN32=1 \ + __linux__=1 \ FMT_USE_VARIADIC_TEMPLATES=1 \ FMT_USE_RVALUE_REFERENCES=1 \ FMT_USE_USER_DEFINED_LITERALS=1 \ @@ -94,20 +66,37 @@ def build_docs(version='dev', **kwargs): "FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_END_NAMESPACE=}}" \ "FMT_STRING_ALIAS=1" \ - "FMT_ENABLE_IF(B)=" - EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str + "FMT_DOC=1" + EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \ + fmt::basic_format_arg::handle '''.format(include_dir, doxyxml_dir).encode('UTF-8')) + out = out.decode('utf-8') + internal_symbols = [ + 'fmt::detail::.*', + 'basic_data<>', + 'fmt::type_identity', + 'fmt::dynamic_formatter' + ] + noisy_warnings = [ + 'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \ + ') is not documented.', + 'warning: Internal inconsistency: .* does not belong to any container!' + ] + for w in noisy_warnings: + out = re.sub('.*' + w + '\n', '', out) + print(out) if p.returncode != 0: raise CalledProcessError(p.returncode, cmd) + html_dir = os.path.join(work_dir, 'html') main_versions = reversed(versions[-3:]) - check_call(['sphinx-build', + check_call([os.path.join(work_dir, 'virtualenv', 'bin', 'sphinx-build'), '-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir), '-Dversion=' + version, '-Drelease=' + version, '-Aversion=' + version, '-Aversions=' + ','.join(main_versions), '-b', 'html', doc_dir, html_dir]) try: - check_call(['lessc', '--clean-css', + check_call(['lessc', '--verbose', '--clean-css', '--include-path=' + os.path.join(doc_dir, 'bootstrap'), os.path.join(doc_dir, 'fmt.less'), os.path.join(html_dir, '_static', 'fmt.css')]) diff --git a/doc/fmt.less b/doc/fmt.less index 55c716b8..3a97b9fd 100644 --- a/doc/fmt.less +++ b/doc/fmt.less @@ -56,6 +56,11 @@ div.sphinxsidebar { padding: 0; } +// Override center alignment in tables. +td { + text-align: left; +} + p.rubric { margin-top: 10px; } diff --git a/doc/index.rst b/doc/index.rst index 59dc6652..92221225 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -23,24 +23,26 @@ Format API The format API is similar in spirit to the C ``printf`` family of function but is safer, simpler and several times `faster -`_ +`_ than common standard library implementations. The `format string syntax `_ is similar to the one used by -`str.format `_ in +`str.format `_ in Python: .. code:: c++ - fmt::format("The answer is {}.", 42); + std::string s = fmt::format("The answer is {}.", 42); The ``fmt::format`` function returns a string "The answer is 42.". You can use ``fmt::memory_buffer`` to avoid constructing ``std::string``: .. code:: c++ - fmt::memory_buffer out; - format_to(out, "For a moment, {} happened.", "nothing"); - out.data(); // returns a pointer to the formatted data + auto out = fmt::memory_buffer(); + format_to(std::back_inserter(out), + "For a moment, {} happened.", "nothing"); + auto data = out.data(); // pointer to the formatted data + auto size = out.size(); // size of the formatted data The ``fmt::print`` function performs formatting and writes the result to a stream: @@ -48,21 +50,19 @@ The ``fmt::print`` function performs formatting and writes the result to a strea fmt::print(stderr, "System error code = {}\n", errno); -The file argument can be omitted in which case the function prints to -``stdout``: +If you omit the file argument the function will print to ``stdout``: .. code:: c++ fmt::print("Don't {}\n", "panic"); -The Format API also supports positional arguments useful for localization: +The format API also supports positional arguments useful for localization: .. code:: c++ fmt::print("I'd rather be {1} than {0}.", "right", "happy"); -Named arguments can be created with ``fmt::arg``. This makes it easier to track -what goes where when multiple arguments are being formatted: +You can pass named arguments with ``fmt::arg``: .. code:: c++ @@ -91,16 +91,17 @@ time. For example, the code fmt::format("The answer is {:d}", "forty-two"); -throws a ``format_error`` exception with description "unknown format code 'd' for -string", because the argument ``"forty-two"`` is a string while the format code -``d`` only applies to integers, while +throws the ``format_error`` exception because the argument ``"forty-two"`` is a +string while the format code ``d`` only applies to integers. + +The code .. code:: c++ format(FMT_STRING("The answer is {:d}"), "forty-two"); -reports a compile-time error for the same reason on compilers that support -relaxed ``constexpr``. See `here `_ for details. +reports a compile-time error on compilers that support relaxed ``constexpr``. +See `here `_ for details. The following code @@ -109,21 +110,15 @@ The following code fmt::format("Cyrillic letter {}", L'\x42e'); produces a compile-time error because wide character ``L'\x42e'`` cannot be -formatted into a narrow string. You can use a wide format string instead: - -.. code:: c++ - - fmt::format(L"Cyrillic letter {}", L'\x42e'); - -For comparison, writing a wide character to ``std::ostream`` results in -its numeric value being written to the stream (i.e. 1070 instead of letter 'ю' -which is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is -needed. +formatted into a narrow string. For comparison, writing a wide character to +``std::ostream`` results in its numeric value being written to the stream +(i.e. 1070 instead of letter 'ю' which is represented by ``L'\x42e'`` if we +use Unicode) which is rarely desirable. Compact Binary Code ------------------- -The library is designed to produce compact per-call compiled code. For example +The library produces compact per-call compiled code. For example (`godbolt `_), .. code:: c++ @@ -144,8 +139,8 @@ compiles to just mov rcx, rsp mov edi, offset .L.str mov esi, 17 - mov edx, 2 - call fmt::v5::vprint(fmt::v5::basic_string_view, fmt::v5::format_args) + mov edx, 1 + call fmt::v7::vprint(fmt::v7::basic_string_view, fmt::v7::format_args) xor eax, eax add rsp, 24 ret @@ -167,20 +162,19 @@ The library is highly portable and relies only on a small set of C++11 features: * deleted functions * alias templates -These are available since GCC 4.8, Clang 3.0 and MSVC 19.0 (2015). For older -compilers use {fmt} `version 4.x -`_ which continues to be -maintained and only requires C++98. +These are available in GCC 4.8, Clang 3.4, MSVC 19.0 (2015) and more recent +compiler version. For older compilers use {fmt} `version 4.x +`_ which is maintained and +only requires C++98. -The output of all formatting functions is consistent across platforms. In -particular, formatting a floating-point infinity always gives ``inf`` while the -output of ``printf`` is platform-dependent. For example, +The output of all formatting functions is consistent across platforms. +For example, .. code:: fmt::print("{}", std::numeric_limits::infinity()); -always prints ``inf``. +always prints ``inf`` while the output of ``printf`` is platform-dependent. .. _ease-of-use: @@ -192,11 +186,13 @@ just three header files and no external dependencies. A permissive MIT `license `_ allows using the library both in open-source and commercial projects. +`Learn more... `_ + .. raw:: html GitHub Repository diff --git a/doc/syntax.rst b/doc/syntax.rst index 8265f702..4eff1d4f 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -16,7 +16,7 @@ literal text, it can be escaped by doubling: ``{{`` and ``}}``. The grammar for a replacement field is as follows: .. productionlist:: sf - replacement_field: "{" [`arg_id`] [":" `format_spec`] "}" + replacement_field: "{" [`arg_id`] [":" (`format_spec` | `chrono_format_spec`)] "}" arg_id: `integer` | `identifier` integer: `digit`+ digit: "0"..."9" @@ -27,8 +27,8 @@ The grammar for a replacement field is as follows: In less formal terms, the replacement field can start with an *arg_id* that specifies the argument whose value is to be formatted and inserted into the output instead of the replacement field. -The *arg_id* is optionally followed by a *format_spec*, which is preceded -by a colon ``':'``. These specify a non-default format for the replacement value. +The *arg_id* is optionally followed by a *format_spec*, which is preceded by a +colon ``':'``. These specify a non-default format for the replacement value. See also the :ref:`formatspec` section. @@ -75,14 +75,14 @@ although some of the formatting options are only supported by the numeric types. The general form of a *standard format specifier* is: .. productionlist:: sf - format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`][`type`] + format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`]["L"][`type`] fill: align: "<" | ">" | "^" sign: "+" | "-" | " " width: `integer` | "{" [`arg_id`] "}" precision: `integer` | "{" [`arg_id`] "}" - type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s" - int_type: "b" | "B" | "d" | "o" | "x" | "X" + type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | + : "o" | "p" | "s" | "x" | "X" The *fill* character can be any Unicode code point other than ``'{'`` or ``'}'``. The presence of a fill character is signaled by the character following @@ -163,6 +163,9 @@ 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. +The ``'L'`` option uses the current locale setting to insert the appropriate +number separator characters. This option is only valid for numeric types. + Finally, the *type* determines how the data should be presented. The available string presentation types are: @@ -200,6 +203,8 @@ The available integer presentation types are: | | ``'#'`` option with this type adds the prefix ``"0B"`` | | | to the output value. | +---------+----------------------------------------------------------+ +| ``'c'`` | Character format. Outputs the number as a character. | ++---------+----------------------------------------------------------+ | ``'d'`` | Decimal integer. Outputs the number in base 10. | +---------+----------------------------------------------------------+ | ``'o'`` | Octal format. Outputs the number in base 8. | @@ -214,10 +219,6 @@ The available integer presentation types are: | | ``'#'`` option with this type adds the prefix ``"0X"`` | | | to the output value. | +---------+----------------------------------------------------------+ -| ``'L'`` | Locale-specific format. This is the same as ``'d'``, | -| | except that it uses the current locale setting to insert | -| | the appropriate number separator characters. | -+---------+----------------------------------------------------------+ | none | The same as ``'d'``. | +---------+----------------------------------------------------------+ @@ -261,14 +262,8 @@ The available presentation types for floating-point values are: | | ``'E'`` if the number gets too large. The | | | representations of infinity and NaN are uppercased, too. | +---------+----------------------------------------------------------+ -| ``'L'`` | Locale-specific format. This is the same as ``'g'``, | -| | except that it uses the current locale setting to insert | -| | the appropriate number separator characters. | -+---------+----------------------------------------------------------+ -| none | Similar to ``'g'``, except that fixed-point notation, | -| | when used, has at least one digit past the decimal | -| | point. The default precision is as high as needed to | -| | represent the particular value. | +| none | Similar to ``'g'``, except that the default precision is | +| | as high as needed to represent the particular value. | +---------+----------------------------------------------------------+ .. ifconfig:: False @@ -303,6 +298,59 @@ The available presentation types for pointers are: | none | The same as ``'p'``. | +---------+----------------------------------------------------------+ +.. _chrono-specs: + +Chrono Format Specifications +============================ + +Format specifications for chrono types have the following syntax: + +.. productionlist:: sf + chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`] + chrono_specs: [`chrono_specs`] `conversion_spec` | `chrono_specs` `literal_char` + conversion_spec: "%" [`modifier`] `chrono_type` + literal_char: + modifier: "E" | "O" + chrono_type: "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" | + : "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" | + : "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" | + : "w" | "W" | "x" | "X" | "y" | "Y" | "z" | "Z" | "%" + +Literal chars are copied unchanged to the output. Precision is valid only for +``std::chrono::duration`` types with a floating-point representation type. + +The available presentation types (*chrono_type*) for chrono durations and time +points are: + ++---------+--------------------------------------------------------------------+ +| Type | Meaning | ++=========+====================================================================+ +| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a | +| | single digit, it is prefixed with 0. The modified command ``%OH`` | +| | produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'M'`` | The minute as a decimal number. If the result is a single digit, | +| | it is prefixed with 0. The modified command ``%OM`` produces the | +| | locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than | +| | 10, the result is prefixed with 0. If the precision of the input | +| | cannot be exactly represented with seconds, then the format is a | +| | decimal floating-point number with a fixed format and a precision | +| | matching that of the precision of the input (or to a microseconds | +| | precision if the conversion to floating-point decimal seconds | +| | cannot be made within 18 fractional digits). The character for the | +| | decimal point is localized according to the locale. The modified | +| | command ``%OS`` produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ + +Specifiers that have a calendaric component such as `'d'` (the day of month) +are valid only for ``std::tm`` and not durations or time points. + +``std::tm`` uses the system's `strftime +`_ so refer to its +documentation for details on supported conversion specifiers. + .. _formatexamples: Format Examples @@ -391,7 +439,7 @@ Using type-specific formatting:: auto t = tm(); t.tm_year = 2010 - 1900; - t.tm_mon = 6; + t.tm_mon = 7; t.tm_mday = 4; t.tm_hour = 12; t.tm_min = 15; diff --git a/doc/usage.rst b/doc/usage.rst index d42719e0..f9f81858 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -15,7 +15,7 @@ Building the Library The included `CMake build script`__ can be used to build the fmt library on a wide range of platforms. CMake is freely available for -download from http://www.cmake.org/download/. +download from https://www.cmake.org/download/. __ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt @@ -50,7 +50,15 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to cmake -DBUILD_SHARED_LIBS=TRUE ... -__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries +__ https://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries + + +To build a `static library` with position independent code (required if the main +consumer of the fmt library is a shared library i.e. a Python extension) set the +``CMAKE_POSITION_INDEPENDENT_CODE`` CMake variable to ``TRUE``:: + + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ... + Installing the Library ====================== @@ -83,6 +91,49 @@ Setting up your target to use a header-only version of ``fmt`` is equally easy:: target_link_libraries( PRIVATE fmt::fmt-header-only) +Usage with build2 +================= + +You can use `build2 `_, a dependency manager and a +build-system combined, to use ``fmt``. + +Currently this package is available in these package repositories: + +- **https://cppget.org/fmt/** for released and published versions. +- `The git repository with the sources of the build2 package of fmt `_ + for unreleased or custom revisions of ``fmt``. + +**Usage:** + +- ``build2`` package name: ``fmt`` +- Library target name : ``lib{fmt}`` + +For example, to make your ``build2`` project depend on ``fmt``: + +- Add one of the repositories to your configurations, or in your + ``repositories.manifest``, if not already there:: + + : + role: prerequisite + location: https://pkg.cppget.org/1/stable + +- Add this package as a dependency to your ``./manifest`` file + (example for ``v7.0.x``):: + + depends: fmt ~7.0.0 + +- Import the target and use it as a prerequisite to your own target + using `fmt` in the appropriate ``buildfile``:: + + import fmt = fmt%lib{fmt} + lib{mylib} : cxx{**} ... $fmt + +Then build your project as usual with `b` or `bdep update`. + +For ``build2`` newcomers or to get more details and use cases, you can read the +``build2`` +`toolchain introduction `_. + Building the Documentation ========================== @@ -130,6 +181,18 @@ The fmt port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please `create an issue or pull request `__ on the vcpkg repository. +LHelper +======= + +You can download and install fmt using +`lhelper `__ dependency manager:: + + lhelper activate + lhelper install fmt + +All the recipes for lhelper are kept in the +`lhelper's recipe `__ repository. + Android NDK =========== @@ -139,11 +202,11 @@ For an example of using fmt with Android NDK, see the `android-ndk-example `_ repository. -__ https://github.com/fmtlib/fmt/blob/master/Android.mk +__ https://github.com/fmtlib/fmt/blob/master/support/Android.mk Homebrew ======== -fmt can be installed on OS X using `Homebrew `_:: +fmt can be installed on OS X using `Homebrew `_:: brew install fmt diff --git a/include/fmt/args.h b/include/fmt/args.h new file mode 100644 index 00000000..562e8ab1 --- /dev/null +++ b/include/fmt/args.h @@ -0,0 +1,232 @@ +// Formatting library for C++ - dynamic format arguments +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_ARGS_H_ +#define FMT_ARGS_H_ + +#include // std::reference_wrapper +#include // std::unique_ptr +#include + +#include "core.h" + +FMT_BEGIN_NAMESPACE + +namespace detail { + +template struct is_reference_wrapper : std::false_type {}; +template +struct is_reference_wrapper> : std::true_type {}; + +template const T& unwrap(const T& v) { return v; } +template const T& unwrap(const std::reference_wrapper& v) { + return static_cast(v); +} + +class dynamic_arg_list { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + template struct node { + virtual ~node() = default; + std::unique_ptr> next; + }; + + template struct typed_node : node<> { + T value; + + template + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} + + template + FMT_CONSTEXPR typed_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} + }; + + std::unique_ptr> head_; + + public: + template const T& push(const Arg& arg) { + auto new_node = std::unique_ptr>(new typed_node(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); + return value; + } +}; +} // namespace detail + +/** + \rst + A dynamic version of `fmt::format_arg_store`. + It's equipped with a storage to potentially temporary objects which lifetimes + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_copy { + static constexpr detail::type mapped_type = + detail::mapped_type_constant::value; + + enum { + value = !(detail::is_reference_wrapper::value || + std::is_same>::value || + std::is_same>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) + }; + }; + + template + using stored_type = conditional_t::value && + !has_formatter::value && + !detail::is_reference_wrapper::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + std::vector> named_info_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + detail::dynamic_arg_list dynamic_args_; + + friend class basic_format_args; + + unsigned long long get_types() const { + return detail::is_unpacked_bit | data_.size() | + (named_info_.empty() + ? 0ULL + : static_cast(detail::has_named_args_bit)); + } + + const basic_format_arg* data() const { + return named_info_.empty() ? data_.data() : data_.data() + 1; + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(detail::make_arg(arg)); + } + + template + void emplace_arg(const detail::named_arg& arg) { + if (named_info_.empty()) { + constexpr const detail::named_arg_info* zero_ptr{nullptr}; + data_.insert(data_.begin(), {zero_ptr, 0}); + } + data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + guard.release(); + } + + public: + /** + \rst + Adds an argument into the dynamic store for later passing to a formatting + function. + + Note that custom types and string types (but not string views) are copied + into the store dynamically allocating memory if necessary. + + **Example**:: + + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template void push_back(const T& arg) { + if (detail::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); + else + emplace_arg(detail::unwrap(arg)); + } + + /** + \rst + Adds a reference to the argument into the dynamic store for later passing to + a formatting function. + + **Example**:: + + fmt::dynamic_format_arg_store store; + char band[] = "Rolling Stones"; + store.push_back(std::cref(band)); + band[9] = 'c'; // Changing str affects the output. + std::string result = fmt::vformat("{}", store); + // result == "Rolling Scones" + \endrst + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_copy::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } + + /** + Adds named argument into the dynamic store for later passing to a formatting + function. ``std::reference_wrapper`` is supported to avoid copying of the + argument. The name is always copied into the store. + */ + template + void push_back(const detail::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (detail::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } + + /** Erase all elements from the store */ + void clear() { + data_.clear(); + named_info_.clear(); + dynamic_args_ = detail::dynamic_arg_list(); + } + + /** + \rst + Reserves space to store at least *new_cap* arguments including + *new_cap_named* named arguments. + \endrst + */ + void reserve(size_t new_cap, size_t new_cap_named) { + FMT_ASSERT(new_cap >= new_cap_named, + "Set of arguments includes set of named arguments"); + data_.reserve(new_cap); + named_info_.reserve(new_cap_named); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_ARGS_H_ diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index e70b8053..c024fd71 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -8,13 +8,13 @@ #ifndef FMT_CHRONO_H_ #define FMT_CHRONO_H_ +#include #include #include #include #include #include "format.h" -#include "locale.h" FMT_BEGIN_NAMESPACE @@ -72,43 +72,27 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { static_assert(F::is_integer, "From must be integral"); static_assert(T::is_integer, "To must be integral"); - if (F::is_signed && !T::is_signed) { + if (detail::const_check(F::is_signed && !T::is_signed)) { // From may be negative, not allowed! if (fmt::detail::is_negative(from)) { ec = 1; return {}; } - // From is positive. Can it always fit in To? - if (F::digits <= T::digits) { - // yes, From always fits in To. - } else { - // from may not fit in To, we have to do a dynamic check - if (from > static_cast((T::max)())) { - ec = 1; - return {}; - } + if (F::digits > T::digits && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; } } - if (!F::is_signed && T::is_signed) { - // can from be held in To? - if (F::digits < T::digits) { - // yes, From always fits in To. - } else { - // from may not fit in To, we have to do a dynamic check - if (from > static_cast((T::max)())) { - // outside range. - ec = 1; - return {}; - } - } + if (!F::is_signed && T::is_signed && F::digits >= T::digits && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; } - - // reaching here means all is ok for lossless conversion. - return static_cast(from); - -} // function + return static_cast(from); // Lossless conversion. +} template ::value)> @@ -190,11 +174,9 @@ To safe_duration_cast(std::chrono::duration from, // safe conversion to IntermediateRep IntermediateRep count = lossless_integral_conversion(from.count(), ec); - if (ec) { - return {}; - } + if (ec) return {}; // multiply with Factor::num without overflow or underflow - if (Factor::num != 1) { + if (detail::const_check(Factor::num != 1)) { const auto max1 = detail::max_value() / Factor::num; if (count > max1) { ec = 1; @@ -209,17 +191,9 @@ To safe_duration_cast(std::chrono::duration from, count *= Factor::num; } - // this can't go wrong, right? den>0 is checked earlier. - if (Factor::den != 1) { - count /= Factor::den; - } - // convert to the to type, safely - using ToRep = typename To::rep; - const ToRep tocount = lossless_integral_conversion(count, ec); - if (ec) { - return {}; - } - return To{tocount}; + if (detail::const_check(Factor::den != 1)) count /= Factor::den; + auto tocount = lossless_integral_conversion(count, ec); + return ec ? To() : To(tocount); } /** @@ -308,13 +282,89 @@ To safe_duration_cast(std::chrono::duration from, #define FMT_NOMACRO namespace detail { +template struct null {}; inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } + +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; + // 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; +#else + 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) { + 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 str; +} + +template +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); +} } // namespace detail -// Thread-safe replacement for std::localtime +FMT_MODULE_EXPORT_BEGIN + +/** + Converts given time since epoch as ``std::time_t`` value into calendar time, + expressed in local time. Unlike ``std::localtime``, this function is + thread-safe on most platforms. + */ inline std::tm localtime(std::time_t time) { struct dispatcher { std::time_t time_; @@ -351,7 +401,16 @@ inline std::tm localtime(std::time_t time) { return lt.tm_; } -// Thread-safe replacement for std::gmtime +inline std::tm localtime( + std::chrono::time_point time_point) { + return localtime(std::chrono::system_clock::to_time_t(time_point)); +} + +/** + Converts given time since epoch as ``std::time_t`` value into calendar time, + expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this + function is thread-safe on most platforms. + */ inline std::tm gmtime(std::time_t time) { struct dispatcher { std::time_t time_; @@ -387,33 +446,89 @@ inline std::tm gmtime(std::time_t time) { return gt.tm_; } -namespace detail { +inline std::tm gmtime( + std::chrono::time_point time_point) { + return gmtime(std::chrono::system_clock::to_time_t(time_point)); +} + +FMT_BEGIN_DETAIL_NAMESPACE + inline size_t strftime(char* str, size_t count, const char* format, const std::tm* time) { - return std::strftime(str, count, format, 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); } inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, const std::tm* time) { - return std::wcsftime(str, count, format, 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); } -} // namespace detail -template struct formatter { +FMT_END_DETAIL_NAMESPACE + +template +struct formatter, + Char> : formatter { + FMT_CONSTEXPR formatter() { + this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)}; + } + template - auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + 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; - tm_format.reserve(detail::to_unsigned(end - it + 1)); - tm_format.append(it, end); - tm_format.push_back('\0'); + if (end != it) this->specs = {it, detail::to_unsigned(end - it)}; return end; } template - auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { + 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 (;;) { @@ -423,49 +538,40 @@ template struct formatter { buf.resize(start + count); break; } - if (size >= tm_format.size() * 256) { - // If the buffer is 256 times larger than the format string, assume - // that `strftime` gives an empty result. There doesn't seem to be a - // better way to distinguish the two cases: - // https://github.com/fmtlib/fmt/issues/367 - break; - } const size_t MIN_GROWTH = 10; buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); } - return std::copy(buf.begin(), buf.end(), ctx.out()); + // Remove the extra space. + return std::copy(buf.begin(), buf.end() - 1, ctx.out()); } - basic_memory_buffer tm_format; + basic_string_view specs; }; -namespace detail { -template FMT_CONSTEXPR const char* get_units() { +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"; + if (std::is_same::value) return "ps"; + if (std::is_same::value) return "ns"; + if (std::is_same::value) return "µs"; + if (std::is_same::value) return "ms"; + if (std::is_same::value) return "cs"; + if (std::is_same::value) return "ds"; + if (std::is_same>::value) return "s"; + if (std::is_same::value) return "das"; + if (std::is_same::value) return "hs"; + if (std::is_same::value) return "ks"; + if (std::is_same::value) return "Ms"; + if (std::is_same::value) return "Gs"; + if (std::is_same::value) return "Ts"; + if (std::is_same::value) return "Ps"; + if (std::is_same::value) return "Es"; + if (std::is_same>::value) return "m"; + if (std::is_same>::value) return "h"; return nullptr; } -template <> FMT_CONSTEXPR const char* get_units() { return "as"; } -template <> FMT_CONSTEXPR const char* get_units() { return "fs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ps"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ns"; } -template <> FMT_CONSTEXPR const char* get_units() { return "µs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ms"; } -template <> FMT_CONSTEXPR const char* get_units() { return "cs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ds"; } -template <> FMT_CONSTEXPR const char* get_units>() { return "s"; } -template <> FMT_CONSTEXPR const char* get_units() { return "das"; } -template <> FMT_CONSTEXPR const char* get_units() { return "hs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ks"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ms"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Gs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ts"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ps"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Es"; } -template <> FMT_CONSTEXPR const char* get_units>() { - return "m"; -} -template <> FMT_CONSTEXPR const char* get_units>() { - return "h"; -} enum class numeric_system { standard, @@ -631,33 +737,50 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, return ptr; } -struct chrono_format_checker { - FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } +template struct null_chrono_spec_handler { + FMT_CONSTEXPR void unsupported() { + static_cast(this)->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_24_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_us_date() { unsupported(); } + FMT_CONSTEXPR void on_iso_date() { unsupported(); } + FMT_CONSTEXPR void on_12_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_24_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_iso_time() { unsupported(); } + FMT_CONSTEXPR void on_am_pm() { unsupported(); } + FMT_CONSTEXPR void on_duration_value() { unsupported(); } + FMT_CONSTEXPR void on_duration_unit() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset() { unsupported(); } + FMT_CONSTEXPR void on_tz_name() { unsupported(); } +}; - template void on_text(const Char*, const Char*) {} - FMT_NORETURN void on_abbr_weekday() { report_no_date(); } - FMT_NORETURN void on_full_weekday() { report_no_date(); } - FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } - FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } - FMT_NORETURN void on_abbr_month() { report_no_date(); } - FMT_NORETURN void on_full_month() { report_no_date(); } - void on_24_hour(numeric_system) {} - void on_12_hour(numeric_system) {} - void on_minute(numeric_system) {} - void on_second(numeric_system) {} - FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } - FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } - FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } - FMT_NORETURN void on_us_date() { report_no_date(); } - FMT_NORETURN void on_iso_date() { report_no_date(); } - void on_12_hour_time() {} - void on_24_hour_time() {} - void on_iso_time() {} - void on_am_pm() {} - void on_duration_value() {} - void on_duration_unit() {} - FMT_NORETURN void on_utc_offset() { report_no_date(); } - FMT_NORETURN void on_tz_name() { report_no_date(); } +struct chrono_format_checker : null_chrono_spec_handler { + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + 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_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_duration_value() {} + FMT_CONSTEXPR void on_duration_unit() {} }; template ::value)> @@ -681,7 +804,8 @@ inline bool isfinite(T 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) { - FMT_ASSERT(value >= 0 && value <= upper, "invalid value"); + FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper), + "invalid value"); (void)upper; return static_cast(value); } @@ -759,15 +883,21 @@ inline std::chrono::duration get_milliseconds( return std::chrono::duration(static_cast(ms)); } -template -OutputIt format_duration_value(OutputIt out, Rep val, int precision) { - const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0}; - if (precision >= 0) return format_to(out, pr_f, val, precision); - const Char fp_f[] = {'{', ':', 'g', '}', 0}; - const Char format[] = {'{', '}', 0}; - return format_to(out, std::is_floating_point::value ? fp_f : format, - val); +template ::value)> +OutputIt format_duration_value(OutputIt out, Rep val, int) { + return write(out, val); } + +template ::value)> +OutputIt format_duration_value(OutputIt out, Rep val, int precision) { + auto specs = basic_format_specs(); + specs.precision = precision; + specs.type = precision > 0 ? 'f' : 'g'; + return write(out, val, specs); +} + template OutputIt copy_unit(string_view unit, OutputIt out, Char) { return std::copy(unit.begin(), unit.end(), out); @@ -785,10 +915,15 @@ template OutputIt format_duration_unit(OutputIt out) { if (const char* unit = get_units()) return copy_unit(string_view(unit), out, Char()); - const Char num_f[] = {'[', '{', '}', ']', 's', 0}; - if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num); - const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0}; - return format_to(out, num_def_f, Period::num, Period::den); + *out++ = '['; + out = write(out, Period::num); + if (const_check(Period::den != 1)) { + *out++ = '/'; + out = write(out, Period::den); + } + *out++ = ']'; + *out++ = 's'; + return out; } template ::value && sizeof(Rep) < sizeof(int), @@ -891,13 +1027,9 @@ struct chrono_formatter { void format_localized(const tm& time, char format, char modifier = 0) { if (isnan(val)) return write_nan(); - auto locale = context.locale().template get(); - auto& facet = std::use_facet>(locale); - std::basic_ostringstream os; - os.imbue(locale); - facet.put(os, os, ' ', &time, format, modifier); - auto str = os.str(); - std::copy(str.begin(), str.end(), out); + const auto& loc = localized ? context.locale().template get() + : std::locale::classic(); + out = detail::write(out, time, loc, format, modifier); } void on_text(const char_type* begin, const char_type* end) { @@ -1010,17 +1142,59 @@ struct chrono_formatter { out = format_duration_unit(out); } }; -} // namespace detail + +FMT_END_DETAIL_NAMESPACE + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 +using weekday = std::chrono::weekday; +#else +// A fallback version of weekday. +class weekday { + private: + unsigned char value; + + public: + weekday() = default; + explicit constexpr weekday(unsigned wd) noexcept + : value(static_cast(wd != 7 ? wd : 0)) {} + constexpr unsigned c_encoding() const noexcept { return value; } +}; +#endif + +// A rudimentary weekday formatter. +template <> struct formatter { + private: + bool localized = false; + + public: + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin != end && *begin == 'L') { + ++begin; + localized = true; + } + return begin; + } + + auto format(weekday wd, format_context& ctx) -> 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'); + } +}; template struct formatter, Char> { private: basic_format_specs specs; - int precision; + int precision = -1; using arg_ref_type = detail::arg_ref; arg_ref_type width_ref; arg_ref_type precision_ref; - mutable basic_string_view format_str; + bool localized = false; + basic_string_view format_str; using duration = std::chrono::duration; struct spec_handler { @@ -1043,17 +1217,21 @@ struct formatter, Char> { } void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - void on_fill(basic_string_view fill) { f.specs.fill = fill; } - void on_align(align_t align) { f.specs.align = align; } - void on_width(int width) { f.specs.width = width; } - void on_precision(int _precision) { f.precision = _precision; } - void end_precision() {} + FMT_CONSTEXPR void on_fill(basic_string_view fill) { + f.specs.fill = fill; + } + FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; } + FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; } + FMT_CONSTEXPR void on_precision(int _precision) { + f.precision = _precision; + } + FMT_CONSTEXPR void end_precision() {} - template void on_dynamic_width(Id arg_id) { + template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { f.width_ref = make_arg_ref(arg_id); } - template void on_dynamic_precision(Id arg_id) { + template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { f.precision_ref = make_arg_ref(arg_id); } }; @@ -1078,13 +1256,15 @@ struct formatter, Char> { else handler.on_error("precision not allowed for this argument type"); } + if (begin != end && *begin == 'L') { + ++begin; + localized = true; + } end = parse_chrono_format(begin, end, detail::chrono_format_checker()); return {begin, end}; } public: - formatter() : precision(-1) {} - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { auto range = do_parse(ctx); @@ -1094,30 +1274,35 @@ struct formatter, Char> { } template - auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) { + auto format(const duration& d, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs_copy = specs; + auto precision_copy = precision; auto begin = format_str.begin(), end = format_str.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. basic_memory_buffer buf; auto out = std::back_inserter(buf); - detail::handle_dynamic_spec(specs.width, width_ref, - ctx); - detail::handle_dynamic_spec(precision, + detail::handle_dynamic_spec(specs_copy.width, + width_ref, ctx); + detail::handle_dynamic_spec(precision_copy, precision_ref, ctx); if (begin == end || *begin == '}') { - out = detail::format_duration_value(out, d.count(), precision); + out = detail::format_duration_value(out, d.count(), precision_copy); detail::format_duration_unit(out); } else { detail::chrono_formatter f( ctx, out, d); - f.precision = precision; - parse_chrono_format(begin, end, f); + f.precision = precision_copy; + f.localized = localized; + detail::parse_chrono_format(begin, end, f); } return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + ctx.out(), basic_string_view(buf.data(), buf.size()), specs_copy); } }; +FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ diff --git a/include/fmt/color.h b/include/fmt/color.h index b65f892a..7fa5490e 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -10,7 +10,15 @@ #include "format.h" +// __declspec(deprecated) is broken in some MSVC versions. +#if FMT_MSC_VER +# define FMT_DEPRECATED_NONMSVC +#else +# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED +#endif + FMT_BEGIN_NAMESPACE +FMT_MODULE_EXPORT_BEGIN enum class color : uint32_t { alice_blue = 0xF0F8FF, // rgb(240,248,255) @@ -198,7 +206,7 @@ struct rgb { uint8_t b; }; -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE // color is a struct of either a rgb color or a terminal color. struct color_type { @@ -221,9 +229,10 @@ struct color_type { uint32_t rgb_color; } value; }; -} // namespace detail -// Experimental text formatting support. +FMT_END_DETAIL_NAMESPACE + +/** A text style consisting of foreground and background colors and emphasis. */ class text_style { public: FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT @@ -260,33 +269,14 @@ class text_style { return lhs |= rhs; } - FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) { - if (!set_foreground_color) { - set_foreground_color = rhs.set_foreground_color; - foreground_color = rhs.foreground_color; - } else if (rhs.set_foreground_color) { - if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - FMT_THROW(format_error("can't AND a terminal color")); - foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color; - } - - if (!set_background_color) { - set_background_color = rhs.set_background_color; - background_color = rhs.background_color; - } else if (rhs.set_background_color) { - if (!background_color.is_rgb || !rhs.background_color.is_rgb) - FMT_THROW(format_error("can't AND a terminal color")); - background_color.value.rgb_color &= rhs.background_color.value.rgb_color; - } - - ems = static_cast(static_cast(ems) & - static_cast(rhs.ems)); - return *this; + FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=( + const text_style& rhs) { + return and_assign(rhs); } - friend FMT_CONSTEXPR text_style operator&(text_style lhs, - const text_style& rhs) { - return lhs &= rhs; + FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style + operator&(text_style lhs, const text_style& rhs) { + return lhs.and_assign(rhs); } FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { @@ -326,8 +316,34 @@ class text_style { } } + // DEPRECATED! + FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) { + if (!set_foreground_color) { + set_foreground_color = rhs.set_foreground_color; + foreground_color = rhs.foreground_color; + } else if (rhs.set_foreground_color) { + if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) + FMT_THROW(format_error("can't AND a terminal color")); + foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color; + } + + if (!set_background_color) { + set_background_color = rhs.set_background_color; + background_color = rhs.background_color; + } else if (rhs.set_background_color) { + if (!background_color.is_rgb || !rhs.background_color.is_rgb) + FMT_THROW(format_error("can't AND a terminal color")); + background_color.value.rgb_color &= rhs.background_color.value.rgb_color; + } + + ems = static_cast(static_cast(ems) & + static_cast(rhs.ems)); + return *this; + } + friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground) FMT_NOEXCEPT; + friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background) FMT_NOEXCEPT; @@ -338,19 +354,22 @@ class text_style { emphasis ems; }; -FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT { - return text_style(/*is_foreground=*/true, foreground); +/** Creates a text style from the foreground (text) color. */ +FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT { + return text_style(true, foreground); } -FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT { - return text_style(/*is_foreground=*/false, background); +/** Creates a text style from the background color. */ +FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT { + return text_style(false, background); } -FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { +FMT_CONSTEXPR inline text_style operator|(emphasis lhs, + emphasis rhs) FMT_NOEXCEPT { return text_style(lhs) | rhs; } -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE template struct ansi_color_escape { FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, @@ -358,7 +377,7 @@ template struct ansi_color_escape { // If we have a terminal color, we need to output another escape code // sequence. if (!text_color.is_rgb) { - bool is_background = esc == detail::data::background_color; + bool is_background = esc == string_view("\x1b[48;2;"); uint32_t value = text_color.value.term_color; // Background ASCII codes are the same as the foreground ones but with // 10 more. @@ -411,7 +430,7 @@ template struct ansi_color_escape { FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } - FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { + FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT { return buffer + std::char_traits::length(buffer); } @@ -430,13 +449,13 @@ template struct ansi_color_escape { template FMT_CONSTEXPR ansi_color_escape make_foreground_color( detail::color_type foreground) FMT_NOEXCEPT { - return ansi_color_escape(foreground, detail::data::foreground_color); + return ansi_color_escape(foreground, "\x1b[38;2;"); } template FMT_CONSTEXPR ansi_color_escape make_background_color( detail::color_type background) FMT_NOEXCEPT { - return ansi_color_escape(background, detail::data::background_color); + return ansi_color_escape(background, "\x1b[48;2;"); } template @@ -455,24 +474,23 @@ inline void fputs(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { } template inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(detail::data::reset_color, stream); + fputs("\x1b[0m", stream); } template <> inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(detail::data::wreset_color, stream); + fputs(L"\x1b[0m", stream); } template -inline void reset_color(basic_memory_buffer& buffer) FMT_NOEXCEPT { - const char* begin = data::reset_color; - const char* end = begin + sizeof(data::reset_color) - 1; - buffer.append(begin, end); +inline void reset_color(buffer& buffer) FMT_NOEXCEPT { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); } template -void vformat_to(basic_memory_buffer& buf, const text_style& ts, +void vformat_to(buffer& buf, const text_style& ts, basic_string_view format_str, - basic_format_args> args) { + basic_format_args>> args) { bool has_style = false; if (ts.has_emphasis()) { has_style = true; @@ -492,11 +510,12 @@ void vformat_to(basic_memory_buffer& buf, const text_style& ts, detail::vformat_to(buf, format_str, args); if (has_style) detail::reset_color(buf); } -} // namespace detail + +FMT_END_DETAIL_NAMESPACE template > void vprint(std::FILE* f, const text_style& ts, const S& format, - basic_format_args> args) { + basic_format_args>> args) { basic_memory_buffer buf; detail::vformat_to(buf, ts, to_string_view(format), args); buf.push_back(Char(0)); @@ -504,28 +523,34 @@ void vprint(std::FILE* f, const text_style& ts, const S& format, } /** + \rst Formats a string and prints it to the specified file stream using ANSI escape sequences to specify text formatting. - Example: + + **Example**:: + fmt::print(fmt::emphasis::bold | fg(fmt::color::red), "Elapsed time: {0:.2f} seconds", 1.23); + \endrst */ template ::value)> void print(std::FILE* f, const text_style& ts, const S& format_str, const Args&... args) { - detail::check_format_string(format_str); - using context = buffer_context>; - format_arg_store as{args...}; - vprint(f, ts, format_str, basic_format_args(as)); + vprint(f, ts, format_str, + fmt::make_args_checked(format_str, args...)); } /** + \rst Formats a string and prints it to stdout using ANSI escape sequences to specify text formatting. - Example: + + **Example**:: + fmt::print(fmt::emphasis::bold | fg(fmt::color::red), "Elapsed time: {0:.2f} seconds", 1.23); + \endrst */ template ::value)> @@ -557,10 +582,46 @@ inline std::basic_string vformat( template > inline std::basic_string format(const text_style& ts, const S& format_str, const Args&... args) { - return vformat(ts, to_string_view(format_str), - detail::make_args_checked(format_str, args...)); + return fmt::vformat(ts, to_string_view(format_str), + fmt::make_args_checked(format_str, args...)); } +/** + Formats a string with the given text_style and writes the output to ``out``. + */ +template ::value)> +OutputIt vformat_to( + OutputIt out, const text_style& ts, basic_string_view format_str, + basic_format_args>> args) { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, format_str, args); + return detail::get_iterator(buf); +} + +/** + \rst + Formats arguments with the given text_style, writes the result to the output + iterator ``out`` and returns the iterator past the end of the output range. + + **Example**:: + + std::vector out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + \endrst +*/ +template >::value&& + detail::is_string::value> +inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, + Args&&... args) -> + typename std::enable_if::type { + return vformat_to(out, ts, to_string_view(format_str), + fmt::make_args_checked(format_str, args...)); +} + +FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_COLOR_H_ diff --git a/include/fmt/compile.h b/include/fmt/compile.h index d7e6449e..00000c92 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -8,13 +8,135 @@ #ifndef FMT_COMPILE_H_ #define FMT_COMPILE_H_ -#include - #include "format.h" FMT_BEGIN_NAMESPACE namespace detail { +// An output iterator that counts the number of objects written to it and +// discards them. +class counting_iterator { + private: + size_t count_; + + public: + using iterator_category = std::output_iterator_tag; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + using _Unchecked_type = counting_iterator; // Mark iterator as checked. + + struct value_type { + template void operator=(const T&) {} + }; + + counting_iterator() : count_(0) {} + + size_t count() const { return count_; } + + counting_iterator& operator++() { + ++count_; + return *this; + } + counting_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + friend counting_iterator operator+(counting_iterator it, difference_type n) { + it.count_ += static_cast(n); + return it; + } + + value_type operator*() const { return {}; } +}; + +template +inline counting_iterator copy_str(InputIt begin, InputIt end, + counting_iterator it) { + return it + (end - begin); +} + +template class truncating_iterator_base { + protected: + OutputIt out_; + size_t limit_; + size_t count_ = 0; + + truncating_iterator_base() : out_(), limit_(0) {} + + truncating_iterator_base(OutputIt out, size_t limit) + : out_(out), limit_(limit) {} + + public: + using iterator_category = std::output_iterator_tag; + using value_type = typename std::iterator_traits::value_type; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + using _Unchecked_type = + truncating_iterator_base; // Mark iterator as checked. + + OutputIt base() const { return out_; } + size_t count() const { return count_; } +}; + +// An output iterator that truncates the output and counts the number of objects +// written to it. +template ::value_type>::type> +class truncating_iterator; + +template +class truncating_iterator + : public truncating_iterator_base { + mutable typename truncating_iterator_base::value_type blackhole_; + + public: + using value_type = typename truncating_iterator_base::value_type; + + truncating_iterator() = default; + + truncating_iterator(OutputIt out, size_t limit) + : truncating_iterator_base(out, limit) {} + + truncating_iterator& operator++() { + if (this->count_++ < this->limit_) ++this->out_; + return *this; + } + + truncating_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + value_type& operator*() const { + return this->count_ < this->limit_ ? *this->out_ : blackhole_; + } +}; + +template +class truncating_iterator + : public truncating_iterator_base { + public: + truncating_iterator() = default; + + truncating_iterator(OutputIt out, size_t limit) + : truncating_iterator_base(out, limit) {} + + template truncating_iterator& operator=(T val) { + if (this->count_++ < this->limit_) *this->out_++ = val; + return *this; + } + + truncating_iterator& operator++() { return *this; } + truncating_iterator& operator++(int) { return *this; } + truncating_iterator& operator*() { return *this; } +}; + // A compile-time string which is compiled into fast formatting code. class compiled_string {}; @@ -34,341 +156,36 @@ struct is_compiled_string : std::is_base_of {}; std::string s = fmt::format(FMT_COMPILE("{}"), 42); \endrst */ -#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string) +#ifdef __cpp_if_constexpr +# define FMT_COMPILE(s) \ + FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) +#else +# define FMT_COMPILE(s) FMT_STRING(s) +#endif + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +template Str> +struct udl_compiled_string : compiled_string { + using char_type = Char; + constexpr operator basic_string_view() const { + return {Str.data, N - 1}; + } +}; +#endif template const T& first(const T& value, const Tail&...) { return value; } -// Part of a compiled format string. It can be either literal text or a -// replacement field. -template struct format_part { - enum class kind { arg_index, arg_name, text, replacement }; - - struct replacement { - arg_ref arg_id; - dynamic_format_specs specs; - }; - - kind part_kind; - union value { - int arg_index; - basic_string_view str; - replacement repl; - - FMT_CONSTEXPR value(int index = 0) : arg_index(index) {} - FMT_CONSTEXPR value(basic_string_view s) : str(s) {} - FMT_CONSTEXPR value(replacement r) : repl(r) {} - } val; - // Position past the end of the argument id. - const Char* arg_id_end = nullptr; - - FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {}) - : part_kind(k), val(v) {} - - static FMT_CONSTEXPR format_part make_arg_index(int index) { - return format_part(kind::arg_index, index); - } - static FMT_CONSTEXPR format_part make_arg_name(basic_string_view name) { - return format_part(kind::arg_name, name); - } - static FMT_CONSTEXPR format_part make_text(basic_string_view text) { - return format_part(kind::text, text); - } - static FMT_CONSTEXPR format_part make_replacement(replacement repl) { - return format_part(kind::replacement, repl); - } -}; - -template struct part_counter { - unsigned num_parts = 0; - - FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { - if (begin != end) ++num_parts; - } - - FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; } - FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; } - FMT_CONSTEXPR int on_arg_id(basic_string_view) { - return ++num_parts, 0; - } - - FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} - - FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, - const Char* end) { - // Find the matching brace. - unsigned brace_counter = 0; - for (; begin != end; ++begin) { - if (*begin == '{') { - ++brace_counter; - } else if (*begin == '}') { - if (brace_counter == 0u) break; - --brace_counter; - } - } - return begin; - } - - FMT_CONSTEXPR void on_error(const char*) {} -}; - -// Counts the number of parts in a format string. -template -FMT_CONSTEXPR unsigned count_parts(basic_string_view format_str) { - part_counter counter; - parse_format_string(format_str, counter); - return counter.num_parts; -} - -template -class format_string_compiler : public error_handler { - private: - using part = format_part; - - PartHandler handler_; - part part_; - basic_string_view format_str_; - basic_format_parse_context parse_context_; - - public: - FMT_CONSTEXPR format_string_compiler(basic_string_view format_str, - PartHandler handler) - : handler_(handler), - format_str_(format_str), - parse_context_(format_str) {} - - FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { - if (begin != end) - handler_(part::make_text({begin, to_unsigned(end - begin)})); - } - - FMT_CONSTEXPR int on_arg_id() { - part_ = part::make_arg_index(parse_context_.next_arg_id()); - return 0; - } - - FMT_CONSTEXPR int on_arg_id(int id) { - parse_context_.check_arg_id(id); - part_ = part::make_arg_index(id); - return 0; - } - - FMT_CONSTEXPR int on_arg_id(basic_string_view id) { - part_ = part::make_arg_name(id); - return 0; - } - - FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) { - part_.arg_id_end = ptr; - handler_(part_); - } - - FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, - const Char* end) { - auto repl = typename part::replacement(); - dynamic_specs_handler> handler( - repl.specs, parse_context_); - auto it = parse_format_specs(begin, end, handler); - if (*it != '}') on_error("missing '}' in format string"); - repl.arg_id = part_.part_kind == part::kind::arg_index - ? arg_ref(part_.val.arg_index) - : arg_ref(part_.val.str); - auto part = part::make_replacement(repl); - part.arg_id_end = begin; - handler_(part); - return it; - } -}; - -// Compiles a format string and invokes handler(part) for each parsed part. -template -FMT_CONSTEXPR void compile_format_string(basic_string_view format_str, - PartHandler handler) { - parse_format_string( - format_str, - format_string_compiler(format_str, handler)); -} - -template -void format_arg( - basic_format_parse_context& parse_ctx, - Context& ctx, Id arg_id) { - ctx.advance_to(visit_format_arg( - arg_formatter(ctx, &parse_ctx), - ctx.arg(arg_id))); -} - -// vformat_to is defined in a subnamespace to prevent ADL. -namespace cf { -template -auto vformat_to(OutputIt out, CompiledFormat& cf, - basic_format_args args) -> typename Context::iterator { - using char_type = typename Context::char_type; - basic_format_parse_context parse_ctx( - to_string_view(cf.format_str_)); - Context ctx(out, args); - - const auto& parts = cf.parts(); - for (auto part_it = std::begin(parts); part_it != std::end(parts); - ++part_it) { - const auto& part = *part_it; - const auto& value = part.val; - - using format_part_t = format_part; - switch (part.part_kind) { - case format_part_t::kind::text: { - const auto text = value.str; - auto output = ctx.out(); - auto&& it = reserve(output, text.size()); - it = std::copy_n(text.begin(), text.size(), it); - ctx.advance_to(output); - break; - } - - case format_part_t::kind::arg_index: - advance_to(parse_ctx, part.arg_id_end); - detail::format_arg(parse_ctx, ctx, value.arg_index); - break; - - case format_part_t::kind::arg_name: - advance_to(parse_ctx, part.arg_id_end); - detail::format_arg(parse_ctx, ctx, value.str); - break; - - case format_part_t::kind::replacement: { - const auto& arg_id_value = value.repl.arg_id.val; - const auto arg = value.repl.arg_id.kind == arg_id_kind::index - ? ctx.arg(arg_id_value.index) - : ctx.arg(arg_id_value.name); - - auto specs = value.repl.specs; - - handle_dynamic_spec(specs.width, specs.width_ref, ctx); - handle_dynamic_spec(specs.precision, - specs.precision_ref, ctx); - - error_handler h; - numeric_specs_checker checker(h, arg.type()); - if (specs.align == align::numeric) checker.require_numeric_argument(); - if (specs.sign != sign::none) checker.check_sign(); - if (specs.alt) checker.require_numeric_argument(); - if (specs.precision >= 0) checker.check_precision(); - - advance_to(parse_ctx, part.arg_id_end); - ctx.advance_to( - visit_format_arg(arg_formatter( - ctx, nullptr, &specs), - arg)); - break; - } - } - } - return ctx.out(); -} -} // namespace cf - -struct basic_compiled_format {}; - -template -struct compiled_format_base : basic_compiled_format { - using char_type = char_t; - using parts_container = std::vector>; - - parts_container compiled_parts; - - explicit compiled_format_base(basic_string_view format_str) { - compile_format_string(format_str, - [this](const format_part& part) { - compiled_parts.push_back(part); - }); - } - - const parts_container& parts() const { return compiled_parts; } -}; - -template struct format_part_array { - format_part data[N] = {}; - FMT_CONSTEXPR format_part_array() = default; -}; - -template -FMT_CONSTEXPR format_part_array compile_to_parts( - basic_string_view format_str) { - format_part_array parts; - unsigned counter = 0; - // This is not a lambda for compatibility with older compilers. - struct { - format_part* parts; - unsigned* counter; - FMT_CONSTEXPR void operator()(const format_part& part) { - parts[(*counter)++] = part; - } - } collector{parts.data, &counter}; - compile_format_string(format_str, collector); - if (counter < N) { - parts.data[counter] = - format_part::make_text(basic_string_view()); - } - return parts; -} - -template constexpr const T& constexpr_max(const T& a, const T& b) { - return (a < b) ? b : a; -} - -template -struct compiled_format_base::value>> - : basic_compiled_format { - using char_type = char_t; - - FMT_CONSTEXPR explicit compiled_format_base(basic_string_view) {} - -// Workaround for old compilers. Format string compilation will not be -// performed there anyway. -#if FMT_USE_CONSTEXPR - static FMT_CONSTEXPR_DECL const unsigned num_format_parts = - constexpr_max(count_parts(to_string_view(S())), 1u); -#else - static const unsigned num_format_parts = 1; -#endif - - using parts_container = format_part[num_format_parts]; - - const parts_container& parts() const { - static FMT_CONSTEXPR_DECL const auto compiled_parts = - compile_to_parts( - detail::to_string_view(S())); - return compiled_parts.data; - } -}; - -template -class compiled_format : private compiled_format_base { - public: - using typename compiled_format_base::char_type; - - private: - basic_string_view format_str_; - - template - friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf, - basic_format_args args) -> - typename Context::iterator; - - public: - compiled_format() = delete; - explicit constexpr compiled_format(basic_string_view format_str) - : compiled_format_base(format_str), format_str_(format_str) {} -}; - #ifdef __cpp_if_constexpr template struct type_list {}; // Returns a reference to the argument at index N from [first, rest...]. template -constexpr const auto& get(const T& first, const Args&... rest) { +constexpr const auto& get([[maybe_unused]] const T& first, + [[maybe_unused]] const Args&... rest) { static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); if constexpr (N == 0) return first; @@ -376,6 +193,12 @@ constexpr const auto& get(const T& first, const Args&... rest) { return get(rest...); } +template +constexpr int get_arg_index_by_name(basic_string_view name, + type_list) { + return get_arg_index_by_name(name); +} + template struct get_type_impl; template struct get_type_impl> { @@ -392,7 +215,7 @@ template struct text { using char_type = Char; template - OutputIt format(OutputIt out, const Args&...) const { + constexpr OutputIt format(OutputIt out, const Args&...) const { return write(out, data); } }; @@ -406,32 +229,87 @@ constexpr text make_text(basic_string_view s, size_t pos, return {{&s[pos], size}}; } +template struct code_unit { + Char value; + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&...) const { + return write(out, value); + } +}; + +// 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...); + if constexpr (detail::is_named_arg>()) { + return arg.value; + } else { + return arg; + } +} + +template +struct is_compiled_format> : std::true_type {}; + // A replacement field that refers to argument N. template struct field { using char_type = Char; template - OutputIt format(OutputIt out, const Args&... args) const { - // This ensures that the argument type is convertile to `const T&`. - const T& arg = get(args...); - return write(out, arg); + constexpr OutputIt format(OutputIt out, const Args&... args) const { + return write(out, get_arg_checked(args...)); } }; template struct is_compiled_format> : std::true_type {}; +// A replacement field that refers to argument with name. +template struct runtime_named_field { + using char_type = Char; + basic_string_view name; + + template + constexpr static bool try_format_argument( + OutputIt& out, + // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 + [[maybe_unused]] basic_string_view arg_name, const T& arg) { + if constexpr (is_named_arg::type>::value) { + if (arg_name == arg.name) { + out = write(out, arg.value); + return true; + } + } + return false; + } + + template + 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"); + } + return out; + } +}; + +template +struct is_compiled_format> : std::true_type {}; + // A replacement field that refers to argument N and has format specifiers. template struct spec_field { using char_type = Char; - mutable formatter fmt; + formatter fmt; template - OutputIt format(OutputIt out, const Args&... args) const { - // This ensures that the argument type is convertile to `const T&`. - const T& arg = get(args...); - basic_format_context ctx(out, {}); - return fmt.format(arg, ctx); + constexpr FMT_INLINE OutputIt format(OutputIt out, + const Args&... args) const { + const auto& vargs = + fmt::make_format_args>(args...); + basic_format_context ctx(out, vargs); + return fmt.format(get_arg_checked(args...), ctx); } }; @@ -444,7 +322,7 @@ template struct concat { using char_type = typename L::char_type; template - OutputIt format(OutputIt out, const Args&... args) const { + constexpr OutputIt format(OutputIt out, const Args&... args) const { out = lhs.format(out, args...); return rhs.format(out, args...); } @@ -489,16 +367,80 @@ constexpr auto parse_tail(T head, S format_str) { template struct parse_specs_result { formatter fmt; size_t end; + int next_arg_id; }; +constexpr int manual_indexing_id = -1; + template constexpr parse_specs_result parse_specs(basic_string_view str, - size_t pos) { + size_t pos, int next_arg_id) { str.remove_prefix(pos); - auto ctx = basic_format_parse_context(str); + auto ctx = basic_format_parse_context(str, {}, next_arg_id); auto f = formatter(); auto end = f.parse(ctx); - return {f, pos + (end - str.data()) + 1}; + return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, + next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; +} + +template struct arg_id_handler { + arg_ref arg_id; + + constexpr int operator()() { + FMT_ASSERT(false, "handler cannot be used with automatic indexing"); + return 0; + } + constexpr int operator()(int id) { + arg_id = arg_ref(id); + return 0; + } + constexpr int operator()(basic_string_view id) { + arg_id = arg_ref(id); + return 0; + } + + constexpr void on_error(const char* message) { throw format_error(message); } +}; + +template struct parse_arg_id_result { + arg_ref arg_id; + const Char* arg_id_end; +}; + +template +constexpr auto parse_arg_id(const Char* begin, const Char* end) { + auto handler = arg_id_handler{arg_ref{}}; + auto arg_id_end = parse_arg_id(begin, end, handler); + return parse_arg_id_result{handler.arg_id, arg_id_end}; +} + +template struct field_type { + using type = remove_cvref_t; +}; + +template +struct field_type::value>> { + using type = remove_cvref_t; +}; + +template +constexpr auto parse_replacement_field_then_tail(S format_str) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(format_str); + constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); + if constexpr (c == '}') { + return parse_tail( + field::type, ARG_INDEX>(), + format_str); + } else if constexpr (c == ':') { + constexpr auto result = parse_specs::type>( + str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); + return parse_tail( + spec_field::type, ARG_INDEX>{ + result.fmt}, + format_str); + } } // Compiles a non-empty format string and returns the compiled representation @@ -506,160 +448,192 @@ constexpr parse_specs_result parse_specs(basic_string_view str, template constexpr auto compile_format_string(S format_str) { using char_type = typename S::char_type; - constexpr basic_string_view str = format_str; + constexpr auto str = basic_string_view(format_str); if constexpr (str[POS] == '{') { - if (POS + 1 == str.size()) + if constexpr (POS + 1 == str.size()) 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] == '}') { - using type = get_type; - return parse_tail(field(), - format_str); - } else if constexpr (str[POS + 1] == ':') { - using type = get_type; - constexpr auto result = parse_specs(str, POS + 2); - return parse_tail( - spec_field{result.fmt}, format_str); + } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { + static_assert(ID != manual_indexing_id, + "cannot switch from manual to automatic argument indexing"); + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail, Args, + POS + 1, ID, next_id>( + format_str); } else { - return unknown_format(); + constexpr auto arg_id_result = + parse_arg_id(str.data() + POS + 1, str.data() + str.size()); + constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); + constexpr char_type c = + arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); + static_assert(c == '}' || c == ':', "missing '}' in format string"); + if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { + static_assert( + ID == manual_indexing_id || ID == 0, + "cannot switch from automatic to manual argument indexing"); + constexpr auto arg_index = arg_id_result.arg_id.val.index; + return parse_replacement_field_then_tail, + Args, arg_id_end_pos, + arg_index, manual_indexing_id>( + format_str); + } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { + constexpr auto arg_index = + get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); + if constexpr (arg_index != invalid_arg_index) { + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail< + decltype(get_type::value), Args, arg_id_end_pos, + arg_index, next_id>(format_str); + } else { + if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.val.name}, + format_str); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing + } + } + } } } else if constexpr (str[POS] == '}') { - if (POS + 1 == str.size()) + if constexpr (POS + 1 == str.size()) 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); - return parse_tail(make_text(str, POS, end - POS), - format_str); + if constexpr (end - POS > 1) { + return parse_tail(make_text(str, POS, end - POS), + format_str); + } else { + return parse_tail(code_unit{str[POS]}, + format_str); + } } } template ::value || - detail::is_compiled_string::value)> + FMT_ENABLE_IF(detail::is_compiled_string::value)> constexpr auto compile(S format_str) { - constexpr basic_string_view str = format_str; + constexpr auto str = basic_string_view(format_str); if constexpr (str.size() == 0) { return detail::make_text(str, 0, 0); } else { constexpr auto result = detail::compile_format_string, 0, 0>( format_str); - if constexpr (std::is_same, - detail::unknown_format>()) { - return detail::compiled_format(to_string_view(format_str)); - } else { - return result; - } + return result; } } -#else -template ::value)> -constexpr auto compile(S format_str) -> detail::compiled_format { - return detail::compiled_format(to_string_view(format_str)); -} #endif // __cpp_if_constexpr - -// Compiles the format string which must be a string literal. -template -auto compile(const Char (&format_str)[N]) - -> detail::compiled_format { - return detail::compiled_format( - basic_string_view(format_str, N - 1)); -} } // namespace detail -// DEPRECATED! use FMT_COMPILE instead. -template -FMT_DEPRECATED auto compile(const Args&... args) - -> decltype(detail::compile(args...)) { - return detail::compile(args...); -} +FMT_MODULE_EXPORT_BEGIN -#if FMT_USE_CONSTEXPR -# ifdef __cpp_if_constexpr +#ifdef __cpp_if_constexpr template ::value)> FMT_INLINE std::basic_string format(const CompiledFormat& cf, const Args&... args) { - basic_memory_buffer buffer; - detail::buffer& base = buffer; - cf.format(std::back_inserter(base), args...); - return to_string(buffer); + auto s = std::basic_string(); + cf.format(std::back_inserter(s), args...); + return s; } template ::value)> -OutputIt format_to(OutputIt out, const CompiledFormat& cf, - const Args&... args) { +constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, + const Args&... args) { return cf.format(out, args...); } -# endif // __cpp_if_constexpr -#endif // FMT_USE_CONSTEXPR - -template ::value)> -std::basic_string format(const CompiledFormat& cf, const Args&... args) { - basic_memory_buffer buffer; - using context = buffer_context; - detail::buffer& base = buffer; - detail::cf::vformat_to(std::back_inserter(base), cf, - make_format_args(args...)); - return to_string(buffer); -} template ::value)> FMT_INLINE std::basic_string format(const S&, Args&&... args) { - constexpr basic_string_view str = S(); - if (str.size() == 2 && str[0] == '{' && str[1] == '}') - return fmt::to_string(detail::first(args...)); + if constexpr (std::is_same::value) { + constexpr auto str = basic_string_view(S()); + if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { + const auto& first = detail::first(args...); + if constexpr (detail::is_named_arg< + remove_cvref_t>::value) { + return fmt::to_string(first.value); + } else { + return fmt::to_string(first); + } + } + } constexpr auto compiled = detail::compile(S()); - return format(compiled, std::forward(args)...); -} - -template ::value)> -OutputIt format_to(OutputIt out, const CompiledFormat& cf, - const Args&... args) { - using char_type = typename CompiledFormat::char_type; - using context = format_context_t; - return detail::cf::vformat_to(out, cf, - make_format_args(args...)); + if constexpr (std::is_same, + detail::unknown_format>()) { + return format(static_cast>(S()), + std::forward(args)...); + } else { + return format(compiled, std::forward(args)...); + } } template ::value)> -OutputIt format_to(OutputIt out, const S&, const Args&... args) { +FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { constexpr auto compiled = detail::compile(S()); - return format_to(out, compiled, args...); + if constexpr (std::is_same, + detail::unknown_format>()) { + return format_to(out, + static_cast>(S()), + std::forward(args)...); + } else { + return format_to(out, compiled, std::forward(args)...); + } } +#endif -template < - typename OutputIt, typename CompiledFormat, typename... Args, - FMT_ENABLE_IF(detail::is_output_iterator::value&& std::is_base_of< - detail::basic_compiled_format, CompiledFormat>::value)> +template ::value)> format_to_n_result format_to_n(OutputIt out, size_t n, - const CompiledFormat& cf, - const Args&... args) { - auto it = - format_to(detail::truncating_iterator(out, n), cf, args...); + const S& format_str, Args&&... args) { + auto it = format_to(detail::truncating_iterator(out, n), format_str, + std::forward(args)...); return {it.base(), it.count()}; } -template -size_t formatted_size(const CompiledFormat& cf, const Args&... args) { - return format_to(detail::counting_iterator(), cf, args...).count(); +template ::value)> +size_t formatted_size(const S& format_str, const Args&... args) { + return format_to(detail::counting_iterator(), format_str, args...).count(); } +template ::value)> +void print(std::FILE* f, const S& format_str, const Args&... args) { + memory_buffer buffer; + format_to(std::back_inserter(buffer), format_str, args...); + detail::print(f, {buffer.data(), buffer.size()}); +} + +template ::value)> +void print(const S& format_str, const Args&... args) { + print(stdout, format_str, args...); +} + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +inline namespace literals { +template +constexpr detail::udl_compiled_string< + remove_cvref_t, + sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> +operator""_cf() { + return {}; +} +} // namespace literals +#endif + +FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ diff --git a/include/fmt/core.h b/include/fmt/core.h index 37799a3a..4c1f5e2c 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1,4 +1,4 @@ -// Formatting library for C++ - the core API +// Formatting library for C++ - the core API for char/UTF-8 // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. @@ -10,15 +10,13 @@ #include // std::FILE #include -#include #include -#include +#include #include #include -#include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 70003 +#define FMT_VERSION 80000 #ifdef __clang__ # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) @@ -28,14 +26,10 @@ #if defined(__GNUC__) && !defined(__clang__) # define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# define FMT_GCC_PRAGMA(arg) _Pragma(arg) #else # define FMT_GCC_VERSION 0 -#endif - -#if defined(__INTEL_COMPILER) -# define FMT_ICC_VERSION __INTEL_COMPILER -#else -# define FMT_ICC_VERSION 0 +# define FMT_GCC_PRAGMA(arg) #endif #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) @@ -44,6 +38,12 @@ # define FMT_HAS_GXX_CXX11 0 #endif +#if defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif + #ifdef __NVCC__ # define FMT_NVCC __NVCC__ #else @@ -52,11 +52,12 @@ #ifdef _MSC_VER # define FMT_MSC_VER _MSC_VER -# define FMT_SUPPRESS_MSC_WARNING(n) __pragma(warning(suppress : n)) +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) #else # define FMT_MSC_VER 0 -# define FMT_SUPPRESS_MSC_WARNING(n) +# define FMT_MSC_WARNING(...) #endif + #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) #else @@ -64,7 +65,7 @@ #endif #if defined(__has_include) && !defined(__INTELLISENSE__) && \ - !(FMT_ICC_VERSION && FMT_ICC_VERSION < 1600) + (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600) # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 @@ -94,12 +95,28 @@ # define FMT_CONSTEXPR constexpr # define FMT_CONSTEXPR_DECL constexpr #else -# define FMT_CONSTEXPR inline +# define FMT_CONSTEXPR # define FMT_CONSTEXPR_DECL #endif +// Check if constexpr std::char_traits<>::compare,length is supported. +#if defined(__GLIBCXX__) +# if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \ + _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE. +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +# endif +#elif defined(_LIBCPP_VERSION) && __cplusplus >= 201703L && \ + _LIBCPP_VERSION >= 4000 +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +#elif FMT_MSC_VER >= 1914 && _MSVC_LANG >= 201703L +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +#endif +#ifndef FMT_CONSTEXPR_CHAR_TRAITS +# define FMT_CONSTEXPR_CHAR_TRAITS +#endif + #ifndef FMT_OVERRIDE -# if FMT_HAS_FEATURE(cxx_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 @@ -148,25 +165,40 @@ # define FMT_NORETURN #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(__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 -// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. -#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC -# define FMT_DEPRECATED_ALIAS +#if __cplusplus == 201103L || __cplusplus == 201402L +# if defined(__INTEL_COMPILER) || defined(__PGI) +# define FMT_FALLTHROUGH +# elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +# elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +# else +# define FMT_FALLTHROUGH +# endif +#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ + (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define FMT_FALLTHROUGH [[fallthrough]] #else -# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED +# define FMT_FALLTHROUGH +#endif + +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 #endif #ifndef FMT_INLINE @@ -177,9 +209,17 @@ # endif #endif -#ifndef FMT_BEGIN_NAMESPACE +#ifndef FMT_USE_INLINE_NAMESPACES # if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ - FMT_MSC_VER >= 1900 + (FMT_MSC_VER >= 1900 && (!defined(_MANAGED) || !_MANAGED)) +# define FMT_USE_INLINE_NAMESPACES 1 +# else +# define FMT_USE_INLINE_NAMESPACES 0 +# endif +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# if FMT_USE_INLINE_NAMESPACES # define FMT_INLINE_NAMESPACE inline namespace # define FMT_END_NAMESPACE \ } \ @@ -196,33 +236,37 @@ FMT_INLINE_NAMESPACE v7 { #endif +#ifndef FMT_MODULE_EXPORT +# define FMT_MODULE_EXPORT +# define FMT_MODULE_EXPORT_BEGIN +# define FMT_MODULE_EXPORT_END +# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { +# define FMT_END_DETAIL_NAMESPACE } +#endif + #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# define FMT_CLASS_API FMT_SUPPRESS_MSC_WARNING(4275) +# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) # ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) -# define FMT_EXTERN_TEMPLATE_API FMT_API -# define FMT_EXPORTED # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) -# define FMT_EXTERN_TEMPLATE_API FMT_API # endif #else # define FMT_CLASS_API +# if defined(FMT_EXPORT) || defined(FMT_SHARED) +# if defined(__GNUC__) || defined(__clang__) +# define FMT_API __attribute__((visibility("default"))) +# endif +# endif #endif #ifndef FMT_API # define FMT_API #endif -#ifndef FMT_EXTERN_TEMPLATE_API -# define FMT_EXTERN_TEMPLATE_API -#endif -#ifndef FMT_INSTANTIATION_DEF_API -# define FMT_INSTANTIATION_DEF_API FMT_API -#endif -#ifndef FMT_HEADER_ONLY -# define FMT_EXTERN extern +#if FMT_GCC_VERSION +# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) #else -# define FMT_EXTERN +# define FMT_GCC_VISIBILITY_HIDDEN #endif // libc++ supports string_view in pre-c++17. @@ -239,11 +283,37 @@ #ifndef FMT_UNICODE # define FMT_UNICODE !FMT_MSC_VER #endif -#if FMT_UNICODE && FMT_MSC_VER -# pragma execution_character_set("utf-8") + +#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. +# define FMT_CONSTEVAL consteval +# define FMT_HAS_CONSTEVAL +# else +# define FMT_CONSTEVAL +# endif +#endif + +#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +# if defined(__cpp_nontype_template_args) && \ + ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \ + __cpp_nontype_template_args >= 201911L) +# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1 +# else +# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0 +# endif +#endif + +// Enable minimal optimizations for more compact code in debug mode. +FMT_GCC_PRAGMA("GCC push_options") +#ifndef __OPTIMIZE__ +FMT_GCC_PRAGMA("GCC optimize(\"Og\")") #endif FMT_BEGIN_NAMESPACE +FMT_MODULE_EXPORT_BEGIN // Implementations of enable_if_t and other metafunctions for older systems. template @@ -254,24 +324,35 @@ 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; -struct monostate {}; +struct monostate { + constexpr monostate() {} +}; // An enable_if helper to be used in template parameters which results in much // shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed // to workaround a bug in MSVC 2019 (see #1140 and #1186). -#define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +#endif -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE -// A helper function to suppress bogus "conditional expression is constant" -// warnings. -template constexpr T const_check(T value) { return value; } +constexpr FMT_INLINE auto is_constant_evaluated() FMT_NOEXCEPT -> bool { +#ifdef __cpp_lib_is_constant_evaluated + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +// A function to suppress "conditional expression is constant" warnings. +template constexpr auto const_check(T value) -> T { return value; } FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); @@ -299,42 +380,44 @@ template struct std_string_view {}; #ifdef FMT_USE_INT128 // Do nothing. -#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && !(FMT_CLANG_VERSION && FMT_MSC_VER) +#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \ + !(FMT_CLANG_VERSION && FMT_MSC_VER) # define FMT_USE_INT128 1 using int128_t = __int128_t; using uint128_t = __uint128_t; +template inline auto convert_for_visit(T value) -> T { + return value; +} #else # define FMT_USE_INT128 0 #endif #if !FMT_USE_INT128 -struct int128_t {}; -struct uint128_t {}; +enum class int128_t {}; +enum class uint128_t {}; +// Reduce template instantiations. +template inline auto convert_for_visit(T) -> monostate { + return {}; +} #endif // Casts a nonnegative integer to unsigned. template -FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { +FMT_CONSTEXPR auto to_unsigned(Int value) -> + typename std::make_unsigned::type { FMT_ASSERT(value >= 0, "negative value"); return static_cast::type>(value); } -FMT_SUPPRESS_MSC_WARNING(4566) constexpr unsigned char micro[] = "\u00B5"; +FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; -template constexpr bool is_unicode() { - return FMT_UNICODE || sizeof(Char) != 1 || - (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5); +constexpr auto is_utf8() -> bool { + // Avoid buggy sign extensions in MSVC's constant evaluation mode. + // https://developercommunity.visualstudio.com/t/C-difference-in-behavior-for-unsigned/1233612 + using uchar = unsigned char; + return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && + uchar(micro[1]) == 0xB5); } - -#ifdef __cpp_char8_t -using char8_type = char8_t; -#else -enum char8_type : unsigned char {}; -#endif -} // namespace detail - -#ifdef FMT_USE_INTERNAL -namespace internal = detail; // DEPRECATED -#endif +FMT_END_DETAIL_NAMESPACE /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -365,11 +448,15 @@ template class basic_string_view { the size with ``std::char_traits::length``. \endrst */ -#if __cplusplus >= 201703L // C++17's char_traits::length() is constexpr. - FMT_CONSTEXPR -#endif - basic_string_view(const Char* s) - : data_(s), size_(std::char_traits::length(s)) {} + 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); + } /** Constructs a string reference from a ``std::basic_string`` object. */ template @@ -384,15 +471,17 @@ template class basic_string_view { size_(s.size()) {} /** Returns a pointer to the string data. */ - constexpr const Char* data() const { return data_; } + constexpr auto data() const -> const Char* { return data_; } /** Returns the string size. */ - constexpr size_t size() const { return size_; } + constexpr auto size() const -> size_t { return size_; } - constexpr iterator begin() const { return data_; } - constexpr iterator end() const { return data_ + size_; } + constexpr auto begin() const -> iterator { return data_; } + constexpr auto end() const -> iterator { return data_ + size_; } - constexpr const Char& operator[](size_t pos) const { return data_[pos]; } + constexpr auto operator[](size_t pos) const -> const Char& { + return data_[pos]; + } FMT_CONSTEXPR void remove_prefix(size_t n) { data_ += n; @@ -400,7 +489,7 @@ template class basic_string_view { } // Lexicographically compare this string reference to other. - int compare(basic_string_view other) const { + FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; int result = std::char_traits::compare(data_, other.data_, str_size); if (result == 0) @@ -408,36 +497,33 @@ template class basic_string_view { return result; } - friend bool operator==(basic_string_view lhs, basic_string_view rhs) { + FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs, + basic_string_view rhs) + -> bool { return lhs.compare(rhs) == 0; } - friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) != 0; } - friend bool operator<(basic_string_view lhs, basic_string_view rhs) { + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) < 0; } - friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) <= 0; } - friend bool operator>(basic_string_view lhs, basic_string_view rhs) { + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) > 0; } - friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) >= 0; } }; using string_view = basic_string_view; -using wstring_view = basic_string_view; /** Specifies if ``T`` is a character type. Can be specialized by users. */ template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; /** \rst @@ -456,24 +542,26 @@ template <> struct is_char : std::true_type {}; \endrst */ template ::value)> -inline basic_string_view to_string_view(const Char* s) { +FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { return s; } template -inline basic_string_view to_string_view( - const std::basic_string& s) { +inline auto to_string_view(const std::basic_string& s) + -> basic_string_view { return s; } template -inline basic_string_view to_string_view(basic_string_view s) { +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { return s; } template >::value)> -inline basic_string_view to_string_view(detail::std_string_view s) { +inline auto to_string_view(detail::std_string_view s) + -> basic_string_view { return s; } @@ -485,11 +573,13 @@ template struct is_compile_string : std::is_base_of {}; template ::value)> -constexpr basic_string_view to_string_view(const S& s) { - return s; +constexpr auto to_string_view(const S& s) + -> basic_string_view { + return basic_string_view(s); } -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE + void to_string_view(...); using fmt::v7::to_string_view; @@ -506,6 +596,18 @@ template struct char_t_impl::value>> { using type = typename result::value_type; }; +// Reports a compile-time error if S is not a valid format string. +template ::value)> +FMT_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert(is_compile_string::value, + "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " + "FMT_STRING."); +#endif +} +template ::value)> +void check_format_string(S); + struct error_handler { constexpr error_handler() = default; constexpr error_handler(const error_handler&) = default; @@ -513,7 +615,7 @@ struct error_handler { // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN FMT_API void on_error(const char* message); }; -} // namespace detail +FMT_END_DETAIL_NAMESPACE /** String's character type. */ template using char_t = typename detail::char_t_impl::type; @@ -522,16 +624,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 one of the following type aliases for common character types: - - +-----------------------+-------------------------------------+ - | Type | Definition | - +=======================+=====================================+ - | format_parse_context | basic_format_parse_context | - +-----------------------+-------------------------------------+ - | wformat_parse_context | basic_format_parse_context | - +-----------------------+-------------------------------------+ + You can use the ```format_parse_context`` type alias for ``char`` instead. \endrst */ template @@ -545,19 +638,24 @@ class basic_format_parse_context : private ErrorHandler { using iterator = typename basic_string_view::iterator; explicit constexpr basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = {}) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} + basic_string_view format_str, ErrorHandler eh = {}, + int next_arg_id = 0) + : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} /** Returns an iterator to the beginning of the format string range being parsed. */ - constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); } + constexpr auto begin() const FMT_NOEXCEPT -> iterator { + return format_str_.begin(); + } /** Returns an iterator past the end of the format string range being parsed. */ - constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); } + constexpr auto end() const FMT_NOEXCEPT -> iterator { + return format_str_.end(); + } /** Advances the begin iterator to ``it``. */ FMT_CONSTEXPR void advance_to(iterator it) { @@ -568,7 +666,7 @@ class basic_format_parse_context : private ErrorHandler { Reports an error if using the manual argument indexing; otherwise returns the next argument index and switches to the automatic indexing. */ - FMT_CONSTEXPR int next_arg_id() { + FMT_CONSTEXPR auto next_arg_id() -> int { // Don't check if the argument id is valid to avoid overhead and because it // will be checked during formatting anyway. if (next_arg_id_ >= 0) return next_arg_id_++; @@ -593,11 +691,10 @@ class basic_format_parse_context : private ErrorHandler { ErrorHandler::on_error(message); } - constexpr ErrorHandler error_handler() const { return *this; } + constexpr auto error_handler() const -> ErrorHandler { return *this; } }; using format_parse_context = basic_format_parse_context; -using wformat_parse_context = basic_format_parse_context; template class basic_format_arg; template class basic_format_args; @@ -616,7 +713,43 @@ template using has_formatter = std::is_constructible>; -namespace detail { +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; +template +struct is_contiguous> : std::true_type {}; + +class appender; + +FMT_BEGIN_DETAIL_NAMESPACE + +// Extracts a reference to the container from back_insert_iterator. +template +inline auto get_container(std::back_insert_iterator it) + -> Container& { + using bi_iterator = std::back_insert_iterator; + struct accessor : bi_iterator { + accessor(bi_iterator iter) : bi_iterator(iter) {} + using bi_iterator::container; + }; + return *accessor(it).container; +} + +template +FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + 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); + auto size = to_unsigned(end - begin); + memcpy(out, begin, size); + return out + size; +} /** \rst @@ -632,7 +765,7 @@ template class buffer { protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. - FMT_SUPPRESS_MSC_WARNING(26495) + 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 @@ -640,6 +773,9 @@ template class buffer { size_(sz), capacity_(cap) {} + ~buffer() = default; + buffer(buffer&&) = default; + /** Sets the buffer data and capacity. */ void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { ptr_ = buf_data; @@ -655,82 +791,182 @@ template class buffer { buffer(const buffer&) = delete; void operator=(const buffer&) = delete; - virtual ~buffer() = default; - T* begin() FMT_NOEXCEPT { return ptr_; } - T* end() FMT_NOEXCEPT { return ptr_ + size_; } + auto begin() FMT_NOEXCEPT -> T* { return ptr_; } + auto end() FMT_NOEXCEPT -> T* { return ptr_ + size_; } - const T* begin() const FMT_NOEXCEPT { return ptr_; } - const T* end() const FMT_NOEXCEPT { return ptr_ + size_; } + auto begin() const FMT_NOEXCEPT -> const T* { return ptr_; } + auto end() const FMT_NOEXCEPT -> const T* { return ptr_ + size_; } /** Returns the size of this buffer. */ - size_t size() const FMT_NOEXCEPT { return size_; } + auto size() const FMT_NOEXCEPT -> size_t { return size_; } /** Returns the capacity of this buffer. */ - size_t capacity() const FMT_NOEXCEPT { return capacity_; } + auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; } /** Returns a pointer to the buffer data. */ - T* data() FMT_NOEXCEPT { return ptr_; } + auto data() FMT_NOEXCEPT -> T* { return ptr_; } /** Returns a pointer to the buffer data. */ - const T* data() const FMT_NOEXCEPT { return ptr_; } - - /** - Resizes the buffer. If T is a POD type new elements may not be initialized. - */ - void resize(size_t new_size) { - reserve(new_size); - size_ = new_size; - } + auto data() const FMT_NOEXCEPT -> const T* { return ptr_; } /** Clears this buffer. */ void clear() { size_ = 0; } - /** Reserves space to store at least *capacity* elements. */ - void reserve(size_t new_capacity) { + // 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) { + try_reserve(count); + size_ = count <= capacity_ ? count : capacity_; + } + + // Tries increasing the buffer capacity to *new_capacity*. It can increase the + // 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) { if (new_capacity > capacity_) grow(new_capacity); } void push_back(const T& value) { - reserve(size_ + 1); + try_reserve(size_ + 1); ptr_[size_++] = value; } /** Appends data to the end of the buffer. */ template void append(const U* begin, const U* end); - template T& operator[](I index) { return ptr_[index]; } - template const T& operator[](I index) const { + template auto operator[](I index) -> T& { return ptr_[index]; } + template auto operator[](I index) const -> const T& { return ptr_[index]; } }; -// A container-backed buffer. +struct buffer_traits { + explicit buffer_traits(size_t) {} + auto count() const -> size_t { return 0; } + auto limit(size_t size) -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + auto count() const -> size_t { return count_; } + auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return size < n ? size : n; + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer final : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + void grow(size_t) final FMT_OVERRIDE { + if (this->size() == buffer_size) flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + out_ = copy_str(data_, data_ + this->limit(size), out_); + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) + : Traits(other), buffer(data_, 0, buffer_size), out_(other.out_) {} + ~iterator_buffer() { flush(); } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template class iterator_buffer final : public buffer { + protected: + void grow(size_t) final FMT_OVERRIDE {} + + public: + explicit iterator_buffer(T* out, size_t = 0) : buffer(out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +// A buffer that writes to a container with the contiguous storage. template -class container_buffer : public buffer { +class iterator_buffer, + enable_if_t::value, + typename Container::value_type>> + final : public buffer { private: Container& container_; protected: - void grow(size_t capacity) FMT_OVERRIDE { + void grow(size_t capacity) final FMT_OVERRIDE { container_.resize(capacity); this->set(&container_[0], capacity); } public: - explicit container_buffer(Container& c) + explicit iterator_buffer(Container& c) : buffer(c.size()), container_(c) {} + explicit iterator_buffer(std::back_insert_iterator out, size_t = 0) + : iterator_buffer(get_container(out)) {} + auto out() -> std::back_insert_iterator { + return std::back_inserter(container_); + } }; -// Extracts a reference to the container from back_insert_iterator. -template -inline Container& get_container(std::back_insert_iterator it) { - using bi_iterator = std::back_insert_iterator; - struct accessor : bi_iterator { - accessor(bi_iterator iter) : bi_iterator(iter) {} - using bi_iterator::container; - }; - return *accessor(it).container; +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer final : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + protected: + void grow(size_t) final FMT_OVERRIDE { + if (this->size() != buffer_size) return; + count_ += this->size(); + this->clear(); + } + + public: + counting_buffer() : buffer(data_, 0, buffer_size) {} + + auto count() -> size_t { return count_ + this->size(); } +}; + +template +using buffer_appender = conditional_t::value, appender, + std::back_insert_iterator>>; + +// Maps an output iterator to a buffer. +template +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} + +template +auto get_iterator(Buffer& buf) -> decltype(buf.out()) { + return buf.out(); +} +template auto get_iterator(buffer& buf) -> buffer_appender { + return buffer_appender(buf); } template @@ -739,9 +975,9 @@ struct fallback_formatter { }; // Specifies if T has an enabled fallback_formatter specialization. -template +template using has_fallback_formatter = - std::is_constructible>; + std::is_constructible>; struct view {}; @@ -759,57 +995,64 @@ template struct named_arg_info { template struct arg_data { // args_[0].named_args points to named_args_ to avoid bloating format_args. - T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)]; + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; named_arg_info named_args_[NUM_NAMED_ARGS]; template arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} arg_data(const arg_data& other) = delete; - const T* args() const { return args_ + 1; } - named_arg_info* named_args() { return named_args_; } + auto args() const -> const T* { return args_ + 1; } + auto named_args() -> named_arg_info* { return named_args_; } }; template struct arg_data { - T args_[NUM_ARGS != 0 ? NUM_ARGS : 1]; + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; template - FMT_INLINE arg_data(const U&... init) : args_{init...} {} - FMT_INLINE const T* args() const { return args_; } - FMT_INLINE std::nullptr_t named_args() { return nullptr; } + FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} + FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; } + FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t { + return nullptr; + } }; template inline void init_named_args(named_arg_info*, int, int) {} -template +template struct is_named_arg : std::false_type {}; +template struct is_statically_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template ::value)> void init_named_args(named_arg_info* named_args, int arg_count, int named_arg_count, const T&, const Tail&... args) { init_named_args(named_args, arg_count + 1, named_arg_count, args...); } -template +template ::value)> void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const named_arg& arg, - const Tail&... args) { + int named_arg_count, const T& arg, const Tail&... args) { named_args[named_arg_count++] = {arg.name, arg_count}; init_named_args(named_args, arg_count + 1, named_arg_count, args...); } template -FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {} +FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, + const Args&...) {} -template struct is_named_arg : std::false_type {}; - -template -struct is_named_arg> : std::true_type {}; - -template constexpr size_t count() { return B ? 1 : 0; } -template constexpr size_t count() { +template constexpr auto count() -> size_t { return B ? 1 : 0; } +template constexpr auto count() -> size_t { return (B1 ? 1 : 0) + count(); } -template constexpr size_t count_named_args() { +template constexpr auto count_named_args() -> size_t { return count::value...>(); } @@ -890,6 +1133,7 @@ template class value { using char_type = typename Context::char_type; union { + monostate no_value; int int_value; unsigned uint_value; long long long_long_value; @@ -907,19 +1151,23 @@ template class value { named_arg_value named_args; }; - constexpr FMT_INLINE value(int val = 0) : int_value(val) {} + constexpr FMT_INLINE value() : no_value() {} + constexpr FMT_INLINE value(int val) : int_value(val) {} constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} - FMT_INLINE value(long long val) : long_long_value(val) {} - FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} + constexpr FMT_INLINE value(long long val) : long_long_value(val) {} + 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) {} FMT_INLINE value(long double val) : long_double_value(val) {} - FMT_INLINE value(bool val) : bool_value(val) {} - FMT_INLINE value(char_type val) : char_value(val) {} - FMT_INLINE value(const char_type* val) { string.data = val; } - FMT_INLINE value(basic_string_view val) { + constexpr FMT_INLINE value(bool val) : bool_value(val) {} + constexpr FMT_INLINE value(char_type val) : char_value(val) {} + FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { + string.data = val; + if (is_constant_evaluated()) string.size = {}; + } + FMT_CONSTEXPR FMT_INLINE value(basic_string_view val) { string.data = val.data(); string.size = val.size(); } @@ -927,7 +1175,7 @@ template class value { FMT_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} - template FMT_INLINE value(const T& val) { + template FMT_CONSTEXPR FMT_INLINE value(const T& val) { custom.value = &val; // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and @@ -951,7 +1199,7 @@ template class value { }; template -FMT_CONSTEXPR basic_format_arg make_arg(const T& value); +FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg; // To minimize the number of types we need to deal with, long is translated // either to int or to long long depending on its size. @@ -959,40 +1207,58 @@ 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. template struct arg_mapper { using char_type = typename Context::char_type; - FMT_CONSTEXPR int map(signed char val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned char val) { return val; } - FMT_CONSTEXPR int map(short val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned short val) { return val; } - FMT_CONSTEXPR int map(int val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned val) { return val; } - FMT_CONSTEXPR long_type map(long val) { return val; } - FMT_CONSTEXPR ulong_type map(unsigned long val) { return val; } - FMT_CONSTEXPR long long map(long long val) { return val; } - FMT_CONSTEXPR unsigned long long map(unsigned long long val) { return val; } - FMT_CONSTEXPR int128_t map(int128_t val) { return val; } - FMT_CONSTEXPR uint128_t map(uint128_t val) { return val; } - FMT_CONSTEXPR bool map(bool val) { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val) + -> unsigned long long { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(int128_t val) -> int128_t { return val; } + 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)> - FMT_CONSTEXPR char_type map(T val) { + 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; } - FMT_CONSTEXPR float map(float val) { return val; } - FMT_CONSTEXPR double map(double val) { return val; } - FMT_CONSTEXPR long double map(long double val) { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double { + return val; + } - FMT_CONSTEXPR const char_type* map(char_type* val) { return val; } - FMT_CONSTEXPR const char_type* map(const char_type* val) { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { + return val; + } template ::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { + 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); @@ -1001,8 +1267,9 @@ template struct arg_mapper { FMT_ENABLE_IF( std::is_constructible, T>::value && !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { + !has_fallback_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view { return basic_string_view(val); } template < @@ -1011,31 +1278,40 @@ template struct arg_mapper { std::is_constructible, T>::value && !std::is_constructible, T>::value && !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { + !has_fallback_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view { return std_string_view(val); } - FMT_CONSTEXPR const char* map(const signed char* 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); } - FMT_CONSTEXPR const char* map(const unsigned char* 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_CONSTEXPR const char* map(signed char* val) { + FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) -> const char* { const auto* const_val = val; return map(const_val); } - FMT_CONSTEXPR const char* map(unsigned char* val) { + FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) -> const char* { const auto* const_val = val; return map(const_val); } - FMT_CONSTEXPR const void* map(void* val) { return val; } - FMT_CONSTEXPR const void* map(const void* val) { return val; } - FMT_CONSTEXPR const void* map(std::nullptr_t val) { return val; } - template FMT_CONSTEXPR int map(const T*) { + FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* { + return val; + } + + // 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 @@ -1044,11 +1320,16 @@ template struct arg_mapper { return 0; } + template + 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_CONSTEXPR auto map(const T& val) + !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)); @@ -1056,26 +1337,18 @@ template struct arg_mapper { template ::value && !is_char::value && (has_formatter::value || - has_fallback_formatter::value))> - FMT_CONSTEXPR const T& map(const T& val) { + has_fallback_formatter::value))> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> const T& { return val; } - template - FMT_CONSTEXPR auto map(const named_arg& val) - -> decltype(std::declval().map(val.value)) { - return map(val.value); + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) + -> decltype(std::declval().map(named_arg.value)) { + return map(named_arg.value); } - int map(...) { - constexpr bool formattable = sizeof(Context) == 0; - static_assert( - formattable, - "Cannot format argument. To make type T formattable provide a " - "formatter specialization: " - "https://fmt.dev/latest/api.html#formatting-user-defined-types"); - return 0; - } + auto map(...) -> unformattable { return {}; } }; // A type constant after applying arg_mapper. @@ -1089,7 +1362,35 @@ enum { packed_arg_bits = 4 }; enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; -} // namespace detail + +FMT_END_DETAIL_NAMESPACE + +// An output iterator that appends to a buffer. +// It is used to reduce symbol sizes for the common case. +class appender : public std::back_insert_iterator> { + using base = std::back_insert_iterator>; + + template + friend auto get_buffer(appender out) -> detail::buffer& { + return detail::get_container(out); + } + + public: + using std::back_insert_iterator>::back_insert_iterator; + appender(base it) : base(it) {} + using _Unchecked_type = appender; // Mark iterator as checked. + + auto operator++() -> appender& { + base::operator++(); + return *this; + } + + auto operator++(int) -> appender { + auto tmp = *this; + ++*this; + return tmp; + } +}; // A formatting argument. It is a trivially copyable/constructible type to // allow storage in basic_memory_buffer. @@ -1099,8 +1400,8 @@ template class basic_format_arg { detail::type type_; template - friend FMT_CONSTEXPR basic_format_arg detail::make_arg( - const T& value); + friend FMT_CONSTEXPR auto detail::make_arg(const T& value) + -> basic_format_arg; template friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, @@ -1138,10 +1439,12 @@ template class basic_format_arg { return type_ != detail::type::none_type; } - detail::type type() const { return type_; } + auto type() const -> detail::type { return type_; } - bool is_integral() const { return detail::is_integral_type(type_); } - bool is_arithmetic() const { return detail::is_arithmetic_type(type_); } + auto is_integral() const -> bool { return detail::is_integral_type(type_); } + auto is_arithmetic() const -> bool { + return detail::is_arithmetic_type(type_); + } }; /** @@ -1152,9 +1455,8 @@ template class basic_format_arg { \endrst */ template -FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( +FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { - using char_type = typename Context::char_type; switch (arg.type_) { case detail::type::none_type: break; @@ -1166,16 +1468,10 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( return vis(arg.value_.long_long_value); case detail::type::ulong_long_type: return vis(arg.value_.ulong_long_value); -#if FMT_USE_INT128 case detail::type::int128_type: - return vis(arg.value_.int128_value); + return vis(detail::convert_for_visit(arg.value_.int128_value)); case detail::type::uint128_type: - return vis(arg.value_.uint128_value); -#else - case detail::type::int128_type: - case detail::type::uint128_type: - break; -#endif + return vis(detail::convert_for_visit(arg.value_.uint128_value)); case detail::type::bool_type: return vis(arg.value_.bool_value); case detail::type::char_type: @@ -1189,8 +1485,8 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( case detail::type::cstring_type: return vis(arg.value_.string.data); case detail::type::string_type: - return vis(basic_string_view(arg.value_.string.data, - arg.value_.string.size)); + using sv = basic_string_view; + return vis(sv(arg.value_.string.data, arg.value_.string.size)); case detail::type::pointer_type: return vis(arg.value_.pointer); case detail::type::custom_type: @@ -1199,14 +1495,32 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( return vis(monostate()); } -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; -template -struct is_contiguous> : std::true_type {}; -template -struct is_contiguous> : std::true_type {}; +FMT_BEGIN_DETAIL_NAMESPACE -namespace detail { +template +auto copy_str(InputIt begin, InputIt end, appender out) -> appender { + get_container(out).append(begin, end); + return out; +} + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { using type = void; }; +template +using void_t = typename detail::void_t_impl::type; +#else +template using void_t = void; +#endif + +template +struct is_output_iterator : std::false_type {}; + +template +struct is_output_iterator< + It, T, + void_t::iterator_category, + decltype(*std::declval() = std::declval())>> + : std::true_type {}; template struct is_back_insert_iterator : std::false_type {}; @@ -1219,6 +1533,8 @@ struct is_contiguous_back_insert_iterator : std::false_type {}; template struct is_contiguous_back_insert_iterator> : is_contiguous {}; +template <> +struct is_contiguous_back_insert_iterator : std::true_type {}; // A type-erased reference to an std::locale to avoid heavy include. class locale_ref { @@ -1226,24 +1542,26 @@ class locale_ref { const void* locale_; // A type-erased pointer to std::locale. public: - locale_ref() : locale_(nullptr) {} + constexpr locale_ref() : locale_(nullptr) {} template explicit locale_ref(const Locale& loc); explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; } - template Locale get() const; + template auto get() const -> Locale; }; -template constexpr unsigned long long encode_types() { return 0; } +template constexpr auto encode_types() -> unsigned long long { + return 0; +} template -constexpr unsigned long long encode_types() { +constexpr auto encode_types() -> unsigned long long { return static_cast(mapped_type_constant::value) | (encode_types() << packed_arg_bits); } template -FMT_CONSTEXPR basic_format_arg make_arg(const T& value) { +FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg { basic_format_arg arg; arg.type_ = mapped_type_constant::value; arg.value_ = arg_mapper().map(value); @@ -1255,57 +1573,21 @@ FMT_CONSTEXPR basic_format_arg make_arg(const T& value) { // another (not recommended). template -inline value make_arg(const T& val) { - return arg_mapper().map(val); +FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value { + const auto& arg = arg_mapper().map(val); + static_assert( + !std::is_same::value, + "Cannot format an argument. To make type T formattable provide a " + "formatter specialization: https://fmt.dev/latest/api.html#udt"); + return {arg}; } template -inline basic_format_arg make_arg(const T& value) { +inline auto make_arg(const T& value) -> basic_format_arg { return make_arg(value); } - -template struct is_reference_wrapper : std::false_type {}; -template -struct is_reference_wrapper> : std::true_type {}; - -template const T& unwrap(const T& v) { return v; } -template const T& unwrap(const std::reference_wrapper& v) { - return static_cast(v); -} - -class dynamic_arg_list { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - template struct node { - virtual ~node() = default; - std::unique_ptr> next; - }; - - template struct typed_node : node<> { - T value; - - template - FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} - - template - FMT_CONSTEXPR typed_node(const basic_string_view& arg) - : value(arg.data(), arg.size()) {} - }; - - std::unique_ptr> head_; - - public: - template const T& push(const Arg& arg) { - auto new_node = std::unique_ptr>(new typed_node(arg)); - auto& value = new_node->value; - new_node->next = std::move(head_); - head_ = std::move(new_node); - return value; - } -}; -} // namespace detail +FMT_END_DETAIL_NAMESPACE // Formatting context. template class basic_format_context { @@ -1324,45 +1606,58 @@ template class basic_format_context { using parse_context_type = basic_format_parse_context; template using formatter_type = formatter; + basic_format_context(basic_format_context&&) = default; basic_format_context(const basic_format_context&) = delete; void operator=(const basic_format_context&) = delete; /** Constructs a ``basic_format_context`` object. References to the arguments are stored in the object so make sure they have appropriate lifetimes. */ - basic_format_context(OutputIt out, - basic_format_args ctx_args, - detail::locale_ref loc = detail::locale_ref()) + constexpr basic_format_context( + OutputIt out, basic_format_args ctx_args, + detail::locale_ref loc = detail::locale_ref()) : out_(out), args_(ctx_args), loc_(loc) {} - format_arg arg(int id) const { return args_.get(id); } - format_arg arg(basic_string_view name) { return args_.get(name); } - int arg_id(basic_string_view name) { return args_.get_id(name); } - const basic_format_args& args() const { return args_; } + constexpr auto arg(int id) const -> format_arg { return args_.get(id); } + FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + return args_.get_id(name); + } + auto args() const -> const basic_format_args& { + return args_; + } - detail::error_handler error_handler() { return {}; } + FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } // Returns an iterator to the beginning of the output range. - iterator out() { return out_; } + FMT_CONSTEXPR auto out() -> iterator { return out_; } // Advances the begin iterator to ``it``. void advance_to(iterator it) { if (!detail::is_back_insert_iterator()) out_ = it; } - detail::locale_ref locale() { return loc_; } + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } }; template using buffer_context = - basic_format_context>, Char>; + basic_format_context, Char>; using format_context = buffer_context; -using wformat_context = buffer_context; -// Workaround a bug in gcc: https://stackoverflow.com/q/62767544/471164. +// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. #define FMT_BUFFER_CONTEXT(Char) \ - basic_format_context>, Char> + basic_format_context, Char> + +template +using is_formattable = bool_constant< + !std::is_same>().map( + std::declval())), + detail::unformattable>::value && + !detail::has_fallback_formatter::value>; /** \rst @@ -1400,7 +1695,7 @@ class format_arg_store : 0); public: - format_arg_store(const Args&... args) + FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), @@ -1414,22 +1709,23 @@ class format_arg_store /** \rst - Constructs an `~fmt::format_arg_store` object that contains references to + Constructs a `~fmt::format_arg_store` object that contains references to arguments and can be implicitly converted to `~fmt::format_args`. `Context` can be omitted in which case it defaults to `~fmt::context`. See `~fmt::arg` for lifetime considerations. \endrst */ template -inline format_arg_store make_format_args( - const Args&... args) { +constexpr auto make_format_args(const Args&... args) + -> format_arg_store { return {args...}; } /** \rst - Returns a named argument to be used in a formatting function. It should only - be used in a call to a formatting function. + Returns a named argument to be used in a formatting function. + It should only be used in a call to a formatting function or + `dynamic_format_arg_store::push_back`. **Example**:: @@ -1437,184 +1733,11 @@ inline format_arg_store make_format_args( \endrst */ template -inline detail::named_arg arg(const Char* name, const T& arg) { +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { static_assert(!detail::is_named_arg(), "nested named arguments"); return {name, arg}; } -/** - \rst - A dynamic version of `fmt::format_arg_store`. - It's equipped with a storage to potentially temporary objects which lifetimes - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst - */ -template -class dynamic_format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - using char_type = typename Context::char_type; - - template struct need_copy { - static constexpr detail::type mapped_type = - detail::mapped_type_constant::value; - - enum { - value = !(detail::is_reference_wrapper::value || - std::is_same>::value || - std::is_same>::value || - (mapped_type != detail::type::cstring_type && - mapped_type != detail::type::string_type && - mapped_type != detail::type::custom_type)) - }; - }; - - template - using stored_type = conditional_t::value, - std::basic_string, T>; - - // Storage of basic_format_arg must be contiguous. - std::vector> data_; - std::vector> named_info_; - - // Storage of arguments not fitting into basic_format_arg must grow - // without relocation because items in data_ refer to it. - detail::dynamic_arg_list dynamic_args_; - - friend class basic_format_args; - - unsigned long long get_types() const { - return detail::is_unpacked_bit | data_.size() | - (named_info_.empty() - ? 0ULL - : static_cast(detail::has_named_args_bit)); - } - - const basic_format_arg* data() const { - return named_info_.empty() ? data_.data() : data_.data() + 1; - } - - template void emplace_arg(const T& arg) { - data_.emplace_back(detail::make_arg(arg)); - } - - template - void emplace_arg(const detail::named_arg& arg) { - if (named_info_.empty()) { - constexpr const detail::named_arg_info* zero_ptr{nullptr}; - data_.insert(data_.begin(), {zero_ptr, 0}); - } - data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); - auto pop_one = [](std::vector>* data) { - data->pop_back(); - }; - std::unique_ptr>, decltype(pop_one)> - guard{&data_, pop_one}; - named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); - data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; - guard.release(); - } - - public: - /** - \rst - Adds an argument into the dynamic store for later passing to a formatting - function. - - Note that custom types and string types (but not string views) are copied - into the store dynamically allocating memory if necessary. - - **Example**:: - - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ - template void push_back(const T& arg) { - if (detail::const_check(need_copy::value)) - emplace_arg(dynamic_args_.push>(arg)); - else - emplace_arg(detail::unwrap(arg)); - } - - /** - \rst - Adds a reference to the argument into the dynamic store for later passing to - a formatting function. Supports named arguments wrapped in - ``std::reference_wrapper`` via ``std::ref()``/``std::cref()``. - - **Example**:: - - fmt::dynamic_format_arg_store store; - char str[] = "1234567890"; - store.push_back(std::cref(str)); - int a1_val{42}; - auto a1 = fmt::arg("a1_", a1_val); - store.push_back(std::cref(a1)); - - // Changing str affects the output but only for string and custom types. - str[0] = 'X'; - - std::string result = fmt::vformat("{} and {a1_}"); - assert(result == "X234567890 and 42"); - \endrst - */ - template void push_back(std::reference_wrapper arg) { - static_assert( - detail::is_named_arg::type>::value || - need_copy::value, - "objects of built-in types and string views are always copied"); - emplace_arg(arg.get()); - } - - /** - Adds named argument into the dynamic store for later passing to a formatting - function. ``std::reference_wrapper`` is supported to avoid copying of the - argument. - */ - template - void push_back(const detail::named_arg& arg) { - const char_type* arg_name = - dynamic_args_.push>(arg.name).c_str(); - if (detail::const_check(need_copy::value)) { - emplace_arg( - fmt::arg(arg_name, dynamic_args_.push>(arg.value))); - } else { - emplace_arg(fmt::arg(arg_name, arg.value)); - } - } - - /** Erase all elements from the store */ - void clear() { - data_.clear(); - named_info_.clear(); - dynamic_args_ = detail::dynamic_arg_list(); - } - - /** - \rst - Reserves space to store at least *new_cap* arguments including - *new_cap_named* named arguments. - \endrst - */ - void reserve(size_t new_cap, size_t new_cap_named) { - FMT_ASSERT(new_cap >= new_cap_named, - "Set of arguments includes set of named arguments"); - data_.reserve(new_cap); - named_info_.reserve(new_cap_named); - } -}; - /** \rst A view of a collection of formatting arguments. To avoid lifetime issues it @@ -1646,25 +1769,27 @@ template class basic_format_args { const format_arg* args_; }; - bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; } - bool has_named_args() const { + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + auto has_named_args() const -> bool { return (desc_ & detail::has_named_args_bit) != 0; } - detail::type type(int index) const { + FMT_CONSTEXPR auto type(int index) const -> detail::type { int shift = index * detail::packed_arg_bits; unsigned int mask = (1 << detail::packed_arg_bits) - 1; return static_cast((desc_ >> shift) & mask); } - basic_format_args(unsigned long long desc, - const detail::value* values) + constexpr FMT_INLINE basic_format_args(unsigned long long desc, + const detail::value* values) : desc_(desc), values_(values) {} - basic_format_args(unsigned long long desc, const format_arg* args) + constexpr basic_format_args(unsigned long long desc, const format_arg* args) : desc_(desc), args_(args) {} public: - basic_format_args() : desc_(0) {} + constexpr basic_format_args() : desc_(0), args_(nullptr) {} /** \rst @@ -1672,8 +1797,10 @@ template class basic_format_args { \endrst */ template - FMT_INLINE basic_format_args(const format_arg_store& store) - : basic_format_args(store.desc, store.data_.args()) {} + constexpr FMT_INLINE basic_format_args( + const format_arg_store& store) + : basic_format_args(format_arg_store::desc, + store.data_.args()) {} /** \rst @@ -1681,7 +1808,8 @@ template class basic_format_args { `~fmt::dynamic_format_arg_store`. \endrst */ - FMT_INLINE basic_format_args(const dynamic_format_arg_store& store) + constexpr FMT_INLINE basic_format_args( + const dynamic_format_arg_store& store) : basic_format_args(store.get_types(), store.data()) {} /** @@ -1689,12 +1817,12 @@ template class basic_format_args { Constructs a `basic_format_args` object from a dynamic set of arguments. \endrst */ - basic_format_args(const format_arg* args, int count) + constexpr basic_format_args(const format_arg* args, int count) : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), args) {} /** Returns the argument with the specified id. */ - format_arg get(int id) const { + FMT_CONSTEXPR auto get(int id) const -> format_arg { format_arg arg; if (!is_packed()) { if (id < max_size()) arg = args_[id]; @@ -1707,12 +1835,14 @@ template class basic_format_args { return arg; } - template format_arg get(basic_string_view name) const { + template + auto get(basic_string_view name) const -> format_arg { int id = get_id(name); return id >= 0 ? get(id) : format_arg(); } - template int get_id(basic_string_view name) const { + template + auto get_id(basic_string_view name) const -> int { if (!has_named_args()) return -1; const auto& named_args = (is_packed() ? values_[-1] : args_[-1].value_).named_args; @@ -1722,105 +1852,1032 @@ template class basic_format_args { return -1; } - int max_size() const { + auto max_size() const -> int { unsigned long long max_packed = detail::max_packed_args; return static_cast(is_packed() ? max_packed : desc_ & ~detail::is_unpacked_bit); } }; -/** An alias to ``basic_format_args``. */ -// It is a separate type rather than an alias to make symbols readable. -struct format_args : basic_format_args { - template - FMT_INLINE format_args(const Args&... args) : basic_format_args(args...) {} +/** An alias to ``basic_format_args``. */ +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +using format_args = basic_format_args; + +// We cannot use enum classes as bit fields because of a gcc bug +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. +namespace align { +enum type { none, left, right, center, numeric }; +} +using align_t = align::type; +namespace sign { +enum type { none, minus, plus, space }; +} +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: + enum { max_size = 4 }; + Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; + unsigned char size_ = 1; + + public: + FMT_CONSTEXPR void operator=(basic_string_view s) { + auto size = s.size(); + if (size > max_size) return throw_format_error("invalid fill"); + for (size_t i = 0; i < size; ++i) data_[i] = s[i]; + size_ = static_cast(size); + } + + constexpr auto size() const -> size_t { return size_; } + constexpr auto data() const -> const Char* { return data_; } + + FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } + FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { + return data_[index]; + } }; -struct wformat_args : basic_format_args { - using basic_format_args::basic_format_args; +FMT_END_DETAIL_NAMESPACE + +// Format specifiers for built-in and string types. +template struct basic_format_specs { + int width; + int precision; + char type; + align_t align : 4; + sign_t sign : 3; + bool alt : 1; // Alternate form ('#'). + bool localized : 1; + detail::fill_t fill; + + constexpr basic_format_specs() + : width(0), + precision(-1), + type(0), + align(align::none), + sign(sign::none), + alt(false), + localized(false) {} }; -namespace detail { +using format_specs = basic_format_specs; -// Reports a compile-time error if S is not a valid format string. -template ::value)> -FMT_INLINE void check_format_string(const S&) { -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert(is_compile_string::value, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " - "FMT_STRING."); -#endif -} -template ::value)> -void check_format_string(S); +FMT_BEGIN_DETAIL_NAMESPACE -template > -inline format_arg_store, remove_reference_t...> -make_args_checked(const S& format_str, - const remove_reference_t&... args) { - static_assert(count<(std::is_base_of>::value && - std::is_reference::value)...>() == 0, - "passing views as lvalues is disallowed"); - check_format_string(format_str); - return {args...}; +enum class arg_id_kind { none, index, name }; + +// An argument reference. +template struct arg_ref { + FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} + + FMT_CONSTEXPR explicit arg_ref(int index) + : kind(arg_id_kind::index), val(index) {} + FMT_CONSTEXPR explicit arg_ref(basic_string_view name) + : kind(arg_id_kind::name), val(name) {} + + FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { + kind = arg_id_kind::index; + val.index = idx; + return *this; + } + + arg_id_kind kind; + union value { + FMT_CONSTEXPR value(int id = 0) : index{id} {} + FMT_CONSTEXPR value(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; + } val; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template +struct dynamic_format_specs : basic_format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +struct auto_id {}; + +// A format specifier handler that sets fields in basic_format_specs. +template class specs_setter { + protected: + basic_format_specs& specs_; + + public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) + : specs_(specs) {} + + FMT_CONSTEXPR specs_setter(const specs_setter& other) + : specs_(other.specs_) {} + + FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } + FMT_CONSTEXPR void on_fill(basic_string_view fill) { + specs_.fill = fill; + } + FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } + FMT_CONSTEXPR void on_hash() { specs_.alt = true; } + FMT_CONSTEXPR void on_localized() { specs_.localized = true; } + + FMT_CONSTEXPR void on_zero() { + if (specs_.align == align::none) specs_.align = align::numeric; + specs_.fill[0] = Char('0'); + } + + FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } + FMT_CONSTEXPR void on_precision(int precision) { + specs_.precision = precision; + } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(Char type) { + specs_.type = static_cast(type); + } +}; + +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template +class dynamic_specs_handler + : public specs_setter { + public: + using char_type = typename ParseContext::char_type; + + FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, + ParseContext& ctx) + : specs_setter(specs), specs_(specs), context_(ctx) {} + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) + : specs_setter(other), + specs_(other.specs_), + context_(other.context_) {} + + template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + specs_.width_ref = make_arg_ref(arg_id); + } + + template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + specs_.precision_ref = make_arg_ref(arg_id); + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } + + private: + dynamic_format_specs& specs_; + ParseContext& context_; + + using arg_ref_type = arg_ref; + + FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { + context_.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { + return arg_ref_type(context_.next_arg_id()); + } + + FMT_CONSTEXPR auto make_arg_ref(basic_string_view arg_id) + -> arg_ref_type { + context_.check_arg_id(arg_id); + basic_string_view format_str( + context_.begin(), to_unsigned(context_.end() - context_.begin())); + return arg_ref_type(arg_id); + } +}; + +template constexpr bool is_ascii_letter(Char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } -template ::value)> -std::basic_string vformat( - basic_string_view format_str, - basic_format_args>> args); - -FMT_API std::string vformat(string_view format_str, format_args args); +// Converts a character to ASCII. Returns a number > 127 on conversion failure. +template ::value)> +constexpr auto to_ascii(Char value) -> Char { + return value; +} +template ::value)> +constexpr auto to_ascii(Char value) -> + typename std::underlying_type::type { + return value; +} template -typename FMT_BUFFER_CONTEXT(Char)::iterator vformat_to( - buffer& buf, basic_string_view format_str, - basic_format_args)> args); +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}; + int len = lengths[static_cast(*begin) >> 3]; -template ::value)> -inline void vprint_mojibake(std::FILE*, basic_string_view, const Args&) {} + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + return len + !len; +} + +// Return the result via the out param to workaround gcc bug 77539. +template +FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline auto find(const char* first, const char* last, char value, + const char*& out) -> bool { + out = static_cast( + std::memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + if (num_digits <= std::numeric_limits::digits10) + return static_cast(value); + // Check for overflow. + const unsigned max = to_unsigned((std::numeric_limits::max)()); + return num_digits == std::numeric_limits::digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +// Parses fill and alignment. +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (p >= end) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + default: + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '{') + return handler.on_error("invalid fill character '{'"), begin; + handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); + begin = p + 1; + } else + ++begin; + handler.on_align(align); + break; + } else if (p == begin) { + break; + } + p = begin; + } + return begin; +} + +template FMT_CONSTEXPR bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +template +FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = + parse_nonnegative_int(begin, end, (std::numeric_limits::max)()); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + handler.on_error("invalid format string"); + else + handler(index); + return begin; + } + if (!is_name_start(c)) { + handler.on_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); + handler(basic_string_view(begin, to_unsigned(it - begin))); + return it; +} + +template +FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) -> const Char* { + Char c = *begin; + if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); + handler(); + return begin; +} + +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + using detail::auto_id; + struct width_adapter { + Handler& handler; + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + handler.on_dynamic_width(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + FMT_ASSERT(begin != end, ""); + if ('0' <= *begin && *begin <= '9') { + int width = parse_nonnegative_int(begin, end, -1); + if (width != -1) + handler.on_width(width); + else + handler.on_error("number is too big"); + } else if (*begin == '{') { + ++begin; + if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); + if (begin == end || *begin != '}') + return handler.on_error("invalid format string"), begin; + ++begin; + } + return begin; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + using detail::auto_id; + struct precision_adapter { + Handler& handler; + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + handler.on_dynamic_precision(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + ++begin; + auto c = begin != end ? *begin : Char(); + if ('0' <= c && c <= '9') { + auto precision = parse_nonnegative_int(begin, end, -1); + if (precision != -1) + handler.on_precision(precision); + else + handler.on_error("number is too big"); + } else if (c == '{') { + ++begin; + if (begin != end) + begin = parse_arg_id(begin, end, precision_adapter{handler}); + if (begin == end || *begin++ != '}') + return handler.on_error("invalid format string"), begin; + } else { + return handler.on_error("missing precision specifier"), begin; + } + handler.end_precision(); + return begin; +} + +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +template +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) && + *begin != 'L') { + handler.on_type(*begin++); + return begin; + } + + if (begin == end) return begin; + + begin = parse_align(begin, end, handler); + if (begin == end) return begin; + + // Parse sign. + switch (to_ascii(*begin)) { + case '+': + handler.on_sign(sign::plus); + ++begin; + break; + case '-': + handler.on_sign(sign::minus); + ++begin; + break; + case ' ': + handler.on_sign(sign::space); + ++begin; + break; + default: + break; + } + if (begin == end) return begin; + + if (*begin == '#') { + handler.on_hash(); + if (++begin == end) return begin; + } + + // Parse zero flag. + if (*begin == '0') { + handler.on_zero(); + if (++begin == end) return begin; + } + + begin = parse_width(begin, end, handler); + if (begin == end) return begin; + + // Parse precision. + if (*begin == '.') { + begin = parse_precision(begin, end, handler); + if (begin == end) return begin; + } + + if (*begin == 'L') { + handler.on_localized(); + ++begin; + } + + // Parse type. + if (begin != end && *begin != '}') handler.on_type(*begin++); + return begin; +} + +template +FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + ++begin; + if (begin == end) return handler.on_error("invalid format string"), end; + if (*begin == '}') { + handler.on_replacement_field(handler.on_arg_id(), begin); + } else if (*begin == '{') { + handler.on_text(begin, begin + 1); + } else { + auto adapter = id_adapter{handler, 0}; + begin = parse_arg_id(begin, end, adapter); + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(adapter.arg_id, begin); + } else if (c == ':') { + begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + } else { + return handler.on_error("missing '}' in format string"), end; + } + } + return begin + 1; +} + +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 + using detail::find; + + auto begin = format_str.data(); + auto end = begin + format_str.size(); + if (end - begin < 32) { + // Use a simple loop instead of memchr for small strings. + const Char* p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); + return; + } + struct writer { + FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) { + if (pbegin == pend) return; + for (;;) { + const Char* p = nullptr; + if (!find(pbegin, pend, '}', p)) + return handler_.on_text(pbegin, pend); + ++p; + if (p == pend || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(pbegin, p); + pbegin = p + 1; + } + } + Handler& handler_; + } write{handler}; + while (begin != end) { + // 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)) + return write(begin, end); + write(begin, p); + begin = parse_replacement_field(p, end, handler); + } +} + +template +FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) + -> decltype(ctx.begin()) { + using char_type = typename ParseContext::char_type; + using context = buffer_context; + using mapped_type = conditional_t< + mapped_type_constant::value != type::custom_type, + decltype(arg_mapper().map(std::declval())), T>; + auto f = conditional_t::value, + formatter, + fallback_formatter>(); + return f.parse(ctx); +} + +// A parse context with extra argument id checks. It is only used at compile +// time because adding checks at runtime would introduce substantial overhead +// and would be redundant since argument ids are checked when arguments are +// retrieved anyway. +template +class compile_parse_context + : public basic_format_parse_context { + private: + int num_args_; + using base = basic_format_parse_context; + + public: + explicit FMT_CONSTEXPR compile_parse_context( + basic_string_view format_str, + int num_args = (std::numeric_limits::max)(), ErrorHandler eh = {}) + : base(format_str, eh), num_args_(num_args) {} + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) this->on_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) this->on_error("argument not found"); + } + using base::check_arg_id; +}; + +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: + 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') { + check_int_type_spec(specs.type, eh); + return false; + } + if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) + eh.on_error("invalid format specifier for char"); + return true; +} + +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed, // Fixed point with the default precision of 6, e.g. 0.0012. + hex +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool upper : 1; + bool locale : 1; + bool binary32 : 1; + bool use_grisu : 1; + bool showpoint : 1; +}; + +template +FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, + ErrorHandler&& eh = {}) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + case 0: + result.format = float_format::general; + break; + case 'G': + result.upper = true; + FMT_FALLTHROUGH; + case 'g': + result.format = float_format::general; + break; + case 'E': + result.upper = true; + FMT_FALLTHROUGH; + case 'e': + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case 'F': + result.upper = true; + FMT_FALLTHROUGH; + case 'f': + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case 'A': + result.upper = true; + FMT_FALLTHROUGH; + case 'a': + result.format = float_format::hex; + break; + default: + eh.on_error("invalid type specifier"); + break; + } + 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"); + 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_pointer_type_spec(Char spec, ErrorHandler&& eh) { + if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier"); +} + +// A parse_format_specs handler that checks if specifiers are consistent with +// the argument type. +template class specs_checker : public Handler { + private: + detail::type arg_type_; + + FMT_CONSTEXPR void require_numeric_argument() { + if (!is_arithmetic_type(arg_type_)) + this->on_error("format specifier requires numeric argument"); + } + + public: + FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + FMT_CONSTEXPR void on_align(align_t align) { + if (align == align::numeric) require_numeric_argument(); + Handler::on_align(align); + } + + FMT_CONSTEXPR void on_sign(sign_t s) { + require_numeric_argument(); + if (is_integral_type(arg_type_) && arg_type_ != type::int_type && + arg_type_ != type::long_long_type && arg_type_ != type::char_type) { + this->on_error("format specifier requires signed argument"); + } + Handler::on_sign(s); + } + + FMT_CONSTEXPR void on_hash() { + require_numeric_argument(); + Handler::on_hash(); + } + + FMT_CONSTEXPR void on_localized() { + require_numeric_argument(); + Handler::on_localized(); + } + + FMT_CONSTEXPR void on_zero() { + require_numeric_argument(); + Handler::on_zero(); + } + + FMT_CONSTEXPR void end_precision() { + if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) + this->on_error("precision not allowed for this argument type"); + } +}; + +constexpr int invalid_arg_index = -1; + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (detail::is_statically_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return invalid_arg_index; +} +#endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +#endif + (void)name; + return invalid_arg_index; +} + +template +class format_string_checker { + private: + using parse_context_type = compile_parse_context; + enum { num_args = sizeof...(Args) }; + + // Format specifier parsing function. + using parse_func = const Char* (*)(parse_context_type&); + + parse_context_type context_; + parse_func parse_funcs_[num_args > 0 ? num_args : 1]; + + public: + explicit FMT_CONSTEXPR format_string_checker( + basic_string_view format_str, ErrorHandler eh) + : context_(format_str, num_args, eh), + parse_funcs_{&parse_format_specs...} {} + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + return context_.check_arg_id(id), id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + auto index = get_arg_index_by_name(id); + if (index == invalid_arg_index) on_error("named argument is not found"); + return context_.check_arg_id(index), index; +#else + (void)id; + on_error("compile-time checks for named arguments require C++20 support"); + return 0; +#endif + } + + FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) + -> const Char* { + context_.advance_to(context_.begin() + (begin - &*context_.begin())); + // id >= 0 check is a workaround for gcc 10 bug (#2065). + return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } +}; + +template ::value), int>> +void check_format_string(S format_str) { + FMT_CONSTEXPR auto s = to_string_view(format_str); + using checker = format_string_checker...>; + FMT_CONSTEXPR bool invalid_format = + (parse_format_string(s, checker(s, {})), true); + (void)invalid_format; +} + +template +void vformat_to( + buffer& buf, basic_string_view fmt, + basic_format_args)> args, + detail::locale_ref loc = {}); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif -} // namespace detail +FMT_END_DETAIL_NAMESPACE -/** Formats a string and writes the output to ``out``. */ -// GCC 8 and earlier cannot handle std::back_insert_iterator with -// vformat_to(...) overload, so SFINAE on iterator type instead. -template < - typename OutputIt, typename S, typename Char = char_t, - FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator::value)> -OutputIt vformat_to( - OutputIt out, const S& format_str, - basic_format_args>> args) { - auto& c = detail::get_container(out); - detail::container_buffer> buf(c); - detail::vformat_to(buf, to_string_view(format_str), args); - return out; -} +// A formatter specialization for the core types corresponding to detail::type +// constants. +template +struct formatter::value != + detail::type::custom_type>> { + private: + detail::dynamic_format_specs specs_; -template ::value&& detail::is_string::value)> -inline std::back_insert_iterator format_to( - std::back_insert_iterator out, const S& format_str, - Args&&... args) { - return vformat_to(out, to_string_view(format_str), - detail::make_args_checked(format_str, args...)); -} + public: + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin == end) return begin; + using handler_type = detail::dynamic_specs_handler; + auto type = detail::type_constant::value; + auto checker = + detail::specs_checker(handler_type(specs_, ctx), type); + auto it = detail::parse_format_specs(begin, end, checker); + auto eh = ctx.error_handler(); + switch (type) { + case detail::type::none_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case detail::type::bool_type: + if (!specs_.type || specs_.type == 's') break; + FMT_FALLTHROUGH; + 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::int128_type: + case detail::type::uint128_type: + detail::check_int_type_spec(specs_.type, eh); + break; + case detail::type::char_type: + detail::check_char_specs(specs_, eh); + break; + case detail::type::float_type: + if (detail::const_check(FMT_USE_FLOAT)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "float support disabled"); + break; + case detail::type::double_type: + if (detail::const_check(FMT_USE_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "double support disabled"); + break; + case detail::type::long_double_type: + if (detail::const_check(FMT_USE_LONG_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "long double support disabled"); + break; + case detail::type::cstring_type: + detail::check_cstring_type_spec(specs_.type, eh); + break; + case detail::type::string_type: + detail::check_string_type_spec(specs_.type, eh); + break; + case detail::type::pointer_type: + detail::check_pointer_type_spec(specs_.type, eh); + break; + case detail::type::custom_type: + // Custom format specifiers are checked in parse functions of + // formatter specializations. + break; + } + return it; + } -template > -FMT_INLINE std::basic_string vformat( - const S& format_str, - basic_format_args>> args) { - return detail::vformat(to_string_view(format_str), args); + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; + +template struct basic_runtime { basic_string_view str; }; + +template class basic_format_string { + private: + basic_string_view str_; + + public: + template >::value)> + FMT_CONSTEVAL 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) { + using checker = detail::format_string_checker...>; + detail::parse_format_string(str_, checker(s, {})); + } +#else + detail::check_format_string(s); +#endif + } + basic_format_string(basic_runtime r) : str_(r.str) {} + + FMT_INLINE operator basic_string_view() const { return str_; } +}; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +// Workaround broken conversion on older gcc. +template using format_string = string_view; +template auto runtime(const S& s) -> basic_string_view> { + return s; } +#else +template +using format_string = basic_format_string...>; +// Creates a runtime format string. +template auto runtime(const S& s) -> basic_runtime> { + return {{s}}; +} +#endif + +FMT_API auto vformat(string_view fmt, format_args args) -> std::string; /** \rst - Formats arguments and returns the result as a string. + Formats ``args`` according to specifications in ``fmt`` and returns the result + as a string. **Example**:: @@ -1828,55 +2885,122 @@ FMT_INLINE std::basic_string vformat( std::string message = fmt::format("The answer is {}", 42); \endrst */ -// Pass char_t as a default template parameter instead of using -// std::basic_string> to reduce the symbol size. -template > -FMT_INLINE std::basic_string format(const S& format_str, Args&&... args) { - const auto& vargs = detail::make_args_checked(format_str, args...); - return detail::vformat(to_string_view(format_str), vargs); +template +FMT_INLINE auto format(format_string fmt, T&&... args) -> std::string { + return vformat(fmt, fmt::make_format_args(args...)); } -FMT_API void vprint(string_view, format_args); -FMT_API void vprint(std::FILE*, string_view, format_args); +/** Formats a string and writes the output to ``out``. */ +template ::value)> +auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { + using detail::get_buffer; + auto&& buf = get_buffer(out); + detail::vformat_to(buf, string_view(fmt), args); + return detail::get_iterator(buf); +} + +/** + \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. + + **Example**:: + + auto out = std::vector(); + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template ::value)> +FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) + -> OutputIt { + return vformat_to(out, fmt, fmt::make_format_args(args...)); +} + +template struct format_to_n_result { + /** Iterator past the end of the output range. */ + OutputIt out; + /** Total (not truncated) output size. */ + size_t size; +}; + +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); + return {buf.out(), buf.count()}; +} /** \rst - Formats ``args`` according to specifications in ``format_str`` and writes the - output to the file ``f``. Strings are assumed to be Unicode-encoded unless the - ``FMT_UNICODE`` macro is set to 0. - - **Example**:: - - fmt::print(stderr, "Don't {}!", "panic"); + 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. \endrst */ -template > -inline void print(std::FILE* f, const S& format_str, Args&&... args) { - const auto& vargs = detail::make_args_checked(format_str, args...); - return detail::is_unicode() - ? vprint(f, to_string_view(format_str), vargs) - : detail::vprint_mojibake(f, to_string_view(format_str), vargs); +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + const 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 { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...)); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(std::FILE* f, string_view fmt, format_args args); + /** \rst - Formats ``args`` according to specifications in ``format_str`` and writes - the output to ``stdout``. Strings are assumed to be Unicode-encoded unless - the ``FMT_UNICODE`` macro is set to 0. + Formats ``args`` according to specifications in ``fmt`` and writes the output + to ``stdout``. **Example**:: fmt::print("Elapsed time: {0:.2f} seconds", 1.23); \endrst */ -template > -inline void print(const S& format_str, Args&&... args) { - const auto& vargs = detail::make_args_checked(format_str, args...); - return detail::is_unicode() - ? vprint(to_string_view(format_str), vargs) - : detail::vprint_mojibake(stdout, to_string_view(format_str), - vargs); +template +FMT_INLINE void print(format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::is_utf8() ? vprint(fmt, vargs) + : detail::vprint_mojibake(stdout, fmt, vargs); } + +/** + \rst + Formats ``args`` according to specifications in ``fmt`` and writes the + output to the file ``f``. + + **Example**:: + + fmt::print(stderr, "Don't {}!", "panic"); + \endrst + */ +template +FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::is_utf8() ? vprint(f, fmt, vargs) + : detail::vprint_mojibake(f, fmt, vargs); +} + +FMT_MODULE_EXPORT_END +FMT_GCC_PRAGMA("GCC pop_options") FMT_END_NAMESPACE +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif #endif // FMT_CORE_H_ diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index d8c9c8a5..a802aea5 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -8,42 +8,25 @@ #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ -#include +#include #include +#include // errno #include #include #include -#include // for std::memmove +#include // std::memmove #include #include -#include "format.h" -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR # include #endif #ifdef _WIN32 -# if !defined(NOMINMAX) && !defined(WIN32_LEAN_AND_MEAN) -# define NOMINMAX -# define WIN32_LEAN_AND_MEAN -# include -# undef WIN32_LEAN_AND_MEAN -# undef NOMINMAX -# else -# include -# endif -# include +# include // _isatty #endif -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4702) // unreachable code -#endif - -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; } -inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; } +#include "format.h" FMT_BEGIN_NAMESPACE namespace detail { @@ -70,82 +53,12 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { # define FMT_SNPRINTF fmt_snprintf #endif // _MSC_VER -// A portable thread-safe version of strerror. -// Sets buffer to point to a string describing the error code. -// This can be either a pointer to a string stored in buffer, -// or a pointer to some static immutable string. -// Returns one of the following values: -// 0 - success -// ERANGE - buffer is not large enough to store the error message -// other - failure -// Buffer should be at least of size 1. -FMT_FUNC int safe_strerror(int error_code, char*& buffer, - size_t buffer_size) FMT_NOEXCEPT { - FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer"); - - class dispatcher { - private: - int error_code_; - char*& buffer_; - size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const dispatcher&) {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - FMT_MAYBE_UNUSED - int handle(char* message) { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - FMT_MAYBE_UNUSED - int handle(detail::null<>) { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - FMT_MAYBE_UNUSED - int fallback(int result) { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE - : result; - } - -#if !FMT_MSC_VER - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(detail::null<>) { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } -#endif - - public: - dispatcher(int err_code, char*& buf, size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} - - int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } - }; - return dispatcher(error_code, buffer, buffer_size).run(); -} - FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { // Report error code making sure that the output fits into // inline_buffer_size to avoid dynamic memory allocation and potential // bad_alloc. - out.resize(0); + out.try_resize(0); static const char SEP[] = ": "; static const char ERROR_STR[] = "error "; // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. @@ -156,33 +69,30 @@ FMT_FUNC void format_error_code(detail::buffer& out, int error_code, ++error_code_size; } error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); - auto it = std::back_inserter(out); + auto it = buffer_appender(out); if (message.size() <= inline_buffer_size - error_code_size) - format_to(it, "{}{}", message, SEP); - format_to(it, "{}{}", ERROR_STR, error_code); - assert(out.size() <= inline_buffer_size); + format_to(it, FMT_STRING("{}{}"), message, SEP); + format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + FMT_ASSERT(out.size() <= inline_buffer_size, ""); } FMT_FUNC void report_error(format_func func, int error_code, - string_view message) FMT_NOEXCEPT { + const char* message) FMT_NOEXCEPT { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_fully because the latter may throw. - (void)std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); + if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) + std::fputc('\n', stderr); } // A wrapper around fwrite that throws on error. -FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count, - FILE* stream) { +inline void fwrite_fully(const void* ptr, size_t size, size_t count, + FILE* stream) { size_t written = std::fwrite(ptr, size, count, stream); if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); } -} // namespace detail - -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -namespace detail { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR template locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same::value, ""); @@ -193,41 +103,36 @@ template Locale locale_ref::get() const { return locale_ ? *static_cast(locale_) : std::locale(); } -template FMT_FUNC std::string grouping_impl(locale_ref loc) { - return std::use_facet>(loc.get()).grouping(); -} -template FMT_FUNC Char thousands_sep_impl(locale_ref loc) { - return std::use_facet>(loc.get()) - .thousands_sep(); +template +FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { + auto& facet = std::use_facet>(loc.get()); + auto grouping = facet.grouping(); + auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); + return {std::move(grouping), thousands_sep}; } template FMT_FUNC Char decimal_point_impl(locale_ref loc) { return std::use_facet>(loc.get()) .decimal_point(); } -} // namespace detail #else template -FMT_FUNC std::string detail::grouping_impl(locale_ref) { - return "\03"; +FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result { + return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR}; } -template FMT_FUNC Char detail::thousands_sep_impl(locale_ref) { - return FMT_STATIC_THOUSANDS_SEPARATOR; -} -template FMT_FUNC Char detail::decimal_point_impl(locale_ref) { +template FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif +} // namespace detail +#if !FMT_MSC_VER FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default; -FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT = default; +#endif -FMT_FUNC void system_error::init(int err_code, string_view format_str, - format_args args) { - error_code_ = err_code; - memory_buffer buffer; - format_system_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error& base = *this; - base = std::runtime_error(to_string(buffer)); +FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, + format_args args) { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, vformat(format_str, args)); } namespace detail { @@ -240,110 +145,15 @@ 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 -const typename basic_data::digit_pair basic_data::digits[] = { - {'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'}}; - -template -const char basic_data::hex_digits[] = "0123456789abcdef"; - -#define FMT_POWERS_OF_10(factor) \ - factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ - (factor)*1000000, (factor)*10000000, (factor)*100000000, \ - (factor)*1000000000 - -template -const uint64_t basic_data::powers_of_10_64[] = { - 1, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; - -template -const uint32_t basic_data::zero_or_powers_of_10_32[] = {0, - FMT_POWERS_OF_10(1)}; - -template -const uint64_t basic_data::zero_or_powers_of_10_64[] = { - 0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; - -// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. -// These are generated by support/compute-powers.py. -template -const uint64_t basic_data::pow10_significands[] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, -}; - -// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding -// to significands above. -template -const int16_t basic_data::pow10_exponents[] = { - -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, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 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}; - -template -const char basic_data::foreground_color[] = "\x1b[38;2;"; -template -const char basic_data::background_color[] = "\x1b[48;2;"; -template const char basic_data::reset_color[] = "\x1b[0m"; -template const wchar_t basic_data::wreset_color[] = L"\x1b[0m"; -template const char basic_data::signs[] = {0, '-', '+', ' '}; -template -const char basic_data::left_padding_shifts[] = {31, 31, 0, 1, 0}; -template -const char basic_data::right_padding_shifts[] = {0, 31, 0, 1, 0}; +constexpr const char basic_data::right_padding_shifts[]; +#endif template struct bits { static FMT_CONSTEXPR_DECL const int value = @@ -366,6 +176,10 @@ class fp { private: using significand_type = uint64_t; + template + using is_supported_float = bool_constant; + public: significand_type f; int e; @@ -388,63 +202,38 @@ class fp { template explicit fp(Double d) { assign(d); } // Assigns d to this and return true iff predecessor is closer than successor. - template - bool assign(Double d) { - // Assume double is in the format [sign][exponent][significand]. - using limits = std::numeric_limits; + 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 - double_significand_size - 1; // -1 for sign - const uint64_t significand_mask = implicit_bit - 1; + 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; - auto u = bit_cast(d); + 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) >> double_significand_size); + 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 += implicit_bit; + f += float_implicit_bit; else biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = biased_e - exponent_bias - double_significand_size; + e = biased_e - exponent_bias - float_significand_size; return is_predecessor_closer; } - template - bool assign(Double) { + template ::value)> + bool assign(Float) { *this = fp(); return false; } - - // Assigns d to this together with computing lower and upper boundaries, - // where a boundary is a value half way between the number and its predecessor - // (lower) or successor (upper). The upper boundary is normalized and lower - // has the same exponent but may be not normalized. - template boundaries assign_with_boundaries(Double d) { - bool is_lower_closer = assign(d); - fp lower = - is_lower_closer ? fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1); - // 1 in normalize accounts for the exponent shift above. - fp upper = normalize<1>(fp((f << 1) + 1, e - 1)); - lower.f <<= lower.e - upper.e; - return boundaries{lower.f, upper.f}; - } - - template boundaries assign_float_with_boundaries(Double d) { - assign(d); - constexpr int min_normal_e = std::numeric_limits::min_exponent - - std::numeric_limits::digits; - significand_type half_ulp = 1 << (std::numeric_limits::digits - - std::numeric_limits::digits - 1); - if (min_normal_e > e) half_ulp <<= min_normal_e - e; - fp upper = normalize<0>(fp(f + half_ulp, e)); - fp lower = fp( - f - (half_ulp >> ((f == implicit_bit && e > min_normal_e) ? 1 : 0)), e); - lower.f <<= lower.e - upper.e; - return boundaries{lower.f, upper.f}; - } }; // Normalizes the value converted from double and multiplied by (1 << SHIFT). @@ -488,11 +277,58 @@ 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) { - const int64_t one_over_log2_10 = 0x4d104d42; // round(pow(2, 32) / log2(10)) + // 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[] = { + 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, + 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, + 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, + 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, + 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, + 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, + 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, + 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, + 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, + 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, + 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, + 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, + 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, + 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, + 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, + 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, + 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, + 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, + 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, + 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, + 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, + 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, + 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, + 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, + 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, + 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, + 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, + 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, + 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, + }; + + // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding + // to significands above. + static constexpr const int16_t pow10_exponents[] = { + -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, + -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, + -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, + 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}; + + const int shift = 32; + const auto significand = static_cast(data::log10_2_significand); int index = static_cast( - ((min_exponent + fp::significand_size - 1) * one_over_log2_10 + - ((int64_t(1) << 32) - 1)) // ceil - >> 32 // arithmetic shift + ((min_exponent + fp::significand_size - 1) * (significand >> shift) + + ((int64_t(1) << shift) - 1)) // ceil + >> 32 // arithmetic shift ); // Decimal exponent of the first (smallest) cached power of 10. const int first_dec_exp = -348; @@ -500,7 +336,7 @@ 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 {data::pow10_significands[index], data::pow10_exponents[index]}; + return {pow10_significands[index], pow10_exponents[index]}; } // A simple accumulator to hold the sums of terms in bigint::square if uint128_t @@ -517,7 +353,7 @@ struct accumulator { if (lower < n) ++upper; } void operator>>=(int shift) { - assert(shift == 32); + FMT_ASSERT(shift == 32, ""); (void)shift; lower = (upper << 32) | (lower >> 32); upper >>= 32; @@ -559,9 +395,8 @@ class bigint { FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; int i = other.exp_ - exp_; - for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) { + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) subtract_bigits(i, other.bigits_[j], borrow); - } while (borrow > 0) subtract_bigits(i, 0, borrow); remove_leading_zeros(); } @@ -597,7 +432,7 @@ class bigint { public: bigint() : exp_(0) {} explicit bigint(uint64_t n) { assign(n); } - ~bigint() { assert(bigits_.capacity() <= bigits_capacity); } + ~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); } bigint(const bigint&) = delete; void operator=(const bigint&) = delete; @@ -623,7 +458,7 @@ class bigint { int num_bigits() const { return static_cast(bigits_.size()) + exp_; } FMT_NOINLINE bigint& operator<<=(int shift) { - assert(shift >= 0); + FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; if (shift == 0) return *this; @@ -685,7 +520,7 @@ class bigint { // Assigns pow(10, exp) to this bigint. void assign_pow10(int exp) { - assert(exp >= 0); + FMT_ASSERT(exp >= 0, ""); if (exp == 0) return assign(1); // Find the top bit. int bitmask = 1; @@ -704,9 +539,9 @@ class bigint { } void square() { - basic_memory_buffer n(std::move(bigits_)); int num_bigits = static_cast(bigits_.size()); int num_result_bigits = 2 * num_bigits; + basic_memory_buffer n(std::move(bigits_)); bigits_.resize(to_unsigned(num_result_bigits)); using accumulator_t = conditional_t; auto sum = accumulator_t(); @@ -733,22 +568,26 @@ class bigint { 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) { + int exp_difference = exp_ - other.exp_; + if (exp_difference <= 0) return; + int num_bigits = static_cast(bigits_.size()); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); + for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) + bigits_[j] = bigits_[i]; + std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); + exp_ -= exp_difference; + } + // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. int divmod_assign(const bigint& divisor) { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; - int num_bigits = static_cast(bigits_.size()); FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); - int exp_difference = exp_ - divisor.exp_; - if (exp_difference > 0) { - // Align bigints by adding trailing zeros to simplify subtraction. - bigits_.resize(to_unsigned(num_bigits + exp_difference)); - for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) - bigits_[j] = bigits_[i]; - std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); - exp_ -= exp_difference; - } + align(divisor); int quotient = 0; do { subtract_aligned(divisor); @@ -788,26 +627,19 @@ enum result { }; } -// A version of count_digits optimized for grisu_gen_digits. -inline int grisu_count_digits(uint32_t n) { - if (n < 10) return 1; - if (n < 100) return 2; - if (n < 1000) return 3; - if (n < 10000) return 4; - if (n < 100000) return 5; - if (n < 1000000) return 6; - if (n < 10000000) return 7; - if (n < 100000000) return 8; - if (n < 1000000000) return 9; - return 10; +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]; } // 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_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, - int& exp, Handler& handler) { +FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, + 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 @@ -817,9 +649,9 @@ FMT_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, FMT_ASSERT(integral == value.f >> -one.e, ""); // The fractional part of scaled value (p2 in Grisu) c = value % one. uint64_t fractional = value.f & (one.f - 1); - exp = grisu_count_digits(integral); // kappa in Grisu. + exp = count_digits(integral); // kappa in Grisu. // Divide by 10 to prevent overflow. - auto result = handler.on_start(data::powers_of_10_64[exp - 1] << -one.e, + 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; // Generate digits for the integral part. This can produce up to 10 digits. @@ -867,19 +699,17 @@ FMT_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, FMT_ASSERT(false, "invalid number of digits"); } --exp; - uint64_t remainder = - (static_cast(integral) << -one.e) + fractional; + auto remainder = (static_cast(integral) << -one.e) + fractional; result = handler.on_digit(static_cast('0' + digit), - data::powers_of_10_64[exp] << -one.e, remainder, - error, exp, true); + power_of_10_64(exp) << -one.e, remainder, error, + exp, true); if (result != digits::more) return result; } while (exp > 0); // Generate digits for the fractional part. for (;;) { fractional *= 10; error *= 10; - char digit = - static_cast('0' + static_cast(fractional >> -one.e)); + char digit = static_cast('0' + (fractional >> -one.e)); fractional &= one.f - 1; --exp; result = handler.on_digit(digit, one.f, fractional, error, exp, false); @@ -916,6 +746,7 @@ struct fixed_handler { 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. @@ -935,59 +766,1457 @@ struct fixed_handler { } if (buf[0] > '9') { buf[0] = '1'; - buf[size++] = '0'; + if (fixed) + buf[size++] = '0'; + else + ++exp10; } return digits::done; } }; -// The shortest representation digit handler. -struct grisu_shortest_handler { - char* buf; - int size; - // Distance between scaled value and upper bound (wp_W in Grisu3). - uint64_t diff; +// A 128-bit integer type used internally, +struct uint128_wrapper { + uint128_wrapper() = default; - digits::result on_start(uint64_t, uint64_t, uint64_t, int&) { - return digits::more; +#if FMT_USE_INT128 + uint128_t internal_; + + constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT + : internal_{static_cast(low) | + (static_cast(high) << 64)} {} + + constexpr uint128_wrapper(uint128_t u) : internal_{u} {} + + constexpr uint64_t high() const FMT_NOEXCEPT { + return uint64_t(internal_ >> 64); + } + constexpr uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); } + + uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { + internal_ += n; + return *this; + } +#else + uint64_t high_; + uint64_t low_; + + constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT + : high_{high}, + low_{low} {} + + constexpr uint64_t high() const FMT_NOEXCEPT { return high_; } + constexpr uint64_t low() const FMT_NOEXCEPT { return low_; } + + uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { +# if defined(_MSC_VER) && defined(_M_X64) + unsigned char carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); + return *this; +# else + uint64_t sum = low_ + n; + high_ += (sum < low_ ? 1 : 0); + low_ = sum; + return *this; +# endif + } +#endif +}; + +// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. +namespace dragonbox { +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline uint128_wrapper umul128(uint64_t x, uint64_t y) FMT_NOEXCEPT { +#if FMT_USE_INT128 + return static_cast(x) * static_cast(y); +#elif defined(_MSC_VER) && defined(_M_X64) + uint128_wrapper result; + result.low_ = _umul128(x, y, &result.high_); + return result; +#else + const uint64_t mask = (uint64_t(1) << 32) - uint64_t(1); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline uint64_t umul128_upper64(uint64_t x, uint64_t y) FMT_NOEXCEPT { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 64 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { + uint128_wrapper g0 = umul128(x, y.high()); + g0 += umul128_upper64(x, y.low()); + return g0.high(); +} + +// Computes upper 32 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline uint32_t umul96_upper32(uint32_t x, uint64_t y) FMT_NOEXCEPT { + return static_cast(umul128_upper64(x, y)); +} + +// Computes middle 64 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { + uint64_t g01 = x * y.high(); + uint64_t g10 = umul128_upper64(x, y.low()); + return g01 + g10; +} + +// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT { + return x * y; +} + +// Computes floor(log10(pow(2, e))) for e in [-1700, 1700] using the method from +// https://fmt.dev/papers/Grisu-Exact.pdf#page=5, section 3.4. +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; +} + +// Various fast log computations. +inline int floor_log2_pow10(int e) FMT_NOEXCEPT { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + const uint64_t log2_10_integer_part = 3; + const uint64_t log2_10_fractional_digits = 0x5269e12f346e2bf9; + const int shift_amount = 19; + return (e * static_cast( + (log2_10_integer_part << shift_amount) | + (log2_10_fractional_digits >> (64 - shift_amount)))) >> + shift_amount; +} +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)) - + static_cast(log10_4_over_3_fractional_digits >> + (64 - shift_amount))) >> + shift_amount; +} + +// Returns true iff x is divisible by pow(2, exp). +inline bool divisible_by_power_of_2(uint32_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp >= 1, ""); + FMT_ASSERT(x != 0, ""); +#ifdef FMT_BUILTIN_CTZ + return FMT_BUILTIN_CTZ(x) >= exp; +#else + return exp < num_bits() && x == ((x >> exp) << exp); +#endif +} +inline bool divisible_by_power_of_2(uint64_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp >= 1, ""); + FMT_ASSERT(x != 0, ""); +#ifdef FMT_BUILTIN_CTZLL + return FMT_BUILTIN_CTZLL(x) >= exp; +#else + return exp < num_bits() && x == ((x >> exp) << exp); +#endif +} + +// Table entry type for divisibility test. +template struct divtest_table_entry { + T mod_inv; + T max_quotient; +}; + +// Returns true iff x is divisible by pow(5, exp). +inline bool divisible_by_power_of_5(uint32_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp <= 10, "too large exponent"); + static constexpr const divtest_table_entry divtest_table[] = { + {0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333}, + {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba}, + {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5}, + {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf}, + {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897}, + {0x3ed61f49, 0x000001b7}}; + return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; +} +inline bool divisible_by_power_of_5(uint64_t x, int exp) FMT_NOEXCEPT { + FMT_ASSERT(exp <= 23, "too large exponent"); + static constexpr const divtest_table_entry divtest_table[] = { + {0x0000000000000001, 0xffffffffffffffff}, + {0xcccccccccccccccd, 0x3333333333333333}, + {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70}, + {0x1cac083126e978d5, 0x020c49ba5e353f7c}, + {0xd288ce703afb7e91, 0x0068db8bac710cb2}, + {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0}, + {0x790fb65668c26139, 0x000431bde82d7b63}, + {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a}, + {0xc767074b22e90e21, 0x00002af31dc46118}, + {0x8e47ce423a2e9c6d, 0x0000089705f4136b}, + {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b}, + {0x0fee64690c913975, 0x00000057f5ff85e5}, + {0x3662e0e1cf503eb1, 0x000000119799812d}, + {0xa47a2cf9f6433fbd, 0x0000000384b84d09}, + {0x54186f653140a659, 0x00000000b424dc35}, + {0x7738164770402145, 0x0000000024075f3d}, + {0xe4a4d1417cd9a041, 0x000000000734aca5}, + {0xc75429d9e5c5200d, 0x000000000170ef54}, + {0xc1773b91fac10669, 0x000000000049c977}, + {0x26b172506559ce15, 0x00000000000ec1e4}, + {0xd489e3a9addec2d1, 0x000000000002f394}, + {0x90e860bb892c8d5d, 0x000000000000971d}, + {0x502e79bf1b6f4f79, 0x0000000000001e39}, + {0xdcd618596be30fe5, 0x000000000000060b}}; + return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; +} + +// Replaces n by floor(n / pow(5, N)) returning true if and only if n is +// divisible by pow(5, N). +// Precondition: n <= 2 * pow(5, N + 1). +template +bool check_divisibility_and_divide_by_pow5(uint32_t& n) FMT_NOEXCEPT { + static constexpr struct { + uint32_t magic_number; + int bits_for_comparison; + uint32_t threshold; + int shift_amount; + } infos[] = {{0xcccd, 16, 0x3333, 18}, {0xa429, 8, 0x0a, 20}}; + constexpr auto info = infos[N - 1]; + n *= info.magic_number; + const uint32_t comparison_mask = (1u << info.bits_for_comparison) - 1; + bool result = (n & comparison_mask) <= info.threshold; + n >>= info.shift_amount; + return result; +} + +// Computes floor(n / pow(10, N)) for small n and N. +// Precondition: n <= pow(10, N + 1). +template uint32_t small_division_by_pow10(uint32_t n) FMT_NOEXCEPT { + static constexpr struct { + uint32_t magic_number; + int shift_amount; + uint32_t divisor_times_10; + } infos[] = {{0xcccd, 19, 100}, {0xa3d8, 22, 1000}}; + constexpr auto info = infos[N - 1]; + FMT_ASSERT(n <= info.divisor_times_10, "n is too large"); + return n * info.magic_number >> info.shift_amount; +} + +// Computes floor(n / 10^(kappa + 1)) (float) +inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) FMT_NOEXCEPT { + return n / float_info::big_divisor; +} +// Computes floor(n / 10^(kappa + 1)) (double) +inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) FMT_NOEXCEPT { + return umul128_upper64(n, 0x83126e978d4fdf3c) >> 9; +} + +// Various subroutines using pow10 cache +template struct cache_accessor; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint64_t; + + 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[] = { + 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, + 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, + 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, + 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, + 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, + 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, + 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, + 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, + 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, + 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, + 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, + 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, + 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, + 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, + 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, + 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, + 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, + 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, + 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984, + 0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296, + 0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6, + 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20, + 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd, + 0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719, + 0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e}; + return pow10_significands[k - float_info::min_k]; } - // Decrement the generated number approaching value from above. - void round(uint64_t d, uint64_t divisor, uint64_t& remainder, - uint64_t error) { - while ( - remainder < d && error - remainder >= divisor && - (remainder + divisor < d || d - remainder >= remainder + divisor - d)) { - --buf[size - 1]; - remainder += divisor; - } + static carrier_uint compute_mul(carrier_uint u, + const cache_entry_type& cache) FMT_NOEXCEPT { + return umul96_upper32(u, cache); } - // Implements Grisu's round_weed. - digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, - uint64_t error, int exp, bool integral) { - buf[size++] = digit; - if (remainder >= error) return digits::more; - uint64_t unit = integral ? 1 : data::powers_of_10_64[-exp]; - uint64_t up = (diff - 1) * unit; // wp_Wup - round(up, divisor, remainder, error); - uint64_t down = (diff + 1) * unit; // wp_Wdown - if (remainder < down && error - remainder >= divisor && - (remainder + divisor < down || - down - remainder > remainder + divisor - down)) { - return digits::error; - } - return 2 * unit <= remainder && remainder <= error - 4 * unit - ? digits::done - : digits::error; + static uint32_t compute_delta(const cache_entry_type& cache, + int beta_minus_1) FMT_NOEXCEPT { + return static_cast(cache >> (64 - 1 - beta_minus_1)); + } + + static bool compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta_minus_1) FMT_NOEXCEPT { + FMT_ASSERT(beta_minus_1 >= 1, ""); + FMT_ASSERT(beta_minus_1 < 64, ""); + + return ((umul96_lower64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; + } + + static carrier_uint compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return static_cast( + (cache - (cache >> (float_info::significand_bits + 2))) >> + (64 - float_info::significand_bits - 1 - beta_minus_1)); + } + + static carrier_uint compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return static_cast( + (cache + (cache >> (float_info::significand_bits + 1))) >> + (64 - float_info::significand_bits - 1 - beta_minus_1)); + } + + static carrier_uint compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return (static_cast( + cache >> + (64 - float_info::significand_bits - 2 - beta_minus_1)) + + 1) / + 2; } }; +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint128_wrapper; + + static uint128_wrapper get_cached_power(int k) FMT_NOEXCEPT { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + + static constexpr const uint128_wrapper pow10_significands[] = { +#if FMT_USE_FULL_CACHE_DRAGONBOX + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0x9faacf3df73609b1, 0x77b191618c54e9ad}, + {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, + {0x9becce62836ac577, 0x4ee367f9430aec33}, + {0xc2e801fb244576d5, 0x229c41f793cda740}, + {0xf3a20279ed56d48a, 0x6b43527578c11110}, + {0x9845418c345644d6, 0x830a13896b78aaaa}, + {0xbe5691ef416bd60c, 0x23cc986bc656d554}, + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, + {0x91376c36d99995be, 0x23100809b9c21fa2}, + {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, + {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, + {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, + {0xdd95317f31c7fa1d, 0x40405643d711d584}, + {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, + {0xad1c8eab5ee43b66, 0xda3243650005eed0}, + {0xd863b256369d4a40, 0x90bed43e40076a83}, + {0x873e4f75e2224e68, 0x5a7744a6e804a292}, + {0xa90de3535aaae202, 0x711515d0a205cb37}, + {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, + {0x8412d9991ed58091, 0xe858790afe9486c3}, + {0xa5178fff668ae0b6, 0x626e974dbe39a873}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, + {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, + {0xc987434744ac874e, 0xa327ffb266b56221}, + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, + {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, + {0xf6019da07f549b2b, 0x7e2a53a146606a49}, + {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, + {0xc0314325637a1939, 0xfa911155fefb5309}, + {0xf03d93eebc589f88, 0x793555ab7eba27cb}, + {0x96267c7535b763b5, 0x4bc1558b2f3458df}, + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, + {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, + {0x92a1958a7675175f, 0x0bfacd89ec191eca}, + {0xb749faed14125d36, 0xcef980ec671f667c}, + {0xe51c79a85916f484, 0x82b7e12780e7401b}, + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, + {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, + {0xaecc49914078536d, 0x58fae9f773886e19}, + {0xda7f5bf590966848, 0xaf39a475506a899f}, + {0x888f99797a5e012d, 0x6d8406c952429604}, + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, + {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0xd0601d8efc57b08b, 0xf13b94daf124da27}, + {0x823c12795db6ce57, 0x76c53d08d6b70859}, + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, + {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, + {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, + {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, + {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, + {0xc21094364dfb5636, 0x985915fc12f542e5}, + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, + {0xbd8430bd08277231, 0x50c6ff782a838354}, + {0xece53cec4a314ebd, 0xa4f8bf5635246429}, + {0x940f4613ae5ed136, 0x871b7795e136be9a}, + {0xb913179899f68584, 0x28e2557b59846e40}, + {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, + {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, + {0xb4bca50b065abe63, 0x0fed077a756b53aa}, + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, + {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, + {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, + {0x89e42caaf9491b60, 0xf41686c49db57245}, + {0xac5d37d5b79b6239, 0x311c2875c522ced6}, + {0xd77485cb25823ac7, 0x7d633293366b828c}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, + {0xd267caa862a12d66, 0xd072df63c324fd7c}, + {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, + {0xa46116538d0deb78, 0x52d9be85f074e609}, + {0xcd795be870516656, 0x67902e276c921f8c}, + {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, + {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, + {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, + {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, + {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, + {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, + {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, + {0xef340a98172aace4, 0x86fb897116c87c35}, + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, + {0xbae0a846d2195712, 0x8974836059cca10a}, + {0xe998d258869facd7, 0x2bd1a438703fc94c}, + {0x91ff83775423cc06, 0x7b6306a34627ddd0}, + {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, + {0x8e938662882af53e, 0x547eb47b7282ee9d}, + {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, + {0xae0b158b4738705e, 0x9624ab50b148d446}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, + {0xd47487cc8470652b, 0x7647c32000696720}, + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, + {0xa5fb0a17c777cf09, 0xf468107100525891}, + {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, + {0x81ac1fe293d599bf, 0xc6f14cd848405531}, + {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, + {0xfd442e4688bd304a, 0x908f4a166d1da664}, + {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, + {0xf7549530e188c128, 0xd12bee59e68ef47d}, + {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, + {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, + {0xebdf661791d60f56, 0x111b495b3464ad22}, + {0x936b9fcebb25c995, 0xcab10dd900beec35}, + {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, + {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, + {0xb3f4e093db73a093, 0x59ed216765690f57}, + {0xe0f218b8d25088b8, 0x306869c13ec3532d}, + {0x8c974f7383725573, 0x1e414218c73a13fc}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, + {0x894bc396ce5da772, 0x6b8bba8c328eb784}, + {0xab9eb47c81f5114f, 0x066ea92f3f326565}, + {0xd686619ba27255a2, 0xc80a537b0efefebe}, + {0x8613fd0145877585, 0xbd06742ce95f5f37}, + {0xa798fc4196e952e7, 0x2c48113823b73705}, + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, + {0x82ef85133de648c4, 0x9a984d73dbe722fc}, + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, + {0xcc963fee10b7d1b3, 0x318df905079926a9}, + {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, + {0x9c1661a651213e2d, 0x06bea10ca65c084f}, + {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, + {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, + {0xbe89523386091465, 0xf6bbb397f1135824}, + {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, + {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, + {0xba121a4650e4ddeb, 0x92f34d62616ce414}, + {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, + {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, + {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, + {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, + {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, + {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, + {0x87625f056c7c4a8b, 0x11471cd764ad4973}, + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, + {0xd389b47879823479, 0x4aff1d108d4ec2c4}, + {0x843610cb4bf160cb, 0xcedf722a585139bb}, + {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, + {0xce947a3da6a9273e, 0x733d226229feea33}, + {0x811ccc668829b887, 0x0806357d5a3f5260}, + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, + {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, + {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, + {0xc5029163f384a931, 0x0a9e795e65d4df12}, + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, + {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, + {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, + {0x964e858c91ba2655, 0x3a6a07f8d510f870}, + {0xbbe226efb628afea, 0x890489f70a55368c}, + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, + {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, + {0xb32df8e9f3546564, 0x47939822dc96abfa}, + {0xdff9772470297ebd, 0x59787e2b93bc56f8}, + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, + {0xaefae51477a06b03, 0xede622920b6b23f2}, + {0xdab99e59958885c4, 0xe95fab368e45ecee}, + {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, + {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, + {0xd59944a37c0752a2, 0x4be76d3346f04960}, + {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, + {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, + {0x825ecc24c873782f, 0x8ed400668c0c28c9}, + {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, + {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, + {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, + {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, + {0xc24452da229b021b, 0xfbe85badce996169}, + {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, + {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, + {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, + {0xed246723473e3813, 0x290123e9aab23b69}, + {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, + {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, + {0x8d590723948a535f, 0x579c487e5a38ad0f}, + {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, + {0xdcdb1b2798182244, 0xf8e431456cf88e66}, + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, + {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, + {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, + {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, + {0xa87fea27a539e9a5, 0x3f2398d747b36225}, + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, + {0x83a3eeeef9153e89, 0x1953cf68300424ad}, + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, + {0xcdb02555653131b6, 0x3792f412cb06794e}, + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, + {0xc8de047564d20a8b, 0xf245825a5a445276}, + {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, + {0x9ced737bb6c4183d, 0x55464dd69685606c}, + {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, + {0xf53304714d9265df, 0xd53dd99f4b3066a9}, + {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, + {0xbf8fdb78849a5f96, 0xde98520472bdd034}, + {0xef73d256a5c0f77c, 0x963e66858f6d4441}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xbb127c53b17ec159, 0x5560c018580d5d53}, + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, + {0x9226712162ab070d, 0xcab3961304ca70e9}, + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, + {0xb267ed1940f1c61c, 0x55f038b237591ed4}, + {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, + {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, + {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, + {0xd9c7dced53c72255, 0x96e7bd358c904a22}, + {0x881cea14545c7575, 0x7e50d64177da2e55}, + {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, + {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, + {0xcfb11ead453994ba, 0x67de18eda5814af3}, + {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, + {0xa2425ff75e14fc31, 0xa1258379a94d028e}, + {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, + {0x9e74d1b791e07e48, 0x775ea264cf55347e}, + {0xc612062576589dda, 0x95364afe032a819e}, + {0xf79687aed3eec551, 0x3a83ddbd83f52205}, + {0x9abe14cd44753b52, 0xc4926a9672793543}, + {0xc16d9a0095928a27, 0x75b7053c0f178294}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, + {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, + {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, + {0xb877aa3236a4b449, 0x09befeb9fad487c3}, + {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, + {0xb424dc35095cd80f, 0x538484c19ef38c95}, + {0xe12e13424bb40e13, 0x2865a5f206b06fba}, + {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, + {0xafebff0bcb24aafe, 0xf78f69a51539d749}, + {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, + {0x89705f4136b4a597, 0x31680a88f8953031}, + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, + {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, + {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, + {0xd1b71758e219652b, 0xd3c36113404ea4a9}, + {0x83126e978d4fdf3b, 0x645a1cac083126ea}, + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, + {0xcccccccccccccccc, 0xcccccccccccccccd}, + {0x8000000000000000, 0x0000000000000000}, + {0xa000000000000000, 0x0000000000000000}, + {0xc800000000000000, 0x0000000000000000}, + {0xfa00000000000000, 0x0000000000000000}, + {0x9c40000000000000, 0x0000000000000000}, + {0xc350000000000000, 0x0000000000000000}, + {0xf424000000000000, 0x0000000000000000}, + {0x9896800000000000, 0x0000000000000000}, + {0xbebc200000000000, 0x0000000000000000}, + {0xee6b280000000000, 0x0000000000000000}, + {0x9502f90000000000, 0x0000000000000000}, + {0xba43b74000000000, 0x0000000000000000}, + {0xe8d4a51000000000, 0x0000000000000000}, + {0x9184e72a00000000, 0x0000000000000000}, + {0xb5e620f480000000, 0x0000000000000000}, + {0xe35fa931a0000000, 0x0000000000000000}, + {0x8e1bc9bf04000000, 0x0000000000000000}, + {0xb1a2bc2ec5000000, 0x0000000000000000}, + {0xde0b6b3a76400000, 0x0000000000000000}, + {0x8ac7230489e80000, 0x0000000000000000}, + {0xad78ebc5ac620000, 0x0000000000000000}, + {0xd8d726b7177a8000, 0x0000000000000000}, + {0x878678326eac9000, 0x0000000000000000}, + {0xa968163f0a57b400, 0x0000000000000000}, + {0xd3c21bcecceda100, 0x0000000000000000}, + {0x84595161401484a0, 0x0000000000000000}, + {0xa56fa5b99019a5c8, 0x0000000000000000}, + {0xcecb8f27f4200f3a, 0x0000000000000000}, + {0x813f3978f8940984, 0x4000000000000000}, + {0xa18f07d736b90be5, 0x5000000000000000}, + {0xc9f2c9cd04674ede, 0xa400000000000000}, + {0xfc6f7c4045812296, 0x4d00000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xc5371912364ce305, 0x6c28000000000000}, + {0xf684df56c3e01bc6, 0xc732000000000000}, + {0x9a130b963a6c115c, 0x3c7f400000000000}, + {0xc097ce7bc90715b3, 0x4b9f100000000000}, + {0xf0bdc21abb48db20, 0x1e86d40000000000}, + {0x96769950b50d88f4, 0x1314448000000000}, + {0xbc143fa4e250eb31, 0x17d955a000000000}, + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, + {0xb7abc627050305ad, 0xf14a3d9e40000000}, + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, + {0xe0352f62a19e306e, 0xd50b2037ad200000}, + {0x8c213d9da502de45, 0x4526f422cc340000}, + {0xaf298d050e4395d6, 0x9670b12b7f410000}, + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, + {0xd5d238a4abe98068, 0x72a4904598d6d880}, + {0x85a36366eb71f041, 0x47a6da2b7f864750}, + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, + {0x82818f1281ed449f, 0xbff8f10e7a8921a4}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0d}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490}, + {0xfee50b7025c36a08, 0x02f236d04753d5b4}, + {0x9f4f2726179a2245, 0x01d762422c946590}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb2f}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7a}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b44}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b616}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe41}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd1}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcb}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebe}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784337}, + {0xacb92ed9397bf996, 0x49c2c37f07965404}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be906}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30190}, + {0x83c7088e1aab65db, 0x792667c6da79e0fa}, + {0xa4b8cab1a1563f52, 0x577001b891185938}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, + {0x80b05e5ac60b6178, 0x544f8158315b05b4}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c721}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38e9}, + {0xfb5878494ace3a5f, 0x04ab48a04065c723}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8394}, + {0xf5746577930d6500, 0xca8f44ec7ee36479}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a12}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97}, + {0xea1575143cf97226, 0xf52d09d71a3293bd}, + {0x924d692ca61be758, 0x593c2626705f9c56}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956447}, + {0x8edf98b59a373fec, 0x4724bd4189bd5eac}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb657}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed}, + {0x8b865b215899f46c, 0xbd79e0d20082ee74}, + {0xae67f1e9aec07187, 0xecd8590680a3aa11}, + {0xda01ee641a708de9, 0xe80e6f4820cc9495}, + {0x884134fe908658b2, 0x3109058d147fdcdd}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd415}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb0}, + {0xa6539930bf6bff45, 0x84db8346b786151c}, + {0xcfe87f7cef46ff16, 0xe612641865679a63}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e}, + {0xa26da3999aef7749, 0xe3be5e330f38f09d}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa}, + {0xc646d63501a1511d, 0xb281e1fd541501b8}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4226}, + {0x9ae757596946075f, 0x3375788de9b06958}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83ae}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49a}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e0}, + {0xbd176620a501fbff, 0xb650e5a93bc3d898}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe}, + {0x93ba47c980e98cdf, 0xc66f336c36b10137}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4184}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e5}, + {0x9043ea1ac7e41392, 0x87c89837ad68db2f}, + {0xb454e4a179dd1877, 0x29babe4598c311fb}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660c}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f}, + {0xdc21a1171d42645d, 0x76707543f4fa1f73}, + {0x899504ae72497eba, 0x6a06494a791c53a8}, + {0xabfa45da0edbde69, 0x0487db9d17636892}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b6}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, + {0xa7f26836f282b732, 0x8e6cac7768d7141e}, + {0xd1ef0244af2364ff, 0x3207d795430cd926}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b8}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6}, + {0xcd036837130890a1, 0x36dba887c37a8c0f}, + {0x802221226be55a64, 0xc2494954da2c9789}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc7}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9}, + {0x9c69a97284b578d7, 0xff2a760414536efb}, + {0xc38413cf25e2d70d, 0xfef5138519684aba}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a61}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fa}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26183}, + {0xba756174393d88df, 0x94f971119aeef9e4}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85d}, + {0x91abb422ccb812ee, 0xac62e055c10ab33a}, + {0xb616a12b7fe617aa, 0x577b986b314d6009}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b}, + {0x8e41ade9fbebc27d, 0x14588f13be847307}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb}, + {0x8aec23d680043bee, 0x25de7bb9480d5854}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6a}, + {0xd910f7ff28069da4, 0x1b2ba1518094da04}, + {0x87aa9aff79042286, 0x90fb44d2f05d0842}, + {0xa99541bf57452b28, 0x353a1607ac744a53}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce8}, + {0x847c9b5d7c2e09b7, 0x69956135febada11}, + {0xa59bc234db398c25, 0x43fab9837e699095}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f5}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731732}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43e}, + {0x9defbf01b061adab, 0x3a0888136afa64a7}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd0}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d45}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864b}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b325}, + {0xbc4665b596706114, 0x873d5d9f0dde1fee}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f2}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa}, + {0x8fa475791a569d10, 0xf96e017d694487bc}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ac}, + {0xe070f78d3927556a, 0x85bbe253f47b1417}, + {0x8c469ab843b89562, 0x93956d7478ccec8e}, + {0xaf58416654a6babb, 0x387ac8d1970027b2}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e}, + {0x88fcf317f22241e2, 0x441fece3bdf81f03}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c3}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074}, + {0x85c7056562757456, 0xf6872d5667844e49}, + {0xa738c6bebb12d16c, 0xb428f8ac016561db}, + {0xd106f86e69d785c7, 0xe13336d701beba52}, + {0x82a45b450226b39c, 0xecc0024661173473}, + {0xa34d721642b06084, 0x27f002d7f95d0190}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4}, + {0xff290242c83396ce, 0x7e67047175a15271}, + {0x9f79a169bd203e41, 0x0f0062c6e984d386}, + {0xc75809c42c684dd1, 0x52c07b78a3e60868}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a82}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb691}, + {0xc2abf989935ddbfe, 0x6acff893d00ea435}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d43}, + {0x98165af37b2153de, 0xc3727a337a8b704a}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c}, + {0xeda2ee1c7064130c, 0x1162def06f79df73}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a2}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, + {0xb10d8e1456105dad, 0x7425a83e872c5f47}, + {0xdd50f1996b947518, 0xd12f124e28f77719}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550b}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4e}, + {0x8714a775e3e95c78, 0x65acfaec34810a71}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d}, + {0xd31045a8341ca07c, 0x1ede48111209a050}, + {0x83ea2b892091e44d, 0x934aed0aab460432}, + {0xa4e4b66b68b65d60, 0xf81da84d5617853f}, + {0xce1de40642e3f4b9, 0x36251260ab9d668e}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019}, + {0xa1075a24e4421730, 0xb24cf65b8612f81f}, + {0xc94930ae1d529cfc, 0xdee033f26797b627}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864e}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1db}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af3}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1}, + {0xea53df5fd18d5513, 0x84c86189216dc5ed}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511012}, + {0xdf78e4b2bd342cf6, 0x914da9246b255416}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e}, + {0xae9672aba3d0c320, 0xa184ac2473b529b1}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e}, + {0x8865899617fb1871, 0x7e2fa67c7a658892}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7}, + {0xd51ea6fa85785631, 0x552a74227f3ea565}, + {0x8533285c936b35de, 0xd53a88958f87275f}, + {0xa67ff273b8460356, 0x8a892abaf368f137}, + {0xd01fef10a657842c, 0x2d2b7569b0432d85}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc73}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b8f}, + {0xcb3f2f7642717713, 0x241c70a936219a73}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0110}, + {0x9ec95d1463e8a506, 0xf4363804324a40aa}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050a}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8326}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8cec}, + {0x976e41088617ca01, 0xd5be0503e085d813}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219e}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b503}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad4}, + {0x906a617d450187e2, 0x27fb2b80668b24c5}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf6}, + {0xe1a63853bbd26451, 0x5e7873f8a0396973}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e8}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fb}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d}, + {0xac2820d9623bf429, 0x546345fa9fbdcd44}, + {0xd732290fbacaf133, 0xa97c177947ad4095}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a74}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3111}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3}, + {0xa0555e361951c366, 0xd7e105bcc332621f}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7}, + {0xfa856334878fc150, 0xb14f98f6f0feb951}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c8}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fa}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3}, + {0xeeea5d5004981478, 0x1858ccfce06cac74}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc8}, + {0xbaa718e68396cffd, 0xd30560258f54e6ba}, + {0xe950df20247c83fd, 0x47c6b82ef32a2069}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5441}, + {0xb6472e511c81471d, 0xe0133fe4adf8e952}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a6}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648}, + {0xb201833b35d63f73, 0x2cd2cc6551e513da}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1}, + {0x8b112e86420f6191, 0xfb04afaf27faf782}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b563}, + {0xd94ad8b1c7380874, 0x18375281ae7822bc}, + {0x87cec76f1c830548, 0x8f2293910d0b15b5}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22}, + {0xd433179d9c8cb841, 0x5fa60692a46151eb}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd333}, + {0xa5c7ea73224deff3, 0x12b9b522906c0800}, + {0xcf39e50feae16bef, 0xd768226b34870a00}, + {0x81842f29f2cce375, 0xe6a1158300d46640}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd0}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc4}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1}, + {0xc5a05277621be293, 0xc7098b7305241885}, + { 0xf70867153aa2db38, + 0xb8cbee4fc66d1ea7 } +#else + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0xc350000000000000, 0x0000000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xfee50b7025c36a08, 0x02f236d04753d5b4}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, + {0xa6539930bf6bff45, 0x84db8346b786151c}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, + {0xd910f7ff28069da4, 0x1b2ba1518094da04}, + {0xaf58416654a6babb, 0x387ac8d1970027b2}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, + { 0x95527a5202df0ccb, + 0x0f37801e0c43ebc8 } +#endif + }; + +#if FMT_USE_FULL_CACHE_DRAGONBOX + return pow10_significands[k - float_info::min_k]; +#else + static constexpr const uint64_t powers_of_5_64[] = { + 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, + 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, + 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, + 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, + 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, + 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, + 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, + 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, + 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; + + static constexpr const uint32_t pow10_recovery_errors[] = { + 0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001, + 0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555, + 0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110, + 0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454, + 0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014, + 0x69514555, 0x05151109, 0x00155555}; + + static const int compression_ratio = 27; + + // Compute base index. + int cache_index = (k - float_info::min_k) / compression_ratio; + int kb = cache_index * compression_ratio + float_info::min_k; + int offset = k - kb; + + // Get base cache. + uint128_wrapper base_cache = pow10_significands[cache_index]; + if (offset == 0) return base_cache; + + // Compute the required amount of bit-shift. + int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; + FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); + + // Try to recover the real cache. + uint64_t pow5 = powers_of_5_64[offset]; + uint128_wrapper recovered_cache = umul128(base_cache.high(), pow5); + uint128_wrapper middle_low = + umul128(base_cache.low() - (kb < 0 ? 1u : 0u), pow5); + + recovered_cache += middle_low.high(); + + uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); + uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); + + recovered_cache = + uint128_wrapper{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; + + if (kb < 0) recovered_cache += 1; + + // Get error. + int error_idx = (k - float_info::min_k) / 16; + uint32_t error = (pow10_recovery_errors[error_idx] >> + ((k - float_info::min_k) % 16) * 2) & + 0x3; + + // Add the error back. + FMT_ASSERT(recovered_cache.low() + error >= recovered_cache.low(), ""); + return {recovered_cache.high(), recovered_cache.low() + error}; +#endif + } + + static carrier_uint compute_mul(carrier_uint u, + const cache_entry_type& cache) FMT_NOEXCEPT { + return umul192_upper64(u, cache); + } + + static uint32_t compute_delta(cache_entry_type const& cache, + int beta_minus_1) FMT_NOEXCEPT { + return static_cast(cache.high() >> (64 - 1 - beta_minus_1)); + } + + static bool compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta_minus_1) FMT_NOEXCEPT { + FMT_ASSERT(beta_minus_1 >= 1, ""); + FMT_ASSERT(beta_minus_1 < 64, ""); + + return ((umul192_middle64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; + } + + static carrier_uint compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return (cache.high() - + (cache.high() >> (float_info::significand_bits + 2))) >> + (64 - float_info::significand_bits - 1 - beta_minus_1); + } + + static carrier_uint compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return (cache.high() + + (cache.high() >> (float_info::significand_bits + 1))) >> + (64 - float_info::significand_bits - 1 - beta_minus_1); + } + + static carrier_uint compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + return ((cache.high() >> + (64 - float_info::significand_bits - 2 - beta_minus_1)) + + 1) / + 2; + } +}; + +// Various integer checks +template +bool is_left_endpoint_integer_shorter_interval(int exponent) FMT_NOEXCEPT { + return exponent >= + float_info< + T>::case_shorter_interval_left_endpoint_lower_threshold && + exponent <= + float_info::case_shorter_interval_left_endpoint_upper_threshold; +} +template +bool is_endpoint_integer(typename float_info::carrier_uint two_f, + int exponent, int minus_k) FMT_NOEXCEPT { + if (exponent < float_info::case_fc_pm_half_lower_threshold) return false; + // For k >= 0. + if (exponent <= float_info::case_fc_pm_half_upper_threshold) return true; + // For k < 0. + if (exponent > float_info::divisibility_check_by_5_threshold) return false; + return divisible_by_power_of_5(two_f, minus_k); +} + +template +bool is_center_integer(typename float_info::carrier_uint two_f, int exponent, + int minus_k) FMT_NOEXCEPT { + // Exponent for 5 is negative. + if (exponent > float_info::divisibility_check_by_5_threshold) return false; + if (exponent > float_info::case_fc_upper_threshold) + return divisible_by_power_of_5(two_f, minus_k); + // Both exponents are nonnegative. + if (exponent >= float_info::case_fc_lower_threshold) return true; + // Exponent for 2 is negative. + return divisible_by_power_of_2(two_f, minus_k - exponent + 1); +} + +// Remove trailing zeros from n and return the number of zeros removed (float) +FMT_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT { +#ifdef FMT_BUILTIN_CTZ + int t = FMT_BUILTIN_CTZ(n); +#else + int t = ctz(n); +#endif + if (t > float_info::max_trailing_zeros) + t = float_info::max_trailing_zeros; + + const uint32_t mod_inv1 = 0xcccccccd; + const uint32_t max_quotient1 = 0x33333333; + const uint32_t mod_inv2 = 0xc28f5c29; + const uint32_t max_quotient2 = 0x0a3d70a3; + + int s = 0; + for (; s < t - 1; s += 2) { + if (n * mod_inv2 > max_quotient2) break; + n *= mod_inv2; + } + if (s < t && n * mod_inv1 <= max_quotient1) { + n *= mod_inv1; + ++s; + } + n >>= s; + return s; +} + +// Removes trailing zeros and returns the number of zeros removed (double) +FMT_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT { +#ifdef FMT_BUILTIN_CTZLL + int t = FMT_BUILTIN_CTZLL(n); +#else + int t = ctzll(n); +#endif + if (t > float_info::max_trailing_zeros) + t = float_info::max_trailing_zeros; + // Divide by 10^8 and reduce to 32-bits + // Since ret_value.significand <= (2^64 - 1) / 1000 < 10^17, + // both of the quotient and the r should fit in 32-bits + + const uint32_t mod_inv1 = 0xcccccccd; + const uint32_t max_quotient1 = 0x33333333; + const uint64_t mod_inv8 = 0xc767074b22e90e21; + const uint64_t max_quotient8 = 0x00002af31dc46118; + + // If the number is divisible by 1'0000'0000, work with the quotient + if (t >= 8) { + auto quotient_candidate = n * mod_inv8; + + if (quotient_candidate <= max_quotient8) { + auto quotient = static_cast(quotient_candidate >> 8); + + int s = 8; + for (; s < t; ++s) { + if (quotient * mod_inv1 > max_quotient1) break; + quotient *= mod_inv1; + } + quotient >>= (s - 8); + n = quotient; + return s; + } + } + + // Otherwise, work with the remainder + auto quotient = static_cast(n / 100000000); + auto remainder = static_cast(n - 100000000 * quotient); + + if (t == 0 || remainder * mod_inv1 > max_quotient1) { + return 0; + } + remainder *= mod_inv1; + + if (t == 1 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 1) + quotient * 10000000ull; + return 1; + } + remainder *= mod_inv1; + + if (t == 2 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 2) + quotient * 1000000ull; + return 2; + } + remainder *= mod_inv1; + + if (t == 3 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 3) + quotient * 100000ull; + return 3; + } + remainder *= mod_inv1; + + if (t == 4 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 4) + quotient * 10000ull; + return 4; + } + remainder *= mod_inv1; + + if (t == 5 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 5) + quotient * 1000ull; + return 5; + } + remainder *= mod_inv1; + + if (t == 6 || remainder * mod_inv1 > max_quotient1) { + n = (remainder >> 6) + quotient * 100ull; + return 6; + } + remainder *= mod_inv1; + + n = (remainder >> 7) + quotient * 10ull; + return 7; +} + +// The main algorithm for shorter interval case +template +FMT_INLINE decimal_fp shorter_interval_case(int exponent) FMT_NOEXCEPT { + decimal_fp ret_value; + // Compute k and beta + const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); + const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); + + // Compute xi and zi + using cache_entry_type = typename cache_accessor::cache_entry_type; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + + auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( + cache, beta_minus_1); + auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( + cache, beta_minus_1); + + // If the left endpoint is not an integer, increase it + if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; + + // Try bigger divisor + ret_value.significand = zi / 10; + + // If succeed, remove trailing zeros if necessary and return + if (ret_value.significand * 10 >= xi) { + ret_value.exponent = minus_k + 1; + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + } + + // Otherwise, compute the round-up of y + ret_value.significand = + cache_accessor::compute_round_up_for_shorter_interval_case( + cache, beta_minus_1); + ret_value.exponent = minus_k; + + // When tie occurs, choose one of them according to the rule + if (exponent >= float_info::shorter_interval_tie_lower_threshold && + exponent <= float_info::shorter_interval_tie_upper_threshold) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } else if (ret_value.significand < xi) { + ++ret_value.significand; + } + return ret_value; +} + +template decimal_fp to_decimal(T x) FMT_NOEXCEPT { + // Step 1: integer promotion & Schubfach multiplier calculation. + + using carrier_uint = typename float_info::carrier_uint; + using cache_entry_type = typename cache_accessor::cache_entry_type; + auto br = bit_cast(x); + + // Extract significand bits and exponent bits. + const carrier_uint significand_mask = + (static_cast(1) << float_info::significand_bits) - 1; + carrier_uint significand = (br & significand_mask); + int exponent = static_cast((br & exponent_mask()) >> + float_info::significand_bits); + + if (exponent != 0) { // Check if normal. + exponent += float_info::exponent_bias - float_info::significand_bits; + + // Shorter interval case; proceed like Schubfach. + if (significand == 0) return shorter_interval_case(exponent); + + significand |= + (static_cast(1) << float_info::significand_bits); + } else { + // Subnormal case; the interval is always regular. + if (significand == 0) return {0, 0}; + exponent = float_info::min_exponent - float_info::significand_bits; + } + + const bool include_left_endpoint = (significand % 2 == 0); + const bool include_right_endpoint = include_left_endpoint; + + // Compute k and beta. + const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); + + // Compute zi and deltai + // 10^kappa <= deltai < 10^(kappa + 1) + const uint32_t deltai = cache_accessor::compute_delta(cache, beta_minus_1); + const carrier_uint two_fc = significand << 1; + const carrier_uint two_fr = two_fc | 1; + const carrier_uint zi = + cache_accessor::compute_mul(two_fr << beta_minus_1, cache); + + // Step 2: Try larger divisor; remove trailing zeros if necessary + + // Using an upper bound on zi, we might be able to optimize the division + // better than the compiler; we are computing zi / big_divisor here + decimal_fp ret_value; + ret_value.significand = divide_by_10_to_kappa_plus_1(zi); + uint32_t r = static_cast(zi - float_info::big_divisor * + ret_value.significand); + + if (r > deltai) { + goto small_divisor_case_label; + } else if (r < deltai) { + // Exclude the right endpoint if necessary + if (r == 0 && !include_right_endpoint && + is_endpoint_integer(two_fr, exponent, minus_k)) { + --ret_value.significand; + r = float_info::big_divisor; + goto small_divisor_case_label; + } + } else { + // r == deltai; compare fractional parts + // Check conditions in the order different from the paper + // to take advantage of short-circuiting + const carrier_uint two_fl = two_fc - 1; + if ((!include_left_endpoint || + !is_endpoint_integer(two_fl, exponent, minus_k)) && + !cache_accessor::compute_mul_parity(two_fl, cache, beta_minus_1)) { + goto small_divisor_case_label; + } + } + ret_value.exponent = minus_k + float_info::kappa + 1; + + // We may need to remove trailing zeros + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + + // Step 3: Find the significand with the smaller divisor + +small_divisor_case_label: + ret_value.significand *= 10; + ret_value.exponent = minus_k + float_info::kappa; + + const uint32_t mask = (1u << float_info::kappa) - 1; + auto dist = r - (deltai / 2) + (float_info::small_divisor / 2); + + // Is dist divisible by 2^kappa? + if ((dist & mask) == 0) { + const bool approx_y_parity = + ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; + dist >>= float_info::kappa; + + // Is dist divisible by 5^kappa? + if (check_divisibility_and_divide_by_pow5::kappa>(dist)) { + ret_value.significand += dist; + + // Check z^(f) >= epsilon^(f) + // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, + // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f) + // Since there are only 2 possibilities, we only need to care about the + // parity. Also, zi and r should have the same parity since the divisor + // is an even number + if (cache_accessor::compute_mul_parity(two_fc, cache, beta_minus_1) != + approx_y_parity) { + --ret_value.significand; + } else { + // If z^(f) >= epsilon^(f), we might have a tie + // when z^(f) == epsilon^(f), or equivalently, when y is an integer + if (is_center_integer(two_fc, exponent, minus_k)) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } + } + } + // Is dist not divisible by 5^kappa? + else { + ret_value.significand += dist; + } + } + // Is dist not divisible by 2^kappa? + else { + // Since we know dist is small, we might be able to optimize the division + // better than the compiler; we are computing dist / small_divisor here + ret_value.significand += + small_division_by_pow10::kappa>(dist); + } + return ret_value; +} +} // namespace dragonbox + // Formats value using a variation of the Fixed-Precision Positive // Floating-Point Printout ((FPP)^2) algorithm by Steele & White: -// https://fmt.dev/p372-steele.pdf. +// https://fmt.dev/papers/p372-steele.pdf. template -void fallback_format(Double d, buffer& buf, int& exp10) { +void fallback_format(Double d, int num_digits, bool binary32, 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. @@ -998,8 +2227,9 @@ void fallback_format(Double d, buffer& buf, int& exp10) { // 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. - // TODO: handle float - int shift = value.assign(d) ? 2 : 1; + 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) { numerator.assign(significand); @@ -1012,7 +2242,7 @@ void fallback_format(Double d, buffer& buf, int& exp10) { upper = &upper_store; } denominator.assign_pow10(exp10); - denominator <<= 1; + denominator <<= shift; } else if (exp10 < 0) { numerator.assign_pow10(-exp10); lower.assign(numerator); @@ -1034,39 +2264,73 @@ void fallback_format(Double d, buffer& buf, int& exp10) { upper = &upper_store; } } - if (!upper) upper = &lower; // Invariant: value == (numerator / denominator) * pow(10, exp10). - bool even = (value.f & 1) == 0; - int num_digits = 0; - char* data = buf.data(); - for (;;) { - int digit = numerator.divmod_assign(denominator); - bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. - // numerator + upper >[=] pow10: - bool high = add_compare(numerator, *upper, denominator) + even > 0; - data[num_digits++] = static_cast('0' + digit); - if (low || high) { - if (!low) { - ++data[num_digits - 1]; - } else if (high) { - int result = add_compare(numerator, numerator, denominator); - // Round half to even. - if (result > 0 || (result == 0 && (digit % 2) != 0)) + if (num_digits < 0) { + // Generate the shortest representation. + if (!upper) upper = &lower; + bool even = (value.f & 1) == 0; + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast('0' + digit); + if (low || high) { + if (!low) { ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // 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'; + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '1'; + ++exp10; } - buf.resize(to_unsigned(num_digits)); - exp10 -= num_digits - 1; return; } - numerator *= 10; - lower *= 10; - if (upper != &lower) *upper *= 10; + ++digit; } + buf[num_digits - 1] = static_cast('0' + digit); } -// Formats value using the Grisu algorithm -// (https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf) -// if T is a IEEE754 binary32 or binary64 and snprintf otherwise. template int format_float(T value, int precision, float_specs specs, buffer& buf) { static_assert(!std::is_same::value, ""); @@ -1078,66 +2342,57 @@ int format_float(T value, int precision, float_specs specs, buffer& buf) { buf.push_back('0'); return 0; } - buf.resize(to_unsigned(precision)); + buf.try_resize(to_unsigned(precision)); std::uninitialized_fill_n(buf.data(), precision, '0'); return -precision; } if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); + if (precision < 0) { + // Use Dragonbox for the shortest format. + if (specs.binary32) { + auto dec = dragonbox::to_decimal(static_cast(value)); + write(buffer_appender(buf), dec.significand); + return dec.exponent; + } + auto dec = dragonbox::to_decimal(static_cast(value)); + write(buffer_appender(buf), dec.significand); + 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. - if (precision < 0) { - fp fp_value; - auto boundaries = specs.binary32 - ? fp_value.assign_float_with_boundaries(value) - : fp_value.assign_with_boundaries(value); - fp_value = normalize(fp_value); - // Find a cached power of 10 such that multiplying value by it will bring - // the exponent in the range [min_exp, -32]. - const fp cached_pow = get_cached_power( - min_exp - (fp_value.e + fp::significand_size), cached_exp10); - // Multiply value and boundaries by the cached power of 10. - fp_value = fp_value * cached_pow; - boundaries.lower = multiply(boundaries.lower, cached_pow.f); - boundaries.upper = multiply(boundaries.upper, cached_pow.f); - assert(min_exp <= fp_value.e && fp_value.e <= -32); - --boundaries.lower; // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}. - ++boundaries.upper; // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}. - // Numbers outside of (lower, upper) definitely do not round to value. - grisu_shortest_handler handler{buf.data(), 0, - boundaries.upper - fp_value.f}; - auto result = - grisu_gen_digits(fp(boundaries.upper, fp_value.e), - boundaries.upper - boundaries.lower, exp, handler); - if (result == digits::error) { - exp += handler.size - cached_exp10 - 1; - fallback_format(value, buf, exp); - return exp; - } - buf.resize(to_unsigned(handler.size)); + 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 { - if (precision > 17) return snprintf_float(value, precision, specs, buf); - fp normalized = normalize(fp(value)); - const auto cached_pow = get_cached_power( - min_exp - (normalized.e + fp::significand_size), cached_exp10); - normalized = normalized * cached_pow; - fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) - return snprintf_float(value, precision, specs, buf); - int num_digits = handler.size; - if (!fixed) { - // Remove trailing zeros. - while (num_digits > 0 && buf[num_digits - 1] == '0') { - --num_digits; - ++exp; - } - } - buf.resize(to_unsigned(num_digits)); + exp += handler.exp10; + buf.try_resize(to_unsigned(handler.size)); } - return exp - cached_exp10; -} + if (!fixed && !specs.showpoint) { + // Remove trailing zeros. + auto num_digits = buf.size(); + while (num_digits > 0 && buf[num_digits - 1] == '0') { + --num_digits; + ++exp; + } + buf.try_resize(num_digits); + } + return exp; +} // namespace detail template int snprintf_float(T value, int precision, float_specs specs, @@ -1185,19 +2440,20 @@ int snprintf_float(T value, int precision, float_specs specs, ? snprintf_ptr(begin, capacity, format, precision, value) : snprintf_ptr(begin, capacity, format, value); if (result < 0) { - buf.reserve(buf.capacity() + 1); // The buffer will grow exponentially. + // The buffer will grow exponentially. + buf.try_reserve(buf.capacity() + 1); continue; } auto size = to_unsigned(result); // Size equal to capacity means that the last character was truncated. if (size >= capacity) { - buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'. + buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. continue; } auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; if (specs.format == float_format::fixed) { if (precision == 0) { - buf.resize(size); + buf.try_resize(size); return 0; } // Find and remove the decimal point. @@ -1207,11 +2463,11 @@ int snprintf_float(T value, int precision, float_specs specs, } while (is_digit(*p)); int fraction_size = static_cast(end - p - 1); std::memmove(p, p + 1, to_unsigned(fraction_size)); - buf.resize(size - 1); + buf.try_resize(size - 1); return -fraction_size; } if (specs.format == float_format::hex) { - buf.resize(size + offset); + buf.try_resize(size + offset); return 0; } // Find and parse the exponent. @@ -1220,11 +2476,11 @@ int snprintf_float(T value, int precision, float_specs specs, --exp_pos; } while (*exp_pos != 'e'); char sign = exp_pos[1]; - assert(sign == '+' || sign == '-'); + FMT_ASSERT(sign == '+' || sign == '-', ""); int exp = 0; auto p = exp_pos + 2; // Skip 'e' and sign. do { - assert(is_digit(*p)); + FMT_ASSERT(is_digit(*p), ""); exp = exp * 10 + (*p++ - '0'); } while (p != end); if (sign == '-') exp = -exp; @@ -1237,69 +2493,15 @@ int snprintf_float(T value, int precision, float_specs specs, fraction_size = static_cast(fraction_end - begin - 1); std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size)); } - buf.resize(to_unsigned(fraction_size) + offset + 1); + buf.try_resize(to_unsigned(fraction_size) + offset + 1); return exp - fraction_size; } } - -// A public domain branchless UTF-8 decoder by Christopher Wellons: -// https://github.com/skeeto/branchless-utf8 -/* Decode the next character, c, from buf, reporting errors in e. - * - * Since this is a branchless decoder, four bytes will be read from the - * buffer regardless of the actual length of the next character. This - * means the buffer _must_ have at least three bytes of zero padding - * following the end of the data stream. - * - * Errors are reported in e, which will be non-zero if the parsed - * character was somehow invalid: invalid byte sequence, non-canonical - * encoding, or a surrogate half. - * - * The function returns a pointer to the next character. When an error - * occurs, this pointer will be a guess that depends on the particular - * error, but it will always advance at least one byte. - */ -FMT_FUNC const char* utf8_decode(const char* buf, uint32_t* c, int* e) { - static const 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}; - static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; - static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; - static const int shiftc[] = {0, 18, 12, 6, 0}; - static const int shifte[] = {0, 6, 4, 2, 0}; - - auto s = reinterpret_cast(buf); - int len = lengths[s[0] >> 3]; - - // Compute the pointer to the next character early so that the next - // iteration can start working on the next character. Neither Clang - // nor GCC figure out this reordering on their own. - const char* next = buf + len + !len; - - // Assume a four-byte character and load four bytes. Unused bits are - // shifted out. - *c = uint32_t(s[0] & masks[len]) << 18; - *c |= uint32_t(s[1] & 0x3f) << 12; - *c |= uint32_t(s[2] & 0x3f) << 6; - *c |= uint32_t(s[3] & 0x3f) << 0; - *c >>= shiftc[len]; - - // Accumulate the various error conditions. - *e = (*c < mins[len]) << 6; // non-canonical encoding - *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? - *e |= (*c > 0x10FFFF) << 8; // out of range? - *e |= (s[1] & 0xc0) >> 2; - *e |= (s[2] & 0xc0) >> 4; - *e |= (s[3]) >> 6; - *e ^= 0x2a; // top two bits of each tail byte correct? - *e >>= shifte[len]; - - return next; -} } // namespace detail template <> struct formatter { - format_parse_context::iterator parse(format_parse_context& ctx) { + FMT_CONSTEXPR format_parse_context::iterator parse( + format_parse_context& ctx) { return ctx.begin(); } @@ -1310,23 +2512,21 @@ template <> struct formatter { for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { - out = format_to(out, "{:x}", value); + out = format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } - out = format_to(out, "{:08x}", value); + out = format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) - out = format_to(out, "p{}", n.exp_ * detail::bigint::bigit_bits); + out = format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); return out; } }; FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { - auto transcode = [this](const char* p) { - auto cp = uint32_t(); - auto error = 0; - p = utf8_decode(p, &cp, &error); + for_each_codepoint(s, [this](uint32_t cp, int error) { if (error != 0) FMT_THROW(std::runtime_error("invalid utf8")); if (cp <= 0xFFFF) { buffer_.push_back(static_cast(cp)); @@ -1335,41 +2535,16 @@ 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 p; - }; - 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 = transcode(p); - } - if (auto num_chars_left = s.data() + s.size() - p) { - char buf[2 * block_size - 1] = {}; - memcpy(buf, p, to_unsigned(num_chars_left)); - p = buf; - do { - p = transcode(p); - } while (p - buf < num_chars_left); - } + }); buffer_.push_back(0); } FMT_FUNC void format_system_error(detail::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT { + const char* message) FMT_NOEXCEPT { FMT_TRY { - memory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - char* system_message = &buf[0]; - int result = - detail::safe_strerror(error_code, system_message, buf.size()); - if (result == 0) { - format_to(std::back_inserter(out), "{}: {}", message, system_message); - return; - } - if (result != ERANGE) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } + auto ec = std::error_code(error_code, std::generic_category()); + write(std::back_inserter(out), std::system_error(ec, message).what()); + return; } FMT_CATCH(...) {} format_error_code(out, error_code, message); @@ -1380,53 +2555,50 @@ FMT_FUNC void detail::error_handler::on_error(const char* message) { } FMT_FUNC void report_system_error(int error_code, - fmt::string_view message) FMT_NOEXCEPT { + const char* message) FMT_NOEXCEPT { report_error(format_system_error, error_code, message); } -struct stringifier { - template FMT_INLINE std::string operator()(T value) const { - return to_string(value); - } - std::string operator()(basic_format_arg::handle h) const { - memory_buffer buf; - detail::buffer& base = buf; - format_parse_context parse_ctx({}); - format_context format_ctx(std::back_inserter(base), {}, {}); - h.format(parse_ctx, format_ctx); - return to_string(buf); - } -}; - -FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) { - if (format_str.size() == 2 && equal2(format_str.data(), "{}")) { - auto arg = args.get(0); - if (!arg) error_handler().on_error("argument not found"); - return visit_format_arg(stringifier(), arg); - } - memory_buffer buffer; - detail::vformat_to(buffer, format_str, args); +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. + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); return to_string(buffer); } -FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, - basic_format_args>(args)); +#ifdef _WIN32 +namespace detail { +using dword = conditional_t; +extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // + void*, const void*, dword, dword*, void*); +} // namespace detail +#endif + +namespace detail { +FMT_FUNC void print(std::FILE* f, string_view text) { #ifdef _WIN32 auto fd = _fileno(f); if (_isatty(fd)) { - detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size())); - auto written = DWORD(); - if (!WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), - u16.c_str(), static_cast(u16.size()), &written, - nullptr)) { - FMT_THROW(format_error("failed to write to console")); + detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); + auto written = detail::dword(); + if (detail::WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), + u16.c_str(), static_cast(u16.size()), + &written, nullptr)) { + return; } - return; + // Fallback to fwrite on failure. It can happen if the output has been + // redirected to NUL. } #endif - detail::fwrite_fully(buffer.data(), 1, buffer.size(), f); + detail::fwrite_fully(text.data(), 1, text.size(), f); +} +} // namespace detail + +FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { + memory_buffer buffer; + detail::vformat_to(buffer, format_str, args); + detail::print(f, {buffer.data(), buffer.size()}); } #ifdef _WIN32 @@ -1446,8 +2618,4 @@ FMT_FUNC void vprint(string_view format_str, format_args args) { FMT_END_NAMESPACE -#ifdef _MSC_VER -# pragma warning(pop) -#endif - #endif // FMT_FORMAT_INL_H_ diff --git a/include/fmt/format.h b/include/fmt/format.h index 17509b7b..03ae1c96 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -33,13 +33,13 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include -#include -#include -#include -#include -#include -#include +#include // std::signbit +#include // uint32_t +#include // std::numeric_limits +#include // std::uninitialized_copy +#include // std::runtime_error +#include // std::system_error +#include // std::swap #include "core.h" @@ -69,28 +69,10 @@ # define FMT_NOINLINE #endif -#if __cplusplus == 201103L || __cplusplus == 201402L -# if defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -# elif FMT_GCC_VERSION >= 700 && !defined(__PGI) && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] -# else -# define FMT_FALLTHROUGH -# endif -#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ - (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define FMT_FALLTHROUGH [[fallthrough]] +#if FMT_MSC_VER +# define FMT_MSC_DEFAULT = default #else -# define FMT_FALLTHROUGH -#endif - -#ifndef FMT_MAYBE_UNUSED -# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) -# define FMT_MAYBE_UNUSED [[maybe_unused]] -# else -# define FMT_MAYBE_UNUSED -# endif +# define FMT_MSC_DEFAULT #endif #ifndef FMT_THROW @@ -111,10 +93,9 @@ FMT_END_NAMESPACE # define FMT_THROW(x) throw x # endif # else -# define FMT_THROW(x) \ - do { \ - static_cast(sizeof(x)); \ - FMT_ASSERT(false, ""); \ +# define FMT_THROW(x) \ + do { \ + FMT_ASSERT(false, (x).what()); \ } while (false) # endif #endif @@ -127,6 +108,27 @@ 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]] +# 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 +# endif +#endif + +// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. +#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC +# define FMT_DEPRECATED_ALIAS +#else +# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED +#endif + #ifndef FMT_USE_USER_DEFINED_LITERALS // EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. # if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ @@ -138,29 +140,12 @@ FMT_END_NAMESPACE # endif #endif -#ifndef FMT_USE_UDL_TEMPLATE -// EDG frontend based compilers (icc, nvcc, etc) and GCC < 6.4 do not properly -// support UDL templates and GCC >= 9 warns about them. -# if FMT_USE_USER_DEFINED_LITERALS && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 501) && \ - ((FMT_GCC_VERSION >= 604 && __cplusplus >= 201402L) || \ - FMT_CLANG_VERSION >= 304) -# define FMT_USE_UDL_TEMPLATE 1 -# else -# define FMT_USE_UDL_TEMPLATE 0 -# endif -#endif - -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 -#endif - -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif - -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 +// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. +#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) +# define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif // __builtin_clz is broken in clang with Microsoft CodeGen: @@ -171,80 +156,116 @@ FMT_END_NAMESPACE #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) +#endif + +#if FMT_MSC_VER +# include // _BitScanReverse[64], _BitScanForward[64], _umul128 +#endif // Some compilers masquerade as both MSVC and GCC-likes or otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) -# include // _BitScanReverse, _BitScanReverse64 - +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL) FMT_BEGIN_NAMESPACE namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. -# ifndef __clang__ +# if !defined(__clang__) +# pragma managed(push, off) +# pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) +# if defined(_WIN64) +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# endif # endif -inline uint32_t clz(uint32_t x) { + +inline auto clz(uint32_t x) -> int { unsigned long r = 0; _BitScanReverse(&r, x); - FMT_ASSERT(x != 0, ""); // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. - FMT_SUPPRESS_MSC_WARNING(6102) - return 31 - r; + FMT_MSC_WARNING(suppress : 6102) + return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) -# if defined(_WIN64) && !defined(__clang__) -# pragma intrinsic(_BitScanReverse64) -# endif - -inline uint32_t clzll(uint64_t x) { +inline auto clzll(uint64_t x) -> int { unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); # else // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 - (r + 32); - + if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif - FMT_ASSERT(x != 0, ""); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. - FMT_SUPPRESS_MSC_WARNING(6102) - return 63 - r; + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) + +inline auto ctz(uint32_t x) -> int { + unsigned long r = 0; + _BitScanForward(&r, x); + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return static_cast(r); +} +# define FMT_BUILTIN_CTZ(n) detail::ctz(n) + +inline auto ctzll(uint64_t x) -> int { + unsigned long r = 0; + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. +# ifdef _WIN64 + _BitScanForward64(&r, x); +# else + // Scan the low 32 bits. + if (_BitScanForward(&r, static_cast(x))) return static_cast(r); + // Scan the high 32 bits. + _BitScanForward(&r, static_cast(x >> 32)); + r += 32; +# endif + 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 -// Enable the deprecated numeric alignment. -#ifndef FMT_DEPRECATED_NUMERIC_ALIGN -# define FMT_DEPRECATED_NUMERIC_ALIGN 0 -#endif - FMT_BEGIN_NAMESPACE namespace detail { +#if __cplusplus >= 202002L || \ + (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEXPR20 +#endif + // 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 Dest bit_cast(const Source& source) { +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; } -inline bool is_big_endian() { +inline auto is_big_endian() -> bool { const auto u = 1u; struct bytes { char data[sizeof(u)]; @@ -267,26 +288,28 @@ struct fallback_uintptr { }; #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; -inline uintptr_t to_uintptr(const void* p) { return bit_cast(p); } +inline auto to_uintptr(const void* p) -> uintptr_t { + return bit_cast(p); +} #else using uintptr_t = fallback_uintptr; -inline fallback_uintptr to_uintptr(const void* p) { +inline auto to_uintptr(const void* p) -> fallback_uintptr { return fallback_uintptr(p); } #endif // Returns the largest possible value for type T. Same as // std::numeric_limits::max() but shorter and not affected by the max macro. -template constexpr T max_value() { +template constexpr auto max_value() -> T { return (std::numeric_limits::max)(); } -template constexpr int num_bits() { +template constexpr auto num_bits() -> int { return std::numeric_limits::digits; } // std::numeric_limits::digits may return 0 for 128-bit ints. -template <> constexpr int num_bits() { return 128; } -template <> constexpr int num_bits() { return 128; } -template <> constexpr int num_bits() { +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return static_cast(sizeof(void*) * std::numeric_limits::digits); } @@ -298,283 +321,279 @@ FMT_INLINE void assume(bool condition) { #endif } -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; - -template -using void_t = typename detail::void_t_impl::type; - // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); template using sentinel_t = decltype(std::end(std::declval())); -// Detect the iterator category of *any* given type in a SFINAE-friendly way. -// Unfortunately, older implementations of std::iterator_traits are not safe -// for use in a SFINAE-context. -template -struct iterator_category : std::false_type {}; - -template struct iterator_category { - using type = std::random_access_iterator_tag; -}; - -template -struct iterator_category> { - using type = typename It::iterator_category; -}; - -// Detect if *any* given type models the OutputIterator concept. -template class is_output_iterator { - // Check for mutability because all iterator categories derived from - // std::input_iterator_tag *may* also meet the requirements of an - // OutputIterator, thereby falling into the category of 'mutable iterators' - // [iterator.requirements.general] clause 4. The compiler reveals this - // property only at the point of *actually dereferencing* the iterator! - template - static decltype(*(std::declval())) test(std::input_iterator_tag); - template static char& test(std::output_iterator_tag); - template static const char& test(...); - - using type = decltype(test(typename iterator_category::type{})); - - public: - enum { value = !std::is_const>::value }; -}; - // A workaround for std::string not having mutable data() until C++17. -template inline Char* get_data(std::basic_string& s) { +template +inline auto get_data(std::basic_string& s) -> Char* { return &s[0]; } template -inline typename Container::value_type* get_data(Container& c) { +inline auto get_data(Container& c) -> typename Container::value_type* { return c.data(); } #if defined(_SECURE_SCL) && _SECURE_SCL // Make a checked iterator to avoid MSVC warnings. template using checked_ptr = stdext::checked_array_iterator; -template checked_ptr make_checked(T* p, size_t size) { +template auto make_checked(T* p, size_t size) -> checked_ptr { return {p, size}; } #else template using checked_ptr = T*; -template inline T* make_checked(T* p, size_t) { return p; } +template inline auto make_checked(T* p, size_t) -> T* { return p; } #endif +// Attempts to reserve space for n extra characters in the output range. +// Returns a pointer to the reserved range or a reference to it. template ::value)> -#if FMT_CLANG_VERSION +#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif -inline checked_ptr -reserve(std::back_insert_iterator it, size_t n) { +inline auto +reserve(std::back_insert_iterator it, size_t n) + -> checked_ptr { Container& c = get_container(it); size_t size = c.size(); c.resize(size + n); return make_checked(get_data(c) + size, n); } -template inline Iterator& reserve(Iterator& it, size_t) { - return it; -} - -template ::value)> -inline std::back_insert_iterator base_iterator( - std::back_insert_iterator& it, - checked_ptr) { +template +inline auto reserve(buffer_appender it, size_t n) -> buffer_appender { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); return it; } template -inline Iterator base_iterator(Iterator, Iterator it) { +constexpr auto reserve(Iterator& it, size_t) -> Iterator& { return it; } -// An output iterator that counts the number of objects written to it and -// discards them. -class counting_iterator { - private: - size_t count_; +template +using reserve_iterator = + remove_reference_t(), 0))>; - public: - using iterator_category = std::output_iterator_tag; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - using _Unchecked_type = counting_iterator; // Mark iterator as checked. +template +constexpr auto to_pointer(OutputIt, size_t) -> T* { + return nullptr; +} +template auto to_pointer(buffer_appender it, size_t n) -> T* { + buffer& buf = get_container(it); + auto size = buf.size(); + if (buf.capacity() < size + n) return nullptr; + buf.try_resize(size + n); + return buf.data() + size; +} - struct value_type { - template void operator=(const T&) {} +template ::value)> +inline auto base_iterator(std::back_insert_iterator& it, + checked_ptr) + -> std::back_insert_iterator { + return it; +} + +template +constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { + return it; +} + +// is spectacularly slow to compile in C++20 so use a simple fill_n +// instead (#1998). +template +FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) + -> OutputIt { + for (Size i = 0; i < count; ++i) *out++ = value; + return out; +} +template +FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { + if (is_constant_evaluated()) { + return fill_n(out, count, value); + } + std::memset(out, value, to_unsigned(count)); + return out + count; +} + +#ifdef __cpp_char8_t +using char8_type = char8_t; +#else +enum char8_type : unsigned char {}; +#endif + +template +FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy_str(begin, end, out); +} + +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +/* Decode the next character, c, from s, reporting errors in e. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in e, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) + -> const char* { + constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr const int shiftc[] = {0, 18, 12, 6, 0}; + constexpr const int shifte[] = {0, 6, 4, 2, 0}; + + int len = code_point_length(s); + const char* next = s + len; + + // Assume a four-byte character and load four bytes. Unused bits are + // shifted out. + *c = uint32_t(s[0] & masks[len]) << 18; + *c |= uint32_t(s[1] & 0x3f) << 12; + *c |= uint32_t(s[2] & 0x3f) << 6; + *c |= uint32_t(s[3] & 0x3f) << 0; + *c >>= shiftc[len]; + + // Accumulate the various error conditions. + using uchar = unsigned char; + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (uchar(s[1]) & 0xc0) >> 2; + *e |= (uchar(s[2]) & 0xc0) >> 4; + *e |= uchar(s[3]) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +template +FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { + auto decode = [f](const char* p) { + auto cp = uint32_t(); + auto error = 0; + p = utf8_decode(p, &cp, &error); + f(cp, error); + return p; }; - - counting_iterator() : count_(0) {} - - size_t count() const { return count_; } - - counting_iterator& operator++() { - ++count_; - return *this; + 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); } - - counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; + 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; + do { + p = decode(p); + } while (p - buf < num_chars_left); } - - value_type operator*() const { return {}; } -}; - -template class truncating_iterator_base { - protected: - OutputIt out_; - size_t limit_; - size_t count_; - - truncating_iterator_base(OutputIt out, size_t limit) - : out_(out), limit_(limit), count_(0) {} - - public: - using iterator_category = std::output_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = void; - using pointer = void; - using reference = void; - using _Unchecked_type = - truncating_iterator_base; // Mark iterator as checked. - - OutputIt base() const { return out_; } - size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template ::value_type>::type> -class truncating_iterator; - -template -class truncating_iterator - : public truncating_iterator_base { - mutable typename truncating_iterator_base::value_type blackhole_; - - public: - using value_type = typename truncating_iterator_base::value_type; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) ++this->out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; - -template -class truncating_iterator - : public truncating_iterator_base { - public: - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - template truncating_iterator& operator=(T val) { - if (this->count_++ < this->limit_) *this->out_++ = val; - return *this; - } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; +} template -inline size_t count_code_points(basic_string_view s) { +inline auto compute_width(basic_string_view s) -> size_t { return s.size(); } -// Counts the number of code points in a UTF-8 string. -inline size_t count_code_points(basic_string_view s) { - const char* data = s.data(); +// Computes approximate display width of a UTF-8 string. +FMT_CONSTEXPR inline size_t compute_width(string_view s) { size_t num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80) ++num_code_points; - } + // 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 { + *count += detail::to_unsigned( + 1 + + (error == 0 && cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo init. consonants + cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET〈 + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET 〉 + // CJK ... Yi except Unicode Character “〿”: + (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || + (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables + (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs + (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms + (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms + (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms + (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms + (cp >= 0x20000 && cp <= 0x2fffd) || // CJK + (cp >= 0x30000 && cp <= 0x3fffd) || + // Miscellaneous Symbols and Pictographs + Emoticons: + (cp >= 0x1f300 && cp <= 0x1f64f) || + // Supplemental Symbols and Pictographs: + (cp >= 0x1f900 && cp <= 0x1f9ff)))); + } + }; + for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } -inline size_t count_code_points(basic_string_view s) { - return count_code_points(basic_string_view( +inline auto compute_width(basic_string_view s) -> size_t { + return compute_width(basic_string_view( reinterpret_cast(s.data()), s.size())); } template -inline size_t code_point_index(basic_string_view s, size_t n) { +inline auto code_point_index(basic_string_view s, size_t n) -> size_t { size_t size = s.size(); return n < size ? n : size; } // Calculates the index of the nth code point in a UTF-8 string. -inline size_t code_point_index(basic_string_view s, size_t n) { +inline auto code_point_index(basic_string_view s, size_t n) + -> size_t { const char8_type* data = s.data(); size_t num_code_points = 0; for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) { - return i; - } + if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; } return s.size(); } -template -using needs_conversion = bool_constant< - std::is_same::value_type, - char>::value && - std::is_same::value>; +template +using is_fast_float = bool_constant::is_iec559 && + sizeof(T) <= sizeof(double)>; -template ::value)> -OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { - return std::copy(begin, end, it); -} - -template ::value)> -OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { - return std::transform(begin, end, it, - [](char c) { return static_cast(c); }); -} - -#ifndef FMT_USE_GRISU -# define FMT_USE_GRISU 1 +#ifndef FMT_USE_FULL_CACHE_DRAGONBOX +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif -template constexpr bool use_grisu() { - return FMT_USE_GRISU && std::numeric_limits::is_iec559 && - sizeof(T) <= sizeof(double); -} - template template void buffer::append(const U* begin, const U* end) { - size_t new_size = size_ + to_unsigned(end - begin); - reserve(new_size); - std::uninitialized_copy(begin, end, - make_checked(ptr_ + size_, capacity_ - size_)); - size_ = new_size; + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); + size_ += count; + begin += count; + } } + +template +struct is_locale : std::false_type {}; +template +struct is_locale> : std::true_type {}; } // namespace detail +FMT_MODULE_EXPORT_BEGIN + // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. enum { inline_buffer_size = 500 }; @@ -584,15 +603,7 @@ 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 one of the following type aliases for common character types: - - +----------------+------------------------------+ - | Type | Definition | - +================+==============================+ - | memory_buffer | basic_memory_buffer | - +----------------+------------------------------+ - | wmemory_buffer | basic_memory_buffer | - +----------------+------------------------------+ + You can use the ```memory_buffer`` type alias for ``char`` instead. **Example**:: @@ -610,7 +621,7 @@ enum { inline_buffer_size = 500 }; */ template > -class basic_memory_buffer : public detail::buffer { +class basic_memory_buffer final : public detail::buffer { private: T store_[SIZE]; @@ -624,7 +635,7 @@ class basic_memory_buffer : public detail::buffer { } protected: - void grow(size_t size) FMT_OVERRIDE; + void grow(size_t size) final FMT_OVERRIDE; public: using value_type = T; @@ -634,7 +645,7 @@ class basic_memory_buffer : public detail::buffer { : alloc_(alloc) { this->set(store_, SIZE); } - ~basic_memory_buffer() FMT_OVERRIDE { deallocate(); } + ~basic_memory_buffer() { deallocate(); } private: // Move data from other to this buffer. @@ -669,7 +680,8 @@ class basic_memory_buffer : public detail::buffer { Moves the content of the other ``basic_memory_buffer`` object to this one. \endrst */ - basic_memory_buffer& operator=(basic_memory_buffer&& other) FMT_NOEXCEPT { + auto operator=(basic_memory_buffer&& other) FMT_NOEXCEPT + -> basic_memory_buffer& { FMT_ASSERT(this != &other, ""); deallocate(); move(other); @@ -677,7 +689,23 @@ class basic_memory_buffer : public detail::buffer { } // Returns a copy of the allocator associated with this buffer. - Allocator get_allocator() const { return alloc_; } + auto get_allocator() const -> Allocator { return alloc_; } + + /** + 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); } + + /** Increases the buffer capacity to *new_capacity*. */ + void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } + + // Directly append data into the buffer + using detail::buffer::append; + template + void append(const ContiguousRange& range) { + append(range.data(), range.data() + range.size()); + } }; template @@ -685,9 +713,13 @@ 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 + const size_t max_size = std::allocator_traits::max_size(alloc_); size_t old_capacity = this->capacity(); size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) new_capacity = size; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; T* old_data = this->data(); T* new_data = std::allocator_traits::allocate(alloc_, new_capacity); @@ -702,12 +734,15 @@ void basic_memory_buffer::grow(size_t size) { } using memory_buffer = basic_memory_buffer; -using wmemory_buffer = basic_memory_buffer; template struct is_contiguous> : std::true_type { }; +namespace detail { +FMT_API void print(std::FILE*, string_view); +} + /** A formatting error such as invalid format string. */ FMT_CLASS_API class FMT_API format_error : public std::runtime_error { @@ -719,10 +754,66 @@ 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; + ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT; }; -namespace detail { +/** + \rst + Constructs a `~fmt::format_arg_store` object that contains references + to arguments and can be implicitly converted to `~fmt::format_args`. + If ``fmt`` is a compile-time string then `make_args_checked` checks + its validity at compile time. + \endrst + */ +template > +FMT_INLINE auto make_args_checked(const S& fmt, + const remove_reference_t&... args) + -> format_arg_store, remove_reference_t...> { + static_assert( + detail::count<( + std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); + detail::check_format_string(fmt); + return {args...}; +} + +// compile-time support +namespace detail_exported { +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +template struct fixed_string { + constexpr fixed_string(const Char (&str)[N]) { + detail::copy_str(static_cast(str), + str + N, data); + } + Char data[N]{}; +}; +#endif + +// Converts a compile-time string to basic_string_view. +template +constexpr auto compile_string_to_view(const Char (&s)[N]) + -> basic_string_view { + // Remove trailing NUL character if needed. Won't be present if this is used + // with a raw character array (i.e. not defined as a string). + return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; +} +template +constexpr auto compile_string_to_view(detail::std_string_view s) + -> basic_string_view { + return {s.data(), s.size()}; +} +} // namespace detail_exported + +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 {}; template using is_signed = @@ -732,67 +823,78 @@ using is_signed = // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> -FMT_CONSTEXPR bool is_negative(T value) { +FMT_CONSTEXPR auto is_negative(T value) -> bool { return value < 0; } template ::value)> -FMT_CONSTEXPR bool is_negative(T) { +FMT_CONSTEXPR auto is_negative(T) -> bool { return false; } template ::value)> -FMT_CONSTEXPR bool is_supported_floating_point(T) { +FMT_CONSTEXPR auto is_supported_floating_point(T) -> uint16_t { return (std::is_same::value && FMT_USE_FLOAT) || (std::is_same::value && FMT_USE_DOUBLE) || (std::is_same::value && FMT_USE_LONG_DOUBLE); } // Smallest of uint32_t, uint64_t, uint128_t that is large enough to -// represent all values of T. +// represent all values of an integral type T. template using uint32_or_64_or_128_t = - conditional_t() <= 32, uint32_t, + conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, + uint32_t, conditional_t() <= 64, uint64_t, uint128_t>>; +template +using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ + (factor)*1000000, (factor)*10000000, (factor)*100000000, \ + (factor)*1000000000 // Static data is placed in this class template for the header-only config. -template struct FMT_EXTERN_TEMPLATE_API basic_data { - static const uint64_t powers_of_10_64[]; - static const uint32_t zero_or_powers_of_10_32[]; - static const uint64_t zero_or_powers_of_10_64[]; - static const uint64_t pow10_significands[]; - static const int16_t pow10_exponents[]; +template struct basic_data { + // log10(2) = 0x0.4d104d427de7fbcc... + static const uint64_t log10_2_significand = 0x4d104d427de7fbcc; + // GCC generates slightly better code for pairs than chars. - using digit_pair = char[2]; - static const digit_pair digits[]; - static const char hex_digits[]; - static const char foreground_color[]; - static const char background_color[]; - static const char reset_color[5]; - static const wchar_t wreset_color[5]; - static const char signs[]; - static const char left_padding_shifts[5]; - static const char right_padding_shifts[5]; + 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}; }; -#ifndef FMT_EXPORTED -FMT_EXTERN template struct basic_data; +#ifdef FMT_SHARED +// Required for -flto, -fivisibility=hidden and -shared to work +extern template struct basic_data; #endif // This is a struct rather than an alias to avoid shadowing warnings in gcc. struct data : basic_data<> {}; -#ifdef FMT_BUILTIN_CLZLL -// Returns the number of decimal digits in n. Leading zeros are not counted -// except for n == 0 in which case count_digits returns 1. -inline int count_digits(uint64_t n) { - // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 - // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. - int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; - return t - (n < data::zero_or_powers_of_10_64[t]) + 1; -} -#else -// Fallback version of count_digits used when __builtin_clz is not available. -inline int count_digits(uint64_t n) { +template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { int count = 1; for (;;) { // Integer division is slow so do it for a group of four digits instead @@ -806,27 +908,41 @@ inline int count_digits(uint64_t n) { count += 4; } } -#endif - #if FMT_USE_INT128 -inline int count_digits(uint128_t n) { - int count = 1; - for (;;) { - // Integer division is slow so do it for a group of four digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - if (n < 10) return count; - if (n < 100) return count + 1; - if (n < 1000) return count + 2; - if (n < 10000) return count + 3; - n /= 10000U; - count += 4; - } +FMT_CONSTEXPR inline auto count_digits(uint128_t n) -> int { + return count_digits_fallback(n); } #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]); + } +#endif + return count_digits_fallback(n); +} + // Counts the number of digits in n. BITS = log2(radix). -template inline int count_digits(UInt n) { +template +FMT_CONSTEXPR auto count_digits(UInt n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (num_bits() == 32) + return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; +#endif int num_digits = 0; do { ++num_digits; @@ -834,57 +950,82 @@ template inline int count_digits(UInt n) { return num_digits; } -template <> int count_digits<4>(detail::fallback_uintptr n); +template <> auto count_digits<4>(detail::fallback_uintptr n) -> int; -#if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) -#else -# define FMT_ALWAYS_INLINE -#endif - -#ifdef FMT_BUILTIN_CLZ -// Optional version of count_digits for better performance on 32-bit platforms. -inline int count_digits(uint32_t n) { - int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; - return t - (n < data::zero_or_powers_of_10_32[t]) + 1; +// 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) + 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 + FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 + FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 + FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k + FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k + FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k + FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M + FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M + FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M + FMT_INC(1000000000), FMT_INC(1000000000) // 4B + }; + return table[n]; } -#endif -template constexpr int digits10() FMT_NOEXCEPT { +// 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); + } +#endif + return count_digits_fallback(n); +} + +template constexpr auto digits10() FMT_NOEXCEPT -> int { return std::numeric_limits::digits10; } -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } - -template FMT_API std::string grouping_impl(locale_ref loc); -template inline std::string grouping(locale_ref loc) { - return grouping_impl(loc); +template <> constexpr auto digits10() FMT_NOEXCEPT -> int { + return 38; } -template <> inline std::string grouping(locale_ref loc) { - return grouping_impl(loc); +template <> constexpr auto digits10() FMT_NOEXCEPT -> int { + return 38; } -template FMT_API Char thousands_sep_impl(locale_ref loc); -template inline Char thousands_sep(locale_ref loc) { - return Char(thousands_sep_impl(loc)); +template struct thousands_sep_result { + std::string grouping; + Char thousands_sep; +}; + +template +FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; +template +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + auto result = thousands_sep_impl(loc); + return {result.grouping, Char(result.thousands_sep)}; } -template <> inline wchar_t thousands_sep(locale_ref loc) { +template <> +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { return thousands_sep_impl(loc); } -template FMT_API Char decimal_point_impl(locale_ref loc); -template inline Char decimal_point(locale_ref loc) { +template +FMT_API auto decimal_point_impl(locale_ref loc) -> Char; +template inline auto decimal_point(locale_ref loc) -> Char { return Char(decimal_point_impl(loc)); } -template <> inline wchar_t decimal_point(locale_ref loc) { +template <> inline auto decimal_point(locale_ref loc) -> wchar_t { return decimal_point_impl(loc); } // Compares two characters for equality. -template bool equal2(const Char* lhs, const char* rhs) { +template auto equal2(const Char* lhs, const char* rhs) -> bool { return lhs[0] == rhs[0] && lhs[1] == rhs[1]; } -inline bool equal2(const char* lhs, const char* rhs) { +inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } @@ -893,7 +1034,7 @@ template void copy2(Char* dst, const char* src) { *dst++ = static_cast(*src++); *dst = static_cast(*src); } -inline void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } +FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } template struct format_decimal_result { Iterator begin; @@ -904,11 +1045,19 @@ template struct format_decimal_result { // buffer of specified size. The caller must ensure that the buffer is large // enough. template -inline format_decimal_result format_decimal(Char* out, UInt value, - int size) { +FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) + -> format_decimal_result { 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 @@ -928,18 +1077,17 @@ inline format_decimal_result format_decimal(Char* out, UInt value, template >::value)> -inline format_decimal_result format_decimal(Iterator out, UInt value, - int num_digits) { - // Buffer should be large enough to hold all digits (<= digits10 + 1). - enum { max_size = digits10() + 1 }; - Char buffer[2 * max_size]; - auto end = format_decimal(buffer, value, num_digits).end; - return {out, detail::copy_str(buffer, end, out)}; +inline auto format_decimal(Iterator out, UInt value, int size) + -> format_decimal_result { + // Buffer is large enough to hold all digits (digits10 + 1). + Char buffer[digits10() + 1]; + auto end = format_decimal(buffer, value, size).end; + return {out, detail::copy_str_noinline(buffer, end, out)}; } template -inline Char* format_uint(Char* buffer, UInt value, int num_digits, - bool upper = false) { +FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, + bool upper = false) -> Char* { buffer += num_digits; Char* end = buffer; do { @@ -952,8 +1100,8 @@ inline Char* format_uint(Char* buffer, UInt value, int num_digits, } template -Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, - bool = false) { +auto format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, + bool = false) -> Char* { auto char_digits = std::numeric_limits::digits / 4; int start = (num_digits + char_digits - 1) / char_digits - 1; if (int start_digits = num_digits % char_digits) { @@ -974,120 +1122,109 @@ Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, } template -inline It format_uint(It out, UInt value, int num_digits, bool upper = false) { +inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) + -> It { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + format_uint(ptr, value, num_digits, upper); + return out; + } // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). char buffer[num_bits() / BASE_BITS + 1]; format_uint(buffer, value, num_digits, upper); - return detail::copy_str(buffer, buffer + num_digits, out); + return detail::copy_str_noinline(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. class utf8_to_utf16 { private: - wmemory_buffer buffer_; + basic_memory_buffer buffer_; public: FMT_API explicit utf8_to_utf16(string_view s); - operator wstring_view() const { return {&buffer_[0], size()}; } - size_t size() const { return buffer_.size() - 1; } - const wchar_t* c_str() const { return &buffer_[0]; } - std::wstring str() const { return {&buffer_[0], size()}; } + operator basic_string_view() const { return {&buffer_[0], size()}; } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const wchar_t* { return &buffer_[0]; } + auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; -template struct null {}; +namespace dragonbox { -// Workaround an array initialization issue in gcc 4.8. -template struct fill_t { - private: - enum { max_size = 4 }; - Char data_[max_size]; - unsigned char size_; +// Type-specific information that Dragonbox uses. +template struct float_info; - public: - FMT_CONSTEXPR void operator=(basic_string_view s) { - auto size = s.size(); - if (size > max_size) { - FMT_THROW(format_error("invalid fill")); - return; - } - for (size_t i = 0; i < size; ++i) data_[i] = s[i]; - size_ = static_cast(size); - } - - size_t size() const { return size_; } - const Char* data() const { return data_; } - - FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; } - FMT_CONSTEXPR const Char& operator[](size_t index) const { - return data_[index]; - } - - static FMT_CONSTEXPR fill_t make() { - auto fill = fill_t(); - fill[0] = Char(' '); - fill.size_ = 1; - return fill; - } +template <> struct float_info { + using carrier_uint = uint32_t; + static const int significand_bits = 23; + static const int exponent_bits = 8; + static const int min_exponent = -126; + static const int max_exponent = 127; + static const int exponent_bias = -127; + static const int decimal_digits = 9; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int cache_bits = 64; + static const int divisibility_check_by_5_threshold = 39; + static const int case_fc_pm_half_lower_threshold = -1; + static const int case_fc_pm_half_upper_threshold = 6; + static const int case_fc_lower_threshold = -2; + static const int case_fc_upper_threshold = 6; + static const int case_shorter_interval_left_endpoint_lower_threshold = 2; + static const int case_shorter_interval_left_endpoint_upper_threshold = 3; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; + static const int max_trailing_zeros = 7; }; -} // namespace detail -// We cannot use enum classes as bit fields because of a gcc bug -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. -namespace align { -enum type { none, left, right, center, numeric }; +template <> struct float_info { + using carrier_uint = uint64_t; + static const int significand_bits = 52; + static const int exponent_bits = 11; + static const int min_exponent = -1022; + static const int max_exponent = 1023; + static const int exponent_bias = -1023; + static const int decimal_digits = 17; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 326; + static const int cache_bits = 128; + static const int divisibility_check_by_5_threshold = 86; + static const int case_fc_pm_half_lower_threshold = -2; + static const int case_fc_pm_half_upper_threshold = 9; + static const int case_fc_lower_threshold = -4; + static const int case_fc_upper_threshold = 9; + static const int case_shorter_interval_left_endpoint_lower_threshold = 2; + static const int case_shorter_interval_left_endpoint_upper_threshold = 3; + static const int shorter_interval_tie_lower_threshold = -77; + static const int shorter_interval_tie_upper_threshold = -77; + static const int max_trailing_zeros = 16; +}; + +template struct decimal_fp { + using significand_type = typename float_info::carrier_uint; + significand_type significand; + int exponent; +}; + +template +FMT_API auto to_decimal(T x) FMT_NOEXCEPT -> decimal_fp; +} // namespace dragonbox + +template +constexpr auto exponent_mask() -> + typename dragonbox::float_info::carrier_uint { + using uint = typename dragonbox::float_info::carrier_uint; + return ((uint(1) << dragonbox::float_info::exponent_bits) - 1) + << dragonbox::float_info::significand_bits; } -using align_t = align::type; - -namespace sign { -enum type { none, minus, plus, space }; -} -using sign_t = sign::type; - -// Format specifiers for built-in and string types. -template struct basic_format_specs { - int width; - int precision; - char type; - align_t align : 4; - sign_t sign : 3; - bool alt : 1; // Alternate form ('#'). - detail::fill_t fill; - - constexpr basic_format_specs() - : width(0), - precision(-1), - type(0), - align(align::none), - sign(sign::none), - alt(false), - fill(detail::fill_t::make()) {} -}; - -using format_specs = basic_format_specs; - -namespace detail { - -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex -}; - -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool use_grisu : 1; - bool showpoint : 1; -}; // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. -template It write_exponent(int exp, It it) { +template +auto write_exponent(int exp, It it) -> It { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { *it++ = static_cast('-'); @@ -1107,281 +1244,28 @@ template It write_exponent(int exp, It it) { return it; } -template class float_writer { - private: - // The number is given as v = digits_ * pow(10, exp_). - const char* digits_; - int num_digits_; - int exp_; - size_t size_; - float_specs specs_; - Char decimal_point_; - - template It prettify(It it) const { - // pow(10, full_exp - 1) <= v <= pow(10, full_exp). - int full_exp = num_digits_ + exp_; - if (specs_.format == float_format::exp) { - // Insert a decimal point after the first digit and add an exponent. - *it++ = static_cast(*digits_); - int num_zeros = specs_.precision - num_digits_; - if (num_digits_ > 1 || specs_.showpoint) *it++ = decimal_point_; - it = copy_str(digits_ + 1, digits_ + num_digits_, it); - if (num_zeros > 0 && specs_.showpoint) - it = std::fill_n(it, num_zeros, static_cast('0')); - *it++ = static_cast(specs_.upper ? 'E' : 'e'); - return write_exponent(full_exp - 1, it); - } - if (num_digits_ <= full_exp) { - // 1234e7 -> 12340000000[.0+] - it = copy_str(digits_, digits_ + num_digits_, it); - it = std::fill_n(it, full_exp - num_digits_, static_cast('0')); - if (specs_.showpoint || specs_.precision < 0) { - *it++ = decimal_point_; - int num_zeros = specs_.precision - full_exp; - if (num_zeros <= 0) { - if (specs_.format != float_format::fixed) - *it++ = static_cast('0'); - return it; - } -#ifdef FMT_FUZZ - if (num_zeros > 5000) - throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); -#endif - it = std::fill_n(it, num_zeros, static_cast('0')); - } - } else if (full_exp > 0) { - // 1234e-2 -> 12.34[0+] - it = copy_str(digits_, digits_ + full_exp, it); - if (!specs_.showpoint) { - // Remove trailing zeros. - int num_digits = num_digits_; - while (num_digits > full_exp && digits_[num_digits - 1] == '0') - --num_digits; - if (num_digits != full_exp) *it++ = decimal_point_; - return copy_str(digits_ + full_exp, digits_ + num_digits, it); - } - *it++ = decimal_point_; - it = copy_str(digits_ + full_exp, digits_ + num_digits_, it); - if (specs_.precision > num_digits_) { - // Add trailing zeros. - int num_zeros = specs_.precision - num_digits_; - it = std::fill_n(it, num_zeros, static_cast('0')); - } - } else { - // 1234e-6 -> 0.001234 - *it++ = static_cast('0'); - int num_zeros = -full_exp; - int num_digits = num_digits_; - if (num_digits == 0 && specs_.precision >= 0 && - specs_.precision < num_zeros) { - num_zeros = specs_.precision; - } - // Remove trailing zeros. - if (!specs_.showpoint) - while (num_digits > 0 && digits_[num_digits - 1] == '0') --num_digits; - if (num_zeros != 0 || num_digits != 0 || specs_.showpoint) { - *it++ = decimal_point_; - it = std::fill_n(it, num_zeros, static_cast('0')); - it = copy_str(digits_, digits_ + num_digits, it); - } - } - return it; - } - - public: - float_writer(const char* digits, int num_digits, int exp, float_specs specs, - Char decimal_point) - : digits_(digits), - num_digits_(num_digits), - exp_(exp), - specs_(specs), - decimal_point_(decimal_point) { - int full_exp = num_digits + exp - 1; - int precision = specs.precision > 0 ? specs.precision : 16; - if (specs_.format == float_format::general && - !(full_exp >= -4 && full_exp < precision)) { - specs_.format = float_format::exp; - } - size_ = prettify(counting_iterator()).count(); - size_ += specs.sign ? 1 : 0; - } - - size_t size() const { return size_; } - - template It operator()(It it) const { - if (specs_.sign) *it++ = static_cast(data::signs[specs_.sign]); - return prettify(it); - } -}; - template -int format_float(T value, int precision, float_specs specs, buffer& buf); +auto format_float(T value, int precision, float_specs specs, buffer& buf) + -> int; // Formats a floating-point number with snprintf. template -int snprintf_float(T value, int precision, float_specs specs, - buffer& buf); +auto snprintf_float(T value, int precision, float_specs specs, + buffer& buf) -> int; -template T promote_float(T value) { return value; } -inline double promote_float(float value) { return static_cast(value); } - -template -FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) { - switch (spec) { - case 0: - case 'd': - handler.on_dec(); - break; - case 'x': - case 'X': - handler.on_hex(); - break; - case 'b': - case 'B': - handler.on_bin(); - break; - case 'o': - handler.on_oct(); - break; -#ifdef FMT_DEPRECATED_N_SPECIFIER - case 'n': -#endif - case 'L': - handler.on_num(); - break; - case 'c': - handler.on_chr(); - break; - default: - handler.on_error(); - } +template auto promote_float(T value) -> T { return value; } +inline auto promote_float(float value) -> double { + return static_cast(value); } -template -FMT_CONSTEXPR float_specs parse_float_type_spec( - const basic_format_specs& specs, ErrorHandler&& eh = {}) { - auto result = float_specs(); - result.showpoint = specs.alt; - switch (specs.type) { - case 0: - result.format = float_format::general; - result.showpoint |= specs.precision > 0; - break; - case 'G': - result.upper = true; - FMT_FALLTHROUGH; - case 'g': - result.format = float_format::general; - break; - case 'E': - result.upper = true; - FMT_FALLTHROUGH; - case 'e': - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case 'F': - result.upper = true; - FMT_FALLTHROUGH; - case 'f': - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case 'A': - result.upper = true; - FMT_FALLTHROUGH; - case 'a': - result.format = float_format::hex; - break; -#ifdef FMT_DEPRECATED_N_SPECIFIER - case 'n': -#endif - case 'L': - result.locale = true; - break; - default: - eh.on_error("invalid type specifier"); - break; - } - return result; -} - -template -FMT_CONSTEXPR void handle_char_specs(const basic_format_specs* specs, - Handler&& handler) { - if (!specs) return handler.on_char(); - if (specs->type && specs->type != 'c') return handler.on_int(); - if (specs->align == align::numeric || specs->sign != sign::none || specs->alt) - handler.on_error("invalid format specifier for char"); - handler.on_char(); -} - -template -FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler&& handler) { - if (spec == 0 || spec == 's') - handler.on_string(); - else if (spec == 'p') - handler.on_pointer(); - else - handler.on_error("invalid type specifier"); -} - -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_pointer_type_spec(Char spec, ErrorHandler&& eh) { - if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier"); -} - -template class int_type_checker : private ErrorHandler { - public: - FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_dec() {} - FMT_CONSTEXPR void on_hex() {} - FMT_CONSTEXPR void on_bin() {} - FMT_CONSTEXPR void on_oct() {} - FMT_CONSTEXPR void on_num() {} - FMT_CONSTEXPR void on_chr() {} - - FMT_CONSTEXPR void on_error() { - ErrorHandler::on_error("invalid type specifier"); - } -}; - -template -class char_specs_checker : public ErrorHandler { - private: - char type_; - - public: - FMT_CONSTEXPR char_specs_checker(char type, ErrorHandler eh) - : ErrorHandler(eh), type_(type) {} - - FMT_CONSTEXPR void on_int() { - handle_int_type_spec(type_, int_type_checker(*this)); - } - FMT_CONSTEXPR void on_char() {} -}; - -template -class cstring_type_checker : public ErrorHandler { - public: - FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) - : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_string() {} - FMT_CONSTEXPR void on_pointer() {} -}; - template -FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, + const fill_t& fill) -> OutputIt { auto fill_size = fill.size(); - if (fill_size == 1) return std::fill_n(it, n, fill[0]); - for (size_t i = 0; i < n; ++i) it = std::copy_n(fill.data(), fill_size, it); + if (fill_size == 1) return detail::fill_n(it, n, fill[0]); + auto data = fill.data(); + for (size_t i = 0; i < n; ++i) + it = copy_str(data, data + fill_size, it); return it; } @@ -1390,39 +1274,72 @@ FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { // width: output display width in (terminal) column positions. template -inline OutputIt write_padded(OutputIt out, - const basic_format_specs& specs, size_t size, - size_t width, const F& f) { +FMT_CONSTEXPR auto write_padded(OutputIt out, + const basic_format_specs& specs, + size_t size, size_t width, F&& f) -> OutputIt { 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; size_t left_padding = padding >> shifts[specs.align]; + size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); - it = fill(it, left_padding, specs.fill); + if (left_padding != 0) it = fill(it, left_padding, specs.fill); it = f(it); - it = fill(it, padding - left_padding, specs.fill); + if (right_padding != 0) it = fill(it, right_padding, specs.fill); return base_iterator(out, it); } template -inline OutputIt write_padded(OutputIt out, - const basic_format_specs& specs, size_t size, - const F& f) { +constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, + size_t size, F&& f) -> OutputIt { return write_padded(out, specs, size, size, f); } +template +FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, + const basic_format_specs& specs) + -> OutputIt { + return write_padded( + out, specs, bytes.size(), [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy_str(data, data + bytes.size(), it); + }); +} + +template +auto write_ptr(OutputIt out, UIntPtr value, + const basic_format_specs* specs) -> OutputIt { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_uint<4, Char>(it, value, num_digits); + }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + template -OutputIt write_bytes(OutputIt out, string_view bytes, - const basic_format_specs& specs) { - using iterator = remove_reference_t; - return write_padded(out, specs, bytes.size(), [bytes](iterator it) { - const char* data = bytes.data(); - return copy_str(data, data + bytes.size(), it); +FMT_CONSTEXPR auto write_char(OutputIt out, Char value, + const basic_format_specs& specs) + -> OutputIt { + return write_padded(out, specs, 1, [=](reserve_iterator it) { + *it++ = value; + return it; }); } +template +FMT_CONSTEXPR auto write(OutputIt out, Char value, + const basic_format_specs& specs, + locale_ref loc = {}) -> OutputIt { + return check_char_specs(specs) + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); +} // Data for write_int that doesn't depend on output iterator type. It is used to // avoid template code bloat. @@ -1430,9 +1347,9 @@ template struct write_int_data { size_t size; size_t padding; - write_int_data(int num_digits, string_view prefix, - const basic_format_specs& specs) - : size(prefix.size() + to_unsigned(num_digits)), padding(0) { + FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, + const basic_format_specs& specs) + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { @@ -1440,7 +1357,7 @@ template struct write_int_data { size = width; } } else if (specs.precision > num_digits) { - size = prefix.size() + to_unsigned(specs.precision); + size = (prefix >> 24) + to_unsigned(specs.precision); padding = to_unsigned(specs.precision - num_digits); } } @@ -1448,190 +1365,407 @@ template struct write_int_data { // Writes an integer in the format // -// where are written by f(it). -template -OutputIt write_int(OutputIt out, int num_digits, string_view prefix, - const basic_format_specs& specs, F f) { - auto data = write_int_data(num_digits, prefix, specs); - using iterator = remove_reference_t; - return write_padded(out, specs, data.size, [=](iterator it) { - if (prefix.size() != 0) - it = copy_str(prefix.begin(), prefix.end(), it); - it = std::fill_n(it, data.padding, static_cast('0')); - return f(it); - }); -} - -template -OutputIt write(OutputIt out, basic_string_view s, - const basic_format_specs& specs) { - auto data = s.data(); - auto size = s.size(); - if (specs.precision >= 0 && to_unsigned(specs.precision) < size) - size = code_point_index(s, to_unsigned(specs.precision)); - auto width = specs.width != 0 - ? count_code_points(basic_string_view(data, size)) - : 0; - using iterator = remove_reference_t; - return write_padded(out, specs, size, width, [=](iterator it) { - return copy_str(data, data + size, it); - }); -} - -// The handle_int_type_spec handler that writes an integer. -template struct int_writer { - OutputIt out; - locale_ref locale; - const basic_format_specs& specs; - UInt abs_value; - char prefix[4]; - unsigned prefix_size; - - using iterator = - remove_reference_t(), 0))>; - - string_view get_prefix() const { return string_view(prefix, prefix_size); } - - template - int_writer(OutputIt output, locale_ref loc, Int value, - const basic_format_specs& s) - : out(output), - locale(loc), - specs(s), - abs_value(static_cast(value)), - prefix_size(0) { - static_assert(std::is_same, UInt>::value, ""); - if (is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (specs.sign != sign::none && specs.sign != sign::minus) { - prefix[0] = specs.sign == sign::plus ? '+' : ' '; - ++prefix_size; +// where are written by write_digits(it). +// prefix contains chars in three lower bytes and the size in the fourth byte. +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, + unsigned prefix, + const basic_format_specs& specs, + W write_digits) -> OutputIt { + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + if (prefix != 0) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); } + return base_iterator(out, write_digits(it)); } + auto data = write_int_data(num_digits, prefix, specs); + return write_padded( + out, specs, data.size, [=](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + it = detail::fill_n(it, data.padding, static_cast('0')); + return write_digits(it); + }); +} - void on_dec() { +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); + }); + return true; +} + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +template struct write_int_arg { + UInt abs_value; + unsigned prefix; +}; + +template +FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) + -> write_int_arg> { + auto prefix = 0u; + auto abs_value = static_cast>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + prefix = data::prefixes[sign]; + } + return {abs_value, prefix}; +} + +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, + const basic_format_specs& specs, + locale_ref loc) -> OutputIt { + 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': { + if (specs.localized && + write_int_localized(out, static_cast>(abs_value), + prefix, specs, loc)) { + return out; + } auto num_digits = count_digits(abs_value); - out = write_int( - out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) { + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_decimal(it, abs_value, num_digits).end; }); } - - void on_hex() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = specs.type; - } + case 'x': + case 'X': { + if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); + bool upper = specs.type != 'x'; int num_digits = count_digits<4>(abs_value); - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<4, Char>(it, abs_value, num_digits, - specs.type != 'x'); - }); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<4, Char>(it, abs_value, num_digits, upper); + }); } - - void on_bin() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast(specs.type); - } + case 'b': + case 'B': { + if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); int num_digits = count_digits<1>(abs_value); - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<1, Char>(it, abs_value, num_digits); - }); + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); } - - void on_oct() { + case 'o': { 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. - prefix[prefix_size++] = '0'; + prefix_append(prefix, '0'); } - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<3, Char>(it, abs_value, num_digits); - }); + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); } - - enum { sep_size = 1 }; - - void on_num() { - std::string groups = grouping(locale); - if (groups.empty()) return on_dec(); - auto sep = thousands_sep(locale); - if (!sep) return on_dec(); - int num_digits = count_digits(abs_value); - int size = num_digits, n = num_digits; - 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, abs_value, num_digits); - basic_memory_buffer buffer; - size += prefix_size; - buffer.resize(size); - basic_string_view s(&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; - 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; - } - p -= s.size(); - std::uninitialized_copy(s.data(), s.data() + s.size(), - make_checked(p, s.size())); - } - if (prefix_size != 0) p[-1] = static_cast('-'); - using iterator = remove_reference_t; - auto data = buffer.data(); - out = write_padded(out, specs, size, size, [=](iterator it) { - return copy_str(data, data + size, it); - }); - } - - void on_chr() { *out++ = static_cast(abs_value); } - - FMT_NORETURN void on_error() { + case 'c': + return write_char(out, static_cast(abs_value), specs); + default: FMT_THROW(format_error("invalid type specifier")); } -}; + return out; +} +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); +} +// An inlined version of write used in format string compilation. +template ::value && + !std::is_same::value && + !std::is_same>::value)> +FMT_CONSTEXPR FMT_INLINE 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); +} template -OutputIt write_nonfinite(OutputIt out, bool isinf, - const basic_format_specs& specs, - const float_specs& fspecs) { +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const basic_format_specs& specs) -> OutputIt { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + auto width = + specs.width != 0 ? compute_width(basic_string_view(data, size)) : 0; + return write_padded(out, specs, size, width, + [=](reserve_iterator it) { + return copy_str(data, data + size, it); + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, + basic_string_view> s, + const basic_format_specs& specs, locale_ref) + -> OutputIt { + return write(out, s, specs); +} +template +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, + const basic_format_specs& specs, locale_ref) + -> OutputIt { + return check_cstring_type_spec(specs.type) + ? write(out, basic_string_view(s), specs, {}) + : write_ptr(out, to_uintptr(s), &specs); +} + +template +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; auto sign = fspecs.sign; auto size = str_size + (sign ? 1 : 0); - using iterator = remove_reference_t; - return write_padded(out, specs, size, [=](iterator it) { + // Replace '0'-padding with space for non-finite values. + const bool is_zero_fill = + 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]); return copy_str(str, str + str_size, it); }); } +// A decimal floating-point number significand * pow(10, exp). +struct big_decimal_fp { + const char* significand; + int significand_size; + int exponent; +}; + +inline auto get_significand_size(const big_decimal_fp& fp) -> int { + return fp.significand_size; +} +template +inline auto get_significand_size(const dragonbox::decimal_fp& fp) -> int { + return count_digits(fp.significand); +} + +template +inline auto write_significand(OutputIt out, const char* significand, + int& significand_size) -> OutputIt { + return copy_str(significand, significand + significand_size, out); +} +template +inline auto write_significand(OutputIt out, UInt significand, + int significand_size) -> OutputIt { + return format_decimal(out, significand, significand_size).end; +} + +template ::value)> +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[integral_size] = decimal_point; + return end; +} + +template >::value)> +inline auto write_significand(OutputIt out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. + Char buffer[digits10() + 2]; + auto end = write_significand(buffer, significand, significand_size, + integral_size, decimal_point); + return detail::copy_str_noinline(buffer, end, out); +} + +template +inline 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; + *out++ = decimal_point; + return detail::copy_str_noinline(significand + integral_size, + 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 { + auto significand = fp.significand; + int significand_size = get_significand_size(fp); + static const Char zero = static_cast('0'); + auto sign = fspecs.sign; + size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); + using iterator = reserve_iterator; + + int output_exp = fp.exponent + significand_size - 1; + auto use_exp_format = [=]() { + if (fspecs.format == float_format::exp) return true; + if (fspecs.format != float_format::general) return false; + // Use the fixed notation if the exponent is in [exp_lower, exp_upper), + // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. + const int exp_lower = -4, exp_upper = 16; + return output_exp < exp_lower || + output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); + }; + if (use_exp_format()) { + int num_zeros = 0; + if (fspecs.showpoint) { + num_zeros = fspecs.precision - significand_size; + if (num_zeros < 0) num_zeros = 0; + size += to_unsigned(num_zeros); + } else if (significand_size == 1) { + decimal_point = Char(); + } + auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; + int exp_digits = 2; + if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; + + 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]); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, significand, significand_size, 1, + decimal_point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); + *it++ = static_cast(exp_char); + return write_exponent(output_exp, it); + }; + return specs.width > 0 ? write_padded(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); + } + + int exp = fp.exponent + significand_size; + if (fp.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += to_unsigned(fp.exponent); + int num_zeros = fspecs.precision - exp; +#ifdef FMT_FUZZ + if (num_zeros > 5000) + throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); +#endif + if (fspecs.showpoint) { + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; + if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; + } + 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 (!fspecs.showpoint) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } else if (exp > 0) { + // 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); + return write_padded(out, specs, size, [&](iterator it) { + if (sign) *it++ = static_cast(data::signs[sign]); + it = write_significand(it, significand, significand_size, exp, + decimal_point); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && fspecs.precision >= 0 && + fspecs.precision < num_zeros) { + num_zeros = fspecs.precision; + } + 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]); + *it++ = zero; + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, zero); + return write_significand(it, significand, significand_size); + }); +} + template ::value)> -OutputIt write(OutputIt out, T value, basic_format_specs specs, - locale_ref loc = {}) { +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; @@ -1657,7 +1791,8 @@ OutputIt write(OutputIt out, T value, basic_format_specs specs, if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); snprintf_float(promote_float(value), specs.precision, fspecs, buffer); - return write_bytes(out, {buffer.data(), buffer.size()}, specs); + return write_bytes(out, {buffer.data(), buffer.size()}, + specs); } int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; if (fspecs.format == float_format::exp) { @@ -1667,121 +1802,123 @@ OutputIt write(OutputIt out, T value, basic_format_specs specs, ++precision; } if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = use_grisu(); + fspecs.use_grisu = is_fast_float(); int exp = format_float(promote_float(value), precision, fspecs, buffer); fspecs.precision = precision; Char point = fspecs.locale ? decimal_point(loc) : static_cast('.'); - float_writer w(buffer.data(), static_cast(buffer.size()), exp, - fspecs, point); - return write_padded(out, specs, w.size(), w); + auto fp = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; + return write_float(out, fp, specs, fspecs, point); } template ::value)> -OutputIt write(OutputIt out, T value) { + FMT_ENABLE_IF(is_fast_float::value)> +auto write(OutputIt out, T value) -> OutputIt { if (const_check(!is_supported_floating_point(value))) return out; + + using floaty = conditional_t::value, double, T>; + using uint = typename dragonbox::float_info::carrier_uint; + auto bits = bit_cast(value); + auto fspecs = float_specs(); - if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. + auto sign_bit = bits & (uint(1) << (num_bits() - 1)); + if (sign_bit != 0) { fspecs.sign = sign::minus; value = -value; } - auto specs = basic_format_specs(); - if (!std::isfinite(value)) + static const auto specs = basic_format_specs(); + uint mask = exponent_mask(); + if ((bits & mask) == mask) return write_nonfinite(out, std::isinf(value), specs, fspecs); - memory_buffer buffer; - int precision = -1; - if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = use_grisu(); - int exp = format_float(promote_float(value), precision, fspecs, buffer); - fspecs.precision = precision; - float_writer w(buffer.data(), static_cast(buffer.size()), exp, - fspecs, static_cast('.')); - return base_iterator(out, w(reserve(out, w.size()))); + auto dec = dragonbox::to_decimal(static_cast(value)); + return write_float(out, dec, specs, fspecs, static_cast('.')); +} + +template ::value && + !is_fast_float::value)> +inline auto write(OutputIt out, T value) -> OutputIt { + return write(out, value, basic_format_specs()); } template -OutputIt write_char(OutputIt out, Char value, - const basic_format_specs& specs) { - using iterator = remove_reference_t; - return write_padded(out, specs, 1, [=](iterator it) { - *it++ = value; - return it; - }); -} - -template -OutputIt write_ptr(OutputIt out, UIntPtr value, - const basic_format_specs* specs) { - int num_digits = count_digits<4>(value); - auto size = to_unsigned(num_digits) + size_t(2); - using iterator = remove_reference_t; - auto write = [=](iterator it) { - *it++ = static_cast('0'); - *it++ = static_cast('x'); - return format_uint<4, Char>(it, value, num_digits); - }; - return specs ? write_padded(out, *specs, size, write) - : base_iterator(out, write(reserve(out, size))); -} - -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -OutputIt write(OutputIt out, monostate) { +auto write(OutputIt out, monostate, basic_format_specs = {}, + locale_ref = {}) -> OutputIt { FMT_ASSERT(false, ""); return out; } -template ::value)> -OutputIt write(OutputIt out, string_view value) { +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) + -> OutputIt { auto it = reserve(out, value.size()); - it = copy_str(value.begin(), value.end(), it); + it = copy_str_noinline(value.begin(), value.end(), it); return base_iterator(out, it); } -template -OutputIt write(OutputIt out, basic_string_view value) { - auto it = reserve(out, value.size()); - it = std::copy(value.begin(), value.end(), it); - return base_iterator(out, it); +template ::value)> +constexpr auto write(OutputIt out, const T& value) -> OutputIt { + return write(out, to_string_view(value)); } template ::value && !std::is_same::value && !std::is_same::value)> -OutputIt write(OutputIt out, T value) { +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { auto abs_value = static_cast>(value); bool negative = is_negative(value); // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. if (negative) abs_value = ~abs_value + 1; int num_digits = count_digits(abs_value); - auto it = reserve(out, (negative ? 1 : 0) + static_cast(num_digits)); + auto size = (negative ? 1 : 0) + static_cast(num_digits); + auto it = reserve(out, size); + if (auto ptr = to_pointer(it, size)) { + if (negative) *ptr++ = static_cast('-'); + format_decimal(ptr, abs_value, num_digits); + return out; + } if (negative) *it++ = static_cast('-'); it = format_decimal(it, abs_value, num_digits).end; return base_iterator(out, it); } -template -OutputIt write(OutputIt out, bool value) { - return write(out, string_view(value ? "true" : "false")); +// FMT_ENABLE_IF() condition separated to workaround MSVC bug +template < + typename Char, typename OutputIt, typename T, + bool check = + std::is_enum::value && !std::is_same::value && + mapped_type_constant>::value != + type::custom_type, + FMT_ENABLE_IF(check)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + return write( + out, static_cast::type>(value)); +} + +template ::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value, + const basic_format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return specs.type && specs.type != 's' + ? write(out, value ? 1 : 0, specs, {}) + : write_bytes(out, value ? "true" : "false", specs); } template -OutputIt write(OutputIt out, Char value) { +FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { auto it = reserve(out, 1); *it++ = value; return base_iterator(out, it); } template -OutputIt write(OutputIt out, const Char* value) { +FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) + -> OutputIt { if (!value) { FMT_THROW(format_error("string pointer is null")); } else { @@ -1791,268 +1928,79 @@ OutputIt write(OutputIt out, const Char* value) { return out; } -template -OutputIt write(OutputIt out, const void* value) { - return write_ptr(out, to_uintptr(value), nullptr); +template ::value)> +auto write(OutputIt out, const T* value, + const basic_format_specs& specs = {}, locale_ref = {}) + -> OutputIt { + check_pointer_type_spec(specs.type, error_handler()); + return write_ptr(out, to_uintptr(value), &specs); } template -auto write(OutputIt out, const T& value) -> typename std::enable_if< - mapped_type_constant>::value == - type::custom_type, - OutputIt>::type { - basic_format_context ctx(out, {}, {}); - return formatter().format(value, ctx); +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; + using formatter_type = + conditional_t::value, + typename context_type::template formatter_type, + fallback_formatter>; + context_type ctx(out, {}, {}); + return formatter_type().format(value, ctx); } // An argument visitor that formats the argument and writes it via the output // iterator. It's a class and not a generic lambda for compatibility with C++11. -template struct default_arg_formatter { - using context = basic_format_context; +template struct default_arg_formatter { + using iterator = buffer_appender; + using context = buffer_context; - OutputIt out; + iterator out; basic_format_args args; locale_ref loc; - template OutputIt operator()(T value) { + template auto operator()(T value) -> iterator { return write(out, value); } - - OutputIt operator()(typename basic_format_arg::handle handle) { + auto operator()(typename basic_format_arg::handle h) -> iterator { basic_format_parse_context parse_ctx({}); - basic_format_context format_ctx(out, args, loc); - handle.format(parse_ctx, format_ctx); + context format_ctx(out, args, loc); + h.format(parse_ctx, format_ctx); return format_ctx.out(); } }; -template -class arg_formatter_base { - public: - using iterator = OutputIt; - using char_type = Char; - using format_specs = basic_format_specs; +template struct arg_formatter { + using iterator = buffer_appender; + using context = buffer_context; - private: - iterator out_; - locale_ref locale_; - format_specs* specs_; + iterator out; + const basic_format_specs& specs; + locale_ref locale; - // Attempts to reserve space for n extra characters in the output range. - // Returns a pointer to the reserved range or a reference to out_. - auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) { - return detail::reserve(out_, n); + template + FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { + return detail::write(out, value, specs, locale); } - - using reserve_iterator = remove_reference_t(), 0))>; - - template void write_int(T value, const format_specs& spec) { - using uint_type = uint32_or_64_or_128_t; - int_writer w(out_, locale_, value, spec); - handle_int_type_spec(spec.type, w); - out_ = w.out; - } - - void write(char value) { - auto&& it = reserve(1); - *it++ = value; - } - - template ::value)> - void write(Ch value) { - out_ = detail::write(out_, value); - } - - void write(string_view value) { - auto&& it = reserve(value.size()); - it = copy_str(value.begin(), value.end(), it); - } - void write(wstring_view value) { - static_assert(std::is_same::value, ""); - auto&& it = reserve(value.size()); - it = std::copy(value.begin(), value.end(), it); - } - - template - void write(const Ch* s, size_t size, const format_specs& specs) { - auto width = specs.width != 0 - ? count_code_points(basic_string_view(s, size)) - : 0; - out_ = write_padded(out_, specs, size, width, [=](reserve_iterator it) { - return copy_str(s, s + size, it); - }); - } - - template - void write(basic_string_view s, const format_specs& specs = {}) { - out_ = detail::write(out_, s, specs); - } - - void write_pointer(const void* p) { - out_ = write_ptr(out_, to_uintptr(p), specs_); - } - - struct char_spec_handler : ErrorHandler { - arg_formatter_base& formatter; - Char value; - - char_spec_handler(arg_formatter_base& f, Char val) - : formatter(f), value(val) {} - - void on_int() { - // char is only formatted as int if there are specs. - formatter.write_int(static_cast(value), *formatter.specs_); - } - void on_char() { - if (formatter.specs_) - formatter.out_ = write_char(formatter.out_, value, *formatter.specs_); - else - formatter.write(value); - } - }; - - struct cstring_spec_handler : error_handler { - arg_formatter_base& formatter; - const Char* value; - - cstring_spec_handler(arg_formatter_base& f, const Char* val) - : formatter(f), value(val) {} - - void on_string() { formatter.write(value); } - void on_pointer() { formatter.write_pointer(value); } - }; - - protected: - iterator out() { return out_; } - format_specs* specs() { return specs_; } - - void write(bool value) { - if (specs_) - write(string_view(value ? "true" : "false"), *specs_); - else - out_ = detail::write(out_, value); - } - - void write(const Char* value) { - if (!value) { - FMT_THROW(format_error("string pointer is null")); - } else { - auto length = std::char_traits::length(value); - basic_string_view sv(value, length); - specs_ ? write(sv, *specs_) : write(sv); - } - } - - public: - arg_formatter_base(OutputIt out, format_specs* s, locale_ref loc) - : out_(out), locale_(loc), specs_(s) {} - - iterator operator()(monostate) { - FMT_ASSERT(false, "invalid argument type"); - return out_; - } - - template ::value)> - FMT_INLINE iterator operator()(T value) { - if (specs_) - write_int(value, *specs_); - else - out_ = detail::write(out_, value); - return out_; - } - - iterator operator()(Char value) { - handle_char_specs(specs_, - char_spec_handler(*this, static_cast(value))); - return out_; - } - - iterator operator()(bool value) { - if (specs_ && specs_->type) return (*this)(value ? 1 : 0); - write(value != 0); - return out_; - } - - template ::value)> - iterator operator()(T value) { - auto specs = specs_ ? *specs_ : format_specs(); - if (const_check(is_supported_floating_point(value))) - out_ = detail::write(out_, value, specs, locale_); - else - FMT_ASSERT(false, "unsupported float argument type"); - return out_; - } - - iterator operator()(const Char* value) { - if (!specs_) return write(value), out_; - handle_cstring_type_spec(specs_->type, cstring_spec_handler(*this, value)); - return out_; - } - - iterator operator()(basic_string_view value) { - if (specs_) { - check_string_type_spec(specs_->type, error_handler()); - write(value, *specs_); - } else { - write(value); - } - return out_; - } - - iterator operator()(const void* value) { - if (specs_) check_pointer_type_spec(specs_->type, error_handler()); - write_pointer(value); - return out_; + auto operator()(typename basic_format_arg::handle) -> iterator { + // User-defined types are handled separately because they require access + // to the parse context. + return out; } }; -template FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; -} +template struct custom_formatter { + basic_format_parse_context& parse_ctx; + buffer_context& ctx; -// Parses the range [begin, end) as an unsigned integer. This function assumes -// that the range is non-empty and the first character is a digit. -template -FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end, - ErrorHandler&& eh) { - FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - unsigned value = 0; - // Convert to unsigned to prevent a warning. - constexpr unsigned max_int = max_value(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; - break; - } - value = value * 10 + unsigned(*begin - '0'); - ++begin; - } while (begin != end && '0' <= *begin && *begin <= '9'); - if (value > max_int) eh.on_error("number is too big"); - return static_cast(value); -} - -template class custom_formatter { - private: - using char_type = typename Context::char_type; - - basic_format_parse_context& parse_ctx_; - Context& ctx_; - - public: - explicit custom_formatter(basic_format_parse_context& parse_ctx, - Context& ctx) - : parse_ctx_(parse_ctx), ctx_(ctx) {} - - bool operator()(typename basic_format_arg::handle h) const { - h.format(parse_ctx_, ctx_); - return true; + void operator()( + typename basic_format_arg>::handle h) const { + h.format(parse_ctx, ctx); } - - template bool operator()(T) const { return false; } + template void operator()(T) const {} }; template @@ -2066,13 +2014,13 @@ template class width_checker { explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T value) { + FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { if (is_negative(value)) handler_.on_error("negative width"); return static_cast(value); } template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T) { + FMT_CONSTEXPR auto operator()(T) -> unsigned long long { handler_.on_error("width is not integer"); return 0; } @@ -2086,13 +2034,13 @@ template class precision_checker { explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T value) { + FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { if (is_negative(value)) handler_.on_error("negative precision"); return static_cast(value); } template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T) { + FMT_CONSTEXPR auto operator()(T) -> unsigned long long { handler_.on_error("precision is not integer"); return 0; } @@ -2101,148 +2049,50 @@ template class precision_checker { ErrorHandler& handler_; }; -// A format specifier handler that sets fields in basic_format_specs. -template class specs_setter { - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) - : specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter& other) - : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - specs_.fill = fill; - } - FMT_CONSTEXPR void on_plus() { specs_.sign = sign::plus; } - FMT_CONSTEXPR void on_minus() { specs_.sign = sign::minus; } - FMT_CONSTEXPR void on_space() { specs_.sign = sign::space; } - FMT_CONSTEXPR void on_hash() { specs_.alt = true; } - - FMT_CONSTEXPR void on_zero() { - specs_.align = align::numeric; - specs_.fill[0] = Char('0'); - } - - FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } - FMT_CONSTEXPR void on_precision(int precision) { - specs_.precision = precision; - } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(Char type) { - specs_.type = static_cast(type); - } - - protected: - basic_format_specs& specs_; -}; - -template class numeric_specs_checker { - public: - FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, detail::type arg_type) - : error_handler_(eh), arg_type_(arg_type) {} - - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic_type(arg_type_)) - error_handler_.on_error("format specifier requires numeric argument"); - } - - FMT_CONSTEXPR void check_sign() { - require_numeric_argument(); - if (is_integral_type(arg_type_) && arg_type_ != type::int_type && - arg_type_ != type::long_long_type && arg_type_ != type::char_type) { - error_handler_.on_error("format specifier requires signed argument"); - } - } - - FMT_CONSTEXPR void check_precision() { - if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) - error_handler_.on_error("precision not allowed for this argument type"); - } - - private: - ErrorHandler& error_handler_; - detail::type arg_type_; -}; - -// A format specifier handler that checks if specifiers are consistent with the -// argument type. -template class specs_checker : public Handler { - private: - numeric_specs_checker checker_; - - // Suppress an MSVC warning about using this in initializer list. - FMT_CONSTEXPR Handler& error_handler() { return *this; } - - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) - : Handler(handler), checker_(error_handler(), arg_type) {} - - FMT_CONSTEXPR specs_checker(const specs_checker& other) - : Handler(other), checker_(error_handler(), other.arg_type_) {} - - FMT_CONSTEXPR void on_align(align_t align) { - if (align == align::numeric) checker_.require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_plus() { - checker_.check_sign(); - Handler::on_plus(); - } - - FMT_CONSTEXPR void on_minus() { - checker_.check_sign(); - Handler::on_minus(); - } - - FMT_CONSTEXPR void on_space() { - checker_.check_sign(); - Handler::on_space(); - } - - FMT_CONSTEXPR void on_hash() { - checker_.require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_zero() { - checker_.require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { checker_.check_precision(); } -}; - template