diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-07 02:08:45 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-07 02:08:45 +0000 |
commit | d419391dc911f385a0b0a46853223c364906b838 (patch) | |
tree | 77eb0afe0529d93aaf8f54988182a425173c056c | |
parent | 4c5ee89ab509121bfa1dc16fbef0168f43609807 (diff) | |
parent | 3b8b60f70480ead65ad864cb3311fc52ec122907 (diff) | |
download | fmtlib-d419391dc911f385a0b0a46853223c364906b838.tar.gz |
Snap for 11541002 from 3b8b60f70480ead65ad864cb3311fc52ec122907 to sdk-releaseplatform-tools-35.0.1sdk-release
Change-Id: Icc3710924873571a2f4447c9b32d4bb64f63a95b
47 files changed, 2993 insertions, 2026 deletions
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index f87899d0..25250e8b 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -10,20 +10,20 @@ jobs: steps: - name: Build Fuzzers id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@061583ebb5a96653e42feb3a97ee513eedc18078 # master with: oss-fuzz-project-name: 'fmt' dry-run: false language: c++ - name: Run Fuzzers - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@061583ebb5a96653e42feb3a97ee513eedc18078 # master with: oss-fuzz-project-name: 'fmt' fuzz-seconds: 300 dry-run: false language: c++ - name: Upload Crash - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..b906fac2 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,26 @@ +name: lint + +on: + pull_request: + paths: + - '**.h' + - '**.cc' + +permissions: + contents: read + +jobs: + format_code: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install clang-format + uses: aminya/setup-cpp@v1 + with: + clangformat: 17.0.5 + + - name: Run clang-format + run: | + find include src -name '*.h' -o -name '*.cc' | xargs clang-format -i -style=file -fallback-style=none + git diff --exit-code diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 33aecd30..c5f2ea9d 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -52,7 +52,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 with: name: SARIF file path: results.sarif @@ -1,37 +1,24 @@ +*.a +*.so* +*.xcodeproj +*~ .vscode/ -.vs/ - -*.iml -.idea/ -.externalNativeBuild/ -.gradle/ -gradle/ -gradlew* -local.properties -build/ -support/.cxx - -bin/ -/_CPack_Packages /CMakeScripts +/Testing +/_CPack_Packages /doc/doxyxml /doc/html /doc/node_modules -virtualenv -/Testing /install_manifest.txt -*~ -*.a -*.so* -*.xcodeproj -*.zip -cmake_install.cmake -CPack*.cmake -fmt-*.cmake -CTestTestfile.cmake CMakeCache.txt CMakeFiles +CPack*.cmake +CTestTestfile.cmake FMT.build Makefile -run-msbuild.bat +bin/ +build/ +cmake_install.cmake +fmt-*.cmake fmt.pc +virtualenv @@ -133,7 +133,11 @@ cc_defaults { host_supported: true, test_suites: ["general-tests"], // The tests require exceptions and RTTI. - cflags: ["-fexceptions"], + cflags: [ + "-fexceptions", + // https://github.com/fmtlib/fmt/issues/3884 + "-Wno-non-virtual-dtor", + ], rtti: true, // The usual "gtest *and* gmock, please" dance... gtest: false, diff --git a/CMakeLists.txt b/CMakeLists.txt index 485eb86c..4b928ae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required(VERSION 3.8...3.26) # Fallback for using newer policies on CMake <3.12. -if(${CMAKE_VERSION} VERSION_LESS 3.12) +if (${CMAKE_VERSION} VERSION_LESS 3.12) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -endif() +endif () # Determine if fmt is built as a subproject (using add_subdirectory) # or if it is the master project. @@ -162,10 +162,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "") if (FMT_SYSTEM_HEADERS) set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) endif () -if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS") +if (CMAKE_SYSTEM_NAME STREQUAL "MSDOS") set(FMT_TEST OFF) message(STATUS "MSDOS is incompatible with gtest") -endif() +endif () # Get version from core.h file(READ include/fmt/core.h core_h) @@ -283,7 +283,7 @@ if (FMT_OS) endif () add_module_library(fmt src/fmt.cc FALLBACK - ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.md + ${FMT_SOURCES} ${FMT_HEADERS} README.md ChangeLog.md IF FMT_MODULE) add_library(fmt::fmt ALIAS fmt) if (FMT_MODULE) @@ -334,7 +334,7 @@ if (BUILD_SHARED_LIBS) endif () if (FMT_SAFE_DURATION_CAST) target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST) -endif() +endif () add_library(fmt-header-only INTERFACE) add_library(fmt::fmt-header-only ALIAS fmt-header-only) @@ -342,7 +342,8 @@ add_library(fmt::fmt-header-only ALIAS fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) target_compile_features(fmt-header-only INTERFACE cxx_std_11) -target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE +target_include_directories(fmt-header-only + ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${FMT_INC_DIR}>) @@ -447,6 +448,6 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) set(CPACK_SOURCE_IGNORE_FILES ${ignored_files}) set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION}) set(CPACK_PACKAGE_NAME fmt) - set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst) + set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) include(CPack) endif () diff --git a/ChangeLog.md b/ChangeLog.md index 2e51d107..8f8075ac 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,248 @@ +# 10.2.0 - 2024-01-01 + +- Added support for the `%j` specifier (the number of days) for + `std::chrono::duration` (https://github.com/fmtlib/fmt/issues/3643, + https://github.com/fmtlib/fmt/pull/3732). Thanks @intelfx. + +- Added support for the chrono suffix for days and changed + the suffix for minutes from "m" to the correct "min" + (https://github.com/fmtlib/fmt/issues/3662, + https://github.com/fmtlib/fmt/pull/3664). + For example ([godbolt](https://godbolt.org/z/9KhMnq9ba)): + + ```c++ + #include <fmt/chrono.h> + + int main() { + fmt::print("{}\n", std::chrono::days(42)); // prints "42d" + } + ``` + + Thanks @Richardk2n. + +- Fixed an overflow in `std::chrono::time_point` formatting with large dates + (https://github.com/fmtlib/fmt/issues/3725, + https://github.com/fmtlib/fmt/pull/3727). Thanks @cschreib. + +- Added a formatter for `std::source_location` + (https://github.com/fmtlib/fmt/pull/3730). + For example ([godbolt](https://godbolt.org/z/YajfKjhhr)): + + ```c++ + #include <source_location> + #include <fmt/std.h> + + int main() { + fmt::print("{}\n", std::source_location::current()); + } + ``` + + prints + + ``` + /app/example.cpp:5:51: int main() + ``` + + Thanks @felix642. + +- Added a formatter for `std::bitset` + (https://github.com/fmtlib/fmt/pull/3660). + For example ([godbolt](https://godbolt.org/z/bdEaGeYxe)): + + ```c++ + #include <bitset> + #include <fmt/std.h> + + int main() { + fmt::print("{}\n", std::bitset<6>(42)); // prints "101010" + } + ``` + + Thanks @muggenhor. + +- Added an experimental `nested_formatter` that provides an easy way of + applying a formatter to one or more subobjects while automatically handling + width, fill and alignment. For example: + + ```c++ + #include <fmt/format.h> + + struct point { + double x, y; + }; + + template <> + struct fmt::formatter<point> : nested_formatter<double> { + auto format(point p, format_context& ctx) const { + return write_padded(ctx, [=](auto out) { + return format_to(out, "({}, {})", nested(p.x), nested(p.y)); + }); + } + }; + + int main() { + fmt::print("[{:>20.2f}]", point{1, 2}); + } + ``` + + prints + + ``` + [ (1.00, 2.00)] + ``` + +- Added the generic representation (`g`) to `std::filesystem::path` + (https://github.com/fmtlib/fmt/issues/3715, + https://github.com/fmtlib/fmt/pull/3729). For example: + + ```c++ + #include <filesystem> + #include <fmt/std.h> + + int main() { + fmt::print("{:g}\n", std::filesystem::path("C:\\foo")); + } + ``` + + prints `"C:/foo"` on Windows. + + Thanks @js324. + +- Made `format_as` work with references + (https://github.com/fmtlib/fmt/pull/3739). Thanks @tchaikov. + +- Fixed formatting of invalid UTF-8 with precision + (https://github.com/fmtlib/fmt/issues/3284). + +- Fixed an inconsistency between `fmt::to_string` and `fmt::format` + (https://github.com/fmtlib/fmt/issues/3684). + +- Disallowed unsafe uses of `fmt::styled` + (https://github.com/fmtlib/fmt/issues/3625): + + ```c++ + auto s = fmt::styled(std::string("dangle"), fmt::emphasis::bold); + fmt::print("{}\n", s); // compile error + ``` + + Pass `fmt::styled(...)` as a parameter instead. + +- Added a null check when formatting a C string with the `s` specifier + (https://github.com/fmtlib/fmt/issues/3706). + +- Disallowed the `c` specifier for `bool` + (https://github.com/fmtlib/fmt/issues/3726, + https://github.com/fmtlib/fmt/pull/3734). Thanks @js324. + +- Made the default formatting unlocalized in `fmt::ostream_formatter` for + consistency with the rest of the library + (https://github.com/fmtlib/fmt/issues/3460). + +- Fixed localized formatting in bases other than decimal + (https://github.com/fmtlib/fmt/issues/3693, + https://github.com/fmtlib/fmt/pull/3750). Thanks @js324. + +- Fixed a performance regression in experimental `fmt::ostream::print` + (https://github.com/fmtlib/fmt/issues/3674). + +- Added synchronization with the underlying output stream when writing to + the Windows console + (https://github.com/fmtlib/fmt/pull/3668, + https://github.com/fmtlib/fmt/issues/3688, + https://github.com/fmtlib/fmt/pull/3689). + Thanks @Roman-Koshelev and @dimztimz. + +- Changed to only export `format_error` when {fmt} is built as a shared + library (https://github.com/fmtlib/fmt/issues/3626, + https://github.com/fmtlib/fmt/pull/3627). Thanks @phprus. + +- Made `fmt::streamed` `constexpr`. + (https://github.com/fmtlib/fmt/pull/3650). Thanks @muggenhor. + +- Enabled `consteval` on older versions of MSVC + (https://github.com/fmtlib/fmt/pull/3757). Thanks @phprus. + +- Added an option to build without `wchar_t` support on Windows + (https://github.com/fmtlib/fmt/issues/3631, + https://github.com/fmtlib/fmt/pull/3636). Thanks @glebm. + +- Improved build and CI configuration + (https://github.com/fmtlib/fmt/pull/3679, + https://github.com/fmtlib/fmt/issues/3701, + https://github.com/fmtlib/fmt/pull/3702, + https://github.com/fmtlib/fmt/pull/3749). + Thanks @jcar87, @pklima and @tchaikov. + +- Fixed various warnings, compilation and test issues + (https://github.com/fmtlib/fmt/issues/3607, + https://github.com/fmtlib/fmt/pull/3610, + https://github.com/fmtlib/fmt/pull/3624, + https://github.com/fmtlib/fmt/pull/3630, + https://github.com/fmtlib/fmt/pull/3634, + https://github.com/fmtlib/fmt/pull/3638, + https://github.com/fmtlib/fmt/issues/3645, + https://github.com/fmtlib/fmt/issues/3646, + https://github.com/fmtlib/fmt/pull/3647, + https://github.com/fmtlib/fmt/pull/3652, + https://github.com/fmtlib/fmt/issues/3654, + https://github.com/fmtlib/fmt/pull/3663, + https://github.com/fmtlib/fmt/issues/3670, + https://github.com/fmtlib/fmt/pull/3680, + https://github.com/fmtlib/fmt/issues/3694, + https://github.com/fmtlib/fmt/pull/3695, + https://github.com/fmtlib/fmt/pull/3699, + https://github.com/fmtlib/fmt/issues/3705, + https://github.com/fmtlib/fmt/issues/3710, + https://github.com/fmtlib/fmt/issues/3712, + https://github.com/fmtlib/fmt/pull/3713, + https://github.com/fmtlib/fmt/issues/3714, + https://github.com/fmtlib/fmt/pull/3716, + https://github.com/fmtlib/fmt/pull/3723, + https://github.com/fmtlib/fmt/issues/3738, + https://github.com/fmtlib/fmt/issues/3740, + https://github.com/fmtlib/fmt/pull/3741, + https://github.com/fmtlib/fmt/pull/3743, + https://github.com/fmtlib/fmt/issues/3745, + https://github.com/fmtlib/fmt/pull/3747, + https://github.com/fmtlib/fmt/pull/3748, + https://github.com/fmtlib/fmt/pull/3751, + https://github.com/fmtlib/fmt/pull/3754, + https://github.com/fmtlib/fmt/pull/3755, + https://github.com/fmtlib/fmt/issues/3760, + https://github.com/fmtlib/fmt/pull/3762, + https://github.com/fmtlib/fmt/issues/3763, + https://github.com/fmtlib/fmt/pull/3764, + https://github.com/fmtlib/fmt/issues/3774, + https://github.com/fmtlib/fmt/pull/3779). + Thanks @danakj, @vinayyadav3016, @cyyever, @phprus, @qimiko, @saschasc, + @gsjaardema, @lazka, @Zhaojun-Liu, @carlsmedstad, @hotwatermorning, + @cptFracassa, @kuguma, @PeterJohnson, @H1X4Dev, @asantoni, @eltociear, + @msimberg, @tchaikov, @waywardmonkeys. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/issues/2086, + https://github.com/fmtlib/fmt/issues/3637, + https://github.com/fmtlib/fmt/pull/3642, + https://github.com/fmtlib/fmt/pull/3653, + https://github.com/fmtlib/fmt/pull/3655, + https://github.com/fmtlib/fmt/pull/3661, + https://github.com/fmtlib/fmt/issues/3673, + https://github.com/fmtlib/fmt/pull/3677, + https://github.com/fmtlib/fmt/pull/3737, + https://github.com/fmtlib/fmt/issues/3742, + https://github.com/fmtlib/fmt/pull/3744). + Thanks @idzm, @perlun, @joycebrum, @fennewald, @reinhardt1053, @GeorgeLS. + +- Updated CI dependencies + (https://github.com/fmtlib/fmt/pull/3615, + https://github.com/fmtlib/fmt/pull/3622, + https://github.com/fmtlib/fmt/pull/3623, + https://github.com/fmtlib/fmt/pull/3666, + https://github.com/fmtlib/fmt/pull/3696, + https://github.com/fmtlib/fmt/pull/3697, + https://github.com/fmtlib/fmt/pull/3759, + https://github.com/fmtlib/fmt/pull/3782). + # 10.1.1 - 2023-08-28 - Added formatters for `std::atomic` and `atomic_flag` @@ -13,8 +258,7 @@ https://github.com/fmtlib/fmt/pull/3605). Thanks @MathewBensonCode. - Made `fmt::to_string` work with types that have `format_as` - overloads (https://github.com/fmtlib/fmt/pull/3575). - Thanks @phprus. + overloads (https://github.com/fmtlib/fmt/pull/3575). Thanks @phprus. - Made `formatted_size` work with integral format specifiers at compile time (https://github.com/fmtlib/fmt/pull/3591). Thanks @elbeno. @@ -388,7 +632,9 @@ Thanks @ShawnZhong. -- Added a formatter for `std::optional` to `fmt/std.h`. +- Added a formatter for `std::optional` to `fmt/std.h` + (https://github.com/fmtlib/fmt/issues/1367, + https://github.com/fmtlib/fmt/pull/3303). Thanks @tom-huntington. - Fixed formatting of valueless by exception variants @@ -1,19 +1,20 @@ +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update external/fmtlib +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md + name: "fmtlib" description: "{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams." third_party { - url { - type: HOMEPAGE - value: "https://github.com/fmtlib/fmt" - } - url { - type: GIT - value: "https://github.com/fmtlib/fmt.git" - } - version: "10.1.1" license_type: NOTICE last_upgrade_date { - year: 2023 - month: 12 - day: 7 + year: 2024 + month: 3 + day: 5 + } + homepage: "https://github.com/fmtlib/fmt" + identifier { + type: "Git" + value: "https://github.com/fmtlib/fmt.git" + version: "10.2.0" } } diff --git a/README.md b/README.md new file mode 100644 index 00000000..dcfb16ec --- /dev/null +++ b/README.md @@ -0,0 +1,490 @@ +<img src="https://user-images.githubusercontent.com/576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png" alt="{fmt}" width="25%"/> + +[![image](https://github.com/fmtlib/fmt/workflows/linux/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux) +[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos) +[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows) +[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1) +[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt) +[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt) + +**{fmt}** is an open-source formatting library providing a fast and safe +alternative to C stdio and C++ iostreams. + +If you like this project, please consider donating to one of the funds +that help victims of the war in Ukraine: <https://www.stopputin.net/>. + +[Documentation](https://fmt.dev) + +[Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html) + +Q&A: ask questions on [StackOverflow with the tag +fmt](https://stackoverflow.com/questions/tagged/fmt). + +Try {fmt} in [Compiler Explorer](https://godbolt.org/z/Eq5763). + +# Features + +- Simple [format API](https://fmt.dev/latest/api.html) with positional + arguments for localization +- Implementation of [C++20 + std::format](https://en.cppreference.com/w/cpp/utility/format) and + [C++23 std::print](https://en.cppreference.com/w/cpp/io/print) +- [Format string syntax](https://fmt.dev/latest/syntax.html) similar + to Python\'s + [format](https://docs.python.org/3/library/stdtypes.html#str.format) +- Fast IEEE 754 floating-point formatter with correct rounding, + shortness and round-trip guarantees using the + [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm +- Portable Unicode support +- Safe [printf + implementation](https://fmt.dev/latest/api.html#printf-formatting) + including the POSIX extension for positional arguments +- Extensibility: [support for user-defined + types](https://fmt.dev/latest/api.html#formatting-user-defined-types) +- High performance: faster than common standard library + implementations of `(s)printf`, iostreams, `to_string` and + `to_chars`, see [Speed tests](#speed-tests) and [Converting a + hundred million integers to strings per + second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html) +- 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](#compile-time-and-code-bloat) +- Reliability: the library has an extensive set of + [tests](https://github.com/fmtlib/fmt/tree/master/test) and is + [continuously fuzzed](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1) +- Safety: the library is fully type-safe, errors in format strings can + be reported at compile time, automatic memory management prevents + buffer overflow errors +- Ease of use: small self-contained code base, no external + dependencies, permissive MIT + [license](https://github.com/fmtlib/fmt/blob/master/LICENSE.rst) +- [Portability](https://fmt.dev/latest/index.html#portability) with + consistent output across platforms and support for older compilers +- Clean warning-free codebase even on high warning levels such as + `-Wall -Wextra -pedantic` +- Locale independence by default +- Optional header-only configuration enabled with the + `FMT_HEADER_ONLY` macro + +See the [documentation](https://fmt.dev) for more details. + +# Examples + +**Print to stdout** ([run](https://godbolt.org/z/Tevcjh)) + +``` c++ +#include <fmt/core.h> + +int main() { + fmt::print("Hello, world!\n"); +} +``` + +**Format a string** ([run](https://godbolt.org/z/oK8h33)) + +``` c++ +std::string s = fmt::format("The answer is {}.", 42); +// s == "The answer is 42." +``` + +**Format a string using positional arguments** +([run](https://godbolt.org/z/Yn7Txe)) + +``` c++ +std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); +// s == "I'd rather be happy than right." +``` + +**Print dates and times** ([run](https://godbolt.org/z/c31ExdY3W)) + +``` c++ +#include <fmt/chrono.h> + +int main() { + auto now = std::chrono::system_clock::now(); + fmt::print("Date and time: {}\n", now); + fmt::print("Time: {:%H:%M}\n", now); +} +``` + +Output: + + Date and time: 2023-12-26 19:10:31.557195597 + Time: 19:10 + +**Print a container** ([run](https://godbolt.org/z/MxM1YqjE7)) + +``` c++ +#include <vector> +#include <fmt/ranges.h> + +int main() { + std::vector<int> v = {1, 2, 3}; + fmt::print("{}\n", v); +} +``` + +Output: + + [1, 2, 3] + +**Check a format string at compile time** + +``` c++ +std::string s = fmt::format("{:d}", "I am not a number"); +``` + +This gives a compile-time error in C++20 because `d` is an invalid +format specifier for a string. + +**Write a file from a single thread** + +``` c++ +#include <fmt/os.h> + +int main() { + auto out = fmt::output_file("guide.txt"); + out.print("Don't {}", "Panic"); +} +``` + +This can be [5 to 9 times faster than +fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html). + +**Print with colors and text styles** + +``` c++ +#include <fmt/color.h> + +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, "Olá, {}!\n", "Mundo"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, + "你好{}!\n", "世界"); +} +``` + +Output on a modern terminal with Unicode support: + +![image](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7) + +# Benchmarks + +## Speed tests + +| Library | Method | Run Time, s | +|-------------------|---------------|-------------| +| libc | printf | 0.91 | +| libc++ | std::ostream | 2.49 | +| {fmt} 9.1 | fmt::print | 0.74 | +| Boost Format 1.80 | boost::format | 6.26 | +| Folly Format | folly::format | 1.87 | + +{fmt} is the fastest of the benchmarked methods, \~20% faster than +`printf`. + +The above results were generated by building `tinyformat_test.cpp` on +macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and +taking the best of three runs. In the test, the format string +`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000 +times with output sent to `/dev/null`; for further details refer to the +[source](https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc). + +{fmt} is up to 20-30x faster than `std::ostringstream` and `sprintf` on +IEEE754 `float` and `double` formatting +([dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark)) and faster +than [double-conversion](https://github.com/google/double-conversion) +and [ryu](https://github.com/ulfjack/ryu): + +[![image](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png)](https://fmt.dev/unknown_mac64_clang12.0.html) + +## Compile time and code bloat + +The script +[bloat-test.py](https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py) +from [format-benchmark](https://github.com/fmtlib/format-benchmark) +tests compile time and code bloat for nontrivial projects. It generates +100 translation units and uses `printf()` or its alternative five times +in each to simulate a medium-sized project. The resulting executable +size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42), macOS +Sierra, best of three) is shown in the following tables. + +**Optimized build (-O3)** + +| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | +|---------------|-----------------|----------------------|--------------------| +| printf | 2.6 | 29 | 26 | +| printf+string | 16.4 | 29 | 26 | +| iostreams | 31.1 | 59 | 55 | +| {fmt} | 19.0 | 37 | 34 | +| Boost Format | 91.9 | 226 | 203 | +| Folly Format | 115.7 | 101 | 88 | + +As you can see, {fmt} has 60% less overhead in terms of resulting binary +code size compared to iostreams and comes pretty close to `printf`. +Boost Format and Folly Format have the largest overheads. + +`printf+string` is the same as `printf` but with an extra `<string>` +include to measure the overhead of the latter. + +**Non-optimized build** + +| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | +|---------------|-----------------|----------------------|--------------------| +| printf | 2.2 | 33 | 30 | +| printf+string | 16.0 | 33 | 30 | +| iostreams | 28.3 | 56 | 52 | +| {fmt} | 18.2 | 59 | 50 | +| Boost Format | 54.1 | 365 | 303 | +| Folly Format | 79.9 | 445 | 430 | + +`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries +to compare formatting function overhead only. Boost Format is a +header-only library so it doesn\'t provide any linkage options. + +## Running the tests + +Please refer to [Building the +library](https://fmt.dev/latest/usage.html#building-the-library) for +instructions on how to build the library and run the unit tests. + +Benchmarks reside in a separate repository, +[format-benchmarks](https://github.com/fmtlib/format-benchmark), so to +run the benchmarks you first need to clone this repository and generate +Makefiles with CMake: + + $ git clone --recursive https://github.com/fmtlib/format-benchmark.git + $ cd format-benchmark + $ cmake . + +Then you can run the speed test: + + $ make speed-test + +or the bloat test: + + $ make bloat-test + +# Migrating code + +[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) v17 (not yet +released) provides the +[modernize-use-std-print](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html) +check that is capable of converting occurrences of `printf` and +`fprintf` to `fmt::print` if configured to do so. (By default it +converts to `std::print`.) + +# Notable projects using this library + +- [0 A.D.](https://play0ad.com/): a free, open-source, cross-platform + real-time strategy game +- [AMPL/MP](https://github.com/ampl/mp): an open-source library for + mathematical programming +- [Apple's FoundationDB](https://github.com/apple/foundationdb): an open-source, + distributed, transactional key-value store +- [Aseprite](https://github.com/aseprite/aseprite): animated sprite + editor & pixel art tool +- [AvioBook](https://www.aviobook.aero/en): a comprehensive aircraft + operations suite +- [Blizzard Battle.net](https://battle.net/): an online gaming + platform +- [Celestia](https://celestia.space/): real-time 3D visualization of + space +- [Ceph](https://ceph.com/): a scalable distributed storage system +- [ccache](https://ccache.dev/): a compiler cache +- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an + analytical database management system +- [Contour](https://github.com/contour-terminal/contour/): a modern + terminal emulator +- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous + underwater vehicle +- [Drake](https://drake.mit.edu/): a planning, control, and analysis + toolbox for nonlinear dynamical systems (MIT) +- [Envoy](https://lyft.github.io/envoy/): C++ L7 proxy and + communication bus (Lyft) +- [FiveM](https://fivem.net/): a modification framework for GTA V +- [fmtlog](https://github.com/MengRao/fmtlog): a performant + fmtlib-style logging library with latency in nanoseconds +- [Folly](https://github.com/facebook/folly): Facebook open-source + library +- [GemRB](https://gemrb.org/): a portable open-source implementation + of Bioware's Infinity Engine +- [Grand Mountain + Adventure](https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/): + a beautiful open-world ski & snowboarding game +- [HarpyWar/pvpgn](https://github.com/pvpgn/pvpgn-server): Player vs + Player Gaming Network with tweaks +- [KBEngine](https://github.com/kbengine/kbengine): an open-source + MMOG server engine +- [Keypirinha](https://keypirinha.com/): a semantic launcher for + Windows +- [Kodi](https://kodi.tv/) (formerly xbmc): home theater software +- [Knuth](https://kth.cash/): high-performance Bitcoin full-node +- [libunicode](https://github.com/contour-terminal/libunicode/): a + modern C++17 Unicode library +- [MariaDB](https://mariadb.org/): relational database management + system +- [Microsoft Verona](https://github.com/microsoft/verona): research + programming language for concurrent ownership +- [MongoDB](https://mongodb.com/): distributed document database +- [MongoDB Smasher](https://github.com/duckie/mongo_smasher): a small + tool to generate randomized datasets +- [OpenSpace](https://openspaceproject.com/): an open-source + astrovisualization framework +- [PenUltima Online (POL)](https://www.polserver.com/): an MMO server, + compatible with most Ultima Online clients +- [PyTorch](https://github.com/pytorch/pytorch): an open-source + machine learning library +- [quasardb](https://www.quasardb.net/): a distributed, + high-performance, associative database +- [Quill](https://github.com/odygrd/quill): asynchronous low-latency + logging library +- [QKW](https://github.com/ravijanjam/qkw): generalizing aliasing to + simplify navigation, and executing complex multi-line terminal + command sequences +- [redis-cerberus](https://github.com/HunanTV/redis-cerberus): a Redis + cluster proxy +- [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka® + replacement for mission-critical systems written in C++ +- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and + client library +- [Salesforce Analytics + Cloud](https://www.salesforce.com/analytics-cloud/overview/): + business intelligence software +- [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL + data store that can handle 1 million transactions per second on a + single server +- [Seastar](http://www.seastar-project.org/): an advanced, open-source + C++ framework for high-performance server applications on modern + hardware +- [spdlog](https://github.com/gabime/spdlog): super fast C++ logging + library +- [Stellar](https://www.stellar.org/): financial platform +- [Touch Surgery](https://www.touchsurgery.com/): surgery simulator +- [TrinityCore](https://github.com/TrinityCore/TrinityCore): + open-source MMORPG framework +- [🐙 userver framework](https://userver.tech/): open-source + asynchronous framework with a rich set of abstractions and database + drivers +- [Windows Terminal](https://github.com/microsoft/terminal): the new + Windows terminal + +[More\...](https://github.com/search?q=fmtlib&type=Code) + +If you are aware of other projects using this library, please let me +know by [email](mailto:victor.zverovich@gmail.com) or by submitting an +[issue](https://github.com/fmtlib/fmt/issues). + +# Motivation + +So why yet another formatting library? + +There are plenty of methods for doing this task, from standard ones like +the printf family of function and iostreams to Boost Format and +FastFormat libraries. The reason for creating a new library is that +every existing solution that I found either had serious issues or +didn\'t provide all the features I needed. + +## printf + +The good thing about `printf` is that it is pretty fast and readily +available being a part of the C standard library. The main drawback is +that it doesn\'t support user-defined types. `printf` also has safety +issues although they are somewhat mitigated with [\_\_attribute\_\_ +((format (printf, +\...))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) in +GCC. There is a POSIX extension that adds positional arguments required +for +[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization) +to `printf` but it is not a part of C99 and may not be available on some +platforms. + +## iostreams + +The main issue with iostreams is best illustrated with an example: + +``` c++ +std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; +``` + +which is a lot of typing compared to printf: + +``` c++ +printf("%.2f\n", 1.23456); +``` + +Matthew Wilson, the author of FastFormat, called this \"chevron hell\". +iostreams don\'t support positional arguments by design. + +The good part is that iostreams support user-defined types and are safe +although error handling is awkward. + +## Boost Format + +This is a very powerful library that 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 Format also has excessive build times and severe +code bloat issues (see [Benchmarks](#benchmarks)). + +## FastFormat + +This is an interesting library that is fast, safe, and has positional +arguments. However, it has significant limitations, citing its author: + +> Three features that have no hope of being accommodated within the +> current design are: +> +> - Leading zeros (or any other non-space padding) +> - Octal/hexadecimal encoding +> - Runtime width/alignment specification + +It is also quite big and has a heavy dependency, STLSoft, which might be +too restrictive for using it in some projects. + +## Boost Spirit.Karma + +This is not a formatting library but I decided to include it here for +completeness. As iostreams, it suffers from the problem of mixing +verbatim text with arguments. The library is pretty fast, but slower on +integer formatting than `fmt::format_to` with format string compilation +on Karma\'s own benchmark, see [Converting a hundred million integers to +strings per +second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html). + +# License + +{fmt} is distributed under the MIT +[license](https://github.com/fmtlib/fmt/blob/master/LICENSE). + +# Documentation License + +The [Format String Syntax](https://fmt.dev/latest/syntax.html) section +in the documentation is based on the one from Python [string module +documentation](https://docs.python.org/3/library/string.html#module-string). +For this reason, the documentation is distributed under the Python +Software Foundation license available in +[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt). +It only applies if you distribute the documentation of {fmt}. + +# Maintainers + +The {fmt} library is maintained by Victor Zverovich +([vitaut](https://github.com/vitaut)) with contributions from many other +people. See +[Contributors](https://github.com/fmtlib/fmt/graphs/contributors) and +[Releases](https://github.com/fmtlib/fmt/releases) for some of the +names. Let us know if your contribution is not listed or mentioned +incorrectly and we\'ll make it right. + +# Security Policy + +To report a security issue, please disclose it at [security +advisory](https://github.com/fmtlib/fmt/security/advisories/new). + +This project is maintained by a team of volunteers on a +reasonable-effort basis. As such, please give us at least 90 days to +work on a fix before public exposure. diff --git a/README.rst b/README.rst deleted file mode 100644 index 81948bd6..00000000 --- a/README.rst +++ /dev/null @@ -1,553 +0,0 @@ -.. image:: https://user-images.githubusercontent.com/ - 576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png - :width: 25% - :alt: {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://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%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 - -.. image:: https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge - :target: https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt - -**{fmt}** is an open-source formatting library providing a fast and safe -alternative to C stdio and C++ iostreams. - -If you like this project, please consider donating to one of the funds that -help victims of the war in Ukraine: https://www.stopputin.net/. - -`Documentation <https://fmt.dev>`__ - -`Cheat Sheets <https://hackingcpp.com/cpp/libs/fmt.html>`__ - -Q&A: ask questions on `StackOverflow with the tag fmt -<https://stackoverflow.com/questions/tagged/fmt>`_. - -Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_. - -Features --------- - -* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments - for localization -* Implementation of `C++20 std::format - <https://en.cppreference.com/w/cpp/utility/format>`__ and `C++23 std::print - <https://en.cppreference.com/w/cpp/io/print>`__ -* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's - `format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ -* Fast IEEE 754 floating-point formatter with correct rounding, shortness and - round-trip guarantees using the `Dragonbox <https://github.com/jk-jeon/dragonbox>`_ - algorithm -* Portable Unicode support -* Safe `printf implementation - <https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX - extension for positional arguments -* Extensibility: `support for user-defined types - <https://fmt.dev/latest/api.html#formatting-user-defined-types>`_ -* High performance: faster than common standard library implementations of - ``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ - and `Converting a hundred million integers to strings per second - <http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_ -* 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 - <https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed - <https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20 - Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_ -* Safety: the library is fully type-safe, errors in format strings can be - reported at compile time, automatic memory management prevents buffer overflow - errors -* Ease of use: small self-contained code base, no external dependencies, - permissive MIT `license - <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_ -* `Portability <https://fmt.dev/latest/index.html#portability>`_ with - consistent output across platforms and support for older compilers -* Clean warning-free codebase even on high warning levels such as - ``-Wall -Wextra -pedantic`` -* Locale independence by default -* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro - -See the `documentation <https://fmt.dev>`_ for more details. - -Examples --------- - -**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_) - -.. code:: c++ - - #include <fmt/core.h> - - int main() { - fmt::print("Hello, world!\n"); - } - -**Format a string** (`run <https://godbolt.org/z/oK8h33>`_) - -.. code:: c++ - - std::string s = fmt::format("The answer is {}.", 42); - // s == "The answer is 42." - -**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_) - -.. 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 chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_) - -.. code:: c++ - - #include <fmt/chrono.h> - - int main() { - 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); - } - -Output:: - - Default format: 42s 100ms - strftime-like format: 03:15:30 - -**Print a container** (`run <https://godbolt.org/z/MxM1YqjE7>`_) - -.. code:: c++ - - #include <vector> - #include <fmt/ranges.h> - - int main() { - std::vector<int> v = {1, 2, 3}; - fmt::print("{}\n", v); - } - -Output:: - - [1, 2, 3] - -**Check a format string at compile time** - -.. code:: c++ - - std::string s = fmt::format("{:d}", "I am not a number"); - -This gives a compile-time error in C++20 because ``d`` is an invalid format -specifier for a string. - -**Write a file from a single thread** - -.. code:: c++ - - #include <fmt/os.h> - - int main() { - auto out = fmt::output_file("guide.txt"); - out.print("Don't {}", "Panic"); - } - -This can be `5 to 9 times faster than fprintf -<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_. - -**Print with colors and text styles** - -.. code:: c++ - - #include <fmt/color.h> - - 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, "Olá, {}!\n", "Mundo"); - fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, - "你好{}!\n", "世界"); - } - -Output on a modern terminal with Unicode support: - -.. image:: https://github.com/fmtlib/fmt/assets/ - 576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7 - -Benchmarks ----------- - -Speed tests -~~~~~~~~~~~ - -================= ============= =========== -Library Method Run Time, s -================= ============= =========== -libc printf 0.91 -libc++ std::ostream 2.49 -{fmt} 9.1 fmt::print 0.74 -Boost Format 1.80 boost::format 6.26 -Folly Format folly::format 1.87 -================= ============= =========== - -{fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``. - -The above results were generated by building ``tinyformat_test.cpp`` on macOS -12.6.1 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the -best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"`` -or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for -further details refer to the `source -<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_. - -{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on -IEEE754 ``float`` and ``double`` formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_) -and faster than `double-conversion <https://github.com/google/double-conversion>`_ and -`ryu <https://github.com/ulfjack/ryu>`_: - -.. 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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The script `bloat-test.py -<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_ -from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_ -tests compile time and code bloat for nontrivial projects. -It generates 100 translation units and uses ``printf()`` or its alternative -five times in each to simulate a medium-sized project. The resulting -executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42), -macOS Sierra, best of three) is shown in the following tables. - -**Optimized build (-O3)** - -============= =============== ==================== ================== -Method Compile Time, s Executable size, KiB Stripped size, KiB -============= =============== ==================== ================== -printf 2.6 29 26 -printf+string 16.4 29 26 -iostreams 31.1 59 55 -{fmt} 19.0 37 34 -Boost Format 91.9 226 203 -Folly Format 115.7 101 88 -============= =============== ==================== ================== - -As you can see, {fmt} has 60% less overhead in terms of resulting binary code -size compared to iostreams and comes pretty close to ``printf``. Boost Format -and Folly Format have the largest overheads. - -``printf+string`` is the same as ``printf`` but with an extra ``<string>`` -include to measure the overhead of the latter. - -**Non-optimized build** - -============= =============== ==================== ================== -Method Compile Time, s Executable size, KiB Stripped size, KiB -============= =============== ==================== ================== -printf 2.2 33 30 -printf+string 16.0 33 30 -iostreams 28.3 56 52 -{fmt} 18.2 59 50 -Boost Format 54.1 365 303 -Folly Format 79.9 445 430 -============= =============== ==================== ================== - -``libc``, ``lib(std)c++``, and ``libfmt`` are all linked as shared libraries to -compare formatting function overhead only. Boost Format is a -header-only library so it doesn't provide any linkage options. - -Running the tests -~~~~~~~~~~~~~~~~~ - -Please refer to `Building the library`__ for instructions on how to build -the library and run the unit tests. - -__ https://fmt.dev/latest/usage.html#building-the-library - -Benchmarks reside in a separate repository, -`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_, -so to run the benchmarks you first need to clone this repository and -generate Makefiles with CMake:: - - $ git clone --recursive https://github.com/fmtlib/format-benchmark.git - $ cd format-benchmark - $ cmake . - -Then you can run the speed test:: - - $ make speed-test - -or the bloat test:: - - $ make bloat-test - -Migrating code --------------- - -`clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`_ v17 (not yet -released) provides the `modernize-use-std-print -<https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html>`_ -check that is capable of converting occurrences of ``printf`` and -``fprintf`` to ``fmt::print`` if configured to do so. (By default it -converts to ``std::print``.) - -Projects using this library ---------------------------- - -* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform - real-time strategy game - -* `AMPL/MP <https://github.com/ampl/mp>`_: - an open-source library for mathematical programming - -* `Aseprite <https://github.com/aseprite/aseprite>`_: - animated sprite editor & pixel art tool - -* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft - operations suite - -* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform - -* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space - -* `Ceph <https://ceph.com/>`_: a scalable distributed storage system - -* `ccache <https://ccache.dev/>`_: a compiler cache - -* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: an analytical database - management system - -* `Contour <https://github.com/contour-terminal/contour/>`_: a modern terminal emulator - -* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater - vehicle - -* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox - for nonlinear dynamical systems (MIT) - -* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus - (Lyft) - -* `FiveM <https://fivem.net/>`_: a modification framework for GTA V - -* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style - logging library with latency in nanoseconds - -* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library - -* `GemRB <https://gemrb.org/>`_: a portable open-source implementation of - Bioware’s Infinity Engine - -* `Grand Mountain Adventure - <https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/>`_: - a beautiful open-world ski & snowboarding game - -* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_: - Player vs Player Gaming Network with tweaks - -* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server - engine - -* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows - -* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software - -* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node - -* `libunicode <https://github.com/contour-terminal/libunicode/>`_: a modern C++17 Unicode library - -* `MariaDB <https://mariadb.org/>`_: relational database management system - -* `Microsoft Verona <https://github.com/microsoft/verona>`_: - research programming language for concurrent ownership - -* `MongoDB <https://mongodb.com/>`_: distributed document database - -* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to - generate randomized datasets - -* `OpenSpace <https://openspaceproject.com/>`_: an open-source - astrovisualization framework - -* `PenUltima Online (POL) <https://www.polserver.com/>`_: - an MMO server, compatible with most Ultima Online clients - -* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine - learning library - -* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance, - associative database - -* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library - -* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify - navigation, and executing complex multi-line terminal command sequences - -* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster - proxy - -* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement - for mission-critical systems written in C++ - -* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client - library - -* `Salesforce Analytics Cloud - <https://www.salesforce.com/analytics-cloud/overview/>`_: - business intelligence software - -* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store - that can handle 1 million transactions per second on a single server - -* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++ - framework for high-performance server applications on modern hardware - -* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library - -* `Stellar <https://www.stellar.org/>`_: financial platform - -* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator - -* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source - MMORPG framework - -* `🐙 userver framework <https://userver.tech/>`_: open-source asynchronous - framework with a rich set of abstractions and database drivers - -* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows - terminal - -`More... <https://github.com/search?q=fmtlib&type=Code>`_ - -If you are aware of other projects using this library, please let me know -by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an -`issue <https://github.com/fmtlib/fmt/issues>`_. - -Motivation ----------- - -So why yet another formatting library? - -There are plenty of methods for doing this task, from standard ones like -the printf family of function and iostreams to Boost Format and FastFormat -libraries. The reason for creating a new library is that every existing -solution that I found either had serious issues or didn't provide -all the features I needed. - -printf -~~~~~~ - -The good thing about ``printf`` is that it is pretty fast and readily available -being a part of the C standard library. The main drawback is that it -doesn't support user-defined types. ``printf`` also has safety issues although -they are somewhat mitigated with `__attribute__ ((format (printf, ...)) -<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC. -There is a POSIX extension that adds positional arguments required for -`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_ -to ``printf`` but it is not a part of C99 and may not be available on some -platforms. - -iostreams -~~~~~~~~~ - -The main issue with iostreams is best illustrated with an example: - -.. code:: c++ - - std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; - -which is a lot of typing compared to printf: - -.. code:: c++ - - printf("%.2f\n", 1.23456); - -Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams -don't support positional arguments by design. - -The good part is that iostreams support user-defined types and are safe although -error handling is awkward. - -Boost Format -~~~~~~~~~~~~ - -This is a very powerful library that 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 -Format also has excessive build times and severe code bloat issues (see -`Benchmarks`_). - -FastFormat -~~~~~~~~~~ - -This is an interesting library that is fast, safe, and has positional arguments. -However, it has significant limitations, citing its author: - - Three features that have no hope of being accommodated within the - current design are: - - * Leading zeros (or any other non-space padding) - * Octal/hexadecimal encoding - * Runtime width/alignment specification - -It is also quite big and has a heavy dependency, STLSoft, which might be too -restrictive for using it in some projects. - -Boost Spirit.Karma -~~~~~~~~~~~~~~~~~~ - -This is not a formatting library but I decided to include it here for -completeness. As iostreams, it suffers from the problem of mixing verbatim text -with arguments. The library is pretty fast, but slower on integer formatting -than ``fmt::format_to`` with format string compilation on Karma's own benchmark, -see `Converting a hundred million integers to strings per second -<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_. - -License -------- - -{fmt} is distributed under the MIT `license -<https://github.com/fmtlib/fmt/blob/master/LICENSE>`_. - -Documentation License ---------------------- - -The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_ -section in the documentation is based on the one from Python `string module -documentation <https://docs.python.org/3/library/string.html#module-string>`_. -For this reason, the documentation is distributed under the Python Software -Foundation license available in `doc/python-license.txt -<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_. -It only applies if you distribute the documentation of {fmt}. - -Maintainers ------------ - -The {fmt} library is maintained by Victor Zverovich (`vitaut -<https://github.com/vitaut>`_) with contributions from many other people. -See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and -`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names. -Let us know if your contribution is not listed or mentioned incorrectly and -we'll make it right. - -Security Policy ---------------- - -To report a security issue, please disclose it at `security advisory <https://github.com/fmtlib/fmt/security/advisories/new>`_. - -This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. diff --git a/doc/api.rst b/doc/api.rst index 8b8b19c8..31b0328d 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -167,8 +167,8 @@ for example will return ``" blue"``. -The experimental ``nested_formatter`` provides an easy way applying a formatter -to one or more subobjects. +The experimental ``nested_formatter`` provides an easy way of applying a +formatter to one or more subobjects. For example:: @@ -600,7 +600,6 @@ System APIs :members: .. doxygenfunction:: fmt::windows_error - :members: .. _ostream-api: diff --git a/doc/build.py b/doc/build.py index bd10aa84..a6ae8f85 100755 --- a/doc/build.py +++ b/doc/build.py @@ -4,7 +4,12 @@ import errno, os, re, sys from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT -versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0', '10.0.0', '10.1.0', '10.1.1', '10.1.1'] +versions = [ + '1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', + '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', + '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', + '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0'] +versions += ['10.0.0', '10.1.0', '10.1.1', '10.1.1', '10.2.0'] class Pip: def __init__(self, venv_dir): @@ -31,7 +36,7 @@ def create_build_env(venv_dir='virtualenv'): # Jinja2 >= 3.1 incompatible with sphinx 3.3.0 # See: https://github.com/sphinx-doc/sphinx/issues/10291 pip.install('Jinja2<3.1') - pip.install('sphinx-doc/sphinx', 'v3.3.0') + pip.install('sphinx==3.3.0') pip.install('michaeljones/breathe', 'v4.25.0') def build_docs(version='dev', **kwargs): @@ -50,7 +55,7 @@ def build_docs(version='dev', **kwargs): GENERATE_RTF = NO CASE_SENSE_NAMES = NO INPUT = {0}/args.h {0}/chrono.h {0}/color.h {0}/core.h \ - {0}/compile.h {0}/format.h {0}/os.h {0}/ostream.h \ + {0}/compile.h {0}/format.h {0}/os.h {0}/ostream.h \ {0}/printf.h {0}/xchar.h QUIET = YES JAVADOC_AUTOBRIEF = YES diff --git a/doc/syntax.rst b/doc/syntax.rst index 74b64c5a..3c21902c 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -82,7 +82,7 @@ The general form of a *standard format specifier* is: width: `integer` | "{" [`arg_id`] "}" precision: `integer` | "{" [`arg_id`] "}" type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | - : "o" | "p" | "s" | "x" | "X" + : "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 @@ -177,6 +177,9 @@ The available string presentation types are: | ``'s'`` | String format. This is the default type for strings and | | | may be omitted. | +---------+----------------------------------------------------------+ +| ``'?'`` | Debug format. The string is quoted and special | +| | characters escaped. | ++---------+----------------------------------------------------------+ | none | The same as ``'s'``. | +---------+----------------------------------------------------------+ @@ -188,6 +191,9 @@ The available character presentation types are: | ``'c'`` | Character format. This is the default type for | | | characters and may be omitted. | +---------+----------------------------------------------------------+ +| ``'?'`` | Debug format. The character is quoted and special | +| | characters escaped. | ++---------+----------------------------------------------------------+ | none | The same as ``'c'``. | +---------+----------------------------------------------------------+ @@ -223,9 +229,10 @@ The available integer presentation types are: | none | The same as ``'d'``. | +---------+----------------------------------------------------------+ -Integer presentation types can also be used with character and Boolean values. -Boolean values are formatted using textual representation, either ``true`` or -``false``, if the presentation type is not specified. +Integer presentation types can also be used with character and Boolean values +with the only exception that ``'c'`` cannot be used with `bool`. Boolean values +are formatted using textual representation, either ``true`` or ``false``, if the +presentation type is not specified. The available presentation types for floating-point values are: diff --git a/include/fmt/args.h b/include/fmt/args.h index 2d684e7c..ad1654bb 100644 --- a/include/fmt/args.h +++ b/include/fmt/args.h @@ -22,8 +22,9 @@ template <typename T> struct is_reference_wrapper : std::false_type {}; template <typename T> struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {}; -template <typename T> const T& unwrap(const T& v) { return v; } -template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) { +template <typename T> auto unwrap(const T& v) -> const T& { return v; } +template <typename T> +auto unwrap(const std::reference_wrapper<T>& v) -> const T& { return static_cast<const T&>(v); } @@ -50,7 +51,7 @@ class dynamic_arg_list { std::unique_ptr<node<>> head_; public: - template <typename T, typename Arg> const T& push(const Arg& arg) { + template <typename T, typename Arg> auto push(const Arg& arg) -> const T& { auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg)); auto& value = new_node->value; new_node->next = std::move(head_); @@ -110,14 +111,14 @@ class dynamic_format_arg_store friend class basic_format_args<Context>; - unsigned long long get_types() const { + auto get_types() const -> unsigned long long { return detail::is_unpacked_bit | data_.size() | (named_info_.empty() ? 0ULL : static_cast<unsigned long long>(detail::has_named_args_bit)); } - const basic_format_arg<Context>* data() const { + auto data() const -> const basic_format_arg<Context>* { return named_info_.empty() ? data_.data() : data_.data() + 1; } diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 7bd4206d..9d54574e 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -18,7 +18,7 @@ #include <ostream> #include <type_traits> -#include "format.h" +#include "ostream.h" // formatbuf FMT_BEGIN_NAMESPACE @@ -72,7 +72,8 @@ template <typename To, typename From, FMT_ENABLE_IF(!std::is_same<From, To>::value && std::numeric_limits<From>::is_signed == std::numeric_limits<To>::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits<From>; using T = std::numeric_limits<To>; @@ -101,7 +102,8 @@ template <typename To, typename From, FMT_ENABLE_IF(!std::is_same<From, To>::value && std::numeric_limits<From>::is_signed != std::numeric_limits<To>::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits<From>; using T = std::numeric_limits<To>; @@ -133,7 +135,8 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { template <typename To, typename From, FMT_ENABLE_IF(std::is_same<From, To>::value)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; return from; } // function @@ -154,7 +157,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // clang-format on template <typename To, typename From, FMT_ENABLE_IF(!std::is_same<From, To>::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; using T = std::numeric_limits<To>; static_assert(std::is_floating_point<From>::value, "From must be floating"); @@ -176,7 +179,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { template <typename To, typename From, FMT_ENABLE_IF(std::is_same<From, To>::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; static_assert(std::is_floating_point<From>::value, "From must be floating"); return from; @@ -188,8 +191,8 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { template <typename To, typename FromRep, typename FromPeriod, FMT_ENABLE_IF(std::is_integral<FromRep>::value), FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)> -To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, - int& ec) { +auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, + int& ec) -> To { using From = std::chrono::duration<FromRep, FromPeriod>; ec = 0; // the basic idea is that we need to convert from count() in the from type @@ -240,8 +243,8 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, template <typename To, typename FromRep, typename FromPeriod, FMT_ENABLE_IF(std::is_floating_point<FromRep>::value), FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)> -To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, - int& ec) { +auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, + int& ec) -> To { using From = std::chrono::duration<FromRep, FromPeriod>; ec = 0; if (std::isnan(from.count())) { @@ -321,12 +324,12 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, namespace detail { template <typename T = void> 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 localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } +inline auto localtime_s(...) -> null<> { return null<>(); } +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } -inline const std::locale& get_classic_locale() { +inline auto get_classic_locale() -> const std::locale& { static const auto& locale = std::locale::classic(); return locale; } @@ -406,8 +409,7 @@ inline void do_write(buffer<Char>& buf, const std::tm& time, auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf); auto&& os = std::basic_ostream<Char>(&format_buf); os.imbue(loc); - using iterator = std::ostreambuf_iterator<Char>; - const auto& facet = std::use_facet<std::time_put<Char, iterator>>(loc); + const auto& facet = std::use_facet<std::time_put<Char>>(loc); auto end = facet.put(os, os, Char(' '), &time, format, modifier); if (end.failed()) FMT_THROW(format_error("failed to format time")); } @@ -430,6 +432,51 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); } +template <typename Rep1, typename Rep2> +struct is_same_arithmetic_type + : public std::integral_constant<bool, + (std::is_integral<Rep1>::value && + std::is_integral<Rep2>::value) || + (std::is_floating_point<Rep1>::value && + std::is_floating_point<Rep2>::value)> { +}; + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(is_same_arithmetic_type<FromRep, typename To::rep>::value)> +auto fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { +#if FMT_SAFE_DURATION_CAST + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast<To>(from, ec); + if (ec) FMT_THROW(format_error("cannot format duration")); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast<To>(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)> +auto fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { + // Mixed integer <-> float cast is not supported by safe_duration_cast. + return std::chrono::duration_cast<To>(from); +} + +template <typename Duration> +auto to_time_t( + std::chrono::time_point<std::chrono::system_clock, Duration> time_point) + -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return fmt_duration_cast<std::chrono::duration<std::time_t>>( + time_point.time_since_epoch()) + .count(); +} } // namespace detail FMT_BEGIN_EXPORT @@ -439,29 +486,29 @@ FMT_BEGIN_EXPORT expressed in local time. Unlike ``std::localtime``, this function is thread-safe on most platforms. */ -inline std::tm localtime(std::time_t time) { +inline auto localtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; dispatcher(std::time_t t) : time_(t) {} - bool run() { + auto run() -> bool { using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION - bool fallback(detail::null<>) { + auto fallback(detail::null<>) -> bool { using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; @@ -478,8 +525,8 @@ inline std::tm localtime(std::time_t time) { #if FMT_USE_LOCAL_TIME template <typename Duration> inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm { - return localtime(std::chrono::system_clock::to_time_t( - std::chrono::current_zone()->to_sys(time))); + return localtime( + detail::to_time_t(std::chrono::current_zone()->to_sys(time))); } #endif @@ -488,29 +535,29 @@ inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm { 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) { +inline auto gmtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; dispatcher(std::time_t t) : time_(t) {} - bool run() { + auto run() -> bool { using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION - bool fallback(detail::null<>) { + auto fallback(detail::null<>) -> bool { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; @@ -523,9 +570,11 @@ inline std::tm gmtime(std::time_t time) { return gt.tm_; } -inline std::tm gmtime( - std::chrono::time_point<std::chrono::system_clock> time_point) { - return gmtime(std::chrono::system_clock::to_time_t(time_point)); +template <typename Duration> +inline auto gmtime( + std::chrono::time_point<std::chrono::system_clock, Duration> time_point) + -> std::tm { + return gmtime(detail::to_time_t(time_point)); } namespace detail { @@ -564,7 +613,8 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b, } } -template <typename Period> FMT_CONSTEXPR inline const char* get_units() { +template <typename Period> +FMT_CONSTEXPR inline auto get_units() -> const char* { if (std::is_same<Period, std::atto>::value) return "as"; if (std::is_same<Period, std::femto>::value) return "fs"; if (std::is_same<Period, std::pico>::value) return "ps"; @@ -620,9 +670,8 @@ auto write_padding(OutputIt out, pad_type pad) -> OutputIt { // Parses a put_time-like format string and invokes handler actions. template <typename Char, typename Handler> -FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, - const Char* end, - Handler&& handler) { +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { if (begin == end || *begin == '}') return begin; if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; @@ -953,25 +1002,25 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> { FMT_CONSTEXPR void on_tz_name() {} }; -inline const char* tm_wday_full_name(int wday) { +inline auto tm_wday_full_name(int wday) -> const char* { static constexpr const char* full_name_list[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; } -inline const char* tm_wday_short_name(int wday) { +inline auto tm_wday_short_name(int wday) -> const char* { static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; } -inline const char* tm_mon_full_name(int mon) { +inline auto tm_mon_full_name(int mon) -> const char* { static constexpr const char* full_name_list[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; } -inline const char* tm_mon_short_name(int mon) { +inline auto tm_mon_short_name(int mon) -> const char* { static constexpr const char* short_name_list[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", @@ -1003,21 +1052,21 @@ inline void tzset_once() { // Converts value to Int and checks that it's in the range [0, upper). template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)> -inline Int to_nonnegative_int(T value, Int upper) { - FMT_ASSERT(std::is_unsigned<Int>::value || - (value >= 0 && to_unsigned(value) <= to_unsigned(upper)), - "invalid value"); - (void)upper; +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned<Int>::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(fmt::format_error("chrono value is out of range")); + } return static_cast<Int>(value); } template <typename T, typename Int, FMT_ENABLE_IF(!std::is_integral<T>::value)> -inline Int to_nonnegative_int(T value, Int upper) { +inline auto to_nonnegative_int(T value, Int upper) -> Int { if (value < 0 || value > static_cast<T>(upper)) FMT_THROW(format_error("invalid value")); return static_cast<Int>(value); } -constexpr long long pow10(std::uint32_t n) { +constexpr auto pow10(std::uint32_t n) -> long long { return n == 0 ? 1 : 10 * pow10(n - 1); } @@ -1051,13 +1100,12 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { std::chrono::seconds::rep>::type, std::ratio<1, detail::pow10(num_fractional_digits)>>; - const auto fractional = - d - std::chrono::duration_cast<std::chrono::seconds>(d); + const auto fractional = d - fmt_duration_cast<std::chrono::seconds>(d); const auto subseconds = std::chrono::treat_as_floating_point< typename subsecond_precision::rep>::value ? fractional.count() - : std::chrono::duration_cast<subsecond_precision>(fractional).count(); + : fmt_duration_cast<subsecond_precision>(fractional).count(); auto n = static_cast<uint32_or_64_or_128_t<long long>>(subseconds); const int num_digits = detail::count_digits(n); @@ -1108,11 +1156,11 @@ void write_floating_seconds(memory_buffer& buf, Duration duration, num_fractional_digits = 6; } - format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), - std::fmod(val * static_cast<rep>(Duration::period::num) / - static_cast<rep>(Duration::period::den), - static_cast<rep>(60)), - num_fractional_digits); + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast<rep>(Duration::period::num) / + static_cast<rep>(Duration::period::den), + static_cast<rep>(60)), + num_fractional_digits); } template <typename OutputIt, typename Char, @@ -1173,8 +1221,7 @@ class tm_writer { return static_cast<int>(l); } - // Algorithm: - // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. auto iso_year_weeks(long long curr_year) const noexcept -> int { const auto prev_year = curr_year - 1; const auto curr_p = @@ -1314,7 +1361,7 @@ class tm_writer { subsecs_(subsecs), tm_(tm) {} - OutputIt out() const { return out_; } + auto out() const -> OutputIt { return out_; } FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { out_ = copy_str<Char>(begin, end, out_); @@ -1578,6 +1625,7 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> { template <typename Char> FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year() {} FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} @@ -1596,16 +1644,16 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> { template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value&& has_isfinite<T>::value)> -inline bool isfinite(T) { +inline auto isfinite(T) -> bool { return true; } template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return x % static_cast<T>(y); } template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return std::fmod(x, static_cast<T>(y)); } @@ -1620,49 +1668,38 @@ template <typename T> struct make_unsigned_or_unchanged<T, true> { using type = typename std::make_unsigned<T>::type; }; -#if FMT_SAFE_DURATION_CAST -// throwing version of safe_duration_cast -template <typename To, typename FromRep, typename FromPeriod> -To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) { - int ec; - To to = safe_duration_cast::safe_duration_cast<To>(from, ec); - if (ec) FMT_THROW(format_error("cannot format duration")); - return to; -} -#endif - template <typename Rep, typename Period, FMT_ENABLE_IF(std::is_integral<Rep>::value)> -inline std::chrono::duration<Rep, std::milli> get_milliseconds( - std::chrono::duration<Rep, Period> d) { +inline auto get_milliseconds(std::chrono::duration<Rep, Period> d) + -> std::chrono::duration<Rep, std::milli> { // this may overflow and/or the result may not fit in the // target type. #if FMT_SAFE_DURATION_CAST using CommonSecondsType = typename std::common_type<decltype(d), std::chrono::seconds>::type; - const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d); + const auto d_as_common = fmt_duration_cast<CommonSecondsType>(d); const auto d_as_whole_seconds = - fmt_safe_duration_cast<std::chrono::seconds>(d_as_common); + fmt_duration_cast<std::chrono::seconds>(d_as_common); // this conversion should be nonproblematic const auto diff = d_as_common - d_as_whole_seconds; const auto ms = - fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff); + fmt_duration_cast<std::chrono::duration<Rep, std::milli>>(diff); return ms; #else - auto s = std::chrono::duration_cast<std::chrono::seconds>(d); - return std::chrono::duration_cast<std::chrono::milliseconds>(d - s); + auto s = fmt_duration_cast<std::chrono::seconds>(d); + return fmt_duration_cast<std::chrono::milliseconds>(d - s); #endif } template <typename Char, typename Rep, typename OutputIt, FMT_ENABLE_IF(std::is_integral<Rep>::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int) { +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { return write<Char>(out, val); } template <typename Char, typename Rep, typename OutputIt, FMT_ENABLE_IF(std::is_floating_point<Rep>::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int precision) { +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { auto specs = format_specs<Char>(); specs.precision = precision; specs.type = precision >= 0 ? presentation_type::fixed_lower @@ -1671,12 +1708,12 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) { } template <typename Char, typename OutputIt> -OutputIt copy_unit(string_view unit, OutputIt out, Char) { +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { return std::copy(unit.begin(), unit.end(), out); } template <typename OutputIt> -OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { // This works when wchar_t is UTF-32 because units only contain characters // that have the same representation in UTF-16 and UTF-32. utf8_to_utf16 u(unit); @@ -1684,7 +1721,7 @@ OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { } template <typename Char, typename Period, typename OutputIt> -OutputIt format_duration_unit(OutputIt out) { +auto format_duration_unit(OutputIt out) -> OutputIt { if (const char* unit = get_units<Period>()) return copy_unit(string_view(unit), out, Char()); *out++ = '['; @@ -1751,18 +1788,12 @@ struct chrono_formatter { // this may overflow and/or the result may not fit in the // target type. -#if FMT_SAFE_DURATION_CAST // might need checked conversion (rep!=Rep) - auto tmpval = std::chrono::duration<rep, Period>(val); - s = fmt_safe_duration_cast<seconds>(tmpval); -#else - s = std::chrono::duration_cast<seconds>( - std::chrono::duration<rep, Period>(val)); -#endif + s = fmt_duration_cast<seconds>(std::chrono::duration<rep, Period>(val)); } // returns true if nan or inf, writes to out. - bool handle_nan_inf() { + auto handle_nan_inf() -> bool { if (isfinite(val)) { return false; } @@ -1779,17 +1810,22 @@ struct chrono_formatter { return true; } - Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); } + auto days() const -> Rep { return static_cast<Rep>(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast<Rep>(mod((s.count() / 3600), 24)); + } - Rep hour12() const { + auto hour12() const -> Rep { Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12)); return hour <= 0 ? 12 : hour; } - Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); } - Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); } + auto minute() const -> Rep { + return static_cast<Rep>(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast<Rep>(mod(s.count(), 60)); } - std::tm time() const { + auto time() const -> std::tm { auto time = std::tm(); time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_min = to_nonnegative_int(minute(), 60); @@ -1857,10 +1893,14 @@ struct chrono_formatter { void on_dec0_week_of_year(numeric_system) {} void on_dec1_week_of_year(numeric_system) {} void on_iso_week_of_year(numeric_system) {} - void on_day_of_year() {} void on_day_of_month(numeric_system) {} void on_day_of_month_space(numeric_system) {} + void on_day_of_year() { + if (handle_nan_inf()) return; + write(days(), 0); + } + void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; @@ -1967,7 +2007,7 @@ class weekday { weekday() = default; explicit constexpr weekday(unsigned wd) noexcept : value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {} - constexpr unsigned c_encoding() const noexcept { return value; } + constexpr auto c_encoding() const noexcept -> unsigned { return value; } }; class year_month_day {}; @@ -2082,25 +2122,22 @@ struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>, period::num != 1 || period::den != 1 || std::is_floating_point<typename Duration::rep>::value)) { const auto epoch = val.time_since_epoch(); - auto subsecs = std::chrono::duration_cast<Duration>( - epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch)); + auto subsecs = detail::fmt_duration_cast<Duration>( + epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch)); if (subsecs.count() < 0) { auto second = - std::chrono::duration_cast<Duration>(std::chrono::seconds(1)); + detail::fmt_duration_cast<Duration>(std::chrono::seconds(1)); if (epoch.count() < ((Duration::min)() + second).count()) FMT_THROW(format_error("duration is too small")); subsecs += second; val -= second; } - return formatter<std::tm, Char>::do_format( - gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx, - &subsecs); + return formatter<std::tm, Char>::do_format(gmtime(val), ctx, &subsecs); } - return formatter<std::tm, Char>::format( - gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx); + return formatter<std::tm, Char>::format(gmtime(val), ctx); } }; @@ -2119,17 +2156,13 @@ struct formatter<std::chrono::local_time<Duration>, Char> if (period::num != 1 || period::den != 1 || std::is_floating_point<typename Duration::rep>::value) { const auto epoch = val.time_since_epoch(); - const auto subsecs = std::chrono::duration_cast<Duration>( - epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch)); + const auto subsecs = detail::fmt_duration_cast<Duration>( + epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch)); - return formatter<std::tm, Char>::do_format( - localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), - ctx, &subsecs); + return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs); } - return formatter<std::tm, Char>::format( - localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), - ctx); + return formatter<std::tm, Char>::format(localtime(val), ctx); } }; #endif diff --git a/include/fmt/color.h b/include/fmt/color.h index 57b7f578..367849a8 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -233,7 +233,7 @@ class text_style { FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept : set_foreground_color(), set_background_color(), ems(em) {} - FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { if (!set_foreground_color) { set_foreground_color = rhs.set_foreground_color; foreground_color = rhs.foreground_color; @@ -257,29 +257,29 @@ class text_style { return *this; } - friend FMT_CONSTEXPR text_style operator|(text_style lhs, - const text_style& rhs) { + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { return lhs |= rhs; } - FMT_CONSTEXPR bool has_foreground() const noexcept { + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { return set_foreground_color; } - FMT_CONSTEXPR bool has_background() const noexcept { + FMT_CONSTEXPR auto has_background() const noexcept -> bool { return set_background_color; } - FMT_CONSTEXPR bool has_emphasis() const noexcept { + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { return static_cast<uint8_t>(ems) != 0; } - FMT_CONSTEXPR detail::color_type get_foreground() const noexcept { + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return foreground_color; } - FMT_CONSTEXPR detail::color_type get_background() const noexcept { + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_ASSERT(has_background(), "no background specified for this style"); return background_color; } - FMT_CONSTEXPR emphasis get_emphasis() const noexcept { + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); return ems; } @@ -297,9 +297,11 @@ class text_style { } } - friend FMT_CONSTEXPR text_style fg(detail::color_type foreground) noexcept; + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; - friend FMT_CONSTEXPR text_style bg(detail::color_type background) noexcept; + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; detail::color_type foreground_color; detail::color_type background_color; @@ -309,16 +311,19 @@ class text_style { }; /** Creates a text style from the foreground (text) color. */ -FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) noexcept { +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { return text_style(true, foreground); } /** Creates a text style from the background color. */ -FMT_CONSTEXPR inline text_style bg(detail::color_type background) noexcept { +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { return text_style(false, background); } -FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept { +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { return text_style(lhs) | rhs; } @@ -384,8 +389,8 @@ template <typename Char> struct ansi_color_escape { } FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } - FMT_CONSTEXPR const Char* begin() const noexcept { return buffer; } - FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const noexcept { + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR_CHAR_TRAITS auto end() const noexcept -> const Char* { return buffer + std::char_traits<Char>::length(buffer); } @@ -400,25 +405,27 @@ template <typename Char> struct ansi_color_escape { out[2] = static_cast<Char>('0' + c % 10); out[3] = static_cast<Char>(delimiter); } - static FMT_CONSTEXPR bool has_emphasis(emphasis em, emphasis mask) noexcept { + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask); } }; template <typename Char> -FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( - detail::color_type foreground) noexcept { +FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept + -> ansi_color_escape<Char> { return ansi_color_escape<Char>(foreground, "\x1b[38;2;"); } template <typename Char> -FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( - detail::color_type background) noexcept { +FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept + -> ansi_color_escape<Char> { return ansi_color_escape<Char>(background, "\x1b[48;2;"); } template <typename Char> -FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept { +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape<Char> { return ansi_color_escape<Char>(em); } @@ -511,9 +518,10 @@ void print(const text_style& ts, const S& format_str, const Args&... args) { } template <typename S, typename Char = char_t<S>> -inline std::basic_string<Char> vformat( +inline auto vformat( const text_style& ts, const S& format_str, - basic_format_args<buffer_context<type_identity_t<Char>>> args) { + basic_format_args<buffer_context<type_identity_t<Char>>> args) + -> std::basic_string<Char> { basic_memory_buffer<Char> buf; detail::vformat_to(buf, ts, detail::to_string_view(format_str), args); return fmt::to_string(buf); @@ -532,8 +540,8 @@ inline std::basic_string<Char> vformat( \endrst */ template <typename S, typename... Args, typename Char = char_t<S>> -inline std::basic_string<Char> format(const text_style& ts, const S& format_str, - const Args&... args) { +inline auto format(const text_style& ts, const S& format_str, + const Args&... args) -> std::basic_string<Char> { return fmt::vformat(ts, detail::to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); } @@ -543,9 +551,10 @@ inline std::basic_string<Char> format(const text_style& ts, const S& format_str, */ template <typename OutputIt, typename Char, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)> -OutputIt vformat_to( - OutputIt out, const text_style& ts, basic_string_view<Char> format_str, - basic_format_args<buffer_context<type_identity_t<Char>>> args) { +auto vformat_to(OutputIt out, const text_style& ts, + basic_string_view<Char> format_str, + basic_format_args<buffer_context<type_identity_t<Char>>> args) + -> OutputIt { auto&& buf = detail::get_buffer<Char>(out); detail::vformat_to(buf, ts, format_str, args); return detail::get_iterator(buf, out); @@ -563,9 +572,10 @@ OutputIt vformat_to( fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); \endrst */ -template <typename OutputIt, typename S, typename... Args, - bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&& - detail::is_string<S>::value> +template < + typename OutputIt, typename S, typename... Args, + bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value && + detail::is_string<S>::value> inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, Args&&... args) -> typename std::enable_if<enable, OutputIt>::type { diff --git a/include/fmt/compile.h b/include/fmt/compile.h index a4c7e495..3b3f166e 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -14,8 +14,8 @@ FMT_BEGIN_NAMESPACE namespace detail { template <typename Char, typename InputIt> -FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end, - counting_iterator it) { +FMT_CONSTEXPR inline auto copy_str(InputIt begin, InputIt end, + counting_iterator it) -> counting_iterator { return it + (end - begin); } @@ -57,7 +57,7 @@ struct udl_compiled_string : compiled_string { #endif template <typename T, typename... Tail> -const T& first(const T& value, const Tail&...) { +auto first(const T& value, const Tail&...) -> const T& { return value; } @@ -488,18 +488,19 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { template <typename OutputIt, typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> -format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, - const S& format_str, Args&&... args) { +auto format_to_n(OutputIt out, size_t n, const S& format_str, Args&&... args) + -> format_to_n_result<OutputIt> { using traits = detail::fixed_buffer_traits; auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n); - format_to(std::back_inserter(buf), format_str, std::forward<Args>(args)...); + fmt::format_to(std::back_inserter(buf), format_str, + std::forward<Args>(args)...); return {buf.out(), buf.count()}; } template <typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> -FMT_CONSTEXPR20 size_t formatted_size(const S& format_str, - const Args&... args) { +FMT_CONSTEXPR20 auto formatted_size(const S& format_str, const Args&... args) + -> size_t { return fmt::format_to(detail::counting_iterator(), format_str, args...) .count(); } diff --git a/include/fmt/core.h b/include/fmt/core.h index 18ebada2..94f3f22b 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -18,7 +18,7 @@ #include <type_traits> // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 100101 +#define FMT_VERSION 100200 #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) @@ -105,9 +105,12 @@ # define FMT_CONSTEXPR #endif -#if ((FMT_CPLUSPLUS >= 202002L) && \ - (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ - (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) +#if (FMT_CPLUSPLUS >= 202002L || \ + (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)) && \ + ((!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 10) && \ + (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 10000) && \ + (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1928)) && \ + defined(__cpp_lib_is_constant_evaluated) # define FMT_CONSTEXPR20 constexpr #else # define FMT_CONSTEXPR20 @@ -224,8 +227,9 @@ __apple_build_version__ >= 14000029L) && \ FMT_CPLUSPLUS >= 202002L) || \ (defined(__cpp_consteval) && \ - (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) -// consteval is broken in MSVC before VS2022 and Apple clang before 14. + (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1929)) +// consteval is broken in MSVC before VS2019 version 16.10 and Apple clang +// before 14. # define FMT_CONSTEVAL consteval # define FMT_HAS_CONSTEVAL # else @@ -244,6 +248,15 @@ # endif #endif +// GCC < 5 requires this-> in decltype +#ifndef FMT_DECLTYPE_THIS +# if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +# define FMT_DECLTYPE_THIS this-> +# else +# define FMT_DECLTYPE_THIS +# endif +#endif + // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") #if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) && !defined(__LCC__) && \ @@ -458,15 +471,15 @@ template <typename Char> class basic_string_view { size_ -= n; } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( - basic_string_view<Char> sv) const noexcept { + FMT_CONSTEXPR_CHAR_TRAITS auto starts_with( + basic_string_view<Char> sv) const noexcept -> bool { return size_ >= sv.size_ && std::char_traits<Char>::compare(data_, sv.data_, sv.size_) == 0; } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept { + FMT_CONSTEXPR_CHAR_TRAITS auto starts_with(Char c) const noexcept -> bool { return size_ >= 1 && std::char_traits<Char>::eq(*data_, c); } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { + FMT_CONSTEXPR_CHAR_TRAITS auto starts_with(const Char* s) const -> bool { return starts_with(basic_string_view<Char>(s)); } @@ -604,10 +617,10 @@ FMT_TYPE_CONSTANT(const Char*, cstring_type); FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type); FMT_TYPE_CONSTANT(const void*, pointer_type); -constexpr bool is_integral_type(type t) { +constexpr auto is_integral_type(type t) -> bool { return t > type::none_type && t <= type::last_integer_type; } -constexpr bool is_arithmetic_type(type t) { +constexpr auto is_arithmetic_type(type t) -> bool { return t > type::none_type && t <= type::last_numeric_type; } @@ -631,6 +644,7 @@ enum { pointer_set = set(type::pointer_type) }; +// DEPRECATED! FMT_NORETURN FMT_API void throw_format_error(const char* message); struct error_handler { @@ -808,7 +822,7 @@ template <typename T> class buffer { protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. FMT_MSC_WARNING(suppress : 26495) - buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} + FMT_CONSTEXPR buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept : ptr_(p), size_(sz), capacity_(cap) {} @@ -823,6 +837,7 @@ template <typename T> class buffer { } /** Increases the buffer capacity to hold at least *capacity* elements. */ + // DEPRECATED! virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; public: @@ -1309,6 +1324,7 @@ template <typename Context> class value { parse_ctx.advance_to(f.parse(parse_ctx)); using qualified_type = conditional_t<has_const_formatter<T, Context>(), const T, T>; + // Calling format through a mutable reference is deprecated. ctx.advance_to(f.format(*static_cast<qualified_type*>(arg), ctx)); } }; @@ -1322,7 +1338,7 @@ using ulong_type = conditional_t<long_short, unsigned, unsigned long long>; template <typename T> struct format_as_result { template <typename U, FMT_ENABLE_IF(std::is_enum<U>::value || std::is_class<U>::value)> - static auto map(U*) -> decltype(format_as(std::declval<U>())); + static auto map(U*) -> remove_cvref_t<decltype(format_as(std::declval<U>()))>; static auto map(...) -> void; using type = decltype(map(static_cast<T*>(nullptr))); @@ -1439,7 +1455,8 @@ template <typename Context> struct arg_mapper { // Only map owning types because mapping views can be unsafe. template <typename T, typename U = format_as_t<T>, FMT_ENABLE_IF(std::is_arithmetic<U>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(this->map(U())) { + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(FMT_DECLTYPE_THIS map(U())) { return map(format_as(val)); } @@ -1463,13 +1480,14 @@ template <typename Context> struct arg_mapper { !is_string<U>::value && !is_char<U>::value && !is_named_arg<U>::value && !std::is_arithmetic<format_as_t<U>>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(T& val) -> decltype(this->do_map(val)) { + FMT_CONSTEXPR FMT_INLINE auto map(T& val) + -> decltype(FMT_DECLTYPE_THIS do_map(val)) { return do_map(val); } template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) - -> decltype(this->map(named_arg.value)) { + -> decltype(FMT_DECLTYPE_THIS map(named_arg.value)) { return map(named_arg.value); } @@ -1604,8 +1622,8 @@ FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg<Context> { } // namespace detail FMT_BEGIN_EXPORT -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. template <typename Context> class basic_format_arg { private: detail::value<Context> value_; @@ -1657,6 +1675,15 @@ template <typename Context> class basic_format_arg { auto is_arithmetic() const -> bool { return detail::is_arithmetic_type(type_); } + + FMT_INLINE auto format_custom(const char_type* parse_begin, + typename Context::parse_context_type& parse_ctx, + Context& ctx) -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } }; /** @@ -1745,6 +1772,7 @@ template <typename OutputIt, typename Char> class basic_format_context { } auto args() const -> const format_args& { return args_; } + // DEPRECATED! FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } @@ -2417,6 +2445,8 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( case 'G': return parse_presentation_type(pres::general_upper, float_set); case 'c': + if (arg_type == type::bool_type) + throw_format_error("invalid format specifier"); return parse_presentation_type(pres::chr, integral_set); case 's': return parse_presentation_type(pres::string, diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 5f8c83a2..e9a4ca45 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -58,8 +58,8 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code, error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); auto it = buffer_appender<char>(out); if (message.size() <= inline_buffer_size - error_code_size) - format_to(it, FMT_STRING("{}{}"), message, SEP); - format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); FMT_ASSERT(out.size() <= inline_buffer_size, ""); } @@ -85,7 +85,7 @@ locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same<Locale, std::locale>::value, ""); } -template <typename Locale> Locale locale_ref::get() const { +template <typename Locale> auto locale_ref::get() const -> Locale { static_assert(std::is_same<Locale, std::locale>::value, ""); return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale(); } @@ -97,7 +97,8 @@ FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> { auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); return {std::move(grouping), thousands_sep}; } -template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) { +template <typename Char> +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()) .decimal_point(); } @@ -143,24 +144,25 @@ FMT_API FMT_FUNC auto format_facet<std::locale>::do_put( } #endif -FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt, - format_args args) { +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { auto ec = std::error_code(error_code, std::generic_category()); return std::system_error(ec, vformat(fmt, args)); } namespace detail { -template <typename F> inline bool operator==(basic_fp<F> x, basic_fp<F> y) { +template <typename F> +inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool { return x.f == y.f && x.e == y.e; } // Compilers should be able to optimize this into the ror instruction. -FMT_CONSTEXPR inline uint32_t rotr(uint32_t n, uint32_t r) noexcept { +FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { r &= 31; return (n >> r) | (n << (32 - r)); } -FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { +FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { r &= 63; return (n >> r) | (n << (64 - r)); } @@ -169,14 +171,14 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { namespace dragonbox { // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. -inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept { +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { return umul128_upper64(static_cast<uint64_t>(x) << 32, y); } // Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline uint128_fallback umul192_lower128(uint64_t x, - uint128_fallback y) noexcept { +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { uint64_t high = x * y.high(); uint128_fallback high_low = umul128(x, y.low()); return {high + high_low.high(), high_low.low()}; @@ -184,12 +186,12 @@ inline uint128_fallback umul192_lower128(uint64_t x, // 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) noexcept { +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { return x * y; } // Various fast log computations. -inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } @@ -203,7 +205,7 @@ FMT_INLINE_VARIABLE constexpr struct { // divisible by pow(10, N). // Precondition: n <= pow(10, N + 1). template <int N> -bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept { +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { // The numbers below are chosen such that: // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, // 2. nm mod 2^k < m if and only if n is divisible by d, @@ -228,7 +230,7 @@ bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept { // Computes floor(n / pow(10, N)) for small n and N. // Precondition: n <= pow(10, N + 1). -template <int N> uint32_t small_division_by_pow10(uint32_t n) noexcept { +template <int N> auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { constexpr auto info = div_small_pow10_infos[N - 1]; FMT_ASSERT(n <= info.divisor * 10, "n is too large"); constexpr uint32_t magic_number = @@ -237,12 +239,12 @@ template <int N> uint32_t small_division_by_pow10(uint32_t n) noexcept { } // Computes floor(n / 10^(kappa + 1)) (float) -inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) noexcept { +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { // 1374389535 = ceil(2^37/100) return static_cast<uint32_t>((static_cast<uint64_t>(n) * 1374389535) >> 37); } // Computes floor(n / 10^(kappa + 1)) (double) -inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept { +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { // 2361183241434822607 = ceil(2^(64+7)/1000) return umul128_upper64(n, 2361183241434822607ull) >> 7; } @@ -254,7 +256,7 @@ template <> struct cache_accessor<float> { using carrier_uint = float_info<float>::carrier_uint; using cache_entry_type = uint64_t; - static uint64_t get_cached_power(int k) noexcept { + static auto get_cached_power(int k) noexcept -> uint64_t { FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k, "k is out of range"); static constexpr const uint64_t pow10_significands[] = { @@ -296,20 +298,23 @@ template <> struct cache_accessor<float> { bool is_integer; }; - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { auto r = umul96_upper64(u, cache); return {static_cast<carrier_uint>(r >> 32), static_cast<carrier_uint>(r) == 0}; } - static uint32_t compute_delta(const cache_entry_type& cache, - int beta) noexcept { + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { return static_cast<uint32_t>(cache >> (64 - 1 - beta)); } - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); @@ -318,22 +323,22 @@ template <> struct cache_accessor<float> { static_cast<uint32_t>(r >> (32 - beta)) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast<carrier_uint>( (cache - (cache >> (num_significand_bits<float>() + 2))) >> (64 - num_significand_bits<float>() - 1 - beta)); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast<carrier_uint>( (cache + (cache >> (num_significand_bits<float>() + 1))) >> (64 - num_significand_bits<float>() - 1 - beta)); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (static_cast<carrier_uint>( cache >> (64 - num_significand_bits<float>() - 2 - beta)) + 1) / @@ -345,7 +350,7 @@ template <> struct cache_accessor<double> { using carrier_uint = float_info<double>::carrier_uint; using cache_entry_type = uint128_fallback; - static uint128_fallback get_cached_power(int k) noexcept { + static auto get_cached_power(int k) noexcept -> uint128_fallback { FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k, "k is out of range"); @@ -984,7 +989,7 @@ template <> struct cache_accessor<double> { {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, - {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2} + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1069,19 +1074,22 @@ template <> struct cache_accessor<double> { bool is_integer; }; - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { auto r = umul192_upper128(u, cache); return {r.high(), r.low() == 0}; } - static uint32_t compute_delta(cache_entry_type const& cache, - int beta) noexcept { + static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + -> uint32_t { return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta)); } - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); @@ -1090,35 +1098,35 @@ template <> struct cache_accessor<double> { ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() - (cache.high() >> (num_significand_bits<double>() + 2))) >> (64 - num_significand_bits<double>() - 1 - beta); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() + (cache.high() >> (num_significand_bits<double>() + 1))) >> (64 - num_significand_bits<double>() - 1 - beta); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return ((cache.high() >> (64 - num_significand_bits<double>() - 2 - beta)) + 1) / 2; } }; -FMT_FUNC uint128_fallback get_cached_power(int k) noexcept { +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { return cache_accessor<double>::get_cached_power(k); } // Various integer checks template <typename T> -bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; return exponent >= case_shorter_interval_left_endpoint_lower_threshold && @@ -1232,7 +1240,7 @@ FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept { return ret_value; } -template <typename T> decimal_fp<T> to_decimal(T x) noexcept { +template <typename T> auto to_decimal(T x) noexcept -> decimal_fp<T> { // Step 1: integer promotion & Schubfach multiplier calculation. using carrier_uint = typename float_info<T>::carrier_uint; @@ -1371,15 +1379,15 @@ template <> struct formatter<detail::bigint> { for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { - out = format_to(out, FMT_STRING("{:x}"), value); + out = fmt::format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } - out = format_to(out, FMT_STRING("{:08x}"), value); + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) - out = format_to(out, FMT_STRING("p{}"), - n.exp_ * detail::bigint::bigit_bits); + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); return out; } }; @@ -1415,7 +1423,7 @@ FMT_FUNC void report_system_error(int error_code, report_error(format_system_error, error_code, message); } -FMT_FUNC std::string vformat(string_view fmt, format_args args) { +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { // 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(); @@ -1425,7 +1433,7 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) { namespace detail { #if !defined(_WIN32) || defined(FMT_WINDOWS_NO_WCHAR) -FMT_FUNC bool write_console(int, string_view) { return false; } +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } #else using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // diff --git a/include/fmt/format.h b/include/fmt/format.h index c8e1c46d..f0ca55cf 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -43,7 +43,7 @@ #include <system_error> // std::system_error #ifdef __cpp_lib_bit_cast -# include <bit> // std::bitcast +# include <bit> // std::bit_cast #endif #include "core.h" @@ -277,19 +277,6 @@ FMT_END_NAMESPACE #endif FMT_BEGIN_NAMESPACE - -template <typename...> struct disjunction : std::false_type {}; -template <typename P> struct disjunction<P> : P {}; -template <typename P1, typename... Pn> -struct disjunction<P1, Pn...> - : conditional_t<bool(P1::value), P1, disjunction<Pn...>> {}; - -template <typename...> struct conjunction : std::true_type {}; -template <typename P> struct conjunction<P> : P {}; -template <typename P1, typename... Pn> -struct conjunction<P1, Pn...> - : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {}; - namespace detail { FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { @@ -311,37 +298,6 @@ template <typename CharT, CharT... C> constexpr CharT string_literal<CharT, C...>::value[sizeof...(C)]; #endif -template <typename Streambuf> class formatbuf : public Streambuf { - private: - using char_type = typename Streambuf::char_type; - using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0)); - using int_type = typename Streambuf::int_type; - using traits_type = typename Streambuf::traits_type; - - buffer<char_type>& buffer_; - - public: - explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {} - - protected: - // The put area is always empty. This makes the implementation simpler and has - // the advantage that the streambuf and the buffer are always in sync and - // sputc never writes into uninitialized memory. A disadvantage is that each - // call to sputc always results in a (virtual) call to overflow. There is no - // disadvantage here for sputn since this always results in a call to xsputn. - - auto overflow(int_type ch) -> int_type override { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast<char_type>(ch)); - return ch; - } - - auto xsputn(const char_type* s, streamsize count) -> streamsize override { - buffer_.append(s, s + count); - return count; - } -}; - // Implementation of std::bit_cast for pre-C++20. template <typename To, typename From, FMT_ENABLE_IF(sizeof(To) == sizeof(From))> FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { @@ -377,8 +333,8 @@ class uint128_fallback { constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} - constexpr uint64_t high() const noexcept { return hi_; } - constexpr uint64_t low() const noexcept { return lo_; } + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> constexpr explicit operator T() const { @@ -454,7 +410,7 @@ class uint128_fallback { hi_ &= n.hi_; } - FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { if (is_constant_evaluated()) { lo_ += n; hi_ += (lo_ < n ? 1 : 0); @@ -744,7 +700,7 @@ inline auto compute_width(basic_string_view<Char> s) -> size_t { } // Computes approximate display width of a UTF-8 string. -FMT_CONSTEXPR inline size_t compute_width(string_view s) { +FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { size_t num_code_points = 0; // It is not a lambda for compatibility with C++14. struct count_code_points { @@ -791,12 +747,17 @@ inline auto code_point_index(basic_string_view<Char> s, size_t n) -> size_t { // Calculates the index of the nth code point in a UTF-8 string. inline auto code_point_index(string_view s, size_t n) -> size_t { - const char* 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; - } - return s.size(); + size_t result = s.size(); + const char* begin = s.begin(); + for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { + if (n != 0) { + --n; + return true; + } + result = to_unsigned(sv.begin() - begin); + return false; + }); + return result; } inline auto code_point_index(basic_string_view<char8_type> s, size_t n) @@ -906,7 +867,7 @@ enum { inline_buffer_size = 500 }; **Example**:: auto out = fmt::memory_buffer(); - format_to(std::back_inserter(out), "The answer is {}.", 42); + fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); This will append the following output to the ``out`` object: @@ -1022,7 +983,6 @@ class basic_memory_buffer final : public detail::buffer<T> { /** 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<T>::append; template <typename ContiguousRange> void append(const ContiguousRange& range) { @@ -1038,7 +998,7 @@ struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type { FMT_END_EXPORT namespace detail { -FMT_API bool write_console(int fd, string_view text); +FMT_API auto write_console(int fd, string_view text) -> bool; FMT_API void print(std::FILE*, string_view); } // namespace detail @@ -1157,13 +1117,13 @@ using uint32_or_64_or_128_t = template <typename T> using uint64_or_128_t = conditional_t<num_bits<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 +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 // Converts value in the range [0, 100) to a string. -constexpr const char* digits2(size_t value) { +constexpr auto digits2(size_t value) -> const char* { // GCC generates slightly better code when value is pointer-size. return &"0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" @@ -1173,7 +1133,7 @@ constexpr const char* digits2(size_t value) { } // Sign is a template parameter to workaround a bug in gcc 4.8. -template <typename Char, typename Sign> constexpr Char sign(Sign s) { +template <typename Char, typename Sign> constexpr auto sign(Sign s) -> Char { #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 static_assert(std::is_same<Sign, sign_t>::value, ""); #endif @@ -1434,22 +1394,23 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 { : "invalid utf32")); } operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char* c_str() const { return &buffer_[0]; } - std::string str() const { return std::string(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } // Performs conversion returning a bool instead of throwing exception on // conversion error. This method may still throw in case of memory allocation // error. - bool convert(basic_string_view<WChar> s, - to_utf8_error_policy policy = to_utf8_error_policy::abort) { + auto convert(basic_string_view<WChar> s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { if (!convert(buffer_, s, policy)) return false; buffer_.push_back(0); return true; } - static bool convert( - Buffer& buf, basic_string_view<WChar> s, - to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static auto convert(Buffer& buf, basic_string_view<WChar> s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { for (auto p = s.begin(); p != s.end(); ++p) { uint32_t c = static_cast<uint32_t>(*p); if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { @@ -1485,7 +1446,7 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 { }; // Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { +inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { #if FMT_USE_INT128 auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y); return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)}; @@ -1516,19 +1477,19 @@ inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { namespace dragonbox { // Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from // https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. -inline int floor_log10_pow2(int e) noexcept { +inline auto floor_log10_pow2(int e) noexcept -> int { FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); return (e * 315653) >> 20; } -inline int floor_log2_pow10(int e) noexcept { +inline auto floor_log2_pow10(int e) noexcept -> int { FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); return (e * 1741647) >> 19; } // Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { #if FMT_USE_INT128 auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y); return static_cast<uint64_t>(p >> 64); @@ -1541,14 +1502,14 @@ inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { // Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline uint128_fallback umul192_upper128(uint64_t x, - uint128_fallback y) noexcept { +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { uint128_fallback r = umul128(x, y.high()); r += umul128_upper64(x, y.low()); return r; } -FMT_API uint128_fallback get_cached_power(int k) noexcept; +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; // Type-specific information that Dragonbox uses. template <typename T, typename Enable = void> struct float_info; @@ -1602,14 +1563,14 @@ template <typename T> FMT_API auto to_decimal(T x) noexcept -> decimal_fp<T>; } // namespace dragonbox // Returns true iff Float has the implicit bit which is not stored. -template <typename Float> constexpr bool has_implicit_bit() { +template <typename Float> constexpr auto has_implicit_bit() -> bool { // An 80-bit FP number has a 64-bit significand an no implicit bit. return std::numeric_limits<Float>::digits != 64; } // Returns the number of significand bits stored in Float. The implicit bit is // not counted since it is not stored. -template <typename Float> constexpr int num_significand_bits() { +template <typename Float> constexpr auto num_significand_bits() -> int { // std::numeric_limits may not support __float128. return is_float128<Float>() ? 112 : (std::numeric_limits<Float>::digits - @@ -1702,7 +1663,7 @@ using fp = basic_fp<unsigned long long>; // Normalizes the value converted from double and multiplied by (1 << SHIFT). template <int SHIFT = 0, typename F> -FMT_CONSTEXPR basic_fp<F> normalize(basic_fp<F> value) { +FMT_CONSTEXPR auto normalize(basic_fp<F> value) -> basic_fp<F> { // Handle subnormals. const auto implicit_bit = F(1) << num_significand_bits<double>(); const auto shifted_implicit_bit = implicit_bit << SHIFT; @@ -1719,7 +1680,7 @@ FMT_CONSTEXPR basic_fp<F> normalize(basic_fp<F> value) { } // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. -FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { #if FMT_USE_INT128 auto product = static_cast<__uint128_t>(lhs) * rhs; auto f = static_cast<uint64_t>(product >> 64); @@ -1736,7 +1697,7 @@ FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { #endif } -FMT_CONSTEXPR inline fp operator*(fp x, fp y) { +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { return {multiply(x.f, y.f), x.e + y.e + 64}; } @@ -1962,8 +1923,9 @@ auto write_escaped_char(OutputIt out, Char v) -> OutputIt { *out++ = static_cast<Char>('\''); if ((needs_escape(static_cast<uint32_t>(v)) && v != static_cast<Char>('"')) || v == static_cast<Char>('\'')) { - out = write_escaped_cp( - out, find_escape_result<Char>{v_array, v_array + 1, static_cast<uint32_t>(v)}); + out = write_escaped_cp(out, + find_escape_result<Char>{v_array, v_array + 1, + static_cast<uint32_t>(v)}); } else { *out++ = v; } @@ -2052,10 +2014,10 @@ template <typename Char> class digit_grouping { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {grouping_.begin(), 0}; } + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. - int next(next_state& state) const { + auto next(next_state& state) const -> int { if (thousands_sep_.empty()) return max_value<int>(); if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value<char>()) @@ -2074,9 +2036,9 @@ template <typename Char> class digit_grouping { digit_grouping(std::string grouping, std::basic_string<Char> sep) : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} - bool has_separator() const { return !thousands_sep_.empty(); } + auto has_separator() const -> bool { return !thousands_sep_.empty(); } - int count_separators(int num_digits) const { + auto count_separators(int num_digits) const -> int { int count = 0; auto state = initial_state(); while (num_digits > next(state)) ++count; @@ -2085,7 +2047,7 @@ template <typename Char> class digit_grouping { // Applies grouping to digits and write the output to out. template <typename Out, typename C> - Out apply(Out out, basic_string_view<C> digits) const { + auto apply(Out out, basic_string_view<C> digits) const -> Out { auto num_digits = static_cast<int>(digits.size()); auto separators = basic_memory_buffer<int>(); separators.push_back(0); @@ -2108,24 +2070,66 @@ template <typename Char> class digit_grouping { } }; +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + // Writes a decimal integer with digit grouping. template <typename OutputIt, typename UInt, typename Char> auto write_int(OutputIt out, UInt value, unsigned prefix, const format_specs<Char>& specs, const digit_grouping<Char>& grouping) -> OutputIt { static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, ""); - int num_digits = count_digits(value); - char digits[40]; - format_decimal(digits, value, num_digits); - unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + - grouping.count_separators(num_digits)); + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type) { + case presentation_type::none: + case presentation_type::dec: { + num_digits = count_digits(value); + format_decimal<char>(appender(buffer), value, num_digits); + break; + } + case presentation_type::hex_lower: + case presentation_type::hex_upper: { + bool upper = specs.type == presentation_type::hex_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_uint<4, char>(appender(buffer), value, num_digits, upper); + break; + } + case presentation_type::bin_lower: + case presentation_type::bin_upper: { + bool upper = specs.type == presentation_type::bin_upper; + if (specs.alt) + prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_uint<1, char>(appender(buffer), value, num_digits); + break; + } + case presentation_type::oct: { + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_uint<3, char>(appender(buffer), value, num_digits); + break; + } + case presentation_type::chr: + return write_char(out, static_cast<Char>(value), specs); + default: + throw_format_error("invalid format specifier"); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); return write_padded<align::right>( out, specs, size, size, [&](reserve_iterator<OutputIt> it) { - if (prefix != 0) { - char sign = static_cast<char>(prefix); - *it++ = static_cast<Char>(sign); - } - return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast<Char>(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); }); } @@ -2138,11 +2142,6 @@ inline auto write_loc(OutputIt, loc_value, const format_specs<Char>&, return false; } -FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { - prefix |= prefix != 0 ? value << 8 : value; - prefix += (1u + (value > 0xff ? 1 : 0)) << 24; -} - template <typename UInt> struct write_int_arg { UInt abs_value; unsigned prefix; @@ -2289,25 +2288,25 @@ class counting_iterator { FMT_CONSTEXPR counting_iterator() : count_(0) {} - FMT_CONSTEXPR size_t count() const { return count_; } + FMT_CONSTEXPR auto count() const -> size_t { return count_; } - FMT_CONSTEXPR counting_iterator& operator++() { + FMT_CONSTEXPR auto operator++() -> counting_iterator& { ++count_; return *this; } - FMT_CONSTEXPR counting_iterator operator++(int) { + FMT_CONSTEXPR auto operator++(int) -> counting_iterator { auto it = *this; ++*this; return it; } - FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it, - difference_type n) { + FMT_CONSTEXPR friend auto operator+(counting_iterator it, difference_type n) + -> counting_iterator { it.count_ += static_cast<size_t>(n); return it; } - FMT_CONSTEXPR value_type operator*() const { return {}; } + FMT_CONSTEXPR auto operator*() const -> value_type { return {}; } }; template <typename Char, typename OutputIt> @@ -2342,9 +2341,10 @@ template <typename Char, typename OutputIt> FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs<Char>& specs, locale_ref) -> OutputIt { - return specs.type != presentation_type::pointer - ? write(out, basic_string_view<Char>(s), specs, {}) - : write_ptr<Char>(out, bit_cast<uintptr_t>(s), &specs); + if (specs.type == presentation_type::pointer) + return write_ptr<Char>(out, bit_cast<uintptr_t>(s), &specs); + if (!s) throw_format_error("string pointer is null"); + return write(out, basic_string_view<Char>(s), specs, {}); } template <typename Char, typename OutputIt, typename T, @@ -2430,9 +2430,8 @@ struct float_specs { bool showpoint : 1; }; -template <typename ErrorHandler = error_handler, typename Char> -FMT_CONSTEXPR auto parse_float_type_spec(const format_specs<Char>& specs, - ErrorHandler&& eh = {}) +template <typename Char> +FMT_CONSTEXPR auto parse_float_type_spec(const format_specs<Char>& specs) -> float_specs { auto result = float_specs(); result.showpoint = specs.alt; @@ -2468,7 +2467,7 @@ FMT_CONSTEXPR auto parse_float_type_spec(const format_specs<Char>& specs, result.format = float_format::hex; break; default: - eh.on_error("invalid format specifier"); + throw_format_error("invalid format specifier"); break; } return result; @@ -2707,12 +2706,12 @@ template <typename Char> class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr bool has_separator() const { return false; } + constexpr auto has_separator() const -> bool { return false; } - constexpr int count_separators(int) const { return 0; } + constexpr auto count_separators(int) const -> int { return 0; } template <typename Out, typename C> - constexpr Out apply(Out out, basic_string_view<C>) const { + constexpr auto apply(Out out, basic_string_view<C>) const -> Out { return out; } }; @@ -2731,7 +2730,7 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, } } -template <typename T> constexpr bool isnan(T value) { +template <typename T> constexpr auto isnan(T value) -> bool { return !(value >= value); // std::isnan doesn't support __float128. } @@ -2744,14 +2743,14 @@ struct has_isfinite<T, enable_if_t<sizeof(std::isfinite(T())) != 0>> template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value&& has_isfinite<T>::value)> -FMT_CONSTEXPR20 bool isfinite(T value) { +FMT_CONSTEXPR20 auto isfinite(T value) -> bool { constexpr T inf = T(std::numeric_limits<double>::infinity()); if (is_constant_evaluated(true)) return !detail::isnan(value) && value < inf && value > -inf; return std::isfinite(value); } template <typename T, FMT_ENABLE_IF(!has_isfinite<T>::value)> -FMT_CONSTEXPR bool isfinite(T value) { +FMT_CONSTEXPR auto isfinite(T value) -> bool { T inf = T(std::numeric_limits<double>::infinity()); // std::isfinite doesn't support __float128. return !detail::isnan(value) && value < inf && value > -inf; @@ -2788,10 +2787,10 @@ class bigint { basic_memory_buffer<bigit, bigits_capacity> bigits_; int exp_; - FMT_CONSTEXPR20 bigit operator[](int index) const { + FMT_CONSTEXPR20 auto operator[](int index) const -> bigit { return bigits_[to_unsigned(index)]; } - FMT_CONSTEXPR20 bigit& operator[](int index) { + FMT_CONSTEXPR20 auto operator[](int index) -> bigit& { return bigits_[to_unsigned(index)]; } @@ -2887,11 +2886,11 @@ class bigint { assign(uint64_or_128_t<Int>(n)); } - FMT_CONSTEXPR20 int num_bigits() const { + FMT_CONSTEXPR20 auto num_bigits() const -> int { return static_cast<int>(bigits_.size()) + exp_; } - FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) { + FMT_NOINLINE FMT_CONSTEXPR20 auto operator<<=(int shift) -> bigint& { FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; @@ -2906,13 +2905,15 @@ class bigint { return *this; } - template <typename Int> FMT_CONSTEXPR20 bigint& operator*=(Int value) { + template <typename Int> + FMT_CONSTEXPR20 auto operator*=(Int value) -> bigint& { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t<Int>(value)); return *this; } - friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) { + friend FMT_CONSTEXPR20 auto compare(const bigint& lhs, const bigint& rhs) + -> int { int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); if (num_lhs_bigits != num_rhs_bigits) return num_lhs_bigits > num_rhs_bigits ? 1 : -1; @@ -2929,8 +2930,9 @@ class bigint { } // Returns compare(lhs1 + lhs2, rhs). - friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2, - const bigint& rhs) { + friend FMT_CONSTEXPR20 auto add_compare(const bigint& lhs1, + const bigint& lhs2, const bigint& rhs) + -> int { auto minimum = [](int a, int b) { return a < b ? a : b; }; auto maximum = [](int a, int b) { return a > b ? a : b; }; int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits()); @@ -3017,7 +3019,7 @@ class bigint { // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. - FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) { + FMT_CONSTEXPR20 auto divmod_assign(const bigint& divisor) -> int { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); @@ -3260,7 +3262,7 @@ FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, format_hexfloat(static_cast<double>(value), precision, specs, buf); } -constexpr uint32_t fractional_part_rounding_thresholds(int index) { +constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { // For checking rounding thresholds. // The kth entry is chosen to be the smallest integer such that the // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. @@ -3799,62 +3801,39 @@ template <typename Char> struct arg_formatter { } }; -template <typename Char> struct custom_formatter { - basic_format_parse_context<Char>& parse_ctx; - buffer_context<Char>& ctx; - - void operator()( - typename basic_format_arg<buffer_context<Char>>::handle h) const { - h.format(parse_ctx, ctx); - } - template <typename T> void operator()(T) const {} -}; - -template <typename ErrorHandler> class width_checker { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} - +struct width_checker { template <typename T, FMT_ENABLE_IF(is_integer<T>::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative width"); + if (is_negative(value)) throw_format_error("negative width"); return static_cast<unsigned long long>(value); } template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("width is not integer"); + throw_format_error("width is not integer"); return 0; } - - private: - ErrorHandler& handler_; }; -template <typename ErrorHandler> class precision_checker { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} - +struct precision_checker { template <typename T, FMT_ENABLE_IF(is_integer<T>::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative precision"); + if (is_negative(value)) throw_format_error("negative precision"); return static_cast<unsigned long long>(value); } template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("precision is not integer"); + throw_format_error("precision is not integer"); return 0; } - - private: - ErrorHandler& handler_; }; -template <template <typename> class Handler, typename FormatArg, - typename ErrorHandler> -FMT_CONSTEXPR auto get_dynamic_spec(FormatArg arg, ErrorHandler eh) -> int { - unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg); - if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big"); +template <typename Handler, typename FormatArg> +FMT_CONSTEXPR auto get_dynamic_spec(FormatArg arg) -> int { + unsigned long long value = visit_format_arg(Handler(), arg); + if (value > to_unsigned(max_value<int>())) + throw_format_error("number is too big"); return static_cast<int>(value); } @@ -3865,7 +3844,7 @@ FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> decltype(ctx.arg(id)) { return arg; } -template <template <typename> class Handler, typename Context> +template <typename Handler, typename Context> FMT_CONSTEXPR void handle_dynamic_spec(int& value, arg_ref<typename Context::char_type> ref, Context& ctx) { @@ -3873,12 +3852,10 @@ FMT_CONSTEXPR void handle_dynamic_spec(int& value, case arg_id_kind::none: break; case arg_id_kind::index: - value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.index), - ctx.error_handler()); + value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.index)); break; case arg_id_kind::name: - value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name), - ctx.error_handler()); + value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name)); break; } } @@ -4050,12 +4027,10 @@ class format_int { template <typename T, typename Char> struct formatter<T, Char, enable_if_t<detail::has_format_as<T>::value>> - : private formatter<detail::format_as_t<T>, Char> { - using base = formatter<detail::format_as_t<T>, Char>; - using base::parse; - + : formatter<detail::format_as_t<T>, Char> { template <typename FormatContext> auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { + using base = formatter<detail::format_as_t<T>, Char>; return base::format(format_as(value), ctx); } }; @@ -4380,7 +4355,7 @@ void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt, auto out = buffer_appender<Char>(buf); if (fmt.size() == 2 && equal2(fmt.data(), "{}")) { auto arg = args.get(0); - if (!arg) error_handler().on_error("argument not found"); + if (!arg) throw_format_error("argument not found"); visit_format_arg(default_arg_formatter<Char>{out, args, loc}, arg); return; } @@ -4407,7 +4382,7 @@ void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt, } FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int { int arg_id = context.arg_id(id); - if (arg_id < 0) on_error("argument not found"); + if (arg_id < 0) throw_format_error("argument not found"); return arg_id; } @@ -4422,11 +4397,9 @@ void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt, auto on_format_specs(int id, const Char* begin, const Char* end) -> const Char* { auto arg = get_arg(context, id); - if (arg.type() == type::custom_type) { - parse_context.advance_to(begin); - visit_format_arg(custom_formatter<Char>{parse_context, context}, arg); + // Not using a visitor for custom types gives better codegen. + if (arg.format_custom(begin, parse_context, context)) return parse_context.begin(); - } auto specs = detail::dynamic_format_specs<Char>(); begin = parse_format_specs(begin, end, specs, parse_context, arg.type()); detail::handle_dynamic_spec<detail::width_checker>( @@ -4434,7 +4407,7 @@ void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt, detail::handle_dynamic_spec<detail::precision_checker>( specs.precision, specs.precision_ref, context); if (begin == end || *begin != '}') - on_error("missing '}' in format string"); + throw_format_error("missing '}' in format string"); auto f = arg_formatter<Char>{context.out(), specs, context.locale()}; context.advance_to(visit_format_arg(f, arg)); return begin; @@ -4537,16 +4510,16 @@ formatter<T, Char, detail::type::custom_type>>::format(const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { - if (specs_.width_ref.kind != detail::arg_id_kind::none || - specs_.precision_ref.kind != detail::arg_id_kind::none) { - auto specs = specs_; - detail::handle_dynamic_spec<detail::width_checker>(specs.width, - specs.width_ref, ctx); - detail::handle_dynamic_spec<detail::precision_checker>( - specs.precision, specs.precision_ref, ctx); - return detail::write<Char>(ctx.out(), val, specs, ctx.locale()); - } - return detail::write<Char>(ctx.out(), val, specs_, ctx.locale()); + if (specs_.width_ref.kind == detail::arg_id_kind::none && + specs_.precision_ref.kind == detail::arg_id_kind::none) { + return detail::write<Char>(ctx.out(), val, specs_, ctx.locale()); + } + auto specs = specs_; + detail::handle_dynamic_spec<detail::width_checker>(specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec<detail::precision_checker>( + specs.precision, specs.precision_ref, ctx); + return detail::write<Char>(ctx.out(), val, specs, ctx.locale()); } FMT_END_NAMESPACE diff --git a/include/fmt/os.h b/include/fmt/os.h index 2b517cd4..3c7b3ccb 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -48,6 +48,7 @@ // Calls to system functions are wrapped in FMT_SYSTEM for testability. #ifdef FMT_SYSTEM +# define FMT_HAS_SYSTEM # define FMT_POSIX_CALL(call) FMT_SYSTEM(call) #else # define FMT_SYSTEM(call) ::call @@ -116,7 +117,7 @@ template <typename Char> class basic_cstring_view { basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {} /** Returns the pointer to a C string. */ - const Char* c_str() const { return data_; } + auto c_str() const -> const Char* { return data_; } }; using cstring_view = basic_cstring_view<char>; @@ -171,7 +172,7 @@ std::system_error windows_error(int error_code, string_view message, // Can be used to report errors from destructors. FMT_API void report_windows_error(int error_code, const char* message) noexcept; #else -inline const std::error_category& system_category() noexcept { +inline auto system_category() noexcept -> const std::error_category& { return std::system_category(); } #endif // _WIN32 @@ -208,7 +209,7 @@ class buffered_file { other.file_ = nullptr; } - buffered_file& operator=(buffered_file&& other) { + auto operator=(buffered_file&& other) -> buffered_file& { close(); file_ = other.file_; other.file_ = nullptr; @@ -222,9 +223,9 @@ class buffered_file { FMT_API void close(); // Returns the pointer to a FILE object representing this file. - FILE* get() const noexcept { return file_; } + auto get() const noexcept -> FILE* { return file_; } - FMT_API int descriptor() const; + FMT_API auto descriptor() const -> int; void vprint(string_view format_str, format_args args) { fmt::vprint(file_, format_str, args); @@ -274,7 +275,7 @@ class FMT_API file { file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } // Move assignment is not noexcept because close may throw. - file& operator=(file&& other) { + auto operator=(file&& other) -> file& { close(); fd_ = other.fd_; other.fd_ = -1; @@ -285,24 +286,24 @@ class FMT_API file { ~file() noexcept; // Returns the file descriptor. - int descriptor() const noexcept { return fd_; } + auto descriptor() const noexcept -> int { return fd_; } // Closes the file. void close(); // Returns the file size. The size has signed type for consistency with // stat::st_size. - long long size() const; + auto size() const -> long long; // Attempts to read count bytes from the file into the specified buffer. - size_t read(void* buffer, size_t count); + auto read(void* buffer, size_t count) -> size_t; // Attempts to write count bytes from the specified buffer to the file. - size_t write(const void* buffer, size_t count); + auto write(const void* buffer, size_t count) -> size_t; // Duplicates a file descriptor with the dup function and returns // the duplicate as a file object. - static file dup(int fd); + static auto dup(int fd) -> file; // Makes fd be the copy of this file descriptor, closing fd first if // necessary. @@ -314,11 +315,12 @@ class FMT_API file { // Creates a pipe setting up read_end and write_end file objects for reading // and writing respectively. + // DEPRECATED! Taking files as out parameters is deprecated. static void pipe(file& read_end, file& write_end); // Creates a buffered_file object associated with this file and detaches // this file object from the file. - buffered_file fdopen(const char* mode); + auto fdopen(const char* mode) -> buffered_file; # if defined(_WIN32) && !defined(__MINGW32__) // Opens a file and constructs a file object representing this file by @@ -328,14 +330,14 @@ class FMT_API file { }; // Returns the memory page size. -long getpagesize(); +auto getpagesize() -> long; namespace detail { struct buffer_size { buffer_size() = default; size_t value = 0; - buffer_size operator=(size_t val) const { + auto operator=(size_t val) const -> buffer_size { auto bs = buffer_size(); bs.value = val; return bs; @@ -412,7 +414,7 @@ class FMT_API ostream { void flush() { buffer_.flush(); } template <typename... T> - friend ostream output_file(cstring_view path, T... params); + friend auto output_file(cstring_view path, T... params) -> ostream; void close() { buffer_.close(); } @@ -442,7 +444,7 @@ class FMT_API ostream { \endrst */ template <typename... T> -inline ostream output_file(cstring_view path, T... params) { +inline auto output_file(cstring_view path, T... params) -> ostream { return {path, detail::ostream_params(params...)}; } #endif // FMT_USE_FCNTL diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 782ace5c..26fb3b5a 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -21,9 +21,39 @@ #include "format.h" FMT_BEGIN_NAMESPACE - namespace detail { +template <typename Streambuf> class formatbuf : public Streambuf { + private: + using char_type = typename Streambuf::char_type; + using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0)); + using int_type = typename Streambuf::int_type; + using traits_type = typename Streambuf::traits_type; + + buffer<char_type>& buffer_; + + public: + explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast<char_type>(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + // Generate a unique explicit instantion in every translation unit using a tag // type in an anonymous namespace. namespace { @@ -40,7 +70,8 @@ template class file_access<file_access_tag, std::filebuf, auto get_file(std::filebuf&) -> FILE*; #endif -inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) { +inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data) + -> bool { FILE* f = nullptr; #if FMT_MSC_VERSION if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf())) @@ -69,8 +100,8 @@ inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) { #endif return false; } -inline bool write_ostream_unicode(std::wostream&, - fmt::basic_string_view<wchar_t>) { +inline auto write_ostream_unicode(std::wostream&, + fmt::basic_string_view<wchar_t>) -> bool { return false; } @@ -91,12 +122,11 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { } template <typename Char, typename T> -void format_value(buffer<Char>& buf, const T& value, - locale_ref loc = locale_ref()) { +void format_value(buffer<Char>& buf, const T& value) { auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf); auto&& output = std::basic_ostream<Char>(&format_buf); #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) - if (loc) output.imbue(loc.get<std::locale>()); + output.imbue(std::locale::classic()); // The default is always unlocalized. #endif output << value; output.exceptions(std::ios_base::failbit | std::ios_base::badbit); @@ -117,7 +147,7 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> { auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const -> OutputIt { auto buffer = basic_memory_buffer<Char>(); - detail::format_value(buffer, value, ctx.locale()); + detail::format_value(buffer, value); return formatter<basic_string_view<Char>, Char>::format( {buffer.data(), buffer.size()}, ctx); } diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index c0b51aee..3638fffb 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -183,7 +183,7 @@ template <size_t N> using make_index_sequence = std::make_index_sequence<N>; template <typename T, T... N> struct integer_sequence { using value_type = T; - static FMT_CONSTEXPR size_t size() { return sizeof...(N); } + static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); } }; template <size_t... N> using index_sequence = integer_sequence<size_t, N...>; @@ -207,15 +207,15 @@ class is_tuple_formattable_ { }; template <typename T, typename C> class is_tuple_formattable_<T, C, true> { template <std::size_t... Is> - static std::true_type check2(index_sequence<Is...>, - integer_sequence<bool, (Is == Is)...>); - static std::false_type check2(...); + static auto check2(index_sequence<Is...>, + integer_sequence<bool, (Is == Is)...>) -> std::true_type; + static auto check2(...) -> std::false_type; template <std::size_t... Is> - static decltype(check2( + static auto check(index_sequence<Is...>) -> decltype(check2( index_sequence<Is...>{}, - integer_sequence< - bool, (is_formattable<typename std::tuple_element<Is, T>::type, - C>::value)...>{})) check(index_sequence<Is...>); + integer_sequence<bool, + (is_formattable<typename std::tuple_element<Is, T>::type, + C>::value)...>{})); public: static constexpr const bool value = @@ -417,6 +417,12 @@ struct is_formattable_delayed #endif } // namespace detail +template <typename...> struct conjunction : std::true_type {}; +template <typename P> struct conjunction<P> : P {}; +template <typename P1, typename... Pn> +struct conjunction<P1, Pn...> + : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {}; + template <typename T, typename Char, typename Enable = void> struct range_formatter; @@ -482,7 +488,8 @@ struct range_formatter< for (; it != end; ++it) { if (i > 0) out = detail::copy_str<Char>(separator_, out); ctx.advance_to(out); - out = underlying_.format(mapper.map(*it), ctx); + auto&& item = *it; + out = underlying_.format(mapper.map(item), ctx); ++i; } out = detail::copy_str<Char>(closing_bracket_, out); diff --git a/include/fmt/std.h b/include/fmt/std.h index 4d1f97d2..7cff1159 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -38,6 +38,10 @@ # endif #endif +#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>) +# include <source_location> +#endif + // GCC 4 does not support FMT_HAS_INCLUDE. #if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__) # include <cxxabi.h> @@ -59,13 +63,31 @@ # endif #endif -#ifdef __cpp_lib_filesystem +// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined. +#ifndef FMT_CPP_LIB_FILESYSTEM +# ifdef __cpp_lib_filesystem +# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem +# else +# define FMT_CPP_LIB_FILESYSTEM 0 +# endif +#endif + +#ifndef FMT_CPP_LIB_VARIANT +# ifdef __cpp_lib_variant +# define FMT_CPP_LIB_VARIANT __cpp_lib_variant +# else +# define FMT_CPP_LIB_VARIANT 0 +# endif +#endif + +#if FMT_CPP_LIB_FILESYSTEM FMT_BEGIN_NAMESPACE namespace detail { -template <typename Char, typename PathChar> auto get_path_string( - const std::filesystem::path& p, const std::basic_string<PathChar>& native) { +template <typename Char, typename PathChar> +auto get_path_string(const std::filesystem::path& p, + const std::basic_string<PathChar>& native) { if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>) return to_utf8<wchar_t>(native, to_utf8_error_policy::replace); else @@ -76,7 +98,8 @@ template <typename Char, typename PathChar> void write_escaped_path(basic_memory_buffer<Char>& quoted, const std::filesystem::path& p, const std::basic_string<PathChar>& native) { - if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>) { + if constexpr (std::is_same_v<Char, char> && + std::is_same_v<PathChar, wchar_t>) { auto buf = basic_memory_buffer<wchar_t>(); write_escaped_string<wchar_t>(std::back_inserter(buf), native); bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()}); @@ -97,6 +120,7 @@ template <typename Char> struct formatter<std::filesystem::path, Char> { format_specs<Char> specs_; detail::arg_ref<Char> width_ref_; bool debug_ = false; + char path_type_ = 0; public: FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } @@ -113,27 +137,34 @@ template <typename Char> struct formatter<std::filesystem::path, Char> { debug_ = true; ++it; } + if (it != end && (*it == 'g')) path_type_ = *it++; return it; } template <typename FormatContext> auto format(const std::filesystem::path& p, FormatContext& ctx) const { auto specs = specs_; +# ifdef _WIN32 + auto path_string = !path_type_ ? p.native() : p.generic_wstring(); +# else + auto path_string = !path_type_ ? p.native() : p.generic_string(); +# endif + detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_, ctx); if (!debug_) { - auto s = detail::get_path_string<Char>(p, p.native()); + auto s = detail::get_path_string<Char>(p, path_string); return detail::write(ctx.out(), basic_string_view<Char>(s), specs); } auto quoted = basic_memory_buffer<Char>(); - detail::write_escaped_path(quoted, p, p.native()); + detail::write_escaped_path(quoted, p, path_string); return detail::write(ctx.out(), basic_string_view<Char>(quoted.data(), quoted.size()), specs); } }; FMT_END_NAMESPACE -#endif +#endif // FMT_CPP_LIB_FILESYSTEM FMT_BEGIN_NAMESPACE FMT_EXPORT @@ -145,7 +176,7 @@ struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> { const std::bitset<N>& bs; template <typename OutputIt> - FMT_CONSTEXPR OutputIt operator()(OutputIt out) { + FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { for (auto pos = N; pos > 0; --pos) { out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0')); } @@ -197,7 +228,7 @@ struct formatter<std::optional<T>, Char, } template <typename FormatContext> - auto format(std::optional<T> const& opt, FormatContext& ctx) const + auto format(const std::optional<T>& opt, FormatContext& ctx) const -> decltype(ctx.out()) { if (!opt) return detail::write<Char>(ctx.out(), none); @@ -211,7 +242,32 @@ struct formatter<std::optional<T>, Char, FMT_END_NAMESPACE #endif // __cpp_lib_optional -#ifdef __cpp_lib_variant +#ifdef __cpp_lib_source_location +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template <> struct formatter<std::source_location> { + template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { + return ctx.begin(); + } + + template <typename FormatContext> + auto format(const std::source_location& loc, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + out = detail::write(out, loc.file_name()); + out = detail::write(out, ':'); + out = detail::write<char>(out, loc.line()); + out = detail::write(out, ':'); + out = detail::write<char>(out, loc.column()); + out = detail::write(out, ": "); + out = detail::write(out, loc.function_name()); + return out; + } +}; +FMT_END_NAMESPACE +#endif + +#if FMT_CPP_LIB_VARIANT FMT_BEGIN_NAMESPACE namespace detail { @@ -302,7 +358,7 @@ struct formatter< } }; FMT_END_NAMESPACE -#endif // __cpp_lib_variant +#endif // FMT_CPP_LIB_VARIANT FMT_BEGIN_NAMESPACE FMT_EXPORT @@ -326,7 +382,7 @@ template <typename Char> struct formatter<std::error_code, Char> { FMT_EXPORT template <typename T, typename Char> struct formatter< - T, Char, + T, Char, // DEPRECATED! Mixing code unit types. typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> { private: bool with_typename_ = false; @@ -357,7 +413,7 @@ struct formatter< # ifdef FMT_HAS_ABI_CXA_DEMANGLE int status = 0; std::size_t size = 0; - std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr( + std::unique_ptr<char, void (*)(void*)> demangled_name_ptr( abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free); string_view demangled_name_view; diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 625ec369..f609c5c4 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -63,14 +63,15 @@ template <> struct is_char<char16_t> : std::true_type {}; template <> struct is_char<char32_t> : std::true_type {}; template <typename... T> -constexpr format_arg_store<wformat_context, T...> make_wformat_args( - const T&... args) { +constexpr auto make_wformat_args(const T&... args) + -> format_arg_store<wformat_context, T...> { return {args...}; } inline namespace literals { #if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS -constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) { +constexpr auto operator""_a(const wchar_t* s, size_t) + -> detail::udl_arg<wchar_t> { return {s}; } #endif @@ -172,11 +173,11 @@ inline auto vformat_to( return detail::get_iterator(buf, out); } -template < - typename OutputIt, typename Locale, typename S, typename... T, - typename Char = char_t<S>, - bool enable = detail::is_output_iterator<OutputIt, Char>::value&& - detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value> +template <typename OutputIt, typename Locale, typename S, typename... T, + typename Char = char_t<S>, + bool enable = detail::is_output_iterator<OutputIt, Char>::value && + detail::is_locale<Locale>::value && + detail::is_exotic_char<Char>::value> inline auto format_to(OutputIt out, const Locale& loc, const S& format_str, T&&... args) -> typename std::enable_if<enable, OutputIt>::type { @@ -101,7 +101,7 @@ extern "C++" { // gcc doesn't yet implement private module fragments #if !FMT_GCC_VERSION -module : private; +module :private; #endif #include "format.cc" @@ -18,8 +18,8 @@ # include <sys/stat.h> # include <sys/types.h> -# ifdef _WRS_KERNEL // VxWorks7 kernel -# include <ioLib.h> // getpagesize +# ifdef _WRS_KERNEL // VxWorks7 kernel +# include <ioLib.h> // getpagesize # endif # ifndef _WIN32 @@ -182,10 +182,14 @@ void buffered_file::close() { } int buffered_file::descriptor() const { -#ifdef fileno // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL. - int fd = fileno(file_); -#else +#if !defined(fileno) int fd = FMT_POSIX_CALL(fileno(file_)); +#elif defined(FMT_HAS_SYSTEM) + // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL. +# define FMT_DISABLE_MACRO + int fd = FMT_SYSTEM(fileno FMT_DISABLE_MACRO(file_)); +#else + int fd = fileno(file_); #endif if (fd == -1) FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor"))); diff --git a/support/AndroidManifest.xml b/support/AndroidManifest.xml index b5281fee..c282ef5a 100644 --- a/support/AndroidManifest.xml +++ b/support/AndroidManifest.xml @@ -1 +1 @@ -<manifest package="net.fmtlib" /> +<manifest package="dev.fmt" /> diff --git a/support/manage.py b/support/manage.py index 36f61d9b..cfb49797 100755 --- a/support/manage.py +++ b/support/manage.py @@ -12,7 +12,7 @@ obtained from https://github.com/settings/tokens. from __future__ import print_function import datetime, docopt, errno, fileinput, json, os -import re, requests, shutil, sys, tempfile +import re, requests, shutil, sys from contextlib import contextmanager from distutils.version import LooseVersion from subprocess import check_call @@ -229,12 +229,50 @@ def release(args): if not fmt_repo.update('-b', branch, fmt_repo_url): clean_checkout(fmt_repo, branch) - # Convert changelog from RST to GitHub-flavored Markdown and get the - # version. + # Update the date in the changelog and extract the version and the first + # section content. changelog = 'ChangeLog.md' changelog_path = os.path.join(fmt_repo.dir, changelog) - import rst2md - changes, version = rst2md.convert(changelog_path) + is_first_section = True + first_section = [] + for i, line in enumerate(fileinput.input(changelog_path, inplace=True)): + if i == 0: + version = re.match(r'# (.*) - TBD', line).group(1) + line = '# {} - {}\n'.format( + version, datetime.date.today().isoformat()) + elif not is_first_section: + pass + elif line.startswith('#'): + is_first_section = False + else: + first_section.append(line) + sys.stdout.write(line) + if first_section[0] == '\n': + first_section.pop(0) + + changes = '' + code_block = False + stripped = False + for line in first_section: + if re.match(r'^\s*```', line): + code_block = not code_block + changes += line + stripped = False + continue + if code_block: + changes += line + continue + if line == '\n': + changes += line + if stripped: + changes += line + stripped = False + continue + if stripped: + line = ' ' + line.lstrip() + changes += line.rstrip() + stripped = True + cmakelists = 'CMakeLists.txt' for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists), inplace=True): @@ -243,23 +281,11 @@ def release(args): line = prefix + version + ')\n' sys.stdout.write(line) - # Update the version in the changelog. - title_len = 0 - for line in fileinput.input(changelog_path, inplace=True): - if line.startswith(version + ' - TBD'): - line = version + ' - ' + datetime.date.today().isoformat() - title_len = len(line) - line += '\n' - elif title_len: - line = '-' * title_len + '\n' - title_len = 0 - sys.stdout.write(line) - # Add the version to the build script. script = os.path.join('doc', 'build.py') script_path = os.path.join(fmt_repo.dir, script) for line in fileinput.input(script_path, inplace=True): - m = re.match(r'( *versions = )\[(.+)\]', line) + m = re.match(r'( *versions \+= )\[(.+)\]', line) if m: line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version) sys.stdout.write(line) diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 07760688..b2d03f97 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -17,6 +17,9 @@ using fmt::runtime; using testing::Contains; +template <typename Duration> +using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>; + #if defined(__MINGW32__) && !defined(_UCRT) // Only C89 conversion specifiers when using MSVCRT instead of UCRT # define FMT_HAS_C99_STRFTIME 0 @@ -24,6 +27,12 @@ using testing::Contains; # define FMT_HAS_C99_STRFTIME 1 #endif +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L +using days = std::chrono::days; +#else +using days = std::chrono::duration<std::chrono::hours::rep, std::ratio<86400>>; +#endif + auto make_tm() -> std::tm { auto time = std::tm(); time.tm_mday = 1; @@ -260,9 +269,8 @@ TEST(chrono_test, system_clock_time_point) { EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1)); EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1)); - using time_point = - std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>; - auto t2 = time_point(std::chrono::seconds(42)); + + auto t2 = sys_time<std::chrono::seconds>(std::chrono::seconds(42)); EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); std::vector<std::string> spec_list = { @@ -331,14 +339,14 @@ TEST(chrono_test, system_clock_time_point) { auto t = std::chrono::system_clock::to_time_t(t1); auto tm = *std::gmtime(&t); - EXPECT_EQ("+0000", fmt::format("{:%z}", t1)); - EXPECT_EQ("+0000", fmt::format("{:%z}", tm)); + EXPECT_EQ(fmt::format("{:%z}", t1), "+0000"); + EXPECT_EQ(fmt::format("{:%z}", tm), "+0000"); - EXPECT_EQ("+00:00", fmt::format("{:%Ez}", t1)); - EXPECT_EQ("+00:00", fmt::format("{:%Ez}", tm)); + EXPECT_EQ(fmt::format("{:%Ez}", t1), "+00:00"); + EXPECT_EQ(fmt::format("{:%Ez}", tm), "+00:00"); - EXPECT_EQ("+00:00", fmt::format("{:%Oz}", t1)); - EXPECT_EQ("+00:00", fmt::format("{:%Oz}", tm)); + EXPECT_EQ(fmt::format("{:%Oz}", t1), "+00:00"); + EXPECT_EQ(fmt::format("{:%Oz}", tm), "+00:00"); } } @@ -423,122 +431,122 @@ TEST(chrono_test, local_system_clock_time_point) { #ifndef FMT_STATIC_THOUSANDS_SEPARATOR TEST(chrono_test, format_default) { - EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42))); - EXPECT_EQ("42as", - fmt::format("{}", std::chrono::duration<int, std::atto>(42))); - EXPECT_EQ("42fs", - fmt::format("{}", std::chrono::duration<int, std::femto>(42))); - EXPECT_EQ("42ps", - fmt::format("{}", std::chrono::duration<int, std::pico>(42))); - EXPECT_EQ("42ns", fmt::format("{}", std::chrono::nanoseconds(42))); - EXPECT_EQ("42µs", fmt::format("{}", std::chrono::microseconds(42))); - EXPECT_EQ("42ms", fmt::format("{}", std::chrono::milliseconds(42))); - EXPECT_EQ("42cs", - fmt::format("{}", std::chrono::duration<int, std::centi>(42))); - EXPECT_EQ("42ds", - fmt::format("{}", std::chrono::duration<int, std::deci>(42))); - EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42))); - EXPECT_EQ("42das", - fmt::format("{}", std::chrono::duration<int, std::deca>(42))); - EXPECT_EQ("42hs", - fmt::format("{}", std::chrono::duration<int, std::hecto>(42))); - EXPECT_EQ("42ks", - fmt::format("{}", std::chrono::duration<int, std::kilo>(42))); - EXPECT_EQ("42Ms", - fmt::format("{}", std::chrono::duration<int, std::mega>(42))); - EXPECT_EQ("42Gs", - fmt::format("{}", std::chrono::duration<int, std::giga>(42))); - EXPECT_EQ("42Ts", - fmt::format("{}", std::chrono::duration<int, std::tera>(42))); - EXPECT_EQ("42Ps", - fmt::format("{}", std::chrono::duration<int, std::peta>(42))); - EXPECT_EQ("42Es", - fmt::format("{}", std::chrono::duration<int, std::exa>(42))); - EXPECT_EQ("42min", fmt::format("{}", std::chrono::minutes(42))); - EXPECT_EQ("42h", fmt::format("{}", std::chrono::hours(42))); -# if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L - EXPECT_EQ("42d", fmt::format("{}", std::chrono::days(42))); -# endif + EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)), + "42as"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::femto>(42)), + "42fs"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::pico>(42)), + "42ps"); + EXPECT_EQ(fmt::format("{}", std::chrono::nanoseconds(42)), "42ns"); + EXPECT_EQ(fmt::format("{}", std::chrono::microseconds(42)), "42µs"); + EXPECT_EQ(fmt::format("{}", std::chrono::milliseconds(42)), "42ms"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::centi>(42)), + "42cs"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::deci>(42)), + "42ds"); + EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::deca>(42)), + "42das"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::hecto>(42)), + "42hs"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::kilo>(42)), + "42ks"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::mega>(42)), + "42Ms"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::giga>(42)), + "42Gs"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::tera>(42)), + "42Ts"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::peta>(42)), + "42Ps"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::exa>(42)), + "42Es"); + EXPECT_EQ(fmt::format("{}", std::chrono::minutes(42)), "42min"); + EXPECT_EQ(fmt::format("{}", std::chrono::hours(42)), "42h"); + EXPECT_EQ(fmt::format("{}", days(42)), "42d"); EXPECT_EQ( - "42[15]s", - fmt::format("{}", std::chrono::duration<int, std::ratio<15, 1>>(42))); + fmt::format("{}", std::chrono::duration<int, std::ratio<15, 1>>(42)), + "42[15]s"); EXPECT_EQ( - "42[15/4]s", - fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42))); + fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)), + "42[15/4]s"); } TEST(chrono_test, duration_align) { auto s = std::chrono::seconds(42); - EXPECT_EQ("42s ", fmt::format("{:5}", s)); - EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5)); - EXPECT_EQ(" 42s", fmt::format("{:>5}", s)); - EXPECT_EQ("**42s**", fmt::format("{:*^7}", s)); - EXPECT_EQ("03:25:45 ", - fmt::format("{:12%H:%M:%S}", std::chrono::seconds(12345))); - EXPECT_EQ(" 03:25:45", - fmt::format("{:>12%H:%M:%S}", std::chrono::seconds(12345))); - EXPECT_EQ("~~03:25:45~~", - fmt::format("{:~^12%H:%M:%S}", std::chrono::seconds(12345))); - EXPECT_EQ("03:25:45 ", - fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12)); + EXPECT_EQ(fmt::format("{:5}", s), "42s "); + EXPECT_EQ(fmt::format("{:{}}", s, 5), "42s "); + EXPECT_EQ(fmt::format("{:>5}", s), " 42s"); + EXPECT_EQ(fmt::format("{:*^7}", s), "**42s**"); + EXPECT_EQ(fmt::format("{:12%H:%M:%S}", std::chrono::seconds(12345)), + "03:25:45 "); + EXPECT_EQ(fmt::format("{:>12%H:%M:%S}", std::chrono::seconds(12345)), + " 03:25:45"); + EXPECT_EQ(fmt::format("{:~^12%H:%M:%S}", std::chrono::seconds(12345)), + "~~03:25:45~~"); + EXPECT_EQ(fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12), + "03:25:45 "); } TEST(chrono_test, tm_align) { auto t = make_tm(1975, 12, 29, 12, 14, 16); - EXPECT_EQ("1975-12-29 12:14:16", fmt::format("{:%F %T}", t)); - EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:30%F %T}", t)); - EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:{}%F %T}", t, 30)); - EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:<30%F %T}", t)); - EXPECT_EQ(" 1975-12-29 12:14:16 ", fmt::format("{:^30%F %T}", t)); - EXPECT_EQ(" 1975-12-29 12:14:16", fmt::format("{:>30%F %T}", t)); + EXPECT_EQ(fmt::format("{:%F %T}", t), "1975-12-29 12:14:16"); + EXPECT_EQ(fmt::format("{:30%F %T}", t), "1975-12-29 12:14:16 "); + EXPECT_EQ(fmt::format("{:{}%F %T}", t, 30), "1975-12-29 12:14:16 "); + EXPECT_EQ(fmt::format("{:<30%F %T}", t), "1975-12-29 12:14:16 "); + EXPECT_EQ(fmt::format("{:^30%F %T}", t), " 1975-12-29 12:14:16 "); + EXPECT_EQ(fmt::format("{:>30%F %T}", t), " 1975-12-29 12:14:16"); - EXPECT_EQ("1975-12-29 12:14:16***********", fmt::format("{:*<30%F %T}", t)); - EXPECT_EQ("*****1975-12-29 12:14:16******", fmt::format("{:*^30%F %T}", t)); - EXPECT_EQ("***********1975-12-29 12:14:16", fmt::format("{:*>30%F %T}", t)); + EXPECT_EQ(fmt::format("{:*<30%F %T}", t), "1975-12-29 12:14:16***********"); + EXPECT_EQ(fmt::format("{:*^30%F %T}", t), "*****1975-12-29 12:14:16******"); + EXPECT_EQ(fmt::format("{:*>30%F %T}", t), "***********1975-12-29 12:14:16"); } TEST(chrono_test, tp_align) { auto tp = std::chrono::time_point_cast<std::chrono::microseconds>( std::chrono::system_clock::from_time_t(0)); - EXPECT_EQ("00:00.000000", fmt::format("{:%M:%S}", tp)); - EXPECT_EQ("00:00.000000 ", fmt::format("{:15%M:%S}", tp)); - EXPECT_EQ("00:00.000000 ", fmt::format("{:{}%M:%S}", tp, 15)); - EXPECT_EQ("00:00.000000 ", fmt::format("{:<15%M:%S}", tp)); - EXPECT_EQ(" 00:00.000000 ", fmt::format("{:^15%M:%S}", tp)); - EXPECT_EQ(" 00:00.000000", fmt::format("{:>15%M:%S}", tp)); + EXPECT_EQ(fmt::format("{:%M:%S}", tp), "00:00.000000"); + EXPECT_EQ(fmt::format("{:15%M:%S}", tp), "00:00.000000 "); + EXPECT_EQ(fmt::format("{:{}%M:%S}", tp, 15), "00:00.000000 "); + EXPECT_EQ(fmt::format("{:<15%M:%S}", tp), "00:00.000000 "); + EXPECT_EQ(fmt::format("{:^15%M:%S}", tp), " 00:00.000000 "); + EXPECT_EQ(fmt::format("{:>15%M:%S}", tp), " 00:00.000000"); - EXPECT_EQ("00:00.000000***", fmt::format("{:*<15%M:%S}", tp)); - EXPECT_EQ("*00:00.000000**", fmt::format("{:*^15%M:%S}", tp)); - EXPECT_EQ("***00:00.000000", fmt::format("{:*>15%M:%S}", tp)); + EXPECT_EQ(fmt::format("{:*<15%M:%S}", tp), "00:00.000000***"); + EXPECT_EQ(fmt::format("{:*^15%M:%S}", tp), "*00:00.000000**"); + EXPECT_EQ(fmt::format("{:*>15%M:%S}", tp), "***00:00.000000"); } TEST(chrono_test, format_specs) { - EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0))); - EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0))); - EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0))); - EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0))); - EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60))); - EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42))); - EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234))); - EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0))); - EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60))); - EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42))); - EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61))); - EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0))); - EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24))); - EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14))); - EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61))); - EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0))); - EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12))); - EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24))); - EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4))); - EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14))); - EXPECT_EQ("03:25:45", - fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345))); - EXPECT_EQ("03:25", fmt::format("{:%R}", std::chrono::seconds(12345))); - EXPECT_EQ("03:25:45", fmt::format("{:%T}", std::chrono::seconds(12345))); - EXPECT_EQ("12345", fmt::format("{:%Q}", std::chrono::seconds(12345))); - EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345))); + EXPECT_EQ(fmt::format("{:%%}", std::chrono::seconds(0)), "%"); + EXPECT_EQ(fmt::format("{:%n}", std::chrono::seconds(0)), "\n"); + EXPECT_EQ(fmt::format("{:%t}", std::chrono::seconds(0)), "\t"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::seconds(0)), "00"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::seconds(60)), "00"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::seconds(42)), "42"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds(1234)), "01.234"); + EXPECT_EQ(fmt::format("{:%M}", std::chrono::minutes(0)), "00"); + EXPECT_EQ(fmt::format("{:%M}", std::chrono::minutes(60)), "00"); + EXPECT_EQ(fmt::format("{:%M}", std::chrono::minutes(42)), "42"); + EXPECT_EQ(fmt::format("{:%M}", std::chrono::seconds(61)), "01"); + EXPECT_EQ(fmt::format("{:%H}", std::chrono::hours(0)), "00"); + EXPECT_EQ(fmt::format("{:%H}", std::chrono::hours(24)), "00"); + EXPECT_EQ(fmt::format("{:%H}", std::chrono::hours(14)), "14"); + EXPECT_EQ(fmt::format("{:%H}", std::chrono::minutes(61)), "01"); + EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(0)), "12"); + EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(12)), "12"); + EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(24)), "12"); + EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(4)), "04"); + EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(14)), "02"); + EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345"); + EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)), + "03:25:45"); + EXPECT_EQ(fmt::format("{:%R}", std::chrono::seconds(12345)), "03:25"); + EXPECT_EQ(fmt::format("{:%T}", std::chrono::seconds(12345)), "03:25:45"); + EXPECT_EQ(fmt::format("{:%Q}", std::chrono::seconds(12345)), "12345"); + EXPECT_EQ(fmt::format("{:%q}", std::chrono::seconds(12345)), "s"); } TEST(chrono_test, invalid_specs) { @@ -619,78 +627,77 @@ TEST(chrono_test, locale) { using dms = std::chrono::duration<double, std::milli>; TEST(chrono_test, format_default_fp) { - typedef std::chrono::duration<float> fs; - EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234))); - typedef std::chrono::duration<float, std::milli> fms; - EXPECT_EQ("1.234ms", fmt::format("{}", fms(1.234))); - typedef std::chrono::duration<double> ds; - EXPECT_EQ("1.234s", fmt::format("{}", ds(1.234))); - EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234))); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<float>(1.234)), "1.234s"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::milli>(1.234)), + "1.234ms"); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<double>(1.234)), "1.234s"); + EXPECT_EQ(fmt::format("{}", dms(1.234)), "1.234ms"); } TEST(chrono_test, format_precision) { EXPECT_THROW_MSG( (void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)), fmt::format_error, "precision not allowed for this argument type"); - EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234))); - EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); - EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2)); + EXPECT_EQ(fmt::format("{:.0}", dms(1.234)), "1ms"); + EXPECT_EQ(fmt::format("{:.1}", dms(1.234)), "1.2ms"); + EXPECT_EQ(fmt::format("{:.{}}", dms(1.234), 2), "1.23ms"); - EXPECT_EQ("13ms", fmt::format("{:.0}", dms(12.56))); - EXPECT_EQ("12.6ms", fmt::format("{:.1}", dms(12.56))); - EXPECT_EQ("12.56ms", fmt::format("{:.2}", dms(12.56))); + EXPECT_EQ(fmt::format("{:.0}", dms(12.56)), "13ms"); + EXPECT_EQ(fmt::format("{:.1}", dms(12.56)), "12.6ms"); + EXPECT_EQ(fmt::format("{:.2}", dms(12.56)), "12.56ms"); } TEST(chrono_test, format_full_specs) { - EXPECT_EQ("1ms ", fmt::format("{:6.0}", dms(1.234))); - EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234))); - EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2)); - EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1)); - EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8)); - EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3)); - EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234))); - - EXPECT_EQ("13ms ", fmt::format("{:6.0}", dms(12.56))); - EXPECT_EQ(" 13ms", fmt::format("{:>8.{}}", dms(12.56), 0)); - EXPECT_EQ(" 13ms ", fmt::format("{:^{}.{}}", dms(12.56), 6, 0)); - EXPECT_EQ(" 13ms ", fmt::format("{0:^{2}.{1}}", dms(12.56), 0, 8)); - EXPECT_EQ("==13ms===", fmt::format("{:=^{}.{}}", dms(12.56), 9, 0)); - EXPECT_EQ("***13ms***", fmt::format("{:*^10.0}", dms(12.56))); + EXPECT_EQ(fmt::format("{:6.0}", dms(1.234)), "1ms "); + EXPECT_EQ(fmt::format("{:6.1}", dms(1.234)), "1.2ms "); + EXPECT_EQ(fmt::format("{:>8.{}}", dms(1.234), 2), " 1.23ms"); + EXPECT_EQ(fmt::format("{:^{}.{}}", dms(1.234), 7, 1), " 1.2ms "); + EXPECT_EQ(fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8), " 1.23ms "); + EXPECT_EQ(fmt::format("{:=^{}.{}}", dms(1.234), 9, 3), "=1.234ms="); + EXPECT_EQ(fmt::format("{:*^10.4}", dms(1.234)), "*1.2340ms*"); + + EXPECT_EQ(fmt::format("{:6.0}", dms(12.56)), "13ms "); + EXPECT_EQ(fmt::format("{:>8.{}}", dms(12.56), 0), " 13ms"); + EXPECT_EQ(fmt::format("{:^{}.{}}", dms(12.56), 6, 0), " 13ms "); + EXPECT_EQ(fmt::format("{0:^{2}.{1}}", dms(12.56), 0, 8), " 13ms "); + EXPECT_EQ(fmt::format("{:=^{}.{}}", dms(12.56), 9, 0), "==13ms==="); + EXPECT_EQ(fmt::format("{:*^10.0}", dms(12.56)), "***13ms***"); } TEST(chrono_test, format_simple_q) { - typedef std::chrono::duration<float> fs; - EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234))); - typedef std::chrono::duration<float, std::milli> fms; - EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", fms(1.234))); - typedef std::chrono::duration<double> ds; - EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", ds(1.234))); - EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234))); + EXPECT_EQ(fmt::format("{:%Q %q}", std::chrono::duration<float>(1.234)), + "1.234 s"); + EXPECT_EQ( + fmt::format("{:%Q %q}", std::chrono::duration<float, std::milli>(1.234)), + "1.234 ms"); + EXPECT_EQ(fmt::format("{:%Q %q}", std::chrono::duration<double>(1.234)), + "1.234 s"); + EXPECT_EQ(fmt::format("{:%Q %q}", dms(1.234)), "1.234 ms"); } TEST(chrono_test, format_precision_q) { EXPECT_THROW_MSG( (void)fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)), fmt::format_error, "precision not allowed for this argument type"); - EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234))); - EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2)); + EXPECT_EQ(fmt::format("{:.1%Q %q}", dms(1.234)), "1.2 ms"); + EXPECT_EQ(fmt::format("{:.{}%Q %q}", dms(1.234), 2), "1.23 ms"); } TEST(chrono_test, format_full_specs_q) { - EXPECT_EQ("1 ms ", fmt::format("{:7.0%Q %q}", dms(1.234))); - EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234))); - EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2)); - EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1)); - EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9)); - EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3)); - EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234))); - - EXPECT_EQ("13 ms ", fmt::format("{:7.0%Q %q}", dms(12.56))); - EXPECT_EQ(" 13 ms", fmt::format("{:>8.{}%Q %q}", dms(12.56), 0)); - EXPECT_EQ(" 13 ms ", fmt::format("{:^{}.{}%Q %q}", dms(12.56), 8, 0)); - EXPECT_EQ(" 13 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(12.56), 0, 9)); - EXPECT_EQ("==13 ms==", fmt::format("{:=^{}.{}%Q %q}", dms(12.56), 9, 0)); - EXPECT_EQ("***13 ms***", fmt::format("{:*^11.0%Q %q}", dms(12.56))); + EXPECT_EQ(fmt::format("{:7.0%Q %q}", dms(1.234)), "1 ms "); + EXPECT_EQ(fmt::format("{:7.1%Q %q}", dms(1.234)), "1.2 ms "); + EXPECT_EQ(fmt::format("{:>8.{}%Q %q}", dms(1.234), 2), " 1.23 ms"); + EXPECT_EQ(fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1), " 1.2 ms "); + EXPECT_EQ(fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9), " 1.23 ms "); + EXPECT_EQ(fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3), "=1.234 ms="); + EXPECT_EQ(fmt::format("{:*^11.4%Q %q}", dms(1.234)), "*1.2340 ms*"); + + EXPECT_EQ(fmt::format("{:7.0%Q %q}", dms(12.56)), "13 ms "); + EXPECT_EQ(fmt::format("{:>8.{}%Q %q}", dms(12.56), 0), " 13 ms"); + EXPECT_EQ(fmt::format("{:^{}.{}%Q %q}", dms(12.56), 8, 0), " 13 ms "); + EXPECT_EQ(fmt::format("{0:^{2}.{1}%Q %q}", dms(12.56), 0, 9), " 13 ms "); + EXPECT_EQ(fmt::format("{:=^{}.{}%Q %q}", dms(12.56), 9, 0), "==13 ms=="); + EXPECT_EQ(fmt::format("{:*^11.0%Q %q}", dms(12.56)), "***13 ms***"); } TEST(chrono_test, invalid_width_id) { @@ -704,27 +711,26 @@ TEST(chrono_test, invalid_colons) { } TEST(chrono_test, negative_durations) { - EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345))); - EXPECT_EQ("-03:25:45", - fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345))); - EXPECT_EQ("-00:01", - fmt::format("{:%M:%S}", std::chrono::duration<double>(-1))); - EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(-12345))); - EXPECT_EQ("-00.127", - fmt::format("{:%S}", - std::chrono::duration<signed char, std::milli>{-127})); + EXPECT_EQ(fmt::format("{:%Q}", std::chrono::seconds(-12345)), "-12345"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)), + "-03:25:45"); + EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>(-1)), + "-00:01"); + EXPECT_EQ(fmt::format("{:%q}", std::chrono::seconds(-12345)), "s"); + EXPECT_EQ(fmt::format("{:%S}", + std::chrono::duration<signed char, std::milli>(-127)), + "-00.127"); auto min = std::numeric_limits<int>::min(); EXPECT_EQ(fmt::format("{}", min), fmt::format("{:%Q}", std::chrono::duration<int>(min))); } TEST(chrono_test, special_durations) { - auto value = fmt::format("{:%S}", std::chrono::duration<double>(1e20)); - EXPECT_EQ(value, "40"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>(1e20)), "40"); auto nan = std::numeric_limits<double>::quiet_NaN(); EXPECT_EQ( - "nan nan nan nan nan:nan nan", - fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan))); + fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan)), + "nan nan nan nan nan:nan nan"); EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)), "1Es"); EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)), @@ -733,13 +739,13 @@ TEST(chrono_test, special_durations) { "03:33"); EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}), "03:33:20"); - EXPECT_EQ("01.234", - fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>( - 1.234e12))); + EXPECT_EQ( + fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>(1.234e12)), + "01.234"); } TEST(chrono_test, unsigned_duration) { - EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42))); + EXPECT_EQ(fmt::format("{}", std::chrono::duration<unsigned>(42)), "42s"); } TEST(chrono_test, weekday) { @@ -852,103 +858,86 @@ TEST(chrono_test, utc_clock) { } #endif -TEST(chrono_test, timestamps_ratios) { - std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds> - t1(std::chrono::milliseconds(67890)); - +TEST(chrono_test, timestamp_ratios) { + auto t1 = + sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(67890)); EXPECT_EQ(fmt::format("{:%M:%S}", t1), "01:07.890"); - std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes> - t2(std::chrono::minutes(7)); - + auto t2 = sys_time<std::chrono::minutes>(std::chrono::minutes(7)); EXPECT_EQ(fmt::format("{:%M:%S}", t2), "07:00"); - std::chrono::time_point<std::chrono::system_clock, - std::chrono::duration<int, std::ratio<9>>> - t3(std::chrono::duration<int, std::ratio<9>>(7)); - + auto t3 = sys_time<std::chrono::duration<int, std::ratio<9>>>( + std::chrono::duration<int, std::ratio<9>>(7)); EXPECT_EQ(fmt::format("{:%M:%S}", t3), "01:03"); - std::chrono::time_point<std::chrono::system_clock, - std::chrono::duration<int, std::ratio<63>>> - t4(std::chrono::duration<int, std::ratio<63>>(1)); - + auto t4 = sys_time<std::chrono::duration<int, std::ratio<63>>>( + std::chrono::duration<int, std::ratio<63>>(1)); EXPECT_EQ(fmt::format("{:%M:%S}", t4), "01:03"); -} -TEST(chrono_test, timestamps_sub_seconds) { - std::chrono::time_point<std::chrono::system_clock, - std::chrono::duration<long long, std::ratio<1, 3>>> - t1(std::chrono::duration<long long, std::ratio<1, 3>>(4)); + if (sizeof(time_t) > 4) { + auto tp = + sys_time<std::chrono::milliseconds>(std::chrono::seconds(32503680000)); + EXPECT_EQ(fmt::format("{:%Y-%m-%d}", tp), "3000-01-01"); + } + + if (FMT_SAFE_DURATION_CAST) { + using years = std::chrono::duration<std::int64_t, std::ratio<31556952>>; + auto tp = sys_time<years>(years(std::numeric_limits<std::int64_t>::max())); + EXPECT_THROW_MSG((void)fmt::format("{:%Y-%m-%d}", tp), fmt::format_error, + "cannot format duration"); + } +} +TEST(chrono_test, timestamp_sub_seconds) { + auto t1 = sys_time<std::chrono::duration<long long, std::ratio<1, 3>>>( + std::chrono::duration<long long, std::ratio<1, 3>>(4)); EXPECT_EQ(fmt::format("{:%S}", t1), "01.333333"); - std::chrono::time_point<std::chrono::system_clock, - std::chrono::duration<double, std::ratio<1, 3>>> - t2(std::chrono::duration<double, std::ratio<1, 3>>(4)); - + auto t2 = sys_time<std::chrono::duration<double, std::ratio<1, 3>>>( + std::chrono::duration<double, std::ratio<1, 3>>(4)); EXPECT_EQ(fmt::format("{:%S}", t2), "01.333333"); - const std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> - t3(std::chrono::seconds(2)); - + auto t3 = sys_time<std::chrono::seconds>(std::chrono::seconds(2)); EXPECT_EQ(fmt::format("{:%S}", t3), "02"); - const std::chrono::time_point<std::chrono::system_clock, - std::chrono::duration<double>> - t4(std::chrono::duration<double, std::ratio<1, 1>>(9.5)); - + auto t4 = sys_time<std::chrono::duration<double>>( + std::chrono::duration<double, std::ratio<1, 1>>(9.5)); EXPECT_EQ(fmt::format("{:%S}", t4), "09.500000"); - const std::chrono::time_point<std::chrono::system_clock, - std::chrono::duration<double>> - t5(std::chrono::duration<double, std::ratio<1, 1>>(9)); - + auto t5 = sys_time<std::chrono::duration<double>>( + std::chrono::duration<double, std::ratio<1, 1>>(9)); EXPECT_EQ(fmt::format("{:%S}", t5), "09"); - const std::chrono::time_point<std::chrono::system_clock, - std::chrono::milliseconds> - t6(std::chrono::seconds(1) + std::chrono::milliseconds(120)); - + auto t6 = sys_time<std::chrono::milliseconds>(std::chrono::seconds(1) + + std::chrono::milliseconds(120)); EXPECT_EQ(fmt::format("{:%S}", t6), "01.120"); - const std::chrono::time_point<std::chrono::system_clock, - std::chrono::microseconds> - t7(std::chrono::microseconds(1234567)); - + auto t7 = + sys_time<std::chrono::microseconds>(std::chrono::microseconds(1234567)); EXPECT_EQ(fmt::format("{:%S}", t7), "01.234567"); - const std::chrono::time_point<std::chrono::system_clock, - std::chrono::nanoseconds> - t8(std::chrono::nanoseconds(123456789)); - + auto t8 = + sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789)); EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789"); - const auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>( + auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>( std::chrono::system_clock::now()); - const auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9); + auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9); auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count()); - EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part), fmt::format("{:%Y-%m-%d %H:%M:%S}", t9)); EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part), fmt::format("{:%Y-%m-%d %T}", t9)); - const std::chrono::time_point<std::chrono::system_clock, - std::chrono::milliseconds> - t10(std::chrono::milliseconds(2000)); - + auto t10 = + sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000)); EXPECT_EQ(fmt::format("{:%S}", t10), "02.000"); - { - const auto epoch = std::chrono::time_point<std::chrono::system_clock, - std::chrono::milliseconds>(); - const auto d = std::chrono::milliseconds(250); - - EXPECT_EQ("59.750", fmt::format("{:%S}", epoch - d)); - EXPECT_EQ("00.000", fmt::format("{:%S}", epoch)); - EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d)); - } + auto epoch = sys_time<std::chrono::milliseconds>(); + auto d = std::chrono::milliseconds(250); + EXPECT_EQ(fmt::format("{:%S}", epoch - d), "59.750"); + EXPECT_EQ(fmt::format("{:%S}", epoch), "00.000"); + EXPECT_EQ(fmt::format("{:%S}", epoch + d), "00.250"); } TEST(chrono_test, glibc_extensions) { @@ -1003,3 +992,8 @@ TEST(chrono_test, glibc_extensions) { EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000"); } } + +TEST(chrono_test, out_of_range) { + auto d = std::chrono::duration<unsigned long, std::giga>(538976288); + EXPECT_THROW((void)fmt::format("{:%j}", d), fmt::format_error); +}
\ No newline at end of file diff --git a/test/compile-test.cc b/test/compile-test.cc index d6c7c643..8551303e 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -280,15 +280,18 @@ TEST(compile_test, compile_format_string_literal) { #endif // MSVS 2019 19.29.30145.0 - Support C++20 and OK. -// MSVS 2022 19.32.31332.0 - compile-test.cc(362,3): fatal error C1001: Internal -// compiler error. +// MSVS 2022 19.32.31332.0, 19.37.32826.1 - compile-test.cc(362,3): fatal error +// C1001: Internal compiler error. // (compiler file // 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp', // line 8635) -#if ((FMT_CPLUSPLUS >= 202002L) && \ - (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \ - (!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \ - (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) +#if (FMT_CPLUSPLUS >= 202002L || \ + (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)) && \ + ((!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 10) && \ + (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 10000) && \ + (!FMT_MSC_VERSION || \ + (FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930))) && \ + defined(__cpp_lib_is_constant_evaluated) template <size_t max_string_length, typename Char = char> struct test_string { template <typename T> constexpr bool operator==(const T& rhs) const noexcept { return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0; diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 4d6198b6..eda1f239 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -246,13 +246,6 @@ TEST(format_impl_test, format_error_code) { } } -TEST(format_impl_test, compute_width) { - EXPECT_EQ(4, - fmt::detail::compute_width( - fmt::basic_string_view<fmt::detail::char8_type>( - reinterpret_cast<const fmt::detail::char8_type*>("ёжик")))); -} - // Tests fmt::detail::count_digits for integer type Int. template <typename Int> void test_count_digits() { for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i)); diff --git a/test/format-test.cc b/test/format-test.cc index 9788c246..325bef99 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -173,6 +173,10 @@ TEST(util_test, parse_nonnegative_int) { EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1); } +TEST(format_impl_test, compute_width) { + EXPECT_EQ(fmt::detail::compute_width("вожык"), 5); +} + TEST(util_test, utf8_to_utf16) { auto u = fmt::detail::utf8_to_utf16("лошадка"); EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str()); @@ -349,9 +353,9 @@ TEST(memory_buffer_test, move_assignment) { } TEST(memory_buffer_test, grow) { - typedef allocator_ref<mock_allocator<int>> Allocator; + using allocator = allocator_ref<mock_allocator<int>>; mock_allocator<int> alloc; - basic_memory_buffer<int, 10, Allocator> buffer((Allocator(&alloc))); + basic_memory_buffer<int, 10, allocator> buffer((allocator(&alloc))); buffer.resize(7); using fmt::detail::to_unsigned; for (int i = 0; i < 7; ++i) buffer[to_unsigned(i)] = i * i; @@ -448,18 +452,18 @@ TEST(format_test, exception_from_lib) { } TEST(format_test, escape) { - EXPECT_EQ("{", fmt::format("{{")); - EXPECT_EQ("before {", fmt::format("before {{")); - EXPECT_EQ("{ after", fmt::format("{{ after")); - EXPECT_EQ("before { after", fmt::format("before {{ after")); + EXPECT_EQ(fmt::format("{{"), "{"); + EXPECT_EQ(fmt::format("before {{"), "before {"); + EXPECT_EQ(fmt::format("{{ after"), "{ after"); + EXPECT_EQ(fmt::format("before {{ after"), "before { after"); - EXPECT_EQ("}", fmt::format("}}")); - EXPECT_EQ("before }", fmt::format("before }}")); - EXPECT_EQ("} after", fmt::format("}} after")); - EXPECT_EQ("before } after", fmt::format("before }} after")); + EXPECT_EQ(fmt::format("}}"), "}"); + EXPECT_EQ(fmt::format("before }}"), "before }"); + EXPECT_EQ(fmt::format("}} after"), "} after"); + EXPECT_EQ(fmt::format("before }} after"), "before } after"); - EXPECT_EQ("{}", fmt::format("{{}}")); - EXPECT_EQ("{42}", fmt::format("{{{0}}}", 42)); + EXPECT_EQ(fmt::format("{{}}"), "{}"); + EXPECT_EQ(fmt::format("{{{0}}}", 42), "{42}"); } TEST(format_test, unmatched_braces) { @@ -471,16 +475,16 @@ TEST(format_test, unmatched_braces) { "invalid format string"); } -TEST(format_test, no_args) { EXPECT_EQ("test", fmt::format("test")); } +TEST(format_test, no_args) { EXPECT_EQ(fmt::format("test"), "test"); } TEST(format_test, args_in_different_positions) { - EXPECT_EQ("42", fmt::format("{0}", 42)); - EXPECT_EQ("before 42", fmt::format("before {0}", 42)); - EXPECT_EQ("42 after", fmt::format("{0} after", 42)); - EXPECT_EQ("before 42 after", fmt::format("before {0} after", 42)); - EXPECT_EQ("answer = 42", fmt::format("{0} = {1}", "answer", 42)); - EXPECT_EQ("42 is the answer", fmt::format("{1} is the {0}", "answer", 42)); - EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad")); + EXPECT_EQ(fmt::format("{0}", 42), "42"); + EXPECT_EQ(fmt::format("before {0}", 42), "before 42"); + EXPECT_EQ(fmt::format("{0} after", 42), "42 after"); + EXPECT_EQ(fmt::format("before {0} after", 42), "before 42 after"); + EXPECT_EQ(fmt::format("{0} = {1}", "answer", 42), "answer = 42"); + EXPECT_EQ(fmt::format("{1} is the {0}", "answer", 42), "42 is the answer"); + EXPECT_EQ(fmt::format("{0}{1}{0}", "abra", "cad"), "abracadabra"); } TEST(format_test, arg_errors) { @@ -495,32 +499,29 @@ TEST(format_test, arg_errors) { EXPECT_THROW_MSG((void)fmt::format(runtime("{00}"), 42), format_error, "invalid format string"); - char format_str[buffer_size]; - safe_sprintf(format_str, "{%u", INT_MAX); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, + auto int_max = std::to_string(INT_MAX); + EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_max)), format_error, "invalid format string"); - safe_sprintf(format_str, "{%u}", INT_MAX); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, - "argument not found"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_max + "}")), + format_error, "argument not found"); - safe_sprintf(format_str, "{%u", INT_MAX + 1u); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, + auto int_maxer = std::to_string(INT_MAX + 1u); + EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_maxer)), format_error, "invalid format string"); - safe_sprintf(format_str, "{%u}", INT_MAX + 1u); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error, - "argument not found"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{" + int_maxer + "}")), + format_error, "argument not found"); } template <int N> struct test_format { template <typename... T> - static std::string format(fmt::string_view fmt, const T&... args) { + static auto format(fmt::string_view fmt, const T&... args) -> std::string { return test_format<N - 1>::format(fmt, N - 1, args...); } }; template <> struct test_format<0> { template <typename... T> - static std::string format(fmt::string_view fmt, const T&... args) { + static auto format(fmt::string_view fmt, const T&... args) -> std::string { return fmt::format(runtime(fmt), args...); } }; @@ -540,10 +541,10 @@ TEST(format_test, many_args) { TEST(format_test, named_arg) { EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'), fmt::arg("A_", "A"), fmt::arg("_1", 1))); - EXPECT_EQ(" -42", fmt::format("{0:{width}}", -42, fmt::arg("width", 4))); + EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42"); EXPECT_EQ("st", fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2))); - EXPECT_EQ("1 2", fmt::format("{} {two}", 1, fmt::arg("two", 2))); + EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2"); EXPECT_EQ("42", fmt::format("{c}", fmt::arg("a", 0), fmt::arg("b", 0), fmt::arg("c", 42), fmt::arg("d", 0), fmt::arg("e", 0), @@ -558,12 +559,12 @@ TEST(format_test, named_arg) { } TEST(format_test, auto_arg_index) { - EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c')); + EXPECT_EQ(fmt::format("{}{}{}", 'a', 'b', 'c'), "abc"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0}{}"), 'a', 'b'), format_error, "cannot switch from manual to automatic argument indexing"); EXPECT_THROW_MSG((void)fmt::format(runtime("{}{0}"), 'a', 'b'), format_error, "cannot switch from automatic to manual argument indexing"); - EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2)); + EXPECT_EQ(fmt::format("{:.{}}", 1.2345, 2), "1.2"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0}:.{}"), 1.2345, 2), format_error, "cannot switch from manual to automatic argument indexing"); @@ -574,57 +575,57 @@ TEST(format_test, auto_arg_index) { "argument not found"); } -TEST(format_test, empty_specs) { EXPECT_EQ("42", fmt::format("{0:}", 42)); } +TEST(format_test, empty_specs) { EXPECT_EQ(fmt::format("{0:}", 42), "42"); } TEST(format_test, left_align) { - EXPECT_EQ("42 ", fmt::format("{0:<4}", 42)); - EXPECT_EQ("42 ", fmt::format("{0:<4o}", 042)); - EXPECT_EQ("42 ", fmt::format("{0:<4x}", 0x42)); - EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42)); - EXPECT_EQ("42 ", fmt::format("{0:<5}", 42u)); - EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42l)); - EXPECT_EQ("42 ", fmt::format("{0:<5}", 42ul)); - EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42ll)); - EXPECT_EQ("42 ", fmt::format("{0:<5}", 42ull)); - EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42.0)); - EXPECT_EQ("-42 ", fmt::format("{0:<5}", -42.0l)); - EXPECT_EQ("c ", fmt::format("{0:<5}", 'c')); - EXPECT_EQ("abc ", fmt::format("{0:<5}", "abc")); - EXPECT_EQ("0xface ", fmt::format("{0:<8}", reinterpret_cast<void*>(0xface))); + EXPECT_EQ(fmt::format("{0:<4}", 42), "42 "); + EXPECT_EQ(fmt::format("{0:<4o}", 042), "42 "); + EXPECT_EQ(fmt::format("{0:<4x}", 0x42), "42 "); + EXPECT_EQ(fmt::format("{0:<5}", -42), "-42 "); + EXPECT_EQ(fmt::format("{0:<5}", 42u), "42 "); + EXPECT_EQ(fmt::format("{0:<5}", -42l), "-42 "); + EXPECT_EQ(fmt::format("{0:<5}", 42ul), "42 "); + EXPECT_EQ(fmt::format("{0:<5}", -42ll), "-42 "); + EXPECT_EQ(fmt::format("{0:<5}", 42ull), "42 "); + EXPECT_EQ(fmt::format("{0:<5}", -42.0), "-42 "); + EXPECT_EQ(fmt::format("{0:<5}", -42.0l), "-42 "); + EXPECT_EQ(fmt::format("{0:<5}", 'c'), "c "); + EXPECT_EQ(fmt::format("{0:<5}", "abc"), "abc "); + EXPECT_EQ(fmt::format("{0:<8}", reinterpret_cast<void*>(0xface)), "0xface "); } TEST(format_test, right_align) { - EXPECT_EQ(" 42", fmt::format("{0:>4}", 42)); - EXPECT_EQ(" 42", fmt::format("{0:>4o}", 042)); - EXPECT_EQ(" 42", fmt::format("{0:>4x}", 0x42)); - EXPECT_EQ(" -42", fmt::format("{0:>5}", -42)); - EXPECT_EQ(" 42", fmt::format("{0:>5}", 42u)); - EXPECT_EQ(" -42", fmt::format("{0:>5}", -42l)); - EXPECT_EQ(" 42", fmt::format("{0:>5}", 42ul)); - EXPECT_EQ(" -42", fmt::format("{0:>5}", -42ll)); - EXPECT_EQ(" 42", fmt::format("{0:>5}", 42ull)); - EXPECT_EQ(" -42", fmt::format("{0:>5}", -42.0)); - EXPECT_EQ(" -42", fmt::format("{0:>5}", -42.0l)); - EXPECT_EQ(" c", fmt::format("{0:>5}", 'c')); - EXPECT_EQ(" abc", fmt::format("{0:>5}", "abc")); - EXPECT_EQ(" 0xface", fmt::format("{0:>8}", reinterpret_cast<void*>(0xface))); + EXPECT_EQ(fmt::format("{0:>4}", 42), " 42"); + EXPECT_EQ(fmt::format("{0:>4o}", 042), " 42"); + EXPECT_EQ(fmt::format("{0:>4x}", 0x42), " 42"); + EXPECT_EQ(fmt::format("{0:>5}", -42), " -42"); + EXPECT_EQ(fmt::format("{0:>5}", 42u), " 42"); + EXPECT_EQ(fmt::format("{0:>5}", -42l), " -42"); + EXPECT_EQ(fmt::format("{0:>5}", 42ul), " 42"); + EXPECT_EQ(fmt::format("{0:>5}", -42ll), " -42"); + EXPECT_EQ(fmt::format("{0:>5}", 42ull), " 42"); + EXPECT_EQ(fmt::format("{0:>5}", -42.0), " -42"); + EXPECT_EQ(fmt::format("{0:>5}", -42.0l), " -42"); + EXPECT_EQ(fmt::format("{0:>5}", 'c'), " c"); + EXPECT_EQ(fmt::format("{0:>5}", "abc"), " abc"); + EXPECT_EQ(fmt::format("{0:>8}", reinterpret_cast<void*>(0xface)), " 0xface"); } TEST(format_test, center_align) { - EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42)); - EXPECT_EQ(" 42 ", fmt::format("{0:^5o}", 042)); - EXPECT_EQ(" 42 ", fmt::format("{0:^5x}", 0x42)); - EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42)); - EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42u)); - EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42l)); - EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42ul)); - EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42ll)); - EXPECT_EQ(" 42 ", fmt::format("{0:^5}", 42ull)); - EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0)); - EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0l)); - EXPECT_EQ(" c ", fmt::format("{0:^5}", 'c')); - EXPECT_EQ(" abc ", fmt::format("{0:^6}", "abc")); - EXPECT_EQ(" 0xface ", fmt::format("{0:^8}", reinterpret_cast<void*>(0xface))); + EXPECT_EQ(fmt::format("{0:^5}", 42), " 42 "); + EXPECT_EQ(fmt::format("{0:^5o}", 042), " 42 "); + EXPECT_EQ(fmt::format("{0:^5x}", 0x42), " 42 "); + EXPECT_EQ(fmt::format("{0:^5}", -42), " -42 "); + EXPECT_EQ(fmt::format("{0:^5}", 42u), " 42 "); + EXPECT_EQ(fmt::format("{0:^5}", -42l), " -42 "); + EXPECT_EQ(fmt::format("{0:^5}", 42ul), " 42 "); + EXPECT_EQ(fmt::format("{0:^5}", -42ll), " -42 "); + EXPECT_EQ(fmt::format("{0:^5}", 42ull), " 42 "); + EXPECT_EQ(fmt::format("{0:^5}", -42.0), " -42 "); + EXPECT_EQ(fmt::format("{0:^5}", -42.0l), " -42 "); + EXPECT_EQ(fmt::format("{0:^5}", 'c'), " c "); + EXPECT_EQ(fmt::format("{0:^6}", "abc"), " abc "); + EXPECT_EQ(fmt::format("{0:^8}", reinterpret_cast<void*>(0xface)), " 0xface "); } TEST(format_test, fill) { @@ -632,44 +633,44 @@ TEST(format_test, fill) { "invalid fill character '{'"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{<5}}"), 'c'), format_error, "invalid fill character '{'"); - EXPECT_EQ("**42", fmt::format("{0:*>4}", 42)); - EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42)); - EXPECT_EQ("***42", fmt::format("{0:*>5}", 42u)); - EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42l)); - EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ul)); - EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42ll)); - EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ull)); - EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0)); - EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0l)); - EXPECT_EQ("c****", fmt::format("{0:*<5}", 'c')); - EXPECT_EQ("abc**", fmt::format("{0:*<5}", "abc")); + EXPECT_EQ(fmt::format("{0:*>4}", 42), "**42"); + EXPECT_EQ(fmt::format("{0:*>5}", -42), "**-42"); + EXPECT_EQ(fmt::format("{0:*>5}", 42u), "***42"); + EXPECT_EQ(fmt::format("{0:*>5}", -42l), "**-42"); + EXPECT_EQ(fmt::format("{0:*>5}", 42ul), "***42"); + EXPECT_EQ(fmt::format("{0:*>5}", -42ll), "**-42"); + EXPECT_EQ(fmt::format("{0:*>5}", 42ull), "***42"); + EXPECT_EQ(fmt::format("{0:*>5}", -42.0), "**-42"); + EXPECT_EQ(fmt::format("{0:*>5}", -42.0l), "**-42"); + EXPECT_EQ(fmt::format("{0:*<5}", 'c'), "c****"); + EXPECT_EQ(fmt::format("{0:*<5}", "abc"), "abc**"); EXPECT_EQ("**0xface", fmt::format("{0:*>8}", reinterpret_cast<void*>(0xface))); - EXPECT_EQ("foo=", fmt::format("{:}=", "foo")); + EXPECT_EQ(fmt::format("{:}=", "foo"), "foo="); EXPECT_EQ(std::string("\0\0\0*", 4), fmt::format(string_view("{:\0>4}", 6), '*')); - EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42)); + EXPECT_EQ(fmt::format("{0:ж>4}", 42), "жж42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0), format_error, "invalid format specifier"); } TEST(format_test, plus_sign) { - EXPECT_EQ("+42", fmt::format("{0:+}", 42)); - EXPECT_EQ("-42", fmt::format("{0:+}", -42)); - EXPECT_EQ("+42", fmt::format("{0:+}", 42)); + EXPECT_EQ(fmt::format("{0:+}", 42), "+42"); + EXPECT_EQ(fmt::format("{0:+}", -42), "-42"); + EXPECT_EQ(fmt::format("{0:+}", 42), "+42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42u), format_error, "invalid format specifier"); - EXPECT_EQ("+42", fmt::format("{0:+}", 42l)); + EXPECT_EQ(fmt::format("{0:+}", 42l), "+42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ul), format_error, "invalid format specifier"); - EXPECT_EQ("+42", fmt::format("{0:+}", 42ll)); + EXPECT_EQ(fmt::format("{0:+}", 42ll), "+42"); #if FMT_USE_INT128 - EXPECT_EQ("+42", fmt::format("{0:+}", __int128_t(42))); + EXPECT_EQ(fmt::format("{0:+}", __int128_t(42)), "+42"); #endif EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ull), format_error, "invalid format specifier"); - EXPECT_EQ("+42", fmt::format("{0:+}", 42.0)); - EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l)); + EXPECT_EQ(fmt::format("{0:+}", 42.0), "+42"); + EXPECT_EQ(fmt::format("{0:+}", 42.0l), "+42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 'c'), format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), "abc"), format_error, @@ -680,19 +681,19 @@ TEST(format_test, plus_sign) { } TEST(format_test, minus_sign) { - EXPECT_EQ("42", fmt::format("{0:-}", 42)); - EXPECT_EQ("-42", fmt::format("{0:-}", -42)); - EXPECT_EQ("42", fmt::format("{0:-}", 42)); + EXPECT_EQ(fmt::format("{0:-}", 42), "42"); + EXPECT_EQ(fmt::format("{0:-}", -42), "-42"); + EXPECT_EQ(fmt::format("{0:-}", 42), "42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42u), format_error, "invalid format specifier"); - EXPECT_EQ("42", fmt::format("{0:-}", 42l)); + EXPECT_EQ(fmt::format("{0:-}", 42l), "42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ul), format_error, "invalid format specifier"); - EXPECT_EQ("42", fmt::format("{0:-}", 42ll)); + EXPECT_EQ(fmt::format("{0:-}", 42ll), "42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ull), format_error, "invalid format specifier"); - EXPECT_EQ("42", fmt::format("{0:-}", 42.0)); - EXPECT_EQ("42", fmt::format("{0:-}", 42.0l)); + EXPECT_EQ(fmt::format("{0:-}", 42.0), "42"); + EXPECT_EQ(fmt::format("{0:-}", 42.0l), "42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 'c'), format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), "abc"), format_error, @@ -703,19 +704,19 @@ TEST(format_test, minus_sign) { } TEST(format_test, space_sign) { - EXPECT_EQ(" 42", fmt::format("{0: }", 42)); - EXPECT_EQ("-42", fmt::format("{0: }", -42)); - EXPECT_EQ(" 42", fmt::format("{0: }", 42)); + EXPECT_EQ(fmt::format("{0: }", 42), " 42"); + EXPECT_EQ(fmt::format("{0: }", -42), "-42"); + EXPECT_EQ(fmt::format("{0: }", 42), " 42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42u), format_error, "invalid format specifier"); - EXPECT_EQ(" 42", fmt::format("{0: }", 42l)); + EXPECT_EQ(fmt::format("{0: }", 42l), " 42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ul), format_error, "invalid format specifier"); - EXPECT_EQ(" 42", fmt::format("{0: }", 42ll)); + EXPECT_EQ(fmt::format("{0: }", 42ll), " 42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ull), format_error, "invalid format specifier"); - EXPECT_EQ(" 42", fmt::format("{0: }", 42.0)); - EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l)); + EXPECT_EQ(fmt::format("{0: }", 42.0), " 42"); + EXPECT_EQ(fmt::format("{0: }", 42.0l), " 42"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 'c'), format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), "abc"), format_error, @@ -726,45 +727,45 @@ TEST(format_test, space_sign) { } TEST(format_test, hash_flag) { - EXPECT_EQ("42", fmt::format("{0:#}", 42)); - EXPECT_EQ("-42", fmt::format("{0:#}", -42)); - EXPECT_EQ("0b101010", fmt::format("{0:#b}", 42)); - EXPECT_EQ("0B101010", fmt::format("{0:#B}", 42)); - EXPECT_EQ("-0b101010", fmt::format("{0:#b}", -42)); - EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42)); - EXPECT_EQ("0X42", fmt::format("{0:#X}", 0x42)); - EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42)); - EXPECT_EQ("0", fmt::format("{0:#o}", 0)); - EXPECT_EQ("042", fmt::format("{0:#o}", 042)); - EXPECT_EQ("-042", fmt::format("{0:#o}", -042)); - EXPECT_EQ("42", fmt::format("{0:#}", 42u)); - EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42u)); - EXPECT_EQ("042", fmt::format("{0:#o}", 042u)); - - EXPECT_EQ("-42", fmt::format("{0:#}", -42l)); - EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42l)); - EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42l)); - EXPECT_EQ("042", fmt::format("{0:#o}", 042l)); - EXPECT_EQ("-042", fmt::format("{0:#o}", -042l)); - EXPECT_EQ("42", fmt::format("{0:#}", 42ul)); - EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ul)); - EXPECT_EQ("042", fmt::format("{0:#o}", 042ul)); - - EXPECT_EQ("-42", fmt::format("{0:#}", -42ll)); - EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ll)); - EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42ll)); - EXPECT_EQ("042", fmt::format("{0:#o}", 042ll)); - EXPECT_EQ("-042", fmt::format("{0:#o}", -042ll)); - EXPECT_EQ("42", fmt::format("{0:#}", 42ull)); - EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ull)); - EXPECT_EQ("042", fmt::format("{0:#o}", 042ull)); - - EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0)); - EXPECT_EQ("-42.", fmt::format("{0:#}", -42.0l)); - EXPECT_EQ("4.e+01", fmt::format("{:#.0e}", 42.0)); - EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01)); - EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5)); - EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5)); + EXPECT_EQ(fmt::format("{0:#}", 42), "42"); + EXPECT_EQ(fmt::format("{0:#}", -42), "-42"); + EXPECT_EQ(fmt::format("{0:#b}", 42), "0b101010"); + EXPECT_EQ(fmt::format("{0:#B}", 42), "0B101010"); + EXPECT_EQ(fmt::format("{0:#b}", -42), "-0b101010"); + EXPECT_EQ(fmt::format("{0:#x}", 0x42), "0x42"); + EXPECT_EQ(fmt::format("{0:#X}", 0x42), "0X42"); + EXPECT_EQ(fmt::format("{0:#x}", -0x42), "-0x42"); + EXPECT_EQ(fmt::format("{0:#o}", 0), "0"); + EXPECT_EQ(fmt::format("{0:#o}", 042), "042"); + EXPECT_EQ(fmt::format("{0:#o}", -042), "-042"); + EXPECT_EQ(fmt::format("{0:#}", 42u), "42"); + EXPECT_EQ(fmt::format("{0:#x}", 0x42u), "0x42"); + EXPECT_EQ(fmt::format("{0:#o}", 042u), "042"); + + EXPECT_EQ(fmt::format("{0:#}", -42l), "-42"); + EXPECT_EQ(fmt::format("{0:#x}", 0x42l), "0x42"); + EXPECT_EQ(fmt::format("{0:#x}", -0x42l), "-0x42"); + EXPECT_EQ(fmt::format("{0:#o}", 042l), "042"); + EXPECT_EQ(fmt::format("{0:#o}", -042l), "-042"); + EXPECT_EQ(fmt::format("{0:#}", 42ul), "42"); + EXPECT_EQ(fmt::format("{0:#x}", 0x42ul), "0x42"); + EXPECT_EQ(fmt::format("{0:#o}", 042ul), "042"); + + EXPECT_EQ(fmt::format("{0:#}", -42ll), "-42"); + EXPECT_EQ(fmt::format("{0:#x}", 0x42ll), "0x42"); + EXPECT_EQ(fmt::format("{0:#x}", -0x42ll), "-0x42"); + EXPECT_EQ(fmt::format("{0:#o}", 042ll), "042"); + EXPECT_EQ(fmt::format("{0:#o}", -042ll), "-042"); + EXPECT_EQ(fmt::format("{0:#}", 42ull), "42"); + EXPECT_EQ(fmt::format("{0:#x}", 0x42ull), "0x42"); + EXPECT_EQ(fmt::format("{0:#o}", 042ull), "042"); + + EXPECT_EQ(fmt::format("{0:#}", -42.0), "-42."); + EXPECT_EQ(fmt::format("{0:#}", -42.0l), "-42."); + EXPECT_EQ(fmt::format("{:#.0e}", 42.0), "4.e+01"); + EXPECT_EQ(fmt::format("{:#.0f}", 0.01), "0."); + EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50"); + EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0."); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error, @@ -777,15 +778,15 @@ TEST(format_test, hash_flag) { } TEST(format_test, zero_flag) { - EXPECT_EQ("42", fmt::format("{0:0}", 42)); - EXPECT_EQ("-0042", fmt::format("{0:05}", -42)); - EXPECT_EQ("00042", fmt::format("{0:05}", 42u)); - EXPECT_EQ("-0042", fmt::format("{0:05}", -42l)); - EXPECT_EQ("00042", fmt::format("{0:05}", 42ul)); - EXPECT_EQ("-0042", fmt::format("{0:05}", -42ll)); - EXPECT_EQ("00042", fmt::format("{0:05}", 42ull)); - EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0)); - EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l)); + EXPECT_EQ(fmt::format("{0:0}", 42), "42"); + EXPECT_EQ(fmt::format("{0:05}", -42), "-0042"); + EXPECT_EQ(fmt::format("{0:05}", 42u), "00042"); + EXPECT_EQ(fmt::format("{0:05}", -42l), "-0042"); + EXPECT_EQ(fmt::format("{0:05}", 42ul), "00042"); + EXPECT_EQ(fmt::format("{0:05}", -42ll), "-0042"); + EXPECT_EQ(fmt::format("{0:05}", 42ull), "00042"); + EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042"); + EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error, "missing '}' in format string"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error, @@ -800,44 +801,33 @@ TEST(format_test, zero_flag) { TEST(format_test, zero_flag_and_align) { // If the 0 character and an align option both appear, the 0 character is // ignored. - EXPECT_EQ("42 ", fmt::format("{0:<05}", 42)); - EXPECT_EQ("-42 ", fmt::format("{0:<05}", -42)); - EXPECT_EQ(" 42 ", fmt::format("{0:^05}", 42)); - EXPECT_EQ(" -42 ", fmt::format("{0:^05}", -42)); - EXPECT_EQ(" 42", fmt::format("{0:>05}", 42)); - EXPECT_EQ(" -42", fmt::format("{0:>05}", -42)); + EXPECT_EQ(fmt::format("{:<05}", 42), "42 "); + EXPECT_EQ(fmt::format("{:<05}", -42), "-42 "); + EXPECT_EQ(fmt::format("{:^05}", 42), " 42 "); + EXPECT_EQ(fmt::format("{:^05}", -42), " -42 "); + EXPECT_EQ(fmt::format("{:>05}", 42), " 42"); + EXPECT_EQ(fmt::format("{:>05}", -42), " -42"); } TEST(format_test, width) { - char format_str[buffer_size]; - safe_sprintf(format_str, "{0:%u", UINT_MAX); - increment(format_str + 3); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, - "number is too big"); - size_t size = std::strlen(format_str); - format_str[size] = '}'; - format_str[size + 1] = 0; - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, - "number is too big"); + auto int_maxer = std::to_string(INT_MAX + 1u); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:" + int_maxer), 0), + format_error, "number is too big"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:" + int_maxer + "}"), 0), + format_error, "number is too big"); - safe_sprintf(format_str, "{0:%u", INT_MAX + 1u); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, - "number is too big"); - safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, - "number is too big"); - EXPECT_EQ(" -42", fmt::format("{0:4}", -42)); - EXPECT_EQ(" 42", fmt::format("{0:5}", 42u)); - EXPECT_EQ(" -42", fmt::format("{0:6}", -42l)); - EXPECT_EQ(" 42", fmt::format("{0:7}", 42ul)); - EXPECT_EQ(" -42", fmt::format("{0:6}", -42ll)); - EXPECT_EQ(" 42", fmt::format("{0:7}", 42ull)); - EXPECT_EQ(" -1.23", fmt::format("{0:8}", -1.23)); - EXPECT_EQ(" -1.23", fmt::format("{0:9}", -1.23l)); - EXPECT_EQ(" 0xcafe", - fmt::format("{0:10}", reinterpret_cast<void*>(0xcafe))); - EXPECT_EQ("x ", fmt::format("{0:11}", 'x')); - EXPECT_EQ("str ", fmt::format("{0:12}", "str")); + EXPECT_EQ(fmt::format("{:4}", -42), " -42"); + EXPECT_EQ(fmt::format("{:5}", 42u), " 42"); + EXPECT_EQ(fmt::format("{:6}", -42l), " -42"); + EXPECT_EQ(fmt::format("{:7}", 42ul), " 42"); + EXPECT_EQ(fmt::format("{:6}", -42ll), " -42"); + EXPECT_EQ(fmt::format("{:7}", 42ull), " 42"); + EXPECT_EQ(fmt::format("{:8}", -1.23), " -1.23"); + EXPECT_EQ(fmt::format("{:9}", -1.23l), " -1.23"); + EXPECT_EQ(fmt::format("{:10}", reinterpret_cast<void*>(0xcafe)), + " 0xcafe"); + EXPECT_EQ(fmt::format("{:11}", 'x'), "x "); + EXPECT_EQ(fmt::format("{:12}", "str"), "str "); EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**"); EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**"); EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42."); @@ -846,20 +836,13 @@ TEST(format_test, width) { } TEST(format_test, runtime_width) { - char format_str[buffer_size]; - safe_sprintf(format_str, "{0:{%u", UINT_MAX); - increment(format_str + 4); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, - "invalid format string"); - size_t size = std::strlen(format_str); - format_str[size] = '}'; - format_str[size + 1] = 0; - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, - "argument not found"); - format_str[size + 1] = '}'; - format_str[size + 2] = 0; - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, - "argument not found"); + auto int_maxer = std::to_string(INT_MAX + 1u); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0), + format_error, "invalid format string"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer + "}"), 0), + format_error, "argument not found"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer + "}}"), 0), + format_error, "argument not found"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{"), 0), format_error, "invalid format string"); @@ -892,18 +875,18 @@ TEST(format_test, runtime_width) { EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error, "width is not integer"); - EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4)); - EXPECT_EQ(" 42", fmt::format("{0:{1}}", 42u, 5)); - EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42l, 6)); - EXPECT_EQ(" 42", fmt::format("{0:{1}}", 42ul, 7)); - EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42ll, 6)); - EXPECT_EQ(" 42", fmt::format("{0:{1}}", 42ull, 7)); - EXPECT_EQ(" -1.23", fmt::format("{0:{1}}", -1.23, 8)); - EXPECT_EQ(" -1.23", fmt::format("{0:{1}}", -1.23l, 9)); + EXPECT_EQ(fmt::format("{0:{1}}", -42, 4), " -42"); + EXPECT_EQ(fmt::format("{0:{1}}", 42u, 5), " 42"); + EXPECT_EQ(fmt::format("{0:{1}}", -42l, 6), " -42"); + EXPECT_EQ(fmt::format("{0:{1}}", 42ul, 7), " 42"); + EXPECT_EQ(fmt::format("{0:{1}}", -42ll, 6), " -42"); + EXPECT_EQ(fmt::format("{0:{1}}", 42ull, 7), " 42"); + EXPECT_EQ(fmt::format("{0:{1}}", -1.23, 8), " -1.23"); + EXPECT_EQ(fmt::format("{0:{1}}", -1.23l, 9), " -1.23"); EXPECT_EQ(" 0xcafe", fmt::format("{0:{1}}", reinterpret_cast<void*>(0xcafe), 10)); - EXPECT_EQ("x ", fmt::format("{0:{1}}", 'x', 11)); - EXPECT_EQ("str ", fmt::format("{0:{1}}", "str", 12)); + EXPECT_EQ(fmt::format("{0:{1}}", 'x', 11), "x "); + EXPECT_EQ(fmt::format("{0:{1}}", "str", 12), "str "); EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), " 42"); } @@ -959,12 +942,12 @@ TEST(format_test, precision) { "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.0}"), 'x'), format_error, "invalid format specifier"); - EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345)); - EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l)); - EXPECT_EQ("1.2e+56", fmt::format("{:.2}", 1.234e56)); - EXPECT_EQ("1.1", fmt::format("{0:.3}", 1.1)); - EXPECT_EQ("1e+00", fmt::format("{:.0e}", 1.0L)); - EXPECT_EQ(" 0.0e+00", fmt::format("{:9.1e}", 0.0)); + EXPECT_EQ(fmt::format("{0:.2}", 1.2345), "1.2"); + EXPECT_EQ(fmt::format("{0:.2}", 1.2345l), "1.2"); + EXPECT_EQ(fmt::format("{:.2}", 1.234e56), "1.2e+56"); + EXPECT_EQ(fmt::format("{0:.3}", 1.1), "1.1"); + EXPECT_EQ(fmt::format("{:.0e}", 1.0L), "1e+00"); + EXPECT_EQ(fmt::format("{:9.1e}", 0.0), " 0.0e+00"); EXPECT_EQ( fmt::format("{:.494}", 4.9406564584124654E-324), "4.9406564584124654417656879286822137236505980261432476442558568250067550" @@ -1028,12 +1011,12 @@ TEST(format_test, precision) { "3.6452e-4951"); } - EXPECT_EQ("123.", fmt::format("{:#.0f}", 123.0)); - EXPECT_EQ("1.23", fmt::format("{:.02f}", 1.234)); - EXPECT_EQ("0.001", fmt::format("{:.1g}", 0.001)); - EXPECT_EQ("1019666400", fmt::format("{}", 1019666432.0f)); - EXPECT_EQ("1e+01", fmt::format("{:.0e}", 9.5)); - EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34)); + EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123."); + EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23"); + EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001"); + EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1019666400"); + EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01"); + EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34"); EXPECT_THROW_MSG( (void)fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)), @@ -1048,8 +1031,15 @@ TEST(format_test, precision) { (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304), format_error, "number is too big"); - EXPECT_EQ("st", fmt::format("{0:.2}", "str")); - EXPECT_EQ("вожык", fmt::format("{0:.5}", "вожыкі")); + EXPECT_EQ(fmt::format("{0:.2}", "str"), "st"); + EXPECT_EQ(fmt::format("{0:.5}", "вожыкі"), "вожык"); + EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456"); +} + +TEST(xchar_test, utf8_precision) { + auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés + EXPECT_EQ(fmt::detail::compute_width(result), 4); + EXPECT_EQ(result, "caf\u00e9"); } TEST(format_test, runtime_precision) { @@ -1127,8 +1117,8 @@ TEST(format_test, runtime_precision) { format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.{1}}"), 'x', 0), format_error, "invalid format specifier"); - EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2)); - EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l)); + EXPECT_EQ(fmt::format("{0:.{1}}", 1.2345, 2), "1.2"); + EXPECT_EQ(fmt::format("{1:.{0}}", 2, 1.2345l), "1.2"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), reinterpret_cast<void*>(0xcafe), 2), @@ -1137,24 +1127,26 @@ TEST(format_test, runtime_precision) { reinterpret_cast<void*>(0xcafe), 2), format_error, "invalid format specifier"); - EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2)); + EXPECT_EQ(fmt::format("{0:.{1}}", "str", 2), "st"); } TEST(format_test, format_bool) { - EXPECT_EQ("true", fmt::format("{}", true)); - EXPECT_EQ("false", fmt::format("{}", false)); - EXPECT_EQ("1", fmt::format("{:d}", true)); - EXPECT_EQ("true ", fmt::format("{:5}", true)); - EXPECT_EQ("true", fmt::format("{:s}", true)); - EXPECT_EQ("false", fmt::format("{:s}", false)); - EXPECT_EQ("false ", fmt::format("{:6s}", false)); + EXPECT_EQ(fmt::format("{}", true), "true"); + EXPECT_EQ(fmt::format("{}", false), "false"); + EXPECT_EQ(fmt::format("{:d}", true), "1"); + EXPECT_EQ(fmt::format("{:5}", true), "true "); + EXPECT_EQ(fmt::format("{:s}", true), "true"); + EXPECT_EQ(fmt::format("{:s}", false), "false"); + EXPECT_EQ(fmt::format("{:6s}", false), "false "); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:c}"), false), format_error, + "invalid format specifier"); } TEST(format_test, format_short) { short s = 42; - EXPECT_EQ("42", fmt::format("{0:d}", s)); + EXPECT_EQ(fmt::format("{0:d}", s), "42"); unsigned short us = 42; - EXPECT_EQ("42", fmt::format("{0:d}", us)); + EXPECT_EQ(fmt::format("{0:d}", us), "42"); } template <typename T> @@ -1176,16 +1168,16 @@ TEST(format_test, format_int) { EXPECT_THROW_MSG((void)fmt::format(runtime("{0:v"), 42), format_error, "invalid format specifier"); check_unknown_types(42, "bBdoxXnLc", "integer"); - EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x'))); + EXPECT_EQ(fmt::format("{:c}", static_cast<int>('x')), "x"); } TEST(format_test, format_bin) { - EXPECT_EQ("0", fmt::format("{0:b}", 0)); - EXPECT_EQ("101010", fmt::format("{0:b}", 42)); - EXPECT_EQ("101010", fmt::format("{0:b}", 42u)); - EXPECT_EQ("-101010", fmt::format("{0:b}", -42)); - EXPECT_EQ("11000000111001", fmt::format("{0:b}", 12345)); - EXPECT_EQ("10010001101000101011001111000", fmt::format("{0:b}", 0x12345678)); + EXPECT_EQ(fmt::format("{0:b}", 0), "0"); + EXPECT_EQ(fmt::format("{0:b}", 42), "101010"); + EXPECT_EQ(fmt::format("{0:b}", 42u), "101010"); + EXPECT_EQ(fmt::format("{0:b}", -42), "-101010"); + EXPECT_EQ(fmt::format("{0:b}", 12345), "11000000111001"); + EXPECT_EQ(fmt::format("{0:b}", 0x12345678), "10010001101000101011001111000"); EXPECT_EQ("10010000101010111100110111101111", fmt::format("{0:b}", 0x90ABCDEF)); EXPECT_EQ("11111111111111111111111111111111", @@ -1201,17 +1193,17 @@ constexpr auto uint128_max = ~static_cast<__uint128_t>(0); #endif TEST(format_test, format_dec) { - EXPECT_EQ("0", fmt::format("{0}", 0)); - EXPECT_EQ("42", fmt::format("{0}", 42)); - EXPECT_EQ("42>", fmt::format("{:}>", 42)); - EXPECT_EQ("42", fmt::format("{0:d}", 42)); - EXPECT_EQ("42", fmt::format("{0}", 42u)); - EXPECT_EQ("-42", fmt::format("{0}", -42)); - EXPECT_EQ("12345", fmt::format("{0}", 12345)); - EXPECT_EQ("67890", fmt::format("{0}", 67890)); + EXPECT_EQ(fmt::format("{0}", 0), "0"); + EXPECT_EQ(fmt::format("{0}", 42), "42"); + EXPECT_EQ(fmt::format("{:}>", 42), "42>"); + EXPECT_EQ(fmt::format("{0:d}", 42), "42"); + EXPECT_EQ(fmt::format("{0}", 42u), "42"); + EXPECT_EQ(fmt::format("{0}", -42), "-42"); + EXPECT_EQ(fmt::format("{0}", 12345), "12345"); + EXPECT_EQ(fmt::format("{0}", 67890), "67890"); #if FMT_USE_INT128 - EXPECT_EQ("0", fmt::format("{0}", static_cast<__int128_t>(0))); - EXPECT_EQ("0", fmt::format("{0}", static_cast<__uint128_t>(0))); + EXPECT_EQ(fmt::format("{0}", static_cast<__int128_t>(0)), "0"); + EXPECT_EQ(fmt::format("{0}", static_cast<__uint128_t>(0)), "0"); EXPECT_EQ("9223372036854775808", fmt::format("{0}", static_cast<__int128_t>(INT64_MAX) + 1)); EXPECT_EQ("-9223372036854775809", @@ -1242,17 +1234,17 @@ TEST(format_test, format_dec) { } TEST(format_test, format_hex) { - EXPECT_EQ("0", fmt::format("{0:x}", 0)); - EXPECT_EQ("42", fmt::format("{0:x}", 0x42)); - EXPECT_EQ("42", fmt::format("{0:x}", 0x42u)); - EXPECT_EQ("-42", fmt::format("{0:x}", -0x42)); - EXPECT_EQ("12345678", fmt::format("{0:x}", 0x12345678)); - EXPECT_EQ("90abcdef", fmt::format("{0:x}", 0x90abcdef)); - EXPECT_EQ("12345678", fmt::format("{0:X}", 0x12345678)); - EXPECT_EQ("90ABCDEF", fmt::format("{0:X}", 0x90ABCDEF)); + EXPECT_EQ(fmt::format("{0:x}", 0), "0"); + EXPECT_EQ(fmt::format("{0:x}", 0x42), "42"); + EXPECT_EQ(fmt::format("{0:x}", 0x42u), "42"); + EXPECT_EQ(fmt::format("{0:x}", -0x42), "-42"); + EXPECT_EQ(fmt::format("{0:x}", 0x12345678), "12345678"); + EXPECT_EQ(fmt::format("{0:x}", 0x90abcdef), "90abcdef"); + EXPECT_EQ(fmt::format("{0:X}", 0x12345678), "12345678"); + EXPECT_EQ(fmt::format("{0:X}", 0x90ABCDEF), "90ABCDEF"); #if FMT_USE_INT128 - EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__int128_t>(0))); - EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__uint128_t>(0))); + EXPECT_EQ(fmt::format("{0:x}", static_cast<__int128_t>(0)), "0"); + EXPECT_EQ(fmt::format("{0:x}", static_cast<__uint128_t>(0)), "0"); EXPECT_EQ("8000000000000000", fmt::format("{0:x}", static_cast<__int128_t>(INT64_MAX) + 1)); EXPECT_EQ("-8000000000000001", @@ -1283,14 +1275,14 @@ TEST(format_test, format_hex) { } TEST(format_test, format_oct) { - EXPECT_EQ("0", fmt::format("{0:o}", 0)); - EXPECT_EQ("42", fmt::format("{0:o}", 042)); - EXPECT_EQ("42", fmt::format("{0:o}", 042u)); - EXPECT_EQ("-42", fmt::format("{0:o}", -042)); - EXPECT_EQ("12345670", fmt::format("{0:o}", 012345670)); + EXPECT_EQ(fmt::format("{0:o}", 0), "0"); + EXPECT_EQ(fmt::format("{0:o}", 042), "42"); + EXPECT_EQ(fmt::format("{0:o}", 042u), "42"); + EXPECT_EQ(fmt::format("{0:o}", -042), "-42"); + EXPECT_EQ(fmt::format("{0:o}", 012345670), "12345670"); #if FMT_USE_INT128 - EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__int128_t>(0))); - EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__uint128_t>(0))); + EXPECT_EQ(fmt::format("{0:o}", static_cast<__int128_t>(0)), "0"); + EXPECT_EQ(fmt::format("{0:o}", static_cast<__uint128_t>(0)), "0"); EXPECT_EQ("1000000000000000000000", fmt::format("{0:o}", static_cast<__int128_t>(INT64_MAX) + 1)); EXPECT_EQ("-1000000000000000000001", @@ -1321,12 +1313,12 @@ TEST(format_test, format_oct) { } TEST(format_test, format_int_locale) { - EXPECT_EQ("1234", fmt::format("{:L}", 1234)); + EXPECT_EQ(fmt::format("{:L}", 1234), "1234"); } TEST(format_test, format_float) { - EXPECT_EQ("0", fmt::format("{}", 0.0f)); - EXPECT_EQ("392.500000", fmt::format("{0:f}", 392.5f)); + EXPECT_EQ(fmt::format("{}", 0.0f), "0"); + EXPECT_EQ(fmt::format("{0:f}", 392.5f), "392.500000"); } TEST(format_test, format_double) { @@ -1348,14 +1340,11 @@ TEST(format_test, format_double) { EXPECT_EQ(fmt::format("{0:e}", 392.65), "3.926500e+02"); EXPECT_EQ(fmt::format("{0:E}", 392.65), "3.926500E+02"); EXPECT_EQ(fmt::format("{0:+010.4g}", 392.65), "+0000392.6"); - char buffer[buffer_size]; #if FMT_CPLUSPLUS >= 201703L double xd = 0x1.ffffffffffp+2; - safe_sprintf(buffer, "%.*a", 10, xd); - EXPECT_EQ(fmt::format("{:.10a}", xd), buffer); - safe_sprintf(buffer, "%.*a", 9, xd); - EXPECT_EQ(fmt::format("{:.9a}", xd), buffer); + EXPECT_EQ(fmt::format("{:.10a}", xd), "0x1.ffffffffffp+2"); + EXPECT_EQ(fmt::format("{:.9a}", xd), "0x2.000000000p+2"); if (std::numeric_limits<long double>::digits == 64) { auto ld = 0xf.ffffffffffp-3l; @@ -1371,8 +1360,7 @@ TEST(format_test, format_double) { EXPECT_EQ(fmt::format("{:#a}", d), "0x1.p-1022"); d = (std::numeric_limits<double>::max)(); - safe_sprintf(buffer, "%a", d); - EXPECT_EQ(fmt::format("{:a}", d), buffer); + EXPECT_EQ(fmt::format("{:a}", d), "0x1.fffffffffffffp+1023"); d = std::numeric_limits<double>::denorm_min(); EXPECT_EQ(fmt::format("{:a}", d), "0x0.0000000000001p-1022"); @@ -1389,8 +1377,7 @@ TEST(format_test, format_double) { EXPECT_EQ(fmt::format("{:a}", ld), "0x0.000000000000001p-16382"); } - safe_sprintf(buffer, "%.*a", 10, 4.2); - EXPECT_EQ(fmt::format("{:.10a}", 4.2), buffer); + EXPECT_EQ(fmt::format("{:.10a}", 4.2), "0x1.0ccccccccdp+2"); EXPECT_EQ(fmt::format("{:a}", -42.0), "-0x1.5p+5"); EXPECT_EQ(fmt::format("{:A}", -42.0), "-0X1.5P+5"); @@ -1400,92 +1387,92 @@ TEST(format_test, format_double) { } TEST(format_test, precision_rounding) { - EXPECT_EQ("0", fmt::format("{:.0f}", 0.0)); - EXPECT_EQ("0", fmt::format("{:.0f}", 0.01)); - EXPECT_EQ("0", fmt::format("{:.0f}", 0.1)); - EXPECT_EQ("0.000", fmt::format("{:.3f}", 0.00049)); - EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.0005)); - EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.00149)); - EXPECT_EQ("0.002", fmt::format("{:.3f}", 0.0015)); - EXPECT_EQ("1.000", fmt::format("{:.3f}", 0.9999)); - EXPECT_EQ("0.00123", fmt::format("{:.3}", 0.00123)); - EXPECT_EQ("0.1", fmt::format("{:.16g}", 0.1)); - EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); + EXPECT_EQ(fmt::format("{:.0f}", 0.0), "0"); + EXPECT_EQ(fmt::format("{:.0f}", 0.01), "0"); + EXPECT_EQ(fmt::format("{:.0f}", 0.1), "0"); + EXPECT_EQ(fmt::format("{:.3f}", 0.00049), "0.000"); + EXPECT_EQ(fmt::format("{:.3f}", 0.0005), "0.001"); + EXPECT_EQ(fmt::format("{:.3f}", 0.00149), "0.001"); + EXPECT_EQ(fmt::format("{:.3f}", 0.0015), "0.002"); + EXPECT_EQ(fmt::format("{:.3f}", 0.9999), "1.000"); + EXPECT_EQ(fmt::format("{:.3}", 0.00123), "0.00123"); + EXPECT_EQ(fmt::format("{:.16g}", 0.1), "0.1"); + EXPECT_EQ(fmt::format("{:.0}", 1.0), "1"); EXPECT_EQ("225.51575035152063720", fmt::format("{:.17f}", 225.51575035152064)); - EXPECT_EQ("-761519619559038.2", fmt::format("{:.1f}", -761519619559038.2)); + EXPECT_EQ(fmt::format("{:.1f}", -761519619559038.2), "-761519619559038.2"); EXPECT_EQ("1.9156918820264798e-56", fmt::format("{}", 1.9156918820264798e-56)); - EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15)); + EXPECT_EQ(fmt::format("{:.4f}", 7.2809479766055470e-15), "0.0000"); } TEST(format_test, prettify_float) { - EXPECT_EQ("0.0001", fmt::format("{}", 1e-4)); - EXPECT_EQ("1e-05", fmt::format("{}", 1e-5)); - EXPECT_EQ("1000000000000000", fmt::format("{}", 1e15)); - EXPECT_EQ("1e+16", fmt::format("{}", 1e16)); - EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5)); - EXPECT_EQ("10000000000", fmt::format("{}", 1e10)); - EXPECT_EQ("100000000000", fmt::format("{}", 1e11)); - EXPECT_EQ("12340000000", fmt::format("{}", 1234e7)); - EXPECT_EQ("12.34", fmt::format("{}", 1234e-2)); - EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6)); - EXPECT_EQ("0.1", fmt::format("{}", 0.1f)); - EXPECT_EQ("1.3563156e-19", fmt::format("{}", 1.35631564e-19f)); + EXPECT_EQ(fmt::format("{}", 1e-4), "0.0001"); + EXPECT_EQ(fmt::format("{}", 1e-5), "1e-05"); + EXPECT_EQ(fmt::format("{}", 1e15), "1000000000000000"); + EXPECT_EQ(fmt::format("{}", 1e16), "1e+16"); + EXPECT_EQ(fmt::format("{}", 9.999e-5), "9.999e-05"); + EXPECT_EQ(fmt::format("{}", 1e10), "10000000000"); + EXPECT_EQ(fmt::format("{}", 1e11), "100000000000"); + EXPECT_EQ(fmt::format("{}", 1234e7), "12340000000"); + EXPECT_EQ(fmt::format("{}", 1234e-2), "12.34"); + EXPECT_EQ(fmt::format("{}", 1234e-6), "0.001234"); + EXPECT_EQ(fmt::format("{}", 0.1f), "0.1"); + EXPECT_EQ(fmt::format("{}", 1.35631564e-19f), "1.3563156e-19"); } TEST(format_test, format_nan) { double nan = std::numeric_limits<double>::quiet_NaN(); - EXPECT_EQ("nan", fmt::format("{}", nan)); - EXPECT_EQ("+nan", fmt::format("{:+}", nan)); - EXPECT_EQ(" +nan", fmt::format("{:+06}", nan)); - EXPECT_EQ("+nan ", fmt::format("{:<+06}", nan)); - EXPECT_EQ(" +nan ", fmt::format("{:^+06}", nan)); - EXPECT_EQ(" +nan", fmt::format("{:>+06}", nan)); + EXPECT_EQ(fmt::format("{}", nan), "nan"); + EXPECT_EQ(fmt::format("{:+}", nan), "+nan"); + EXPECT_EQ(fmt::format("{:+06}", nan), " +nan"); + EXPECT_EQ(fmt::format("{:<+06}", nan), "+nan "); + EXPECT_EQ(fmt::format("{:^+06}", nan), " +nan "); + EXPECT_EQ(fmt::format("{:>+06}", nan), " +nan"); if (std::signbit(-nan)) { - EXPECT_EQ("-nan", fmt::format("{}", -nan)); - EXPECT_EQ(" -nan", fmt::format("{:+06}", -nan)); + EXPECT_EQ(fmt::format("{}", -nan), "-nan"); + EXPECT_EQ(fmt::format("{:+06}", -nan), " -nan"); } else { fmt::print("Warning: compiler doesn't handle negative NaN correctly"); } - EXPECT_EQ(" nan", fmt::format("{: }", nan)); - EXPECT_EQ("NAN", fmt::format("{:F}", nan)); - EXPECT_EQ("nan ", fmt::format("{:<7}", nan)); - EXPECT_EQ(" nan ", fmt::format("{:^7}", nan)); - EXPECT_EQ(" nan", fmt::format("{:>7}", nan)); + EXPECT_EQ(fmt::format("{: }", nan), " nan"); + EXPECT_EQ(fmt::format("{:F}", nan), "NAN"); + EXPECT_EQ(fmt::format("{:<7}", nan), "nan "); + EXPECT_EQ(fmt::format("{:^7}", nan), " nan "); + EXPECT_EQ(fmt::format("{:>7}", nan), " nan"); } TEST(format_test, format_infinity) { double inf = std::numeric_limits<double>::infinity(); - EXPECT_EQ("inf", fmt::format("{}", inf)); - EXPECT_EQ("+inf", fmt::format("{:+}", inf)); - EXPECT_EQ("-inf", fmt::format("{}", -inf)); - EXPECT_EQ(" +inf", fmt::format("{:+06}", inf)); - EXPECT_EQ(" -inf", fmt::format("{:+06}", -inf)); - EXPECT_EQ("+inf ", fmt::format("{:<+06}", inf)); - EXPECT_EQ(" +inf ", fmt::format("{:^+06}", inf)); - EXPECT_EQ(" +inf", fmt::format("{:>+06}", inf)); - EXPECT_EQ(" inf", fmt::format("{: }", inf)); - EXPECT_EQ("INF", fmt::format("{:F}", inf)); - EXPECT_EQ("inf ", fmt::format("{:<7}", inf)); - EXPECT_EQ(" inf ", fmt::format("{:^7}", inf)); - EXPECT_EQ(" inf", fmt::format("{:>7}", inf)); + EXPECT_EQ(fmt::format("{}", inf), "inf"); + EXPECT_EQ(fmt::format("{:+}", inf), "+inf"); + EXPECT_EQ(fmt::format("{}", -inf), "-inf"); + EXPECT_EQ(fmt::format("{:+06}", inf), " +inf"); + EXPECT_EQ(fmt::format("{:+06}", -inf), " -inf"); + EXPECT_EQ(fmt::format("{:<+06}", inf), "+inf "); + EXPECT_EQ(fmt::format("{:^+06}", inf), " +inf "); + EXPECT_EQ(fmt::format("{:>+06}", inf), " +inf"); + EXPECT_EQ(fmt::format("{: }", inf), " inf"); + EXPECT_EQ(fmt::format("{:F}", inf), "INF"); + EXPECT_EQ(fmt::format("{:<7}", inf), "inf "); + EXPECT_EQ(fmt::format("{:^7}", inf), " inf "); + EXPECT_EQ(fmt::format("{:>7}", inf), " inf"); } TEST(format_test, format_long_double) { - EXPECT_EQ("0", fmt::format("{0:}", 0.0l)); - EXPECT_EQ("0.000000", fmt::format("{0:f}", 0.0l)); - EXPECT_EQ("0.0", fmt::format("{:.1f}", 0.000000001l)); - EXPECT_EQ("0.10", fmt::format("{:.2f}", 0.099l)); - EXPECT_EQ("392.65", fmt::format("{0:}", 392.65l)); - EXPECT_EQ("392.65", fmt::format("{0:g}", 392.65l)); - EXPECT_EQ("392.65", fmt::format("{0:G}", 392.65l)); - EXPECT_EQ("392.650000", fmt::format("{0:f}", 392.65l)); - EXPECT_EQ("392.650000", fmt::format("{0:F}", 392.65l)); + EXPECT_EQ(fmt::format("{0:}", 0.0l), "0"); + EXPECT_EQ(fmt::format("{0:f}", 0.0l), "0.000000"); + EXPECT_EQ(fmt::format("{:.1f}", 0.000000001l), "0.0"); + EXPECT_EQ(fmt::format("{:.2f}", 0.099l), "0.10"); + EXPECT_EQ(fmt::format("{0:}", 392.65l), "392.65"); + EXPECT_EQ(fmt::format("{0:g}", 392.65l), "392.65"); + EXPECT_EQ(fmt::format("{0:G}", 392.65l), "392.65"); + EXPECT_EQ(fmt::format("{0:f}", 392.65l), "392.650000"); + EXPECT_EQ(fmt::format("{0:F}", 392.65l), "392.650000"); char buffer[buffer_size]; safe_sprintf(buffer, "%Le", 392.65l); EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65l)); - EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.64l)); + EXPECT_EQ(fmt::format("{0:+010.4g}", 392.64l), "+0000392.6"); auto ld = 3.31l; if (fmt::detail::is_double_double<decltype(ld)>::value) { @@ -1499,8 +1486,8 @@ TEST(format_test, format_long_double) { TEST(format_test, format_char) { const char types[] = "cbBdoxX"; check_unknown_types('a', types, "char"); - EXPECT_EQ("a", fmt::format("{0}", 'a')); - EXPECT_EQ("z", fmt::format("{0:c}", 'z')); + EXPECT_EQ(fmt::format("{0}", 'a'), "a"); + EXPECT_EQ(fmt::format("{0:c}", 'z'), "z"); int n = 'x'; for (const char* type = types + 1; *type; ++type) { std::string format_str = fmt::format("{{:{}}}", *type); @@ -1510,39 +1497,41 @@ TEST(format_test, format_char) { } EXPECT_EQ(fmt::format("{:02X}", n), fmt::format("{:02X}", 'x')); - EXPECT_EQ("\n", fmt::format("{}", '\n')); - EXPECT_EQ("'\\n'", fmt::format("{:?}", '\n')); - EXPECT_EQ("ff", fmt::format("{:x}", '\xff')); + EXPECT_EQ(fmt::format("{}", '\n'), "\n"); + EXPECT_EQ(fmt::format("{:?}", '\n'), "'\\n'"); + EXPECT_EQ(fmt::format("{:x}", '\xff'), "ff"); } TEST(format_test, format_volatile_char) { volatile char c = 'x'; - EXPECT_EQ("x", fmt::format("{}", c)); + EXPECT_EQ(fmt::format("{}", c), "x"); } TEST(format_test, format_unsigned_char) { - EXPECT_EQ("42", fmt::format("{}", static_cast<unsigned char>(42))); - EXPECT_EQ("42", fmt::format("{}", static_cast<uint8_t>(42))); + EXPECT_EQ(fmt::format("{}", static_cast<unsigned char>(42)), "42"); + EXPECT_EQ(fmt::format("{}", static_cast<uint8_t>(42)), "42"); } TEST(format_test, format_cstring) { check_unknown_types("test", "sp", "string"); - EXPECT_EQ("test", fmt::format("{0}", "test")); - EXPECT_EQ("test", fmt::format("{0:s}", "test")); + EXPECT_EQ(fmt::format("{0}", "test"), "test"); + EXPECT_EQ(fmt::format("{0:s}", "test"), "test"); char nonconst[] = "nonconst"; - EXPECT_EQ("nonconst", fmt::format("{0}", nonconst)); - EXPECT_THROW_MSG( - (void)fmt::format(runtime("{0}"), static_cast<const char*>(nullptr)), - format_error, "string pointer is null"); + EXPECT_EQ(fmt::format("{0}", nonconst), "nonconst"); + auto nullstr = static_cast<const char*>(nullptr); + EXPECT_THROW_MSG((void)fmt::format("{}", nullstr), format_error, + "string pointer is null"); + EXPECT_THROW_MSG((void)fmt::format("{:s}", nullstr), format_error, + "string pointer is null"); } void function_pointer_test(int, double, std::string) {} TEST(format_test, format_pointer) { check_unknown_types(reinterpret_cast<void*>(0x1234), "p", "pointer"); - EXPECT_EQ("0x0", fmt::format("{0}", static_cast<void*>(nullptr))); - EXPECT_EQ("0x1234", fmt::format("{0}", reinterpret_cast<void*>(0x1234))); - EXPECT_EQ("0x1234", fmt::format("{0:p}", reinterpret_cast<void*>(0x1234))); + EXPECT_EQ(fmt::format("{0}", static_cast<void*>(nullptr)), "0x0"); + EXPECT_EQ(fmt::format("{0}", reinterpret_cast<void*>(0x1234)), "0x1234"); + EXPECT_EQ(fmt::format("{0:p}", reinterpret_cast<void*>(0x1234)), "0x1234"); // On CHERI (or other fat-pointer) systems, the size of a pointer is greater // than the size an integer that can hold a virtual address. There is no // portable address-as-an-integer type (yet) in C++, so we use `size_t` as @@ -1566,7 +1555,7 @@ TEST(format_test, format_pointer) { EXPECT_EQ(fmt::format("{}", fmt::detail::bit_cast<const void*>( &function_pointer_test)), fmt::format("{}", fmt::ptr(function_pointer_test))); - EXPECT_EQ("0x0", fmt::format("{}", nullptr)); + EXPECT_EQ(fmt::format("{}", nullptr), "0x0"); } TEST(format_test, write_uintptr_fallback) { @@ -1604,9 +1593,9 @@ TEST(format_test, format_string) { } TEST(format_test, format_string_view) { - EXPECT_EQ("test", fmt::format("{}", string_view("test"))); - EXPECT_EQ("\"t\\nst\"", fmt::format("{:?}", string_view("t\nst"))); - EXPECT_EQ("", fmt::format("{}", string_view())); + EXPECT_EQ(fmt::format("{}", string_view("test")), "test"); + EXPECT_EQ(fmt::format("{:?}", string_view("t\nst")), "\"t\\nst\""); + EXPECT_EQ(fmt::format("{}", string_view()), ""); } #ifdef FMT_USE_STRING_VIEW @@ -1621,8 +1610,8 @@ template <> struct formatter<string_viewable> : formatter<std::string_view> { FMT_END_NAMESPACE TEST(format_test, format_std_string_view) { - EXPECT_EQ("test", fmt::format("{}", std::string_view("test"))); - EXPECT_EQ("foo", fmt::format("{}", string_viewable())); + EXPECT_EQ(fmt::format("{}", std::string_view("test")), "test"); + EXPECT_EQ(fmt::format("{}", string_viewable()), "foo"); } struct explicitly_convertible_to_std_string_view { @@ -1673,8 +1662,8 @@ FMT_END_NAMESPACE TEST(format_test, format_custom) { EXPECT_THROW_MSG((void)fmt::format(runtime("{:s}"), date(2012, 12, 9)), format_error, "unknown format specifier"); - EXPECT_EQ("42", fmt::format("{0}", Answer())); - EXPECT_EQ("0042", fmt::format("{:04}", Answer())); + EXPECT_EQ(fmt::format("{0}", Answer()), "42"); + EXPECT_EQ(fmt::format("{:04}", Answer()), "0042"); } TEST(format_test, format_to_custom) { @@ -1694,7 +1683,7 @@ TEST(format_test, format_examples) { std::string message = fmt::format("The answer is {}", 42); EXPECT_EQ("The answer is 42", message); - EXPECT_EQ("42", fmt::format("{}", 42)); + EXPECT_EQ(fmt::format("{}", 42), "42"); memory_buffer out; fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); @@ -1716,17 +1705,17 @@ TEST(format_test, format_examples) { EXPECT_EQ("First, thou shalt count to three", fmt::format("First, thou shalt count to {0}", "three")); - EXPECT_EQ("Bring me a shrubbery", fmt::format("Bring me a {}", "shrubbery")); - EXPECT_EQ("From 1 to 3", fmt::format("From {} to {}", 1, 3)); + EXPECT_EQ(fmt::format("Bring me a {}", "shrubbery"), "Bring me a shrubbery"); + EXPECT_EQ(fmt::format("From {} to {}", 1, 3), "From 1 to 3"); char buffer[buffer_size]; safe_sprintf(buffer, "%03.2f", -1.2); EXPECT_EQ(buffer, fmt::format("{:03.2f}", -1.2)); - EXPECT_EQ("a, b, c", fmt::format("{0}, {1}, {2}", 'a', 'b', 'c')); - EXPECT_EQ("a, b, c", fmt::format("{}, {}, {}", 'a', 'b', 'c')); - EXPECT_EQ("c, b, a", fmt::format("{2}, {1}, {0}", 'a', 'b', 'c')); - EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad")); + EXPECT_EQ(fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'), "a, b, c"); + EXPECT_EQ(fmt::format("{}, {}, {}", 'a', 'b', 'c'), "a, b, c"); + EXPECT_EQ(fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'), "c, b, a"); + EXPECT_EQ(fmt::format("{0}{1}{0}", "abra", "cad"), "abracadabra"); EXPECT_EQ("left aligned ", fmt::format("{:<30}", "left aligned")); @@ -1737,16 +1726,16 @@ TEST(format_test, format_examples) { EXPECT_EQ("***********centered***********", fmt::format("{:*^30}", "centered")); - EXPECT_EQ("+3.140000; -3.140000", fmt::format("{:+f}; {:+f}", 3.14, -3.14)); - EXPECT_EQ(" 3.140000; -3.140000", fmt::format("{: f}; {: f}", 3.14, -3.14)); - EXPECT_EQ("3.140000; -3.140000", fmt::format("{:-f}; {:-f}", 3.14, -3.14)); + EXPECT_EQ(fmt::format("{:+f}; {:+f}", 3.14, -3.14), "+3.140000; -3.140000"); + EXPECT_EQ(fmt::format("{: f}; {: f}", 3.14, -3.14), " 3.140000; -3.140000"); + EXPECT_EQ(fmt::format("{:-f}; {:-f}", 3.14, -3.14), "3.140000; -3.140000"); EXPECT_EQ("int: 42; hex: 2a; oct: 52", fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}", 42)); EXPECT_EQ("int: 42; hex: 0x2a; oct: 052", fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}", 42)); - EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42)); + EXPECT_EQ(fmt::format("The answer is {}", 42), "The answer is 42"); EXPECT_THROW_MSG( (void)fmt::format(runtime("The answer is {:d}"), "forty-two"), format_error, "invalid format specifier"); @@ -1765,7 +1754,7 @@ TEST(format_test, print) { } TEST(format_test, variadic) { - EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1)); + EXPECT_EQ(fmt::format("{}c{}", "ab", 1), "abc1"); } TEST(format_test, bytes) { @@ -1810,23 +1799,23 @@ TEST(format_test, join) { v2.push_back(3.4f); void* v3[2] = {&v1[0], &v1[1]}; - EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, v1 + 3, ", "))); - EXPECT_EQ("(1)", fmt::format("({})", join(v1, v1 + 1, ", "))); - EXPECT_EQ("()", fmt::format("({})", join(v1, v1, ", "))); - EXPECT_EQ("(001, 002, 003)", fmt::format("({:03})", join(v1, v1 + 3, ", "))); + EXPECT_EQ(fmt::format("({})", join(v1, v1 + 3, ", ")), "(1, 2, 3)"); + EXPECT_EQ(fmt::format("({})", join(v1, v1 + 1, ", ")), "(1)"); + EXPECT_EQ(fmt::format("({})", join(v1, v1, ", ")), "()"); + EXPECT_EQ(fmt::format("({:03})", join(v1, v1 + 3, ", ")), "(001, 002, 003)"); EXPECT_EQ("(+01.20, +03.40)", fmt::format("({:+06.2f})", join(v2.begin(), v2.end(), ", "))); - EXPECT_EQ("1, 2, 3", fmt::format("{0:{1}}", join(v1, v1 + 3, ", "), 1)); + EXPECT_EQ(fmt::format("{0:{1}}", join(v1, v1 + 3, ", "), 1), "1, 2, 3"); EXPECT_EQ(fmt::format("{}, {}", v3[0], v3[1]), fmt::format("{}", join(v3, v3 + 2, ", "))); - EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, ", "))); - EXPECT_EQ("(+01.20, +03.40)", fmt::format("({:+06.2f})", join(v2, ", "))); + EXPECT_EQ(fmt::format("({})", join(v1, ", ")), "(1, 2, 3)"); + EXPECT_EQ(fmt::format("({:+06.2f})", join(v2, ", ")), "(+01.20, +03.40)"); auto v4 = std::vector<test_enum>{foo, bar, foo}; - EXPECT_EQ("0 1 0", fmt::format("{}", join(v4, " "))); + EXPECT_EQ(fmt::format("{}", join(v4, " ")), "0 1 0"); } #ifdef __cpp_lib_byte @@ -1873,45 +1862,45 @@ static constexpr const char static_with_null[3] = {'{', '}', '\0'}; static constexpr const char static_no_null[2] = {'{', '}'}; TEST(format_test, compile_time_string) { - EXPECT_EQ("foo", fmt::format(FMT_STRING("foo"))); - EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); + EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo"); + EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42"); #if FMT_USE_NONTYPE_TEMPLATE_ARGS using namespace fmt::literals; EXPECT_EQ("foobar", fmt::format(FMT_STRING("{foo}{bar}"), "bar"_a = "bar", "foo"_a = "foo")); - EXPECT_EQ("", fmt::format(FMT_STRING(""))); - EXPECT_EQ("", fmt::format(FMT_STRING(""), "arg"_a = 42)); - EXPECT_EQ("42", fmt::format(FMT_STRING("{answer}"), "answer"_a = Answer())); - EXPECT_EQ("1 2", fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2)); + EXPECT_EQ(fmt::format(FMT_STRING("")), ""); + EXPECT_EQ(fmt::format(FMT_STRING(""), "arg"_a = 42), ""); + EXPECT_EQ(fmt::format(FMT_STRING("{answer}"), "answer"_a = Answer()), "42"); + EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2"); #endif (void)static_with_null; (void)static_no_null; #ifndef _MSC_VER - EXPECT_EQ("42", fmt::format(FMT_STRING(static_with_null), 42)); - EXPECT_EQ("42", fmt::format(FMT_STRING(static_no_null), 42)); + EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42"); + EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42"); #endif (void)with_null; (void)no_null; #if FMT_CPLUSPLUS >= 201703L - EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42)); - EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 42)); + EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42"); + EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42"); #endif #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L - EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42)); + EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42"); #endif } TEST(format_test, custom_format_compile_time_string) { - EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), Answer())); + EXPECT_EQ(fmt::format(FMT_STRING("{}"), Answer()), "42"); auto answer = Answer(); - EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), answer)); + EXPECT_EQ(fmt::format(FMT_STRING("{}"), answer), "42"); char buf[10] = {}; fmt::format_to(buf, FMT_STRING("{}"), answer); const Answer const_answer = Answer(); - EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), const_answer)); + EXPECT_EQ(fmt::format(FMT_STRING("{}"), const_answer), "42"); } #if FMT_USE_USER_DEFINED_LITERALS @@ -1924,11 +1913,11 @@ TEST(format_test, named_arg_udl) { fmt::arg("second", "cad"), fmt::arg("third", 99)), udl_a); - EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = Answer())); + EXPECT_EQ(fmt::format("{answer}", "answer"_a = Answer()), "42"); } #endif // FMT_USE_USER_DEFINED_LITERALS -TEST(format_test, enum) { EXPECT_EQ("0", fmt::format("{}", foo)); } +TEST(format_test, enum) { EXPECT_EQ(fmt::format("{}", foo), "0"); } TEST(format_test, formatter_not_specialized) { static_assert(!fmt::has_formatter<fmt::formatter<test_enum>, @@ -1941,12 +1930,12 @@ enum big_enum : unsigned long long { big_enum_value = 5000000000ULL }; auto format_as(big_enum e) -> unsigned long long { return e; } TEST(format_test, strong_enum) { - EXPECT_EQ("5000000000", fmt::format("{}", big_enum_value)); + EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000"); } #endif TEST(format_test, non_null_terminated_format_string) { - EXPECT_EQ("42", fmt::format(string_view("{}foo", 2), 42)); + EXPECT_EQ(fmt::format(string_view("{}foo", 2), 42), "42"); } namespace adl_test { @@ -2079,16 +2068,16 @@ struct test_output_iterator { using pointer = void; using reference = void; - test_output_iterator& operator++() { + auto operator++() -> test_output_iterator& { ++data; return *this; } - test_output_iterator operator++(int) { + auto operator++(int) -> test_output_iterator { auto tmp = *this; ++data; return tmp; } - char& operator*() { return *data; } + auto operator*() -> char& { return *data; } }; TEST(format_test, format_to_n_output_iterator) { @@ -2103,13 +2092,13 @@ TEST(format_test, vformat_to) { auto args = fmt::make_format_args<context>(n); auto s = std::string(); fmt::vformat_to(std::back_inserter(s), "{}", args); - EXPECT_EQ("42", s); + EXPECT_EQ(s, "42"); s.clear(); fmt::vformat_to(std::back_inserter(s), FMT_STRING("{}"), args); - EXPECT_EQ("42", s); + EXPECT_EQ(s, "42"); } -TEST(format_test, char_traits_is_not_ambiguous) { +TEST(format_test, char_traits_not_ambiguous) { // Test that we don't inject detail names into the std namespace. using namespace std; auto c = char_traits<char>::char_type(); @@ -2156,6 +2145,13 @@ auto format_as(scoped_enum_as_string) -> std::string { return "foo"; } struct struct_as_int {}; auto format_as(struct_as_int) -> int { return 42; } + +struct struct_as_const_reference { + const std::string name = "foo"; +}; +auto format_as(const struct_as_const_reference& s) -> const std::string& { + return s.name; +} } // namespace test TEST(format_test, format_as) { @@ -2163,6 +2159,7 @@ TEST(format_test, format_as) { EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string_view()), "foo"); EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_string()), "foo"); EXPECT_EQ(fmt::format("{}", test::struct_as_int()), "42"); + EXPECT_EQ(fmt::format("{}", test::struct_as_const_reference()), "foo"); } TEST(format_test, format_as_to_string) { @@ -2172,7 +2169,7 @@ TEST(format_test, format_as_to_string) { EXPECT_EQ(fmt::to_string(test::struct_as_int()), "42"); } -template <typename Char, typename T> bool check_enabled_formatter() { +template <typename Char, typename T> auto check_enabled_formatter() -> bool { static_assert(std::is_default_constructible<fmt::formatter<T, Char>>::value, ""); return true; @@ -2198,21 +2195,20 @@ TEST(format_test, test_formatters_enabled) { TEST(format_int_test, data) { fmt::format_int format_int(42); - EXPECT_EQ("42", std::string(format_int.data(), format_int.size())); + EXPECT_EQ(std::string(format_int.data(), format_int.size()), "42"); } TEST(format_int_test, format_int) { - EXPECT_EQ("42", fmt::format_int(42).str()); - EXPECT_EQ(2u, fmt::format_int(42).size()); - EXPECT_EQ("-42", fmt::format_int(-42).str()); - EXPECT_EQ(3u, fmt::format_int(-42).size()); - EXPECT_EQ("42", fmt::format_int(42ul).str()); - EXPECT_EQ("-42", fmt::format_int(-42l).str()); - EXPECT_EQ("42", fmt::format_int(42ull).str()); - EXPECT_EQ("-42", fmt::format_int(-42ll).str()); - std::ostringstream os; - os << max_value<int64_t>(); - EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str()); + EXPECT_EQ(fmt::format_int(42).str(), "42"); + EXPECT_EQ(fmt::format_int(42).size(), 2u); + EXPECT_EQ(fmt::format_int(-42).str(), "-42"); + EXPECT_EQ(fmt::format_int(-42).size(), 3u); + EXPECT_EQ(fmt::format_int(42ul).str(), "42"); + EXPECT_EQ(fmt::format_int(-42l).str(), "-42"); + EXPECT_EQ(fmt::format_int(42ull).str(), "42"); + EXPECT_EQ(fmt::format_int(-42ll).str(), "-42");\ + EXPECT_EQ(fmt::format_int(max_value<int64_t>()).str(), + std::to_string(max_value<int64_t>())); } #ifndef FMT_STATIC_THOUSANDS_SEPARATOR @@ -2272,6 +2268,14 @@ TEST(format_test, format_named_arg_with_locale) { "42"); } +TEST(format_test, format_locale) { + auto loc = std::locale({}, new fmt::format_facet<std::locale>(",")); + EXPECT_EQ(fmt::format(loc, "{:Lx}", 123456789), "7,5bc,d15"); + EXPECT_EQ(fmt::format(loc, "{:#Lb}", -123456789), + "-0b111,010,110,111,100,110,100,010,101"); + EXPECT_EQ(fmt::format(loc, "{:10Lo}", 12345), " 30,071"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR struct convertible_to_nonconst_cstring { diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 42340a2d..34054b68 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -354,7 +354,7 @@ TEST(output_redirect_test, dup_error_in_ctor) { FMT_POSIX(close(fd)); std::unique_ptr<output_redirect> redir{nullptr}; EXPECT_SYSTEM_ERROR_NOASSERT( - redir.reset(new output_redirect(f.get())), EBADF, + redir.reset(new output_redirect(f.get(), false)), EBADF, fmt::format("cannot duplicate file descriptor {}", fd)); copy.dup2(fd); // "undo" close or dtor will fail } diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index 542e4b5e..3d27cf96 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -11,8 +11,8 @@ using fmt::file; -output_redirect::output_redirect(FILE* f) : file_(f) { - flush(); +output_redirect::output_redirect(FILE* f, bool flush) : file_(f) { + if (flush) this->flush(); int fd = FMT_POSIX(fileno(f)); // Create a file object referring to the original file. original_ = file::dup(fd); diff --git a/test/gtest-extra.h b/test/gtest-extra.h index 03a07a2a..e08c94c0 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -77,7 +77,7 @@ class output_redirect { void restore(); public: - explicit output_redirect(FILE* file); + explicit output_redirect(FILE* file, bool flush = true); ~output_redirect() noexcept; output_redirect(const output_redirect&) = delete; diff --git a/test/gtest/gmock-gtest-all.cc b/test/gtest/gmock-gtest-all.cc index 7b33134f..9d3b9bc1 100644 --- a/test/gtest/gmock-gtest-all.cc +++ b/test/gtest/gmock-gtest-all.cc @@ -1912,7 +1912,7 @@ void AssertHelper::operator=(const Message& message) const { namespace { // When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P -// to creates test cases for it, a syntetic test case is +// to creates test cases for it, a synthetic test case is // inserted to report ether an error or a log message. // // This configuration bit will likely be removed at some point. diff --git a/test/ostream-test.cc b/test/ostream-test.cc index b2d15466..98ee0757 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -17,7 +17,7 @@ struct test {}; // included after fmt/format.h. namespace fmt { template <> struct formatter<test> : formatter<int> { - auto format(const test&, format_context& ctx) -> decltype(ctx.out()) { + auto format(const test&, format_context& ctx) const -> decltype(ctx.out()) { return formatter<int>::format(42, ctx); } }; @@ -289,3 +289,20 @@ TEST(ostream_test, closed_ofstream) { std::ofstream ofs; fmt::print(ofs, "discard"); } + +struct unlocalized {}; + +auto operator<<(std::ostream& os, unlocalized) + -> std::ostream& { + return os << 12345; +} + +namespace fmt { +template <> struct formatter<unlocalized> : ostream_formatter {}; +} // namespace fmt + +TEST(ostream_test, unlocalized) { + auto loc = get_locale("en_US.UTF-8"); + std::locale::global(loc); + EXPECT_EQ(fmt::format(loc, "{}", unlocalized()), "12345"); +} diff --git a/test/posix-mock.h b/test/posix-mock.h index 4f2a42c1..54580871 100644 --- a/test/posix-mock.h +++ b/test/posix-mock.h @@ -30,13 +30,13 @@ namespace test { #ifndef _MSC_VER // Size type for read and write. -typedef size_t size_t; -typedef ssize_t ssize_t; +using size_t = size_t; +using ssize_t = ssize_t; int open(const char* path, int oflag, int mode); int fstat(int fd, struct stat* buf); #else -typedef unsigned size_t; -typedef int ssize_t; +using size_t = unsigned; +using ssize_t = int; #endif #ifndef _WIN32 diff --git a/test/printf-test.cc b/test/printf-test.cc index 81db9b23..7e09ecca 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -310,10 +310,10 @@ TEST(printf_test, dynamic_precision) { } } -template <typename T> struct make_signed { typedef T type; }; +template <typename T> struct make_signed { using type = T; }; #define SPECIALIZE_MAKE_SIGNED(T, S) \ - template <> struct make_signed<T> { typedef S type; } + template <> struct make_signed<T> { using type = S; } SPECIALIZE_MAKE_SIGNED(char, signed char); SPECIALIZE_MAKE_SIGNED(unsigned char, signed char); diff --git a/test/ranges-test.cc b/test/ranges-test.cc index ba5c464d..8ab66b33 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -7,13 +7,19 @@ #include "fmt/ranges.h" +#include <list> #include <map> +#include <numeric> #include <queue> #include <stack> #include <string> #include <utility> #include <vector> +#if FMT_HAS_INCLUDE(<ranges>) +# include <ranges> +#endif + #include "gtest/gtest.h" #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601 @@ -242,7 +248,7 @@ template <typename T> class non_const_only_range { explicit non_const_only_range(Args&&... args) : vec(std::forward<Args>(args)...) {} - auto begin() -> const_iterator{ return vec.begin(); } + auto begin() -> const_iterator { return vec.begin(); } auto end() -> const_iterator { return vec.end(); } }; @@ -360,7 +366,7 @@ struct cpp20_only_range { iterator() = default; iterator(int i) : val(i) {} auto operator*() const -> int { return val; } - auto operator++() -> iterator&{ + auto operator++() -> iterator& { ++val; return *this; } @@ -416,6 +422,17 @@ TEST(ranges_test, join_range) { } #endif // FMT_RANGES_TEST_ENABLE_JOIN +#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202302L +TEST(ranges_test, nested_ranges) { + auto l = std::list{1, 2, 3}; + auto r = std::views::iota(0, 3) | std::views::transform([&l](auto i) { + return std::views::take(std::ranges::subrange(l), i); + }) | + std::views::transform(std::views::reverse); + EXPECT_EQ(fmt::format("{}", r), "[[], [1], [2, 1]]"); +} +#endif + TEST(ranges_test, is_printable) { using fmt::detail::is_printable; EXPECT_TRUE(is_printable(0x0323)); @@ -528,3 +545,16 @@ TEST(ranges_test, container_adaptor) { EXPECT_EQ(fmt::format("{}", m), "[1, 2]"); } } + +struct tieable { + int a = 3; + double b = 0.42; +}; + +auto format_as(const tieable& t) -> std::tuple<int, double> { + return std::tie(t.a, t.b); +} + +TEST(ranges_test, format_as_tie) { + EXPECT_EQ(fmt::format("{}", tieable()), "(3, 0.42)"); +} diff --git a/test/scan-test.cc b/test/scan-test.cc index bec54134..f0ede192 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -10,23 +10,29 @@ #include <time.h> #include <climits> +#include <thread> +#include "fmt/os.h" #include "gmock/gmock.h" #include "gtest-extra.h" TEST(scan_test, read_text) { - auto s = fmt::string_view("foo"); + fmt::string_view s = "foo"; auto end = fmt::scan(s, "foo"); EXPECT_EQ(end, s.end()); EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input"); } TEST(scan_test, read_int) { - auto n = int(); + int n = 0; fmt::scan("42", "{}", n); EXPECT_EQ(n, 42); fmt::scan("-42", "{}", n); EXPECT_EQ(n, -42); + fmt::scan("42", "{:}", n); + EXPECT_EQ(n, 42); + EXPECT_THROW_MSG(fmt::scan(std::to_string(INT_MAX + 1u), "{}", n), + fmt::format_error, "number is too big"); } TEST(scan_test, read_longlong) { @@ -38,7 +44,7 @@ TEST(scan_test, read_longlong) { } TEST(scan_test, read_uint) { - auto n = unsigned(); + unsigned n = 0; fmt::scan("42", "{}", n); EXPECT_EQ(n, 42); EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error, @@ -53,52 +59,63 @@ TEST(scan_test, read_ulonglong) { "invalid input"); } +TEST(scan_test, read_hex) { + unsigned n = 0; + fmt::scan("2a", "{:x}", n); + EXPECT_EQ(n, 42); + auto num_digits = std::numeric_limits<unsigned>::digits / 4; + EXPECT_THROW_MSG(fmt::scan(fmt::format("1{:0{}}", 0, num_digits), "{:x}", n), + fmt::format_error, "number is too big"); +} + TEST(scan_test, read_string) { - auto s = std::string(); + std::string s; fmt::scan("foo", "{}", s); EXPECT_EQ(s, "foo"); } TEST(scan_test, read_string_view) { - auto s = fmt::string_view(); + fmt::string_view s; fmt::scan("foo", "{}", s); EXPECT_EQ(s, "foo"); } -#ifdef FMT_HAVE_STRPTIME +TEST(scan_test, separator) { + int n1 = 0, n2 = 0; + fmt::scan("10 20", "{} {}", n1, n2); + EXPECT_EQ(n1, 10); + EXPECT_EQ(n2, 20); +} + +struct num { + int value; +}; + namespace fmt { -template <> struct scanner<tm> { - std::string format; - - scan_parse_context::iterator parse(scan_parse_context& ctx) { - auto it = ctx.begin(); - if (it != ctx.end() && *it == ':') ++it; - auto end = it; - while (end != ctx.end() && *end != '}') ++end; - format.reserve(detail::to_unsigned(end - it + 1)); - format.append(it, end); - format.push_back('\0'); - return end; +template <> struct scanner<num> { + bool hex = false; + + auto parse(scan_parse_context& ctx) -> scan_parse_context::iterator { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'x') hex = true; + if (it != end && *it != '}') throw_format_error("invalid format"); + return it; } template <class ScanContext> - typename ScanContext::iterator scan(tm& t, ScanContext& ctx) { - auto result = strptime(ctx.begin(), format.c_str(), &t); - if (!result) throw format_error("failed to parse time"); - return result; + auto scan(num& n, ScanContext& ctx) const -> typename ScanContext::iterator { + // TODO: handle specifier + return fmt::scan(ctx, "{}", n.value); } }; } // namespace fmt TEST(scan_test, read_custom) { - auto input = "Date: 1985-10-25"; - auto t = tm(); - fmt::scan(input, "Date: {0:%Y-%m-%d}", t); - EXPECT_EQ(t.tm_year, 85); - EXPECT_EQ(t.tm_mon, 9); - EXPECT_EQ(t.tm_mday, 25); + auto input = "42"; + auto n = num(); + fmt::scan(input, "{:}", n); + EXPECT_EQ(n.value, 42); } -#endif TEST(scan_test, invalid_format) { EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error, @@ -108,9 +125,64 @@ TEST(scan_test, invalid_format) { } TEST(scan_test, example) { - auto key = std::string(); - auto value = int(); + std::string key; + int value = 0; fmt::scan("answer = 42", "{} = {}", key, value); EXPECT_EQ(key, "answer"); EXPECT_EQ(value, 42); } + +TEST(scan_test, end_of_input) { + int value = 0; + fmt::scan("", "{}", value); +} + +#if FMT_USE_FCNTL +TEST(scan_test, file) { + fmt::file read_end, write_end; + fmt::file::pipe(read_end, write_end); + + fmt::string_view input = "10 20"; + write_end.write(input.data(), input.size()); + write_end.close(); + + int n1 = 0, n2 = 0; + fmt::buffered_file f = read_end.fdopen("r"); + fmt::scan(f.get(), "{} {}", n1, n2); + EXPECT_EQ(n1, 10); + EXPECT_EQ(n2, 20); +} + +TEST(scan_test, lock) { + fmt::file read_end, write_end; + fmt::file::pipe(read_end, write_end); + + std::thread producer([&]() { + fmt::string_view input = "42 "; + for (int i = 0; i < 1000; ++i) write_end.write(input.data(), input.size()); + write_end.close(); + }); + + std::atomic<int> count(0); + fmt::buffered_file f = read_end.fdopen("r"); + auto fun = [&]() { + int value = 0; + while (fmt::scan(f.get(), "{}", value)) { + if (value != 42) { + read_end.close(); + EXPECT_EQ(value, 42); + break; + } + ++count; + } + }; + std::thread consumer1(fun); + std::thread consumer2(fun); + + producer.join(); + consumer1.join(); + consumer2.join(); + EXPECT_EQ(count, 1000); + +} +#endif // FMT_USE_FCNTL diff --git a/test/scan.h b/test/scan.h index a2cb2aa6..a68c77c3 100644 --- a/test/scan.h +++ b/test/scan.h @@ -12,43 +12,284 @@ #include "fmt/format.h" FMT_BEGIN_NAMESPACE -template <typename T, typename Char = char> struct scanner { - // A deleted default constructor indicates a disabled scanner. - scanner() = delete; +namespace detail { + +inline auto is_whitespace(char c) -> bool { return c == ' ' || c == '\n'; } + +// If c is a hex digit returns its numeric value, othewise -1. +inline auto to_hex_digit(char c) -> int { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +struct maybe_contiguous_range { + const char* begin; + const char* end; + + explicit operator bool() const { return begin != nullptr; } }; -class scan_parse_context { +class scan_buffer { private: - string_view format_; + const char* ptr_; + const char* end_; + bool contiguous_; + + protected: + scan_buffer(const char* ptr, const char* end, bool contiguous) + : ptr_(ptr), end_(end), contiguous_(contiguous) {} + ~scan_buffer() = default; + + void set(string_view buf) { + ptr_ = buf.begin(); + end_ = buf.end(); + } + + auto ptr() const -> const char* { return ptr_; } public: - using iterator = string_view::iterator; + scan_buffer(const scan_buffer&) = delete; + void operator=(const scan_buffer&) = delete; - explicit FMT_CONSTEXPR scan_parse_context(string_view format) - : format_(format) {} + // Fills the buffer with more input if available. + virtual void consume() = 0; - FMT_CONSTEXPR iterator begin() const { return format_.begin(); } - FMT_CONSTEXPR iterator end() const { return format_.end(); } + class sentinel {}; - void advance_to(iterator it) { - format_.remove_prefix(detail::to_unsigned(it - begin())); + class iterator { + private: + const char** ptr_; + scan_buffer* buf_; // This could be merged with ptr_. + char value_; + + static auto get_sentinel() -> const char** { + static const char* ptr = nullptr; + return &ptr; + } + + friend class scan_buffer; + + friend auto operator==(iterator lhs, sentinel) -> bool { + return *lhs.ptr_ == nullptr; + } + friend auto operator!=(iterator lhs, sentinel) -> bool { + return *lhs.ptr_ != nullptr; + } + + iterator(scan_buffer* buf) : buf_(buf) { + if (buf->ptr_ == buf->end_) { + ptr_ = get_sentinel(); + return; + } + ptr_ = &buf->ptr_; + value_ = *buf->ptr_; + } + + friend scan_buffer& get_buffer(iterator it) { return *it.buf_; } + + public: + iterator() : ptr_(get_sentinel()), buf_(nullptr) {} + + auto operator++() -> iterator& { + if (!buf_->try_consume()) ptr_ = get_sentinel(); + value_ = *buf_->ptr_; + return *this; + } + auto operator++(int) -> iterator { + iterator copy = *this; + ++*this; + return copy; + } + auto operator*() const -> char { return value_; } + + auto base() const -> const char* { return buf_->ptr_; } + + friend auto to_contiguous(iterator it) -> maybe_contiguous_range; + friend auto advance(iterator it, size_t n) -> iterator; + }; + + friend auto to_contiguous(iterator it) -> maybe_contiguous_range { + if (it.buf_->is_contiguous()) return {it.buf_->ptr_, it.buf_->end_}; + return {nullptr, nullptr}; + } + friend auto advance(iterator it, size_t n) -> iterator { + FMT_ASSERT(it.buf_->is_contiguous(), ""); + const char*& ptr = it.buf_->ptr_; + ptr += n; + it.value_ = *ptr; + if (ptr == it.buf_->end_) it.ptr_ = iterator::get_sentinel(); + return it; + } + + auto begin() -> iterator { return this; } + auto end() -> sentinel { return {}; } + + auto is_contiguous() const -> bool { return contiguous_; } + + // Tries consuming a single code unit. Returns true iff there is more input. + auto try_consume() -> bool { + FMT_ASSERT(ptr_ != end_, ""); + ++ptr_; + if (ptr_ != end_) return true; + consume(); + return ptr_ != end_; + } +}; + +using scan_iterator = scan_buffer::iterator; +using scan_sentinel = scan_buffer::sentinel; + +class string_scan_buffer : public scan_buffer { + private: + void consume() override {} + + public: + explicit string_scan_buffer(string_view s) + : scan_buffer(s.begin(), s.end(), true) {} +}; + +#ifdef _WIN32 +void flockfile(FILE* f) { _lock_file(f); } +void funlockfile(FILE* f) { _unlock_file(f); } +int getc_unlocked(FILE* f) { return _fgetc_nolock(f); } +#endif + +// A FILE wrapper. F is FILE defined as a template parameter to make +// system-specific API detection work. +template <typename F> class file_base { + protected: + F* file_; + + public: + file_base(F* file) : file_(file) {} + operator F*() const { return file_; } + + // Reads a code unit from the stream. + auto get() -> int { + int result = getc_unlocked(file_); + if (result == EOF && ferror(file_) != 0) + FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); + return result; + } + + // Puts the code unit back into the stream buffer. + void unget(char c) { + if (ungetc(c, file_) == EOF) + FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); + } +}; + +// A FILE wrapper for glibc. +template <typename F> class glibc_file : public file_base<F> { + public: + using file_base<F>::file_base; + + // Returns the file's read buffer as a string_view. + auto buffer() const -> string_view { + return {this->file_->_IO_read_ptr, + to_unsigned(this->file_->_IO_read_end - this->file_->_IO_read_ptr)}; + } +}; + +// A FILE wrapper for Apple's libc. +template <typename F> class apple_file : public file_base<F> { + public: + using file_base<F>::file_base; + + auto buffer() const -> string_view { + return {reinterpret_cast<char*>(this->file_->_p), + to_unsigned(this->file_->_r)}; + } +}; + +// A fallback FILE wrapper. +template <typename F> class fallback_file : public file_base<F> { + private: + char next_; // The next unconsumed character in the buffer. + bool has_next_ = false; + + public: + using file_base<F>::file_base; + + auto buffer() const -> string_view { return {&next_, has_next_ ? 1u : 0u}; } + + auto get() -> int { + has_next_ = false; + return file_base<F>::get(); + } + + void unget(char c) { + file_base<F>::unget(c); + next_ = c; + has_next_ = true; + } +}; + +class file_scan_buffer : public scan_buffer { + private: + template <typename F, FMT_ENABLE_IF(sizeof(F::_IO_read_ptr) != 0)> + static auto get_file(F* f, int) -> glibc_file<F> { + return f; + } + template <typename F, FMT_ENABLE_IF(sizeof(F::_p) != 0)> + static auto get_file(F* f, int) -> apple_file<F> { + return f; } + static auto get_file(FILE* f, ...) -> fallback_file<FILE> { return f; } + + decltype(get_file(static_cast<FILE*>(nullptr), 0)) file_; + + // Fills the buffer if it is empty. + void fill() { + string_view buf = file_.buffer(); + if (buf.size() == 0) { + int c = file_.get(); + // Put the character back since we are only filling the buffer. + if (c != EOF) file_.unget(static_cast<char>(c)); + buf = file_.buffer(); + } + set(buf); + } + + void consume() override { + // Consume the current buffer content. + size_t n = to_unsigned(ptr() - file_.buffer().begin()); + for (size_t i = 0; i != n; ++i) file_.get(); + fill(); + } + + public: + explicit file_scan_buffer(FILE* f) + : scan_buffer(nullptr, nullptr, false), file_(f) { + flockfile(f); + fill(); + } + ~file_scan_buffer() { funlockfile(file_); } }; +} // namespace detail -struct scan_context { +template <typename T, typename Char = char> struct scanner { + // A deleted default constructor indicates a disabled scanner. + scanner() = delete; +}; + +class scan_parse_context { private: - string_view input_; + string_view format_; public: - using iterator = const char*; + using iterator = string_view::iterator; - explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {} + explicit FMT_CONSTEXPR scan_parse_context(string_view format) + : format_(format) {} - iterator begin() const { return input_.data(); } - iterator end() const { return begin() + input_.size(); } + FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); } + FMT_CONSTEXPR auto end() const -> iterator { return format_.end(); } void advance_to(iterator it) { - input_.remove_prefix(detail::to_unsigned(it - begin())); + format_.remove_prefix(detail::to_unsigned(it - begin())); } }; @@ -64,181 +305,362 @@ enum class scan_type { custom_type }; -struct custom_scan_arg { +template <typename Context> struct custom_scan_arg { void* value; - void (*scan)(void* arg, scan_parse_context& parse_ctx, scan_context& ctx); + void (*scan)(void* arg, scan_parse_context& parse_ctx, Context& ctx); }; +} // namespace detail -class scan_arg { - public: - scan_type type; +// A scan argument. Context is a template parameter for the compiled API where +// output can be unbuffered. +template <typename Context> class basic_scan_arg { + private: + using scan_type = detail::scan_type; + scan_type type_; union { - int* int_value; - unsigned* uint_value; - long long* long_long_value; - unsigned long long* ulong_long_value; - std::string* string; - fmt::string_view* string_view; - custom_scan_arg custom; + int* int_value_; + unsigned* uint_value_; + long long* long_long_value_; + unsigned long long* ulong_long_value_; + std::string* string_; + string_view* string_view_; + detail::custom_scan_arg<Context> custom_; // TODO: more types }; - FMT_CONSTEXPR scan_arg() : type(scan_type::none_type), int_value(nullptr) {} - FMT_CONSTEXPR scan_arg(int& value) - : type(scan_type::int_type), int_value(&value) {} - FMT_CONSTEXPR scan_arg(unsigned& value) - : type(scan_type::uint_type), uint_value(&value) {} - FMT_CONSTEXPR scan_arg(long long& value) - : type(scan_type::long_long_type), long_long_value(&value) {} - FMT_CONSTEXPR scan_arg(unsigned long long& value) - : type(scan_type::ulong_long_type), ulong_long_value(&value) {} - FMT_CONSTEXPR scan_arg(std::string& value) - : type(scan_type::string_type), string(&value) {} - FMT_CONSTEXPR scan_arg(fmt::string_view& value) - : type(scan_type::string_view_type), string_view(&value) {} - template <typename T> - FMT_CONSTEXPR scan_arg(T& value) : type(scan_type::custom_type) { - custom.value = &value; - custom.scan = scan_custom_arg<T>; - } - - private: template <typename T> static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx, - scan_context& ctx) { - scanner<T> s; + Context& ctx) { + auto s = scanner<T>(); parse_ctx.advance_to(s.parse(parse_ctx)); ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx)); } + + public: + FMT_CONSTEXPR basic_scan_arg() + : type_(scan_type::none_type), int_value_(nullptr) {} + FMT_CONSTEXPR basic_scan_arg(int& value) + : type_(scan_type::int_type), int_value_(&value) {} + FMT_CONSTEXPR basic_scan_arg(unsigned& value) + : type_(scan_type::uint_type), uint_value_(&value) {} + FMT_CONSTEXPR basic_scan_arg(long long& value) + : type_(scan_type::long_long_type), long_long_value_(&value) {} + FMT_CONSTEXPR basic_scan_arg(unsigned long long& value) + : type_(scan_type::ulong_long_type), ulong_long_value_(&value) {} + FMT_CONSTEXPR basic_scan_arg(std::string& value) + : type_(scan_type::string_type), string_(&value) {} + FMT_CONSTEXPR basic_scan_arg(string_view& value) + : type_(scan_type::string_view_type), string_view_(&value) {} + template <typename T> + FMT_CONSTEXPR basic_scan_arg(T& value) : type_(scan_type::custom_type) { + custom_.value = &value; + custom_.scan = scan_custom_arg<T>; + } + + constexpr explicit operator bool() const noexcept { + return type_ != scan_type::none_type; + } + + auto type() const -> detail::scan_type { return type_; } + + template <typename Visitor> + auto visit(Visitor&& vis) -> decltype(vis(monostate())) { + switch (type_) { + case scan_type::none_type: + break; + case scan_type::int_type: + return vis(*int_value_); + case scan_type::uint_type: + return vis(*uint_value_); + case scan_type::long_long_type: + return vis(*long_long_value_); + case scan_type::ulong_long_type: + return vis(*ulong_long_value_); + case scan_type::string_type: + return vis(*string_); + case scan_type::string_view_type: + return vis(*string_view_); + case scan_type::custom_type: + break; + } + return vis(monostate()); + } + + auto scan_custom(const char* parse_begin, scan_parse_context& parse_ctx, + Context& ctx) const -> bool { + if (type_ != scan_type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + custom_.scan(custom_.value, parse_ctx, ctx); + return true; + } }; -} // namespace detail + +class scan_context; +using scan_arg = basic_scan_arg<scan_context>; struct scan_args { int size; - const detail::scan_arg* data; + const scan_arg* data; template <size_t N> - FMT_CONSTEXPR scan_args(const std::array<detail::scan_arg, N>& store) + FMT_CONSTEXPR scan_args(const std::array<scan_arg, N>& store) : size(N), data(store.data()) { static_assert(N < INT_MAX, "too many arguments"); } }; +class scan_context { + private: + detail::scan_buffer& buf_; + scan_args args_; + + public: + using iterator = detail::scan_iterator; + using sentinel = detail::scan_sentinel; + + explicit FMT_CONSTEXPR scan_context(detail::scan_buffer& buf, scan_args args) + : buf_(buf), args_(args) {} + + FMT_CONSTEXPR auto arg(int id) const -> scan_arg { + return id < args_.size ? args_.data[id] : scan_arg(); + } + + auto begin() const -> iterator { return buf_.begin(); } + auto end() const -> sentinel { return {}; } + + void advance_to(iterator) { buf_.consume(); } +}; + namespace detail { +const char* parse_scan_specs(const char* begin, const char* end, + format_specs<>& specs, scan_type) { + while (begin != end) { + switch (to_ascii(*begin)) { + // TODO: parse more scan format specifiers + case 'x': + specs.type = presentation_type::hex_lower; + ++begin; + break; + case '}': + return begin; + } + } + return begin; +} + +template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)> +auto read(scan_iterator it, T& value) + -> scan_iterator { + if (it == scan_sentinel()) return it; + char c = *it; + if (c < '0' || c > '9') throw_format_error("invalid input"); + + int num_digits = 0; + T n = 0, prev = 0; + char prev_digit = c; + do { + prev = n; + n = n * 10 + static_cast<unsigned>(c - '0'); + prev_digit = c; + c = *++it; + ++num_digits; + if (c < '0' || c > '9') break; + } while (it != scan_sentinel()); + + // Check overflow. + if (num_digits <= std::numeric_limits<int>::digits10) { + value = n; + return it; + } + unsigned max = to_unsigned((std::numeric_limits<int>::max)()); + if (num_digits == std::numeric_limits<int>::digits10 + 1 && + prev * 10ull + unsigned(prev_digit - '0') <= max) { + value = n; + } else { + throw_format_error("number is too big"); + } + return it; +} + +template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)> +auto read_hex(scan_iterator it, T& value) + -> scan_iterator { + if (it == scan_sentinel()) return it; + int digit = to_hex_digit(*it); + if (digit < 0) throw_format_error("invalid input"); + + int num_digits = 0; + T n = 0; + do { + n = (n << 4) + static_cast<unsigned>(digit); + ++num_digits; + digit = to_hex_digit(*++it); + if (digit < 0) break; + } while (it != scan_sentinel()); + + // Check overflow. + if (num_digits <= (std::numeric_limits<T>::digits >> 2)) + value = n; + else + throw_format_error("number is too big"); + return it; +} + +template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)> +auto read(scan_iterator it, T& value, const format_specs<>& specs) + -> scan_iterator { + if (specs.type == presentation_type::hex_lower) + return read_hex(it, value); + return read(it, value); +} + +template <typename T, FMT_ENABLE_IF(std::is_signed<T>::value)> +auto read(scan_iterator it, T& value, const format_specs<>& = {}) + -> scan_iterator { + bool negative = it != scan_sentinel() && *it == '-'; + if (negative) { + ++it; + if (it == scan_sentinel()) throw_format_error("invalid input"); + } + using unsigned_type = typename std::make_unsigned<T>::type; + unsigned_type abs_value = 0; + it = read(it, abs_value); + auto n = static_cast<T>(abs_value); + value = negative ? -n : n; + return it; +} + +auto read(scan_iterator it, std::string& value, const format_specs<>& = {}) + -> scan_iterator { + while (it != scan_sentinel() && *it != ' ') value.push_back(*it++); + return it; +} + +auto read(scan_iterator it, string_view& value, const format_specs<>& = {}) + -> scan_iterator { + auto range = to_contiguous(it); + // This could also be checked at compile time in scan. + if (!range) throw_format_error("string_view requires contiguous input"); + auto p = range.begin; + while (p != range.end && *p != ' ') ++p; + size_t size = to_unsigned(p - range.begin); + value = {range.begin, size}; + return advance(it, size); +} + +auto read(scan_iterator it, monostate, const format_specs<>& = {}) + -> scan_iterator { + return it; +} + +// An argument scanner that uses the default format, e.g. decimal for integers. +struct default_arg_scanner { + scan_iterator it; + + template <typename T> FMT_INLINE auto operator()(T&& value) -> scan_iterator { + return read(it, value); + } +}; + +// An argument scanner with format specifiers. +struct arg_scanner { + scan_iterator it; + const format_specs<>& specs; + + template <typename T> auto operator()(T&& value) -> scan_iterator { + return read(it, value, specs); + } +}; + struct scan_handler : error_handler { private: scan_parse_context parse_ctx_; scan_context scan_ctx_; - scan_args args_; int next_arg_id_; - scan_arg arg_; - - template <typename T = unsigned> T read_uint() { - T value = 0; - auto it = scan_ctx_.begin(), end = scan_ctx_.end(); - while (it != end) { - char c = *it++; - if (c < '0' || c > '9') on_error("invalid input"); - // TODO: check overflow - value = value * 10 + static_cast<unsigned>(c - '0'); - } - scan_ctx_.advance_to(it); - return value; - } - template <typename T = int> T read_int() { - auto it = scan_ctx_.begin(), end = scan_ctx_.end(); - bool negative = it != end && *it == '-'; - if (negative) ++it; - scan_ctx_.advance_to(it); - const auto value = read_uint<typename std::make_unsigned<T>::type>(); - if (negative) return -static_cast<T>(value); - return static_cast<T>(value); - } + using sentinel = scan_buffer::sentinel; public: - FMT_CONSTEXPR scan_handler(string_view format, string_view input, + FMT_CONSTEXPR scan_handler(string_view format, scan_buffer& buf, scan_args args) - : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {} + : parse_ctx_(format), scan_ctx_(buf, args), next_arg_id_(0) {} - const char* pos() const { return scan_ctx_.begin(); } + auto pos() const -> scan_buffer::iterator { return scan_ctx_.begin(); } void on_text(const char* begin, const char* end) { - auto size = to_unsigned(end - begin); + if (begin == end) return; auto it = scan_ctx_.begin(); - if (it + size > scan_ctx_.end() || !std::equal(begin, end, it)) - on_error("invalid input"); - scan_ctx_.advance_to(it + size); + for (; begin != end; ++begin, ++it) { + if (it == sentinel() || *begin != *it) on_error("invalid input"); + } + scan_ctx_.advance_to(it); } - FMT_CONSTEXPR int on_arg_id() { return on_arg_id(next_arg_id_++); } - FMT_CONSTEXPR int on_arg_id(int id) { - if (id >= args_.size) on_error("argument index out of range"); - arg_ = args_.data[id]; + FMT_CONSTEXPR auto on_arg_id() -> int { return on_arg_id(next_arg_id_++); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + if (!scan_ctx_.arg(id)) on_error("argument index out of range"); return id; } - FMT_CONSTEXPR int on_arg_id(string_view id) { + FMT_CONSTEXPR auto on_arg_id(string_view id) -> int { if (id.data()) on_error("invalid format"); return 0; } - void on_replacement_field(int, const char*) { - auto it = scan_ctx_.begin(), end = scan_ctx_.end(); - switch (arg_.type) { - case scan_type::int_type: - *arg_.int_value = read_int(); - break; - case scan_type::uint_type: - *arg_.uint_value = read_uint(); - break; - case scan_type::long_long_type: - *arg_.long_long_value = read_int<long long>(); - break; - case scan_type::ulong_long_type: - *arg_.ulong_long_value = read_uint<unsigned long long>(); - break; - case scan_type::string_type: - while (it != end && *it != ' ') arg_.string->push_back(*it++); - scan_ctx_.advance_to(it); - break; - case scan_type::string_view_type: { - auto s = it; - while (it != end && *it != ' ') ++it; - *arg_.string_view = fmt::string_view(s, to_unsigned(it - s)); - scan_ctx_.advance_to(it); - break; - } - case scan_type::none_type: - case scan_type::custom_type: - assert(false); - } + void on_replacement_field(int arg_id, const char*) { + scan_arg arg = scan_ctx_.arg(arg_id); + auto it = scan_ctx_.begin(); + while (it != sentinel() && is_whitespace(*it)) ++it; + scan_ctx_.advance_to(arg.visit(default_arg_scanner{it})); } - const char* on_format_specs(int, const char* begin, const char*) { - if (arg_.type != scan_type::custom_type) return begin; - parse_ctx_.advance_to(begin); - arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_); - return parse_ctx_.begin(); + auto on_format_specs(int arg_id, const char* begin, const char* end) -> const + char* { + scan_arg arg = scan_ctx_.arg(arg_id); + if (arg.scan_custom(begin, parse_ctx_, scan_ctx_)) + return parse_ctx_.begin(); + auto specs = format_specs<>(); + begin = parse_scan_specs(begin, end, specs, arg.type()); + if (begin == end || *begin != '}') on_error("missing '}' in format string"); + scan_ctx_.advance_to(arg.visit(arg_scanner{scan_ctx_.begin(), specs})); + return begin; } + + void on_error(const char* message) { error_handler::on_error(message); } }; } // namespace detail -template <typename... Args> -std::array<detail::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) { +template <typename... T> +auto make_scan_args(T&... args) -> std::array<scan_arg, sizeof...(T)> { return {{args...}}; } -string_view::iterator vscan(string_view input, string_view format_str, - scan_args args) { - detail::scan_handler h(format_str, input, args); - detail::parse_format_string<false>(format_str, h); - return input.begin() + (h.pos() - &*input.begin()); +void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) { + auto h = detail::scan_handler(fmt, buf, args); + detail::parse_format_string<false>(fmt, h); +} + +template <typename... T> +auto scan(string_view input, string_view fmt, T&... args) + -> string_view::iterator { + auto&& buf = detail::string_scan_buffer(input); + vscan(buf, fmt, make_scan_args(args...)); + return input.begin() + (buf.begin().base() - input.data()); } -template <typename... Args> -string_view::iterator scan(string_view input, string_view format_str, - Args&... args) { - return vscan(input, format_str, make_scan_args(args...)); +template <typename InputRange, typename... T, + FMT_ENABLE_IF(!std::is_convertible<InputRange, string_view>::value)> +auto scan(InputRange&& input, string_view fmt, T&... args) + -> decltype(std::begin(input)) { + auto it = std::begin(input); + vscan(get_buffer(it), fmt, make_scan_args(args...)); + return it; } + +template <typename... T> bool scan(std::FILE* f, string_view fmt, T&... args) { + auto&& buf = detail::file_scan_buffer(f); + vscan(buf, fmt, make_scan_args(args...)); + return buf.begin() != buf.end(); +} + FMT_END_NAMESPACE diff --git a/test/std-test.cc b/test/std-test.cc index 41183dbf..de3feaa0 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -26,10 +26,13 @@ TEST(std_test, path) { EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar"); EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\""); + EXPECT_EQ(fmt::format("{:g}", path("/usr/bin")), "/usr/bin"); # ifdef _WIN32 - EXPECT_EQ(fmt::format("{}", path( - L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448" - L"\x0447\x044B\x043D\x0430")), + EXPECT_EQ(fmt::format("{}", path("C:\\foo")), "C:\\foo"); + EXPECT_EQ(fmt::format("{:g}", path("C:\\foo")), "C:/foo"); + + EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448" + L"\x0447\x044B\x043D\x0430")), "Шчучыншчына"); EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "�"); EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\""); @@ -62,6 +65,15 @@ TEST(std_test, thread_id) { EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty()); } +#ifdef __cpp_lib_source_location +TEST(std_test, source_location) { + std::source_location loc = std::source_location::current(); + EXPECT_EQ(fmt::format("{}", loc), + fmt::format("{}:{}:{}: {}", loc.file_name(), loc.line(), + loc.column(), loc.function_name())); +} +#endif + TEST(std_test, optional) { #ifdef __cpp_lib_optional EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none"); @@ -90,6 +102,36 @@ TEST(std_test, optional) { #endif } +namespace my_nso { +enum class my_number { + one, + two, +}; +auto format_as(my_number number) -> fmt::string_view { + return number == my_number::one ? "first" : "second"; +} + +class my_class { + public: + int av; + + private: + friend auto format_as(const my_class& elm) -> std::string { + return fmt::to_string(elm.av); + } +}; +} // namespace my_nso +TEST(std_test, optional_format_as) { +#ifdef __cpp_lib_optional + EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none"); + EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_number::one}), + "optional(\"first\")"); + EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none"); + EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}), + "optional(\"7\")"); +#endif +} + struct throws_on_move { throws_on_move() = default; @@ -255,10 +297,10 @@ TEST(std_test, format_atomic) { #ifdef __cpp_lib_atomic_flag_test TEST(std_test, format_atomic_flag) { std::atomic_flag f = ATOMIC_FLAG_INIT; - (void) f.test_and_set(); + (void)f.test_and_set(); EXPECT_EQ(fmt::format("{}", f), "true"); const std::atomic_flag cf = ATOMIC_FLAG_INIT; EXPECT_EQ(fmt::format("{}", cf), "false"); } -#endif // __cpp_lib_atomic_flag_test +#endif // __cpp_lib_atomic_flag_test diff --git a/test/util.cc b/test/util.cc index 4ff34a91..d3f2dc73 100644 --- a/test/util.cc +++ b/test/util.cc @@ -39,6 +39,11 @@ std::locale get_locale(const char* name, const char* alt_name) { auto loc = do_get_locale(name); if (loc == std::locale::classic() && alt_name) loc = do_get_locale(alt_name); +#ifdef __OpenBSD__ + // Locales are not working in OpenBSD: + // https://github.com/fmtlib/fmt/issues/3670. + loc = std::locale::classic(); +#endif if (loc == std::locale::classic()) fmt::print(stderr, "{} locale is missing.\n", name); return loc; diff --git a/test/util.h b/test/util.h index 9120e22e..803cdeea 100644 --- a/test/util.h +++ b/test/util.h @@ -29,9 +29,9 @@ void safe_sprintf(char (&buffer)[SIZE], const char* format, ...) { extern const char* const file_content; // Opens a buffered file for reading. -fmt::buffered_file open_buffered_file(FILE** fp = nullptr); +auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file; -inline FILE* safe_fopen(const char* filename, const char* mode) { +inline auto safe_fopen(const char* filename, const char* mode) -> FILE* { #if defined(_WIN32) && !defined(__MINGW32__) // Fix MSVC warning about "unsafe" fopen. FILE* f = nullptr; @@ -51,17 +51,17 @@ template <typename Char> class basic_test_string { public: explicit basic_test_string(const Char* value = empty) : value_(value) {} - const std::basic_string<Char>& value() const { return value_; } + auto value() const -> const std::basic_string<Char>& { return value_; } }; template <typename Char> const Char basic_test_string<Char>::empty[] = {0}; -typedef basic_test_string<char> test_string; -typedef basic_test_string<wchar_t> test_wstring; +using test_string = basic_test_string<char>; +using test_wstring = basic_test_string<wchar_t>; template <typename Char> -std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& os, - const basic_test_string<Char>& s) { +auto operator<<(std::basic_ostream<Char>& os, const basic_test_string<Char>& s) + -> std::basic_ostream<Char>& { os << s.value(); return os; } @@ -72,10 +72,12 @@ class date { public: date(int year, int month, int day) : year_(year), month_(month), day_(day) {} - int year() const { return year_; } - int month() const { return month_; } - int day() const { return day_; } + auto year() const -> int { return year_; } + auto month() const -> int { return month_; } + auto day() const -> int { return day_; } }; -// Returns a locale with the given name if available or classic locale otherwise. -std::locale get_locale(const char* name, const char* alt_name = nullptr); +// Returns a locale with the given name if available or classic locale +// otherwise. +auto get_locale(const char* name, const char* alt_name = nullptr) + -> std::locale; diff --git a/test/xchar-test.cc b/test/xchar-test.cc index f72e94dc..7f33fb27 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -187,18 +187,6 @@ template <typename S> std::string from_u8str(const S& str) { return std::string(str.begin(), str.end()); } -TEST(xchar_test, format_utf8_precision) { - using str_type = std::basic_string<fmt::detail::char8_type>; - auto format = - str_type(reinterpret_cast<const fmt::detail::char8_type*>(u8"{:.4}")); - auto str = str_type(reinterpret_cast<const fmt::detail::char8_type*>( - u8"caf\u00e9s")); // cafés - auto result = fmt::format(format, str); - EXPECT_EQ(fmt::detail::compute_width(result), 4); - EXPECT_EQ(result.size(), 5); - EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5))); -} - TEST(xchar_test, format_to) { auto buf = std::vector<wchar_t>(); fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0'); @@ -581,7 +569,7 @@ template <class charT> struct formatter<std::complex<double>, charT> { basic_format_parse_context<charT>& ctx) { auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, detail::type::float_type); - detail::parse_float_type_spec(specs_, detail::error_handler()); + detail::parse_float_type_spec(specs_); return end; } @@ -599,9 +587,9 @@ template <class charT> struct formatter<std::complex<double>, charT> { fmt::runtime("{:" + specs + "}"), c.imag()); auto fill_align_width = std::string(); if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width); - return format_to(ctx.out(), runtime("{:" + fill_align_width + "}"), - c.real() != 0 ? fmt::format("({}+{}i)", real, imag) - : fmt::format("{}i", imag)); + return fmt::format_to(ctx.out(), runtime("{:" + fill_align_width + "}"), + c.real() != 0 ? fmt::format("({}+{}i)", real, imag) + : fmt::format("{}i", imag)); } }; FMT_END_NAMESPACE |