diff options
author | Merry <[email protected]> | 2023-10-08 08:55:04 +0100 |
---|---|---|
committer | Merry <[email protected]> | 2023-10-08 08:55:04 +0100 |
commit | 7a7557f70f46993fb20bf7df0ed65ce4cf272cea (patch) | |
tree | 5617457befdc8aab1ca35320abba741dcf353249 /externals | |
parent | 96e179465884be74987d5847d6741cdabdfe1b48 (diff) | |
parent | 091ca2aa033bf9bf9bfecdc55140d2cd15ed2ccb (diff) | |
download | dynarmic-7a7557f70f46993fb20bf7df0ed65ce4cf272cea.tar.gz dynarmic-7a7557f70f46993fb20bf7df0ed65ce4cf272cea.zip |
externals: Update fmt to 10.1.1
Merge commit '091ca2aa033bf9bf9bfecdc55140d2cd15ed2ccb'
Diffstat (limited to 'externals')
76 files changed, 6824 insertions, 4821 deletions
diff --git a/externals/fmt/.github/dependabot.yml b/externals/fmt/.github/dependabot.yml new file mode 100644 index 00000000..5bacb9a4 --- /dev/null +++ b/externals/fmt/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" # Necessary to update action hashs + directory: "/" + schedule: + interval: "weekly" + # Allow up to 3 opened pull requests for github-actions versions + open-pull-requests-limit: 3 diff --git a/externals/fmt/.github/pull_request_template.md b/externals/fmt/.github/pull_request_template.md index b70c27bf..e8774133 100644 --- a/externals/fmt/.github/pull_request_template.md +++ b/externals/fmt/.github/pull_request_template.md @@ -1,6 +1,7 @@ <!-- Please read the contribution guidelines before submitting a pull request: https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md. -By submitting this pull request, you agree that your contributions are licensed -under the {fmt} license, and agree to future changes to the licensing. +By submitting this pull request, you agree to license your contribution(s) +under the terms outlined in LICENSE.rst and represent that you have the right +to do so. --> diff --git a/externals/fmt/.github/workflows/cifuzz.yml b/externals/fmt/.github/workflows/cifuzz.yml new file mode 100644 index 00000000..f87899d0 --- /dev/null +++ b/externals/fmt/.github/workflows/cifuzz.yml @@ -0,0 +1,30 @@ +name: CIFuzz +on: [pull_request] + +permissions: + contents: read + +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@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 + 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 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/externals/fmt/.github/workflows/doc.yml b/externals/fmt/.github/workflows/doc.yml index 47c09774..5fbd6980 100644 --- a/externals/fmt/.github/workflows/doc.yml +++ b/externals/fmt/.github/workflows/doc.yml @@ -11,7 +11,15 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Add ubuntu mirrors + run: | + # Github Actions caching proxy is at times unreliable + # see https://github.com/actions/runner-images/issues/7048 + printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt + curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt + sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list - name: Create Build Environment run: | diff --git a/externals/fmt/.github/workflows/linux.yml b/externals/fmt/.github/workflows/linux.yml index 70085f5c..9c5a7b80 100644 --- a/externals/fmt/.github/workflows/linux.yml +++ b/externals/fmt/.github/workflows/linux.yml @@ -7,66 +7,83 @@ permissions: jobs: build: - runs-on: ${{ matrix.os }} + runs-on: ubuntu-20.04 strategy: matrix: cxx: [g++-4.8, g++-10, clang++-9] build_type: [Debug, Release] std: [11] - os: [ubuntu-18.04] include: - cxx: g++-4.8 install: sudo apt install g++-4.8 - os: ubuntu-18.04 - cxx: g++-8 build_type: Debug std: 14 install: sudo apt install g++-8 - os: ubuntu-18.04 - cxx: g++-8 build_type: Debug std: 17 install: sudo apt install g++-8 - os: ubuntu-18.04 + - cxx: g++-9 + build_type: Debug + std: 17 - cxx: g++-10 build_type: Debug std: 17 - os: ubuntu-18.04 - cxx: g++-11 build_type: Debug std: 20 - os: ubuntu-20.04 install: sudo apt install g++-11 - cxx: clang++-8 build_type: Debug std: 17 cxxflags: -stdlib=libc++ - os: ubuntu-18.04 install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev - cxx: clang++-9 + install: sudo apt install clang-9 + - cxx: clang++-9 build_type: Debug fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON std: 17 - os: ubuntu-18.04 + install: sudo apt install clang-9 - cxx: clang++-11 build_type: Debug std: 20 - os: ubuntu-20.04 - cxx: clang++-11 build_type: Debug std: 20 cxxflags: -stdlib=libc++ - os: ubuntu-20.04 install: sudo apt install libc++-11-dev libc++abi-11-dev - shared: -DBUILD_SHARED_LIBS=ON steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Set timezone + run: sudo timedatectl set-timezone 'Asia/Yekaterinburg' + + - name: Add repositories for older GCC + run: | + # Below two repos provide GCC 4.8, 5.5 and 6.4 + sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main' + sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe' + # Below two repos additionally update GCC 6 to 6.5 + # sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates main' + # sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates universe' + if: ${{ matrix.cxx == 'g++-4.8' }} + + - name: Add ubuntu mirrors + run: | + # Github Actions caching proxy is at times unreliable + # see https://github.com/actions/runner-images/issues/7048 + printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt + curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt + sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list - name: Create Build Environment run: | - ${{matrix.install}} sudo apt update + ${{matrix.install}} sudo apt install locales-all cmake -E make_directory ${{runner.workspace}}/build diff --git a/externals/fmt/.github/workflows/macos.yml b/externals/fmt/.github/workflows/macos.yml index 24dd5958..a3be1113 100644 --- a/externals/fmt/.github/workflows/macos.yml +++ b/externals/fmt/.github/workflows/macos.yml @@ -7,15 +7,29 @@ permissions: jobs: build: - runs-on: macos-10.15 strategy: matrix: + os: [macos-11, macos-13] build_type: [Debug, Release] + std: [11, 17, 20] + exclude: + - { os: macos-11, std: 20 } + - { os: macos-13, std: 11 } + - { os: macos-13, std: 17 } include: - shared: -DBUILD_SHARED_LIBS=ON + runs-on: '${{ matrix.os }}' + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Set timezone + run: sudo systemsetup -settimezone 'Asia/Yekaterinburg' + + - name: Select Xcode 14.3 (macOS 13) + run: sudo xcode-select -s "/Applications/Xcode_14.3.app" + if: ${{ matrix.os == 'macos-13' }} - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -24,6 +38,7 @@ jobs: working-directory: ${{runner.workspace}}/build run: | cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE diff --git a/externals/fmt/.github/workflows/scorecard.yml b/externals/fmt/.github/workflows/scorecard.yml new file mode 100644 index 00000000..5ad10fa2 --- /dev/null +++ b/externals/fmt/.github/workflows/scorecard.yml @@ -0,0 +1,65 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '26 14 * * 5' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + + steps: + - name: "Checkout code" + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + publish_results: true + + # 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 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4 + with: + sarif_file: results.sarif diff --git a/externals/fmt/.github/workflows/windows.yml b/externals/fmt/.github/workflows/windows.yml index 7d0c4a1f..12d3c1f3 100644 --- a/externals/fmt/.github/workflows/windows.yml +++ b/externals/fmt/.github/workflows/windows.yml @@ -15,27 +15,36 @@ jobs: # https://github.com/actions/virtual-environments. os: [windows-2019] platform: [Win32, x64] + toolset: [v140, v141, v142] + standard: [14, 17, 20] + shared: ["", -DBUILD_SHARED_LIBS=ON] build_type: [Debug, Release] - standard: [11, 17, 20] + exclude: + - { toolset: v140, standard: 17 } + - { toolset: v140, standard: 20 } + - { toolset: v141, standard: 14 } + - { toolset: v141, standard: 20 } + - { toolset: v142, standard: 14 } + - { platform: Win32, toolset: v140 } + - { platform: Win32, toolset: v141 } + - { platform: Win32, standard: 14 } + - { platform: Win32, standard: 20 } + - { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON } + - { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON } + - { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON } + - { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON } include: - - os: windows-2019 - platform: Win32 - build_type: Debug - shared: -DBUILD_SHARED_LIBS=ON - os: windows-2022 platform: x64 + toolset: v143 build_type: Debug standard: 20 - exclude: - - os: windows-2019 - standard: 11 - platform: Win32 - - os: windows-2019 - standard: 20 - platform: Win32 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + + - name: Set timezone + run: tzutil /s "Ekaterinburg Standard Time" - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -45,9 +54,9 @@ jobs: shell: bash working-directory: ${{runner.workspace}}/build run: | - cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ - -A ${{matrix.platform}} \ + cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \ -DCMAKE_CXX_STANDARD=${{matrix.standard}} \ + ${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ $GITHUB_WORKSPACE - name: Build @@ -61,3 +70,31 @@ jobs: run: ctest -C ${{matrix.build_type}} -V env: CTEST_OUTPUT_ON_FAILURE: True + + mingw: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + strategy: + matrix: + sys: [ mingw64, ucrt64 ] + steps: + - name: Set timezone + run: tzutil /s "Ekaterinburg Standard Time" + shell: cmd + - uses: msys2/setup-msys2@7efe20baefed56359985e327d329042cde2434ff # v2 + with: + release: false + msystem: ${{matrix.sys}} + pacboy: cc:p cmake:p ninja:p lld:p + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - name: Configure + run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug + env: { LDFLAGS: -fuse-ld=lld } + - name: Build + run: cmake --build ../build + - name: Test + run: ctest -j `nproc` --test-dir ../build + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/externals/fmt/CMakeLists.txt b/externals/fmt/CMakeLists.txt index 4033ddfc..bcdd1101 100644 --- a/externals/fmt/CMakeLists.txt +++ b/externals/fmt/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1...3.18) +cmake_minimum_required(VERSION 3.8...3.26) # Fallback for using newer policies on CMake <3.12. if(${CMAKE_VERSION} VERSION_LESS 3.12) @@ -24,15 +24,86 @@ function(join result_var) set(${result_var} "${result}" PARENT_SCOPE) endfunction() +# DEPRECATED! Should be merged into add_module_library. function(enable_module target) if (MSVC) set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) target_compile_options(${target} PRIVATE /interface /ifcOutput ${BMI} INTERFACE /reference fmt=${BMI}) + set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) + set_source_files_properties(${BMI} PROPERTIES GENERATED ON) endif () - set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) - set_source_files_properties(${BMI} PROPERTIES GENERATED ON) +endfunction() + +# Adds a library compiled with C++20 module support. +# `enabled` is a CMake variables that specifies if modules are enabled. +# If modules are disabled `add_module_library` falls back to creating a +# non-modular library. +# +# Usage: +# add_module_library(<name> [sources...] FALLBACK [sources...] [IF enabled]) +function(add_module_library name) + cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN}) + set(sources ${AML_UNPARSED_ARGUMENTS}) + + add_library(${name}) + set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX) + + if (NOT ${${AML_IF}}) + # Create a non-modular library. + target_sources(${name} PRIVATE ${AML_FALLBACK}) + return() + endif () + + # Modules require C++20. + target_compile_features(${name} PUBLIC cxx_std_20) + if (CMAKE_COMPILER_IS_GNUCXX) + target_compile_options(${name} PUBLIC -fmodules-ts) + endif () + + # `std` is affected by CMake options and may be higher than C++20. + get_target_property(std ${name} CXX_STANDARD) + + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(pcms) + foreach (src ${sources}) + get_filename_component(pcm ${src} NAME_WE) + set(pcm ${pcm}.pcm) + + # Propagate -fmodule-file=*.pcm to targets that link with this library. + target_compile_options( + ${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm}) + + # Use an absolute path to prevent target_link_libraries prepending -l + # to it. + set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm}) + add_custom_command( + OUTPUT ${pcm} + COMMAND ${CMAKE_CXX_COMPILER} + -std=c++${std} -x c++-module --precompile -c + -o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src} + "-I$<JOIN:$<TARGET_PROPERTY:${name},INCLUDE_DIRECTORIES>,;-I>" + # Required by the -I generator expression above. + COMMAND_EXPAND_LISTS + DEPENDS ${src}) + endforeach () + + # Add .pcm files as sources to make sure they are built before the library. + set(sources) + foreach (pcm ${pcms}) + get_filename_component(pcm_we ${pcm} NAME_WE) + set(obj ${pcm_we}.o) + # Use an absolute path to prevent target_link_libraries prepending -l. + set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj}) + add_custom_command( + OUTPUT ${obj} + COMMAND ${CMAKE_CXX_COMPILER} $<TARGET_PROPERTY:${name},COMPILE_OPTIONS> + -c -o ${obj} ${pcm} + DEPENDS ${pcm}) + endforeach () + endif () + target_sources(${name} PRIVATE ${sources}) endfunction() include(CMakeParseArguments) @@ -75,7 +146,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings." # Options that control generation of various targets. option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) -option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT}) +option(FMT_INSTALL "Generate the install target." ON) option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) @@ -83,16 +154,6 @@ option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_MODULE "Build a module instead of a traditional library." OFF) option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF) -set(FMT_CAN_MODULE OFF) -if (CMAKE_CXX_STANDARD GREATER 17 AND - # msvc 16.10-pre4 - MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035) - set(FMT_CAN_MODULE OFF) -endif () -if (NOT FMT_CAN_MODULE) - set(FMT_MODULE OFF) - message(STATUS "Module support is disabled.") -endif () if (FMT_TEST AND FMT_MODULE) # The tests require {fmt} to be compiled as traditional library message(STATUS "Testing is incompatible with build mode 'module'.") @@ -101,6 +162,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "") if (FMT_SYSTEM_HEADERS) set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) endif () +if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS") + set(FMT_TEST OFF) + message(STATUS "MSDOS is incompatible with gtest") +endif() # Get version from core.h file(READ include/fmt/core.h core_h) @@ -118,23 +183,15 @@ message(STATUS "Version: ${FMT_VERSION}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) endif () set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") -include(cxx14) +include(CheckCXXCompilerFlag) include(JoinPaths) -list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index) -if (${index} GREATER -1) - # Use cxx_variadic_templates instead of more appropriate cxx_std_11 for - # compatibility with older CMake versions. - set(FMT_REQUIRED_FEATURES cxx_variadic_templates) -endif () -message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}") - if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING "Preset for the export of private symbols") @@ -220,16 +277,18 @@ endfunction() add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h format-inl.h os.h ostream.h printf.h ranges.h std.h xchar.h) -if (FMT_MODULE) - set(FMT_SOURCES src/fmt.cc) -elseif (FMT_OS) - set(FMT_SOURCES src/format.cc src/os.cc) -else() - set(FMT_SOURCES src/format.cc) +set(FMT_SOURCES src/format.cc) +if (FMT_OS) + set(FMT_SOURCES ${FMT_SOURCES} src/os.cc) endif () -add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) +add_module_library(fmt src/fmt.cc FALLBACK + ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst + IF FMT_MODULE) add_library(fmt::fmt ALIAS fmt) +if (FMT_MODULE) + enable_module(fmt) +endif () if (FMT_WERROR) target_compile_options(fmt PRIVATE ${WERROR_FLAG}) @@ -237,11 +296,12 @@ endif () if (FMT_PEDANTIC) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) endif () -if (FMT_MODULE) - enable_module(fmt) -endif () -target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) +if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + target_compile_features(fmt PUBLIC cxx_std_11) +else () + message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler") +endif () target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> @@ -262,13 +322,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") endif () if (BUILD_SHARED_LIBS) - if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND - NOT EMSCRIPTEN) - # Fix rpmlint warning: - # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6. - target_link_libraries(fmt -Wl,--as-needed) - endif () - target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED) + target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED) endif () if (FMT_SAFE_DURATION_CAST) target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST) @@ -278,7 +332,7 @@ add_library(fmt-header-only INTERFACE) add_library(fmt::fmt-header-only ALIAS fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) -target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) +target_compile_features(fmt-header-only INTERFACE cxx_std_11) target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> @@ -300,7 +354,7 @@ if (FMT_INSTALL) "Installation directory for libraries, a relative path that " "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.") - set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH + set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING "Installation directory for pkgconfig (.pc) files, a relative " "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an " "absolute path.") @@ -330,7 +384,6 @@ if (FMT_INSTALL) LIBRARY DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR} PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt" - FRAMEWORK DESTINATION "." RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) # Use a namespace because CMake provides better diagnostics for namespaced @@ -345,8 +398,6 @@ if (FMT_INSTALL) install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} NAMESPACE fmt::) - install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}> - DESTINATION ${FMT_LIB_DIR} OPTIONAL) install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") endif () diff --git a/externals/fmt/ChangeLog.rst b/externals/fmt/ChangeLog.rst index 966d039f..644244d8 100644 --- a/externals/fmt/ChangeLog.rst +++ b/externals/fmt/ChangeLog.rst @@ -1,3 +1,796 @@ +10.1.1 - 2023-08-26 +------------------- + +* Added formatters for ``std::atomic`` and ``atomic_flag`` + (`#3574 <https://github.com/fmtlib/fmt/pull/3574>`_, + `#3594 <https://github.com/fmtlib/fmt/pull/3594>`_). + Thanks `@wangzw (Zhanwei Wang) <https://github.com/wangzw>`_ and + `@AlexGuteniev (Alex Guteniev) <https://github.com/AlexGuteniev>`_. + +* Fixed an error about partial specialization of ``formatter<string>`` + after instantiation when compiled with gcc and C++20 + (`#3584 <https://github.com/fmtlib/fmt/issues/3584>`_). + +* Fixed compilation as a C++20 module with gcc and clang + (`#3587 <https://github.com/fmtlib/fmt/issues/3587>`_, + `#3597 <https://github.com/fmtlib/fmt/pull/3597>`_, + `#3605 <https://github.com/fmtlib/fmt/pull/3605>`_). Thanks + `@MathewBensonCode (Mathew Benson) <https://github.com/MathewBensonCode>`_. + +* Made ``fmt::to_string`` work with types that have ``format_as`` overloads + (`#3575 <https://github.com/fmtlib/fmt/pull/3575>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Made ``formatted_size`` work with integral format specifiers at compile time + (`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_). + Thanks `@elbeno (Ben Deane) <https://github.com/elbeno>`_. + +* Fixed a warning about the ``no_unique_address`` attribute on clang-cl + (`#3599 <https://github.com/fmtlib/fmt/pull/3599>`_). + Thanks `@lukester1975 <https://github.com/lukester1975>`_. + +* Improved compatibility with the legacy GBK encoding + (`#3598 <https://github.com/fmtlib/fmt/issues/3598>`_, + `#3599 <https://github.com/fmtlib/fmt/pull/3599>`_). + Thanks `@YuHuanTin <https://github.com/YuHuanTin>`_. + +* Added OpenSSF Scorecard analysis + (`#3530 <https://github.com/fmtlib/fmt/issues/3530>`_, + `#3571 <https://github.com/fmtlib/fmt/pull/3571>`_). + Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_. + +* Updated CI dependencies + (`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_, + `#3592 <https://github.com/fmtlib/fmt/pull/3592>`_, + `#3593 <https://github.com/fmtlib/fmt/pull/3593>`_, + `#3602 <https://github.com/fmtlib/fmt/pull/3602>`_). + +10.1.0 - 2023-08-12 +------------------- + +* Optimized format string compilation resulting in up to 40% speed up in + compiled ``format_to`` and ~4x speed up in compiled ``format_to_n`` on a + concatenation benchmark (`#3133 <https://github.com/fmtlib/fmt/issues/3133>`_, + `#3484 <https://github.com/fmtlib/fmt/issues/3484>`_). + + {fmt} 10.0:: + + --------------------------------------------------------- + Benchmark Time CPU Iterations + --------------------------------------------------------- + BM_format_to 78.9 ns 78.9 ns 8881746 + BM_format_to_n 568 ns 568 ns 1232089 + + {fmt} 10.1:: + + --------------------------------------------------------- + Benchmark Time CPU Iterations + --------------------------------------------------------- + BM_format_to 54.9 ns 54.9 ns 12727944 + BM_format_to_n 133 ns 133 ns 5257795 + +* Optimized storage of an empty allocator in ``basic_memory_buffer`` + (`#3485 <https://github.com/fmtlib/fmt/pull/3485>`_). + Thanks `@Minty-Meeo <https://github.com/Minty-Meeo>`_. + +* Added formatters for proxy references to elements of ``std::vector<bool>`` and + ``std::bitset<N>`` (`#3567 <https://github.com/fmtlib/fmt/issues/3567>`_, + `#3570 <https://github.com/fmtlib/fmt/pull/3570>`_). + For example (`godbolt <https://godbolt.org/z/zYb79Pvn8>`__): + + .. code:: c++ + + #include <vector> + #include <fmt/std.h> + + int main() { + auto v = std::vector<bool>{true}; + fmt::print("{}", v[0]); + } + + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and + `@felix642 (Félix-Antoine Constantin) <https://github.com/felix642>`_. + +* Fixed an ambiguous formatter specialization for containers that look like + container adaptors such as ``boost::flat_set`` + (`#3556 <https://github.com/fmtlib/fmt/issues/3556>`_, + `#3561 <https://github.com/fmtlib/fmt/pull/3561>`_). + Thanks `@5chmidti <https://github.com/5chmidti>`_. + +* Fixed compilation when formatting durations not convertible from + ``std::chrono::seconds`` (`#3430 <https://github.com/fmtlib/fmt/pull/3430>`_). + Thanks `@patlkli (Patrick Geltinger) <https://github.com/patlkli>`_. + +* Made the ``formatter`` specialization for ``char*`` const-correct + (`#3432 <https://github.com/fmtlib/fmt/pull/3432>`_). + Thanks `@timsong-cpp <https://github.com/timsong-cpp>`_. + +* Made ``{}`` and ``{:}`` handled consistently during compile-time checks + (`#3526 <https://github.com/fmtlib/fmt/issues/3526>`_). + +* Disallowed passing temporaries to ``make_format_args`` to improve API safety + by preventing dangling references. + +* Improved the compile-time error for unformattable types + (`#3478 <https://github.com/fmtlib/fmt/pull/3478>`_). + Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Improved the floating-point formatter + (`#3448 <https://github.com/fmtlib/fmt/pull/3448>`_, + `#3450 <https://github.com/fmtlib/fmt/pull/3450>`_). + Thanks `@florimond-collette (Florimond Collette) + <https://github.com/florimond-collette>`_. + +* Fixed handling of precision for ``long double`` larger than 64 bits. + (`#3539 <https://github.com/fmtlib/fmt/issues/3539>`_, + `#3564 <https://github.com/fmtlib/fmt/issues/3564>`_). + +* Made floating-point and chrono tests less platform-dependent + (`#3337 <https://github.com/fmtlib/fmt/issues/3337>`_, + `#3433 <https://github.com/fmtlib/fmt/issues/3433>`_, + `#3434 <https://github.com/fmtlib/fmt/pull/3434>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Removed the remnants of the Grisu floating-point formatter that has been + replaced by Dragonbox in earlier versions. + +* Added ``throw_format_error`` to the public API + (`#3551 <https://github.com/fmtlib/fmt/pull/3551>`_). + Thanks `@mjerabek (Martin Jeřábek) <https://github.com/mjerabek>`_. + +* Made ``FMT_THROW`` assert even if assertions are disabled when compiling with + exceptions disabled (`#3418 <https://github.com/fmtlib/fmt/issues/3418>`_, + `#3439 <https://github.com/fmtlib/fmt/pull/3439>`_). + Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Made ``format_as`` and ``std::filesystem::path`` formatter work with exotic + code unit types. + (`#3457 <https://github.com/fmtlib/fmt/pull/3457>`_, + `#3476 <https://github.com/fmtlib/fmt/pull/3476>`_). + Thanks `@gix (Nico Rieck) <https://github.com/gix>`_, + `@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_. + +* Added support for the ``?`` format specifier to ``std::filesystem::path`` and + made the default unescaped for consistency with strings. + +* Deprecated the wide stream overload of ``printf``. + +* Removed unused ``basic_printf_parse_context``. + +* Improved RTTI detection used when formatting exceptions + (`#3468 <https://github.com/fmtlib/fmt/pull/3468>`_). + Thanks `@danakj (Dana Jansens) <https://github.com/danakj>`_. + +* Improved compatibility with VxWorks7 + (`#3467 <https://github.com/fmtlib/fmt/pull/3467>`_). + Thanks `@wenshan1 (Bin Lan) <https://github.com/wenshan1>`_. + +* Improved documentation + (`#3174 <https://github.com/fmtlib/fmt/issues/3174>`_, + `#3423 <https://github.com/fmtlib/fmt/issues/3423>`_, + `#3454 <https://github.com/fmtlib/fmt/pull/3454>`_, + `#3458 <https://github.com/fmtlib/fmt/issues/3458>`_, + `#3461 <https://github.com/fmtlib/fmt/pull/3461>`_, + `#3487 <https://github.com/fmtlib/fmt/issues/3487>`_, + `#3515 <https://github.com/fmtlib/fmt/pull/3515>`_). + Thanks `@zencatalyst (Kasra Hashemi) <https://github.com/zencatalyst>`_, + `@rlalik <https://github.com/rlalik>`_, + `@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_. + +* Improved build and CI configurations + (`#3449 <https://github.com/fmtlib/fmt/issues/3449>`_, + `#3451 <https://github.com/fmtlib/fmt/pull/3451>`_, + `#3452 <https://github.com/fmtlib/fmt/pull/3452>`_, + `#3453 <https://github.com/fmtlib/fmt/pull/3453>`_, + `#3459 <https://github.com/fmtlib/fmt/pull/3459>`_, + `#3481 <https://github.com/fmtlib/fmt/issues/3481>`_, + `#3486 <https://github.com/fmtlib/fmt/pull/3486>`_, + `#3489 <https://github.com/fmtlib/fmt/issues/3489>`_, + `#3496 <https://github.com/fmtlib/fmt/pull/3496>`_, + `#3517 <https://github.com/fmtlib/fmt/issues/3517>`_, + `#3523 <https://github.com/fmtlib/fmt/pull/3523>`_, + `#3563 <https://github.com/fmtlib/fmt/pull/3563>`_). + Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_, + `@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_, + `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_, + `@petrmanek (Petr Mánek) <https://github.com/petrmanek>`_, + `@setoye (Alta) <https://github.com/setoye>`_, + `@abouvier (Alexandre Bouvier) <https://github.com/abouvier>`_. + +* Fixed various warnings and compilation issues + (`#3408 <https://github.com/fmtlib/fmt/issues/3408>`_, + `#3424 <https://github.com/fmtlib/fmt/issues/3424>`_, + `#3444 <https://github.com/fmtlib/fmt/issues/3444>`_, + `#3446 <https://github.com/fmtlib/fmt/pull/3446>`_, + `#3475 <https://github.com/fmtlib/fmt/pull/3475>`_, + `#3482 <https://github.com/fmtlib/fmt/pull/3482>`_, + `#3492 <https://github.com/fmtlib/fmt/issues/3492>`_, + `#3493 <https://github.com/fmtlib/fmt/pull/3493>`_, + `#3508 <https://github.com/fmtlib/fmt/pull/3508>`_, + `#3509 <https://github.com/fmtlib/fmt/issues/3509>`_, + `#3533 <https://github.com/fmtlib/fmt/issues/3533>`_, + `#3542 <https://github.com/fmtlib/fmt/pull/3542>`_, + `#3543 <https://github.com/fmtlib/fmt/issues/3543>`_, + `#3540 <https://github.com/fmtlib/fmt/issues/3540>`_, + `#3544 <https://github.com/fmtlib/fmt/pull/3544>`_, + `#3548 <https://github.com/fmtlib/fmt/issues/3548>`_, + `#3549 <https://github.com/fmtlib/fmt/pull/3549>`_, + `#3550 <https://github.com/fmtlib/fmt/pull/3550>`_, + `#3552 <https://github.com/fmtlib/fmt/pull/3552>`_). + Thanks `@adesitter (Arnaud Desitter) <https://github.com/adesitter>`_, + `@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_, + `@Minty-Meeo <https://github.com/Minty-Meeo>`_, + `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_, + `@TobiSchluter (Tobias Schlüter) <https://github.com/TobiSchluter>`_, + `@kieranclancy (Kieran Clancy) <https://github.com/kieranclancy>`_, + `@alexeedm (Dmitry Alexeev) <https://github.com/alexeedm>`_, + `@jurihock (Jürgen Hock) <https://github.com/jurihock>`_, + `@Ozomahtli <https://github.com/Ozomahtli>`_, + `@razaqq <https://github.com/razaqq>`_. + +10.0.0 - 2023-05-09 +------------------- + +* Replaced Grisu with a new floating-point formatting algorithm for given + precision (`#3262 <https://github.com/fmtlib/fmt/issues/3262>`_, + `#2750 <https://github.com/fmtlib/fmt/issues/2750>`_, + `#3269 <https://github.com/fmtlib/fmt/pull/3269>`_, + `#3276 <https://github.com/fmtlib/fmt/pull/3276>`_). + The new algorithm is based on Dragonbox already used for the + shortest representation and gives substantial performance improvement: + + .. image:: https://user-images.githubusercontent.com/33922675/ + 211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png + + * Red: new algorithm + * Green: new algorithm with ``FMT_USE_FULL_CACHE_DRAGONBOX`` defined to 1 + * Blue: old algorithm + + Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_. + +* Replaced ``snprintf``-based hex float formatter with an internal + implementation (`#3179 <https://github.com/fmtlib/fmt/pull/3179>`_, + `#3203 <https://github.com/fmtlib/fmt/pull/3203>`_). + This removes the last usage of ``s(n)printf`` in {fmt}. + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Fixed alignment of floating-point numbers with localization + (`#3263 <https://github.com/fmtlib/fmt/issues/3263>`_, + `#3272 <https://github.com/fmtlib/fmt/pull/3272>`_). + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Made handling of ``#`` consistent with ``std::format``. + +* Improved C++20 module support + (`#3134 <https://github.com/fmtlib/fmt/pull/3134>`_, + `#3254 <https://github.com/fmtlib/fmt/pull/3254>`_, + `#3386 <https://github.com/fmtlib/fmt/pull/3386>`_, + `#3387 <https://github.com/fmtlib/fmt/pull/3387>`_, + `#3388 <https://github.com/fmtlib/fmt/pull/3388>`_, + `#3392 <https://github.com/fmtlib/fmt/pull/3392>`_, + `#3397 <https://github.com/fmtlib/fmt/pull/3397>`_, + `#3399 <https://github.com/fmtlib/fmt/pull/3399>`_, + `#3400 <https://github.com/fmtlib/fmt/pull/3400>`_). + Thanks `@laitingsheng (Tinson Lai) <https://github.com/laitingsheng>`_, + `@Orvid (Orvid King) <https://github.com/Orvid>`_, + `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_. + Switched to the `modules CMake library <https://github.com/vitaut/modules>`_ + which allows building {fmt} as a C++20 module with clang:: + + CXX=clang++ cmake -DFMT_MODULE=ON . + make + +* Made ``format_as`` work with any user-defined type and not just enums. + For example (`godbolt <https://godbolt.org/z/b7rqhq5Kh>`__): + + .. code:: c++ + + #include <fmt/format.h> + + struct floaty_mc_floatface { + double value; + }; + + auto format_as(floaty_mc_floatface f) { return f.value; } + + int main() { + fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42" + } + +* Removed deprecated implicit conversions for enums and conversions to primitive + types for compatibility with ``std::format`` and to prevent potential ODR + violations. Use ``format_as`` instead. + +* Added support for fill, align and width to the time point formatter + (`#3237 <https://github.com/fmtlib/fmt/issues/3237>`_, + `#3260 <https://github.com/fmtlib/fmt/pull/3260>`_, + `#3275 <https://github.com/fmtlib/fmt/pull/3275>`_). + For example (`godbolt <https://godbolt.org/z/rKP6MGz6c>`__): + + .. code:: c++ + + #include <fmt/chrono.h> + + int main() { + // prints " 2023" + fmt::print("{:>8%Y}\n", std::chrono::system_clock::now()); + } + + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Implemented formatting of subseconds + (`#2207 <https://github.com/fmtlib/fmt/issues/2207>`_, + `#3117 <https://github.com/fmtlib/fmt/issues/3117>`_, + `#3115 <https://github.com/fmtlib/fmt/pull/3115>`_, + `#3143 <https://github.com/fmtlib/fmt/pull/3143>`_, + `#3144 <https://github.com/fmtlib/fmt/pull/3144>`_, + `#3349 <https://github.com/fmtlib/fmt/pull/3349>`_). + For example (`godbolt <https://godbolt.org/z/45738oGEo>`__): + + .. code:: c++ + + #include <fmt/chrono.h> + + int main() { + // prints 01.234567 + fmt::print("{:%S}\n", std::chrono::microseconds(1234567)); + } + + Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_ + `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_, + `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Added precision support to ``%S`` + (`#3148 <https://github.com/fmtlib/fmt/pull/3148>`_). + Thanks `@SappyJoy (Stepan Ponomaryov) <https://github.com/SappyJoy>`_ + +* Added support for ``std::utc_time`` + (`#3098 <https://github.com/fmtlib/fmt/issues/3098>`_, + `#3110 <https://github.com/fmtlib/fmt/pull/3110>`_). + Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_. + +* Switched formatting of ``std::chrono::system_clock`` from local time to UTC + for compatibility with the standard + (`#3199 <https://github.com/fmtlib/fmt/issues/3199>`_, + `#3230 <https://github.com/fmtlib/fmt/pull/3230>`_). + Thanks `@ned14 (Niall Douglas) <https://github.com/ned14>`_. + +* Added support for ``%Ez`` and ``%Oz`` to chrono formatters. + (`#3220 <https://github.com/fmtlib/fmt/issues/3220>`_, + `#3222 <https://github.com/fmtlib/fmt/pull/3222>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Improved validation of format specifiers for ``std::chrono::duration`` + (`#3219 <https://github.com/fmtlib/fmt/issues/3219>`_, + `#3232 <https://github.com/fmtlib/fmt/pull/3232>`_). + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Fixed formatting of time points before the epoch + (`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_, + `#3261 <https://github.com/fmtlib/fmt/pull/3261>`_). + For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__): + + .. code:: c++ + + #include <fmt/chrono.h> + + int main() { + auto t = std::chrono::system_clock::from_time_t(0) - + std::chrono::milliseconds(250); + fmt::print("{:%S}\n", t); // prints 59.750000000 + } + + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Experimental: implemented glibc extension for padding seconds, minutes and + hours (`#2959 <https://github.com/fmtlib/fmt/issues/2959>`_, + `#3271 <https://github.com/fmtlib/fmt/pull/3271>`_). + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Added a formatter for ``std::exception`` + (`#2977 <https://github.com/fmtlib/fmt/issues/2977>`_, + `#3012 <https://github.com/fmtlib/fmt/issues/3012>`_, + `#3062 <https://github.com/fmtlib/fmt/pull/3062>`_, + `#3076 <https://github.com/fmtlib/fmt/pull/3076>`_, + `#3119 <https://github.com/fmtlib/fmt/pull/3119>`_). + For example (`godbolt <https://godbolt.org/z/8xoWGs9e4>`__): + + .. code:: c++ + + #include <fmt/std.h> + #include <vector> + + int main() { + try { + std::vector<bool>().at(0); + } catch(const std::exception& e) { + fmt::print("{}", e); + } + } + + prints:: + + vector<bool>::_M_range_check: __n (which is 0) >= this->size() (which is 0) + + on libstdc++. + Thanks `@zach2good (Zach Toogood) <https://github.com/zach2good>`_ and + `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Moved ``std::error_code`` formatter from ``fmt/os.h`` to ``fmt/std.h``. + (`#3125 <https://github.com/fmtlib/fmt/pull/3125>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Added formatters for standard container adapters: ``std::priority_queue``, + ``std::queue`` and ``std::stack`` + (`#3215 <https://github.com/fmtlib/fmt/issues/3215>`_, + `#3279 <https://github.com/fmtlib/fmt/pull/3279>`_). + For example (`godbolt <https://godbolt.org/z/74h1xY9qK>`__): + + .. code:: c++ + + #include <fmt/ranges.h> + #include <stack> + #include <vector> + + int main() { + auto s = std::stack<bool, std::vector<bool>>(); + for (auto b: {true, false, true}) s.push(b); + fmt::print("{}\n", s); // prints [true, false, true] + } + + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Added a formatter for ``std::optional`` to ``fmt/std.h``. + Thanks `@tom-huntington <https://github.com/tom-huntington>`_. + +* Fixed formatting of valueless by exception variants + (`#3347 <https://github.com/fmtlib/fmt/pull/3347>`_). + Thanks `@TheOmegaCarrot <https://github.com/TheOmegaCarrot>`_. + +* Made ``fmt::ptr`` accept ``unique_ptr`` with a custom deleter + (`#3177 <https://github.com/fmtlib/fmt/pull/3177>`_). + Thanks `@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_. + +* Fixed formatting of noncopyable ranges and nested ranges of chars + (`#3158 <https://github.com/fmtlib/fmt/pull/3158>`_ + `#3286 <https://github.com/fmtlib/fmt/issues/3286>`_, + `#3290 <https://github.com/fmtlib/fmt/pull/3290>`_). + Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Fixed issues with formatting of paths and ranges of paths + (`#3319 <https://github.com/fmtlib/fmt/issues/3319>`_, + `#3321 <https://github.com/fmtlib/fmt/pull/3321>`_ + `#3322 <https://github.com/fmtlib/fmt/issues/3322>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Improved handling of invalid Unicode in paths. + +* Enabled compile-time checks on Apple clang 14 and later + (`#3331 <https://github.com/fmtlib/fmt/pull/3331>`_). + Thanks `@cloyce (Cloyce D. Spradling) <https://github.com/cloyce>`_. + +* Improved compile-time checks of named arguments + (`#3105 <https://github.com/fmtlib/fmt/issues/3105>`_, + `#3214 <https://github.com/fmtlib/fmt/pull/3214>`_). + Thanks `@rbrich (Radek Brich) <https://github.com/rbrich>`_. + +* Fixed formatting when both alignment and ``0`` are given + (`#3236 <https://github.com/fmtlib/fmt/issues/3236>`_, + `#3248 <https://github.com/fmtlib/fmt/pull/3248>`_). + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Improved Unicode support in the experimental file API on Windows + (`#3234 <https://github.com/fmtlib/fmt/issues/3234>`_, + `#3293 <https://github.com/fmtlib/fmt/pull/3293>`_). + Thanks `@Fros1er (Froster) <https://github.com/Fros1er>`_. + +* Unified UTF transcoding + (`#3416 <https://github.com/fmtlib/fmt/pull/3416>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Added support for UTF-8 digit separators via an experimental locale facet + (`#1861 <https://github.com/fmtlib/fmt/issues/1861>`_). + For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__): + + .. code:: c++ + + auto loc = std::locale( + std::locale(), new fmt::format_facet<std::locale>("’")); + auto s = fmt::format(loc, "{:L}", 1000); + + where ``’`` is U+2019 used as a digit separator in the de_CH locale. + +* Added an overload of ``formatted_size`` that takes a locale + (`#3084 <https://github.com/fmtlib/fmt/issues/3084>`_, + `#3087 <https://github.com/fmtlib/fmt/pull/3087>`_). + Thanks `@gerboengels <https://github.com/gerboengels>`_. + +* Removed the deprecated ``FMT_DEPRECATED_OSTREAM``. + +* Fixed a UB when using a null ``std::string_view`` with ``fmt::to_string`` + or format string compilation + (`#3241 <https://github.com/fmtlib/fmt/issues/3241>`_, + `#3244 <https://github.com/fmtlib/fmt/pull/3244>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Added ``starts_with`` to the fallback ``string_view`` implementation + (`#3080 <https://github.com/fmtlib/fmt/pull/3080>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Added ``fmt::basic_format_string::get()`` for compatibility with + ``basic_format_string`` (`#3111 <https://github.com/fmtlib/fmt/pull/3111>`_). + Thanks `@huangqinjin <https://github.com/huangqinjin>`_. + +* Added ``println`` for compatibility with C++23 + (`#3267 <https://github.com/fmtlib/fmt/pull/3267>`_). + Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_. + +* Renamed the ``FMT_EXPORT`` macro for shared library usage to + ``FMT_LIB_EXPORT``. + +* Improved documentation + (`#3108 <https://github.com/fmtlib/fmt/issues/3108>`_, + `#3169 <https://github.com/fmtlib/fmt/issues/3169>`_, + `#3243 <https://github.com/fmtlib/fmt/pull/3243>`_). + `#3404 <https://github.com/fmtlib/fmt/pull/3404>`_). + Thanks `@Cleroth <https://github.com/Cleroth>`_ and + `@Vertexwahn <https://github.com/Vertexwahn>`_. + +* Improved build configuration and tests + (`#3118 <https://github.com/fmtlib/fmt/pull/3118>`_, + `#3120 <https://github.com/fmtlib/fmt/pull/3120>`_, + `#3188 <https://github.com/fmtlib/fmt/pull/3188>`_, + `#3189 <https://github.com/fmtlib/fmt/issues/3189>`_, + `#3198 <https://github.com/fmtlib/fmt/pull/3198>`_, + `#3205 <https://github.com/fmtlib/fmt/pull/3205>`_, + `#3207 <https://github.com/fmtlib/fmt/pull/3207>`_, + `#3210 <https://github.com/fmtlib/fmt/pull/3210>`_, + `#3240 <https://github.com/fmtlib/fmt/pull/3240>`_, + `#3256 <https://github.com/fmtlib/fmt/pull/3256>`_, + `#3264 <https://github.com/fmtlib/fmt/pull/3264>`_, + `#3299 <https://github.com/fmtlib/fmt/issues/3299>`_, + `#3302 <https://github.com/fmtlib/fmt/pull/3302>`_, + `#3312 <https://github.com/fmtlib/fmt/pull/3312>`_, + `#3317 <https://github.com/fmtlib/fmt/issues/3317>`_, + `#3328 <https://github.com/fmtlib/fmt/pull/3328>`_, + `#3333 <https://github.com/fmtlib/fmt/pull/3333>`_, + `#3369 <https://github.com/fmtlib/fmt/pull/3369>`_, + `#3373 <https://github.com/fmtlib/fmt/issues/3373>`_, + `#3395 <https://github.com/fmtlib/fmt/pull/3395>`_, + `#3406 <https://github.com/fmtlib/fmt/pull/3406>`_, + `#3411 <https://github.com/fmtlib/fmt/pull/3411>`_). + Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_, + `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_, + `@DavidKorczynski <https://github.com/DavidKorczynski>`_, + `@ChrisThrasher (Chris Thrasher) <https://github.com/ChrisThrasher>`_, + `@FrancoisCarouge (François Carouge) <https://github.com/FrancoisCarouge>`_, + `@kennyweiss (Kenny Weiss) <https://github.com/kennyweiss>`_, + `@luzpaz <https://github.com/luzpaz>`_, + `@codeinred (Alecto Irene Perez) <https://github.com/codeinred>`_, + `@Mixaill (Mikhail Paulyshka) <https://github.com/Mixaill>`_, + `@joycebrum (Joyce) <https://github.com/joycebrum>`_, + `@kevinhwang (Kevin Hwang) <https://github.com/kevinhwang>`_, + `@Vertexwahn <https://github.com/Vertexwahn>`_. + +* Fixed a regression in handling empty format specifiers after a colon (``{:}``) + (`#3086 <https://github.com/fmtlib/fmt/pull/3086>`_). + Thanks `@oxidase (Michael Krasnyk) <https://github.com/oxidase>`_. + +* Worked around a broken implementation of ``std::is_constant_evaluated`` in + some versions of libstdc++ on clang + (`#3247 <https://github.com/fmtlib/fmt/issues/3247>`_, + `#3281 <https://github.com/fmtlib/fmt/pull/3281>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Fixed formatting of volatile variables + (`#3068 <https://github.com/fmtlib/fmt/pull/3068>`_). + +* Fixed various warnings and compilation issues + (`#3057 <https://github.com/fmtlib/fmt/pull/3057>`_, + `#3066 <https://github.com/fmtlib/fmt/pull/3066>`_, + `#3072 <https://github.com/fmtlib/fmt/pull/3072>`_, + `#3082 <https://github.com/fmtlib/fmt/pull/3082>`_, + `#3091 <https://github.com/fmtlib/fmt/pull/3091>`_, + `#3092 <https://github.com/fmtlib/fmt/issues/3092>`_, + `#3093 <https://github.com/fmtlib/fmt/pull/3093>`_, + `#3095 <https://github.com/fmtlib/fmt/pull/3095>`_, + `#3096 <https://github.com/fmtlib/fmt/issues/3096>`_, + `#3097 <https://github.com/fmtlib/fmt/pull/3097>`_, + `#3128 <https://github.com/fmtlib/fmt/issues/3128>`_, + `#3129 <https://github.com/fmtlib/fmt/pull/3129>`_, + `#3137 <https://github.com/fmtlib/fmt/pull/3137>`_, + `#3139 <https://github.com/fmtlib/fmt/pull/3139>`_, + `#3140 <https://github.com/fmtlib/fmt/issues/3140>`_, + `#3142 <https://github.com/fmtlib/fmt/pull/3142>`_, + `#3149 <https://github.com/fmtlib/fmt/issues/3149>`_, + `#3150 <https://github.com/fmtlib/fmt/pull/3150>`_, + `#3154 <https://github.com/fmtlib/fmt/issues/3154>`_, + `#3163 <https://github.com/fmtlib/fmt/issues/3163>`_, + `#3178 <https://github.com/fmtlib/fmt/issues/3178>`_, + `#3184 <https://github.com/fmtlib/fmt/pull/3184>`_, + `#3196 <https://github.com/fmtlib/fmt/pull/3196>`_, + `#3204 <https://github.com/fmtlib/fmt/issues/3204>`_, + `#3206 <https://github.com/fmtlib/fmt/pull/3206>`_, + `#3208 <https://github.com/fmtlib/fmt/pull/3208>`_, + `#3213 <https://github.com/fmtlib/fmt/issues/3213>`_, + `#3216 <https://github.com/fmtlib/fmt/pull/3216>`_, + `#3224 <https://github.com/fmtlib/fmt/issues/3224>`_, + `#3226 <https://github.com/fmtlib/fmt/issues/3226>`_, + `#3228 <https://github.com/fmtlib/fmt/issues/3228>`_, + `#3229 <https://github.com/fmtlib/fmt/pull/3229>`_, + `#3259 <https://github.com/fmtlib/fmt/pull/3259>`_, + `#3274 <https://github.com/fmtlib/fmt/issues/3274>`_, + `#3287 <https://github.com/fmtlib/fmt/issues/3287>`_, + `#3288 <https://github.com/fmtlib/fmt/pull/3288>`_, + `#3292 <https://github.com/fmtlib/fmt/issues/3292>`_, + `#3295 <https://github.com/fmtlib/fmt/pull/3295>`_, + `#3296 <https://github.com/fmtlib/fmt/pull/3296>`_, + `#3298 <https://github.com/fmtlib/fmt/issues/3298>`_, + `#3325 <https://github.com/fmtlib/fmt/issues/3325>`_, + `#3326 <https://github.com/fmtlib/fmt/pull/3326>`_, + `#3334 <https://github.com/fmtlib/fmt/issues/3334>`_, + `#3342 <https://github.com/fmtlib/fmt/issues/3342>`_, + `#3343 <https://github.com/fmtlib/fmt/pull/3343>`_, + `#3351 <https://github.com/fmtlib/fmt/issues/3351>`_, + `#3352 <https://github.com/fmtlib/fmt/pull/3352>`_, + `#3362 <https://github.com/fmtlib/fmt/pull/3362>`_, + `#3365 <https://github.com/fmtlib/fmt/issues/3365>`_, + `#3366 <https://github.com/fmtlib/fmt/pull/3366>`_, + `#3374 <https://github.com/fmtlib/fmt/pull/3374>`_, + `#3377 <https://github.com/fmtlib/fmt/issues/3377>`_, + `#3378 <https://github.com/fmtlib/fmt/pull/3378>`_, + `#3381 <https://github.com/fmtlib/fmt/issues/3381>`_, + `#3398 <https://github.com/fmtlib/fmt/pull/3398>`_, + `#3413 <https://github.com/fmtlib/fmt/pull/3413>`_, + `#3415 <https://github.com/fmtlib/fmt/issues/3415>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_, + `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_, + `@NewbieOrange <https://github.com/NewbieOrange>`_, + `@EngineLessCC (VivyaCC) <https://github.com/EngineLessCC>`_, + `@asmaloney (Andy Maloney) <https://github.com/asmaloney>`_, + `@HazardyKnusperkeks (Björn Schäpers) + <https://github.com/HazardyKnusperkeks>`_, + `@sergiud (Sergiu Deitsch) <https://github.com/sergiud>`_, + `@Youw (Ihor Dutchak) <https://github.com/Youw>`_, + `@thesmurph <https://github.com/thesmurph>`_, + `@czudziakm (Maksymilian Czudziak) <https://github.com/czudziakm>`_, + `@Roman-Koshelev <https://github.com/Roman-Koshelev>`_, + `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_, + `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_, + `@russelltg (Russell Greene) <https://github.com/russelltg>`_, + `@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_, + `@tmartin-gh <https://github.com/tmartin-gh>`_, + `@Zhaojun-Liu (June Liu) <https://github.com/Zhaojun-Liu>`_, + `@louiswins (Louis Wilson) <https://github.com/louiswins>`_, + `@mogemimi <https://github.com/mogemimi>`_. + +9.1.0 - 2022-08-27 +------------------ + +* ``fmt::formatted_size`` now works at compile time + (`#3026 <https://github.com/fmtlib/fmt/pull/3026>`_). For example + (`godbolt <https://godbolt.org/z/1MW5rMdf8>`__): + + .. code:: c++ + + #include <fmt/compile.h> + + int main() { + using namespace fmt::literals; + constexpr size_t n = fmt::formatted_size("{}"_cf, 42); + fmt::print("{}\n", n); // prints 2 + } + + Thanks `@marksantaniello (Mark Santaniello) + <https://github.com/marksantaniello>`_. + +* Fixed handling of invalid UTF-8 + (`#3038 <https://github.com/fmtlib/fmt/pull/3038>`_, + `#3044 <https://github.com/fmtlib/fmt/pull/3044>`_, + `#3056 <https://github.com/fmtlib/fmt/pull/3056>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and + `@skeeto (Christopher Wellons) <https://github.com/skeeto>`_. + +* Improved Unicode support in ``ostream`` overloads of ``print`` + (`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_, + `#3001 <https://github.com/fmtlib/fmt/pull/3001>`_, + `#3025 <https://github.com/fmtlib/fmt/pull/3025>`_). + Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_. + +* Fixed handling of the sign specifier in localized formatting on systems with + 32-bit ``wchar_t`` (`#3041 <https://github.com/fmtlib/fmt/issues/3041>`_). + +* Added support for wide streams to ``fmt::streamed`` + (`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Added the ``n`` specifier that disables the output of delimiters when + formatting ranges (`#2981 <https://github.com/fmtlib/fmt/pull/2981>`_, + `#2983 <https://github.com/fmtlib/fmt/pull/2983>`_). + For example (`godbolt <https://godbolt.org/z/roKqGdj8c>`__): + + .. code:: c++ + + #include <fmt/ranges.h> + #include <vector> + + int main() { + auto v = std::vector{1, 2, 3}; + fmt::print("{:n}\n", v); // prints 1, 2, 3 + } + + Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_. + +* Worked around problematic ``std::string_view`` constructors introduced in + C++23 (`#3030 <https://github.com/fmtlib/fmt/issues/3030>`_, + `#3050 <https://github.com/fmtlib/fmt/issues/3050>`_). + Thanks `@strega-nil-ms (nicole mazzuca) <https://github.com/strega-nil-ms>`_. + +* Improve handling (exclusion) of recursive ranges + (`#2968 <https://github.com/fmtlib/fmt/issues/2968>`_, + `#2974 <https://github.com/fmtlib/fmt/pull/2974>`_). + Thanks `@Dani-Hub (Daniel Krügler) <https://github.com/Dani-Hub>`_. + +* Improved error reporting in format string compilation + (`#3055 <https://github.com/fmtlib/fmt/issues/3055>`_). + +* Improved the implementation of + `Dragonbox <https://github.com/jk-jeon/dragonbox>`_, the algorithm used for + the default floating-point formatting + (`#2984 <https://github.com/fmtlib/fmt/pull/2984>`_). + Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_. + +* Fixed issues with floating-point formatting on exotic platforms. + +* Improved the implementation of chrono formatting + (`#3010 <https://github.com/fmtlib/fmt/pull/3010>`_). + Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + +* Improved documentation + (`#2966 <https://github.com/fmtlib/fmt/pull/2966>`_, + `#3009 <https://github.com/fmtlib/fmt/pull/3009>`_, + `#3020 <https://github.com/fmtlib/fmt/issues/3020>`_, + `#3037 <https://github.com/fmtlib/fmt/pull/3037>`_). + Thanks `@mwinterb <https://github.com/mwinterb>`_, + `@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_ + and `@remiburtin (Rémi Burtin) <https://github.com/remiburtin>`_. + +* Improved build configuration + (`#2991 <https://github.com/fmtlib/fmt/pull/2991>`_, + `#2995 <https://github.com/fmtlib/fmt/pull/2995>`_, + `#3004 <https://github.com/fmtlib/fmt/issues/3004>`_, + `#3007 <https://github.com/fmtlib/fmt/pull/3007>`_, + `#3040 <https://github.com/fmtlib/fmt/pull/3040>`_). + Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_ and + `@hwhsu1231 (Haowei Hsu) <https://github.com/hwhsu1231>`_. + +* Fixed various warnings and compilation issues + (`#2969 <https://github.com/fmtlib/fmt/issues/2969>`_, + `#2971 <https://github.com/fmtlib/fmt/pull/2971>`_, + `#2975 <https://github.com/fmtlib/fmt/issues/2975>`_, + `#2982 <https://github.com/fmtlib/fmt/pull/2982>`_, + `#2985 <https://github.com/fmtlib/fmt/pull/2985>`_, + `#2988 <https://github.com/fmtlib/fmt/issues/2988>`_, + `#2989 <https://github.com/fmtlib/fmt/issues/2989>`_, + `#3000 <https://github.com/fmtlib/fmt/issues/3000>`_, + `#3006 <https://github.com/fmtlib/fmt/issues/3006>`_, + `#3014 <https://github.com/fmtlib/fmt/issues/3014>`_, + `#3015 <https://github.com/fmtlib/fmt/issues/3015>`_, + `#3021 <https://github.com/fmtlib/fmt/pull/3021>`_, + `#3023 <https://github.com/fmtlib/fmt/issues/3023>`_, + `#3024 <https://github.com/fmtlib/fmt/pull/3024>`_, + `#3029 <https://github.com/fmtlib/fmt/pull/3029>`_, + `#3043 <https://github.com/fmtlib/fmt/pull/3043>`_, + `#3052 <https://github.com/fmtlib/fmt/issues/3052>`_, + `#3053 <https://github.com/fmtlib/fmt/pull/3053>`_, + `#3054 <https://github.com/fmtlib/fmt/pull/3054>`_). + Thanks `@h-friederich (Hannes Friederich) <https://github.com/h-friederich>`_, + `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_, + `@olupton (Olli Lupton) <https://github.com/olupton>`_, + `@bernhardmgruber (Bernhard Manfred Gruber) + <https://github.com/bernhardmgruber>`_, + `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_. + 9.0.0 - 2022-07-04 ------------------ @@ -19,7 +812,7 @@ return result; } - constexpr auto answer = compile_time_itoa(0.42); + constexpr auto answer = compile_time_dtoa(0.42); works with the default settings. @@ -2134,7 +2927,7 @@ <https://github.com/kwesolowski>`_. * Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ`` - macro to prevent interferring with fuzzing of projects using {fmt} + macro to prevent interfering with fuzzing of projects using {fmt} (`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_). Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_. diff --git a/externals/fmt/LICENSE.rst b/externals/fmt/LICENSE.rst index f0ec3db4..1cd1ef92 100644 --- a/externals/fmt/LICENSE.rst +++ b/externals/fmt/LICENSE.rst @@ -1,4 +1,4 @@ -Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/externals/fmt/README.rst b/externals/fmt/README.rst index 4a3addf0..44bb6868 100644 --- a/externals/fmt/README.rst +++ b/externals/fmt/README.rst @@ -12,9 +12,6 @@ .. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows -.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true - :target: https://ci.appveyor.com/project/vitaut/fmt - .. 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?\ @@ -25,6 +22,9 @@ :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. @@ -50,7 +50,9 @@ Features * `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 + 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 @@ -67,7 +69,7 @@ Features <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 +* 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, @@ -77,7 +79,7 @@ Features 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 +* Locale independence by default * Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro See the `documentation <https://fmt.dev>`_ for more details. @@ -194,24 +196,24 @@ Speed tests ================= ============= =========== Library Method Run Time, s ================= ============= =========== -libc printf 1.04 -libc++ std::ostream 3.05 -{fmt} 6.1.1 fmt::print 0.75 -Boost Format 1.67 boost::format 7.24 -Folly Format folly::format 2.23 +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, ~35% faster than ``printf``. +{fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``. The above results were generated by building ``tinyformat_test.cpp`` on macOS -10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the +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 -floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_) +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>`_: @@ -227,7 +229,7 @@ The script `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 +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. @@ -248,7 +250,7 @@ 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 extra ``<string>`` +``printf+string`` is the same as ``printf`` but with an extra ``<string>`` include to measure the overhead of the latter. **Non-optimized build** @@ -264,14 +266,14 @@ 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 +``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 the instructions on how to build +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 @@ -296,9 +298,12 @@ or the bloat test:: Migrating code -------------- -`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang -tidy checks for converting occurrences of ``printf`` and ``fprintf`` to -``fmt::print``. +`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 --------------------------- @@ -306,8 +311,6 @@ Projects using this library * `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform real-time strategy game -* `2GIS <https://2gis.ru/>`_: free business listings with a city map - * `AMPL/MP <https://github.com/ampl/mp>`_: an open-source library for mathematical programming @@ -325,8 +328,10 @@ Projects using this library * `ccache <https://ccache.dev/>`_: a compiler cache -* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database +* `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 @@ -363,6 +368,10 @@ Projects using this library * `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 @@ -392,7 +401,7 @@ Projects using this library proxy * `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement - for mission critical systems written in C++ + for mission-critical systems written in C++ * `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client library @@ -416,6 +425,9 @@ Projects using this library * `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 @@ -473,7 +485,7 @@ error handling is awkward. Boost Format ~~~~~~~~~~~~ -This is a very powerful library which supports both ``printf``-like 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 @@ -482,7 +494,7 @@ Format also has excessive build times and severe code bloat issues (see FastFormat ~~~~~~~~~~ -This is an interesting library which is fast, safe and has positional arguments. +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 @@ -498,7 +510,7 @@ restrictive for using it in some projects. Boost Spirit.Karma ~~~~~~~~~~~~~~~~~~ -This is not really a formatting library but I decided to include it here for +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, @@ -517,7 +529,7 @@ 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 +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}. @@ -526,8 +538,7 @@ Maintainers ----------- The {fmt} library is maintained by Victor Zverovich (`vitaut -<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan -<https://github.com/foonathan>`_) with contributions from many other people. +<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 diff --git a/externals/fmt/doc/CMakeLists.txt b/externals/fmt/doc/CMakeLists.txt index 06848450..fd9f4d1e 100644 --- a/externals/fmt/doc/CMakeLists.txt +++ b/externals/fmt/doc/CMakeLists.txt @@ -1,10 +1,19 @@ -find_program(DOXYGEN doxygen) +find_program(DOXYGEN doxygen + PATHS "$ENV{ProgramFiles}/doxygen/bin" + "$ENV{ProgramFiles\(x86\)}/doxygen/bin") if (NOT DOXYGEN) message(STATUS "Target 'doc' disabled (requires doxygen)") return () endif () -find_package(PythonInterp QUIET REQUIRED) +# Find the Python interpreter and set the PYTHON_EXECUTABLE variable. +if (CMAKE_VERSION VERSION_LESS 3.12) + # This logic is deprecated in CMake after 3.12. + find_package(PythonInterp QUIET REQUIRED) +else () + find_package(Python QUIET REQUIRED) + set(PYTHON_EXECUTABLE ${Python_EXECUTABLE}) +endif () add_custom_target(doc COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py diff --git a/externals/fmt/doc/api.rst b/externals/fmt/doc/api.rst index f5bc6526..0b3d0231 100644 --- a/externals/fmt/doc/api.rst +++ b/externals/fmt/doc/api.rst @@ -7,7 +7,7 @@ API Reference The {fmt} library API consists of the following parts: * :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions - for ``char``/UTF-8 with compile-time checks and minimal dependencies + for ``char``/UTF-8 with C++20 compile-time checks and minimal dependencies * :ref:`fmt/format.h <format-api>`: the full format API providing additional formatting functions and locale support * :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples @@ -17,6 +17,7 @@ The {fmt} library API consists of the following parts: * :ref:`fmt/color.h <color-api>`: terminal color and text style * :ref:`fmt/os.h <os-api>`: system APIs * :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support +* :ref:`fmt/args.h <args-api>`: dynamic argument lists * :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting * :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support @@ -28,10 +29,11 @@ macros have prefix ``FMT_``. Core API ======== -``fmt/core.h`` defines the core API which provides main formatting functions for -``char``/UTF-8 with compile-time checks. It has minimal include dependencies for -better compile times. This header is only beneficial when using {fmt} as a -library and not in the header-only mode. +``fmt/core.h`` defines the core API which provides main formatting functions +for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include +dependencies for better compile times. This header is only beneficial when +using {fmt} as a library (the default) and not in the header-only mode. +It also provides ``formatter`` specializations for built-in and string types. The following functions use :ref:`format string syntax <syntax>` similar to that of Python's `str.format @@ -70,113 +72,18 @@ checked at compile time in C++20. To pass a runtime format string wrap it in Compile-Time Format String Checks --------------------------------- -Compile-time checks are enabled when using ``FMT_STRING``. They support built-in -and string types as well as user-defined types with ``constexpr`` ``parse`` -functions in their ``formatter`` specializations. -Requires C++14 and is a no-op in C++11. +Compile-time format string checks are enabled by default on compilers +that support C++20 ``consteval``. On older compilers you can use the +:ref:`FMT_STRING <legacy-checks>`: macro defined in ``fmt/format.h`` instead. -.. doxygendefine:: FMT_STRING - -To force the use of compile-time checks, define the preprocessor variable -``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING`` -will fail to compile with regular strings. Runtime-checked -formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc. +Unused arguments are allowed as in Python's `str.format` and ordinary functions. .. doxygenclass:: fmt::basic_format_string :members: .. doxygentypedef:: fmt::format_string -.. doxygenfunction:: fmt::runtime(const S&) - -Named Arguments ---------------- - -.. doxygenfunction:: fmt::arg(const S&, const T&) - -Named arguments are not supported in compile-time checks at the moment. - -Argument Lists --------------- - -You can create your own formatting function with compile-time checks and small -binary footprint, for example (https://godbolt.org/z/oba4Mc): - -.. code:: c++ - - #include <fmt/format.h> - - void vlog(const char* file, int line, fmt::string_view format, - fmt::format_args args) { - fmt::print("{}: {}: ", file, line); - fmt::vprint(format, args); - } - - template <typename S, typename... Args> - void log(const char* file, int line, const S& format, Args&&... args) { - vlog(file, line, format, fmt::make_format_args(args...)); - } - - #define MY_LOG(format, ...) \ - log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) - - MY_LOG("invalid squishiness: {}", 42); - -Note that ``vlog`` is not parameterized on argument types which improves compile -times and reduces binary code size compared to a fully parameterized version. - -.. doxygenfunction:: fmt::make_format_args(const Args&...) - -.. doxygenclass:: fmt::format_arg_store - :members: - -.. doxygenclass:: fmt::dynamic_format_arg_store - :members: - -.. doxygenclass:: fmt::basic_format_args - :members: - -.. doxygentypedef:: fmt::format_args - -.. doxygenclass:: fmt::basic_format_arg - :members: - -.. doxygenclass:: fmt::basic_format_parse_context - :members: - -.. doxygenclass:: fmt::basic_format_context - :members: - -.. doxygentypedef:: fmt::format_context - -Compatibility -------------- - -.. doxygenclass:: fmt::basic_string_view - :members: - -.. doxygentypedef:: fmt::string_view - -Locale ------- - -All formatting is locale-independent by default. Use the ``'L'`` format -specifier to insert the appropriate number separator characters from the -locale:: - - #include <fmt/core.h> - #include <locale> - - std::locale::global(std::locale("en_US.UTF-8")); - auto s = fmt::format("{:L}", 1000000); // s == "1,000,000" - -.. _format-api: - -Format API -========== - -``fmt/format.h`` defines the full format API providing additional formatting -functions and locale support. +.. doxygenfunction:: fmt::runtime(string_view) -> runtime_format_string<> .. _udt: @@ -186,14 +93,39 @@ Formatting User-Defined Types The {fmt} library provides formatters for many standard C++ types. See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date -and time formatting and :ref:`fmt/std.h <std-api>` for path and variant -formatting. +and time formatting and :ref:`fmt/std.h <std-api>` for other standard library +types. + +There are two ways to make a user-defined type formattable: providing a +``format_as`` function or specializing the ``formatter`` struct template. -To make a user-defined type formattable, specialize the ``formatter<T>`` struct -template and implement ``parse`` and ``format`` methods:: +Use ``format_as`` if you want to make your type formattable as some other type +with the same format specifiers. The ``format_as`` function should take an +object of your type and return an object of a formattable type. It should be +defined in the same namespace as your type. + +Example (https://godbolt.org/z/r7vvGE1v7):: #include <fmt/format.h> + namespace kevin_namespacy { + enum class film { + house_of_cards, american_beauty, se7en = 7 + }; + auto format_as(film f) { return fmt::underlying(f); } + } + + int main() { + fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7" + } + +Using the specialization API is more complex but gives you full control over +parsing and formatting. To use this method specialize the ``formatter`` struct +template for your type and implement ``parse`` and ``format`` methods. +For example:: + + #include <fmt/core.h> + struct point { double x, y; }; @@ -203,7 +135,7 @@ template and implement ``parse`` and ``format`` methods:: char presentation = 'f'; // Parses format specifications of the form ['f' | 'e']. - constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator { // [ctx.begin(), ctx.end()) is a character range that contains a part of // the format string starting from the format specifications to be parsed, // e.g. in @@ -224,7 +156,7 @@ template and implement ``parse`` and ``format`` methods:: if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++; // Check if reached the end of the range: - if (it != end && *it != '}') throw format_error("invalid format"); + if (it != end && *it != '}') throw_format_error("invalid format"); // Return an iterator past the end of the parsed range: return it; @@ -232,8 +164,7 @@ template and implement ``parse`` and ``format`` methods:: // Formats the point p using the parsed format specification (presentation) // stored in this formatter. - template <typename FormatContext> - auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) { + auto format(const point& p, format_context& ctx) const -> format_context::iterator { // ctx.out() is an output iterator to write to. return presentation == 'f' ? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y) @@ -250,22 +181,33 @@ Then you can pass objects of type ``point`` to any formatting function:: You can also reuse existing formatters via inheritance or composition, for example:: + // color.h: + #include <fmt/core.h> + enum class color {red, green, blue}; template <> struct fmt::formatter<color>: formatter<string_view> { // parse is inherited from formatter<string_view>. - template <typename FormatContext> - auto format(color c, FormatContext& ctx) const { - string_view name = "unknown"; - switch (c) { - case color::red: name = "red"; break; - case color::green: name = "green"; break; - case color::blue: name = "blue"; break; - } - return formatter<string_view>::format(name, ctx); - } + + auto format(color c, format_context& ctx) const; }; + // color.cc: + #include "color.h" + #include <fmt/format.h> + + auto fmt::formatter<color>::format(color c, format_context& ctx) const { + string_view name = "unknown"; + switch (c) { + case color::red: name = "red"; break; + case color::green: name = "green"; break; + case color::blue: name = "blue"; break; + } + return formatter<string_view>::format(name, ctx); + } + +Note that ``formatter<string_view>::format`` is defined in ``fmt/format.h`` so +it has to be included in the source file. Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize all string format specifications, for example @@ -277,8 +219,9 @@ will return ``" blue"``. You can also write a formatter for a hierarchy of classes:: + // demo.h: #include <type_traits> - #include <fmt/format.h> + #include <fmt/core.h> struct A { virtual ~A() {} @@ -292,41 +235,106 @@ You can also write a formatter for a hierarchy of classes:: template <typename T> struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> : fmt::formatter<std::string> { - template <typename FormatCtx> - auto format(const A& a, FormatCtx& ctx) const { + auto format(const A& a, format_context& ctx) const { return fmt::formatter<std::string>::format(a.name(), ctx); } }; + // demo.cc: + #include "demo.h" + #include <fmt/format.h> + int main() { B b; A& a = b; fmt::print("{}", a); // prints "B" } -If a type provides both a ``formatter`` specialization and an implicit -conversion to a formattable type, the specialization takes precedence over the -conversion. +Providing both a ``formatter`` specialization and a ``format_as`` overload is +disallowed. -For enums {fmt} also provides the ``format_as`` extension API. To format an enum -via this API define ``format_as`` that takes this enum and converts it to the -underlying type. ``format_as`` should be defined in the same namespace as the -enum. +Named Arguments +--------------- -Example (https://godbolt.org/z/r7vvGE1v7):: +.. doxygenfunction:: fmt::arg(const S&, const T&) - #include <fmt/format.h> +Named arguments are not supported in compile-time checks at the moment. - namespace kevin_namespacy { - enum class film { - house_of_cards, american_beauty, se7en = 7 - }; - auto format_as(film f) { return fmt::underlying(f); } - } +Argument Lists +-------------- - int main() { - fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7" - } +You can create your own formatting function with compile-time checks and small +binary footprint, for example (https://godbolt.org/z/vajfWEG4b): + +.. code:: c++ + + #include <fmt/core.h> + + void vlog(const char* file, int line, fmt::string_view format, + fmt::format_args args) { + fmt::print("{}: {}: ", file, line); + fmt::vprint(format, args); + } + + template <typename... T> + void log(const char* file, int line, fmt::format_string<T...> format, T&&... args) { + vlog(file, line, format, fmt::make_format_args(args...)); + } + + #define MY_LOG(format, ...) log(__FILE__, __LINE__, format, __VA_ARGS__) + + MY_LOG("invalid squishiness: {}", 42); + +Note that ``vlog`` is not parameterized on argument types which improves compile +times and reduces binary code size compared to a fully parameterized version. + +.. doxygenfunction:: fmt::make_format_args(const Args&...) + +.. doxygenclass:: fmt::format_arg_store + :members: + +.. doxygenclass:: fmt::basic_format_args + :members: + +.. doxygentypedef:: fmt::format_args + +.. doxygenclass:: fmt::basic_format_arg + :members: + +.. doxygenclass:: fmt::basic_format_parse_context + :members: + +.. doxygenclass:: fmt::basic_format_context + :members: + +.. doxygentypedef:: fmt::format_context + +.. _args-api: + +Dynamic Argument Lists +---------------------- + +The header ``fmt/args.h`` provides ``dynamic_format_arg_store``, a builder-like +API that can be used to construct format argument lists dynamically. + +.. doxygenclass:: fmt::dynamic_format_arg_store + :members: + +Compatibility +------------- + +.. doxygenclass:: fmt::basic_string_view + :members: + +.. doxygentypedef:: fmt::string_view + +.. _format-api: + +Format API +========== + +``fmt/format.h`` defines the full format API providing additional formatting +functions and locale support. Literal-Based API ----------------- @@ -339,7 +347,7 @@ Utilities --------- .. doxygenfunction:: fmt::ptr(T p) -> const void* -.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void* +.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T, Deleter> &p) -> const void* .. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void* .. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type @@ -405,6 +413,41 @@ normally don't do any allocations for built-in and string types except for non-default floating-point formatting that occasionally falls back on ``sprintf``. +Locale +------ + +All formatting is locale-independent by default. Use the ``'L'`` format +specifier to insert the appropriate number separator characters from the +locale:: + + #include <fmt/core.h> + #include <locale> + + std::locale::global(std::locale("en_US.UTF-8")); + auto s = fmt::format("{:L}", 1000000); // s == "1,000,000" + +``fmt/format.h`` provides the following overloads of formatting functions that +take ``std::locale`` as a parameter. The locale type is a template parameter to +avoid the expensive ``<locale>`` include. + +.. doxygenfunction:: format(const Locale& loc, format_string<T...> fmt, T&&... args) -> std::string +.. doxygenfunction:: format_to(OutputIt out, const Locale& loc, format_string<T...> fmt, T&&... args) -> OutputIt +.. doxygenfunction:: formatted_size(const Locale& loc, format_string<T...> fmt, T&&... args) -> size_t + +.. _legacy-checks: + +Legacy Compile-Time Format String Checks +---------------------------------------- + +``FMT_STRING`` enables compile-time checks on older compilers. It requires C++14 +or later and is a no-op in C++11. + +.. doxygendefine:: FMT_STRING + +To force the use of legacy compile-time checks, define the preprocessor variable +``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING`` +will fail to compile with regular strings. + .. _ranges-api: Range and Tuple Formatting @@ -474,10 +517,11 @@ Standard Library Types Formatting ``fmt/std.h`` provides formatters for: -* `std::filesystem::path <std::filesystem::path>`_ +* `std::filesystem::path <https://en.cppreference.com/w/cpp/filesystem/path>`_ * `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_ * `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_ * `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_ +* `std::optional <https://en.cppreference.com/w/cpp/utility/optional>`_ Formatting Variants ------------------- @@ -505,10 +549,20 @@ Format String Compilation ``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into efficient formatting code at compile-time. This supports arguments of built-in -and string types as well as user-defined types with ``constexpr`` ``parse`` -functions in their ``formatter`` specializations. Format string compilation can -generate more binary code compared to the default API and is only recommended in -places where formatting is a performance bottleneck. +and string types as well as user-defined types with ``format`` functions taking +the format context type as a template parameter in their ``formatter`` +specializations. For example:: + + template <> struct fmt::formatter<point> { + constexpr auto parse(format_parse_context& ctx); + + template <typename FormatContext> + auto format(const point& p, FormatContext& ctx) const; + }; + +Format string compilation can generate more binary code compared to the default +API and is only recommended in places where formatting is a performance +bottleneck. .. doxygendefine:: FMT_COMPILE @@ -581,7 +635,7 @@ the POSIX extension for positional arguments. Unlike their standard counterparts, the ``fmt`` functions are type-safe and throw an exception if an argument type doesn't match its format specification. -.. doxygenfunction:: printf(const S &format_str, const T&... args) +.. doxygenfunction:: printf(string_view fmt, const T&... args) -> int .. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int diff --git a/externals/fmt/doc/build.py b/externals/fmt/doc/build.py index 1e37be30..4c4a3311 100755 --- a/externals/fmt/doc/build.py +++ b/externals/fmt/doc/build.py @@ -4,7 +4,7 @@ import errno, os, re, sys from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT -versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0'] +versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0', '10.0.0', '10.1.0', '10.1.1'] class Pip: def __init__(self, venv_dir): @@ -49,9 +49,9 @@ def build_docs(version='dev', **kwargs): GENERATE_MAN = NO GENERATE_RTF = NO CASE_SENSE_NAMES = NO - INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \ - {0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \ - {0}/xchar.h + 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}/printf.h {0}/xchar.h QUIET = YES JAVADOC_AUTOBRIEF = YES AUTOLINK_SUPPORT = NO @@ -83,8 +83,7 @@ def build_docs(version='dev', **kwargs): internal_symbols = [ 'fmt::detail::.*', 'basic_data<>', - 'fmt::type_identity', - 'fmt::dynamic_formatter' + 'fmt::type_identity' ] noisy_warnings = [ 'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \ diff --git a/externals/fmt/doc/index.rst b/externals/fmt/doc/index.rst index d5c4fa5f..8d55c7a1 100644 --- a/externals/fmt/doc/index.rst +++ b/externals/fmt/doc/index.rst @@ -39,7 +39,7 @@ The ``fmt::format`` function returns a string "The answer is 42.". You can use .. code:: c++ auto out = fmt::memory_buffer(); - format_to(std::back_inserter(out), + fmt::format_to(std::back_inserter(out), "For a moment, {} happened.", "nothing"); auto data = out.data(); // pointer to the formatted data auto size = out.size(); // size of the formatted data diff --git a/externals/fmt/doc/syntax.rst b/externals/fmt/doc/syntax.rst index 77d8035e..74b64c5a 100644 --- a/externals/fmt/doc/syntax.rst +++ b/externals/fmt/doc/syntax.rst @@ -109,8 +109,8 @@ Note that unless a minimum field width is defined, the field width will always be the same size as the data to fill it, so that the alignment option has no meaning in this case. -The *sign* option is only valid for number types, and can be one of the -following: +The *sign* option is only valid for floating point and signed integer types, +and can be one of the following: +---------+------------------------------------------------------------+ | Option | Meaning | @@ -304,8 +304,8 @@ The available presentation types for pointers are: Chrono Format Specifications ============================ -Format specifications for chrono types and ``std::tm`` have the following -syntax: +Format specifications for chrono duration and time point types as well as +``std::tm`` have the following syntax: .. productionlist:: sf chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`] @@ -321,20 +321,89 @@ syntax: Literal chars are copied unchanged to the output. Precision is valid only for ``std::chrono::duration`` types with a floating-point representation type. -The available presentation types (*chrono_type*) for chrono durations and time -points are: +The available presentation types (*chrono_type*) are: +---------+--------------------------------------------------------------------+ | Type | Meaning | +=========+====================================================================+ +| ``'a'`` | The abbreviated weekday name, e.g. "Sat". If the value does not | +| | contain a valid weekday, an exception of type ``format_error`` is | +| | thrown. | ++---------+--------------------------------------------------------------------+ +| ``'A'`` | The full weekday name, e.g. "Saturday". If the value does not | +| | contain a valid weekday, an exception of type ``format_error`` is | +| | thrown. | ++---------+--------------------------------------------------------------------+ +| ``'b'`` | The abbreviated month name, e.g. "Nov". If the value does not | +| | contain a valid month, an exception of type ``format_error`` is | +| | thrown. | ++---------+--------------------------------------------------------------------+ +| ``'B'`` | The full month name, e.g. "November". If the value does not | +| | contain a valid month, an exception of type ``format_error`` is | +| | thrown. | ++---------+--------------------------------------------------------------------+ +| ``'c'`` | The date and time representation, e.g. "Sat Nov 12 22:04:00 1955". | +| | The modified command ``%Ec`` produces the locale's alternate date | +| | and time representation. | ++---------+--------------------------------------------------------------------+ +| ``'C'`` | The year divided by 100 using floored division, e.g. "55". If the | +| | result is a single decimal digit, it is prefixed with 0. | +| | The modified command ``%EC`` produces the locale's alternative | +| | representation of the century. | ++---------+--------------------------------------------------------------------+ +| ``'d'`` | The day of month as a decimal number. If the result is a single | +| | decimal digit, it is prefixed with 0. The modified command ``%Od`` | +| | produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'D'`` | Equivalent to ``%m/%d/%y``, e.g. "11/12/55". | ++---------+--------------------------------------------------------------------+ +| ``'e'`` | The day of month as a decimal number. If the result is a single | +| | decimal digit, it is prefixed with a space. The modified command | +| | ``%Oe`` produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'F'`` | Equivalent to ``%Y-%m-%d``, e.g. "1955-11-12". | ++---------+--------------------------------------------------------------------+ +| ``'g'`` | The last two decimal digits of the ISO week-based year. If the | +| | result is a single digit it is prefixed by 0. | ++---------+--------------------------------------------------------------------+ +| ``'G'`` | The ISO week-based year as a decimal number. If the result is less | +| | than four digits it is left-padded with 0 to four digits. | ++---------+--------------------------------------------------------------------+ +| ``'h'`` | Equivalent to ``%b``, e.g. "Nov". | ++---------+--------------------------------------------------------------------+ | ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a | | | single digit, it is prefixed with 0. The modified command ``%OH`` | | | produces the locale's alternative representation. | +---------+--------------------------------------------------------------------+ +| ``'I'`` | The hour (12-hour clock) as a decimal number. If the result is a | +| | single digit, it is prefixed with 0. The modified command ``%OI`` | +| | produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'j'`` | If the type being formatted is a specialization of duration, the | +| | decimal number of days without padding. Otherwise, the day of the | +| | year as a decimal number. Jan 1 is 001. If the result is less than | +| | three digits, it is left-padded with 0 to three digits. | ++---------+--------------------------------------------------------------------+ +| ``'m'`` | The month as a decimal number. Jan is 01. If the result is a | +| | single digit, it is prefixed with 0. The modified command ``%Om`` | +| | produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ | ``'M'`` | The minute as a decimal number. If the result is a single digit, | | | it is prefixed with 0. The modified command ``%OM`` produces the | | | locale's alternative representation. | +---------+--------------------------------------------------------------------+ +| ``'n'`` | A new-line character. | ++---------+--------------------------------------------------------------------+ +| ``'p'`` | The AM/PM designations associated with a 12-hour clock. | ++---------+--------------------------------------------------------------------+ +| ``'q'`` | The duration's unit suffix. | ++---------+--------------------------------------------------------------------+ +| ``'Q'`` | The duration's numeric value (as if extracted via ``.count()``). | ++---------+--------------------------------------------------------------------+ +| ``'r'`` | The 12-hour clock time, e.g. "10:04:00 PM". | ++---------+--------------------------------------------------------------------+ +| ``'R'`` | Equivalent to ``%H:%M``, e.g. "22:04". | ++---------+--------------------------------------------------------------------+ | ``'S'`` | Seconds as a decimal number. If the number of seconds is less than | | | 10, the result is prefixed with 0. If the precision of the input | | | cannot be exactly represented with seconds, then the format is a | @@ -345,9 +414,66 @@ points are: | | decimal point is localized according to the locale. The modified | | | command ``%OS`` produces the locale's alternative representation. | +---------+--------------------------------------------------------------------+ +| ``'t'`` | A horizontal-tab character. | ++---------+--------------------------------------------------------------------+ +| ``'T'`` | Equivalent to ``%H:%M:%S``. | ++---------+--------------------------------------------------------------------+ +| ``'u'`` | The ISO weekday as a decimal number (1-7), where Monday is 1. The | +| | modified command ``%Ou`` produces the locale's alternative | +| | representation. | ++---------+--------------------------------------------------------------------+ +| ``'U'`` | The week number of the year as a decimal number. The first Sunday | +| | of the year is the first day of week 01. Days of the same year | +| | prior to that are in week 00. If the result is a single digit, it | +| | is prefixed with 0. The modified command ``%OU`` produces the | +| | locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'V'`` | The ISO week-based week number as a decimal number. If the result | +| | is a single digit, it is prefixed with 0. The modified command | +| | ``%OV`` produces the locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'w'`` | The weekday as a decimal number (0-6), where Sunday is 0. | +| | The modified command ``%Ow`` produces the locale's alternative | +| | representation. | ++---------+--------------------------------------------------------------------+ +| ``'W'`` | The week number of the year as a decimal number. The first Monday | +| | of the year is the first day of week 01. Days of the same year | +| | prior to that are in week 00. If the result is a single digit, it | +| | is prefixed with 0. The modified command ``%OW`` produces the | +| | locale's alternative representation. | ++---------+--------------------------------------------------------------------+ +| ``'x'`` | The date representation, e.g. "11/12/55". The modified command | +| | ``%Ex`` produces the locale's alternate date representation. | ++---------+--------------------------------------------------------------------+ +| ``'X'`` | The time representation, e.g. "10:04:00". The modified command | +| | ``%EX`` produces the locale's alternate time representation. | ++---------+--------------------------------------------------------------------+ +| ``'y'`` | The last two decimal digits of the year. If the result is a single | +| | digit it is prefixed by 0. The modified command ``%Oy`` produces | +| | the locale's alternative representation. The modified command | +| | ``%Ey`` produces the locale's alternative representation of offset | +| | from ``%EC`` (year only). | ++---------+--------------------------------------------------------------------+ +| ``'Y'`` | The year as a decimal number. If the result is less than four | +| | digits it is left-padded with 0 to four digits. The modified | +| | command ``%EY`` produces the locale's alternative full year | +| | representation. | ++---------+--------------------------------------------------------------------+ +| ``'z'`` | The offset from UTC in the ISO 8601:2004 format. For example -0430 | +| | refers to 4 hours 30 minutes behind UTC. If the offset is zero, | +| | +0000 is used. The modified commands ``%Ez`` and ``%Oz`` insert a | +| | ``:`` between the hours and minutes: -04:30. If the offset | +| | information is not available, an exception of type | +| | ``format_error`` is thrown. | ++---------+--------------------------------------------------------------------+ +| ``'Z'`` | The time zone abbreviation. If the time zone abbreviation is not | +| | available, an exception of type ``format_error`` is thrown. | ++---------+--------------------------------------------------------------------+ +| ``'%'`` | A % character. | ++---------+--------------------------------------------------------------------+ Specifiers that have a calendaric component such as ``'d'`` (the day of month) -are valid only for ``std::tm`` and not durations or time points. +are valid only for ``std::tm`` and time points but not durations. .. range-specs: @@ -356,8 +482,8 @@ Range Format Specifications Format specifications for range types have the following syntax: -..productionlist:: sf - range_format_spec: [":" [`underlying_spec`]] +.. productionlist:: sf + range_format_spec: [":" [`underlying_spec`]] The `underlying_spec` is parsed based on the formatter of the range's reference type. @@ -366,12 +492,12 @@ By default, a range of characters or strings is printed escaped and quoted. But if any `underlying_spec` is provided (even if it is empty), then the characters or strings are printed according to the provided specification. -Examples: +Examples:: fmt::format("{}", std::vector{10, 20, 30}); // Result: [10, 20, 30] fmt::format("{::#x}", std::vector{10, 20, 30}); - // Result: [0xa, 0x14, 0x13] + // Result: [0xa, 0x14, 0x1e] fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'}); // Result: ['h', 'e', 'l', 'l', 'o'] fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'}); diff --git a/externals/fmt/include/fmt/args.h b/externals/fmt/include/fmt/args.h index a3966d14..2d684e7c 100644 --- a/externals/fmt/include/fmt/args.h +++ b/externals/fmt/include/fmt/args.h @@ -1,4 +1,4 @@ -// Formatting library for C++ - dynamic format arguments +// Formatting library for C++ - dynamic argument lists // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. diff --git a/externals/fmt/include/fmt/chrono.h b/externals/fmt/include/fmt/chrono.h index c3c52bf5..ff3e1445 100644 --- a/externals/fmt/include/fmt/chrono.h +++ b/externals/fmt/include/fmt/chrono.h @@ -22,6 +22,24 @@ FMT_BEGIN_NAMESPACE +// Check if std::chrono::local_t is available. +#ifndef FMT_USE_LOCAL_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_LOCAL_TIME 0 +# endif +#endif + +// Check if std::chrono::utc_timestamp is available. +#ifndef FMT_USE_UTC_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_UTC_TIME 0 +# endif +#endif + // Enable tzset. #ifndef FMT_USE_TZSET // UWP doesn't provide _tzset. @@ -203,7 +221,8 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, } const auto min1 = (std::numeric_limits<IntermediateRep>::min)() / Factor::num; - if (count < min1) { + if (detail::const_check(!std::is_unsigned<IntermediateRep>::value) && + count < min1) { ec = 1; return {}; } @@ -358,37 +377,11 @@ auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) unit_t unit; write_codecvt(unit, in, loc); // In UTF-8 is used one to four one-byte code units. - auto&& buf = basic_memory_buffer<char, unit_t::max_size * 4>(); - for (code_unit* p = unit.buf; p != unit.end; ++p) { - uint32_t c = static_cast<uint32_t>(*p); - if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { - // surrogate pair - ++p; - if (p == unit.end || (c & 0xfc00) != 0xd800 || - (*p & 0xfc00) != 0xdc00) { - FMT_THROW(format_error("failed to format time")); - } - c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00; - } - if (c < 0x80) { - buf.push_back(static_cast<char>(c)); - } else if (c < 0x800) { - buf.push_back(static_cast<char>(0xc0 | (c >> 6))); - buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); - } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - buf.push_back(static_cast<char>(0xe0 | (c >> 12))); - buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); - } else if (c >= 0x10000 && c <= 0x10ffff) { - buf.push_back(static_cast<char>(0xf0 | (c >> 18))); - buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12))); - buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); - } else { - FMT_THROW(format_error("failed to format time")); - } - } - return copy_str<char>(buf.data(), buf.data() + buf.size(), out); + auto u = + to_utf8<code_unit, basic_memory_buffer<char, unit_t::max_size * 4>>(); + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) + FMT_THROW(format_error("failed to format time")); + return copy_str<char>(u.c_str(), u.c_str() + u.size(), out); } return copy_str<char>(in.data(), in.data() + in.size(), out); } @@ -427,7 +420,7 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, char format, char modifier = 0) -> OutputIt { auto&& buf = get_buffer<Char>(out); do_write<Char>(buf, time, loc, format, modifier); - return buf.out(); + return get_iterator(buf, out); } template <typename Char, typename OutputIt, @@ -441,7 +434,7 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT /** Converts given time since epoch as ``std::time_t`` value into calendar time, @@ -484,10 +477,13 @@ inline std::tm localtime(std::time_t time) { return lt.tm_; } -inline std::tm localtime( - std::chrono::time_point<std::chrono::system_clock> time_point) { - return localtime(std::chrono::system_clock::to_time_t(time_point)); +#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))); } +#endif /** Converts given time since epoch as ``std::time_t`` value into calendar time, @@ -523,7 +519,7 @@ inline std::tm gmtime(std::time_t time) { } #endif }; - dispatcher gt(time); + auto gt = dispatcher(time); // Too big time values may be unsupported. if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); return gt.tm_; @@ -534,7 +530,7 @@ inline std::tm gmtime( return gmtime(std::chrono::system_clock::to_time_t(time_point)); } -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // Writes two-digit numbers a, b and c separated by sep to buf. // The method by Pavel Novikov based on @@ -599,12 +595,39 @@ enum class numeric_system { alternative }; +// Glibc extensions for formatting numeric values. +enum class pad_type { + unspecified, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with zeros even if the conversion specifier + // character uses space-padding by default. + zero, + // Pad a numeric result string with spaces. + space, +}; + +template <typename OutputIt> +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template <typename OutputIt> +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + // 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) { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; + pad_type pad = pad_type::unspecified; while (ptr != end) { auto c = *ptr; if (c == '}') break; @@ -615,6 +638,22 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + case '0': + pad = pad_type::zero; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': @@ -691,16 +730,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; // Hour, minute, second: case 'H': - handler.on_24_hour(numeric_system::standard); + handler.on_24_hour(numeric_system::standard, pad); break; case 'I': - handler.on_12_hour(numeric_system::standard); + handler.on_12_hour(numeric_system::standard, pad); break; case 'M': - handler.on_minute(numeric_system::standard); + handler.on_minute(numeric_system::standard, pad); break; case 'S': - handler.on_second(numeric_system::standard); + handler.on_second(numeric_system::standard, pad); break; // Other: case 'c': @@ -737,7 +776,7 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_duration_unit(); break; case 'z': - handler.on_utc_offset(); + handler.on_utc_offset(numeric_system::standard); break; case 'Z': handler.on_tz_name(); @@ -765,6 +804,9 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); + break; default: FMT_THROW(format_error("invalid format")); } @@ -802,16 +844,19 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': - handler.on_24_hour(numeric_system::alternative); + handler.on_24_hour(numeric_system::alternative, pad); break; case 'I': - handler.on_12_hour(numeric_system::alternative); + handler.on_12_hour(numeric_system::alternative, pad); break; case 'M': - handler.on_minute(numeric_system::alternative); + handler.on_minute(numeric_system::alternative, pad); break; case 'S': - handler.on_second(numeric_system::alternative); + handler.on_second(numeric_system::alternative, pad); + break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); @@ -864,7 +909,7 @@ template <typename Derived> struct null_chrono_spec_handler { FMT_CONSTEXPR void on_am_pm() { unsupported(); } FMT_CONSTEXPR void on_duration_value() { unsupported(); } FMT_CONSTEXPR void on_duration_unit() { unsupported(); } - FMT_CONSTEXPR void on_utc_offset() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; @@ -892,10 +937,10 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> { FMT_CONSTEXPR void on_day_of_year() {} FMT_CONSTEXPR void on_day_of_month(numeric_system) {} FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_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) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_datetime(numeric_system) {} FMT_CONSTEXPR void on_loc_date(numeric_system) {} FMT_CONSTEXPR void on_loc_time(numeric_system) {} @@ -905,7 +950,7 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> { FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_utc_offset() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) {} FMT_CONSTEXPR void on_tz_name() {} }; @@ -957,13 +1002,130 @@ inline void tzset_once() { } #endif -template <typename OutputIt, typename Char> class tm_writer { +// 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; + 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) { + 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) { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template <long long Num, long long Den, int N = 0, + bool Enabled = (N < 19) && (Num <= max_value<long long>() / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits<Num * 10, Den, N + 1>::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template <long long Num, long long Den, int N> +struct count_fractional_digits<Num, Den, N, false> { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template <typename Char, typename OutputIt, typename Duration> +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits<Duration::period::num, + Duration::period::den>::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type<typename Duration::rep, + 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 subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : std::chrono::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); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, ""); + if (std::ratio_less<typename subsecond_precision::period, + std::chrono::seconds::period>::value) { + *out++ = '.'; + out = std::fill_n(out, leading_zeroes, '0'); + out = format_decimal<Char>(out, n, num_digits).end; + } + } else { + *out++ = '.'; + leading_zeroes = (std::min)(leading_zeroes, precision); + out = std::fill_n(out, leading_zeroes, '0'); + int remaining = precision - leading_zeroes; + if (remaining != 0 && remaining < num_digits) { + n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining))); + out = format_decimal<Char>(out, n, remaining).end; + return; + } + out = format_decimal<Char>(out, n, num_digits).end; + remaining -= num_digits; + out = std::fill_n(out, remaining, '0'); + } +} + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the chrono_formatter. +template <typename Duration> +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point<rep>::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits<Duration::period::num, + Duration::period::den>::value; + if (num_fractional_digits < 6 && static_cast<rep>(round(val)) != val) + 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); +} + +template <typename OutputIt, typename Char, + typename Duration = std::chrono::seconds> +class tm_writer { private: static constexpr int days_per_week = 7; const std::locale& loc_; const bool is_classic_; OutputIt out_; + const Duration* subsecs_; const std::tm& tm_; auto tm_sec() const noexcept -> int { @@ -1051,6 +1213,17 @@ template <typename OutputIt, typename Char> class tm_writer { *out_++ = *d++; *out_++ = *d; } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast<char>('0' + v); + } + } void write_year_extended(long long year) { // At least 4 characters. @@ -1074,7 +1247,7 @@ template <typename OutputIt, typename Char> class tm_writer { } } - void write_utc_offset(long offset) { + void write_utc_offset(long offset, numeric_system ns) { if (offset < 0) { *out_++ = '-'; offset = -offset; @@ -1083,14 +1256,15 @@ template <typename OutputIt, typename Char> class tm_writer { } offset /= 60; write2(static_cast<int>(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; write2(static_cast<int>(offset % 60)); } template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)> - void format_utc_offset_impl(const T& tm) { - write_utc_offset(tm.tm_gmtoff); + void format_utc_offset_impl(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); } template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)> - void format_utc_offset_impl(const T& tm) { + void format_utc_offset_impl(const T& tm, numeric_system ns) { #if defined(_WIN32) && defined(_UCRT) # if FMT_USE_TZSET tzset_once(); @@ -1102,10 +1276,17 @@ template <typename OutputIt, typename Char> class tm_writer { _get_dstbias(&dstbias); offset += dstbias; } - write_utc_offset(-offset); + write_utc_offset(-offset, ns); #else - ignore_unused(tm); - format_localized('z'); + if (ns == numeric_system::standard) return format_localized('z'); + + // Extract timezone offset from timezone conversion functions. + std::tm gtm = tm; + std::time_t gt = std::mktime(>m); + std::tm ltm = gmtime(gt); + std::time_t lt = std::mktime(<m); + long offset = gt - lt; + write_utc_offset(offset, ns); #endif } @@ -1126,10 +1307,12 @@ template <typename OutputIt, typename Char> class tm_writer { } public: - tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) : loc_(loc), is_classic_(loc_ == get_classic_locale()), out_(out), + subsecs_(subsecs), tm_(tm) {} OutputIt out() const { return out_; } @@ -1227,7 +1410,7 @@ template <typename OutputIt, typename Char> class tm_writer { out_ = copy_str<Char>(std::begin(buf) + offset, std::end(buf), out_); } - void on_utc_offset() { format_utc_offset_impl(tm_); } + void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } void on_tz_name() { format_tz_name_impl(tm_); } void on_year(numeric_system ns) { @@ -1315,22 +1498,41 @@ template <typename OutputIt, typename Char> class tm_writer { } } - void on_24_hour(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour()); + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); format_localized('H', 'O'); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) - return write2(tm_hour12()); + return write2(tm_hour12(), pad); format_localized('I', 'O'); } - void on_minute(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_min()); + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); format_localized('M', 'O'); } - void on_second(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_sec()); - format_localized('S', 'O'); + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point<typename Duration::rep>::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = std::copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds<Char>(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } } void on_12_hour_time() { @@ -1351,10 +1553,9 @@ template <typename OutputIt, typename Char> class tm_writer { write2(tm_min()); } void on_iso_time() { - char buf[8]; - write_digit2_separated(buf, to_unsigned(tm_hour()), to_unsigned(tm_min()), - to_unsigned(tm_sec()), ':'); - out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_); + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::unspecified); } void on_am_pm() { @@ -1372,42 +1573,34 @@ template <typename OutputIt, typename Char> class tm_writer { }; struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> { + bool has_precision_integral = false; + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } template <typename Char> FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_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) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_duration_value() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) { + FMT_THROW(format_error("precision not allowed for this argument type")); + } + } FMT_CONSTEXPR void on_duration_unit() {} }; -template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> +template <typename T, + FMT_ENABLE_IF(std::is_integral<T>::value&& has_isfinite<T>::value)> inline bool isfinite(T) { return true; } -// 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(value >= 0 && to_unsigned(value) <= to_unsigned(upper), - "invalid value"); - (void)upper; - 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) { - if (value < 0 || value > static_cast<T>(upper)) - FMT_THROW(format_error("invalid value")); - return static_cast<Int>(value); -} - template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> inline T mod(T x, int y) { return x % static_cast<T>(y); @@ -1462,47 +1655,6 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds( #endif } -// Counts the number of fractional digits in the range [0, 18] according to the -// C++20 spec. If more than 18 fractional digits are required then returns 6 for -// microseconds precision. -template <long long Num, long long Den, int N = 0, - bool Enabled = (N < 19) && (Num <= max_value<long long>() / 10)> -struct count_fractional_digits { - static constexpr int value = - Num % Den == 0 ? N : count_fractional_digits<Num * 10, Den, N + 1>::value; -}; - -// Base case that doesn't instantiate any more templates -// in order to avoid overflow. -template <long long Num, long long Den, int N> -struct count_fractional_digits<Num, Den, N, false> { - static constexpr int value = (Num % Den == 0) ? N : 6; -}; - -constexpr long long pow10(std::uint32_t n) { - return n == 0 ? 1 : 10 * pow10(n - 1); -} - -template <class Rep, class Period, - FMT_ENABLE_IF(std::numeric_limits<Rep>::is_signed)> -constexpr std::chrono::duration<Rep, Period> abs( - std::chrono::duration<Rep, Period> d) { - // We need to compare the duration using the count() method directly - // due to a compiler bug in clang-11 regarding the spaceship operator, - // when -Wzero-as-null-pointer-constant is enabled. - // In clang-12 the bug has been fixed. See - // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: - // https://www.godbolt.org/z/Knbb5joYx. - return d.count() >= d.zero().count() ? d : -d; -} - -template <class Rep, class Period, - FMT_ENABLE_IF(!std::numeric_limits<Rep>::is_signed)> -constexpr std::chrono::duration<Rep, Period> abs( - std::chrono::duration<Rep, Period> d) { - return d; -} - template <typename Char, typename Rep, typename OutputIt, FMT_ENABLE_IF(std::is_integral<Rep>::value)> OutputIt format_duration_value(OutputIt out, Rep val, int) { @@ -1512,7 +1664,7 @@ OutputIt format_duration_value(OutputIt out, Rep val, int) { 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 specs = basic_format_specs<Char>(); + auto specs = format_specs<Char>(); specs.precision = precision; specs.type = precision >= 0 ? presentation_type::fixed_lower : presentation_type::general_lower; @@ -1653,44 +1805,16 @@ struct chrono_formatter { } } - void write(Rep value, int width) { + void write(Rep value, int width, pad_type pad = pad_type::unspecified) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t<int> n = to_unsigned(to_nonnegative_int(value, max_value<int>())); int num_digits = detail::count_digits(n); - if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); - out = format_decimal<char_type>(out, n, num_digits).end; - } - - template <typename Duration> void write_fractional_seconds(Duration d) { - FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, ""); - constexpr auto num_fractional_digits = - count_fractional_digits<Duration::period::num, - Duration::period::den>::value; - - using subsecond_precision = std::chrono::duration< - typename std::common_type<typename Duration::rep, - std::chrono::seconds::rep>::type, - std::ratio<1, detail::pow10(num_fractional_digits)>>; - if (std::ratio_less<typename subsecond_precision::period, - std::chrono::seconds::period>::value) { - *out++ = '.'; - auto fractional = - detail::abs(d) - std::chrono::duration_cast<std::chrono::seconds>(d); - auto subseconds = - std::chrono::treat_as_floating_point< - typename subsecond_precision::rep>::value - ? fractional.count() - : std::chrono::duration_cast<subsecond_precision>(fractional) - .count(); - uint32_or_64_or_128_t<long long> n = - to_unsigned(to_nonnegative_int(subseconds, max_value<long long>())); - int num_digits = detail::count_digits(n); - if (num_fractional_digits > num_digits) - out = std::fill_n(out, num_fractional_digits - num_digits, '0'); - out = format_decimal<char_type>(out, n, num_digits).end; + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); } + out = format_decimal<char_type>(out, n, num_digits).end; } void write_nan() { std::copy_n("nan", 3, out); } @@ -1722,7 +1846,7 @@ struct chrono_formatter { void on_loc_time(numeric_system) {} void on_us_date() {} void on_iso_date() {} - void on_utc_offset() {} + void on_utc_offset(numeric_system) {} void on_tz_name() {} void on_year(numeric_system) {} void on_short_year(numeric_system) {} @@ -1738,58 +1862,56 @@ struct chrono_formatter { void on_day_of_month(numeric_system) {} void on_day_of_month_space(numeric_system) {} - void on_24_hour(numeric_system ns) { + void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour(), 2); + if (ns == numeric_system::standard) return write(hour(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_tm(time, &tm_writer_type::on_24_hour, ns); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour12(), 2); + if (ns == numeric_system::standard) return write(hour12(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_tm(time, &tm_writer_type::on_12_hour, ns); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); } - void on_minute(numeric_system ns) { + void on_minute(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(minute(), 2); + if (ns == numeric_system::standard) return write(minute(), 2, pad); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_tm(time, &tm_writer_type::on_minute, ns); + format_tm(time, &tm_writer_type::on_minute, ns, pad); } - void on_second(numeric_system ns) { + void on_second(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { if (std::is_floating_point<rep>::value) { - constexpr auto num_fractional_digits = - count_fractional_digits<Period::num, Period::den>::value; auto buf = memory_buffer(); - format_to(std::back_inserter(buf), runtime("{:.{}f}"), - std::fmod(val * static_cast<rep>(Period::num) / - static_cast<rep>(Period::den), - 60), - num_fractional_digits); + write_floating_seconds(buf, std::chrono::duration<rep, Period>(val), + precision); if (negative) *out++ = '-'; - if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } out = std::copy(buf.begin(), buf.end(), out); } else { - write(second(), 2); - write_fractional_seconds(std::chrono::duration<rep, Period>(val)); + write(second(), 2, pad); + write_fractional_seconds<char_type>( + out, std::chrono::duration<rep, Period>(val), precision); } return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_tm(time, &tm_writer_type::on_second, ns); + format_tm(time, &tm_writer_type::on_second, ns, pad); } void on_12_hour_time() { @@ -1813,7 +1935,7 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - on_second(numeric_system::standard); + on_second(numeric_system::standard, pad_type::unspecified); } void on_am_pm() { @@ -1832,7 +1954,7 @@ struct chrono_formatter { } }; -FMT_END_DETAIL_NAMESPACE +} // namespace detail #if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 using weekday = std::chrono::weekday; @@ -1882,118 +2004,67 @@ template <typename Char> struct formatter<weekday, Char> { template <typename Rep, typename Period, typename Char> struct formatter<std::chrono::duration<Rep, Period>, Char> { private: - basic_format_specs<Char> specs; - int precision = -1; - using arg_ref_type = detail::arg_ref<Char>; - arg_ref_type width_ref; - arg_ref_type precision_ref; - bool localized = false; - basic_string_view<Char> format_str; - using duration = std::chrono::duration<Rep, Period>; - - struct spec_handler { - formatter& f; - basic_format_parse_context<Char>& context; - basic_string_view<Char> format_str; - - template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { - return arg_ref_type(context.next_arg_id()); - } - - void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) { - f.specs.fill = fill; - } - FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; } - FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; } - FMT_CONSTEXPR void on_precision(int _precision) { - f.precision = _precision; - } - FMT_CONSTEXPR void end_precision() {} + format_specs<Char> specs_; + detail::arg_ref<Char> width_ref_; + detail::arg_ref<Char> precision_ref_; + bool localized_ = false; + basic_string_view<Char> format_str_; - template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - f.width_ref = make_arg_ref(arg_id); - } + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; - template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - f.precision_ref = make_arg_ref(arg_id); - } - }; + it = detail::parse_align(it, end, specs_); + if (it == end) return it; - using iterator = typename basic_format_parse_context<Char>::iterator; - struct parse_range { - iterator begin; - iterator end; - }; + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; - FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context<Char>& ctx) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end || *begin == '}') return {begin, begin}; - spec_handler handler{*this, ctx, format_str}; - begin = detail::parse_align(begin, end, handler); - if (begin == end) return {begin, begin}; - begin = detail::parse_width(begin, end, handler); - if (begin == end) return {begin, begin}; - if (*begin == '.') { - if (std::is_floating_point<Rep>::value) - begin = detail::parse_precision(begin, end, handler); - else - handler.on_error("precision not allowed for this argument type"); + auto checker = detail::chrono_format_checker(); + if (*it == '.') { + checker.has_precision_integral = !std::is_floating_point<Rep>::value; + it = detail::parse_precision(it, end, specs_.precision, precision_ref_, + ctx); } - if (begin != end && *begin == 'L') { - ++begin; - localized = true; + if (it != end && *it == 'L') { + localized_ = true; + ++it; } - end = detail::parse_chrono_format(begin, end, - detail::chrono_format_checker()); - return {begin, end}; - } - - public: - FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) - -> decltype(ctx.begin()) { - auto range = do_parse(ctx); - format_str = basic_string_view<Char>( - &*range.begin, detail::to_unsigned(range.end - range.begin)); - return range.end; + end = detail::parse_chrono_format(it, end, checker); + format_str_ = {it, detail::to_unsigned(end - it)}; + return end; } template <typename FormatContext> - auto format(const duration& d, FormatContext& ctx) const + auto format(std::chrono::duration<Rep, Period> d, FormatContext& ctx) const -> decltype(ctx.out()) { - auto specs_copy = specs; - auto precision_copy = precision; - auto begin = format_str.begin(), end = format_str.end(); + auto specs = specs_; + auto precision = specs.precision; + specs.precision = -1; + auto begin = format_str_.begin(), end = format_str_.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. - basic_memory_buffer<Char> buf; + auto buf = basic_memory_buffer<Char>(); auto out = std::back_inserter(buf); - detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width, - width_ref, ctx); - detail::handle_dynamic_spec<detail::precision_checker>(precision_copy, - precision_ref, ctx); + detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec<detail::precision_checker>(precision, + precision_ref_, ctx); if (begin == end || *begin == '}') { - out = detail::format_duration_value<Char>(out, d.count(), precision_copy); + out = detail::format_duration_value<Char>(out, d.count(), precision); detail::format_duration_unit<Char, Period>(out); } else { - detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f( - ctx, out, d); - f.precision = precision_copy; - f.localized = localized; + using chrono_formatter = + detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>; + auto f = chrono_formatter(ctx, out, d); + f.precision = precision; + f.localized = localized_; detail::parse_chrono_format(begin, end, f); } return detail::write( - ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy); + ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs); } }; @@ -2001,80 +2072,137 @@ template <typename Char, typename Duration> struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>, Char> : formatter<std::tm, Char> { FMT_CONSTEXPR formatter() { - this->do_parse(default_specs, - default_specs + sizeof(default_specs) / sizeof(Char)); - } - - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return this->do_parse(ctx.begin(), ctx.end(), true); + this->format_str_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{}; } template <typename FormatContext> - auto format(std::chrono::time_point<std::chrono::system_clock> val, + auto format(std::chrono::time_point<std::chrono::system_clock, Duration> val, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter<std::tm, Char>::format(localtime(val), ctx); + using period = typename Duration::period; + if (detail::const_check( + 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)); + + if (subsecs.count() < 0) { + auto second = + std::chrono::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>::format( + gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx); + } +}; + +#if FMT_USE_LOCAL_TIME +template <typename Char, typename Duration> +struct formatter<std::chrono::local_time<Duration>, Char> + : formatter<std::tm, Char> { + FMT_CONSTEXPR formatter() { + this->format_str_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{}; } - static constexpr const Char default_specs[] = {'%', 'F', ' ', '%', 'T'}; + template <typename FormatContext> + auto format(std::chrono::local_time<Duration> val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + 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)); + + return formatter<std::tm, Char>::do_format( + localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), + ctx, &subsecs); + } + + return formatter<std::tm, Char>::format( + localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), + ctx); + } }; +#endif +#if FMT_USE_UTC_TIME template <typename Char, typename Duration> -constexpr const Char - formatter<std::chrono::time_point<std::chrono::system_clock, Duration>, - Char>::default_specs[]; +struct formatter<std::chrono::time_point<std::chrono::utc_clock, Duration>, + Char> + : formatter<std::chrono::time_point<std::chrono::system_clock, Duration>, + Char> { + template <typename FormatContext> + auto format(std::chrono::time_point<std::chrono::utc_clock, Duration> val, + FormatContext& ctx) const -> decltype(ctx.out()) { + return formatter< + std::chrono::time_point<std::chrono::system_clock, Duration>, + Char>::format(std::chrono::utc_clock::to_sys(val), ctx); + } +}; +#endif template <typename Char> struct formatter<std::tm, Char> { private: - enum class spec { - unknown, - year_month_day, - hh_mm_ss, - }; - spec spec_ = spec::unknown; - basic_string_view<Char> specs; + format_specs<Char> specs_; + detail::arg_ref<Char> width_ref_; protected: - template <typename It> - FMT_CONSTEXPR auto do_parse(It begin, It end, bool with_default = false) - -> It { - if (begin != end && *begin == ':') ++begin; - end = detail::parse_chrono_format(begin, end, detail::tm_format_checker()); - if (!with_default || end != begin) - specs = {begin, detail::to_unsigned(end - begin)}; - // basic_string_view<>::compare isn't constexpr before C++17. - if (specs.size() == 2 && specs[0] == Char('%')) { - if (specs[1] == Char('F')) - spec_ = spec::year_month_day; - else if (specs[1] == Char('T')) - spec_ = spec::hh_mm_ss; - } - return end; + basic_string_view<Char> format_str_; + + template <typename FormatContext, typename Duration> + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer<Char>(); + auto out = std::back_inserter(buf); + detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_, + ctx); + + auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref); + auto w = + detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs); + detail::parse_chrono_format(format_str_.begin(), format_str_.end(), w); + return detail::write( + ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs); } public: - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return this->do_parse(ctx.begin(), ctx.end()); + FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; + + end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + // Replace the default format_str only if the new spec is not empty. + if (end != it) format_str_ = {it, detail::to_unsigned(end - it)}; + return end; } template <typename FormatContext> auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { - const auto loc_ref = ctx.locale(); - detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref); - auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), tm); - if (spec_ == spec::year_month_day) - w.on_iso_date(); - else if (spec_ == spec::hh_mm_ss) - w.on_iso_time(); - else - detail::parse_chrono_format(specs.begin(), specs.end(), w); - return w.out(); + return do_format<FormatContext, std::chrono::seconds>(tm, ctx, nullptr); } }; -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ diff --git a/externals/fmt/include/fmt/color.h b/externals/fmt/include/fmt/color.h index e6212d24..8697e1ca 100644 --- a/externals/fmt/include/fmt/color.h +++ b/externals/fmt/include/fmt/color.h @@ -11,7 +11,7 @@ #include "format.h" FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT enum class color : uint32_t { alice_blue = 0xF0F8FF, // rgb(240,248,255) @@ -203,7 +203,7 @@ struct rgb { uint8_t b; }; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // color is a struct of either a rgb color or a terminal color. struct color_type { @@ -225,8 +225,7 @@ struct color_type { uint32_t rgb_color; } value; }; - -FMT_END_DETAIL_NAMESPACE +} // namespace detail /** A text style consisting of foreground and background colors and emphasis. */ class text_style { @@ -323,7 +322,7 @@ FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept { return text_style(lhs) | rhs; } -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { template <typename Char> struct ansi_color_escape { FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, @@ -423,26 +422,6 @@ FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept { return ansi_color_escape<Char>(em); } -template <typename Char> inline void fputs(const Char* chars, FILE* stream) { - int result = std::fputs(chars, stream); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); -} - -template <> inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) { - int result = std::fputws(chars, stream); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); -} - -template <typename Char> inline void reset_color(FILE* stream) { - fputs("\x1b[0m", stream); -} - -template <> inline void reset_color<wchar_t>(FILE* stream) { - fputs(L"\x1b[0m", stream); -} - template <typename Char> inline void reset_color(buffer<Char>& buffer) { auto reset_color = string_view("\x1b[0m"); buffer.append(reset_color.begin(), reset_color.end()); @@ -477,19 +456,21 @@ void vformat_to(buffer<Char>& buf, const text_style& ts, if (has_style) detail::reset_color<Char>(buf); } -FMT_END_DETAIL_NAMESPACE +} // namespace detail -template <typename S, typename Char = char_t<S>> -void vprint(std::FILE* f, const text_style& ts, const S& format, - basic_format_args<buffer_context<type_identity_t<Char>>> args) { - basic_memory_buffer<Char> buf; - detail::vformat_to(buf, ts, detail::to_string_view(format), args); +inline void vprint(std::FILE* f, const text_style& ts, string_view fmt, + format_args args) { + // Legacy wide streams are not supported. + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); if (detail::is_utf8()) { - detail::print(f, basic_string_view<Char>(buf.begin(), buf.size())); - } else { - buf.push_back(Char(0)); - detail::fputs(buf.data(), f); + detail::print(f, string_view(buf.begin(), buf.size())); + return; } + buf.push_back('\0'); + int result = std::fputs(buf.data(), f); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } /** @@ -566,7 +547,7 @@ OutputIt vformat_to( basic_format_args<buffer_context<type_identity_t<Char>>> args) { auto&& buf = detail::get_buffer<Char>(out); detail::vformat_to(buf, ts, format_str, args); - return detail::get_iterator(buf); + return detail::get_iterator(buf, out); } /** @@ -634,7 +615,7 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> { **Example**:: - fmt::print("Elapsed time: {s:.2f} seconds", + fmt::print("Elapsed time: {0:.2f} seconds", fmt::styled(1.23, fmt::fg(fmt::color::green) | fmt::bg(fmt::color::blue))); \endrst @@ -645,7 +626,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts) return detail::styled_arg<remove_cvref_t<T>>{value, ts}; } -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COLOR_H_ diff --git a/externals/fmt/include/fmt/compile.h b/externals/fmt/include/fmt/compile.h index c09dd6f2..a4c7e495 100644 --- a/externals/fmt/include/fmt/compile.h +++ b/externals/fmt/include/fmt/compile.h @@ -14,89 +14,11 @@ FMT_BEGIN_NAMESPACE namespace detail { template <typename Char, typename InputIt> -inline counting_iterator copy_str(InputIt begin, InputIt end, - counting_iterator it) { +FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end, + counting_iterator it) { return it + (end - begin); } -template <typename OutputIt> class truncating_iterator_base { - protected: - OutputIt out_; - size_t limit_; - size_t count_ = 0; - - truncating_iterator_base() : out_(), limit_(0) {} - - truncating_iterator_base(OutputIt out, size_t limit) - : out_(out), limit_(limit) {} - - public: - using iterator_category = std::output_iterator_tag; - using value_type = typename std::iterator_traits<OutputIt>::value_type; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - FMT_UNCHECKED_ITERATOR(truncating_iterator_base); - - OutputIt base() const { return out_; } - size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template <typename OutputIt, - typename Enable = typename std::is_void< - typename std::iterator_traits<OutputIt>::value_type>::type> -class truncating_iterator; - -template <typename OutputIt> -class truncating_iterator<OutputIt, std::false_type> - : public truncating_iterator_base<OutputIt> { - mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_; - - public: - using value_type = typename truncating_iterator_base<OutputIt>::value_type; - - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base<OutputIt>(out, limit) {} - - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) ++this->out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; - -template <typename OutputIt> -class truncating_iterator<OutputIt, std::true_type> - : public truncating_iterator_base<OutputIt> { - public: - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base<OutputIt>(out, limit) {} - - template <typename T> truncating_iterator& operator=(T val) { - if (this->count_++ < this->limit_) *this->out_++ = val; - return *this; - } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; - // A compile-time string which is compiled into fast formatting code. class compiled_string {}; @@ -196,7 +118,8 @@ template <typename Char> struct code_unit { template <typename OutputIt, typename... Args> constexpr OutputIt format(OutputIt out, const Args&...) const { - return write<Char>(out, value); + *out++ = value; + return out; } }; @@ -220,7 +143,12 @@ template <typename Char, typename T, int N> struct field { template <typename OutputIt, typename... Args> constexpr OutputIt format(OutputIt out, const Args&... args) const { - return write<Char>(out, get_arg_checked<T, N>(args...)); + const T& arg = get_arg_checked<T, N>(args...); + if constexpr (std::is_convertible_v<T, basic_string_view<Char>>) { + auto s = basic_string_view<Char>(arg); + return copy_str<Char>(s.begin(), s.end(), out); + } + return write<Char>(out, arg); } }; @@ -331,39 +259,35 @@ template <typename T, typename Char> struct parse_specs_result { int next_arg_id; }; -constexpr int manual_indexing_id = -1; +enum { manual_indexing_id = -1 }; template <typename T, typename Char> constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, size_t pos, int next_arg_id) { str.remove_prefix(pos); - auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {}, - next_arg_id); + auto ctx = + compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id); auto f = formatter<T, Char>(); auto end = f.parse(ctx); - return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, + return {f, pos + fmt::detail::to_unsigned(end - str.data()), next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; } template <typename Char> struct arg_id_handler { arg_ref<Char> arg_id; - constexpr int operator()() { + constexpr int on_auto() { FMT_ASSERT(false, "handler cannot be used with automatic indexing"); return 0; } - constexpr int operator()(int id) { + constexpr int on_index(int id) { arg_id = arg_ref<Char>(id); return 0; } - constexpr int operator()(basic_string_view<Char> id) { + constexpr int on_name(basic_string_view<Char> id) { arg_id = arg_ref<Char>(id); return 0; } - - constexpr void on_error(const char* message) { - FMT_THROW(format_error(message)); - } }; template <typename Char> struct parse_arg_id_result { @@ -397,13 +321,20 @@ constexpr auto parse_replacement_field_then_tail(S format_str) { return parse_tail<Args, END_POS + 1, NEXT_ID>( field<char_type, typename field_type<T>::type, ARG_INDEX>(), format_str); - } else if constexpr (c == ':') { + } else if constexpr (c != ':') { + FMT_THROW(format_error("expected ':'")); + } else { constexpr auto result = parse_specs<typename field_type<T>::type>( str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); - return parse_tail<Args, result.end, result.next_arg_id>( - spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{ - result.fmt}, - format_str); + if constexpr (result.end >= str.size() || str[result.end] != '}') { + FMT_THROW(format_error("expected '}'")); + return 0; + } else { + return parse_tail<Args, result.end + 1, result.next_arg_id>( + spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{ + result.fmt}, + format_str); + } } } @@ -445,20 +376,18 @@ constexpr auto compile_format_string(S format_str) { } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { constexpr auto arg_index = get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); - if constexpr (arg_index != invalid_arg_index) { + if constexpr (arg_index >= 0) { constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail< decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos, arg_index, next_id>(format_str); - } else { - if constexpr (c == '}') { - return parse_tail<Args, arg_id_end_pos + 1, ID>( - runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, - format_str); - } else if constexpr (c == ':') { - return unknown_format(); // no type info for specs parsing - } + } else if constexpr (c == '}') { + return parse_tail<Args, arg_id_end_pos + 1, ID>( + runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, + format_str); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing } } } @@ -494,7 +423,7 @@ constexpr auto compile(S format_str) { #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) @@ -561,14 +490,16 @@ 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 it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n), - format_str, std::forward<Args>(args)...); - return {it.base(), it.count()}; + 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)...); + return {buf.out(), buf.count()}; } template <typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> -size_t formatted_size(const S& format_str, const Args&... args) { +FMT_CONSTEXPR20 size_t formatted_size(const S& format_str, + const Args&... args) { return fmt::format_to(detail::counting_iterator(), format_str, args...) .count(); } @@ -597,7 +528,7 @@ template <detail_exported::fixed_string Str> constexpr auto operator""_cf() { } // namespace literals #endif -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ diff --git a/externals/fmt/include/fmt/core.h b/externals/fmt/include/fmt/core.h index 0e7843b8..1fe13888 100644 --- a/externals/fmt/include/fmt/core.h +++ b/externals/fmt/include/fmt/core.h @@ -13,11 +13,12 @@ #include <cstring> // std::strlen #include <iterator> #include <limits> +#include <memory> // std::addressof #include <string> #include <type_traits> // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 90000 +#define FMT_VERSION 100100 #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) @@ -69,9 +70,7 @@ # define FMT_HAS_FEATURE(x) 0 #endif -#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ - FMT_MSC_VERSION > 1900) && \ - !defined(__INTELLISENSE__) +#if defined(__has_include) || FMT_ICC_VERSION >= 1600 || FMT_MSC_VERSION > 1900 # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 @@ -94,7 +93,7 @@ #ifndef FMT_USE_CONSTEXPR # if (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 || \ (FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L)) && \ - !FMT_ICC_VERSION && !defined(__NVCC__) + !FMT_ICC_VERSION && (!defined(__NVCC__) || FMT_CPLUSPLUS >= 202002L) # define FMT_USE_CONSTEXPR 1 # else # define FMT_USE_CONSTEXPR 0 @@ -140,22 +139,7 @@ # endif #endif -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 -# define FMT_DEPRECATED [[deprecated]] -# else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VERSION -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif -# endif -#endif - -// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code -// warnings. +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. #if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && \ !defined(__NVCC__) # define FMT_NORETURN [[noreturn]] @@ -163,17 +147,6 @@ # define FMT_NORETURN #endif -#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) -# define FMT_FALLTHROUGH [[fallthrough]] -#elif defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -#elif FMT_GCC_VERSION >= 700 && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] -#else -# define FMT_FALLTHROUGH -#endif - #ifndef FMT_NODISCARD # if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) # define FMT_NODISCARD [[nodiscard]] @@ -182,16 +155,6 @@ # endif #endif -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 -#endif -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 -#endif - #ifndef FMT_INLINE # if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_INLINE inline __attribute__((always_inline)) @@ -210,30 +173,26 @@ #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ - inline namespace v9 { + inline namespace v10 { # define FMT_END_NAMESPACE \ } \ } #endif -#ifndef FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT_BEGIN -# define FMT_MODULE_EXPORT_END -# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { -# define FMT_END_DETAIL_NAMESPACE } +#ifndef FMT_EXPORT +# define FMT_EXPORT +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) -# ifdef FMT_EXPORT +# ifdef FMT_LIB_EXPORT # define FMT_API __declspec(dllexport) # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # endif #else -# define FMT_CLASS_API -# if defined(FMT_EXPORT) || defined(FMT_SHARED) +# if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) # if defined(__GNUC__) || defined(__clang__) # define FMT_API __attribute__((visibility("default"))) # endif @@ -258,11 +217,13 @@ #endif #ifndef FMT_CONSTEVAL -# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ - FMT_CPLUSPLUS >= 202002L && !defined(__apple_build_version__)) || \ - (defined(__cpp_consteval) && \ +# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ + (!defined(__apple_build_version__) || \ + __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 13. +// consteval is broken in MSVC before VS2022 and Apple clang before 14. # define FMT_CONSTEVAL consteval # define FMT_HAS_CONSTEVAL # else @@ -273,7 +234,8 @@ #ifndef FMT_USE_NONTYPE_TEMPLATE_ARGS # if defined(__cpp_nontype_template_args) && \ ((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \ - __cpp_nontype_template_args >= 201911L) + __cpp_nontype_template_args >= 201911L) && \ + !defined(__NVCOMPILER) && !defined(__LCC__) # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 # else # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 @@ -282,12 +244,12 @@ // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") -#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) +#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) && !defined(__LCC__) && \ + !defined(__CUDACC__) FMT_GCC_PRAGMA("GCC optimize(\"Og\")") #endif FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN // Implementations of enable_if_t and other metafunctions for older systems. template <bool B, typename T = void> @@ -306,17 +268,10 @@ template <typename T> using type_identity_t = typename type_identity<T>::type; template <typename T> using underlying_t = typename std::underlying_type<T>::type; -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> {}; +// Checks whether T is a container with contiguous storage. +template <typename T> struct is_contiguous : std::false_type {}; +template <typename Char> +struct is_contiguous<std::basic_string<Char>> : std::true_type {}; struct monostate { constexpr monostate() {} @@ -328,11 +283,19 @@ struct monostate { #ifdef FMT_DOC # define FMT_ENABLE_IF(...) #else -# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif -FMT_BEGIN_DETAIL_NAMESPACE +// This is defined in core.h instead of format.h to avoid injecting in std. +// It is a template to avoid undesirable implicit conversions to std::byte. +#ifdef __cpp_lib_byte +template <typename T, FMT_ENABLE_IF(std::is_same<T, std::byte>::value)> +inline auto format_as(T b) -> unsigned char { + return static_cast<unsigned char>(b); +} +#endif +namespace detail { // Suppresses "unused variable" warnings with the method described in // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // (void)var does not work on many Intel compilers. @@ -340,7 +303,15 @@ template <typename... T> FMT_CONSTEXPR void ignore_unused(const T&...) {} constexpr FMT_INLINE auto is_constant_evaluated( bool default_value = false) noexcept -> bool { -#ifdef __cpp_lib_is_constant_evaluated +// Workaround for incompatibility between libstdc++ consteval-based +// std::is_constant_evaluated() implementation and clang-14. +// https://github.com/fmtlib/fmt/issues/3247 +#if FMT_CPLUSPLUS >= 202002L && defined(_GLIBCXX_RELEASE) && \ + _GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) ignore_unused(default_value); return std::is_constant_evaluated(); #else @@ -360,12 +331,12 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, # ifdef NDEBUG // FMT_ASSERT is not empty to avoid -Wempty-body. # define FMT_ASSERT(condition, message) \ - ::fmt::detail::ignore_unused((condition), (message)) + fmt::detail::ignore_unused((condition), (message)) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ? (void)0 \ - : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) + : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) # endif #endif @@ -402,19 +373,19 @@ template <typename T> auto convert_for_visit(T) -> monostate { return {}; } template <typename Int> FMT_CONSTEXPR auto to_unsigned(Int value) -> typename std::make_unsigned<Int>::type { - FMT_ASSERT(value >= 0, "negative value"); + FMT_ASSERT(std::is_unsigned<Int>::value || value >= 0, "negative value"); return static_cast<typename std::make_unsigned<Int>::type>(value); } -FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; +FMT_CONSTEXPR inline auto is_utf8() -> bool { + FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char section[] = "\u00A7"; -constexpr auto is_utf8() -> bool { // Avoid buggy sign extensions in MSVC's constant evaluation mode (#2297). using uchar = unsigned char; - return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && - uchar(micro[1]) == 0xB5); + return FMT_UNICODE || (sizeof(section) == 3 && uchar(section[0]) == 0xC2 && + uchar(section[1]) == 0xA7); } -FMT_END_DETAIL_NAMESPACE +} // namespace detail /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -423,6 +394,7 @@ FMT_END_DETAIL_NAMESPACE compiled with a different ``-std`` option than the client code (which is not recommended). */ +FMT_EXPORT template <typename Char> class basic_string_view { private: const Char* data_; @@ -482,6 +454,18 @@ template <typename Char> class basic_string_view { size_ -= n; } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( + basic_string_view<Char> sv) const noexcept { + 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 { + return size_ >= 1 && std::char_traits<Char>::eq(*data_, c); + } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { + return starts_with(basic_string_view<Char>(s)); + } + // Lexicographically compare this string reference to other. FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; @@ -513,13 +497,15 @@ template <typename Char> class basic_string_view { } }; +FMT_EXPORT using string_view = basic_string_view<char>; /** Specifies if ``T`` is a character type. Can be specialized by users. */ +FMT_EXPORT template <typename T> struct is_char : std::false_type {}; template <> struct is_char<char> : std::true_type {}; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // A base class for compile-time strings. struct compile_string {}; @@ -527,7 +513,6 @@ struct compile_string {}; template <typename S> struct is_compile_string : std::is_base_of<compile_string, S> {}; -// Returns a string view of `s`. template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)> FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view<Char> { return s; @@ -557,10 +542,10 @@ void to_string_view(...); // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in // enable_if and MSVC 2015 fails to compile it as an alias template. -// ADL invocation of to_string_view is DEPRECATED! +// ADL is intentionally disabled as to_string_view is not an extension point. template <typename S> -struct is_string : std::is_class<decltype(to_string_view(std::declval<S>()))> { -}; +struct is_string + : std::is_class<decltype(detail::to_string_view(std::declval<S>()))> {}; template <typename S, typename = void> struct char_t_impl {}; template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> { @@ -618,23 +603,44 @@ FMT_TYPE_CONSTANT(const void*, pointer_type); constexpr bool is_integral_type(type t) { return t > type::none_type && t <= type::last_integer_type; } - constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } +constexpr auto set(type rhs) -> int { return 1 << static_cast<int>(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast<int>(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; + FMT_NORETURN FMT_API void throw_format_error(const char* message); struct error_handler { constexpr error_handler() = default; - constexpr error_handler(const error_handler&) = default; // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN void on_error(const char* message) { throw_format_error(message); } }; -FMT_END_DETAIL_NAMESPACE +} // namespace detail + +/** Throws ``format_error`` with a given message. */ +using detail::throw_format_error; /** String's character type. */ template <typename S> using char_t = typename detail::char_t_impl<S>::type; @@ -646,8 +652,8 @@ template <typename S> using char_t = typename detail::char_t_impl<S>::type; You can use the ``format_parse_context`` type alias for ``char`` instead. \endrst */ -template <typename Char, typename ErrorHandler = detail::error_handler> -class basic_format_parse_context : private ErrorHandler { +FMT_EXPORT +template <typename Char> class basic_format_parse_context { private: basic_string_view<Char> format_str_; int next_arg_id_; @@ -656,12 +662,11 @@ class basic_format_parse_context : private ErrorHandler { public: using char_type = Char; - using iterator = typename basic_string_view<Char>::iterator; + using iterator = const Char*; explicit constexpr basic_format_parse_context( - basic_string_view<Char> format_str, ErrorHandler eh = {}, - int next_arg_id = 0) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} + basic_string_view<Char> format_str, int next_arg_id = 0) + : format_str_(format_str), next_arg_id_(next_arg_id) {} /** Returns an iterator to the beginning of the format string range being @@ -687,7 +692,8 @@ class basic_format_parse_context : private ErrorHandler { */ FMT_CONSTEXPR auto next_arg_id() -> int { if (next_arg_id_ < 0) { - on_error("cannot switch from manual to automatic argument indexing"); + detail::throw_format_error( + "cannot switch from manual to automatic argument indexing"); return 0; } int id = next_arg_id_++; @@ -701,110 +707,59 @@ class basic_format_parse_context : private ErrorHandler { */ FMT_CONSTEXPR void check_arg_id(int id) { if (next_arg_id_ > 0) { - on_error("cannot switch from automatic to manual argument indexing"); + detail::throw_format_error( + "cannot switch from automatic to manual argument indexing"); return; } next_arg_id_ = -1; do_check_arg_id(id); } - FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {} - - FMT_CONSTEXPR void on_error(const char* message) { - ErrorHandler::on_error(message); - } - - constexpr auto error_handler() const -> ErrorHandler { return *this; } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); }; +FMT_EXPORT using format_parse_context = basic_format_parse_context<char>; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // A parse context with extra data used only in compile-time checks. -template <typename Char, typename ErrorHandler = detail::error_handler> -class compile_parse_context - : public basic_format_parse_context<Char, ErrorHandler> { +template <typename Char> +class compile_parse_context : public basic_format_parse_context<Char> { private: int num_args_; const type* types_; - using base = basic_format_parse_context<Char, ErrorHandler>; + using base = basic_format_parse_context<Char>; public: explicit FMT_CONSTEXPR compile_parse_context( basic_string_view<Char> format_str, int num_args, const type* types, - ErrorHandler eh = {}, int next_arg_id = 0) - : base(format_str, eh, next_arg_id), num_args_(num_args), types_(types) {} + int next_arg_id = 0) + : base(format_str, next_arg_id), num_args_(num_args), types_(types) {} - constexpr int num_args() const { return num_args_; } + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } FMT_CONSTEXPR auto next_arg_id() -> int { int id = base::next_arg_id(); - if (id >= num_args_) this->on_error("argument not found"); + if (id >= num_args_) throw_format_error("argument not found"); return id; } FMT_CONSTEXPR void check_arg_id(int id) { base::check_arg_id(id); - if (id >= num_args_) this->on_error("argument not found"); + if (id >= num_args_) throw_format_error("argument not found"); } using base::check_arg_id; -}; -FMT_END_DETAIL_NAMESPACE -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR void -basic_format_parse_context<Char, ErrorHandler>::do_check_arg_id(int id) { - // Argument id is only checked at compile-time during parsing because - // formatting has its own validation. - if (detail::is_constant_evaluated() && FMT_GCC_VERSION >= 1200) { - using context = detail::compile_parse_context<Char, ErrorHandler>; - if (id >= static_cast<context*>(this)->num_args()) - on_error("argument not found"); + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + detail::ignore_unused(arg_id); +#if !defined(__LCC__) + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + throw_format_error("width/precision is not integer"); +#endif } -} - -template <typename Context> class basic_format_arg; -template <typename Context> class basic_format_args; -template <typename Context> class dynamic_format_arg_store; - -// A formatter for objects of type T. -template <typename T, typename Char = char, typename Enable = void> -struct formatter { - // A deleted default constructor indicates a disabled formatter. - formatter() = delete; }; -// Specifies if T has an enabled formatter specialization. A type can be -// formattable even if it doesn't have a formatter e.g. via a conversion. -template <typename T, typename Context> -using has_formatter = - std::is_constructible<typename Context::template formatter_type<T>>; - -// Checks whether T is a container with contiguous storage. -template <typename T> struct is_contiguous : std::false_type {}; -template <typename Char> -struct is_contiguous<std::basic_string<Char>> : std::true_type {}; - -class appender; - -FMT_BEGIN_DETAIL_NAMESPACE - -template <typename Context, typename T> -constexpr auto has_const_formatter_impl(T*) - -> decltype(typename Context::template formatter_type<T>().format( - std::declval<const T&>(), std::declval<Context&>()), - true) { - return true; -} -template <typename Context> -constexpr auto has_const_formatter_impl(...) -> bool { - return false; -} -template <typename T, typename Context> -constexpr auto has_const_formatter() -> bool { - return has_const_formatter_impl<Context>(static_cast<T*>(nullptr)); -} - // Extracts a reference to the container from back_insert_iterator. template <typename Container> inline auto get_container(std::back_insert_iterator<Container> it) @@ -830,7 +785,7 @@ template <typename Char, typename T, typename U, FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { if (is_constant_evaluated()) return copy_str<Char, T*, U*>(begin, end, out); auto size = to_unsigned(end - begin); - memcpy(out, begin, size * sizeof(U)); + if (size > 0) memcpy(out, begin, size * sizeof(U)); return out + size; } @@ -873,11 +828,11 @@ template <typename T> class buffer { buffer(const buffer&) = delete; void operator=(const buffer&) = delete; - auto begin() noexcept -> T* { return ptr_; } - auto end() noexcept -> T* { return ptr_ + size_; } + FMT_INLINE auto begin() noexcept -> T* { return ptr_; } + FMT_INLINE auto end() noexcept -> T* { return ptr_ + size_; } - auto begin() const noexcept -> const T* { return ptr_; } - auto end() const noexcept -> const T* { return ptr_ + size_; } + FMT_INLINE auto begin() const noexcept -> const T* { return ptr_; } + FMT_INLINE auto end() const noexcept -> const T* { return ptr_ + size_; } /** Returns the size of this buffer. */ constexpr auto size() const noexcept -> size_t { return size_; } @@ -885,10 +840,8 @@ template <typename T> class buffer { /** Returns the capacity of this buffer. */ constexpr auto capacity() const noexcept -> size_t { return capacity_; } - /** Returns a pointer to the buffer data. */ + /** Returns a pointer to the buffer data (not null-terminated). */ FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } - - /** Returns a pointer to the buffer data. */ FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } /** Clears this buffer. */ @@ -917,11 +870,11 @@ template <typename T> class buffer { /** Appends data to the end of the buffer. */ template <typename U> void append(const U* begin, const U* end); - template <typename I> FMT_CONSTEXPR auto operator[](I index) -> T& { + template <typename Idx> FMT_CONSTEXPR auto operator[](Idx index) -> T& { return ptr_[index]; } - template <typename I> - FMT_CONSTEXPR auto operator[](I index) const -> const T& { + template <typename Idx> + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { return ptr_[index]; } }; @@ -1081,6 +1034,79 @@ template <typename T = char> class counting_buffer final : public buffer<T> { auto count() -> size_t { return count_ + this->size(); } }; +} // namespace detail + +template <typename Char> +FMT_CONSTEXPR void basic_format_parse_context<Char>::do_check_arg_id(int id) { + // Argument id is only checked at compile-time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context<Char>; + if (id >= static_cast<context*>(this)->num_args()) + detail::throw_format_error("argument not found"); + } +} + +template <typename Char> +FMT_CONSTEXPR void basic_format_parse_context<Char>::check_dynamic_spec( + int arg_id) { + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context<Char>; + static_cast<context*>(this)->check_dynamic_spec(arg_id); + } +} + +FMT_EXPORT template <typename Context> class basic_format_arg; +FMT_EXPORT template <typename Context> class basic_format_args; +FMT_EXPORT template <typename Context> class dynamic_format_arg_store; + +// A formatter for objects of type T. +FMT_EXPORT +template <typename T, typename Char = char, typename Enable = void> +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +// Specifies if T has an enabled formatter specialization. A type can be +// formattable even if it doesn't have a formatter e.g. via a conversion. +template <typename T, typename Context> +using has_formatter = + std::is_constructible<typename Context::template formatter_type<T>>; + +// An output iterator that appends to a buffer. +// It is used to reduce symbol sizes for the common case. +class appender : public std::back_insert_iterator<detail::buffer<char>> { + using base = std::back_insert_iterator<detail::buffer<char>>; + + public: + using std::back_insert_iterator<detail::buffer<char>>::back_insert_iterator; + appender(base it) noexcept : base(it) {} + FMT_UNCHECKED_ITERATOR(appender); + + auto operator++() noexcept -> appender& { return *this; } + auto operator++(int) noexcept -> appender { return *this; } +}; + +namespace detail { + +template <typename Context, typename T> +constexpr auto has_const_formatter_impl(T*) + -> decltype(typename Context::template formatter_type<T>().format( + std::declval<const T&>(), std::declval<Context&>()), + true) { + return true; +} +template <typename Context> +constexpr auto has_const_formatter_impl(...) -> bool { + return false; +} +template <typename T, typename Context> +constexpr auto has_const_formatter() -> bool { + return has_const_formatter_impl<Context>(static_cast<T*>(nullptr)); +} template <typename T> using buffer_appender = conditional_t<std::is_same<T, char>::value, appender, @@ -1091,29 +1117,21 @@ template <typename T, typename OutputIt> auto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> { return iterator_buffer<OutputIt, T>(out); } +template <typename T, typename Buf, + FMT_ENABLE_IF(std::is_base_of<buffer<char>, Buf>::value)> +auto get_buffer(std::back_insert_iterator<Buf> out) -> buffer<char>& { + return get_container(out); +} -template <typename Buffer> -auto get_iterator(Buffer& buf) -> decltype(buf.out()) { +template <typename Buf, typename OutputIt> +FMT_INLINE auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { return buf.out(); } -template <typename T> auto get_iterator(buffer<T>& buf) -> buffer_appender<T> { - return buffer_appender<T>(buf); +template <typename T, typename OutputIt> +auto get_iterator(buffer<T>&, OutputIt out) -> OutputIt { + return out; } -template <typename T, typename Char = char, typename Enable = void> -struct fallback_formatter { - fallback_formatter() = delete; -}; - -// Specifies if T has an enabled fallback_formatter specialization. -template <typename T, typename Char> -using has_fallback_formatter = -#ifdef FMT_DEPRECATED_OSTREAM - std::is_constructible<fallback_formatter<T, Char>>; -#else - std::false_type; -#endif - struct view {}; template <typename Char, typename T> struct named_arg : view { @@ -1198,7 +1216,6 @@ constexpr auto count_statically_named_args() -> size_t { struct unformattable {}; struct unformattable_char : unformattable {}; -struct unformattable_const : unformattable {}; struct unformattable_pointer : unformattable {}; template <typename Char> struct string_value { @@ -1265,21 +1282,17 @@ template <typename Context> class value { FMT_INLINE value(const named_arg_info<char_type>* args, size_t size) : named_args{args, size} {} - template <typename T> FMT_CONSTEXPR FMT_INLINE value(T& val) { - using value_type = remove_cvref_t<T>; - custom.value = const_cast<value_type*>(&val); + template <typename T> FMT_CONSTEXPR20 FMT_INLINE value(T& val) { + using value_type = remove_const_t<T>; + custom.value = const_cast<value_type*>(std::addressof(val)); // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter<T>` for `format` and // `printf_formatter<T>` for `printf`. custom.format = format_custom_arg< - value_type, - conditional_t<has_formatter<value_type, Context>::value, - typename Context::template formatter_type<value_type>, - fallback_formatter<value_type, char_type>>>; + value_type, typename Context::template formatter_type<value_type>>; } value(unformattable); value(unformattable_char); - value(unformattable_const); value(unformattable_pointer); private: @@ -1296,29 +1309,25 @@ template <typename Context> class value { } }; -template <typename Context, typename T> -FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg<Context>; - // To minimize the number of types we need to deal with, long is translated // either to int or to long long depending on its size. enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t<long_short, int, long long>; using ulong_type = conditional_t<long_short, unsigned, unsigned long long>; -#ifdef __cpp_lib_byte -inline auto format_as(std::byte b) -> unsigned char { - return static_cast<unsigned char>(b); -} -#endif - -template <typename T> struct has_format_as { - template <typename U, typename V = decltype(format_as(U())), - FMT_ENABLE_IF(std::is_enum<U>::value&& std::is_integral<V>::value)> - static auto check(U*) -> std::true_type; - static auto check(...) -> std::false_type; +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(...) -> void; - enum { value = decltype(check(static_cast<T*>(nullptr)))::value }; + using type = decltype(map(static_cast<T*>(nullptr))); }; +template <typename T> using format_as_t = typename format_as_result<T>::type; + +template <typename T> +struct has_format_as + : bool_constant<!std::is_same<format_as_t<T>, void>::value> {}; // Maps formatting arguments to core types. // arg_mapper reports errors by returning unformattable instead of using @@ -1395,25 +1404,6 @@ template <typename Context> struct arg_mapper { FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { return {}; } - template <typename T, - FMT_ENABLE_IF( - std::is_convertible<T, basic_string_view<char_type>>::value && - !is_string<T>::value && !has_formatter<T, Context>::value && - !has_fallback_formatter<T, char_type>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view<char_type> { - return basic_string_view<char_type>(val); - } - template <typename T, - FMT_ENABLE_IF( - std::is_convertible<T, std_string_view<char_type>>::value && - !std::is_convertible<T, basic_string_view<char_type>>::value && - !is_string<T>::value && !has_formatter<T, Context>::value && - !has_fallback_formatter<T, char_type>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view<char_type> { - return std_string_view<char_type>(val); - } FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { @@ -1423,16 +1413,15 @@ template <typename Context> struct arg_mapper { return val; } - // We use SFINAE instead of a const T* parameter to avoid conflicting with - // the C array overload. + // Use SFINAE instead of a const T* parameter to avoid a conflict with the + // array overload. template < typename T, FMT_ENABLE_IF( std::is_pointer<T>::value || std::is_member_pointer<T>::value || std::is_function<typename std::remove_pointer<T>::type>::value || - (std::is_convertible<const T&, const void*>::value && - !std::is_convertible<const T&, const char_type*>::value && - !has_formatter<T, Context>::value))> + (std::is_array<T>::value && + !std::is_convertible<T, const char_type*>::value))> FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { return {}; } @@ -1443,62 +1432,40 @@ template <typename Context> struct arg_mapper { return values; } - template <typename T, - FMT_ENABLE_IF( - std::is_enum<T>::value&& std::is_convertible<T, int>::value && - !has_format_as<T>::value && !has_formatter<T, Context>::value && - !has_fallback_formatter<T, char_type>::value)> - FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> decltype(std::declval<arg_mapper>().map( - static_cast<underlying_t<T>>(val))) { - return map(static_cast<underlying_t<T>>(val)); - } - - template <typename T, FMT_ENABLE_IF(has_format_as<T>::value && - !has_formatter<T, Context>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> decltype(std::declval<arg_mapper>().map(format_as(T()))) { + // 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())) { return map(format_as(val)); } - template <typename T, typename U = remove_cvref_t<T>> - struct formattable - : bool_constant<has_const_formatter<U, Context>() || - !std::is_const<remove_reference_t<T>>::value || - has_fallback_formatter<U, char_type>::value> {}; + template <typename T, typename U = remove_const_t<T>> + struct formattable : bool_constant<has_const_formatter<U, Context>() || + (has_formatter<U, Context>::value && + !std::is_const<T>::value)> {}; -#if (FMT_MSC_VERSION != 0 && FMT_MSC_VERSION < 1910) || \ - FMT_ICC_VERSION != 0 || defined(__NVCC__) - // Workaround a bug in MSVC and Intel (Issue 2746). - template <typename T> FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { - return val; - } -#else template <typename T, FMT_ENABLE_IF(formattable<T>::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + FMT_CONSTEXPR FMT_INLINE auto do_map(T& val) -> T& { return val; } template <typename T, FMT_ENABLE_IF(!formattable<T>::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { + FMT_CONSTEXPR FMT_INLINE auto do_map(T&) -> unformattable { return {}; } -#endif - template <typename T, typename U = remove_cvref_t<T>, - FMT_ENABLE_IF(!is_string<U>::value && !is_char<U>::value && - !std::is_array<U>::value && - !std::is_pointer<U>::value && - !has_format_as<U>::value && - (has_formatter<U, Context>::value || - has_fallback_formatter<U, char_type>::value))> - FMT_CONSTEXPR FMT_INLINE auto map(T&& val) - -> decltype(this->do_map(std::forward<T>(val))) { - return do_map(std::forward<T>(val)); + template <typename T, typename U = remove_const_t<T>, + FMT_ENABLE_IF((std::is_class<U>::value || std::is_enum<U>::value || + std::is_union<U>::value) && + !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)) { + 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(std::declval<arg_mapper>().map(named_arg.value)) { + -> decltype(this->map(named_arg.value)) { return map(named_arg.value); } @@ -1517,27 +1484,120 @@ enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; -FMT_END_DETAIL_NAMESPACE +template <typename Char, typename InputIt> +auto copy_str(InputIt begin, InputIt end, appender out) -> appender { + get_container(out).append(begin, end); + return out; +} +template <typename Char, typename InputIt> +auto copy_str(InputIt begin, InputIt end, + std::back_insert_iterator<std::string> out) + -> std::back_insert_iterator<std::string> { + get_container(out).append(begin, end); + return out; +} -// An output iterator that appends to a buffer. -// It is used to reduce symbol sizes for the common case. -class appender : public std::back_insert_iterator<detail::buffer<char>> { - using base = std::back_insert_iterator<detail::buffer<char>>; +template <typename Char, typename R, typename OutputIt> +FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { + return detail::copy_str<Char>(rng.begin(), rng.end(), out); +} - template <typename T> - friend auto get_buffer(appender out) -> detail::buffer<char>& { - return detail::get_container(out); - } +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template <typename...> struct void_t_impl { using type = void; }; +template <typename... T> using void_t = typename void_t_impl<T...>::type; +#else +template <typename...> using void_t = void; +#endif + +template <typename It, typename T, typename Enable = void> +struct is_output_iterator : std::false_type {}; + +template <typename It, typename T> +struct is_output_iterator< + It, T, + void_t<typename std::iterator_traits<It>::iterator_category, + decltype(*std::declval<It>() = std::declval<T>())>> + : std::true_type {}; + +template <typename It> struct is_back_insert_iterator : std::false_type {}; +template <typename Container> +struct is_back_insert_iterator<std::back_insert_iterator<Container>> + : std::true_type {}; + +// A type-erased reference to an std::locale to avoid a heavy <locale> include. +class locale_ref { + private: + const void* locale_; // A type-erased pointer to std::locale. public: - using std::back_insert_iterator<detail::buffer<char>>::back_insert_iterator; - appender(base it) noexcept : base(it) {} - FMT_UNCHECKED_ITERATOR(appender); + constexpr FMT_INLINE locale_ref() : locale_(nullptr) {} + template <typename Locale> explicit locale_ref(const Locale& loc); - auto operator++() noexcept -> appender& { return *this; } - auto operator++(int) noexcept -> appender { return *this; } + explicit operator bool() const noexcept { return locale_ != nullptr; } + + template <typename Locale> auto get() const -> Locale; }; +template <typename> constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template <typename Context, typename Arg, typename... Args> +constexpr auto encode_types() -> unsigned long long { + return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) | + (encode_types<Context, Args...>() << packed_arg_bits); +} + +#if defined(__cpp_if_constexpr) +// This type is intentionally undefined, only used for errors +template <typename T, typename Char> struct type_is_unformattable_for; +#endif + +template <bool PACKED, typename Context, typename T, FMT_ENABLE_IF(PACKED)> +FMT_CONSTEXPR FMT_INLINE auto make_arg(T& val) -> value<Context> { + using arg_type = remove_cvref_t<decltype(arg_mapper<Context>().map(val))>; + + constexpr bool formattable_char = + !std::is_same<arg_type, unformattable_char>::value; + static_assert(formattable_char, "Mixing character types is disallowed."); + + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + constexpr bool formattable_pointer = + !std::is_same<arg_type, unformattable_pointer>::value; + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); + + constexpr bool formattable = !std::is_same<arg_type, unformattable>::value; +#if defined(__cpp_if_constexpr) + if constexpr (!formattable) { + type_is_unformattable_for<T, typename Context::char_type> _; + } +#endif + static_assert( + formattable, + "Cannot format an argument. To make type T formattable provide a " + "formatter<T> specialization: https://fmt.dev/latest/api.html#udt"); + return {arg_mapper<Context>().map(val)}; +} + +template <typename Context, typename T> +FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg<Context> { + auto arg = basic_format_arg<Context>(); + arg.type_ = mapped_type_constant<T, Context>::value; + arg.value_ = make_arg<true, Context>(val); + return arg; +} + +template <bool PACKED, typename Context, typename T, FMT_ENABLE_IF(!PACKED)> +FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg<Context> { + return make_arg<Context>(val); +} +} // namespace detail +FMT_BEGIN_EXPORT + // A formatting argument. It is a trivially copyable/constructible type to // allow storage in basic_memory_buffer. template <typename Context> class basic_format_arg { @@ -1546,7 +1606,7 @@ template <typename Context> class basic_format_arg { detail::type type_; template <typename ContextType, typename T> - friend FMT_CONSTEXPR auto detail::make_arg(T&& value) + friend FMT_CONSTEXPR auto detail::make_arg(T& value) -> basic_format_arg<ContextType>; template <typename Visitor, typename Ctx> @@ -1600,6 +1660,7 @@ template <typename Context> class basic_format_arg { ``vis(value)`` will be called with the value of type ``double``. \endrst */ +// DEPRECATED! template <typename Visitor, typename Context> FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) { @@ -1641,131 +1702,8 @@ FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( return vis(monostate()); } -FMT_BEGIN_DETAIL_NAMESPACE - -template <typename Char, typename InputIt> -auto copy_str(InputIt begin, InputIt end, appender out) -> appender { - get_container(out).append(begin, end); - return out; -} - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template <typename... Ts> struct void_t_impl { using type = void; }; -template <typename... Ts> -using void_t = typename detail::void_t_impl<Ts...>::type; -#else -template <typename...> using void_t = void; -#endif - -template <typename It, typename T, typename Enable = void> -struct is_output_iterator : std::false_type {}; - -template <typename It, typename T> -struct is_output_iterator< - It, T, - void_t<typename std::iterator_traits<It>::iterator_category, - decltype(*std::declval<It>() = std::declval<T>())>> - : std::true_type {}; - -template <typename OutputIt> -struct is_back_insert_iterator : std::false_type {}; -template <typename Container> -struct is_back_insert_iterator<std::back_insert_iterator<Container>> - : std::true_type {}; - -template <typename OutputIt> -struct is_contiguous_back_insert_iterator : std::false_type {}; -template <typename Container> -struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>> - : is_contiguous<Container> {}; -template <> -struct is_contiguous_back_insert_iterator<appender> : std::true_type {}; - -// A type-erased reference to an std::locale to avoid a heavy <locale> include. -class locale_ref { - private: - const void* locale_; // A type-erased pointer to std::locale. - - public: - constexpr locale_ref() : locale_(nullptr) {} - template <typename Locale> explicit locale_ref(const Locale& loc); - - explicit operator bool() const noexcept { return locale_ != nullptr; } - - template <typename Locale> auto get() const -> Locale; -}; - -template <typename> constexpr auto encode_types() -> unsigned long long { - return 0; -} - -template <typename Context, typename Arg, typename... Args> -constexpr auto encode_types() -> unsigned long long { - return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) | - (encode_types<Context, Args...>() << packed_arg_bits); -} - -template <typename Context, typename T> -FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value<Context> { - const auto& arg = arg_mapper<Context>().map(std::forward<T>(val)); - - constexpr bool formattable_char = - !std::is_same<decltype(arg), const unformattable_char&>::value; - static_assert(formattable_char, "Mixing character types is disallowed."); - - constexpr bool formattable_const = - !std::is_same<decltype(arg), const unformattable_const&>::value; - static_assert(formattable_const, "Cannot format a const argument."); - - // Formatting of arbitrary pointers is disallowed. If you want to output - // a pointer cast it to "void *" or "const void *". In particular, this - // forbids formatting of "[const] volatile char *" which is printed as bool - // by iostreams. - constexpr bool formattable_pointer = - !std::is_same<decltype(arg), const unformattable_pointer&>::value; - static_assert(formattable_pointer, - "Formatting of non-void pointers is disallowed."); - - constexpr bool formattable = - !std::is_same<decltype(arg), const unformattable&>::value; - static_assert( - formattable, - "Cannot format an argument. To make type T formattable provide a " - "formatter<T> specialization: https://fmt.dev/latest/api.html#udt"); - return {arg}; -} - -template <typename Context, typename T> -FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg<Context> { - basic_format_arg<Context> arg; - arg.type_ = mapped_type_constant<T, Context>::value; - arg.value_ = make_value<Context>(value); - return arg; -} - -// The type template parameter is there to avoid an ODR violation when using -// a fallback formatter in one translation unit and an implicit conversion in -// another (not recommended). -template <bool IS_PACKED, typename Context, type, typename T, - FMT_ENABLE_IF(IS_PACKED)> -FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value<Context> { - return make_value<Context>(val); -} - -template <bool IS_PACKED, typename Context, type, typename T, - FMT_ENABLE_IF(!IS_PACKED)> -FMT_CONSTEXPR inline auto make_arg(T&& value) -> basic_format_arg<Context> { - return make_arg<Context>(value); -} -FMT_END_DETAIL_NAMESPACE - // Formatting context. template <typename OutputIt, typename Char> class basic_format_context { - public: - /** The character type for the output. */ - using char_type = Char; - private: OutputIt out_; basic_format_args<basic_format_context> args_; @@ -1774,31 +1712,32 @@ template <typename OutputIt, typename Char> class basic_format_context { public: using iterator = OutputIt; using format_arg = basic_format_arg<basic_format_context>; + using format_args = basic_format_args<basic_format_context>; using parse_context_type = basic_format_parse_context<Char>; - template <typename T> using formatter_type = formatter<T, char_type>; + template <typename T> using formatter_type = formatter<T, Char>; + + /** The character type for the output. */ + using char_type = Char; basic_format_context(basic_format_context&&) = default; basic_format_context(const basic_format_context&) = delete; void operator=(const basic_format_context&) = delete; /** - Constructs a ``basic_format_context`` object. References to the arguments are - stored in the object so make sure they have appropriate lifetimes. + Constructs a ``basic_format_context`` object. References to the arguments + are stored in the object so make sure they have appropriate lifetimes. */ - constexpr basic_format_context( - OutputIt out, basic_format_args<basic_format_context> ctx_args, - detail::locale_ref loc = detail::locale_ref()) + constexpr basic_format_context(OutputIt out, format_args ctx_args, + detail::locale_ref loc = {}) : out_(out), args_(ctx_args), loc_(loc) {} constexpr auto arg(int id) const -> format_arg { return args_.get(id); } - FMT_CONSTEXPR auto arg(basic_string_view<char_type> name) -> format_arg { + FMT_CONSTEXPR auto arg(basic_string_view<Char> name) -> format_arg { return args_.get(name); } - FMT_CONSTEXPR auto arg_id(basic_string_view<char_type> name) -> int { + FMT_CONSTEXPR auto arg_id(basic_string_view<Char> name) -> int { return args_.get_id(name); } - auto args() const -> const basic_format_args<basic_format_context>& { - return args_; - } + auto args() const -> const format_args& { return args_; } FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } @@ -1819,16 +1758,10 @@ using buffer_context = basic_format_context<detail::buffer_appender<Char>, Char>; using format_context = buffer_context<char>; -// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. -#define FMT_BUFFER_CONTEXT(Char) \ - basic_format_context<detail::buffer_appender<Char>, Char> - template <typename T, typename Char = char> -using is_formattable = bool_constant< - !std::is_base_of<detail::unformattable, - decltype(detail::arg_mapper<buffer_context<Char>>().map( - std::declval<T>()))>::value && - !detail::has_fallback_formatter<T, Char>::value>; +using is_formattable = bool_constant<!std::is_base_of< + detail::unformattable, decltype(detail::arg_mapper<buffer_context<Char>>() + .map(std::declval<T&>()))>::value>; /** \rst @@ -1846,7 +1779,7 @@ class format_arg_store { private: static const size_t num_args = sizeof...(Args); - static const size_t num_named_args = detail::count_named_args<Args...>(); + static constexpr size_t num_named_args = detail::count_named_args<Args...>(); static const bool is_packed = num_args <= detail::max_packed_args; using value_type = conditional_t<is_packed, detail::value<Context>, @@ -1867,16 +1800,14 @@ class format_arg_store public: template <typename... T> - FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) + FMT_CONSTEXPR FMT_INLINE format_arg_store(T&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args<Context>(*this), #endif - data_{detail::make_arg< - is_packed, Context, - detail::mapped_type_constant<remove_cvref_t<T>, Context>::value>( - std::forward<T>(args))...} { - detail::init_named_args(data_.named_args(), 0, 0, args...); + data_{detail::make_arg<is_packed, Context>(args)...} { + if (detail::const_check(num_named_args != 0)) + detail::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1884,14 +1815,15 @@ class format_arg_store \rst Constructs a `~fmt::format_arg_store` object that contains references to arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::context`. + can be omitted in which case it defaults to `~fmt::format_context`. See `~fmt::arg` for lifetime considerations. \endrst */ -template <typename Context = format_context, typename... Args> -constexpr auto make_format_args(Args&&... args) - -> format_arg_store<Context, remove_cvref_t<Args>...> { - return {std::forward<Args>(args)...}; +// Arguments are taken by lvalue references to avoid some lifetime issues. +template <typename Context = format_context, typename... T> +constexpr auto make_format_args(T&... args) + -> format_arg_store<Context, remove_cvref_t<T>...> { + return {args...}; } /** @@ -1910,6 +1842,7 @@ inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> { static_assert(!detail::is_named_arg<T>(), "nested named arguments"); return {name, arg}; } +FMT_END_EXPORT /** \rst @@ -1918,7 +1851,7 @@ inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> { ``vformat``:: void vlog(string_view format_str, format_args args); // OK - format_args args = make_format_args(42); // Error: dangling reference + format_args args = make_format_args(); // Error: dangling reference \endrst */ template <typename Context> class basic_format_args { @@ -2035,7 +1968,7 @@ template <typename Context> class basic_format_args { /** An alias to ``basic_format_args<format_context>``. */ // A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). -using format_args = basic_format_args<format_context>; +FMT_EXPORT using format_args = basic_format_args<format_context>; // We cannot use enum classes as bit fields because of a gcc bug, so we put them // in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). @@ -2056,7 +1989,7 @@ enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; } using sign_t = sign::type; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // Workaround an array initialization issue in gcc 4.8. template <typename Char> struct fill_t { @@ -2068,7 +2001,7 @@ template <typename Char> struct fill_t { public: FMT_CONSTEXPR void operator=(basic_string_view<Char> s) { auto size = s.size(); - if (size > max_size) return throw_format_error("invalid fill"); + FMT_ASSERT(size <= max_size, "invalid fill"); for (size_t i = 0; i < size; ++i) data_[i] = s[i]; size_ = static_cast<unsigned char>(size); } @@ -2081,11 +2014,10 @@ template <typename Char> struct fill_t { return data_[index]; } }; -FMT_END_DETAIL_NAMESPACE +} // namespace detail enum class presentation_type : unsigned char { none, - // Integer types should go first, dec, // 'd' oct, // 'o' hex_lower, // 'x' @@ -2107,7 +2039,7 @@ enum class presentation_type : unsigned char { }; // Format specifiers for built-in and string types. -template <typename Char> struct basic_format_specs { +template <typename Char = char> struct format_specs { int width; int precision; presentation_type type; @@ -2117,7 +2049,7 @@ template <typename Char> struct basic_format_specs { bool localized : 1; detail::fill_t<Char> fill; - constexpr basic_format_specs() + constexpr format_specs() : width(0), precision(-1), type(presentation_type::none), @@ -2127,9 +2059,7 @@ template <typename Char> struct basic_format_specs { localized(false) {} }; -using format_specs = basic_format_specs<char>; - -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { enum class arg_id_kind { none, index, name }; @@ -2150,7 +2080,7 @@ template <typename Char> struct arg_ref { arg_id_kind kind; union value { - FMT_CONSTEXPR value(int id = 0) : index{id} {} + FMT_CONSTEXPR value(int idx = 0) : index(idx) {} FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {} int index; @@ -2159,128 +2089,30 @@ template <typename Char> struct arg_ref { }; // Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow re-using the same parsed specifiers with +// than parsing time to allow reusing the same parsed specifiers with // different sets of arguments (precompilation of format strings). -template <typename Char> -struct dynamic_format_specs : basic_format_specs<Char> { +template <typename Char = char> +struct dynamic_format_specs : format_specs<Char> { arg_ref<Char> width_ref; arg_ref<Char> precision_ref; }; -struct auto_id {}; - -// A format specifier handler that sets fields in basic_format_specs. -template <typename Char> class specs_setter { - protected: - basic_format_specs<Char>& specs_; - - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char>& specs) - : specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter& other) - : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) { - specs_.fill = fill; - } - FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } - FMT_CONSTEXPR void on_hash() { specs_.alt = true; } - FMT_CONSTEXPR void on_localized() { specs_.localized = true; } - - FMT_CONSTEXPR void on_zero() { - if (specs_.align == align::none) specs_.align = align::numeric; - specs_.fill[0] = Char('0'); - } - - FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } - FMT_CONSTEXPR void on_precision(int precision) { - specs_.precision = precision; - } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template <typename ParseContext> -class dynamic_specs_handler - : public specs_setter<typename ParseContext::char_type> { - public: - using char_type = typename ParseContext::char_type; - - FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs<char_type>& specs, - ParseContext& ctx) - : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {} - - FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) - : specs_setter<char_type>(other), - specs_(other.specs_), - context_(other.context_) {} - - template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - specs_.width_ref = make_arg_ref(arg_id); - } - - template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - specs_.precision_ref = make_arg_ref(arg_id); - } - - FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); - } - - private: - dynamic_format_specs<char_type>& specs_; - ParseContext& context_; - - using arg_ref_type = arg_ref<char_type>; - - FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { - context_.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { - return arg_ref_type(context_.next_arg_id()); - } - - FMT_CONSTEXPR auto make_arg_ref(basic_string_view<char_type> arg_id) - -> arg_ref_type { - context_.check_arg_id(arg_id); - basic_string_view<char_type> format_str( - context_.begin(), to_unsigned(context_.end() - context_.begin())); - return arg_ref_type(arg_id); - } -}; - -template <typename Char> constexpr bool is_ascii_letter(Char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -// Converts a character to ASCII. Returns a number > 127 on conversion failure. +// Converts a character to ASCII. Returns '\0' on conversion failure. template <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)> -constexpr auto to_ascii(Char c) -> Char { - return c; +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast<char>(c) : '\0'; } template <typename Char, FMT_ENABLE_IF(std::is_enum<Char>::value)> -constexpr auto to_ascii(Char c) -> underlying_t<Char> { - return c; +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast<char>(c) : '\0'; } +// Returns the number of code units in a code point or 1 on error. template <typename Char> FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { if (const_check(sizeof(Char) != 1)) return 1; - auto lengths = - "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4"; - int len = lengths[static_cast<unsigned char>(*begin) >> 3]; - - // Compute the pointer to the next character early so that the next - // iteration can start working on the next character. Neither Clang - // nor GCC figure out this reordering on their own. - return len + !len; + auto c = static_cast<unsigned char>(*begin); + return static_cast<int>((0x3a55000000000000ull >> (2 * (c >> 3))) & 0x3) + 1; } // Return the result via the out param to workaround gcc bug 77539. @@ -2325,279 +2157,284 @@ FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, : error_value; } -// Parses fill and alignment. -template <typename Char, typename Handler> -FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); - auto align = align::none; - auto p = begin + code_point_length(begin); - if (end - p <= 0) p = begin; - for (;;) { - switch (to_ascii(*p)) { - case '<': - align = align::left; - break; - case '>': - align = align::right; - break; - case '^': - align = align::center; - break; - default: - break; - } - if (align != align::none) { - if (p != begin) { - auto c = *begin; - if (c == '{') - return handler.on_error("invalid fill character '{'"), begin; - handler.on_fill(basic_string_view<Char>(begin, to_unsigned(p - begin))); - begin = p + 1; - } else - ++begin; - handler.on_align(align); - break; - } else if (p == begin) { - break; - } - p = begin; +FMT_CONSTEXPR inline auto parse_align(char c) -> align_t { + switch (c) { + case '<': + return align::left; + case '>': + return align::right; + case '^': + return align::center; } - return begin; + return align::none; } -template <typename Char> FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +template <typename Char> constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; } -template <typename Char, typename IDHandler> +template <typename Char, typename Handler> FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); + Handler&& handler) -> const Char* { Char c = *begin; if (c >= '0' && c <= '9') { int index = 0; + constexpr int max = (std::numeric_limits<int>::max)(); if (c != '0') - index = - parse_nonnegative_int(begin, end, (std::numeric_limits<int>::max)()); + index = parse_nonnegative_int(begin, end, max); else ++begin; if (begin == end || (*begin != '}' && *begin != ':')) - handler.on_error("invalid format string"); + throw_format_error("invalid format string"); else - handler(index); + handler.on_index(index); return begin; } if (!is_name_start(c)) { - handler.on_error("invalid format string"); + throw_format_error("invalid format string"); return begin; } auto it = begin; do { ++it; - } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); - handler(basic_string_view<Char>(begin, to_unsigned(it - begin))); + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); return it; } -template <typename Char, typename IDHandler> +template <typename Char, typename Handler> FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); Char c = *begin; if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); - handler(); + handler.on_auto(); return begin; } -template <typename Char, typename Handler> -FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct width_adapter { - Handler& handler; +template <typename Char> struct dynamic_spec_id_handler { + basic_format_parse_context<Char>& ctx; + arg_ref<Char>& ref; - FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_dynamic_width(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; + FMT_CONSTEXPR void on_auto() { + int id = ctx.next_arg_id(); + ref = arg_ref<Char>(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_index(int id) { + ref = arg_ref<Char>(id); + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view<Char> id) { + ref = arg_ref<Char>(id); + ctx.check_arg_id(id); + } +}; +// Parses [integer | "{" [arg_id] "}"]. +template <typename Char> +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref<Char>& ref, + basic_format_parse_context<Char>& ctx) + -> const Char* { FMT_ASSERT(begin != end, ""); if ('0' <= *begin && *begin <= '9') { - int width = parse_nonnegative_int(begin, end, -1); - if (width != -1) - handler.on_width(width); + int val = parse_nonnegative_int(begin, end, -1); + if (val != -1) + value = val; else - handler.on_error("number is too big"); + throw_format_error("number is too big"); } else if (*begin == '{') { ++begin; - if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); - if (begin == end || *begin != '}') - return handler.on_error("invalid format string"), begin; - ++begin; + auto handler = dynamic_spec_id_handler<Char>{ctx, ref}; + if (begin != end) begin = parse_arg_id(begin, end, handler); + if (begin != end && *begin == '}') return ++begin; + throw_format_error("invalid format string"); } return begin; } -template <typename Char, typename Handler> -FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct precision_adapter { - Handler& handler; - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_dynamic_precision(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - ++begin; - auto c = begin != end ? *begin : Char(); - if ('0' <= c && c <= '9') { - auto precision = parse_nonnegative_int(begin, end, -1); - if (precision != -1) - handler.on_precision(precision); - else - handler.on_error("number is too big"); - } else if (c == '{') { - ++begin; - if (begin != end) - begin = parse_arg_id(begin, end, precision_adapter{handler}); - if (begin == end || *begin++ != '}') - return handler.on_error("invalid format string"), begin; - } else { - return handler.on_error("missing precision specifier"), begin; - } - handler.end_precision(); - return begin; -} - template <typename Char> -FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { - switch (to_ascii(type)) { - case 'd': - return presentation_type::dec; - case 'o': - return presentation_type::oct; - case 'x': - return presentation_type::hex_lower; - case 'X': - return presentation_type::hex_upper; - case 'b': - return presentation_type::bin_lower; - case 'B': - return presentation_type::bin_upper; - case 'a': - return presentation_type::hexfloat_lower; - case 'A': - return presentation_type::hexfloat_upper; - case 'e': - return presentation_type::exp_lower; - case 'E': - return presentation_type::exp_upper; - case 'f': - return presentation_type::fixed_lower; - case 'F': - return presentation_type::fixed_upper; - case 'g': - return presentation_type::general_lower; - case 'G': - return presentation_type::general_upper; - case 'c': - return presentation_type::chr; - case 's': - return presentation_type::string; - case 'p': - return presentation_type::pointer; - case '?': - return presentation_type::debug; - default: - return presentation_type::none; - } -} - -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -template <typename Char, typename SpecHandler> -FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, - const Char* end, - SpecHandler&& handler) +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + int& value, arg_ref<Char>& ref, + basic_format_parse_context<Char>& ctx) -> const Char* { - if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && - *begin != 'L') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); + ++begin; + if (begin == end || *begin == '}') { + throw_format_error("invalid precision"); return begin; } + return parse_dynamic_spec(begin, end, value, ref, ctx); +} - if (begin == end) return begin; - - begin = parse_align(begin, end, handler); - if (begin == end) return begin; - - // Parse sign. - switch (to_ascii(*begin)) { - case '+': - handler.on_sign(sign::plus); - ++begin; - break; - case '-': - handler.on_sign(sign::minus); - ++begin; - break; - case ' ': - handler.on_sign(sign::space); - ++begin; - break; - default: - break; - } - if (begin == end) return begin; - - if (*begin == '#') { - handler.on_hash(); - if (++begin == end) return begin; - } - - // Parse zero flag. - if (*begin == '0') { - handler.on_zero(); - if (++begin == end) return begin; - } - - begin = parse_width(begin, end, handler); - if (begin == end) return begin; +enum class state { start, align, sign, hash, zero, width, precision, locale }; - // Parse precision. - if (*begin == '.') { - begin = parse_precision(begin, end, handler); +// Parses standard format specifiers. +template <typename Char> +FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( + const Char* begin, const Char* end, dynamic_format_specs<Char>& specs, + basic_format_parse_context<Char>& ctx, type arg_type) -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { if (begin == end) return begin; + c = to_ascii(*begin); } - if (*begin == 'L') { - handler.on_localized(); - ++begin; - } + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + throw_format_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + dynamic_format_specs<Char>& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres type, int set) -> const Char* { + if (!in(arg_type, set)) throw_format_error("invalid format specifier"); + specs.type = type; + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; - // Parse type. - if (begin != end && *begin != '}') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.align = parse_align(c); + ++begin; + break; + case '+': + case '-': + case ' ': + enter_state(state::sign, in(arg_type, sint_set | float_set)); + switch (c) { + case '+': + specs.sign = sign::plus; + break; + case '-': + specs.sign = sign::minus; + break; + case ' ': + specs.sign = sign::space; + break; + } + ++begin; + break; + case '#': + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.alt = true; + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) + throw_format_error("format specifier requires numeric argument"); + if (specs.align == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.align = align::numeric; + specs.fill[0] = Char('0'); + } + ++begin; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '{': + enter_state(state::width); + begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); + break; + case '.': + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs.precision, specs.precision_ref, + ctx); + break; + case 'L': + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.localized = true; + ++begin; + break; + case 'd': + return parse_presentation_type(pres::dec, integral_set); + case 'o': + return parse_presentation_type(pres::oct, integral_set); + case 'x': + return parse_presentation_type(pres::hex_lower, integral_set); + case 'X': + return parse_presentation_type(pres::hex_upper, integral_set); + case 'b': + return parse_presentation_type(pres::bin_lower, integral_set); + case 'B': + return parse_presentation_type(pres::bin_upper, integral_set); + case 'a': + return parse_presentation_type(pres::hexfloat_lower, float_set); + case 'A': + return parse_presentation_type(pres::hexfloat_upper, float_set); + case 'e': + return parse_presentation_type(pres::exp_lower, float_set); + case 'E': + return parse_presentation_type(pres::exp_upper, float_set); + case 'f': + return parse_presentation_type(pres::fixed_lower, float_set); + case 'F': + return parse_presentation_type(pres::fixed_upper, float_set); + case 'g': + return parse_presentation_type(pres::general_lower, float_set); + case 'G': + return parse_presentation_type(pres::general_upper, float_set); + case 'c': + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': + return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + throw_format_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + throw_format_error("invalid fill character '{'"); + return begin; + } + auto align = parse_align(to_ascii(*fill_end)); + enter_state(state::align, align != align::none); + specs.fill = {begin, to_unsigned(fill_end - begin)}; + specs.align = align; + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); } - return begin; } template <typename Char, typename Handler> @@ -2607,14 +2444,11 @@ FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, Handler& handler; int arg_id; - FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } - FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + FMT_CONSTEXPR void on_auto() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view<Char> id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } }; ++begin; @@ -2643,9 +2477,6 @@ FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, template <bool IS_CONSTEXPR, typename Char, typename Handler> FMT_CONSTEXPR FMT_INLINE void parse_format_string( basic_string_view<Char> format_str, Handler&& handler) { - // Workaround a name-lookup bug in MSVC's modules implementation. - using detail::find; - auto begin = format_str.data(); auto end = begin + format_str.size(); if (end - begin < 32) { @@ -2705,188 +2536,46 @@ FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) -> decltype(ctx.begin()) { using char_type = typename ParseContext::char_type; using context = buffer_context<char_type>; - using stripped_type = typename strip_named_arg<T>::type; using mapped_type = conditional_t< mapped_type_constant<T, context>::value != type::custom_type, decltype(arg_mapper<context>().map(std::declval<const T&>())), - stripped_type>; - auto f = conditional_t<has_formatter<mapped_type, context>::value, - formatter<mapped_type, char_type>, - fallback_formatter<stripped_type, char_type>>(); - return f.parse(ctx); -} - -template <typename ErrorHandler> -FMT_CONSTEXPR void check_int_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type > presentation_type::bin_upper && type != presentation_type::chr) - eh.on_error("invalid type specifier"); + typename strip_named_arg<T>::type>; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_default_constructible_v< + formatter<mapped_type, char_type>>) { + return formatter<mapped_type, char_type>().parse(ctx); + } else { + type_is_unformattable_for<T, char_type> _; + return ctx.begin(); + } +#else + return formatter<mapped_type, char_type>().parse(ctx); +#endif } -// Checks char specs and returns true if the type spec is char (and not int). -template <typename Char, typename ErrorHandler = error_handler> -FMT_CONSTEXPR auto check_char_specs(const basic_format_specs<Char>& specs, - ErrorHandler&& eh = {}) -> bool { +// Checks char specs and returns true iff the presentation type is char-like. +template <typename Char> +FMT_CONSTEXPR auto check_char_specs(const format_specs<Char>& specs) -> bool { if (specs.type != presentation_type::none && specs.type != presentation_type::chr && specs.type != presentation_type::debug) { - check_int_type_spec(specs.type, eh); return false; } if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) - eh.on_error("invalid format specifier for char"); + throw_format_error("invalid format specifier for char"); return true; } -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex -}; - -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool showpoint : 1; -}; - -template <typename ErrorHandler = error_handler, typename Char> -FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs<Char>& specs, - ErrorHandler&& eh = {}) - -> float_specs { - auto result = float_specs(); - result.showpoint = specs.alt; - result.locale = specs.localized; - switch (specs.type) { - case presentation_type::none: - result.format = float_format::general; - break; - case presentation_type::general_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::general_lower: - result.format = float_format::general; - break; - case presentation_type::exp_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::exp_lower: - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::fixed_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::fixed_lower: - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::hexfloat_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::hexfloat_lower: - result.format = float_format::hex; - break; - default: - eh.on_error("invalid type specifier"); - break; - } - return result; -} - -template <typename ErrorHandler = error_handler> -FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, - ErrorHandler&& eh = {}) -> bool { - if (type == presentation_type::none || type == presentation_type::string) - return true; - if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); - return false; -} - -template <typename ErrorHandler = error_handler> -FMT_CONSTEXPR void check_string_type_spec(presentation_type type, - ErrorHandler&& eh = {}) { - if (type != presentation_type::none && type != presentation_type::string && - type != presentation_type::debug) - eh.on_error("invalid type specifier"); -} - -template <typename ErrorHandler> -FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type != presentation_type::none && type != presentation_type::pointer) - eh.on_error("invalid type specifier"); -} - -// A parse_format_specs handler that checks if specifiers are consistent with -// the argument type. -template <typename Handler> class specs_checker : public Handler { - private: - detail::type arg_type_; - - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic_type(arg_type_)) - this->on_error("format specifier requires numeric argument"); - } - - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) - : Handler(handler), arg_type_(arg_type) {} - - FMT_CONSTEXPR void on_align(align_t align) { - if (align == align::numeric) require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_sign(sign_t s) { - require_numeric_argument(); - if (is_integral_type(arg_type_) && arg_type_ != type::int_type && - arg_type_ != type::long_long_type && arg_type_ != type::int128_type && - arg_type_ != type::char_type) { - this->on_error("format specifier requires signed argument"); - } - Handler::on_sign(s); - } - - FMT_CONSTEXPR void on_hash() { - require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_localized() { - require_numeric_argument(); - Handler::on_localized(); - } - - FMT_CONSTEXPR void on_zero() { - require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { - if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) - this->on_error("precision not allowed for this argument type"); - } -}; - -constexpr int invalid_arg_index = -1; - #if FMT_USE_NONTYPE_TEMPLATE_ARGS template <int N, typename T, typename... Args, typename Char> constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int { - if constexpr (detail::is_statically_named_arg<T>()) { + if constexpr (is_statically_named_arg<T>()) { if (name == T::name) return N; } if constexpr (sizeof...(Args) > 0) return get_arg_index_by_name<N + 1, Args...>(name); (void)name; // Workaround an MSVC bug about "unused" parameter. - return invalid_arg_index; + return -1; } #endif @@ -2897,31 +2586,29 @@ FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int { return get_arg_index_by_name<0, Args...>(name); #endif (void)name; - return invalid_arg_index; + return -1; } -template <typename Char, typename ErrorHandler, typename... Args> -class format_string_checker { +template <typename Char, typename... Args> class format_string_checker { private: - // In the future basic_format_parse_context will replace compile_parse_context - // here and will use is_constant_evaluated and downcasting to access the data - // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. - using parse_context_type = compile_parse_context<Char, ErrorHandler>; + using parse_context_type = compile_parse_context<Char>; static constexpr int num_args = sizeof...(Args); // Format specifier parsing function. + // In the future basic_format_parse_context will replace compile_parse_context + // here and will use is_constant_evaluated and downcasting to access the data + // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. using parse_func = const Char* (*)(parse_context_type&); + type types_[num_args > 0 ? static_cast<size_t>(num_args) : 1]; parse_context_type context_; parse_func parse_funcs_[num_args > 0 ? static_cast<size_t>(num_args) : 1]; - type types_[num_args > 0 ? static_cast<size_t>(num_args) : 1]; public: - explicit FMT_CONSTEXPR format_string_checker( - basic_string_view<Char> format_str, ErrorHandler eh) - : context_(format_str, num_args, types_, eh), - parse_funcs_{&parse_format_specs<Args, parse_context_type>...}, - types_{type_constant<Args, char>::value...} {} + explicit FMT_CONSTEXPR format_string_checker(basic_string_view<Char> fmt) + : types_{mapped_type_constant<Args, buffer_context<Char>>::value...}, + context_(fmt, num_args, types_), + parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {} @@ -2932,8 +2619,8 @@ class format_string_checker { FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int { #if FMT_USE_NONTYPE_TEMPLATE_ARGS auto index = get_arg_index_by_name<Args...>(id); - if (index == invalid_arg_index) on_error("named argument is not found"); - return context_.check_arg_id(index), index; + if (index < 0) on_error("named argument is not found"); + return index; #else (void)id; on_error("compile-time checks for named arguments require C++20 support"); @@ -2941,17 +2628,19 @@ class format_string_checker { #endif } - FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) -> const Char* { - context_.advance_to(context_.begin() + (begin - &*context_.begin())); + context_.advance_to(begin); // id >= 0 check is a workaround for gcc 10 bug (#2065). return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; } FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); + throw_format_error(message); } }; @@ -2967,28 +2656,33 @@ FMT_INLINE void check_format_string(const S&) { template <typename... Args, typename S, FMT_ENABLE_IF(is_compile_string<S>::value)> void check_format_string(S format_str) { - FMT_CONSTEXPR auto s = basic_string_view<typename S::char_type>(format_str); - using checker = format_string_checker<typename S::char_type, error_handler, - remove_cvref_t<Args>...>; - FMT_CONSTEXPR bool invalid_format = - (parse_format_string<true>(s, checker(s, {})), true); - ignore_unused(invalid_format); + using char_t = typename S::char_type; + FMT_CONSTEXPR auto s = basic_string_view<char_t>(format_str); + using checker = format_string_checker<char_t, remove_cvref_t<Args>...>; + FMT_CONSTEXPR bool error = (parse_format_string<true>(s, checker(s)), true); + ignore_unused(error); } +template <typename Char = char> struct vformat_args { + using type = basic_format_args< + basic_format_context<std::back_insert_iterator<buffer<Char>>, Char>>; +}; +template <> struct vformat_args<char> { using type = format_args; }; + +// Use vformat_args and avoid type_identity to keep symbols short. template <typename Char> -void vformat_to( - buffer<Char>& buf, basic_string_view<Char> fmt, - basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args, - locale_ref loc = {}); +void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt, + typename vformat_args<Char>::type args, locale_ref loc = {}); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif -FMT_END_DETAIL_NAMESPACE +} // namespace detail + +FMT_BEGIN_EXPORT -// A formatter specialization for the core types corresponding to detail::type -// constants. +// A formatter specialization for natively supported types. template <typename T, typename Char> struct formatter<T, Char, enable_if_t<detail::type_constant<T, Char>::value != @@ -2997,72 +2691,21 @@ struct formatter<T, Char, detail::dynamic_format_specs<Char> specs_; public: - // Parses format specifiers stopping either at the end of the range or at the - // terminating '}'. template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end) return begin; - using handler_type = detail::dynamic_specs_handler<ParseContext>; + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* { auto type = detail::type_constant<T, Char>::value; - auto checker = - detail::specs_checker<handler_type>(handler_type(specs_, ctx), type); - auto it = detail::parse_format_specs(begin, end, checker); - auto eh = ctx.error_handler(); - switch (type) { - case detail::type::none_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case detail::type::bool_type: - if (specs_.type == presentation_type::none || - specs_.type == presentation_type::string) { - break; - } - FMT_FALLTHROUGH; - case detail::type::int_type: - case detail::type::uint_type: - case detail::type::long_long_type: - case detail::type::ulong_long_type: - case detail::type::int128_type: - case detail::type::uint128_type: - detail::check_int_type_spec(specs_.type, eh); - break; - case detail::type::char_type: - detail::check_char_specs(specs_, eh); - break; - case detail::type::float_type: - if (detail::const_check(FMT_USE_FLOAT)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "float support disabled"); - break; - case detail::type::double_type: - if (detail::const_check(FMT_USE_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "double support disabled"); - break; - case detail::type::long_double_type: - if (detail::const_check(FMT_USE_LONG_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "long double support disabled"); - break; - case detail::type::cstring_type: - detail::check_cstring_type_spec(specs_.type, eh); - break; - case detail::type::string_type: - detail::check_string_type_spec(specs_.type, eh); - break; - case detail::type::pointer_type: - detail::check_pointer_type_spec(specs_.type, eh); - break; - case detail::type::custom_type: - // Custom format specifiers are checked in parse functions of - // formatter specializations. - break; - } - return it; + auto end = + detail::parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, type); + if (type == detail::type::char_type) detail::check_char_specs(specs_); + return end; + } + + template <detail::type U = detail::type_constant<T, Char>::value, + FMT_ENABLE_IF(U == detail::type::string_type || + U == detail::type::cstring_type || + U == detail::type::char_type)> + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.type = set ? presentation_type::debug : presentation_type::none; } template <typename FormatContext> @@ -3070,28 +2713,9 @@ struct formatter<T, Char, -> decltype(ctx.out()); }; -#define FMT_FORMAT_AS(Type, Base) \ - template <typename Char> \ - struct formatter<Type, Char> : formatter<Base, Char> { \ - template <typename FormatContext> \ - auto format(Type const& val, FormatContext& ctx) const \ - -> decltype(ctx.out()) { \ - return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \ - } \ - } - -FMT_FORMAT_AS(signed char, int); -FMT_FORMAT_AS(unsigned char, unsigned); -FMT_FORMAT_AS(short, int); -FMT_FORMAT_AS(unsigned short, unsigned); -FMT_FORMAT_AS(long, long long); -FMT_FORMAT_AS(unsigned long, unsigned long long); -FMT_FORMAT_AS(Char*, const Char*); -FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>); -FMT_FORMAT_AS(std::nullptr_t, const void*); -FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>); - -template <typename Char> struct basic_runtime { basic_string_view<Char> str; }; +template <typename Char = char> struct runtime_format_string { + basic_string_view<Char> str; +}; /** A compile-time format string. */ template <typename Char, typename... Args> class basic_format_string { @@ -3111,23 +2735,24 @@ template <typename Char, typename... Args> class basic_format_string { #ifdef FMT_HAS_CONSTEVAL if constexpr (detail::count_named_args<Args...>() == detail::count_statically_named_args<Args...>()) { - using checker = detail::format_string_checker<Char, detail::error_handler, - remove_cvref_t<Args>...>; - detail::parse_format_string<true>(str_, checker(s, {})); + using checker = + detail::format_string_checker<Char, remove_cvref_t<Args>...>; + detail::parse_format_string<true>(str_, checker(s)); } #else detail::check_format_string<Args...>(s); #endif } - basic_format_string(basic_runtime<Char> r) : str_(r.str) {} + basic_format_string(runtime_format_string<Char> fmt) : str_(fmt.str) {} FMT_INLINE operator basic_string_view<Char>() const { return str_; } + FMT_INLINE auto get() const -> basic_string_view<Char> { return str_; } }; #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 // Workaround broken conversion on older gcc. template <typename...> using format_string = string_view; -inline auto runtime(string_view s) -> basic_string_view<char> { return s; } +inline auto runtime(string_view s) -> string_view { return s; } #else template <typename... Args> using format_string = basic_format_string<char, type_identity_t<Args>...>; @@ -3141,7 +2766,7 @@ using format_string = basic_format_string<char, type_identity_t<Args>...>; fmt::print(fmt::runtime("{:d}"), "I am not a number"); \endrst */ -inline auto runtime(string_view s) -> basic_runtime<char> { return {{s}}; } +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } #endif FMT_API auto vformat(string_view fmt, format_args args) -> std::string; @@ -3167,10 +2792,9 @@ FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args) template <typename OutputIt, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { - using detail::get_buffer; - auto&& buf = get_buffer<char>(out); + auto&& buf = detail::get_buffer<char>(out); detail::vformat_to(buf, fmt, args, {}); - return detail::get_iterator(buf); + return detail::get_iterator(buf, out); } /** @@ -3229,7 +2853,7 @@ template <typename... T> FMT_NODISCARD FMT_INLINE auto formatted_size(format_string<T...> fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); + detail::vformat_to<char>(buf, fmt, fmt::make_format_args(args...), {}); return buf.count(); } @@ -3270,7 +2894,25 @@ FMT_INLINE void print(std::FILE* f, format_string<T...> fmt, T&&... args) { : detail::vprint_mojibake(f, fmt, vargs); } -FMT_MODULE_EXPORT_END +/** + Formats ``args`` according to specifications in ``fmt`` and writes the + output to the file ``f`` followed by a newline. + */ +template <typename... T> +FMT_INLINE void println(std::FILE* f, format_string<T...> fmt, T&&... args) { + return fmt::print(f, "{}\n", fmt::format(fmt, std::forward<T>(args)...)); +} + +/** + Formats ``args`` according to specifications in ``fmt`` and writes the output + to ``stdout`` followed by a newline. + */ +template <typename... T> +FMT_INLINE void println(format_string<T...> fmt, T&&... args) { + return fmt::println(stdout, fmt, std::forward<T>(args)...); +} + +FMT_END_EXPORT FMT_GCC_PRAGMA("GCC pop_options") FMT_END_NAMESPACE diff --git a/externals/fmt/include/fmt/format-inl.h b/externals/fmt/include/fmt/format-inl.h index f44df01c..dac2d437 100644 --- a/externals/fmt/include/fmt/format-inl.h +++ b/externals/fmt/include/fmt/format-inl.h @@ -9,13 +9,9 @@ #define FMT_FORMAT_INL_H_ #include <algorithm> -#include <cctype> #include <cerrno> // errno #include <climits> #include <cmath> -#include <cstdarg> -#include <cstring> // std::memmove -#include <cwchar> #include <exception> #ifndef FMT_STATIC_THOUSANDS_SEPARATOR @@ -115,16 +111,43 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif + +FMT_FUNC auto write_loc(appender out, loc_value value, + const format_specs<>& specs, locale_ref loc) -> bool { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + auto locale = loc.get<std::locale>(); + // We cannot use the num_put<char> facet because it may produce output in + // a wrong encoding. + using facet = format_facet<std::locale>; + if (std::has_facet<facet>(locale)) + return std::use_facet<facet>(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); +#endif + return false; +} } // namespace detail -#if !FMT_MSC_VERSION -FMT_API FMT_FUNC format_error::~format_error() noexcept = default; +template <typename Locale> typename Locale::id format_facet<Locale>::id; + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) { + auto& numpunct = std::use_facet<std::numpunct<char>>(loc); + grouping_ = numpunct.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); +} + +template <> +FMT_API FMT_FUNC auto format_facet<std::locale>::do_put( + appender out, loc_value val, const format_specs<>& specs) const -> bool { + return val.visit( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); +} #endif -FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, +FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt, format_args args) { auto ec = std::error_code(error_code, std::generic_category()); - return std::system_error(ec, vformat(format_str, args)); + return std::system_error(ec, vformat(fmt, args)); } namespace detail { @@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { return (n >> r) | (n << (64 - r)); } -// Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { -#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)}; -#elif defined(_MSC_VER) && defined(_M_X64) - auto result = uint128_fallback(); - result.lo_ = _umul128(x, y, &result.hi_); - return result; -#else - const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>()); - - uint64_t a = x >> 32; - uint64_t b = x & mask; - uint64_t c = y >> 32; - uint64_t d = y & mask; - - uint64_t ac = a * c; - uint64_t bc = b * c; - uint64_t ad = a * d; - uint64_t bd = b * d; - - uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); - - return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), - (intermediate << 32) + (bd & mask)}; -#endif -} - // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. namespace dragonbox { -// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { -#if FMT_USE_INT128 - auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y); - return static_cast<uint64_t>(p >> 64); -#elif defined(_MSC_VER) && defined(_M_X64) - return __umulh(x, y); -#else - return umul128(x, y).high(); -#endif -} - -// 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 { - uint128_fallback r = umul128(x, y.high()); - r += umul128_upper64(x, y.low()); - return r; -} - // 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 { @@ -216,25 +189,13 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept { return x * y; } -// 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 { - FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); - static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); - return (e * 315653) >> 20; -} - // Various fast log computations. -inline int floor_log2_pow10(int e) noexcept { - FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); - return (e * 1741647) >> 19; -} inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } -static constexpr struct { +FMT_INLINE_VARIABLE constexpr struct { uint32_t divisor; int shift_amount; } div_small_pow10_infos[] = {{10, 16}, {100, 16}}; @@ -288,7 +249,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept { } // Various subroutines using pow10 cache -template <class T> struct cache_accessor; +template <typename T> struct cache_accessor; template <> struct cache_accessor<float> { using carrier_uint = float_info<float>::carrier_uint; @@ -1009,8 +970,23 @@ template <> struct cache_accessor<double> { {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, {0xc5a05277621be293, 0xc7098b7305241886}, - { 0xf70867153aa2db38, - 0xb8cbee4fc66d1ea8 } + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + { 0xdb68c2ca82ed2a05, + 0xa67398db9f6820e2 } #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1034,8 +1010,8 @@ template <> struct cache_accessor<double> { {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, - { 0x95527a5202df0ccb, - 0x0f37801e0c43ebc9 } + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} #endif }; @@ -1138,8 +1114,12 @@ template <> struct cache_accessor<double> { } }; +FMT_FUNC uint128_fallback get_cached_power(int k) noexcept { + return cache_accessor<double>::get_cached_power(k); +} + // Various integer checks -template <class T> +template <typename T> bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; @@ -1148,12 +1128,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { } // Remove trailing zeros from n and return the number of zeros removed (float) -FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { +FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { FMT_ASSERT(n != 0, ""); - const uint32_t mod_inv_5 = 0xcccccccd; - const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; + // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. + constexpr uint32_t mod_inv_5 = 0xcccccccd; + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 - int s = 0; while (true) { auto q = rotr(n * mod_inv_25, 2); if (q > max_value<uint32_t>() / 100) break; @@ -1165,7 +1145,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { n = q; s |= 1; } - return s; } @@ -1179,32 +1158,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { // Is n is divisible by 10^8? if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { - // If yes, work with the quotient. + // If yes, work with the quotient... auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64)); - - const uint32_t mod_inv_5 = 0xcccccccd; - const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; - - int s = 8; - while (true) { - auto q = rotr(n32 * mod_inv_25, 2); - if (q > max_value<uint32_t>() / 100) break; - n32 = q; - s += 2; - } - auto q = rotr(n32 * mod_inv_5, 1); - if (q <= max_value<uint32_t>() / 10) { - n32 = q; - s |= 1; - } - + // ... and use the 32 bit variant of the function + int s = remove_trailing_zeros(n32, 8); n = n32; return s; } // If n is not divisible by 10^8, work with n itself. - const uint64_t mod_inv_5 = 0xcccccccccccccccd; - const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5; + constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // = mod_inv_5 * mod_inv_5 int s = 0; while (true) { @@ -1223,7 +1187,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { } // The main algorithm for shorter interval case -template <class T> +template <typename T> FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept { decimal_fp<T> ret_value; // Compute k and beta @@ -1337,7 +1301,7 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept { if (r < deltai) { // Exclude the right endpoint if necessary. - if (r == 0 && z_mul.is_integer && !include_right_endpoint) { + if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { --ret_value.significand; r = float_info<T>::big_divisor; goto small_divisor_case_label; @@ -1346,26 +1310,11 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept { goto small_divisor_case_label; } else { // r == deltai; compare fractional parts. - const carrier_uint two_fl = two_fc - 1; - - if (!include_left_endpoint || - exponent < float_info<T>::case_fc_pm_half_lower_threshold || - exponent > float_info<T>::divisibility_check_by_5_threshold) { - // If the left endpoint is not included, the condition for - // success is z^(f) < delta^(f) (odd parity). - // Otherwise, the inequalities on exponent ensure that - // x is not an integer, so if z^(f) >= delta^(f) (even parity), we in fact - // have strict inequality. - if (!cache_accessor<T>::compute_mul_parity(two_fl, cache, beta).parity) { - goto small_divisor_case_label; - } - } else { - const typename cache_accessor<T>::compute_mul_parity_result x_mul = - cache_accessor<T>::compute_mul_parity(two_fl, cache, beta); - if (!x_mul.parity && !x_mul.is_integer) { - goto small_divisor_case_label; - } - } + const typename cache_accessor<T>::compute_mul_parity_result x_mul = + cache_accessor<T>::compute_mul_parity(two_fc - 1, cache, beta); + + if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) + goto small_divisor_case_label; } ret_value.exponent = minus_k + float_info<T>::kappa + 1; @@ -1404,22 +1353,11 @@ small_divisor_case_label: // or equivalently, when y is an integer. if (y_mul.parity != approx_y_parity) --ret_value.significand; - else if (y_mul.is_integer && ret_value.significand % 2 != 0) + else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) --ret_value.significand; return ret_value; } } // namespace dragonbox - -#ifdef _MSC_VER -FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) - -> int { - auto args = va_list(); - va_start(args, fmt); - int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args); - va_end(args); - return result; -} -#endif } // namespace detail template <> struct formatter<detail::bigint> { @@ -1428,9 +1366,8 @@ template <> struct formatter<detail::bigint> { return ctx.begin(); } - template <typename FormatContext> - auto format(const detail::bigint& n, FormatContext& ctx) const -> - typename FormatContext::iterator { + auto format(const detail::bigint& n, format_context& ctx) const + -> format_context::iterator { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { @@ -1488,53 +1425,45 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) { return to_string(buffer); } -#ifdef _WIN32 namespace detail { +#ifndef _WIN32 +FMT_FUNC bool write_console(std::FILE*, string_view) { return false; } +#else using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); -} // namespace detail -#endif -namespace detail { -FMT_FUNC void print(std::FILE* f, string_view text) { -#ifdef _WIN32 +FMT_FUNC bool write_console(std::FILE* f, string_view text) { auto fd = _fileno(f); - if (_isatty(fd)) { - detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); - auto written = detail::dword(); - if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), - u16.c_str(), static_cast<uint32_t>(u16.size()), - &written, nullptr)) { - return; - } - // Fallback to fwrite on failure. It can happen if the output has been - // redirected to NUL. - } -#endif - detail::fwrite_fully(text.data(), 1, text.size(), f); + if (!_isatty(fd)) return false; + auto u16 = utf8_to_utf16(text); + auto written = dword(); + return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(), + static_cast<uint32_t>(u16.size()), &written, nullptr) != 0; } -} // namespace detail -FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, args); - detail::print(f, {buffer.data(), buffer.size()}); -} - -#ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, - format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, basic_format_args<buffer_context<char>>(args)); fwrite_fully(buffer.data(), 1, buffer.size(), f); } #endif -FMT_FUNC void vprint(string_view format_str, format_args args) { - vprint(stdout, format_str, args); +FMT_FUNC void print(std::FILE* f, string_view text) { + if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f); +} +} // namespace detail + +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(string_view fmt, format_args args) { + vprint(stdout, fmt, args); } namespace detail { diff --git a/externals/fmt/include/fmt/format.h b/externals/fmt/include/fmt/format.h index 0bd2fdb1..87a34b97 100644 --- a/externals/fmt/include/fmt/format.h +++ b/externals/fmt/include/fmt/format.h @@ -33,13 +33,14 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include <cmath> // std::signbit -#include <cstdint> // uint32_t -#include <cstring> // std::memcpy -#include <limits> // std::numeric_limits -#include <memory> // std::uninitialized_copy -#include <stdexcept> // std::runtime_error -#include <system_error> // std::system_error +#include <cmath> // std::signbit +#include <cstdint> // uint32_t +#include <cstring> // std::memcpy +#include <initializer_list> // std::initializer_list +#include <limits> // std::numeric_limits +#include <memory> // std::uninitialized_copy +#include <stdexcept> // std::runtime_error +#include <system_error> // std::system_error #ifdef __cpp_lib_bit_cast # include <bit> // std::bitcast @@ -47,16 +48,55 @@ #include "core.h" -#if FMT_GCC_VERSION -# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline #else -# define FMT_GCC_VISIBILITY_HIDDEN +# define FMT_INLINE_VARIABLE #endif -#ifdef __NVCC__ -# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] #else -# define FMT_CUDA_VERSION 0 +# define FMT_FALLTHROUGH +#endif + +#ifndef FMT_DEPRECATED +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 +# define FMT_DEPRECATED [[deprecated]] +# else +# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) +# define FMT_DEPRECATED __attribute__((deprecated)) +# elif FMT_MSC_VERSION +# define FMT_DEPRECATED __declspec(deprecated) +# else +# define FMT_DEPRECATED /* deprecated */ +# endif +# endif +#endif + +#ifndef FMT_NO_UNIQUE_ADDRESS +# if FMT_CPLUSPLUS >= 202002L +# if FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485) +# elif (FMT_MSC_VERSION >= 1929) && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +# endif +# endif +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS +#endif + +#if FMT_GCC_VERSION || defined(__clang__) +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) #endif #ifdef __has_builtin @@ -71,12 +111,6 @@ # define FMT_NOINLINE #endif -#if FMT_MSC_VERSION -# define FMT_MSC_DEFAULT = default -#else -# define FMT_MSC_DEFAULT -#endif - #ifndef FMT_THROW # if FMT_EXCEPTIONS # if FMT_MSC_VERSION || defined(__NVCC__) @@ -95,10 +129,8 @@ FMT_END_NAMESPACE # define FMT_THROW(x) throw x # endif # else -# define FMT_THROW(x) \ - do { \ - FMT_ASSERT(false, (x).what()); \ - } while (false) +# define FMT_THROW(x) \ + ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) # endif #endif @@ -200,7 +232,8 @@ inline auto clzll(uint64_t x) -> int { _BitScanReverse64(&r, x); # else // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) return 63 ^ (r + 32); + if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) + return 63 ^ static_cast<int>(r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast<uint32_t>(x)); # endif @@ -240,6 +273,19 @@ 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) { @@ -249,6 +295,18 @@ FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { #endif } +template <typename CharT, CharT... C> struct string_literal { + static constexpr CharT value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view<CharT>() const { + return {value, sizeof...(C)}; + } +}; + +#if FMT_CPLUSPLUS < 201703L +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; @@ -287,7 +345,8 @@ FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { if (is_constant_evaluated()) return std::bit_cast<To>(from); #endif auto to = To(); - std::memcpy(&to, &from, sizeof(to)); + // The cast suppresses a bogus -Wclass-memaccess on GCC. + std::memcpy(static_cast<void*>(&to), &from, sizeof(to)); return to; } @@ -310,8 +369,6 @@ class uint128_fallback { private: uint64_t lo_, hi_; - friend uint128_fallback umul128(uint64_t x, uint64_t y) noexcept; - public: constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} @@ -346,6 +403,10 @@ class uint128_fallback { -> uint128_fallback { return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; } + friend constexpr auto operator~(const uint128_fallback& n) + -> uint128_fallback { + return {~n.hi_, ~n.lo_}; + } friend auto operator+(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { auto result = uint128_fallback(lhs); @@ -366,10 +427,12 @@ class uint128_fallback { } FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { if (shift == 64) return {0, hi_}; + if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; } FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { if (shift == 64) return {lo_, 0}; + if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; } FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { @@ -382,6 +445,10 @@ class uint128_fallback { lo_ = new_lo; hi_ = new_hi; } + FMT_CONSTEXPR void operator&=(uint128_fallback n) { + lo_ &= n.lo_; + hi_ &= n.hi_; + } FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { if (is_constant_evaluated()) { @@ -389,11 +456,11 @@ class uint128_fallback { hi_ += (lo_ < n ? 1 : 0); return *this; } -#if FMT_HAS_BUILTIN(__builtin_addcll) +#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) unsigned long long carry; lo_ = __builtin_addcll(lo_, n, 0, &carry); hi_ += carry; -#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) +#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) unsigned long long result; auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); lo_ = result; @@ -448,10 +515,34 @@ inline auto bit_cast(const From& from) -> To { return result; } +template <typename UInt> +FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { + int lz = 0; + constexpr UInt msb_mask = static_cast<UInt>(1) << (num_bits<UInt>() - 1); + for (; (n & msb_mask) == 0; n <<= 1) lz++; + return lz; +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); +#endif + return countl_zero_fallback(n); +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); +#endif + return countl_zero_fallback(n); +} + FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION __builtin_assume(condition); +#elif FMT_GCC_VERSION + if (!condition) __builtin_unreachable(); #endif } @@ -470,20 +561,6 @@ inline auto get_data(Container& c) -> typename Container::value_type* { return c.data(); } -#if defined(_SECURE_SCL) && _SECURE_SCL -// Make a checked iterator to avoid MSVC warnings. -template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>; -template <typename T> -constexpr auto make_checked(T* p, size_t size) -> checked_ptr<T> { - return {p, size}; -} -#else -template <typename T> using checked_ptr = T*; -template <typename T> constexpr auto make_checked(T* p, size_t) -> T* { - return p; -} -#endif - // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to it. template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)> @@ -491,12 +568,12 @@ template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)> __attribute__((no_sanitize("undefined"))) #endif inline auto -reserve(std::back_insert_iterator<Container> it, size_t n) - -> checked_ptr<typename Container::value_type> { +reserve(std::back_insert_iterator<Container> it, size_t n) -> + typename Container::value_type* { Container& c = get_container(it); size_t size = c.size(); c.resize(size + n); - return make_checked(get_data(c) + size, n); + return get_data(c) + size; } template <typename T> @@ -528,8 +605,8 @@ template <typename T> auto to_pointer(buffer_appender<T> it, size_t n) -> T* { } template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)> -inline auto base_iterator(std::back_insert_iterator<Container>& it, - checked_ptr<typename Container::value_type>) +inline auto base_iterator(std::back_insert_iterator<Container> it, + typename Container::value_type*) -> std::back_insert_iterator<Container> { return it; } @@ -592,19 +669,24 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) constexpr const int shiftc[] = {0, 18, 12, 6, 0}; constexpr const int shifte[] = {0, 6, 4, 2, 0}; - int len = code_point_length(s); - const char* next = s + len; + int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast<unsigned char>(*s) >> 3]; + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + const char* next = s + len + !len; + + using uchar = unsigned char; // Assume a four-byte character and load four bytes. Unused bits are // shifted out. - *c = uint32_t(s[0] & masks[len]) << 18; - *c |= uint32_t(s[1] & 0x3f) << 12; - *c |= uint32_t(s[2] & 0x3f) << 6; - *c |= uint32_t(s[3] & 0x3f) << 0; + *c = uint32_t(uchar(s[0]) & masks[len]) << 18; + *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; + *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; + *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; *c >>= shiftc[len]; // Accumulate the various error conditions. - using uchar = unsigned char; *e = (*c < mins[len]) << 6; // non-canonical encoding *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? *e |= (*c > 0x10FFFF) << 8; // out of range? @@ -617,7 +699,7 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) return next; } -constexpr uint32_t invalid_code_point = ~uint32_t(); +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); // Invokes f(cp, sv) for every code point cp in s with sv being the string view // corresponding to the code point. cp is invalid_code_point on error. @@ -628,8 +710,8 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { auto error = 0; auto end = utf8_decode(buf_ptr, &cp, &error); bool result = f(error ? invalid_code_point : cp, - string_view(ptr, to_unsigned(end - buf_ptr))); - return result ? end : nullptr; + string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); + return result ? (error ? buf_ptr + 1 : end) : nullptr; }; auto p = s.data(); const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. @@ -687,6 +769,7 @@ FMT_CONSTEXPR inline size_t compute_width(string_view s) { return true; } }; + // We could avoid branches by using utf8_decode directly. for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } @@ -718,13 +801,48 @@ inline auto code_point_index(basic_string_view<char8_type> s, size_t n) string_view(reinterpret_cast<const char*>(s.data()), s.size()), n); } +template <typename T> struct is_integral : std::is_integral<T> {}; +template <> struct is_integral<int128_opt> : std::true_type {}; +template <> struct is_integral<uint128_t> : std::true_type {}; + +template <typename T> +using is_signed = + std::integral_constant<bool, std::numeric_limits<T>::is_signed || + std::is_same<T, int128_opt>::value>; + +template <typename T> +using is_integer = + bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value && + !std::is_same<T, char>::value && + !std::is_same<T, wchar_t>::value>; + +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + #ifndef FMT_USE_FLOAT128 -# ifdef __SIZEOF_FLOAT128__ -# define FMT_USE_FLOAT128 1 -# else +# ifdef __clang__ +// Clang emulates GCC, so it has to appear early. +# if FMT_HAS_INCLUDE(<quadmath.h>) +# define FMT_USE_FLOAT128 1 +# endif +# elif defined(__GNUC__) +// GNU C++: +# if defined(_GLIBCXX_USE_FLOAT128) && !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +# endif +# endif +# ifndef FMT_USE_FLOAT128 # define FMT_USE_FLOAT128 0 # endif #endif + #if FMT_USE_FLOAT128 using float128 = __float128; #else @@ -756,7 +874,7 @@ void buffer<T>::append(const U* begin, const U* end) { try_reserve(size_ + count); auto free_cap = capacity_ - size_; if (free_cap < count) count = free_cap; - std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); + std::uninitialized_copy_n(begin, count, ptr_ + size_); size_ += count; begin += count; } @@ -768,7 +886,7 @@ template <typename T> struct is_locale<T, void_t<decltype(T::classic())>> : std::true_type {}; } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. @@ -801,8 +919,8 @@ class basic_memory_buffer final : public detail::buffer<T> { private: T store_[SIZE]; - // Don't inherit from Allocator avoid generating type_info for it. - Allocator alloc_; + // Don't inherit from Allocator to avoid generating type_info for it. + FMT_NO_UNIQUE_ADDRESS Allocator alloc_; // Deallocate memory allocated by the buffer. FMT_CONSTEXPR20 void deallocate() { @@ -811,7 +929,28 @@ class basic_memory_buffer final : public detail::buffer<T> { } protected: - FMT_CONSTEXPR20 void grow(size_t size) override; + FMT_CONSTEXPR20 void grow(size_t size) override { + detail::abort_fuzzing_if(size > 5000); + const size_t max_size = std::allocator_traits<Allocator>::max_size(alloc_); + size_t old_capacity = this->capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; + T* old_data = this->data(); + T* new_data = + std::allocator_traits<Allocator>::allocate(alloc_, new_capacity); + // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). + detail::assume(this->size() <= new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy_n(old_data, this->size(), new_data); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) alloc_.deallocate(old_data, old_capacity); + } public: using value_type = T; @@ -833,8 +972,7 @@ class basic_memory_buffer final : public detail::buffer<T> { size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); - detail::copy_str<T>(other.store_, other.store_ + size, - detail::make_checked(store_, capacity)); + detail::copy_str<T>(other.store_, other.store_ + size, store_); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called @@ -888,52 +1026,29 @@ class basic_memory_buffer final : public detail::buffer<T> { } }; -template <typename T, size_t SIZE, typename Allocator> -FMT_CONSTEXPR20 void basic_memory_buffer<T, SIZE, Allocator>::grow( - size_t size) { - detail::abort_fuzzing_if(size > 5000); - const size_t max_size = std::allocator_traits<Allocator>::max_size(alloc_); - size_t old_capacity = this->capacity(); - size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) - new_capacity = size; - else if (new_capacity > max_size) - new_capacity = size > max_size ? size : max_size; - T* old_data = this->data(); - T* new_data = - std::allocator_traits<Allocator>::allocate(alloc_, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - detail::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) alloc_.deallocate(old_data, old_capacity); -} - using memory_buffer = basic_memory_buffer<char>; template <typename T, size_t SIZE, typename Allocator> struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type { }; +FMT_END_EXPORT namespace detail { +FMT_API bool write_console(std::FILE* f, string_view text); FMT_API void print(std::FILE*, string_view); -} +} // namespace detail + +FMT_BEGIN_EXPORT -/** A formatting error such as invalid format string. */ -FMT_CLASS_API -class FMT_API format_error : public std::runtime_error { +// Suppress a misleading warning in older versions of clang. +#if FMT_CLANG_VERSION +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +/** An error reported from a formatting function. */ +class FMT_VISIBILITY("default") format_error : public std::runtime_error { public: - explicit format_error(const char* message) : std::runtime_error(message) {} - explicit format_error(const std::string& message) - : std::runtime_error(message) {} - format_error(const format_error&) = default; - format_error& operator=(const format_error&) = default; - format_error(format_error&&) = default; - format_error& operator=(format_error&&) = default; - ~format_error() noexcept override FMT_MSC_DEFAULT; + using std::runtime_error::runtime_error; }; namespace detail_exported { @@ -962,16 +1077,52 @@ constexpr auto compile_string_to_view(detail::std_string_view<Char> s) } } // namespace detail_exported -FMT_BEGIN_DETAIL_NAMESPACE +class loc_value { + private: + basic_format_arg<format_context> value_; -template <typename T> struct is_integral : std::is_integral<T> {}; -template <> struct is_integral<int128_opt> : std::true_type {}; -template <> struct is_integral<uint128_t> : std::true_type {}; + public: + template <typename T, FMT_ENABLE_IF(!detail::is_float128<T>::value)> + loc_value(T value) : value_(detail::make_arg<format_context>(value)) {} -template <typename T> -using is_signed = - std::integral_constant<bool, std::numeric_limits<T>::is_signed || - std::is_same<T, int128_opt>::value>; + template <typename T, FMT_ENABLE_IF(detail::is_float128<T>::value)> + loc_value(T) {} + + template <typename Visitor> auto visit(Visitor&& vis) -> decltype(vis(0)) { + return visit_format_arg(vis, value_); + } +}; + +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy <locale> include. +template <typename Locale> class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs<>& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", + std::initializer_list<unsigned char> g = {3}, + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(g.begin(), g.end()), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs<>& specs) const + -> bool { + return do_put(out, val, specs); + } +}; + +namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. @@ -1100,7 +1251,7 @@ FMT_CONSTEXPR auto count_digits(UInt n) -> int { FMT_INLINE auto do_count_digits(uint32_t n) -> int { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. -# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) +# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 @@ -1213,10 +1364,10 @@ FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) template <typename Char, typename UInt, typename Iterator, FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<Iterator>>::value)> -inline auto format_decimal(Iterator out, UInt value, int size) +FMT_CONSTEXPR inline auto format_decimal(Iterator out, UInt value, int size) -> format_decimal_result<Iterator> { // Buffer is large enough to hold all digits (digits10 + 1). - Char buffer[digits10<UInt>() + 1]; + Char buffer[digits10<UInt>() + 1] = {}; auto end = format_decimal(buffer, value, size).end; return {out, detail::copy_str_noinline<Char>(buffer, end, out)}; } @@ -1236,8 +1387,8 @@ FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, } template <unsigned BASE_BITS, typename Char, typename It, typename UInt> -inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) - -> It { +FMT_CONSTEXPR inline auto format_uint(It out, UInt value, int num_digits, + bool upper = false) -> It { if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) { format_uint<BASE_BITS>(ptr, value, num_digits, upper); return out; @@ -1261,7 +1412,139 @@ class utf8_to_utf16 { auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; +enum class to_utf8_error_policy { abort, replace }; + +// A converter from UTF-16/UTF-32 (host endian) to UTF-8. +template <typename WChar, typename Buffer = memory_buffer> class to_utf8 { + private: + Buffer buffer_; + + public: + to_utf8() {} + explicit to_utf8(basic_string_view<WChar> s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, + "Expect utf16 or utf32"); + if (!convert(s, policy)) + FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" + : "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()); } + + // 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) { + 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) { + 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) { + // Handle a surrogate pair. + ++p; + if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (policy == to_utf8_error_policy::abort) return false; + buf.append(string_view("\xEF\xBF\xBD")); + --p; + } else { + c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00; + } + } else if (c < 0x80) { + buf.push_back(static_cast<char>(c)); + } else if (c < 0x800) { + buf.push_back(static_cast<char>(0xc0 | (c >> 6))); + buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast<char>(0xe0 | (c >> 12))); + buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast<char>(0xf0 | (c >> 18))); + buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); + } else { + return false; + } + } + return true; + } +}; + +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { +#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)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto hi = uint64_t(); + auto lo = _umul128(x, y, &hi); + return {hi, lo}; +#else + const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>()); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + 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 { + 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 { + 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 { +#if FMT_USE_INT128 + auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y); + return static_cast<uint64_t>(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// 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 { + 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; // Type-specific information that Dragonbox uses. template <typename T, typename Enable = void> struct float_info; @@ -1274,8 +1557,6 @@ template <> struct float_info<float> { static const int small_divisor = 10; static const int min_k = -31; static const int max_k = 46; - static const int divisibility_check_by_5_threshold = 39; - static const int case_fc_pm_half_lower_threshold = -1; static const int shorter_interval_tie_lower_threshold = -35; static const int shorter_interval_tie_upper_threshold = -35; }; @@ -1287,9 +1568,7 @@ template <> struct float_info<double> { static const int big_divisor = 1000; static const int small_divisor = 100; static const int min_k = -292; - static const int max_k = 326; - static const int divisibility_check_by_5_threshold = 86; - static const int case_fc_pm_half_lower_threshold = -2; + static const int max_k = 341; static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; }; @@ -1336,8 +1615,8 @@ template <typename Float> constexpr int num_significand_bits() { template <typename Float> constexpr auto exponent_mask() -> typename dragonbox::float_info<Float>::carrier_uint { - using uint = typename dragonbox::float_info<Float>::carrier_uint; - return ((uint(1) << dragonbox::float_info<Float>::exponent_bits) - 1) + using float_uint = typename dragonbox::float_info<Float>::carrier_uint; + return ((float_uint(1) << dragonbox::float_info<Float>::exponent_bits) - 1) << num_significand_bits<Float>(); } template <typename Float> constexpr auto exponent_bias() -> int { @@ -1458,154 +1737,31 @@ FMT_CONSTEXPR inline fp operator*(fp x, fp y) { } template <typename T = void> struct basic_data { - // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. - // These are generated by support/compute-powers.py. - static constexpr uint64_t pow10_significands[87] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, + // 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. + static constexpr uint32_t fractional_part_rounding_thresholds[8] = { + 2576980378U, // ceil(2^31 + 2^32/10^1) + 2190433321U, // ceil(2^31 + 2^32/10^2) + 2151778616U, // ceil(2^31 + 2^32/10^3) + 2147913145U, // ceil(2^31 + 2^32/10^4) + 2147526598U, // ceil(2^31 + 2^32/10^5) + 2147487943U, // ceil(2^31 + 2^32/10^6) + 2147484078U, // ceil(2^31 + 2^32/10^7) + 2147483691U // ceil(2^31 + 2^32/10^8) }; - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wnarrowing" -#endif - // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding - // to significands above. - static constexpr int16_t pow10_exponents[87] = { - -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, - -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, - -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, - 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, - 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -# pragma GCC diagnostic pop -#endif - - static constexpr uint64_t power_of_10_64[20] = { - 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; }; - -#if FMT_CPLUSPLUS < 201703L -template <typename T> constexpr uint64_t basic_data<T>::pow10_significands[]; -template <typename T> constexpr int16_t basic_data<T>::pow10_exponents[]; -template <typename T> constexpr uint64_t basic_data<T>::power_of_10_64[]; -#endif - // This is a struct rather than an alias to avoid shadowing warnings in gcc. struct data : basic_data<> {}; -// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its -// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. -FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, - int& pow10_exponent) { - const int shift = 32; - // log10(2) = 0x0.4d104d427de7fbcc... - const int64_t significand = 0x4d104d427de7fbcc; - int index = static_cast<int>( - ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) + - ((int64_t(1) << shift) - 1)) // ceil - >> 32 // arithmetic shift - ); - // Decimal exponent of the first (smallest) cached power of 10. - const int first_dec_exp = -348; - // Difference between 2 consecutive decimal exponents in cached powers of 10. - const int dec_exp_step = 8; - index = (index - first_dec_exp - 1) / dec_exp_step + 1; - pow10_exponent = first_dec_exp + index * dec_exp_step; - return {data::pow10_significands[index], data::pow10_exponents[index]}; -} - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else -FMT_API auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) -> int; -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -// Formats a floating-point number with snprintf using the hexfloat format. +#if FMT_CPLUSPLUS < 201703L template <typename T> -auto snprintf_float(T value, int precision, float_specs specs, - buffer<char>& buf) -> int { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); - FMT_ASSERT(specs.format == float_format::hex, ""); - static_assert(!std::is_same<T, float>::value, ""); - - // Build the format string. - char format[7]; // The longest format is "%#.*Le". - char* format_ptr = format; - *format_ptr++ = '%'; - if (specs.showpoint) *format_ptr++ = '#'; - if (precision >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - if (std::is_same<T, long double>()) *format_ptr++ = 'L'; - *format_ptr++ = specs.upper ? 'A' : 'a'; - *format_ptr = '\0'; - - // Format using snprintf. - auto offset = buf.size(); - for (;;) { - auto begin = buf.data() + offset; - auto capacity = buf.capacity() - offset; - abort_fuzzing_if(precision > 100000); - // Suppress the warning about a nonliteral format string. - // Cannot use auto because of a bug in MinGW (#1532). - int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; - int result = precision >= 0 - ? snprintf_ptr(begin, capacity, format, precision, value) - : snprintf_ptr(begin, capacity, format, value); - if (result < 0) { - // The buffer will grow exponentially. - buf.try_reserve(buf.capacity() + 1); - continue; - } - auto size = to_unsigned(result); - // Size equal to capacity means that the last character was truncated. - if (size < capacity) { - buf.try_resize(size + offset); - return 0; - } - buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. - } -} +constexpr uint32_t basic_data<T>::fractional_part_rounding_thresholds[]; +#endif -template <typename T> +template <typename T, bool doublish = num_bits<T>() == num_bits<double>()> using convert_float_result = - conditional_t<std::is_same<T, float>::value || sizeof(T) == sizeof(double), - double, T>; + conditional_t<std::is_same<T, float>::value || doublish, double, T>; template <typename T> constexpr auto convert_float(T value) -> convert_float_result<T> { @@ -1628,8 +1784,7 @@ FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, // width: output display width in (terminal) column positions. template <align::type align = align::left, typename OutputIt, typename Char, typename F> -FMT_CONSTEXPR auto write_padded(OutputIt out, - const basic_format_specs<Char>& specs, +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs<Char>& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); @@ -1648,15 +1803,14 @@ FMT_CONSTEXPR auto write_padded(OutputIt out, template <align::type align = align::left, typename OutputIt, typename Char, typename F> -constexpr auto write_padded(OutputIt out, const basic_format_specs<Char>& specs, +constexpr auto write_padded(OutputIt out, const format_specs<Char>& specs, size_t size, F&& f) -> OutputIt { return write_padded<align>(out, specs, size, size, f); } template <align::type align = align::left, typename Char, typename OutputIt> FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, - const basic_format_specs<Char>& specs) - -> OutputIt { + const format_specs<Char>& specs) -> OutputIt { return write_padded<align>( out, specs, bytes.size(), [bytes](reserve_iterator<OutputIt> it) { const char* data = bytes.data(); @@ -1665,8 +1819,8 @@ FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, } template <typename Char, typename OutputIt, typename UIntPtr> -auto write_ptr(OutputIt out, UIntPtr value, - const basic_format_specs<Char>* specs) -> OutputIt { +auto write_ptr(OutputIt out, UIntPtr value, const format_specs<Char>* specs) + -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator<OutputIt> it) { @@ -1724,18 +1878,18 @@ inline auto find_escape(const char* begin, const char* end) return result; } -#define FMT_STRING_IMPL(s, base, explicit) \ - [] { \ - /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ - /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \ - using char_type = fmt::remove_cvref_t<decltype(s[0])>; \ - FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ - operator fmt::basic_string_view<char_type>() const { \ - return fmt::detail_exported::compile_string_to_view<char_type>(s); \ - } \ - }; \ - return FMT_COMPILE_STRING(); \ +#define FMT_STRING_IMPL(s, base, explicit) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ + using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t<decltype(s[0])>; \ + FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ + operator fmt::basic_string_view<char_type>() const { \ + return fmt::detail_exported::compile_string_to_view<char_type>(s); \ + } \ + }; \ + return FMT_COMPILE_STRING(); \ }() /** @@ -1785,16 +1939,14 @@ auto write_escaped_cp(OutputIt out, const find_escape_result<Char>& escape) *out++ = static_cast<Char>('\\'); break; default: - if (is_utf8()) { - if (escape.cp < 0x100) { - return write_codepoint<2, Char>(out, 'x', escape.cp); - } - if (escape.cp < 0x10000) { - return write_codepoint<4, Char>(out, 'u', escape.cp); - } - if (escape.cp < 0x110000) { - return write_codepoint<8, Char>(out, 'U', escape.cp); - } + if (escape.cp < 0x100) { + return write_codepoint<2, Char>(out, 'x', escape.cp); + } + if (escape.cp < 0x10000) { + return write_codepoint<4, Char>(out, 'u', escape.cp); + } + if (escape.cp < 0x110000) { + return write_codepoint<8, Char>(out, 'U', escape.cp); } for (Char escape_char : basic_string_view<Char>( escape.begin, to_unsigned(escape.end - escape.begin))) { @@ -1839,8 +1991,7 @@ auto write_escaped_char(OutputIt out, Char v) -> OutputIt { template <typename Char, typename OutputIt> FMT_CONSTEXPR auto write_char(OutputIt out, Char value, - const basic_format_specs<Char>& specs) - -> OutputIt { + const format_specs<Char>& specs) -> OutputIt { bool is_debug = specs.type == presentation_type::debug; return write_padded(out, specs, 1, [=](reserve_iterator<OutputIt> it) { if (is_debug) return write_escaped_char(it, value); @@ -1850,11 +2001,14 @@ FMT_CONSTEXPR auto write_char(OutputIt out, Char value, } template <typename Char, typename OutputIt> FMT_CONSTEXPR auto write(OutputIt out, Char value, - const basic_format_specs<Char>& specs, - locale_ref loc = {}) -> OutputIt { + const format_specs<Char>& specs, locale_ref loc = {}) + -> OutputIt { + // char is formatted as unsigned char for consistency across platforms. + using unsigned_type = + conditional_t<std::is_same<Char, char>::value, unsigned char, unsigned>; return check_char_specs(specs) ? write_char(out, value, specs) - : write(out, static_cast<int>(value), specs, loc); + : write(out, static_cast<unsigned_type>(value), specs, loc); } // Data for write_int that doesn't depend on output iterator type. It is used to @@ -1864,7 +2018,7 @@ template <typename Char> struct write_int_data { size_t padding; FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, - const basic_format_specs<Char>& specs) + const format_specs<Char>& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); @@ -1886,7 +2040,7 @@ template <typename Char> struct write_int_data { template <typename OutputIt, typename Char, typename W> FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, unsigned prefix, - const basic_format_specs<Char>& specs, + const format_specs<Char>& specs, W write_digits) -> OutputIt { // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { @@ -1909,19 +2063,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, template <typename Char> class digit_grouping { private: - thousands_sep_result<Char> sep_; + std::string grouping_; + std::basic_string<Char> thousands_sep_; struct next_state { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + next_state initial_state() const { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. int next(next_state& state) const { - if (!sep_.thousands_sep) return max_value<int>(); - if (state.group == sep_.grouping.end()) - return state.pos += sep_.grouping.back(); + 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>()) return max_value<int>(); state.pos += *state.group++; @@ -1930,14 +2084,15 @@ template <typename Char> class digit_grouping { public: explicit digit_grouping(locale_ref loc, bool localized = true) { - if (localized) - sep_ = thousands_sep<Char>(loc); - else - sep_.thousands_sep = Char(); + if (!localized) return; + auto sep = thousands_sep<Char>(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } - explicit digit_grouping(thousands_sep_result<Char> sep) : sep_(sep) {} + digit_grouping(std::string grouping, std::basic_string<Char> sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} - Char separator() const { return sep_.thousands_sep; } + bool has_separator() const { return !thousands_sep_.empty(); } int count_separators(int num_digits) const { int count = 0; @@ -1960,7 +2115,9 @@ template <typename Char> class digit_grouping { for (int i = 0, sep_index = static_cast<int>(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { - *out++ = separator(); + out = + copy_str<Char>(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); --sep_index; } *out++ = static_cast<Char>(digits[to_unsigned(i)]); @@ -1969,10 +2126,11 @@ template <typename Char> class digit_grouping { } }; +// Writes a decimal integer with digit grouping. template <typename OutputIt, typename UInt, typename Char> -auto write_int_localized(OutputIt out, UInt value, unsigned prefix, - const basic_format_specs<Char>& specs, - const digit_grouping<Char>& grouping) -> OutputIt { +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]; @@ -1981,18 +2139,21 @@ auto write_int_localized(OutputIt out, UInt value, unsigned prefix, grouping.count_separators(num_digits)); return write_padded<align::right>( out, specs, size, size, [&](reserve_iterator<OutputIt> it) { - if (prefix != 0) *it++ = static_cast<Char>(prefix); + 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))); }); } -template <typename OutputIt, typename UInt, typename Char> -auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, - const basic_format_specs<Char>& specs, locale_ref loc) - -> bool { - auto grouping = digit_grouping<Char>(loc); - out = write_int_localized(out, value, prefix, specs, grouping); - return true; +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, + const format_specs<>& specs, locale_ref loc) -> bool; +template <typename OutputIt, typename Char> +inline auto write_loc(OutputIt, loc_value, const format_specs<Char>&, + locale_ref) -> bool { + return false; } FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { @@ -2021,21 +2182,37 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) return {abs_value, prefix}; } +template <typename Char = char> struct loc_writer { + buffer_appender<Char> out; + const format_specs<Char>& specs; + std::basic_string<Char> sep; + std::string grouping; + std::basic_string<Char> decimal_point; + + template <typename T, FMT_ENABLE_IF(is_integer<T>::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign); + write_int(out, static_cast<uint64_or_128_t<T>>(arg.abs_value), arg.prefix, + specs, digit_grouping<Char>(grouping, sep)); + return true; + } + + template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)> + auto operator()(T) -> bool { + return false; + } +}; + template <typename Char, typename OutputIt, typename T> FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg, - const basic_format_specs<Char>& specs, - locale_ref loc) -> OutputIt { + const format_specs<Char>& specs, + locale_ref) -> OutputIt { static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type) { case presentation_type::none: case presentation_type::dec: { - if (specs.localized && - write_int_localized(out, static_cast<uint64_or_128_t<T>>(abs_value), - prefix, specs, loc)) { - return out; - } auto num_digits = count_digits(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) { @@ -2078,13 +2255,13 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg, case presentation_type::chr: return write_char(out, static_cast<Char>(abs_value), specs); default: - throw_format_error("invalid type specifier"); + throw_format_error("invalid format specifier"); } return out; } template <typename Char, typename OutputIt, typename T> FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( - OutputIt out, write_int_arg<T> arg, const basic_format_specs<Char>& specs, + OutputIt out, write_int_arg<T> arg, const format_specs<Char>& specs, locale_ref loc) -> OutputIt { return write_int(out, arg, specs, loc); } @@ -2093,8 +2270,9 @@ template <typename Char, typename OutputIt, typename T, !std::is_same<T, bool>::value && std::is_same<OutputIt, buffer_appender<Char>>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs<Char>& specs, + const format_specs<Char>& specs, locale_ref loc) -> OutputIt { + if (specs.localized && write_loc(out, value, specs, loc)) return out; return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2104,8 +2282,9 @@ template <typename Char, typename OutputIt, typename T, !std::is_same<T, bool>::value && !std::is_same<OutputIt, buffer_appender<Char>>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs<Char>& specs, + const format_specs<Char>& specs, locale_ref loc) -> OutputIt { + if (specs.localized && write_loc(out, value, specs, loc)) return out; return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2123,34 +2302,35 @@ class counting_iterator { FMT_UNCHECKED_ITERATOR(counting_iterator); struct value_type { - template <typename T> void operator=(const T&) {} + template <typename T> FMT_CONSTEXPR void operator=(const T&) {} }; - counting_iterator() : count_(0) {} + FMT_CONSTEXPR counting_iterator() : count_(0) {} - size_t count() const { return count_; } + FMT_CONSTEXPR size_t count() const { return count_; } - counting_iterator& operator++() { + FMT_CONSTEXPR counting_iterator& operator++() { ++count_; return *this; } - counting_iterator operator++(int) { + FMT_CONSTEXPR counting_iterator operator++(int) { auto it = *this; ++*this; return it; } - friend counting_iterator operator+(counting_iterator it, difference_type n) { + FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it, + difference_type n) { it.count_ += static_cast<size_t>(n); return it; } - value_type operator*() const { return {}; } + FMT_CONSTEXPR value_type operator*() const { return {}; } }; template <typename Char, typename OutputIt> FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s, - const basic_format_specs<Char>& specs) -> OutputIt { + const format_specs<Char>& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) @@ -2172,16 +2352,15 @@ FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s, template <typename Char, typename OutputIt> FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<type_identity_t<Char>> s, - const basic_format_specs<Char>& specs, locale_ref) + const format_specs<Char>& specs, locale_ref) -> OutputIt { - check_string_type_spec(specs.type); return write(out, s, specs); } template <typename Char, typename OutputIt> FMT_CONSTEXPR auto write(OutputIt out, const Char* s, - const basic_format_specs<Char>& specs, locale_ref) + const format_specs<Char>& specs, locale_ref) -> OutputIt { - return check_cstring_type_spec(specs.type) + return specs.type != presentation_type::pointer ? write(out, basic_string_view<Char>(s), specs, {}) : write_ptr<Char>(out, bit_cast<uintptr_t>(s), &specs); } @@ -2208,9 +2387,114 @@ FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { return base_iterator(out, it); } +// DEPRECATED! +template <typename Char> +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs<Char>& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + throw_format_error("invalid fill character '{'"); + return begin; + } + specs.fill = {begin, to_unsigned(p - begin)}; + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; + } + specs.align = align; + return begin; +} + +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed, // Fixed point with the default precision of 6, e.g. 0.0012. + hex +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool upper : 1; + bool locale : 1; + bool binary32 : 1; + bool showpoint : 1; +}; + +template <typename ErrorHandler = error_handler, typename Char> +FMT_CONSTEXPR auto parse_float_type_spec(const format_specs<Char>& specs, + ErrorHandler&& eh = {}) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + case presentation_type::none: + result.format = float_format::general; + break; + case presentation_type::general_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::general_lower: + result.format = float_format::general; + break; + case presentation_type::exp_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::exp_lower: + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::fixed_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::fixed_lower: + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::hexfloat_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::hexfloat_lower: + result.format = float_format::hex; + break; + default: + eh.on_error("invalid format specifier"); + break; + } + return result; +} + template <typename Char, typename OutputIt> FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, - basic_format_specs<Char> specs, + format_specs<Char> specs, const float_specs& fspecs) -> OutputIt { auto str = isnan ? (fspecs.upper ? "NAN" : "nan") : (fspecs.upper ? "INF" : "inf"); @@ -2256,7 +2540,7 @@ template <typename Char, typename OutputIt, typename T, typename Grouping> FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { out = write_significand<Char>(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast<Char>('0')); } @@ -2318,7 +2602,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } @@ -2334,7 +2618,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, template <typename OutputIt, typename DecimalFP, typename Char, typename Grouping = digit_grouping<Char>> FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, - const basic_format_specs<Char>& specs, + const format_specs<Char>& specs, float_specs fspecs, locale_ref loc) -> OutputIt { auto significand = f.significand; @@ -2393,7 +2677,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, abort_fuzzing_if(num_zeros > 5000); if (fspecs.showpoint) { ++size; - if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 0; if (num_zeros > 0) size += to_unsigned(num_zeros); } auto grouping = Grouping(loc, fspecs.locale); @@ -2411,7 +2695,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); auto grouping = Grouping(loc, fspecs.locale); - size += to_unsigned(grouping.count_separators(significand_size)); + size += to_unsigned(grouping.count_separators(exp)); return write_padded<align::right>(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign<Char>(sign); it = write_significand(it, significand, significand_size, exp, @@ -2441,7 +2725,7 @@ template <typename Char> class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr Char separator() const { return Char(); } + constexpr bool has_separator() const { return false; } constexpr int count_separators(int) const { return 0; } @@ -2453,7 +2737,7 @@ template <typename Char> class fallback_digit_grouping { template <typename OutputIt, typename DecimalFP, typename Char> FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, - const basic_format_specs<Char>& specs, + const format_specs<Char>& specs, float_specs fspecs, locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { @@ -2481,14 +2765,14 @@ template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value&& FMT_CONSTEXPR20 bool isfinite(T value) { constexpr T inf = T(std::numeric_limits<double>::infinity()); if (is_constant_evaluated()) - return !detail::isnan(value) && value != inf && value != -inf; + 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) { T inf = T(std::numeric_limits<double>::infinity()); // std::isfinite doesn't support __float128. - return !detail::isnan(value) && value != inf && value != -inf; + return !detail::isnan(value) && value < inf && value > -inf; } template <typename T, FMT_ENABLE_IF(is_floating_point<T>::value)> @@ -2504,78 +2788,6 @@ FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { return std::signbit(static_cast<double>(value)); } -enum class round_direction { unknown, up, down }; - -// Given the divisor (normally a power of 10), the remainder = v % divisor for -// some number v and the error, returns whether v should be rounded up, down, or -// whether the rounding direction can't be determined due to error. -// error should be less than divisor / 2. -FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor, - uint64_t remainder, - uint64_t error) { - FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. - FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. - FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. - // Round down if (remainder + error) * 2 <= divisor. - if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) - return round_direction::down; - // Round up if (remainder - error) * 2 >= divisor. - if (remainder >= error && - remainder - error >= divisor - (remainder - error)) { - return round_direction::up; - } - return round_direction::unknown; -} - -namespace digits { -enum result { - more, // Generate more digits. - done, // Done generating digits. - error // Digit generation cancelled due to an error. -}; -} - -struct gen_digits_handler { - char* buf; - int size; - int precision; - int exp10; - bool fixed; - - FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor, - uint64_t remainder, uint64_t error, - bool integral) { - FMT_ASSERT(remainder < divisor, ""); - buf[size++] = digit; - if (!integral && error >= remainder) return digits::error; - if (size < precision) return digits::more; - if (!integral) { - // Check if error * 2 < divisor with overflow prevention. - // The check is not needed for the integral part because error = 1 - // and divisor > (1 << 32) there. - if (error >= divisor || error >= divisor - error) return digits::error; - } else { - FMT_ASSERT(error == 1 && divisor > 2, ""); - } - auto dir = get_round_direction(divisor, remainder, error); - if (dir != round_direction::up) - return dir == round_direction::down ? digits::done : digits::error; - ++buf[size - 1]; - for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] > '9') { - buf[0] = '1'; - if (fixed) - buf[size++] = '0'; - else - ++exp10; - } - return digits::done; - } -}; - inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { // Adjust fixed precision by exponent because it is relative to decimal // point. @@ -2584,101 +2796,6 @@ inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { precision += exp10; } -// Generates output using the Grisu digit-gen algorithm. -// error: the size of the region (lower, upper) outside of which numbers -// definitely do not round to value (Delta in Grisu3). -FMT_INLINE FMT_CONSTEXPR20 auto grisu_gen_digits(fp value, uint64_t error, - int& exp, - gen_digits_handler& handler) - -> digits::result { - const fp one(1ULL << -value.e, value.e); - // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be - // zero because it contains a product of two 64-bit numbers with MSB set (due - // to normalization) - 1, shifted right by at most 60 bits. - auto integral = static_cast<uint32_t>(value.f >> -one.e); - FMT_ASSERT(integral != 0, ""); - FMT_ASSERT(integral == value.f >> -one.e, ""); - // The fractional part of scaled value (p2 in Grisu) c = value % one. - uint64_t fractional = value.f & (one.f - 1); - exp = count_digits(integral); // kappa in Grisu. - // Non-fixed formats require at least one digit and no precision adjustment. - if (handler.fixed) { - adjust_precision(handler.precision, exp + handler.exp10); - // Check if precision is satisfied just by leading zeros, e.g. - // format("{:.2f}", 0.001) gives "0.00" without generating any digits. - if (handler.precision <= 0) { - if (handler.precision < 0) return digits::done; - // Divide by 10 to prevent overflow. - uint64_t divisor = data::power_of_10_64[exp - 1] << -one.e; - auto dir = get_round_direction(divisor, value.f / 10, error * 10); - if (dir == round_direction::unknown) return digits::error; - handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0'; - return digits::done; - } - } - // Generate digits for the integral part. This can produce up to 10 digits. - do { - uint32_t digit = 0; - auto divmod_integral = [&](uint32_t divisor) { - digit = integral / divisor; - integral %= divisor; - }; - // This optimization by Milo Yip reduces the number of integer divisions by - // one per iteration. - switch (exp) { - case 10: - divmod_integral(1000000000); - break; - case 9: - divmod_integral(100000000); - break; - case 8: - divmod_integral(10000000); - break; - case 7: - divmod_integral(1000000); - break; - case 6: - divmod_integral(100000); - break; - case 5: - divmod_integral(10000); - break; - case 4: - divmod_integral(1000); - break; - case 3: - divmod_integral(100); - break; - case 2: - divmod_integral(10); - break; - case 1: - digit = integral; - integral = 0; - break; - default: - FMT_ASSERT(false, "invalid number of digits"); - } - --exp; - auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional; - auto result = handler.on_digit(static_cast<char>('0' + digit), - data::power_of_10_64[exp] << -one.e, - remainder, error, true); - if (result != digits::more) return result; - } while (exp > 0); - // Generate digits for the fractional part. - for (;;) { - fractional *= 10; - error *= 10; - char digit = static_cast<char>('0' + (fractional >> -one.e)); - fractional &= one.f - 1; - --exp; - auto result = handler.on_digit(digit, one.f, fractional, error, false); - if (result != digits::more) return result; - } -} - class bigint { private: // A bigint is stored as an array of bigits (big digits), with bigit at index @@ -2779,7 +2896,7 @@ class bigint { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); - std::copy(data, data + size, make_checked(bigits_.data(), size)); + copy_str<bigit>(data, data + size, bigits_.data()); exp_ = other.exp_; } @@ -2991,8 +3108,9 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp<uint128_t> value, upper = &upper_store; } } - bool even = (value.f & 1) == 0; + int even = static_cast<int>((value.f & 1) == 0); if (!upper) upper = &lower; + bool shortest = num_digits < 0; if ((flags & dragon::fixup) != 0) { if (add_compare(numerator, *upper, denominator) + even <= 0) { --exp10; @@ -3005,7 +3123,7 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp<uint128_t> value, if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); } // Invariant: value == (numerator / denominator) * pow(10, exp10). - if (num_digits < 0) { + if (shortest) { // Generate the shortest representation. num_digits = 0; char* data = buf.data(); @@ -3035,7 +3153,7 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp<uint128_t> value, } // Generate the given number of digits. exp10 -= num_digits - 1; - if (num_digits == 0) { + if (num_digits <= 0) { denominator *= 10; auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; buf.push_back(digit); @@ -3060,7 +3178,8 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp<uint128_t> value, } if (buf[0] == overflow) { buf[0] = '1'; - ++exp10; + if ((flags & dragon::fixed) != 0) buf.push_back('0'); + else ++exp10; } return; } @@ -3069,6 +3188,94 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp<uint128_t> value, buf[num_digits - 1] = static_cast<char>('0' + digit); } +// Formats a floating-point number using the hexfloat format. +template <typename Float, FMT_ENABLE_IF(!is_double_double<Float>::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, + float_specs specs, buffer<char>& buf) { + // float is passed as double to reduce the number of instantiations and to + // simplify implementation. + static_assert(!std::is_same<Float, float>::value, ""); + + using info = dragonbox::float_info<Float>; + + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename info::carrier_uint; + + constexpr auto num_float_significand_bits = + detail::num_significand_bits<Float>(); + + basic_fp<carrier_uint> f(value); + f.e += num_float_significand_bits; + if (!has_implicit_bit<Float>()) --f.e; + + constexpr auto num_fraction_bits = + num_float_significand_bits + (has_implicit_bit<Float>() ? 1 : 0); + constexpr auto num_xdigits = (num_fraction_bits + 3) / 4; + + constexpr auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_mask = carrier_uint(0xF) << leading_shift; + const auto leading_xdigit = + static_cast<uint32_t>((f.f & leading_mask) >> leading_shift); + if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); + + int print_xdigits = num_xdigits - 1; + if (precision >= 0 && print_xdigits > precision) { + const int shift = ((print_xdigits - precision - 1) * 4); + const auto mask = carrier_uint(0xF) << shift; + const auto v = static_cast<uint32_t>((f.f & mask) >> shift); + + if (v >= 8) { + const auto inc = carrier_uint(1) << (shift + 4); + f.f += inc; + f.f &= ~(inc - 1); + } + + // Check long double overflow + if (!has_implicit_bit<Float>()) { + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + if ((f.f & implicit_bit) == implicit_bit) { + f.f >>= 4; + f.e += 4; + } + } + + print_xdigits = precision; + } + + char xdigits[num_bits<carrier_uint>() / 4]; + detail::fill_n(xdigits, sizeof(xdigits), '0'); + format_uint<4>(xdigits, f.f, num_xdigits, specs.upper); + + // Remove zero tail + while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; + + buf.push_back('0'); + buf.push_back(specs.upper ? 'X' : 'x'); + buf.push_back(xdigits[0]); + if (specs.showpoint || print_xdigits > 0 || print_xdigits < precision) + buf.push_back('.'); + buf.append(xdigits + 1, xdigits + 1 + print_xdigits); + for (; print_xdigits < precision; ++print_xdigits) buf.push_back('0'); + + buf.push_back(specs.upper ? 'P' : 'p'); + + uint32_t abs_e; + if (f.e < 0) { + buf.push_back('-'); + abs_e = static_cast<uint32_t>(-f.e); + } else { + buf.push_back('+'); + abs_e = static_cast<uint32_t>(f.e); + } + format_decimal<char>(appender(buf), abs_e, detail::count_digits(abs_e)); +} + +template <typename Float, FMT_ENABLE_IF(is_double_double<Float>::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, + float_specs specs, buffer<char>& buf) { + format_hexfloat(static_cast<double>(value), precision, specs, buf); +} + template <typename Float> FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, buffer<char>& buf) -> int { @@ -3091,7 +3298,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, int exp = 0; bool use_dragon = true; unsigned dragon_flags = 0; - if (!is_fast_float<Float>()) { + if (!is_fast_float<Float>() || is_constant_evaluated()) { const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) using info = dragonbox::float_info<decltype(converted_value)>; const auto f = basic_fp<typename info::carrier_uint>(converted_value); @@ -3099,10 +3306,11 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). // This is based on log10(value) == log2(value) / log2(10) and approximation // of log2(value) by e + num_fraction_bits idea from double-conversion. - exp = static_cast<int>( - std::ceil((f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10)); + auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; + exp = static_cast<int>(e); + if (e > exp) ++exp; // Compute ceil. dragon_flags = dragon::fixup; - } else if (!is_constant_evaluated() && precision < 0) { + } else if (precision < 0) { // Use Dragonbox for the shortest format. if (specs.binary32) { auto dec = dragonbox::to_decimal(static_cast<float>(value)); @@ -3113,23 +3321,244 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, write<char>(buffer_appender<char>(buf), dec.significand); return dec.exponent; } else { - // Use Grisu + Dragon4 for the given precision: - // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. - const int min_exp = -60; // alpha in Grisu. - int cached_exp10 = 0; // K in Grisu. - fp normalized = normalize(fp(converted_value)); - const auto cached_pow = get_cached_power( - min_exp - (normalized.e + fp::num_significand_bits), cached_exp10); - normalized = normalized * cached_pow; - gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error && - !is_constant_evaluated()) { - exp += handler.exp10; - buf.try_resize(to_unsigned(handler.size)); - use_dragon = false; + // Extract significand bits and exponent bits. + using info = dragonbox::float_info<double>; + auto br = bit_cast<uint64_t>(static_cast<double>(value)); + + const uint64_t significand_mask = + (static_cast<uint64_t>(1) << num_significand_bits<double>()) - 1; + uint64_t significand = (br & significand_mask); + int exponent = static_cast<int>((br & exponent_mask<double>()) >> + num_significand_bits<double>()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias<double>() + num_significand_bits<double>(); + significand |= + (static_cast<uint64_t>(1) << num_significand_bits<double>()); + significand <<= 1; } else { - exp += handler.size - cached_exp10 - 1; - precision = handler.precision; + // Normalize subnormal inputs. + FMT_ASSERT(significand != 0, "zeros should not appear here"); + int shift = countl_zero(significand); + FMT_ASSERT(shift >= num_bits<uint64_t>() - num_significand_bits<double>(), + ""); + shift -= (num_bits<uint64_t>() - num_significand_bits<double>() - 2); + exponent = (std::numeric_limits<double>::min_exponent - + num_significand_bits<double>()) - + shift; + significand <<= shift; + } + + // Compute the first several nonzero decimal significand digits. + // We call the number we get the first segment. + const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); + exp = -k; + const int beta = exponent + dragonbox::floor_log2_pow10(k); + uint64_t first_segment; + bool has_more_segments; + int digits_in_the_first_segment; + { + const auto r = dragonbox::umul192_upper128( + significand << beta, dragonbox::get_cached_power(k)); + first_segment = r.high(); + has_more_segments = r.low() != 0; + + // The first segment can have 18 ~ 19 digits. + if (first_segment >= 1000000000000000000ULL) { + digits_in_the_first_segment = 19; + } else { + // When it is of 18-digits, we align it to 19-digits by adding a bogus + // zero at the end. + digits_in_the_first_segment = 18; + first_segment *= 10; + } + } + + // Compute the actual number of decimal digits to print. + if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); + + // Use Dragon4 only when there might be not enough digits in the first + // segment. + if (digits_in_the_first_segment > precision) { + use_dragon = false; + + if (precision <= 0) { + exp += digits_in_the_first_segment; + + if (precision < 0) { + // Nothing to do, since all we have are just leading zeros. + buf.try_resize(0); + } else { + // We may need to round-up. + buf.try_resize(1); + if ((first_segment | static_cast<uint64_t>(has_more_segments)) > + 5000000000000000000ULL) { + buf[0] = '1'; + } else { + buf[0] = '0'; + } + } + } // precision <= 0 + else { + exp += digits_in_the_first_segment - precision; + + // When precision > 0, we divide the first segment into three + // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits + // in 32-bits which usually allows faster calculation than in + // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize + // division-by-constant for large 64-bit divisors, we do it here + // manually. The magic number 7922816251426433760 below is equal to + // ceil(2^(64+32) / 10^10). + const uint32_t first_subsegment = static_cast<uint32_t>( + dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> + 32); + const uint64_t second_third_subsegments = + first_segment - first_subsegment * 10000000000ULL; + + uint64_t prod; + uint32_t digits; + bool should_round_up; + int number_of_digits_to_print = precision > 9 ? 9 : precision; + + // Print a 9-digits subsegment, either the first or the second. + auto print_subsegment = [&](uint32_t subsegment, char* buffer) { + int number_of_digits_printed = 0; + + // If we want to print an odd number of digits from the subsegment, + if ((number_of_digits_to_print & 1) != 0) { + // Convert to 64-bit fixed-point fractional form with 1-digit + // integer part. The magic number 720575941 is a good enough + // approximation of 2^(32 + 24) / 10^8; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast<uint64_t>(720575941)) >> 24) + 1; + digits = static_cast<uint32_t>(prod >> 32); + *buffer = static_cast<char>('0' + digits); + number_of_digits_printed++; + } + // If we want to print an even number of digits from the + // first_subsegment, + else { + // Convert to 64-bit fixed-point fractional form with 2-digits + // integer part. The magic number 450359963 is a good enough + // approximation of 2^(32 + 20) / 10^7; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast<uint64_t>(450359963)) >> 20) + 1; + digits = static_cast<uint32_t>(prod >> 32); + copy2(buffer, digits2(digits)); + number_of_digits_printed += 2; + } + + // Print all digit pairs. + while (number_of_digits_printed < number_of_digits_to_print) { + prod = static_cast<uint32_t>(prod) * static_cast<uint64_t>(100); + digits = static_cast<uint32_t>(prod >> 32); + copy2(buffer + number_of_digits_printed, digits2(digits)); + number_of_digits_printed += 2; + } + }; + + // Print first subsegment. + print_subsegment(first_subsegment, buf.data()); + + // Perform rounding if the first subsegment is the last subsegment to + // print. + if (precision <= 9) { + // Rounding inside the subsegment. + // We round-up if: + // - either the fractional part is strictly larger than 1/2, or + // - the fractional part is exactly 1/2 and the last digit is odd. + // We rely on the following observations: + // - If fractional_part >= threshold, then the fractional part is + // strictly larger than 1/2. + // - If the MSB of fractional_part is set, then the fractional part + // must be at least 1/2. + // - When the MSB of fractional_part is set, either + // second_third_subsegments being nonzero or has_more_segments + // being true means there are further digits not printed, so the + // fractional part is strictly larger than 1/2. + if (precision < 9) { + uint32_t fractional_part = static_cast<uint32_t>(prod); + should_round_up = fractional_part >= + data::fractional_part_rounding_thresholds + [8 - number_of_digits_to_print] || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + // In this case, the fractional part is at least 1/2 if and only if + // second_third_subsegments >= 5000000000ULL, and is strictly larger + // than 1/2 if we further have either second_third_subsegments > + // 5000000000ULL or has_more_segments == true. + else { + should_round_up = second_third_subsegments > 5000000000ULL || + (second_third_subsegments == 5000000000ULL && + ((digits & 1) != 0 || has_more_segments)); + } + } + // Otherwise, print the second subsegment. + else { + // Compilers are not aware of how to leverage the maximum value of + // second_third_subsegments to find out a better magic number which + // allows us to eliminate an additional shift. 1844674407370955162 = + // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). + const uint32_t second_subsegment = + static_cast<uint32_t>(dragonbox::umul128_upper64( + second_third_subsegments, 1844674407370955162ULL)); + const uint32_t third_subsegment = + static_cast<uint32_t>(second_third_subsegments) - + second_subsegment * 10; + + number_of_digits_to_print = precision - 9; + print_subsegment(second_subsegment, buf.data() + 9); + + // Rounding inside the subsegment. + if (precision < 18) { + // The condition third_subsegment != 0 implies that the segment was + // of 19 digits, so in this case the third segment should be + // consisting of a genuine digit from the input. + uint32_t fractional_part = static_cast<uint32_t>(prod); + should_round_up = fractional_part >= + data::fractional_part_rounding_thresholds + [8 - number_of_digits_to_print] || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + else { + // In this case, the segment must be of 19 digits, thus + // the third subsegment should be consisting of a genuine digit from + // the input. + should_round_up = third_subsegment > 5 || + (third_subsegment == 5 && + ((digits & 1) != 0 || has_more_segments)); + } + } + + // Round-up if necessary. + if (should_round_up) { + ++buf[precision - 1]; + for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[precision++] = '0'; + else + ++exp; + } + } + buf.try_resize(to_unsigned(precision)); + } + } // if (digits_in_the_first_segment > precision) + else { + // Adjust the exponent for its use in Dragon4. + exp += digits_in_the_first_segment - 1; } } if (use_dragon) { @@ -3156,13 +3585,10 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, } return exp; } - -template <typename Char, typename OutputIt, typename T, - FMT_ENABLE_IF(is_floating_point<T>::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value, - basic_format_specs<Char> specs, locale_ref loc = {}) +template <typename Char, typename OutputIt, typename T> +FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, + format_specs<Char> specs, locale_ref loc) -> OutputIt { - if (const_check(!is_supported_floating_point(value))) return out; float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. @@ -3186,7 +3612,7 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value, memory_buffer buffer; if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(detail::sign<char>(fspecs.sign)); - snprintf_float(convert_float(value), specs.precision, fspecs, buffer); + format_hexfloat(convert_float(value), specs.precision, fspecs, buffer); return write_bytes<align::right>(out, {buffer.data(), buffer.size()}, specs); } @@ -3209,10 +3635,19 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value, } template <typename Char, typename OutputIt, typename T, + FMT_ENABLE_IF(is_floating_point<T>::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs<Char> specs, + locale_ref loc = {}) -> OutputIt { + if (const_check(!is_supported_floating_point(value))) return out; + return specs.localized && write_loc(out, value, specs, loc) + ? out + : write_float(out, value, specs, loc); +} + +template <typename Char, typename OutputIt, typename T, FMT_ENABLE_IF(is_fast_float<T>::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { - if (is_constant_evaluated()) - return write(out, value, basic_format_specs<Char>()); + if (is_constant_evaluated()) return write(out, value, format_specs<Char>()); if (const_check(!is_supported_floating_point(value))) return out; auto fspecs = float_specs(); @@ -3221,11 +3656,11 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { value = -value; } - constexpr auto specs = basic_format_specs<Char>(); + constexpr auto specs = format_specs<Char>(); using floaty = conditional_t<std::is_same<T, long double>::value, double, T>; - using uint = typename dragonbox::float_info<floaty>::carrier_uint; - uint mask = exponent_mask<floaty>(); - if ((bit_cast<uint>(value) & mask) == mask) + using floaty_uint = typename dragonbox::float_info<floaty>::carrier_uint; + floaty_uint mask = exponent_mask<floaty>(); + if ((bit_cast<floaty_uint>(value) & mask) == mask) return write_nonfinite(out, std::isnan(value), specs, fspecs); auto dec = dragonbox::to_decimal(static_cast<floaty>(value)); @@ -3236,12 +3671,12 @@ template <typename Char, typename OutputIt, typename T, FMT_ENABLE_IF(is_floating_point<T>::value && !is_fast_float<T>::value)> inline auto write(OutputIt out, T value) -> OutputIt { - return write(out, value, basic_format_specs<Char>()); + return write(out, value, format_specs<Char>()); } template <typename Char, typename OutputIt> -auto write(OutputIt out, monostate, basic_format_specs<Char> = {}, - locale_ref = {}) -> OutputIt { +auto write(OutputIt out, monostate, format_specs<Char> = {}, locale_ref = {}) + -> OutputIt { FMT_ASSERT(false, ""); return out; } @@ -3275,8 +3710,8 @@ FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { template <typename Char, typename OutputIt, typename T, FMT_ENABLE_IF(std::is_same<T, bool>::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, - const basic_format_specs<Char>& specs = {}, - locale_ref = {}) -> OutputIt { + const format_specs<Char>& specs = {}, locale_ref = {}) + -> OutputIt { return specs.type != presentation_type::none && specs.type != presentation_type::string ? write(out, value ? 1 : 0, specs, {}) @@ -3293,20 +3728,15 @@ FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { template <typename Char, typename OutputIt> FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) -> OutputIt { - if (!value) { - throw_format_error("string pointer is null"); - } else { - out = write(out, basic_string_view<Char>(value)); - } + if (value) return write(out, basic_string_view<Char>(value)); + throw_format_error("string pointer is null"); return out; } template <typename Char, typename OutputIt, typename T, FMT_ENABLE_IF(std::is_same<T, void>::value)> -auto write(OutputIt out, const T* value, - const basic_format_specs<Char>& specs = {}, locale_ref = {}) - -> OutputIt { - check_pointer_type_spec(specs.type, error_handler()); +auto write(OutputIt out, const T* value, const format_specs<Char>& specs = {}, + locale_ref = {}) -> OutputIt { return write_ptr<Char>(out, bit_cast<uintptr_t>(value), &specs); } @@ -3316,8 +3746,8 @@ template <typename Char, typename OutputIt, typename T, FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t< std::is_class<T>::value && !is_string<T>::value && !is_floating_point<T>::value && !std::is_same<T, Char>::value && - !std::is_same<const T&, - decltype(arg_mapper<Context>().map(value))>::value, + !std::is_same<T, remove_cvref_t<decltype(arg_mapper<Context>().map( + value))>>::value, OutputIt> { return write<Char>(out, arg_mapper<Context>().map(value)); } @@ -3327,12 +3757,8 @@ template <typename Char, typename OutputIt, typename T, FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t<mapped_type_constant<T, Context>::value == type::custom_type, OutputIt> { - using formatter_type = - conditional_t<has_formatter<T, Context>::value, - typename Context::template formatter_type<T>, - fallback_formatter<T, Char>>; auto ctx = Context(out, {}, {}); - return formatter_type().format(value, ctx); + return typename Context::template formatter_type<T>().format(value, ctx); } // An argument visitor that formats the argument and writes it via the output @@ -3361,7 +3787,7 @@ template <typename Char> struct arg_formatter { using context = buffer_context<Char>; iterator out; - const basic_format_specs<Char>& specs; + const format_specs<Char>& specs; locale_ref locale; template <typename T> @@ -3386,12 +3812,6 @@ template <typename Char> struct custom_formatter { template <typename T> void operator()(T) const {} }; -template <typename T> -using is_integer = - bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value && - !std::is_same<T, char>::value && - !std::is_same<T, wchar_t>::value>; - template <typename ErrorHandler> class width_checker { public: explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} @@ -3441,55 +3861,12 @@ FMT_CONSTEXPR auto get_dynamic_spec(FormatArg arg, ErrorHandler eh) -> int { } template <typename Context, typename ID> -FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> - typename Context::format_arg { +FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> decltype(ctx.arg(id)) { auto arg = ctx.arg(id); if (!arg) ctx.on_error("argument not found"); return arg; } -// The standard format specifier handler with checking. -template <typename Char> class specs_handler : public specs_setter<Char> { - private: - basic_format_parse_context<Char>& parse_context_; - buffer_context<Char>& context_; - - // This is only needed for compatibility with gcc 4.4. - using format_arg = basic_format_arg<buffer_context<Char>>; - - FMT_CONSTEXPR auto get_arg(auto_id) -> format_arg { - return detail::get_arg(context_, parse_context_.next_arg_id()); - } - - FMT_CONSTEXPR auto get_arg(int arg_id) -> format_arg { - parse_context_.check_arg_id(arg_id); - return detail::get_arg(context_, arg_id); - } - - FMT_CONSTEXPR auto get_arg(basic_string_view<Char> arg_id) -> format_arg { - parse_context_.check_arg_id(arg_id); - return detail::get_arg(context_, arg_id); - } - - public: - FMT_CONSTEXPR specs_handler(basic_format_specs<Char>& specs, - basic_format_parse_context<Char>& parse_ctx, - buffer_context<Char>& ctx) - : specs_setter<Char>(specs), parse_context_(parse_ctx), context_(ctx) {} - - template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - this->specs_.width = get_dynamic_spec<width_checker>( - get_arg(arg_id), context_.error_handler()); - } - - template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - this->specs_.precision = get_dynamic_spec<precision_checker>( - get_arg(arg_id), context_.error_handler()); - } - - void on_error(const char* message) { context_.on_error(message); } -}; - template <template <typename> class Handler, typename Context> FMT_CONSTEXPR void handle_dynamic_spec(int& value, arg_ref<typename Context::char_type> ref, @@ -3498,26 +3875,17 @@ 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>(ctx.arg(ref.val.index), + value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.index), ctx.error_handler()); break; case arg_id_kind::name: - value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.name), + value = detail::get_dynamic_spec<Handler>(get_arg(ctx, ref.val.name), ctx.error_handler()); break; } } #if FMT_USE_USER_DEFINED_LITERALS -template <typename Char> struct udl_formatter { - basic_string_view<Char> str; - - template <typename... T> - auto operator()(T&&... args) const -> std::basic_string<Char> { - return vformat(str, fmt::make_format_args<buffer_context<Char>>(args...)); - } -}; - # if FMT_USE_NONTYPE_TEMPLATE_ARGS template <typename T, typename Char, size_t N, fmt::detail_exported::fixed_string<Char, N> Str> @@ -3556,12 +3924,12 @@ template <typename Char> struct udl_arg { #endif // FMT_USE_USER_DEFINED_LITERALS template <typename Locale, typename Char> -auto vformat(const Locale& loc, basic_string_view<Char> format_str, +auto vformat(const Locale& loc, basic_string_view<Char> fmt, basic_format_args<buffer_context<type_identity_t<Char>>> args) -> std::basic_string<Char> { - basic_memory_buffer<Char> buffer; - detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc)); - return {buffer.data(), buffer.size()}; + auto buf = basic_memory_buffer<Char>(); + detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); + return {buf.data(), buf.size()}; } using format_func = void (*)(detail::buffer<char>&, int, const char*); @@ -3571,28 +3939,28 @@ FMT_API void format_error_code(buffer<char>& out, int error_code, FMT_API void report_error(format_func func, int error_code, const char* message) noexcept; -FMT_END_DETAIL_NAMESPACE +} // namespace detail FMT_API auto vsystem_error(int error_code, string_view format_str, format_args args) -> std::system_error; /** - \rst - Constructs :class:`std::system_error` with a message formatted with - ``fmt::format(fmt, args...)``. + \rst + Constructs :class:`std::system_error` with a message formatted with + ``fmt::format(fmt, args...)``. *error_code* is a system error code as given by ``errno``. - **Example**:: - - // This throws std::system_error with the description - // cannot open file 'madeup': No such file or directory - // or similar (system message may vary). - const char* filename = "madeup"; - std::FILE* file = std::fopen(filename, "r"); - if (!file) - throw fmt::system_error(errno, "cannot open file '{}'", filename); - \endrst -*/ + **Example**:: + + // This throws std::system_error with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char* filename = "madeup"; + std::FILE* file = std::fopen(filename, "r"); + if (!file) + throw fmt::system_error(errno, "cannot open file '{}'", filename); + \endrst + */ template <typename... T> auto system_error(int error_code, format_string<T...> fmt, T&&... args) -> std::system_error { @@ -3683,93 +4051,35 @@ class format_int { }; template <typename T, typename Char> -template <typename FormatContext> -FMT_CONSTEXPR FMT_INLINE auto -formatter<T, Char, - enable_if_t<detail::type_constant<T, Char>::value != - 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()); -} +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; -template <typename Char> -struct formatter<void*, Char> : formatter<const void*, Char> { template <typename FormatContext> - auto format(void* val, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter<const void*, Char>::format(val, ctx); + auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { + return base::format(format_as(value), ctx); } }; -template <typename Char, size_t N> -struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> { - template <typename FormatContext> - FMT_CONSTEXPR auto format(const Char* val, FormatContext& ctx) const - -> decltype(ctx.out()) { - return formatter<basic_string_view<Char>, Char>::format(val, ctx); - } -}; +#define FMT_FORMAT_AS(Type, Base) \ + template <typename Char> \ + struct formatter<Type, Char> : formatter<Base, Char> {} + +FMT_FORMAT_AS(signed char, int); +FMT_FORMAT_AS(unsigned char, unsigned); +FMT_FORMAT_AS(short, int); +FMT_FORMAT_AS(unsigned short, unsigned); +FMT_FORMAT_AS(long, detail::long_type); +FMT_FORMAT_AS(unsigned long, detail::ulong_type); +FMT_FORMAT_AS(Char*, const Char*); +FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>); +FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>); +FMT_FORMAT_AS(void*, const void*); -// A formatter for types known only at run time such as variant alternatives. -// -// Usage: -// using variant = std::variant<int, std::string>; -// template <> -// struct formatter<variant>: dynamic_formatter<> { -// auto format(const variant& v, format_context& ctx) { -// return visit([&](const auto& val) { -// return dynamic_formatter<>::format(val, ctx); -// }, v); -// } -// }; -template <typename Char = char> class dynamic_formatter { - private: - detail::dynamic_format_specs<Char> specs_; - const Char* format_str_; - - struct null_handler : detail::error_handler { - void on_align(align_t) {} - void on_sign(sign_t) {} - void on_hash() {} - }; - - template <typename Context> void handle_specs(Context& ctx) { - 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); - } - - public: - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - format_str_ = ctx.begin(); - // Checks are deferred to formatting time when the argument type is known. - detail::dynamic_specs_handler<ParseContext> handler(specs_, ctx); - return detail::parse_format_specs(ctx.begin(), ctx.end(), handler); - } - - template <typename T, typename FormatContext> - auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) { - handle_specs(ctx); - detail::specs_checker<null_handler> checker( - null_handler(), detail::mapped_type_constant<T, FormatContext>::value); - checker.on_align(specs_.align); - if (specs_.sign != sign::none) checker.on_sign(specs_.sign); - if (specs_.alt) checker.on_hash(); - if (specs_.precision >= 0) checker.end_precision(); - return detail::write<Char>(ctx.out(), val, specs_, ctx.locale()); - } -}; +template <typename Char, size_t N> +struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {}; /** \rst @@ -3784,7 +4094,8 @@ template <typename T> auto ptr(T p) -> const void* { static_assert(std::is_pointer<T>::value, ""); return detail::bit_cast<const void*>(p); } -template <typename T> auto ptr(const std::unique_ptr<T>& p) -> const void* { +template <typename T, typename Deleter> +auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* { return p.get(); } template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* { @@ -3824,17 +4135,13 @@ class bytes { template <> struct formatter<bytes> { private: - detail::dynamic_format_specs<char> specs_; + detail::dynamic_format_specs<> specs_; public: template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - using handler_type = detail::dynamic_specs_handler<ParseContext>; - detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), - detail::type::string_type); - auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); - detail::check_string_type_spec(specs_.type, ctx.error_handler()); - return it; + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::string_type); } template <typename FormatContext> @@ -3848,7 +4155,9 @@ template <> struct formatter<bytes> { }; // group_digits_view is not derived from view because it copies the argument. -template <typename T> struct group_digits_view { T value; }; +template <typename T> struct group_digits_view { + T value; +}; /** \rst @@ -3867,17 +4176,13 @@ template <typename T> auto group_digits(T value) -> group_digits_view<T> { template <typename T> struct formatter<group_digits_view<T>> : formatter<T> { private: - detail::dynamic_format_specs<char> specs_; + detail::dynamic_format_specs<> specs_; public: template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - using handler_type = detail::dynamic_specs_handler<ParseContext>; - detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), - detail::type::int_type); - auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); - detail::check_string_type_spec(specs_.type, ctx.error_handler()); - return it; + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::int_type); } template <typename FormatContext> @@ -3887,12 +4192,13 @@ template <typename T> struct formatter<group_digits_view<T>> : formatter<T> { specs_.width_ref, ctx); detail::handle_dynamic_spec<detail::precision_checker>( specs_.precision, specs_.precision_ref, ctx); - return detail::write_int_localized( + return detail::write_int( ctx.out(), static_cast<detail::uint64_or_128_t<T>>(t.value), 0, specs_, - detail::digit_grouping<char>({"\3", ','})); + detail::digit_grouping<char>("\3", ",")); } }; +// DEPRECATED! join_view will be moved to ranges.h. template <typename It, typename Sentinel, typename Char = char> struct join_view : detail::view { It begin; @@ -3912,30 +4218,11 @@ struct formatter<join_view<It, Sentinel, Char>, Char> { #else typename std::iterator_traits<It>::value_type; #endif - using context = buffer_context<Char>; - using mapper = detail::arg_mapper<context>; - - template <typename T, FMT_ENABLE_IF(has_formatter<T, context>::value)> - static auto map(const T& value) -> const T& { - return value; - } - template <typename T, FMT_ENABLE_IF(!has_formatter<T, context>::value)> - static auto map(const T& value) -> decltype(mapper().map(value)) { - return mapper().map(value); - } - - using formatter_type = - conditional_t<is_formattable<value_type, Char>::value, - formatter<remove_cvref_t<decltype(map( - std::declval<const value_type&>()))>, - Char>, - detail::fallback_formatter<value_type, Char>>; - - formatter_type value_formatter_; + formatter<remove_cvref_t<value_type>, Char> value_formatter_; public: template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* { return value_formatter_.parse(ctx); } @@ -3945,12 +4232,12 @@ struct formatter<join_view<It, Sentinel, Char>, Char> { auto it = value.begin; auto out = ctx.out(); if (it != value.end) { - out = value_formatter_.format(map(*it), ctx); + out = value_formatter_.format(*it, ctx); ++it; while (it != value.end) { out = detail::copy_str<Char>(value.sep.begin(), value.sep.end(), out); ctx.advance_to(out); - out = value_formatter_.format(map(*it), ctx); + out = value_formatter_.format(*it, ctx); ++it; } } @@ -4000,11 +4287,12 @@ auto join(Range&& range, string_view sep) std::string answer = fmt::to_string(42); \endrst */ -template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> +template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value && + !detail::has_format_as<T>::value)> inline auto to_string(const T& value) -> std::string { - auto result = std::string(); - detail::write<char>(std::back_inserter(result), value); - return result; + auto buffer = memory_buffer(); + detail::write<char>(appender(buffer), value); + return {buffer.data(), buffer.size()}; } template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> @@ -4025,27 +4313,19 @@ FMT_NODISCARD auto to_string(const basic_memory_buffer<Char, SIZE>& buf) return std::basic_string<Char>(buf.data(), size); } -FMT_BEGIN_DETAIL_NAMESPACE +template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value && + detail::has_format_as<T>::value)> +inline auto to_string(const T& value) -> std::string { + return to_string(format_as(value)); +} + +FMT_END_EXPORT + +namespace detail { template <typename Char> -void vformat_to( - buffer<Char>& buf, basic_string_view<Char> fmt, - basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args, - locale_ref loc) { - // workaround for msvc bug regarding name-lookup in module - // link names into function scope - using detail::arg_formatter; - using detail::buffer_appender; - using detail::custom_formatter; - using detail::default_arg_formatter; - using detail::get_arg; - using detail::locale_ref; - using detail::parse_format_specs; - using detail::specs_checker; - using detail::specs_handler; - using detail::to_unsigned; - using detail::type; - using detail::write; +void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt, + typename vformat_args<Char>::type args, locale_ref loc) { auto out = buffer_appender<Char>(buf); if (fmt.size() == 2 && equal2(fmt.data(), "{}")) { auto arg = args.get(0); @@ -4092,15 +4372,16 @@ void vformat_to( -> const Char* { auto arg = get_arg(context, id); if (arg.type() == type::custom_type) { - parse_context.advance_to(parse_context.begin() + - (begin - &*parse_context.begin())); + parse_context.advance_to(begin); visit_format_arg(custom_formatter<Char>{parse_context, context}, arg); return parse_context.begin(); } - auto specs = basic_format_specs<Char>(); - specs_checker<specs_handler<Char>> handler( - specs_handler<Char>(specs, parse_context, context), arg.type()); - begin = parse_format_specs(begin, end, handler); + 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>( + specs.width, specs.width_ref, context); + detail::handle_dynamic_spec<detail::precision_checker>( + specs.precision, specs.precision_ref, context); if (begin == end || *begin != '}') on_error("missing '}' in format string"); auto f = arg_formatter<Char>{context.out(), specs, context.locale()}; @@ -4111,7 +4392,12 @@ void vformat_to( detail::parse_format_string<false>(fmt, format_handler(out, fmt, args, loc)); } +FMT_BEGIN_EXPORT + #ifndef FMT_HEADER_ONLY +extern template FMT_API void vformat_to(buffer<char>&, string_view, + typename vformat_args<>::type, + locale_ref); extern template FMT_API auto thousands_sep_impl<char>(locale_ref) -> thousands_sep_result<char>; extern template FMT_API auto thousands_sep_impl<wchar_t>(locale_ref) @@ -4120,7 +4406,7 @@ extern template FMT_API auto decimal_point_impl(locale_ref) -> char; extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; #endif // FMT_HEADER_ONLY -FMT_END_DETAIL_NAMESPACE +} // namespace detail #if FMT_USE_USER_DEFINED_LITERALS inline namespace literals { @@ -4157,7 +4443,7 @@ template <typename Locale, typename... T, FMT_ENABLE_IF(detail::is_locale<Locale>::value)> inline auto format(const Locale& loc, format_string<T...> fmt, T&&... args) -> std::string { - return vformat(loc, string_view(fmt), fmt::make_format_args(args...)); + return fmt::vformat(loc, string_view(fmt), fmt::make_format_args(args...)); } template <typename OutputIt, typename Locale, @@ -4168,7 +4454,7 @@ auto vformat_to(OutputIt out, const Locale& loc, string_view fmt, using detail::get_buffer; auto&& buf = get_buffer<char>(out); detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); - return detail::get_iterator(buf); + return detail::get_iterator(buf, out); } template <typename OutputIt, typename Locale, typename... T, @@ -4179,7 +4465,39 @@ FMT_INLINE auto format_to(OutputIt out, const Locale& loc, return vformat_to(out, loc, fmt, fmt::make_format_args(args...)); } -FMT_MODULE_EXPORT_END +template <typename Locale, typename... T, + FMT_ENABLE_IF(detail::is_locale<Locale>::value)> +FMT_NODISCARD FMT_INLINE auto formatted_size(const Locale& loc, + format_string<T...> fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to<char>(buf, fmt, fmt::make_format_args(args...), + detail::locale_ref(loc)); + return buf.count(); +} + +FMT_END_EXPORT + +template <typename T, typename Char> +template <typename FormatContext> +FMT_CONSTEXPR FMT_INLINE auto +formatter<T, Char, + enable_if_t<detail::type_constant<T, Char>::value != + 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()); +} + FMT_END_NAMESPACE #ifdef FMT_HEADER_ONLY diff --git a/externals/fmt/include/fmt/os.h b/externals/fmt/include/fmt/os.h index d82be112..2126424d 100644 --- a/externals/fmt/include/fmt/os.h +++ b/externals/fmt/include/fmt/os.h @@ -71,7 +71,7 @@ #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT /** \rst @@ -120,51 +120,13 @@ template <typename Char> class basic_cstring_view { using cstring_view = basic_cstring_view<char>; using wcstring_view = basic_cstring_view<wchar_t>; -template <typename Char> struct formatter<std::error_code, Char> { - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } - - template <typename FormatContext> - FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const - -> decltype(ctx.out()) { - auto out = ctx.out(); - out = detail::write_bytes(out, ec.category().name(), - basic_format_specs<Char>()); - out = detail::write<Char>(out, Char(':')); - out = detail::write<Char>(out, ec.value()); - return out; - } -}; - #ifdef _WIN32 FMT_API const std::error_category& system_category() noexcept; -FMT_BEGIN_DETAIL_NAMESPACE -// A converter from UTF-16 to UTF-8. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf16_to_utf8 { - private: - memory_buffer buffer_; - - public: - utf16_to_utf8() {} - FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s); - 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()); } - - // Performs conversion returning a system error code instead of - // throwing exception on conversion error. This method may still throw - // in case of memory allocation error. - FMT_API int convert(basic_string_view<wchar_t> s); -}; - +namespace detail { FMT_API void format_windows_error(buffer<char>& out, int error_code, const char* message) noexcept; -FMT_END_DETAIL_NAMESPACE +} FMT_API std::system_error vwindows_error(int error_code, string_view format_str, format_args args); @@ -355,12 +317,18 @@ class FMT_API file { // Creates a buffered_file object associated with this file and detaches // this file object from the file. buffered_file fdopen(const char* mode); + +# if defined(_WIN32) && !defined(__MINGW32__) + // Opens a file and constructs a file object representing this file by + // wcstring_view filename. Windows only. + static file open_windows_file(wcstring_view path, int oflag); +# endif }; // Returns the memory page size. long getpagesize(); -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { struct buffer_size { buffer_size() = default; @@ -397,56 +365,61 @@ struct ostream_params { # endif }; -FMT_END_DETAIL_NAMESPACE +class file_buffer final : public buffer<char> { + file file_; + + FMT_API void grow(size_t) override; + + public: + FMT_API file_buffer(cstring_view path, const ostream_params& params); + FMT_API file_buffer(file_buffer&& other); + FMT_API ~file_buffer(); + + void flush() { + if (size() == 0) return; + file_.write(data(), size() * sizeof(data()[0])); + clear(); + } + + void close() { + flush(); + file_.close(); + } +}; + +} // namespace detail // Added {} below to work around default constructor error known to // occur in Xcode versions 7.2.1 and 8.2.1. constexpr detail::buffer_size buffer_size{}; /** A fast output stream which is not thread-safe. */ -class FMT_API ostream final : private detail::buffer<char> { +class FMT_API ostream { private: - file file_; - - void grow(size_t) override; + FMT_MSC_WARNING(suppress : 4251) + detail::file_buffer buffer_; ostream(cstring_view path, const detail::ostream_params& params) - : file_(path, params.oflag) { - set(new char[params.buffer_size], params.buffer_size); - } + : buffer_(path, params) {} public: - ostream(ostream&& other) - : detail::buffer<char>(other.data(), other.size(), other.capacity()), - file_(std::move(other.file_)) { - other.clear(); - other.set(nullptr, 0); - } - ~ostream() { - flush(); - delete[] data(); - } + ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {} - void flush() { - if (size() == 0) return; - file_.write(data(), size()); - clear(); - } + ~ostream(); + + void flush() { buffer_.flush(); } template <typename... T> friend ostream output_file(cstring_view path, T... params); - void close() { - flush(); - file_.close(); - } + void close() { buffer_.close(); } /** Formats ``args`` according to specifications in ``fmt`` and writes the output to the file. */ template <typename... T> void print(format_string<T...> fmt, T&&... args) { - vformat_to(detail::buffer_appender<char>(*this), fmt, + vformat_to(detail::buffer_appender<char>(buffer_), fmt, fmt::make_format_args(args...)); } }; @@ -472,7 +445,7 @@ inline ostream output_file(cstring_view path, T... params) { } #endif // FMT_USE_FCNTL -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_OS_H_ diff --git a/externals/fmt/include/fmt/ostream.h b/externals/fmt/include/fmt/ostream.h index 394d947c..a112fe7b 100644 --- a/externals/fmt/include/fmt/ostream.h +++ b/externals/fmt/include/fmt/ostream.h @@ -8,86 +8,65 @@ #ifndef FMT_OSTREAM_H_ #define FMT_OSTREAM_H_ -#include <fstream> -#include <ostream> +#include <fstream> // std::filebuf + +#if defined(_WIN32) && defined(__GLIBCXX__) +# include <ext/stdio_filebuf.h> +# include <ext/stdio_sync_filebuf.h> +#elif defined(_WIN32) && defined(_LIBCPP_VERSION) +# include <__std_stream> +#endif #include "format.h" FMT_BEGIN_NAMESPACE -template <typename OutputIt, typename Char> class basic_printf_context; - namespace detail { -// Checks if T has a user-defined operator<<. -template <typename T, typename Char, typename Enable = void> -class is_streamable { - private: - template <typename U> - static auto test(int) - -> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>() - << std::declval<U>()) != 0>; - - template <typename> static auto test(...) -> std::false_type; - - using result = decltype(test<T>(0)); - - public: - is_streamable() = default; - - static const bool value = result::value; -}; - -// Formatting of built-in types and arrays is intentionally disabled because -// it's handled by standard (non-ostream) formatters. -template <typename T, typename Char> -struct is_streamable< - T, Char, - enable_if_t< - std::is_arithmetic<T>::value || std::is_array<T>::value || - std::is_pointer<T>::value || std::is_same<T, char8_type>::value || - std::is_convertible<T, fmt::basic_string_view<Char>>::value || - std::is_same<T, std_string_view<Char>>::value || - (std::is_convertible<T, int>::value && !std::is_enum<T>::value)>> - : std::false_type {}; - -template <typename Char> FILE* get_file(std::basic_filebuf<Char>&) { - return nullptr; -} - -struct dummy_filebuf { - FILE* _Myfile; -}; -template <typename T, typename U = int> struct ms_filebuf { - using type = dummy_filebuf; -}; -template <typename T> struct ms_filebuf<T, decltype(T::_Myfile, 0)> { - using type = T; -}; -using filebuf_type = ms_filebuf<std::filebuf>::type; - -FILE* get_file(filebuf_type& buf); - // Generate a unique explicit instantion in every translation unit using a tag // type in an anonymous namespace. namespace { -struct filebuf_access_tag {}; +struct file_access_tag {}; } // namespace -template <typename Tag, typename FileMemberPtr, FileMemberPtr file> -class filebuf_access { - friend FILE* get_file(filebuf_type& buf) { return buf.*file; } +template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr> +class file_access { + friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } }; -template class filebuf_access<filebuf_access_tag, - decltype(&filebuf_type::_Myfile), - &filebuf_type::_Myfile>; - -inline bool write(std::filebuf& buf, fmt::string_view data) { - FILE* f = get_file(buf); - if (!f) return false; - print(f, data); - return true; + +#if FMT_MSC_VERSION +template class file_access<file_access_tag, std::filebuf, + &std::filebuf::_Myfile>; +auto get_file(std::filebuf&) -> FILE*; +#elif defined(_WIN32) && defined(_LIBCPP_VERSION) +template class file_access<file_access_tag, std::__stdoutbuf<char>, + &std::__stdoutbuf<char>::__file_>; +auto get_file(std::__stdoutbuf<char>&) -> FILE*; +#endif + +inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) { +#if FMT_MSC_VERSION + if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf())) + if (FILE* f = get_file(*buf)) return write_console(f, data); +#elif defined(_WIN32) && defined(__GLIBCXX__) + auto* rdbuf = os.rdbuf(); + FILE* c_file; + if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf)) + c_file = sfbuf->file(); + else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf)) + c_file = fbuf->file(); + else + return false; + if (c_file) return write_console(c_file, data); +#elif defined(_WIN32) && defined(_LIBCPP_VERSION) + if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf())) + if (FILE* f = get_file(*buf)) return write_console(f, data); +#else + ignore_unused(os, data); +#endif + return false; } -inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) { +inline bool write_ostream_unicode(std::wostream&, + fmt::basic_string_view<wchar_t>) { return false; } @@ -95,10 +74,6 @@ inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) { // It is a separate function rather than a part of vprint to simplify testing. template <typename Char> void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { - if (const_check(FMT_MSC_VERSION)) { - auto filebuf = dynamic_cast<std::basic_filebuf<Char>*>(os.rdbuf()); - if (filebuf && write(*filebuf, {buf.data(), buf.size()})) return; - } const Char* buf_data = buf.data(); using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; unsigned_streamsize size = buf.size(); @@ -130,11 +105,13 @@ template <typename T> struct streamed_view { const T& value; }; // Formats an object of type T that has an overloaded ostream operator<<. template <typename Char> struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> { + void set_debug_format() = delete; + template <typename T, typename OutputIt> auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const -> OutputIt { auto buffer = basic_memory_buffer<Char>(); - format_value(buffer, value, ctx.locale()); + detail::format_value(buffer, value, ctx.locale()); return formatter<basic_string_view<Char>, Char>::format( {buffer.data(), buffer.size()}, ctx); } @@ -142,12 +119,13 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> { using ostream_formatter = basic_ostream_formatter<char>; -template <typename T> -struct formatter<detail::streamed_view<T>> : ostream_formatter { +template <typename T, typename Char> +struct formatter<detail::streamed_view<T>, Char> + : basic_ostream_formatter<Char> { template <typename OutputIt> auto format(detail::streamed_view<T> view, - basic_format_context<OutputIt, char>& ctx) const -> OutputIt { - return ostream_formatter::format(view.value, ctx); + basic_format_context<OutputIt, Char>& ctx) const -> OutputIt { + return basic_ostream_formatter<Char>::format(view.value, ctx); } }; @@ -168,21 +146,22 @@ auto streamed(const T& value) -> detail::streamed_view<T> { namespace detail { -// Formats an object of type T that has an overloaded ostream operator<<. -template <typename T, typename Char> -struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> - : basic_ostream_formatter<Char> { - using basic_ostream_formatter<Char>::format; -}; +inline void vprint_directly(std::ostream& os, string_view format_str, + format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, format_str, args); + detail::write_buffer(os, buffer); +} } // namespace detail -FMT_MODULE_EXPORT template <typename Char> +FMT_EXPORT template <typename Char> void vprint(std::basic_ostream<Char>& os, basic_string_view<type_identity_t<Char>> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) { auto buffer = basic_memory_buffer<Char>(); detail::vformat_to(buffer, format_str, args); + if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return; detail::write_buffer(os, buffer); } @@ -195,12 +174,16 @@ void vprint(std::basic_ostream<Char>& os, fmt::print(cerr, "Don't {}!", "panic"); \endrst */ -FMT_MODULE_EXPORT template <typename... T> +FMT_EXPORT template <typename... T> void print(std::ostream& os, format_string<T...> fmt, T&&... args) { - vprint(os, fmt, fmt::make_format_args(args...)); + const auto& vargs = fmt::make_format_args(args...); + if (detail::is_utf8()) + vprint(os, fmt, vargs); + else + detail::vprint_directly(os, fmt, vargs); } -FMT_MODULE_EXPORT +FMT_EXPORT template <typename... Args> void print(std::wostream& os, basic_format_string<wchar_t, type_identity_t<Args>...> fmt, @@ -208,6 +191,19 @@ void print(std::wostream& os, vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...)); } +FMT_EXPORT template <typename... T> +void println(std::ostream& os, format_string<T...> fmt, T&&... args) { + fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...)); +} + +FMT_EXPORT +template <typename... Args> +void println(std::wostream& os, + basic_format_string<wchar_t, type_identity_t<Args>...> fmt, + Args&&... args) { + print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...)); +} + FMT_END_NAMESPACE #endif // FMT_OSTREAM_H_ diff --git a/externals/fmt/include/fmt/printf.h b/externals/fmt/include/fmt/printf.h index 70a592dc..adef6adf 100644 --- a/externals/fmt/include/fmt/printf.h +++ b/externals/fmt/include/fmt/printf.h @@ -14,24 +14,18 @@ #include "format.h" FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT template <typename T> struct printf_formatter { printf_formatter() = delete; }; -template <typename Char> -class basic_printf_parse_context : public basic_format_parse_context<Char> { - using basic_format_parse_context<Char>::basic_format_parse_context; -}; - -template <typename OutputIt, typename Char> class basic_printf_context { +template <typename Char> class basic_printf_context { private: - OutputIt out_; + detail::buffer_appender<Char> out_; basic_format_args<basic_printf_context> args_; public: using char_type = Char; - using format_arg = basic_format_arg<basic_printf_context>; - using parse_context_type = basic_printf_parse_context<Char>; + using parse_context_type = basic_format_parse_context<Char>; template <typename T> using formatter_type = printf_formatter<T>; /** @@ -40,68 +34,68 @@ template <typename OutputIt, typename Char> class basic_printf_context { stored in the context object so make sure they have appropriate lifetimes. \endrst */ - basic_printf_context(OutputIt out, + basic_printf_context(detail::buffer_appender<Char> out, basic_format_args<basic_printf_context> args) : out_(out), args_(args) {} - OutputIt out() { return out_; } - void advance_to(OutputIt it) { out_ = it; } + auto out() -> detail::buffer_appender<Char> { return out_; } + void advance_to(detail::buffer_appender<Char>) {} - detail::locale_ref locale() { return {}; } + auto locale() -> detail::locale_ref { return {}; } - format_arg arg(int id) const { return args_.get(id); } + auto arg(int id) const -> basic_format_arg<basic_printf_context> { + return args_.get(id); + } FMT_CONSTEXPR void on_error(const char* message) { detail::error_handler().on_error(message); } }; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // Checks if a value fits in int - used to avoid warnings about comparing // signed and unsigned integers. template <bool IsSigned> struct int_checker { - template <typename T> static bool fits_in_int(T value) { + template <typename T> static auto fits_in_int(T value) -> bool { unsigned max = max_value<int>(); return value <= max; } - static bool fits_in_int(bool) { return true; } + static auto fits_in_int(bool) -> bool { return true; } }; template <> struct int_checker<true> { - template <typename T> static bool fits_in_int(T value) { + template <typename T> static auto fits_in_int(T value) -> bool { return value >= (std::numeric_limits<int>::min)() && value <= max_value<int>(); } - static bool fits_in_int(int) { return true; } + static auto fits_in_int(int) -> bool { return true; } }; -class printf_precision_handler { - public: +struct printf_precision_handler { template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> - int operator()(T value) { + auto operator()(T value) -> int { if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) - FMT_THROW(format_error("number is too big")); + throw_format_error("number is too big"); return (std::max)(static_cast<int>(value), 0); } template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> - int operator()(T) { - FMT_THROW(format_error("precision is not integer")); + auto operator()(T) -> int { + throw_format_error("precision is not integer"); return 0; } }; // An argument visitor that returns true iff arg is a zero integer. -class is_zero_int { - public: +struct is_zero_int { template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> - bool operator()(T value) { + auto operator()(T value) -> bool { return value == 0; } template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> - bool operator()(T) { + auto operator()(T) -> bool { return false; } }; @@ -132,22 +126,23 @@ template <typename T, typename Context> class arg_converter { if (const_check(sizeof(target_type) <= sizeof(int))) { // Extra casts are used to silence warnings. if (is_signed) { - arg_ = detail::make_arg<Context>( - static_cast<int>(static_cast<target_type>(value))); + auto n = static_cast<int>(static_cast<target_type>(value)); + arg_ = detail::make_arg<Context>(n); } else { using unsigned_type = typename make_unsigned_or_bool<target_type>::type; - arg_ = detail::make_arg<Context>( - static_cast<unsigned>(static_cast<unsigned_type>(value))); + auto n = static_cast<unsigned>(static_cast<unsigned_type>(value)); + arg_ = detail::make_arg<Context>(n); } } else { if (is_signed) { // glibc's printf doesn't sign extend arguments of smaller types: // std::printf("%lld", -42); // prints "4294967254" // but we don't have to do the same because it's a UB. - arg_ = detail::make_arg<Context>(static_cast<long long>(value)); + auto n = static_cast<long long>(value); + arg_ = detail::make_arg<Context>(n); } else { - arg_ = detail::make_arg<Context>( - static_cast<typename make_unsigned_or_bool<U>::type>(value)); + auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value); + arg_ = detail::make_arg<Context>(n); } } } @@ -175,8 +170,8 @@ template <typename Context> class char_converter { template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> void operator()(T value) { - arg_ = detail::make_arg<Context>( - static_cast<typename Context::char_type>(value)); + auto c = static_cast<typename Context::char_type>(value); + arg_ = detail::make_arg<Context>(c); } template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> @@ -186,122 +181,131 @@ template <typename Context> class char_converter { // An argument visitor that return a pointer to a C string if argument is a // string or null otherwise. template <typename Char> struct get_cstring { - template <typename T> const Char* operator()(T) { return nullptr; } - const Char* operator()(const Char* s) { return s; } + template <typename T> auto operator()(T) -> const Char* { return nullptr; } + auto operator()(const Char* s) -> const Char* { return s; } }; // Checks if an argument is a valid printf width specifier and sets // left alignment if it is negative. template <typename Char> class printf_width_handler { private: - using format_specs = basic_format_specs<Char>; - - format_specs& specs_; + format_specs<Char>& specs_; public: - explicit printf_width_handler(format_specs& specs) : specs_(specs) {} + explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {} template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> - unsigned operator()(T value) { + auto operator()(T value) -> unsigned { auto width = static_cast<uint32_or_64_or_128_t<T>>(value); if (detail::is_negative(value)) { specs_.align = align::left; width = 0 - width; } unsigned int_max = max_value<int>(); - if (width > int_max) FMT_THROW(format_error("number is too big")); + if (width > int_max) throw_format_error("number is too big"); return static_cast<unsigned>(width); } template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> - unsigned operator()(T) { - FMT_THROW(format_error("width is not integer")); + auto operator()(T) -> unsigned { + throw_format_error("width is not integer"); return 0; } }; +// Workaround for a bug with the XL compiler when initializing +// printf_arg_formatter's base class. +template <typename Char> +auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s) + -> arg_formatter<Char> { + return {iter, s, locale_ref()}; +} + // The ``printf`` argument formatter. -template <typename OutputIt, typename Char> +template <typename Char> class printf_arg_formatter : public arg_formatter<Char> { private: using base = arg_formatter<Char>; - using context_type = basic_printf_context<OutputIt, Char>; - using format_specs = basic_format_specs<Char>; + using context_type = basic_printf_context<Char>; context_type& context_; - OutputIt write_null_pointer(bool is_string = false) { + void write_null_pointer(bool is_string = false) { auto s = this->specs; s.type = presentation_type::none; - return write_bytes(this->out, is_string ? "(null)" : "(nil)", s); + write_bytes(this->out, is_string ? "(null)" : "(nil)", s); } public: - printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx) - : base{iter, s, locale_ref()}, context_(ctx) {} + printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s, + context_type& ctx) + : base(make_arg_formatter(iter, s)), context_(ctx) {} - OutputIt operator()(monostate value) { return base::operator()(value); } + void operator()(monostate value) { base::operator()(value); } template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)> - OutputIt operator()(T value) { + void operator()(T value) { // MSVC2013 fails to compile separate overloads for bool and Char so use // std::is_same instead. - if (std::is_same<T, Char>::value) { - format_specs fmt_specs = this->specs; - if (fmt_specs.type != presentation_type::none && - fmt_specs.type != presentation_type::chr) { - return (*this)(static_cast<int>(value)); - } - fmt_specs.sign = sign::none; - fmt_specs.alt = false; - fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. - // align::numeric needs to be overwritten here since the '0' flag is - // ignored for non-numeric types - if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) - fmt_specs.align = align::right; - return write<Char>(this->out, static_cast<Char>(value), fmt_specs); + if (!std::is_same<T, Char>::value) { + base::operator()(value); + return; + } + format_specs<Char> fmt_specs = this->specs; + if (fmt_specs.type != presentation_type::none && + fmt_specs.type != presentation_type::chr) { + return (*this)(static_cast<int>(value)); } - return base::operator()(value); + fmt_specs.sign = sign::none; + fmt_specs.alt = false; + fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. + // align::numeric needs to be overwritten here since the '0' flag is + // ignored for non-numeric types + if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) + fmt_specs.align = align::right; + write<Char>(this->out, static_cast<Char>(value), fmt_specs); } template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> - OutputIt operator()(T value) { - return base::operator()(value); + void operator()(T value) { + base::operator()(value); } /** Formats a null-terminated C string. */ - OutputIt operator()(const char* value) { - if (value) return base::operator()(value); - return write_null_pointer(this->specs.type != presentation_type::pointer); + void operator()(const char* value) { + if (value) + base::operator()(value); + else + write_null_pointer(this->specs.type != presentation_type::pointer); } /** Formats a null-terminated wide C string. */ - OutputIt operator()(const wchar_t* value) { - if (value) return base::operator()(value); - return write_null_pointer(this->specs.type != presentation_type::pointer); + void operator()(const wchar_t* value) { + if (value) + base::operator()(value); + else + write_null_pointer(this->specs.type != presentation_type::pointer); } - OutputIt operator()(basic_string_view<Char> value) { - return base::operator()(value); - } + void operator()(basic_string_view<Char> value) { base::operator()(value); } /** Formats a pointer. */ - OutputIt operator()(const void* value) { - return value ? base::operator()(value) : write_null_pointer(); + void operator()(const void* value) { + if (value) + base::operator()(value); + else + write_null_pointer(); } /** Formats an argument of a custom (user-defined) type. */ - OutputIt operator()(typename basic_format_arg<context_type>::handle handle) { - auto parse_ctx = - basic_printf_parse_context<Char>(basic_string_view<Char>()); + void operator()(typename basic_format_arg<context_type>::handle handle) { + auto parse_ctx = basic_format_parse_context<Char>({}); handle.format(parse_ctx, context_); - return this->out; } }; template <typename Char> -void parse_flags(basic_format_specs<Char>& specs, const Char*& it, - const Char* end) { +void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) { for (; it != end; ++it) { switch (*it) { case '-': @@ -314,9 +318,7 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it, specs.fill[0] = '0'; break; case ' ': - if (specs.sign != sign::plus) { - specs.sign = sign::space; - } + if (specs.sign != sign::plus) specs.sign = sign::space; break; case '#': specs.alt = true; @@ -328,8 +330,8 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it, } template <typename Char, typename GetArg> -int parse_header(const Char*& it, const Char* end, - basic_format_specs<Char>& specs, GetArg get_arg) { +auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs, + GetArg get_arg) -> int { int arg_index = -1; Char c = *it; if (c >= '0' && c <= '9') { @@ -344,7 +346,7 @@ int parse_header(const Char*& it, const Char* end, if (value != 0) { // Nonzero value means that we parsed width and don't need to // parse it or flags again, so return now. - if (value == -1) FMT_THROW(format_error("number is too big")); + if (value == -1) throw_format_error("number is too big"); specs.width = value; return arg_index; } @@ -355,7 +357,7 @@ int parse_header(const Char*& it, const Char* end, if (it != end) { if (*it >= '0' && *it <= '9') { specs.width = parse_nonnegative_int(it, end, -1); - if (specs.width == -1) FMT_THROW(format_error("number is too big")); + if (specs.width == -1) throw_format_error("number is too big"); } else if (*it == '*') { ++it; specs.width = static_cast<int>(visit_format_arg( @@ -365,13 +367,53 @@ int parse_header(const Char*& it, const Char* end, return arg_index; } +inline auto parse_printf_presentation_type(char c, type t) + -> presentation_type { + using pt = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + switch (c) { + case 'd': + return in(t, integral_set) ? pt::dec : pt::none; + case 'o': + return in(t, integral_set) ? pt::oct : pt::none; + case 'x': + return in(t, integral_set) ? pt::hex_lower : pt::none; + case 'X': + return in(t, integral_set) ? pt::hex_upper : pt::none; + case 'a': + return in(t, float_set) ? pt::hexfloat_lower : pt::none; + case 'A': + return in(t, float_set) ? pt::hexfloat_upper : pt::none; + case 'e': + return in(t, float_set) ? pt::exp_lower : pt::none; + case 'E': + return in(t, float_set) ? pt::exp_upper : pt::none; + case 'f': + return in(t, float_set) ? pt::fixed_lower : pt::none; + case 'F': + return in(t, float_set) ? pt::fixed_upper : pt::none; + case 'g': + return in(t, float_set) ? pt::general_lower : pt::none; + case 'G': + return in(t, float_set) ? pt::general_upper : pt::none; + case 'c': + return in(t, integral_set) ? pt::chr : pt::none; + case 's': + return in(t, string_set | cstring_set) ? pt::string : pt::none; + case 'p': + return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none; + default: + return pt::none; + } +} + template <typename Char, typename Context> void vprintf(buffer<Char>& buf, basic_string_view<Char> format, basic_format_args<Context> args) { - using OutputIt = buffer_appender<Char>; - auto out = OutputIt(buf); - auto context = basic_printf_context<OutputIt, Char>(out, args); - auto parse_ctx = basic_printf_parse_context<Char>(format); + using iterator = buffer_appender<Char>; + auto out = iterator(buf); + auto context = basic_printf_context<Char>(out, args); + auto parse_ctx = basic_format_parse_context<Char>(format); // Returns the argument with specified index or, if arg_index is -1, the next // argument. @@ -387,26 +429,24 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format, const Char* end = parse_ctx.end(); auto it = start; while (it != end) { - if (!detail::find<false, Char>(it, end, '%', it)) { - it = end; // detail::find leaves it == nullptr if it doesn't find '%' + if (!find<false, Char>(it, end, '%', it)) { + it = end; // find leaves it == nullptr if it doesn't find '%'. break; } Char c = *it++; if (it != end && *it == c) { - out = detail::write( - out, basic_string_view<Char>(start, detail::to_unsigned(it - start))); + write(out, basic_string_view<Char>(start, to_unsigned(it - start))); start = ++it; continue; } - out = detail::write(out, basic_string_view<Char>( - start, detail::to_unsigned(it - 1 - start))); + write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start))); - basic_format_specs<Char> specs; + auto specs = format_specs<Char>(); specs.align = align::right; // Parse argument index, flags and width. int arg_index = parse_header(it, end, specs, get_arg); - if (arg_index == 0) parse_ctx.on_error("argument not found"); + if (arg_index == 0) throw_format_error("argument not found"); // Parse precision. if (it != end && *it == '.') { @@ -417,7 +457,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format, } else if (c == '*') { ++it; specs.precision = static_cast<int>( - visit_format_arg(detail::printf_precision_handler(), get_arg(-1))); + visit_format_arg(printf_precision_handler(), get_arg(-1))); } else { specs.precision = 0; } @@ -426,20 +466,19 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format, auto arg = get_arg(arg_index); // For d, i, o, u, x, and X conversion specifiers, if a precision is // specified, the '0' flag is ignored - if (specs.precision >= 0 && arg.is_integral()) - specs.fill[0] = - ' '; // Ignore '0' flag for non-numeric types or if '-' present. - if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) { - auto str = visit_format_arg(detail::get_cstring<Char>(), arg); + if (specs.precision >= 0 && arg.is_integral()) { + // Ignore '0' for non-numeric types or if '-' present. + specs.fill[0] = ' '; + } + if (specs.precision >= 0 && arg.type() == type::cstring_type) { + auto str = visit_format_arg(get_cstring<Char>(), arg); auto str_end = str + specs.precision; auto nul = std::find(str, str_end, Char()); - arg = detail::make_arg<basic_printf_context<OutputIt, Char>>( - basic_string_view<Char>( - str, detail::to_unsigned(nul != str_end ? nul - str - : specs.precision))); + auto sv = basic_string_view<Char>( + str, to_unsigned(nul != str_end ? nul - str : specs.precision)); + arg = make_arg<basic_printf_context<Char>>(sv); } - if (specs.alt && visit_format_arg(detail::is_zero_int(), arg)) - specs.alt = false; + if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false; if (specs.fill[0] == '0') { if (arg.is_arithmetic() && specs.align != align::left) specs.align = align::numeric; @@ -451,7 +490,6 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format, // Parse length and convert the argument to the required type. c = it != end ? *it++ : 0; Char t = it != end ? *it : 0; - using detail::convert_arg; switch (c) { case 'h': if (t == 'h') { @@ -490,7 +528,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format, } // Parse type. - if (it == end) FMT_THROW(format_error("invalid format string")); + if (it == end) throw_format_error("invalid format string"); char type = static_cast<char>(*it++); if (arg.is_integral()) { // Normalize type. @@ -500,32 +538,25 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format, type = 'd'; break; case 'c': - visit_format_arg( - detail::char_converter<basic_printf_context<OutputIt, Char>>(arg), - arg); + visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg); break; } } - specs.type = parse_presentation_type(type); + specs.type = parse_printf_presentation_type(type, arg.type()); if (specs.type == presentation_type::none) - parse_ctx.on_error("invalid type specifier"); + throw_format_error("invalid format specifier"); start = it; // Format argument. - out = visit_format_arg( - detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg); + visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg); } - detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start))); + write(out, basic_string_view<Char>(start, to_unsigned(it - start))); } -FMT_END_DETAIL_NAMESPACE - -template <typename Char> -using basic_printf_context_t = - basic_printf_context<detail::buffer_appender<Char>, Char>; +} // namespace detail -using printf_context = basic_printf_context_t<char>; -using wprintf_context = basic_printf_context_t<wchar_t>; +using printf_context = basic_printf_context<char>; +using wprintf_context = basic_printf_context<wchar_t>; using printf_args = basic_format_args<printf_context>; using wprintf_args = basic_format_args<wprintf_context>; @@ -542,26 +573,21 @@ inline auto make_printf_args(const T&... args) return {args...}; } -/** - \rst - Constructs an `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::wprintf_args`. - \endrst - */ +// DEPRECATED! template <typename... T> inline auto make_wprintf_args(const T&... args) -> format_arg_store<wprintf_context, T...> { return {args...}; } -template <typename S, typename Char = char_t<S>> +template <typename Char> inline auto vsprintf( - const S& fmt, - basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) + basic_string_view<Char> fmt, + basic_format_args<basic_printf_context<type_identity_t<Char>>> args) -> std::basic_string<Char> { - basic_memory_buffer<Char> buffer; - vprintf(buffer, detail::to_string_view(fmt), args); - return to_string(buffer); + auto buf = basic_memory_buffer<Char>(); + detail::vprintf(buf, fmt, args); + return to_string(buf); } /** @@ -576,20 +602,19 @@ inline auto vsprintf( template <typename S, typename... T, typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> { - using context = basic_printf_context_t<Char>; return vsprintf(detail::to_string_view(fmt), - fmt::make_format_args<context>(args...)); + fmt::make_format_args<basic_printf_context<Char>>(args...)); } -template <typename S, typename Char = char_t<S>> +template <typename Char> inline auto vfprintf( - std::FILE* f, const S& fmt, - basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) + std::FILE* f, basic_string_view<Char> fmt, + basic_format_args<basic_printf_context<type_identity_t<Char>>> args) -> int { - basic_memory_buffer<Char> buffer; - vprintf(buffer, detail::to_string_view(fmt), args); - size_t size = buffer.size(); - return std::fwrite(buffer.data(), sizeof(Char), size, f) < size + auto buf = basic_memory_buffer<Char>(); + detail::vprintf(buf, fmt, args); + size_t size = buf.size(); + return std::fwrite(buf.data(), sizeof(Char), size, f) < size ? -1 : static_cast<int>(size); } @@ -605,17 +630,16 @@ inline auto vfprintf( */ template <typename S, typename... T, typename Char = char_t<S>> inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { - using context = basic_printf_context_t<Char>; return vfprintf(f, detail::to_string_view(fmt), - fmt::make_format_args<context>(args...)); + fmt::make_format_args<basic_printf_context<Char>>(args...)); } -template <typename S, typename Char = char_t<S>> -inline auto vprintf( - const S& fmt, - basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) +template <typename Char> +FMT_DEPRECATED inline auto vprintf( + basic_string_view<Char> fmt, + basic_format_args<basic_printf_context<type_identity_t<Char>>> args) -> int { - return vfprintf(stdout, detail::to_string_view(fmt), args); + return vfprintf(stdout, fmt, args); } /** @@ -627,14 +651,17 @@ inline auto vprintf( fmt::printf("Elapsed time: %.2f seconds", 1.23); \endrst */ -template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)> -inline auto printf(const S& fmt, const T&... args) -> int { - return vprintf( - detail::to_string_view(fmt), - fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...)); +template <typename... T> +inline auto printf(string_view fmt, const T&... args) -> int { + return vfprintf(stdout, fmt, make_printf_args(args...)); +} +template <typename... T> +FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt, + const T&... args) -> int { + return vfprintf(stdout, fmt, make_wprintf_args(args...)); } -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_PRINTF_H_ diff --git a/externals/fmt/include/fmt/ranges.h b/externals/fmt/include/fmt/ranges.h index 10429fc8..65beba5b 100644 --- a/externals/fmt/include/fmt/ranges.h +++ b/externals/fmt/include/fmt/ranges.h @@ -22,27 +22,25 @@ FMT_BEGIN_NAMESPACE namespace detail { -template <typename RangeT, typename OutputIterator> -OutputIterator copy(const RangeT& range, OutputIterator out) { +template <typename Range, typename OutputIt> +auto copy(const Range& range, OutputIt out) -> OutputIt { for (auto it = range.begin(), end = range.end(); it != end; ++it) *out++ = *it; return out; } -template <typename OutputIterator> -OutputIterator copy(const char* str, OutputIterator out) { +template <typename OutputIt> +auto copy(const char* str, OutputIt out) -> OutputIt { while (*str) *out++ = *str++; return out; } -template <typename OutputIterator> -OutputIterator copy(char ch, OutputIterator out) { +template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt { *out++ = ch; return out; } -template <typename OutputIterator> -OutputIterator copy(wchar_t ch, OutputIterator out) { +template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt { *out++ = ch; return out; } @@ -69,7 +67,7 @@ template <typename T> class is_map { template <typename> static void check(...); public: -#ifdef FMT_FORMAT_MAP_AS_LIST +#ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED! static constexpr const bool value = false; #else static constexpr const bool value = @@ -82,7 +80,7 @@ template <typename T> class is_set { template <typename> static void check(...); public: -#ifdef FMT_FORMAT_SET_AS_LIST +#ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED! static constexpr const bool value = false; #else static constexpr const bool value = @@ -157,8 +155,9 @@ template <typename T> struct has_mutable_begin_end< T, void_t<decltype(detail::range_begin(std::declval<T>())), decltype(detail::range_end(std::declval<T>())), - enable_if_t<std::is_copy_constructible<T>::value>>> - : std::true_type {}; + // the extra int here is because older versions of MSVC don't + // SFINAE properly unless there are distinct types + int>> : std::true_type {}; template <typename T> struct is_range_<T, void> @@ -211,41 +210,61 @@ class is_tuple_formattable_ { static constexpr const bool value = false; }; template <typename T, typename C> class is_tuple_formattable_<T, C, true> { - template <std::size_t... I> - static std::true_type check2(index_sequence<I...>, - integer_sequence<bool, (I == I)...>); + template <std::size_t... Is> + static std::true_type check2(index_sequence<Is...>, + integer_sequence<bool, (Is == Is)...>); static std::false_type check2(...); - template <std::size_t... I> + template <std::size_t... Is> static decltype(check2( - index_sequence<I...>{}, + index_sequence<Is...>{}, integer_sequence< - bool, (is_formattable<typename std::tuple_element<I, T>::type, - C>::value)...>{})) check(index_sequence<I...>); + bool, (is_formattable<typename std::tuple_element<Is, T>::type, + C>::value)...>{})) check(index_sequence<Is...>); public: static constexpr const bool value = decltype(check(tuple_index_sequence<T>{}))::value; }; -template <class Tuple, class F, size_t... Is> -void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept { +template <typename Tuple, typename F, size_t... Is> +FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) { using std::get; - // using free function get<I>(T) now. - const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; - (void)_; // blocks warnings + // Using a free function get<Is>(Tuple) now. + const int unused[] = {0, ((void)f(get<Is>(t)), 0)...}; + ignore_unused(unused); +} + +template <typename Tuple, typename F> +FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) { + for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(), + std::forward<Tuple>(t), std::forward<F>(f)); } -template <class T> -FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes( - T const&) { - return {}; +template <typename Tuple1, typename Tuple2, typename F, size_t... Is> +void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) { + using std::get; + const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...}; + ignore_unused(unused); } -template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) { - const auto indexes = get_indexes(tup); - for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); +template <typename Tuple1, typename Tuple2, typename F> +void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) { + for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(), + std::forward<Tuple1>(t1), std::forward<Tuple2>(t2), + std::forward<F>(f)); } +namespace tuple { +// Workaround a bug in MSVC 2019 (v140). +template <typename Char, typename... T> +using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>; + +using std::get; +template <typename Tuple, typename Char, std::size_t... Is> +auto get_formatters(index_sequence<Is...>) + -> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>; +} // namespace tuple + #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 // Older MSVC doesn't get the reference type correctly for arrays. template <typename R> struct range_reference_type_impl { @@ -269,45 +288,37 @@ using range_reference_type = template <typename Range> using uncvref_type = remove_cvref_t<range_reference_type<Range>>; -template <typename Range> -using uncvref_first_type = remove_cvref_t< - decltype(std::declval<range_reference_type<Range>>().first)>; - -template <typename Range> -using uncvref_second_type = remove_cvref_t< - decltype(std::declval<range_reference_type<Range>>().second)>; - -template <typename OutputIt> OutputIt write_delimiter(OutputIt out) { - *out++ = ','; - *out++ = ' '; - return out; -} - -template <typename Char, typename OutputIt> -auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt { - return write_escaped_string(out, str); -} - -template <typename Char, typename OutputIt, typename T, - FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)> -inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt { - auto sv = std_string_view<Char>(str); - return write_range_entry<Char>(out, basic_string_view<Char>(sv)); -} - -template <typename Char, typename OutputIt, typename Arg, - FMT_ENABLE_IF(std::is_same<Arg, Char>::value)> -OutputIt write_range_entry(OutputIt out, const Arg v) { - return write_escaped_char(out, v); +template <typename Formatter> +FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) + -> decltype(f.set_debug_format(set)) { + f.set_debug_format(set); } +template <typename Formatter> +FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} + +// These are not generic lambdas for compatibility with C++11. +template <typename ParseContext> struct parse_empty_specs { + template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) { + f.parse(ctx); + detail::maybe_set_debug_format(f, true); + } + ParseContext& ctx; +}; +template <typename FormatContext> struct format_tuple_element { + using char_type = typename FormatContext::char_type; + + template <typename T> + void operator()(const formatter<T, char_type>& f, const T& v) { + if (i > 0) + ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out())); + ctx.advance_to(f.format(v, ctx)); + ++i; + } -template < - typename Char, typename OutputIt, typename Arg, - FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value && - !std::is_same<Arg, Char>::value)> -OutputIt write_range_entry(OutputIt out, const Arg& v) { - return write<Char>(out, v); -} + int i; + FormatContext& ctx; + basic_string_view<char_type> separator; +}; } // namespace detail @@ -321,45 +332,58 @@ template <typename T, typename C> struct is_tuple_formattable { detail::is_tuple_formattable_<T, C>::value; }; -template <typename TupleT, typename Char> -struct formatter<TupleT, Char, - enable_if_t<fmt::is_tuple_like<TupleT>::value && - fmt::is_tuple_formattable<TupleT, Char>::value>> { +template <typename Tuple, typename Char> +struct formatter<Tuple, Char, + enable_if_t<fmt::is_tuple_like<Tuple>::value && + fmt::is_tuple_formattable<Tuple, Char>::value>> { private: - // C++11 generic lambda for format(). - template <typename FormatContext> struct format_each { - template <typename T> void operator()(const T& v) { - if (i > 0) out = detail::write_delimiter(out); - out = detail::write_range_entry<Char>(out, v); - ++i; - } - int i; - typename FormatContext::iterator& out; - }; + decltype(detail::tuple::get_formatters<Tuple, Char>( + detail::tuple_index_sequence<Tuple>())) formatters_; + + basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{}; + basic_string_view<Char> opening_bracket_ = + detail::string_literal<Char, '('>{}; + basic_string_view<Char> closing_bracket_ = + detail::string_literal<Char, ')'>{}; public: + FMT_CONSTEXPR formatter() {} + + FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open, + basic_string_view<Char> close) { + opening_bracket_ = open; + closing_bracket_ = close; + } + template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + auto it = ctx.begin(); + if (it != ctx.end() && *it != '}') + FMT_THROW(format_error("invalid format specifier")); + detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx}); + return it; } - template <typename FormatContext = format_context> - auto format(const TupleT& values, FormatContext& ctx) const + template <typename FormatContext> + auto format(const Tuple& value, FormatContext& ctx) const -> decltype(ctx.out()) { - auto out = ctx.out(); - *out++ = '('; - detail::for_each(values, format_each<FormatContext>{0, out}); - *out++ = ')'; - return out; + ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out())); + detail::for_each2( + formatters_, value, + detail::format_tuple_element<FormatContext>{0, ctx, separator_}); + return detail::copy_str<Char>(closing_bracket_, ctx.out()); } }; template <typename T, typename Char> struct is_range { static constexpr const bool value = detail::is_range_<T>::value && !detail::is_std_string_like<T>::value && - !detail::is_map<T>::value && !std::is_convertible<T, std::basic_string<Char>>::value && - !std::is_constructible<detail::std_string_view<Char>, T>::value; + !std::is_convertible<T, detail::std_string_view<Char>>::value; }; namespace detail { @@ -380,127 +404,172 @@ template <typename Context> struct range_mapper { }; template <typename Char, typename Element> -using range_formatter_type = conditional_t< - is_formattable<Element, Char>::value, +using range_formatter_type = formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map( std::declval<Element>()))>, - Char>, - fallback_formatter<Element, Char>>; + Char>; template <typename R> using maybe_const_range = conditional_t<has_const_begin_end<R>::value, const R, R>; -} // namespace detail +// Workaround a bug in MSVC 2015 and earlier. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 template <typename R, typename Char> -struct formatter< - R, Char, - enable_if_t< - conjunction<fmt::is_range<R, Char> -// Workaround a bug in MSVC 2017 and earlier. -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 - , - disjunction< - is_formattable<detail::uncvref_type<detail::maybe_const_range<R>>, - Char>, - detail::has_fallback_formatter< - detail::uncvref_type<detail::maybe_const_range<R>>, Char> - > +struct is_formattable_delayed + : is_formattable<uncvref_type<maybe_const_range<R>>, Char> {}; #endif - >::value - >> { +} // namespace detail - using range_type = detail::maybe_const_range<R>; - using formatter_type = - detail::range_formatter_type<Char, detail::uncvref_type<range_type>>; - formatter_type underlying_; - bool custom_specs_ = false; +template <typename T, typename Char, typename Enable = void> +struct range_formatter; + +template <typename T, typename Char> +struct range_formatter< + T, Char, + enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>, + is_formattable<T, Char>>::value>> { + private: + detail::range_formatter_type<Char, T> underlying_; + basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{}; + basic_string_view<Char> opening_bracket_ = + detail::string_literal<Char, '['>{}; + basic_string_view<Char> closing_bracket_ = + detail::string_literal<Char, ']'>{}; + + public: + FMT_CONSTEXPR range_formatter() {} + + FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& { + return underlying_; + } + + FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open, + basic_string_view<Char> close) { + opening_bracket_ = open; + closing_bracket_ = close; + } template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { auto it = ctx.begin(); auto end = ctx.end(); - if (it == end || *it == '}') return it; - if (*it != ':') - FMT_THROW(format_error("no top-level range formatters supported")); + if (it != end && *it == 'n') { + set_brackets({}, {}); + ++it; + } + + if (it != end && *it != '}') { + if (*it != ':') FMT_THROW(format_error("invalid format specifier")); + ++it; + } else { + detail::maybe_set_debug_format(underlying_, true); + } - custom_specs_ = true; - ++it; ctx.advance_to(it); return underlying_.parse(ctx); } - template <typename FormatContext> - auto format(range_type& range, FormatContext& ctx) const - -> decltype(ctx.out()) { - Char prefix = detail::is_set<R>::value ? '{' : '['; - Char postfix = detail::is_set<R>::value ? '}' : ']'; + template <typename R, typename FormatContext> + auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { detail::range_mapper<buffer_context<Char>> mapper; auto out = ctx.out(); - *out++ = prefix; + out = detail::copy_str<Char>(opening_bracket_, out); int i = 0; auto it = detail::range_begin(range); auto end = detail::range_end(range); for (; it != end; ++it) { - if (i > 0) out = detail::write_delimiter(out); - if (custom_specs_) { - ctx.advance_to(out); - out = underlying_.format(mapper.map(*it), ctx); - } else { - out = detail::write_range_entry<Char>(out, *it); - } + if (i > 0) out = detail::copy_str<Char>(separator_, out); + ctx.advance_to(out); + out = underlying_.format(mapper.map(*it), ctx); ++i; } - *out++ = postfix; + out = detail::copy_str<Char>(closing_bracket_, out); return out; } }; -template <typename T, typename Char> -struct formatter< - T, Char, - enable_if_t<conjunction<detail::is_map<T> -// Workaround a bug in MSVC 2017 and earlier. -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 - , - disjunction< - is_formattable<detail::uncvref_first_type<T>, Char>, - detail::has_fallback_formatter<detail::uncvref_first_type<T>, Char> - >, - disjunction< - is_formattable<detail::uncvref_second_type<T>, Char>, - detail::has_fallback_formatter<detail::uncvref_second_type<T>, Char> - > -#endif - >::value - >> { +enum class range_format { disabled, map, set, sequence, string, debug_string }; + +namespace detail { +template <typename T> +struct range_format_kind_ + : std::integral_constant<range_format, + std::is_same<uncvref_type<T>, T>::value + ? range_format::disabled + : is_map<T>::value ? range_format::map + : is_set<T>::value ? range_format::set + : range_format::sequence> {}; + +template <range_format K, typename R, typename Char, typename Enable = void> +struct range_default_formatter; + +template <range_format K> +using range_format_constant = std::integral_constant<range_format, K>; + +template <range_format K, typename R, typename Char> +struct range_default_formatter< + K, R, Char, + enable_if_t<(K == range_format::sequence || K == range_format::map || + K == range_format::set)>> { + using range_type = detail::maybe_const_range<R>; + range_formatter<detail::uncvref_type<range_type>, Char> underlying_; + + FMT_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); } + + FMT_CONSTEXPR void init(range_format_constant<range_format::set>) { + underlying_.set_brackets(detail::string_literal<Char, '{'>{}, + detail::string_literal<Char, '}'>{}); + } + + FMT_CONSTEXPR void init(range_format_constant<range_format::map>) { + underlying_.set_brackets(detail::string_literal<Char, '{'>{}, + detail::string_literal<Char, '}'>{}); + underlying_.underlying().set_brackets({}, {}); + underlying_.underlying().set_separator( + detail::string_literal<Char, ':', ' '>{}); + } + + FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) {} + template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + return underlying_.parse(ctx); } - template < - typename FormatContext, typename U, - FMT_ENABLE_IF( - std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value, - const T, T>>::value)> - auto format(U& map, FormatContext& ctx) const -> decltype(ctx.out()) { - auto out = ctx.out(); - *out++ = '{'; - int i = 0; - for (const auto& item : map) { - if (i > 0) out = detail::write_delimiter(out); - out = detail::write_range_entry<Char>(out, item.first); - *out++ = ':'; - *out++ = ' '; - out = detail::write_range_entry<Char>(out, item.second); - ++i; - } - *out++ = '}'; - return out; + template <typename FormatContext> + auto format(range_type& range, FormatContext& ctx) const + -> decltype(ctx.out()) { + return underlying_.format(range, ctx); } }; +} // namespace detail + +template <typename T, typename Char, typename Enable = void> +struct range_format_kind + : conditional_t< + is_range<T, Char>::value, detail::range_format_kind_<T>, + std::integral_constant<range_format, range_format::disabled>> {}; + +template <typename R, typename Char> +struct formatter< + R, Char, + enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value != + range_format::disabled> +// Workaround a bug in MSVC 2015 and earlier. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 + , + detail::is_formattable_delayed<R, Char> +#endif + >::value>> + : detail::range_default_formatter<range_format_kind<R, Char>::value, R, + Char> { +}; template <typename Char, typename... T> struct tuple_join_view : detail::view { const std::tuple<T...>& tuple; @@ -510,9 +579,6 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view { : tuple(t), sep{s} {} }; -template <typename Char, typename... T> -using tuple_arg_join = tuple_join_view<Char, T...>; - // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers // support in tuple_join. It is disabled by default because of issues with // the dynamic width and precision. @@ -582,7 +648,45 @@ struct formatter<tuple_join_view<Char, T...>, Char> { } }; -FMT_MODULE_EXPORT_BEGIN +namespace detail { +// Check if T has an interface like a container adaptor (e.g. std::stack, +// std::queue, std::priority_queue). +template <typename T> class is_container_adaptor_like { + template <typename U> static auto check(U* p) -> typename U::container_type; + template <typename> static void check(...); + + public: + static constexpr const bool value = + !std::is_void<decltype(check<T>(nullptr))>::value; +}; + +template <typename Container> struct all { + const Container& c; + auto begin() const -> typename Container::const_iterator { return c.begin(); } + auto end() const -> typename Container::const_iterator { return c.end(); } +}; +} // namespace detail + +template <typename T, typename Char> +struct formatter< + T, Char, + enable_if_t<conjunction<detail::is_container_adaptor_like<T>, + bool_constant<range_format_kind<T, Char>::value == + range_format::disabled>>::value>> + : formatter<detail::all<typename T::container_type>, Char> { + using all = detail::all<typename T::container_type>; + template <typename FormatContext> + auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { + struct getter : T { + static auto get(const T& t) -> all { + return {t.*(&getter::c)}; // Access c through the derived class. + } + }; + return formatter<all>::format(getter::get(t), ctx); + } +}; + +FMT_BEGIN_EXPORT /** \rst @@ -625,7 +729,7 @@ auto join(std::initializer_list<T> list, string_view sep) return join(std::begin(list), std::end(list), sep); } -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_RANGES_H_ diff --git a/externals/fmt/include/fmt/std.h b/externals/fmt/include/fmt/std.h index 227f4841..b4e055c2 100644 --- a/externals/fmt/include/fmt/std.h +++ b/externals/fmt/include/fmt/std.h @@ -8,10 +8,18 @@ #ifndef FMT_STD_H_ #define FMT_STD_H_ +#include <atomic> +#include <bitset> +#include <cstdlib> +#include <exception> +#include <memory> #include <thread> #include <type_traits> +#include <typeinfo> #include <utility> +#include <vector> +#include "format.h" #include "ostream.h" #if FMT_HAS_INCLUDE(<version>) @@ -25,6 +33,30 @@ # if FMT_HAS_INCLUDE(<variant>) # include <variant> # endif +# if FMT_HAS_INCLUDE(<optional>) +# include <optional> +# endif +#endif + +// GCC 4 does not support FMT_HAS_INCLUDE. +#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__) +# include <cxxabi.h> +// Android NDK with gabi++ library on some architectures does not implement +// abi::__cxa_demangle(). +# ifndef __GABIXX_CXXABI_H__ +# define FMT_HAS_ABI_CXA_DEMANGLE +# endif +#endif + +// Check if typeid is available. +#ifndef FMT_USE_TYPEID +// __RTTI is for EDG compilers. In MSVC typeid is available without RTTI. +# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || FMT_MSC_VERSION || \ + defined(__INTEL_RTTI__) || defined(__RTTI) +# define FMT_USE_TYPEID 1 +# else +# define FMT_USE_TYPEID 0 +# endif #endif #ifdef __cpp_lib_filesystem @@ -32,21 +64,32 @@ FMT_BEGIN_NAMESPACE namespace detail { +template <typename Char> auto get_path_string(const std::filesystem::path& p) { + return p.string<Char>(); +} + template <typename Char> void write_escaped_path(basic_memory_buffer<Char>& quoted, const std::filesystem::path& p) { write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>()); } + # ifdef _WIN32 template <> -inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted, +inline auto get_path_string<char>(const std::filesystem::path& p) { + return to_utf8<wchar_t>(p.native(), to_utf8_error_policy::replace); +} + +template <> +inline void write_escaped_path<char>(memory_buffer& quoted, const std::filesystem::path& p) { - auto s = p.u8string(); - write_escaped_string<char>( - std::back_inserter(quoted), - string_view(reinterpret_cast<const char*>(s.c_str()), s.size())); + auto buf = basic_memory_buffer<wchar_t>(); + write_escaped_string<wchar_t>(std::back_inserter(buf), p.native()); + bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()}); + FMT_ASSERT(valid, "invalid utf16"); } -# endif +# endif // _WIN32 + template <> inline void write_escaped_path<std::filesystem::path::value_type>( basic_memory_buffer<std::filesystem::path::value_type>& quoted, @@ -57,67 +100,118 @@ inline void write_escaped_path<std::filesystem::path::value_type>( } // namespace detail -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 -// For MSVC 2017 and earlier using the partial specialization -// would cause an ambiguity error, therefore we provide it only -// conditionally. -template <typename Char> -struct formatter<std::filesystem::path, Char> - : formatter<basic_string_view<Char>> { +FMT_EXPORT +template <typename Char> struct formatter<std::filesystem::path, Char> { + private: + format_specs<Char> specs_; + detail::arg_ref<Char> width_ref_; + bool debug_ = false; + + public: + FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } + + template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it != end && *it == '?') { + debug_ = true; + ++it; + } + return it; + } + template <typename FormatContext> - auto format(const std::filesystem::path& p, FormatContext& ctx) const -> - typename FormatContext::iterator { - basic_memory_buffer<Char> quoted; + auto format(const std::filesystem::path& p, FormatContext& ctx) const { + auto specs = specs_; + detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_, + ctx); + if (!debug_) { + auto s = detail::get_path_string<Char>(p); + return detail::write(ctx.out(), basic_string_view<Char>(s), specs); + } + auto quoted = basic_memory_buffer<Char>(); detail::write_escaped_path(quoted, p); - return formatter<basic_string_view<Char>>::format( - basic_string_view<Char>(quoted.data(), quoted.size()), ctx); + return detail::write(ctx.out(), + basic_string_view<Char>(quoted.data(), quoted.size()), + specs); } }; -#endif FMT_END_NAMESPACE #endif FMT_BEGIN_NAMESPACE +FMT_EXPORT template <typename Char> struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {}; FMT_END_NAMESPACE -#ifdef __cpp_lib_variant +#ifdef __cpp_lib_optional FMT_BEGIN_NAMESPACE -template <typename Char> struct formatter<std::monostate, Char> { - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); +FMT_EXPORT +template <typename T, typename Char> +struct formatter<std::optional<T>, Char, + std::enable_if_t<is_formattable<T, Char>::value>> { + private: + formatter<T, Char> underlying_; + static constexpr basic_string_view<Char> optional = + detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l', + '('>{}; + static constexpr basic_string_view<Char> none = + detail::string_literal<Char, 'n', 'o', 'n', 'e'>{}; + + template <class U> + FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set) + -> decltype(u.set_debug_format(set)) { + u.set_debug_format(set); + } + + template <class U> + FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} + + public: + template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { + maybe_set_debug_format(underlying_, true); + return underlying_.parse(ctx); } template <typename FormatContext> - auto format(const std::monostate&, FormatContext& ctx) const + auto format(std::optional<T> const& opt, FormatContext& ctx) const -> decltype(ctx.out()) { + if (!opt) return detail::write<Char>(ctx.out(), none); + auto out = ctx.out(); - out = detail::write<Char>(out, "monostate"); - return out; + out = detail::write<Char>(out, optional); + ctx.advance_to(out); + out = underlying_.format(*opt, ctx); + return detail::write(out, ')'); } }; +FMT_END_NAMESPACE +#endif // __cpp_lib_optional +#ifdef __cpp_lib_variant +FMT_BEGIN_NAMESPACE namespace detail { template <typename T> using variant_index_sequence = std::make_index_sequence<std::variant_size<T>::value>; -// variant_size and variant_alternative check. -template <typename T, typename U = void> -struct is_variant_like_ : std::false_type {}; -template <typename T> -struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>> - : std::true_type {}; +template <typename> struct is_variant_like_ : std::false_type {}; +template <typename... Types> +struct is_variant_like_<std::variant<Types...>> : std::true_type {}; -// formattable element check +// formattable element check. template <typename T, typename C> class is_variant_formattable_ { - template <std::size_t... I> + template <std::size_t... Is> static std::conjunction< - is_formattable<std::variant_alternative_t<I, T>, C>...> - check(std::index_sequence<I...>); + is_formattable<std::variant_alternative_t<Is, T>, C>...> + check(std::index_sequence<Is...>); public: static constexpr const bool value = @@ -145,6 +239,21 @@ template <typename T, typename C> struct is_variant_formattable { detail::is_variant_formattable_<T, C>::value; }; +FMT_EXPORT +template <typename Char> struct formatter<std::monostate, Char> { + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename FormatContext> + auto format(const std::monostate&, FormatContext& ctx) const + -> decltype(ctx.out()) { + return detail::write<Char>(ctx.out(), "monostate"); + } +}; + +FMT_EXPORT template <typename Variant, typename Char> struct formatter< Variant, Char, @@ -161,16 +270,196 @@ struct formatter< auto out = ctx.out(); out = detail::write<Char>(out, "variant("); - std::visit( - [&](const auto& v) { - out = detail::write_variant_alternative<Char>(out, v); - }, - value); + FMT_TRY { + std::visit( + [&](const auto& v) { + out = detail::write_variant_alternative<Char>(out, v); + }, + value); + } + FMT_CATCH(const std::bad_variant_access&) { + detail::write<Char>(out, "valueless by exception"); + } *out++ = ')'; return out; } }; FMT_END_NAMESPACE +#endif // __cpp_lib_variant + +FMT_BEGIN_NAMESPACE +FMT_EXPORT +template <typename Char> struct formatter<std::error_code, Char> { + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template <typename FormatContext> + FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + out = detail::write_bytes(out, ec.category().name(), format_specs<Char>()); + out = detail::write<Char>(out, Char(':')); + out = detail::write<Char>(out, ec.value()); + return out; + } +}; + +FMT_EXPORT +template <typename T, typename Char> +struct formatter< + T, Char, + typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> { + private: + bool with_typename_ = false; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it == end || *it == '}') return it; + if (*it == 't') { + ++it; + with_typename_ = FMT_USE_TYPEID != 0; + } + return it; + } + + template <typename OutputIt> + auto format(const std::exception& ex, + basic_format_context<OutputIt, Char>& ctx) const -> OutputIt { + format_specs<Char> spec; + auto out = ctx.out(); + if (!with_typename_) + return detail::write_bytes(out, string_view(ex.what()), spec); + +#if FMT_USE_TYPEID + const std::type_info& ti = typeid(ex); +# ifdef FMT_HAS_ABI_CXA_DEMANGLE + int status = 0; + std::size_t size = 0; + std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr( + abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free); + + string_view demangled_name_view; + if (demangled_name_ptr) { + demangled_name_view = demangled_name_ptr.get(); + + // Normalization of stdlib inline namespace names. + // libc++ inline namespaces. + // std::__1::* -> std::* + // std::__1::__fs::* -> std::* + // libstdc++ inline namespaces. + // std::__cxx11::* -> std::* + // std::filesystem::__cxx11::* -> std::filesystem::* + if (demangled_name_view.starts_with("std::")) { + char* begin = demangled_name_ptr.get(); + char* to = begin + 5; // std:: + for (char *from = to, *end = begin + demangled_name_view.size(); + from < end;) { + // This is safe, because demangled_name is NUL-terminated. + if (from[0] == '_' && from[1] == '_') { + char* next = from + 1; + while (next < end && *next != ':') next++; + if (next[0] == ':' && next[1] == ':') { + from = next + 2; + continue; + } + } + *to++ = *from++; + } + demangled_name_view = {begin, detail::to_unsigned(to - begin)}; + } + } else { + demangled_name_view = string_view(ti.name()); + } + out = detail::write_bytes(out, demangled_name_view, spec); +# elif FMT_MSC_VERSION + string_view demangled_name_view(ti.name()); + if (demangled_name_view.starts_with("class ")) + demangled_name_view.remove_prefix(6); + else if (demangled_name_view.starts_with("struct ")) + demangled_name_view.remove_prefix(7); + out = detail::write_bytes(out, demangled_name_view, spec); +# else + out = detail::write_bytes(out, string_view(ti.name()), spec); +# endif + *out++ = ':'; + *out++ = ' '; + return detail::write_bytes(out, string_view(ex.what()), spec); +#endif + } +}; + +namespace detail { + +template <typename T, typename Enable = void> +struct has_flip : std::false_type {}; + +template <typename T> +struct has_flip<T, void_t<decltype(std::declval<T>().flip())>> + : std::true_type {}; + +template <typename T> struct is_bit_reference_like { + static constexpr const bool value = + std::is_convertible<T, bool>::value && + std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value; +}; + +#ifdef _LIBCPP_VERSION + +// Workaround for libc++ incompatibility with C++ standard. +// According to the Standard, `bitset::operator[] const` returns bool. +template <typename C> +struct is_bit_reference_like<std::__bit_const_reference<C>> { + static constexpr const bool value = true; +}; + #endif +} // namespace detail + +// We can't use std::vector<bool, Allocator>::reference and +// std::bitset<N>::reference because the compiler can't deduce Allocator and N +// in partial specialization. +FMT_EXPORT +template <typename BitRef, typename Char> +struct formatter<BitRef, Char, + enable_if_t<detail::is_bit_reference_like<BitRef>::value>> + : formatter<bool, Char> { + template <typename FormatContext> + FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter<bool, Char>::format(v, ctx); + } +}; + +FMT_EXPORT +template <typename T, typename Char> +struct formatter<std::atomic<T>, Char, + enable_if_t<is_formattable<T, Char>::value>> + : formatter<T, Char> { + template <typename FormatContext> + auto format(const std::atomic<T>& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter<T, Char>::format(v.load(), ctx); + } +}; + +#ifdef __cpp_lib_atomic_flag_test +FMT_EXPORT +template <typename Char> +struct formatter<std::atomic_flag, Char> + : formatter<bool, Char> { + template <typename FormatContext> + auto format(const std::atomic_flag& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter<bool, Char>::format(v.test(), ctx); + } +}; +#endif // __cpp_lib_atomic_flag_test + +FMT_END_NAMESPACE #endif // FMT_STD_H_ diff --git a/externals/fmt/include/fmt/xchar.h b/externals/fmt/include/fmt/xchar.h index 2865b76e..625ec369 100644 --- a/externals/fmt/include/fmt/xchar.h +++ b/externals/fmt/include/fmt/xchar.h @@ -9,17 +9,35 @@ #define FMT_XCHAR_H_ #include <cwchar> -#include <tuple> #include "format.h" +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +# include <locale> +#endif + FMT_BEGIN_NAMESPACE namespace detail { + template <typename T> using is_exotic_char = bool_constant<!std::is_same<T, char>::value>; + +inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out, + loc_value value, const format_specs<wchar_t>& specs, + locale_ref loc) -> bool { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + auto& numpunct = + std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>()); + auto separator = std::wstring(); + auto grouping = numpunct.grouping(); + if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); + return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}}); +#endif + return false; } +} // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT using wstring_view = basic_string_view<wchar_t>; using wformat_parse_context = basic_format_parse_context<wchar_t>; @@ -30,9 +48,13 @@ using wmemory_buffer = basic_memory_buffer<wchar_t>; #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 // Workaround broken conversion on older gcc. template <typename... Args> using wformat_string = wstring_view; +inline auto runtime(wstring_view s) -> wstring_view { return s; } #else template <typename... Args> using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>; +inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> { + return {{s}}; +} #endif template <> struct is_char<wchar_t> : std::true_type {}; @@ -40,9 +62,9 @@ template <> struct is_char<detail::char8_type> : std::true_type {}; template <> struct is_char<char16_t> : std::true_type {}; template <> struct is_char<char32_t> : std::true_type {}; -template <typename... Args> -constexpr format_arg_store<wformat_context, Args...> make_wformat_args( - const Args&... args) { +template <typename... T> +constexpr format_arg_store<wformat_context, T...> make_wformat_args( + const T&... args) { return {args...}; } @@ -77,26 +99,22 @@ template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> auto vformat(basic_string_view<Char> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) -> std::basic_string<Char> { - basic_memory_buffer<Char> buffer; - detail::vformat_to(buffer, format_str, args); - return to_string(buffer); + auto buf = basic_memory_buffer<Char>(); + detail::vformat_to(buf, format_str, args); + return to_string(buf); } -#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409 -template <typename... Args> -using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>; -#endif - template <typename... T> auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring { - return vformat(fmt, fmt::make_wformat_args(args...)); + return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); } // Pass char_t as a default template parameter instead of using // std::basic_string<char_t<S>> to reduce the symbol size. -template <typename S, typename... Args, typename Char = char_t<S>, - FMT_ENABLE_IF(!std::is_same<Char, char>::value)> -auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> { +template <typename S, typename... T, typename Char = char_t<S>, + FMT_ENABLE_IF(!std::is_same<Char, char>::value && + !std::is_same<Char, wchar_t>::value)> +auto format(const S& format_str, T&&... args) -> std::basic_string<Char> { return vformat(detail::to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); } @@ -111,11 +129,10 @@ inline auto vformat( return detail::vformat(loc, detail::to_string_view(format_str), args); } -template <typename Locale, typename S, typename... Args, - typename Char = char_t<S>, +template <typename Locale, typename S, typename... T, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value)> -inline auto format(const Locale& loc, const S& format_str, Args&&... args) +inline auto format(const Locale& loc, const S& format_str, T&&... args) -> std::basic_string<Char> { return detail::vformat(loc, detail::to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); @@ -129,14 +146,14 @@ auto vformat_to(OutputIt out, const S& format_str, -> OutputIt { auto&& buf = detail::get_buffer<Char>(out); detail::vformat_to(buf, detail::to_string_view(format_str), args); - return detail::get_iterator(buf); + return detail::get_iterator(buf, out); } -template <typename OutputIt, typename S, typename... Args, +template <typename OutputIt, typename S, typename... T, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& detail::is_exotic_char<Char>::value)> -inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt { +inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt { return vformat_to(out, detail::to_string_view(fmt), fmt::make_format_args<buffer_context<Char>>(args...)); } @@ -152,18 +169,18 @@ inline auto vformat_to( auto&& buf = detail::get_buffer<Char>(out); vformat_to(buf, detail::to_string_view(format_str), args, detail::locale_ref(loc)); - return detail::get_iterator(buf); + return detail::get_iterator(buf, out); } template < - typename OutputIt, typename Locale, typename S, typename... Args, + 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, - Args&&... args) -> + T&&... args) -> typename std::enable_if<enable, OutputIt>::type { - return vformat_to(out, loc, to_string_view(format_str), + return vformat_to(out, loc, detail::to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); } @@ -174,36 +191,36 @@ inline auto vformat_to_n( OutputIt out, size_t n, basic_string_view<Char> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) -> format_to_n_result<OutputIt> { - detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out, - n); + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n); detail::vformat_to(buf, format_str, args); return {buf.out(), buf.count()}; } -template <typename OutputIt, typename S, typename... Args, +template <typename OutputIt, typename S, typename... T, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& detail::is_exotic_char<Char>::value)> -inline auto format_to_n(OutputIt out, size_t n, const S& fmt, - const Args&... args) -> format_to_n_result<OutputIt> { +inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) + -> format_to_n_result<OutputIt> { return vformat_to_n(out, n, detail::to_string_view(fmt), fmt::make_format_args<buffer_context<Char>>(args...)); } -template <typename S, typename... Args, typename Char = char_t<S>, +template <typename S, typename... T, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)> -inline auto formatted_size(const S& fmt, Args&&... args) -> size_t { - detail::counting_buffer<Char> buf; +inline auto formatted_size(const S& fmt, T&&... args) -> size_t { + auto buf = detail::counting_buffer<Char>(); detail::vformat_to(buf, detail::to_string_view(fmt), fmt::make_format_args<buffer_context<Char>>(args...)); return buf.count(); } inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { - wmemory_buffer buffer; - detail::vformat_to(buffer, fmt, args); - buffer.push_back(L'\0'); - if (std::fputws(buffer.data(), f) == -1) + auto buf = wmemory_buffer(); + detail::vformat_to(buf, fmt, args); + buf.push_back(L'\0'); + if (std::fputws(buf.data(), f) == -1) FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } @@ -220,13 +237,22 @@ template <typename... T> void print(wformat_string<T...> fmt, T&&... args) { return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); } +template <typename... T> +void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) { + return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...)); +} + +template <typename... T> void println(wformat_string<T...> fmt, T&&... args) { + return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...)); +} + /** Converts *value* to ``std::wstring`` using the default format for type *T*. */ template <typename T> inline auto to_wstring(const T& value) -> std::wstring { return format(FMT_STRING(L"{}"), value); } -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_XCHAR_H_ diff --git a/externals/fmt/src/fmt.cc b/externals/fmt/src/fmt.cc index 80e77e26..a02167c6 100644 --- a/externals/fmt/src/fmt.cc +++ b/externals/fmt/src/fmt.cc @@ -1,49 +1,44 @@ module; -#ifndef __cpp_modules -# error Module not supported. -#endif - -// put all implementation-provided headers into the global module fragment -// to prevent attachment to this module -#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) -# define _CRT_SECURE_NO_WARNINGS -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32) -# define WIN32_LEAN_AND_MEAN -#endif +// Put all implementation-provided headers into the global module fragment +// to prevent attachment to this module. #include <algorithm> -#include <cctype> #include <cerrno> #include <chrono> #include <climits> -#include <clocale> #include <cmath> -#include <cstdarg> #include <cstddef> #include <cstdint> #include <cstdio> #include <cstdlib> #include <cstring> #include <ctime> -#include <cwchar> #include <exception> +#include <filesystem> +#include <fstream> #include <functional> #include <iterator> #include <limits> #include <locale> #include <memory> +#include <optional> #include <ostream> -#include <sstream> #include <stdexcept> #include <string> #include <string_view> #include <system_error> +#include <thread> #include <type_traits> +#include <typeinfo> #include <utility> +#include <variant> #include <vector> +#include <version> -#if _MSC_VER +#if __has_include(<cxxabi.h>) +# include <cxxabi.h> +#endif +#if defined(_MSC_VER) || defined(__MINGW32__) # include <intrin.h> #endif #if defined __APPLE__ || defined(__FreeBSD__) @@ -65,22 +60,33 @@ module; # endif #endif #ifdef _WIN32 +# if defined(__GLIBCXX__) +# include <ext/stdio_filebuf.h> +# include <ext/stdio_sync_filebuf.h> +# elif defined(_LIBCPP_VERSION) +# include <__std_stream> +# endif +# define WIN32_LEAN_AND_MEAN # include <windows.h> #endif export module fmt; -#define FMT_MODULE_EXPORT export -#define FMT_MODULE_EXPORT_BEGIN export { -#define FMT_MODULE_EXPORT_END } -#define FMT_BEGIN_DETAIL_NAMESPACE \ - } \ - namespace detail { -#define FMT_END_DETAIL_NAMESPACE \ - } \ - export { -// all library-provided declarations and definitions -// must be in the module purview to be exported +#define FMT_EXPORT export +#define FMT_BEGIN_EXPORT export { +#define FMT_END_EXPORT } + +// If you define FMT_ATTACH_TO_GLOBAL_MODULE +// - all declarations are detached from module 'fmt' +// - the module behaves like a traditional static library, too +// - all library symbols are mangled traditionally +// - you can mix TUs with either importing or #including the {fmt} API +#ifdef FMT_ATTACH_TO_GLOBAL_MODULE +extern "C++" { +#endif + +// All library-provided declarations and definitions must be in the module +// purview to be exported. #include "fmt/args.h" #include "fmt/chrono.h" #include "fmt/color.h" @@ -88,8 +94,13 @@ export module fmt; #include "fmt/format.h" #include "fmt/os.h" #include "fmt/printf.h" +#include "fmt/std.h" #include "fmt/xchar.h" +#ifdef FMT_ATTACH_TO_GLOBAL_MODULE +} +#endif + // gcc doesn't yet implement private module fragments #if !FMT_GCC_VERSION module : private; diff --git a/externals/fmt/src/format.cc b/externals/fmt/src/format.cc index 99b7e9dd..391d3a24 100644 --- a/externals/fmt/src/format.cc +++ b/externals/fmt/src/format.cc @@ -28,12 +28,8 @@ template FMT_API auto decimal_point_impl(locale_ref) -> char; template FMT_API void buffer<char>::append(const char*, const char*); -// DEPRECATED! -// There is no correspondent extern template in format.h because of -// incompatibility between clang and gcc (#2377). template FMT_API void vformat_to(buffer<char>&, string_view, - basic_format_args<FMT_BUFFER_CONTEXT(char)>, - locale_ref); + typename vformat_args<>::type, locale_ref); // Explicit instantiations for wchar_t. diff --git a/externals/fmt/src/os.cc b/externals/fmt/src/os.cc index f388ead0..bca410e9 100644 --- a/externals/fmt/src/os.cc +++ b/externals/fmt/src/os.cc @@ -18,6 +18,10 @@ # include <sys/stat.h> # include <sys/types.h> +# ifdef _WRS_KERNEL // VxWorks7 kernel +# include <ioLib.h> // getpagesize +# endif + # ifndef _WIN32 # include <unistd.h> # else @@ -72,34 +76,6 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; } FMT_BEGIN_NAMESPACE #ifdef _WIN32 -detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) { - if (int error_code = convert(s)) { - FMT_THROW(windows_error(error_code, - "cannot convert string from UTF-16 to UTF-8")); - } -} - -int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) { - if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER; - int s_size = static_cast<int>(s.size()); - if (s_size == 0) { - // WideCharToMultiByte does not support zero length, handle separately. - buffer_.resize(1); - buffer_[0] = 0; - return 0; - } - - int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0, - nullptr, nullptr); - if (length == 0) return GetLastError(); - buffer_.resize(length + 1); - length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0], - length, nullptr, nullptr); - if (length == 0) return GetLastError(); - buffer_[length] = 0; - return 0; -} - namespace detail { class system_message { @@ -138,10 +114,10 @@ class utf8_system_category final : public std::error_category { public: const char* name() const noexcept override { return "system"; } std::string message(int error_code) const override { - system_message msg(error_code); + auto&& msg = system_message(error_code); if (msg) { - utf16_to_utf8 utf8_message; - if (utf8_message.convert(msg) == ERROR_SUCCESS) { + auto utf8_message = to_utf8<wchar_t>(); + if (utf8_message.convert(msg)) { return utf8_message.str(); } } @@ -165,11 +141,12 @@ std::system_error vwindows_error(int err_code, string_view format_str, void detail::format_windows_error(detail::buffer<char>& out, int error_code, const char* message) noexcept { FMT_TRY { - system_message msg(error_code); + auto&& msg = system_message(error_code); if (msg) { - utf16_to_utf8 utf8_message; - if (utf8_message.convert(msg) == ERROR_SUCCESS) { - fmt::format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message); + auto utf8_message = to_utf8<wchar_t>(); + if (utf8_message.convert(msg)) { + fmt::format_to(appender(out), FMT_STRING("{}: {}"), message, + string_view(utf8_message)); return; } } @@ -192,37 +169,47 @@ buffered_file::buffered_file(cstring_view filename, cstring_view mode) { FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), nullptr); if (!file_) - FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str())); + FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"), + filename.c_str())); } void buffered_file::close() { if (!file_) return; int result = FMT_SYSTEM(fclose(file_)); file_ = nullptr; - if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); + if (result != 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); } 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 int fd = FMT_POSIX_CALL(fileno(file_)); - if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor")); +#endif + if (fd == -1) + FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor"))); return fd; } #if FMT_USE_FCNTL -file::file(cstring_view path, int oflag) { # ifdef _WIN32 - using mode_t = int; +using mode_t = int; # endif - constexpr mode_t mode = - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; +constexpr mode_t default_open_mode = + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + +file::file(cstring_view path, int oflag) { # if defined(_WIN32) && !defined(__MINGW32__) fd_ = -1; - FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); + auto converted = detail::utf8_to_utf16(string_view(path.c_str())); + *this = file::open_windows_file(converted.c_str(), oflag); # else - FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); -# endif + FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode))); if (fd_ == -1) - FMT_THROW(system_error(errno, "cannot open file {}", path.c_str())); + FMT_THROW( + system_error(errno, FMT_STRING("cannot open file {}"), path.c_str())); +# endif } file::~file() noexcept { @@ -238,7 +225,8 @@ void file::close() { // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html int result = FMT_POSIX_CALL(close(fd_)); fd_ = -1; - if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); + if (result != 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); } long long file::size() const { @@ -260,7 +248,7 @@ long long file::size() const { using Stat = struct stat; Stat file_stat = Stat(); if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) - FMT_THROW(system_error(errno, "cannot get file attributes")); + FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes"))); static_assert(sizeof(long long) >= sizeof(file_stat.st_size), "return type of file::size is not large enough"); return file_stat.st_size; @@ -270,14 +258,16 @@ long long file::size() const { std::size_t file::read(void* buffer, std::size_t count) { rwresult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); - if (result < 0) FMT_THROW(system_error(errno, "cannot read from file")); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot read from file"))); return detail::to_unsigned(result); } std::size_t file::write(const void* buffer, std::size_t count) { rwresult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); - if (result < 0) FMT_THROW(system_error(errno, "cannot write to file")); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); return detail::to_unsigned(result); } @@ -286,7 +276,8 @@ file file::dup(int fd) { // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html int new_fd = FMT_POSIX_CALL(dup(fd)); if (new_fd == -1) - FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd)); + FMT_THROW(system_error( + errno, FMT_STRING("cannot duplicate file descriptor {}"), fd)); return file(new_fd); } @@ -294,8 +285,9 @@ void file::dup2(int fd) { int result = 0; FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); if (result == -1) { - FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}", - fd_, fd)); + FMT_THROW(system_error( + errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_, + fd)); } } @@ -320,7 +312,8 @@ void file::pipe(file& read_end, file& write_end) { // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html int result = FMT_POSIX_CALL(pipe(fds)); # endif - if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe")); + if (result != 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe"))); // The following assignments don't throw because read_fd and write_fd // are closed. read_end = file(fds[0]); @@ -334,28 +327,72 @@ buffered_file file::fdopen(const char* mode) { # else FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode)); # endif - if (!f) - FMT_THROW( - system_error(errno, "cannot associate stream with file descriptor")); + if (!f) { + FMT_THROW(system_error( + errno, FMT_STRING("cannot associate stream with file descriptor"))); + } buffered_file bf(f); fd_ = -1; return bf; } +# if defined(_WIN32) && !defined(__MINGW32__) +file file::open_windows_file(wcstring_view path, int oflag) { + int fd = -1; + auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode); + if (fd == -1) { + FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"), + detail::to_utf8<wchar_t>(path.c_str()).c_str())); + } + return file(fd); +} +# endif + +# if !defined(__MSDOS__) long getpagesize() { -# ifdef _WIN32 +# ifdef _WIN32 SYSTEM_INFO si; GetSystemInfo(&si); return si.dwPageSize; -# else +# else +# ifdef _WRS_KERNEL + long size = FMT_POSIX_CALL(getpagesize()); +# else long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); - if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size")); +# endif + + if (size < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size"))); return size; -# endif +# endif } +# endif -FMT_API void ostream::grow(size_t) { +namespace detail { + +void file_buffer::grow(size_t) { if (this->size() == this->capacity()) flush(); } + +file_buffer::file_buffer(cstring_view path, + const detail::ostream_params& params) + : file_(path, params.oflag) { + set(new char[params.buffer_size], params.buffer_size); +} + +file_buffer::file_buffer(file_buffer&& other) + : detail::buffer<char>(other.data(), other.size(), other.capacity()), + file_(std::move(other.file_)) { + other.clear(); + other.set(nullptr, 0); +} + +file_buffer::~file_buffer() { + flush(); + delete[] data(); +} +} // namespace detail + +ostream::~ostream() = default; #endif // FMT_USE_FCNTL FMT_END_NAMESPACE diff --git a/externals/fmt/support/Vagrantfile b/externals/fmt/support/Vagrantfile index f6b5f936..9680a1a1 100644 --- a/externals/fmt/support/Vagrantfile +++ b/externals/fmt/support/Vagrantfile @@ -13,8 +13,8 @@ Vagrant.configure("2") do |config| config.vm.provision "shell", inline: <<-SHELL apt-get update apt-get install -y g++ make wget git - wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz - tar xzf cmake-3.14.4-Linux-x86_64.tar.gz - ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin + wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz + tar xzf cmake-3.26.0-Linux-x86_64.tar.gz + ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin SHELL end diff --git a/externals/fmt/support/appveyor-build.py b/externals/fmt/support/appveyor-build.py deleted file mode 100644 index 65446103..00000000 --- a/externals/fmt/support/appveyor-build.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -# Build the project on AppVeyor. - -import os -from subprocess import check_call - -build = os.environ['BUILD'] -config = os.environ['CONFIGURATION'] -platform = os.environ['PLATFORM'] -path = os.environ['PATH'] -image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE'] -jobid = os.environ['APPVEYOR_JOB_ID'] -cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..'] -if build == 'mingw': - cmake_command.append('-GMinGW Makefiles') - build_command = ['mingw32-make', '-j4'] - test_command = ['mingw32-make', 'test'] - # Remove the path to Git bin directory from $PATH because it breaks - # MinGW config. - path = path.replace(r'C:\Program Files (x86)\Git\bin', '') - os.environ['PATH'] = r'C:\MinGW\bin;' + path -else: - # Add MSBuild 14.0 to PATH as described in - # http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc. - os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path - if image == 'Visual Studio 2019': - generator = 'Visual Studio 16 2019' - if platform == 'x64': - cmake_command.extend(['-A', 'x64']) - else: - if image == 'Visual Studio 2015': - generator = 'Visual Studio 14 2015' - elif image == 'Visual Studio 2017': - generator = 'Visual Studio 15 2017' - if platform == 'x64': - generator += ' Win64' - cmake_command.append('-G' + generator) - build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4'] - test_command = ['ctest', '-C', config] - -check_call(cmake_command) -check_call(build_command) -check_call(test_command) diff --git a/externals/fmt/support/appveyor.yml b/externals/fmt/support/appveyor.yml deleted file mode 100644 index 64ca58a9..00000000 --- a/externals/fmt/support/appveyor.yml +++ /dev/null @@ -1,31 +0,0 @@ -configuration: - - Debug - - Release - -clone_depth: 1 - -image: - - Visual Studio 2015 - -platform: - - x64 - -environment: - CTEST_OUTPUT_ON_FAILURE: 1 - MSVC_DEFAULT_OPTIONS: ON - BUILD: msvc - -before_build: - - mkdir build - - cd build - -build_script: - - python ../support/appveyor-build.py - -on_failure: - - appveyor PushArtifact Testing/Temporary/LastTest.log - - appveyor AddTest test - -# Uncomment this to debug AppVeyor failures. -#on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/externals/fmt/support/bazel/.bazelrc b/externals/fmt/support/bazel/.bazelrc deleted file mode 100644 index 583cbbd2..00000000 --- a/externals/fmt/support/bazel/.bazelrc +++ /dev/null @@ -1 +0,0 @@ -build --symlink_prefix=/ # Out of source build diff --git a/externals/fmt/support/bazel/.bazelversion b/externals/fmt/support/bazel/.bazelversion index ac14c3df..5e325424 100644 --- a/externals/fmt/support/bazel/.bazelversion +++ b/externals/fmt/support/bazel/.bazelversion @@ -1 +1 @@ -5.1.1 +6.1.2 diff --git a/externals/fmt/support/bazel/BUILD.bazel b/externals/fmt/support/bazel/BUILD.bazel index 08576589..29f639be 100644 --- a/externals/fmt/support/bazel/BUILD.bazel +++ b/externals/fmt/support/bazel/BUILD.bazel @@ -11,8 +11,8 @@ cc_library( "include/fmt/color.h", "include/fmt/compile.h", "include/fmt/core.h", - "include/fmt/format-inl.h", "include/fmt/format.h", + "include/fmt/format-inl.h", "include/fmt/os.h", "include/fmt/ostream.h", "include/fmt/printf.h", @@ -21,7 +21,7 @@ cc_library( "include/fmt/xchar.h", ], includes = [ - "include", + "include", ], strip_include_prefix = "include", visibility = ["//visibility:public"], diff --git a/externals/fmt/support/bazel/README.md b/externals/fmt/support/bazel/README.md index 44af620c..4c29febb 100644 --- a/externals/fmt/support/bazel/README.md +++ b/externals/fmt/support/bazel/README.md @@ -1,6 +1,6 @@ # Bazel support -To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}). +To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}). ## Using {fmt} as a dependency @@ -37,7 +37,6 @@ git_repository( branch = "master", remote = "https://github.com/fmtlib/fmt", patch_cmds = [ - "mv support/bazel/.bazelrc .bazelrc", "mv support/bazel/.bazelversion .bazelversion", "mv support/bazel/BUILD.bazel BUILD.bazel", "mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel", @@ -47,7 +46,6 @@ git_repository( # https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes # Even if MSYS2 is installed the Windows related patch commands can still be used. patch_cmds_win = [ - "Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc", "Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion", "Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel", "Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel", @@ -55,7 +53,7 @@ git_repository( ) ``` -In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace. +In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace. *BUILD.bazel*: @@ -71,3 +69,6 @@ The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}. To execute the binary you can run `bazel run //:Demo`. +# Using Bzlmod + +The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) also provides support for {fmt}. diff --git a/externals/fmt/support/build.gradle b/externals/fmt/support/build.gradle index 8d18da6b..c5126d05 100644 --- a/externals/fmt/support/build.gradle +++ b/externals/fmt/support/build.gradle @@ -11,7 +11,7 @@ buildscript { // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // // Notice that 4.0.0 here is the version of [Android Gradle Plugin] - // Accroding to URL above you will need Gradle 6.1 or higher + // According to URL above you will need Gradle 6.1 or higher // classpath "com.android.tools.build:gradle:4.1.1" } diff --git a/externals/fmt/support/cmake/cxx14.cmake b/externals/fmt/support/cmake/cxx14.cmake deleted file mode 100644 index deb1e26f..00000000 --- a/externals/fmt/support/cmake/cxx14.cmake +++ /dev/null @@ -1,54 +0,0 @@ -# C++14 feature support detection - -include(CheckCXXCompilerFlag) -function (fmt_check_cxx_compiler_flag flag result) - if (NOT MSVC) - check_cxx_compiler_flag("${flag}" ${result}) - endif () -endfunction () - -if (NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 11) -endif() -message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}") - -if (CMAKE_CXX_STANDARD EQUAL 20) - fmt_check_cxx_compiler_flag(-std=c++20 has_std_20_flag) - fmt_check_cxx_compiler_flag(-std=c++2a has_std_2a_flag) - - if (has_std_20_flag) - set(CXX_STANDARD_FLAG -std=c++20) - elseif (has_std_2a_flag) - set(CXX_STANDARD_FLAG -std=c++2a) - endif () - -elseif (CMAKE_CXX_STANDARD EQUAL 17) - fmt_check_cxx_compiler_flag(-std=c++17 has_std_17_flag) - fmt_check_cxx_compiler_flag(-std=c++1z has_std_1z_flag) - - if (has_std_17_flag) - set(CXX_STANDARD_FLAG -std=c++17) - elseif (has_std_1z_flag) - set(CXX_STANDARD_FLAG -std=c++1z) - endif () - -elseif (CMAKE_CXX_STANDARD EQUAL 14) - fmt_check_cxx_compiler_flag(-std=c++14 has_std_14_flag) - fmt_check_cxx_compiler_flag(-std=c++1y has_std_1y_flag) - - if (has_std_14_flag) - set(CXX_STANDARD_FLAG -std=c++14) - elseif (has_std_1y_flag) - set(CXX_STANDARD_FLAG -std=c++1y) - endif () - -elseif (CMAKE_CXX_STANDARD EQUAL 11) - fmt_check_cxx_compiler_flag(-std=c++11 has_std_11_flag) - fmt_check_cxx_compiler_flag(-std=c++0x has_std_0x_flag) - - if (has_std_11_flag) - set(CXX_STANDARD_FLAG -std=c++11) - elseif (has_std_0x_flag) - set(CXX_STANDARD_FLAG -std=c++0x) - endif () -endif () diff --git a/externals/fmt/support/manage.py b/externals/fmt/support/manage.py index bece4566..d2afcee9 100755 --- a/externals/fmt/support/manage.py +++ b/externals/fmt/support/manage.py @@ -183,6 +183,12 @@ def update_site(env): with rewrite(index) as b: b.data = b.data.replace( 'doc/latest/index.html#format-string-syntax', 'syntax.html') + # Fix issues in syntax.rst. + index = os.path.join(target_doc_dir, 'syntax.rst') + with rewrite(index) as b: + b.data = b.data.replace( + '..productionlist:: sf\n', '.. productionlist:: sf\n ') + b.data = b.data.replace('Examples:\n', 'Examples::\n') # Build the docs. html_dir = os.path.join(env.build_dir, 'html') if os.path.exists(html_dir): diff --git a/externals/fmt/support/rst2md.py b/externals/fmt/support/rst2md.py index 3f072007..c6e59098 100755 --- a/externals/fmt/support/rst2md.py +++ b/externals/fmt/support/rst2md.py @@ -138,6 +138,13 @@ class Translator(nodes.NodeVisitor): def depart_table(self, node): pass + def visit_system_message(self, node): + pass + + def depart_system_message(self, node): + pass + + class MDWriter(writers.Writer): """GitHub-flavored markdown writer""" diff --git a/externals/fmt/test/CMakeLists.txt b/externals/fmt/test/CMakeLists.txt index 5ac19629..6f614d13 100644 --- a/externals/fmt/test/CMakeLists.txt +++ b/externals/fmt/test/CMakeLists.txt @@ -1,5 +1,7 @@ add_subdirectory(gtest) +include(CheckSymbolExists) + set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) add_library(test-main STATIC ${TEST_MAIN_SRC}) target_include_directories(test-main PUBLIC @@ -8,9 +10,6 @@ target_link_libraries(test-main gtest fmt) function(add_fmt_executable name) add_executable(${name} ${ARGN}) - if (MINGW) - target_link_libraries(${name} -static-libgcc -static-libstdc++) - endif () # (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443 # Bogus -Wstringop-overflow warning @@ -19,6 +18,8 @@ function(add_fmt_executable name) # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353 if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) + target_compile_options(${name} PRIVATE -Wno-stringop-overflow) + # The linker flag is needed for LTO. target_link_libraries(${name} -Wno-stringop-overflow) endif () endfunction() @@ -36,7 +37,7 @@ function(add_fmt_test name) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) endif () elseif (ADD_FMT_TEST_MODULE) - set(libs gtest test-module) + set(libs test-main test-module) set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module) else () set(libs test-main fmt) @@ -54,6 +55,10 @@ function(add_fmt_test name) add_test(NAME ${name} COMMAND ${name}) endfunction() +if (FMT_MODULE) + return () +endif () + add_fmt_test(args-test) add_fmt_test(assert-test) add_fmt_test(chrono-test) @@ -76,7 +81,13 @@ if (MSVC) endif() add_fmt_test(printf-test) add_fmt_test(ranges-test ranges-odr-test.cc) + add_fmt_test(scan-test) +check_symbol_exists(strptime "time.h" HAVE_STRPTIME) +if (HAVE_STRPTIME) + target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME) +endif () + add_fmt_test(std-test) try_compile(compile_result_unused ${CMAKE_CURRENT_BINARY_DIR} @@ -95,13 +106,13 @@ add_fmt_test(enforce-checks-test) target_compile_definitions(enforce-checks-test PRIVATE -DFMT_ENFORCE_COMPILE_STRING) -if (FMT_CAN_MODULE) +if (FMT_MODULE) # The tests need {fmt} to be compiled as traditional library # because of visibility of implementation details. # If module support is present the module tests require a # test-only module to be built from {fmt} add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc) - target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES}) + target_compile_features(test-module PUBLIC cxx_std_11) target_include_directories(test-module PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>) enable_module(test-module) diff --git a/externals/fmt/test/add-subdirectory-test/CMakeLists.txt b/externals/fmt/test/add-subdirectory-test/CMakeLists.txt index e08d0cf7..362b0494 100644 --- a/externals/fmt/test/add-subdirectory-test/CMakeLists.txt +++ b/externals/fmt/test/add-subdirectory-test/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1...3.18) +cmake_minimum_required(VERSION 3.8...3.25) project(fmt-test CXX) diff --git a/externals/fmt/test/args-test.cc b/externals/fmt/test/args-test.cc index aa01fa4e..3ab31335 100644 --- a/externals/fmt/test/args-test.cc +++ b/externals/fmt/test/args-test.cc @@ -44,7 +44,7 @@ template <> struct formatter<custom_type> { template <typename FormatContext> auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) { - return format_to(ctx.out(), "cust={}", p.i); + return fmt::format_to(ctx.out(), "cust={}", p.i); } }; FMT_END_NAMESPACE diff --git a/externals/fmt/test/chrono-test.cc b/externals/fmt/test/chrono-test.cc index 959fb65a..f047c1ea 100644 --- a/externals/fmt/test/chrono-test.cc +++ b/externals/fmt/test/chrono-test.cc @@ -7,6 +7,7 @@ #include "fmt/chrono.h" +#include <algorithm> #include <ctime> #include <vector> @@ -14,9 +15,15 @@ #include "util.h" // get_locale using fmt::runtime; - using testing::Contains; +#if defined(__MINGW32__) && !defined(_UCRT) +// Only C89 conversion specifiers when using MSVCRT instead of UCRT +# define FMT_HAS_C99_STRFTIME 0 +#else +# define FMT_HAS_C99_STRFTIME 1 +#endif + auto make_tm() -> std::tm { auto time = std::tm(); time.tm_mday = 1; @@ -121,6 +128,13 @@ TEST(chrono_test, format_tm) { make_tm(2000, 1, 2, 12, 14, 16), // W52 make_tm(2000, 1, 3, 12, 14, 16) // W1 }; + +#if !FMT_HAS_C99_STRFTIME + GTEST_SKIP() << "Skip the rest of this test because it relies on strftime() " + "conforming to C99, but on this platform, MINGW + MSVCRT, " + "the function conforms only to C89."; +#endif + const std::string iso_week_spec = "%Y-%m-%d: %G %g %V"; for (auto ctm : tm_list) { // Calculate tm_yday, tm_wday, etc. @@ -227,32 +241,29 @@ auto equal(const std::tm& lhs, const std::tm& rhs) -> bool { lhs.tm_isdst == rhs.tm_isdst; } -TEST(chrono_test, localtime) { - auto t = std::time(nullptr); - auto tm = *std::localtime(&t); - EXPECT_TRUE(equal(tm, fmt::localtime(t))); -} - TEST(chrono_test, gmtime) { auto t = std::time(nullptr); auto tm = *std::gmtime(&t); EXPECT_TRUE(equal(tm, fmt::gmtime(t))); } -template <typename TimePoint> auto strftime_full(TimePoint tp) -> std::string { +template <typename TimePoint> +auto strftime_full_utc(TimePoint tp) -> std::string { auto t = std::chrono::system_clock::to_time_t(tp); - auto tm = *std::localtime(&t); + auto tm = *std::gmtime(&t); return system_strftime("%Y-%m-%d %H:%M:%S", &tm); } -TEST(chrono_test, time_point) { - auto t1 = std::chrono::system_clock::now(); - EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); - EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1)); +TEST(chrono_test, system_clock_time_point) { + auto t1 = std::chrono::time_point_cast<std::chrono::seconds>( + std::chrono::system_clock::now()); + 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)); - EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); + EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); std::vector<std::string> spec_list = { "%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", @@ -260,16 +271,127 @@ TEST(chrono_test, time_point) { "%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e", "%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH", "%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X", - "%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"}; - spec_list.push_back("%Y-%m-%d %H:%M:%S"); + "%EX", "%D", "%F", "%R", "%T", "%p"}; #ifndef _WIN32 // Disabled on Windows because these formats are not consistent among // platforms. spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"}); +#elif !FMT_HAS_C99_STRFTIME + // Only C89 conversion specifiers when using MSVCRT instead of UCRT + spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", + "%a", "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"}; #endif + spec_list.push_back("%Y-%m-%d %H:%M:%S"); for (const auto& spec : spec_list) { auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::gmtime(&t); + + auto sys_output = system_strftime(spec, &tm); + + auto fmt_spec = fmt::format("{{:{}}}", spec); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1)); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); + } + + // Timezone formatters tests makes sense for localtime. +#if FMT_HAS_C99_STRFTIME + spec_list = {"%z", "%Z"}; +#else + spec_list = {"%Z"}; +#endif + for (const auto& spec : spec_list) { + auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::localtime(&t); + + auto sys_output = system_strftime(spec, &tm); + + auto fmt_spec = fmt::format("{{:{}}}", spec); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); + + if (spec == "%z") { + sys_output.insert(sys_output.end() - 2, 1, ':'); + EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm)); + EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm)); + } + } + + // Separate tests for UTC, since std::time_put can use local time and ignoring + // the timezone in std::tm (if it presents on platform). + if (fmt::detail::has_member_data_tm_zone<std::tm>::value) { + auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::gmtime(&t); + + std::vector<std::string> tz_names = {"GMT", "UTC"}; + EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1))); + EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm))); + } + + if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) { + 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("+00:00", fmt::format("{:%Ez}", t1)); + EXPECT_EQ("+00:00", fmt::format("{:%Ez}", tm)); + + EXPECT_EQ("+00:00", fmt::format("{:%Oz}", t1)); + EXPECT_EQ("+00:00", fmt::format("{:%Oz}", tm)); + } +} + +#if FMT_USE_LOCAL_TIME + +TEST(chrono_test, localtime) { + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + EXPECT_TRUE(equal(tm, fmt::localtime(t))); +} + +template <typename Duration> +auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string { + auto t = std::chrono::system_clock::to_time_t( + std::chrono::current_zone()->to_sys(tp)); + auto tm = *std::localtime(&t); + return system_strftime("%Y-%m-%d %H:%M:%S", &tm); +} + +TEST(chrono_test, local_system_clock_time_point) { +# ifdef _WIN32 + return; // Not supported on Windows. +# endif + auto t1 = std::chrono::time_point_cast<std::chrono::seconds>( + std::chrono::current_zone()->to_local(std::chrono::system_clock::now())); + EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); + EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1)); + EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1)); + using time_point = std::chrono::local_time<std::chrono::seconds>; + auto t2 = time_point(std::chrono::seconds(86400 + 42)); + EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); + + std::vector<std::string> spec_list = { + "%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", + "%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U", + "%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e", + "%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH", + "%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X", + "%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"}; +# ifndef _WIN32 + // Disabled on Windows because these formats are not consistent among + // platforms. + spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"}); +# elif !FMT_HAS_C99_STRFTIME + // Only C89 conversion specifiers when using MSVCRT instead of UCRT + spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a", + "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"}; +# endif + spec_list.push_back("%Y-%m-%d %H:%M:%S"); + + for (const auto& spec : spec_list) { + auto t = std::chrono::system_clock::to_time_t( + std::chrono::current_zone()->to_sys(t1)); auto tm = *std::localtime(&t); auto sys_output = system_strftime(spec, &tm); @@ -278,8 +400,26 @@ TEST(chrono_test, time_point) { EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1)); EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); } + + if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") != + spec_list.cend()) { + auto t = std::chrono::system_clock::to_time_t( + std::chrono::current_zone()->to_sys(t1)); + auto tm = *std::localtime(&t); + + auto sys_output = system_strftime("%z", &tm); + sys_output.insert(sys_output.end() - 2, 1, ':'); + + EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1)); + EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm)); + + EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1)); + EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm)); + } } +#endif // FMT_USE_LOCAL_TIME + #ifndef FMT_STATIC_THOUSANDS_SEPARATOR TEST(chrono_test, format_default) { @@ -324,7 +464,7 @@ TEST(chrono_test, format_default) { fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42))); } -TEST(chrono_test, align) { +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)); @@ -340,6 +480,35 @@ TEST(chrono_test, align) { fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12)); } +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("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)); +} + +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("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)); +} + TEST(chrono_test, format_specs) { EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0))); EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0))); @@ -407,6 +576,10 @@ TEST(chrono_test, invalid_specs) { "invalid format"); EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error, "invalid format"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:abc}"), sec), fmt::format_error, + "invalid format"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2f}"), sec), fmt::format_error, + "invalid format"); } auto format_tm(const std::tm& time, fmt::string_view spec, @@ -454,7 +627,7 @@ TEST(chrono_test, format_default_fp) { TEST(chrono_test, format_precision) { EXPECT_THROW_MSG( - (void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)), + (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))); @@ -557,9 +730,9 @@ TEST(chrono_test, special_durations) { "03:33"); EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}), "03:33:20"); - EXPECT_EQ("44.000000000000", - fmt::format("{:%S}", std::chrono::duration<float, std::pico>( - 1.54213895E+26))); + EXPECT_EQ("01.234", + fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>( + 1.234e12))); } TEST(chrono_test, unsigned_duration) { @@ -599,12 +772,28 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}), "-13.420148734"); EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234"); + // Check subsecond presision modifier. + EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::nanoseconds{1234}), + "00.000001"); + EXPECT_EQ(fmt::format("{:.18%S}", std::chrono::nanoseconds{1234}), + "00.000001234000000000"); + EXPECT_EQ(fmt::format("{:.{}%S}", std::chrono::nanoseconds{1234}, 6), + "00.000001"); + EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{1234}), + "01.234000"); + EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}), + "-01.234000"); + EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000"); + EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000"); + EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123"); + EXPECT_EQ(fmt::format("{:.8%S}", dms(1.234)), "00.00123400"); { // Check that {:%H:%M:%S} is equivalent to {:%T}. auto dur = std::chrono::milliseconds{3601234}; auto formatted_dur = fmt::format("{:%T}", dur); EXPECT_EQ(formatted_dur, "01:00:01.234"); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000"); } using nanoseconds_dbl = std::chrono::duration<double, std::nano>; EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); @@ -618,15 +807,197 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { auto formatted_dur = fmt::format("{:%T}", dur); EXPECT_EQ(formatted_dur, "-00:01:39.123456789"); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123"); } // Check that durations with precision greater than std::chrono::seconds have // fixed precision, and print zeros even if there is no fractional part. EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), "07.000000"); - EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 3>>(1)), + EXPECT_EQ(fmt::format("{:%S}", + std::chrono::duration<long long, std::ratio<1, 3>>(1)), "00.333333"); - EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 7>>(1)), + EXPECT_EQ(fmt::format("{:%S}", + std::chrono::duration<long long, std::ratio<1, 7>>(1)), "00.142857"); + + EXPECT_EQ( + fmt::format("{:%S}", + std::chrono::duration<signed char, std::ratio<1, 100>>(0x80)), + "-01.28"); + + EXPECT_EQ( + fmt::format("{:%M:%S}", + std::chrono::duration<short, std::ratio<1, 100>>(0x8000)), + "-05:27.68"); + + // Check that floating point seconds with ratio<1,1> are printed. + EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}), + "01.500000"); + EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}), + "-01:01.250000"); } #endif // FMT_STATIC_THOUSANDS_SEPARATOR + +// Disable the utc_clock test for windows, as the icu.dll used for tzdb +// (time zone database) is not shipped with many windows versions. +#if FMT_USE_UTC_TIME && !defined(_WIN32) +TEST(chrono_test, utc_clock) { + auto t1 = std::chrono::system_clock::now(); + auto t1_utc = std::chrono::utc_clock::from_sys(t1); + EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t1), + fmt::format("{:%Y-%m-%d %H:%M:%S}", t1_utc)); +} +#endif + +TEST(chrono_test, timestamps_ratios) { + std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds> + t1(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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + 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)); + + EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789"); + + const 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_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)); + + 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)); + } +} + +TEST(chrono_test, glibc_extensions) { + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%0}"), std::chrono::seconds()), + fmt::format_error, "invalid format"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%_}"), std::chrono::seconds()), + fmt::format_error, "invalid format"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%-}"), std::chrono::seconds()), + fmt::format_error, "invalid format"); + + { + const auto d = std::chrono::hours(1) + std::chrono::minutes(2) + + std::chrono::seconds(3); + + EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", d), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", d), "1,1,2,3"); + + EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", d), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", d), "1,1,2,3"); + } + + { + const auto tm = make_tm(1970, 1, 1, 1, 2, 3); + EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", tm), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", tm), "1,1,2,3"); + + EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", tm), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", tm), "1,1,2,3"); + } + + { + const auto d = std::chrono::seconds(3) + std::chrono::milliseconds(140); + EXPECT_EQ(fmt::format("{:%S}", d), "03.140"); + EXPECT_EQ(fmt::format("{:%0S}", d), "03.140"); + EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140"); + EXPECT_EQ(fmt::format("{:%-S}", d), "3.140"); + } + + { + const auto d = std::chrono::duration<double>(3.14); + EXPECT_EQ(fmt::format("{:%S}", d), "03.140000"); + EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000"); + EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000"); + EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000"); + } +} diff --git a/externals/fmt/test/compile-error-test/CMakeLists.txt b/externals/fmt/test/compile-error-test/CMakeLists.txt index 9c65dfdc..c4a16068 100644 --- a/externals/fmt/test/compile-error-test/CMakeLists.txt +++ b/externals/fmt/test/compile-error-test/CMakeLists.txt @@ -1,6 +1,6 @@ # Test if compile errors are produced where necessary. -cmake_minimum_required(VERSION 3.1...3.18) +cmake_minimum_required(VERSION 3.8...3.25) project(compile-error-test CXX) set(fmt_headers " @@ -64,7 +64,7 @@ function (run_tests) ") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" " - cmake_minimum_required(VERSION 3.1...3.18) + cmake_minimum_required(VERSION 3.8...3.25) project(tests CXX) add_subdirectory(${FMT_DIR} fmt) ${cmake_targets} diff --git a/externals/fmt/test/compile-fp-test.cc b/externals/fmt/test/compile-fp-test.cc index db0cd906..5b46d74b 100644 --- a/externals/fmt/test/compile-fp-test.cc +++ b/externals/fmt/test/compile-fp-test.cc @@ -12,6 +12,7 @@ defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \ defined(__cpp_constexpr_dynamic_alloc) && \ __cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L + 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/externals/fmt/test/compile-test.cc b/externals/fmt/test/compile-test.cc index 2a9e1619..d6c7c643 100644 --- a/externals/fmt/test/compile-test.cc +++ b/externals/fmt/test/compile-test.cc @@ -21,38 +21,6 @@ TEST(iterator_test, counting_iterator) { EXPECT_EQ((it + 41).count(), 42); } -TEST(iterator_test, truncating_iterator) { - char* p = nullptr; - auto it = fmt::detail::truncating_iterator<char*>(p, 3); - auto prev = it++; - EXPECT_EQ(prev.base(), p); - EXPECT_EQ(it.base(), p + 1); -} - -TEST(iterator_test, truncating_iterator_default_construct) { - auto it = fmt::detail::truncating_iterator<char*>(); - EXPECT_EQ(nullptr, it.base()); - EXPECT_EQ(std::size_t{0}, it.count()); -} - -#ifdef __cpp_lib_ranges -TEST(iterator_test, truncating_iterator_is_output_iterator) { - static_assert( - std::output_iterator<fmt::detail::truncating_iterator<char*>, char>); -} -#endif - -TEST(iterator_test, truncating_back_inserter) { - auto buffer = std::string(); - auto bi = std::back_inserter(buffer); - auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2); - *it++ = '4'; - *it++ = '2'; - *it++ = '1'; - EXPECT_EQ(buffer.size(), 2); - EXPECT_EQ(buffer, "42"); -} - TEST(compile_test, compile_fallback) { // FMT_COMPILE should fallback on runtime formatting when `if constexpr` is // not available. @@ -227,10 +195,27 @@ TEST(compile_test, format_to_n) { EXPECT_STREQ("2a", buffer); } -TEST(compile_test, formatted_size) { - EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42)); - EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0)); +# ifdef __cpp_lib_bit_cast +TEST(compile_test, constexpr_formatted_size) { + FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42); + EXPECT_EQ(size, 2); + FMT_CONSTEXPR20 size_t hex_size = + fmt::formatted_size(FMT_COMPILE("{:x}"), 15); + EXPECT_EQ(hex_size, 1); + FMT_CONSTEXPR20 size_t binary_size = + fmt::formatted_size(FMT_COMPILE("{:b}"), 15); + EXPECT_EQ(binary_size, 4); + FMT_CONSTEXPR20 size_t padded_size = + fmt::formatted_size(FMT_COMPILE("{:*^6}"), 42); + EXPECT_EQ(padded_size, 6); + FMT_CONSTEXPR20 size_t float_size = + fmt::formatted_size(FMT_COMPILE("{:.3}"), 12.345); + EXPECT_EQ(float_size, 4); + FMT_CONSTEXPR20 size_t str_size = + fmt::formatted_size(FMT_COMPILE("{:s}"), "abc"); + EXPECT_EQ(str_size, 3); } +# endif TEST(compile_test, text_and_arg) { EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); @@ -300,8 +285,9 @@ TEST(compile_test, compile_format_string_literal) { // (compiler file // 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp', // line 8635) -#if ((FMT_CPLUSPLUS >= 202002L) && \ - (!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \ +#if ((FMT_CPLUSPLUS >= 202002L) && \ + (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \ + (!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \ (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) template <size_t max_string_length, typename Char = char> struct test_string { template <typename T> constexpr bool operator==(const T& rhs) const noexcept { diff --git a/externals/fmt/test/core-test.cc b/externals/fmt/test/core-test.cc index ce0816c6..1d84ffd7 100644 --- a/externals/fmt/test/core-test.cc +++ b/externals/fmt/test/core-test.cc @@ -70,6 +70,16 @@ TEST(string_view_test, compare) { EXPECT_LT(string_view("foo").compare(string_view("fop")), 0); EXPECT_GT(string_view("foo").compare(string_view("fo")), 0); EXPECT_LT(string_view("fo").compare(string_view("foo")), 0); + + EXPECT_TRUE(string_view("foo").starts_with('f')); + EXPECT_FALSE(string_view("foo").starts_with('o')); + EXPECT_FALSE(string_view().starts_with('o')); + + EXPECT_TRUE(string_view("foo").starts_with("fo")); + EXPECT_TRUE(string_view("foo").starts_with("foo")); + EXPECT_FALSE(string_view("foo").starts_with("fooo")); + EXPECT_FALSE(string_view().starts_with("fooo")); + check_op<std::equal_to>(); check_op<std::not_equal_to>(); check_op<std::less>(); @@ -78,24 +88,6 @@ TEST(string_view_test, compare) { check_op<std::greater_equal>(); } -namespace test_ns { -template <typename Char> class test_string { - private: - std::basic_string<Char> s_; - - public: - test_string(const Char* s) : s_(s) {} - const Char* data() const { return s_.data(); } - size_t length() const { return s_.size(); } - operator const Char*() const { return s_.c_str(); } -}; - -template <typename Char> -fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) { - return {s.data(), s.length()}; -} -} // namespace test_ns - TEST(core_test, is_output_iterator) { EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value)); EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value)); @@ -149,7 +141,7 @@ TEST(buffer_test, indestructible) { } template <typename T> struct mock_buffer final : buffer<T> { - MOCK_METHOD1(do_grow, size_t(size_t capacity)); + MOCK_METHOD(size_t, do_grow, (size_t)); void grow(size_t capacity) override { this->set(this->data(), do_grow(capacity)); @@ -281,7 +273,7 @@ struct custom_context { bool called = false; template <typename T> struct formatter_type { - auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } @@ -298,11 +290,11 @@ struct test_struct {}; FMT_BEGIN_NAMESPACE template <typename Char> struct formatter<test_struct, Char> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } - auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) { + auto format(test_struct, format_context& ctx) const -> decltype(ctx.out()) { auto test = string_view("test"); return std::copy_n(test.data(), test.size(), ctx.out()); } @@ -316,7 +308,7 @@ TEST(arg_test, format_args) { TEST(arg_test, make_value_with_custom_context) { auto t = test_struct(); - fmt::detail::value<custom_context> arg( + auto arg = fmt::detail::value<custom_context>( fmt::detail::arg_mapper<custom_context>().map(t)); auto ctx = custom_context(); auto parse_ctx = fmt::format_parse_context(""); @@ -335,12 +327,12 @@ template <typename T> struct mock_visitor { ON_CALL(*this, visit(_)).WillByDefault(Return(test_result())); } - MOCK_METHOD1_T(visit, test_result(T value)); - MOCK_METHOD0_T(unexpected, void()); + MOCK_METHOD(test_result, visit, (T)); + MOCK_METHOD(void, unexpected, ()); - test_result operator()(T value) { return visit(value); } + auto operator()(T value) -> test_result { return visit(value); } - template <typename U> test_result operator()(U) { + template <typename U> auto operator()(U) -> test_result { unexpected(); return test_result(); } @@ -369,10 +361,11 @@ VISIT_TYPE(unsigned long, unsigned long long); testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \ EXPECT_CALL(visitor, visit(expected)); \ using iterator = std::back_insert_iterator<buffer<Char>>; \ + auto var = value; \ fmt::visit_format_arg( \ visitor, \ fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \ - value)); \ + var)); \ } #define CHECK_ARG_SIMPLE(value) \ @@ -392,13 +385,13 @@ using test_types = TYPED_TEST_SUITE(numeric_arg_test, test_types); template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0> -T test_value() { +auto test_value() -> T { return static_cast<T>(42); } template <typename T, fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0> -T test_value() { +auto test_value() -> T { return static_cast<T>(4.2); } @@ -441,8 +434,8 @@ TEST(arg_test, pointer_arg) { } struct check_custom { - test_result operator()( - fmt::basic_format_arg<fmt::format_context>::handle h) const { + auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const + -> test_result { struct test_buffer final : fmt::detail::buffer<char> { char data[10]; test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {} @@ -461,13 +454,13 @@ TEST(arg_test, custom_arg) { auto test = test_struct(); using visitor = mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>; - testing::StrictMock<visitor> v; + auto&& v = testing::StrictMock<visitor>(); EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom())); fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test)); } TEST(arg_test, visit_invalid_arg) { - testing::StrictMock<mock_visitor<fmt::monostate>> visitor; + auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>(); EXPECT_CALL(visitor, visit(_)); auto arg = fmt::basic_format_arg<fmt::format_context>(); fmt::visit_format_arg(visitor, arg); @@ -475,185 +468,78 @@ TEST(arg_test, visit_invalid_arg) { #if FMT_USE_CONSTEXPR -enum class arg_id_result { none, empty, index, name, error }; +enum class arg_id_result { none, empty, index, name }; struct test_arg_id_handler { arg_id_result res = arg_id_result::none; int index = 0; string_view name; - constexpr void operator()() { res = arg_id_result::empty; } + constexpr void on_auto() { res = arg_id_result::empty; } - constexpr void operator()(int i) { + constexpr void on_index(int i) { res = arg_id_result::index; index = i; } - constexpr void operator()(string_view n) { + constexpr void on_name(string_view n) { res = arg_id_result::name; name = n; } - - constexpr void on_error(const char*) { res = arg_id_result::error; } }; template <size_t N> constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) { - test_arg_id_handler h; + auto h = test_arg_id_handler(); fmt::detail::parse_arg_id(s, s + N, h); return h; } -TEST(format_test, constexpr_parse_arg_id) { +TEST(core_test, constexpr_parse_arg_id) { static_assert(parse_arg_id(":").res == arg_id_result::empty, ""); static_assert(parse_arg_id("}").res == arg_id_result::empty, ""); static_assert(parse_arg_id("42:").res == arg_id_result::index, ""); static_assert(parse_arg_id("42:").index == 42, ""); static_assert(parse_arg_id("foo:").res == arg_id_result::name, ""); static_assert(parse_arg_id("foo:").name.size() == 3, ""); - static_assert(parse_arg_id("!").res == arg_id_result::error, ""); -} - -struct test_format_specs_handler { - enum result { none, hash, zero, loc, error }; - result res = none; - - fmt::align_t alignment = fmt::align::none; - fmt::sign_t sign = fmt::sign::none; - char fill = 0; - int width = 0; - fmt::detail::arg_ref<char> width_ref; - int precision = 0; - fmt::detail::arg_ref<char> precision_ref; - fmt::presentation_type type = fmt::presentation_type::none; - - // Workaround for MSVC2017 bug that results in "expression did not evaluate - // to a constant" with compiler-generated copy ctor. - constexpr test_format_specs_handler() {} - constexpr test_format_specs_handler(const test_format_specs_handler& other) = - default; - - constexpr void on_align(fmt::align_t a) { alignment = a; } - constexpr void on_fill(fmt::string_view f) { fill = f[0]; } - constexpr void on_sign(fmt::sign_t s) { sign = s; } - constexpr void on_hash() { res = hash; } - constexpr void on_zero() { res = zero; } - constexpr void on_localized() { res = loc; } - - constexpr void on_width(int w) { width = w; } - constexpr void on_dynamic_width(fmt::detail::auto_id) {} - constexpr void on_dynamic_width(int index) { width_ref = index; } - constexpr void on_dynamic_width(string_view) {} - - constexpr void on_precision(int p) { precision = p; } - constexpr void on_dynamic_precision(fmt::detail::auto_id) {} - constexpr void on_dynamic_precision(int index) { precision_ref = index; } - constexpr void on_dynamic_precision(string_view) {} - - constexpr void end_precision() {} - constexpr void on_type(fmt::presentation_type t) { type = t; } - constexpr void on_error(const char*) { res = error; } -}; +} -template <size_t N> -constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) { - auto h = test_format_specs_handler(); - fmt::detail::parse_format_specs(s, s + N - 1, h); - return h; +template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) { + auto ctx = fmt::detail::compile_parse_context<char>(fmt::string_view(s, N), + 43, nullptr); + auto specs = fmt::detail::dynamic_format_specs<>(); + fmt::detail::parse_format_specs(s, s + N - 1, specs, ctx, + fmt::detail::type::float_type); + return specs; } TEST(core_test, constexpr_parse_format_specs) { - using handler = test_format_specs_handler; - static_assert(parse_test_specs("<").alignment == fmt::align::left, ""); - static_assert(parse_test_specs("*^").fill == '*', ""); + static_assert(parse_test_specs("<").align == fmt::align::left, ""); + static_assert(parse_test_specs("*^").fill[0] == '*', ""); static_assert(parse_test_specs("+").sign == fmt::sign::plus, ""); static_assert(parse_test_specs("-").sign == fmt::sign::minus, ""); static_assert(parse_test_specs(" ").sign == fmt::sign::space, ""); - static_assert(parse_test_specs("#").res == handler::hash, ""); - static_assert(parse_test_specs("0").res == handler::zero, ""); - static_assert(parse_test_specs("L").res == handler::loc, ""); + static_assert(parse_test_specs("#").alt, ""); + static_assert(parse_test_specs("0").align == fmt::align::numeric, ""); + static_assert(parse_test_specs("L").localized, ""); static_assert(parse_test_specs("42").width == 42, ""); static_assert(parse_test_specs("{42}").width_ref.val.index == 42, ""); static_assert(parse_test_specs(".42").precision == 42, ""); static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, ""); - static_assert(parse_test_specs("d").type == fmt::presentation_type::dec, ""); - static_assert(parse_test_specs("{<").res == handler::error, ""); -} - -struct test_parse_context { - using char_type = char; - - constexpr int next_arg_id() { return 11; } - template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {} - - constexpr const char* begin() { return nullptr; } - constexpr const char* end() { return nullptr; } - - void on_error(const char*) {} -}; - -template <size_t N> -constexpr fmt::detail::dynamic_format_specs<char> parse_dynamic_specs( - const char (&s)[N]) { - auto specs = fmt::detail::dynamic_format_specs<char>(); - auto ctx = test_parse_context(); - auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx); - parse_format_specs(s, s + N - 1, h); - return specs; -} - -TEST(format_test, constexpr_dynamic_specs_handler) { - static_assert(parse_dynamic_specs("<").align == fmt::align::left, ""); - static_assert(parse_dynamic_specs("*^").fill[0] == '*', ""); - static_assert(parse_dynamic_specs("+").sign == fmt::sign::plus, ""); - static_assert(parse_dynamic_specs("-").sign == fmt::sign::minus, ""); - static_assert(parse_dynamic_specs(" ").sign == fmt::sign::space, ""); - static_assert(parse_dynamic_specs("#").alt, ""); - static_assert(parse_dynamic_specs("0").align == fmt::align::numeric, ""); - static_assert(parse_dynamic_specs("42").width == 42, ""); - static_assert(parse_dynamic_specs("{}").width_ref.val.index == 11, ""); - static_assert(parse_dynamic_specs("{42}").width_ref.val.index == 42, ""); - static_assert(parse_dynamic_specs(".42").precision == 42, ""); - static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, ""); - static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, ""); - static_assert(parse_dynamic_specs("d").type == fmt::presentation_type::dec, - ""); -} - -template <size_t N> -constexpr test_format_specs_handler check_specs(const char (&s)[N]) { - fmt::detail::specs_checker<test_format_specs_handler> checker( - test_format_specs_handler(), fmt::detail::type::double_type); - parse_format_specs(s, s + N - 1, checker); - return checker; -} - -TEST(format_test, constexpr_specs_checker) { - using handler = test_format_specs_handler; - static_assert(check_specs("<").alignment == fmt::align::left, ""); - static_assert(check_specs("*^").fill == '*', ""); - static_assert(check_specs("+").sign == fmt::sign::plus, ""); - static_assert(check_specs("-").sign == fmt::sign::minus, ""); - static_assert(check_specs(" ").sign == fmt::sign::space, ""); - static_assert(check_specs("#").res == handler::hash, ""); - static_assert(check_specs("0").res == handler::zero, ""); - static_assert(check_specs("42").width == 42, ""); - static_assert(check_specs("{42}").width_ref.val.index == 42, ""); - static_assert(check_specs(".42").precision == 42, ""); - static_assert(check_specs(".{42}").precision_ref.val.index == 42, ""); - static_assert(check_specs("d").type == fmt::presentation_type::dec, ""); - static_assert(check_specs("{<").res == handler::error, ""); + static_assert( + parse_test_specs("f").type == fmt::presentation_type::fixed_lower, ""); } struct test_format_string_handler { constexpr void on_text(const char*, const char*) {} - constexpr int on_arg_id() { return 0; } + constexpr auto on_arg_id() -> int { return 0; } - template <typename T> constexpr int on_arg_id(T) { return 0; } + template <typename T> constexpr auto on_arg_id(T) -> int { return 0; } constexpr void on_replacement_field(int, const char*) {} - constexpr const char* on_format_specs(int, const char* begin, const char*) { + constexpr auto on_format_specs(int, const char* begin, const char*) -> const + char* { return begin; } @@ -668,7 +554,7 @@ template <size_t N> constexpr bool parse_string(const char (&s)[N]) { return !h.error; } -TEST(format_test, constexpr_parse_format_string) { +TEST(core_test, constexpr_parse_format_string) { static_assert(parse_string("foo"), ""); static_assert(!parse_string("}"), ""); static_assert(parse_string("{}"), ""); @@ -687,19 +573,20 @@ struct disabled_formatter_convertible { FMT_BEGIN_NAMESPACE template <> struct formatter<enabled_formatter> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } - auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) { + auto format(enabled_formatter, format_context& ctx) const + -> decltype(ctx.out()) { return ctx.out(); } }; template <> struct formatter<enabled_ptr_formatter*> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } - auto format(enabled_ptr_formatter*, format_context& ctx) + auto format(enabled_ptr_formatter*, format_context& ctx) const -> decltype(ctx.out()) { return ctx.out(); } @@ -720,7 +607,7 @@ struct nonconst_formattable {}; FMT_BEGIN_NAMESPACE template <> struct formatter<const_formattable> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } @@ -732,7 +619,7 @@ template <> struct formatter<const_formattable> { }; template <> struct formatter<nonconst_formattable> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } @@ -754,7 +641,7 @@ struct convertible_to_pointer_formattable { FMT_BEGIN_NAMESPACE template <> struct formatter<convertible_to_pointer_formattable> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } @@ -768,29 +655,17 @@ FMT_END_NAMESPACE enum class unformattable_scoped_enum {}; -namespace test { -enum class formattable_scoped_enum {}; -auto format_as(formattable_scoped_enum) -> int { return 42; } - -struct convertible_to_enum { - operator formattable_scoped_enum() const { return {}; } -}; -} // namespace test - TEST(core_test, is_formattable) { -#if 0 - // This should be enabled once corresponding map overloads are gone. - static_assert(fmt::is_formattable<signed char*>::value, ""); - static_assert(fmt::is_formattable<unsigned char*>::value, ""); - static_assert(fmt::is_formattable<const signed char*>::value, ""); - static_assert(fmt::is_formattable<const unsigned char*>::value, ""); -#endif static_assert(!fmt::is_formattable<wchar_t>::value, ""); #ifdef __cpp_char8_t static_assert(!fmt::is_formattable<char8_t>::value, ""); #endif static_assert(!fmt::is_formattable<char16_t>::value, ""); static_assert(!fmt::is_formattable<char32_t>::value, ""); + static_assert(!fmt::is_formattable<signed char*>::value, ""); + static_assert(!fmt::is_formattable<unsigned char*>::value, ""); + static_assert(!fmt::is_formattable<const signed char*>::value, ""); + static_assert(!fmt::is_formattable<const unsigned char*>::value, ""); static_assert(!fmt::is_formattable<const wchar_t*>::value, ""); static_assert(!fmt::is_formattable<const wchar_t[3]>::value, ""); static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value, @@ -798,7 +673,8 @@ TEST(core_test, is_formattable) { static_assert(fmt::is_formattable<enabled_formatter>::value, ""); static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, ""); static_assert(!fmt::is_formattable<disabled_formatter>::value, ""); - static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, ""); + static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value, + ""); static_assert(fmt::is_formattable<const_formattable&>::value, ""); static_assert(fmt::is_formattable<const const_formattable&>::value, ""); @@ -818,45 +694,47 @@ TEST(core_test, is_formattable) { static_assert(!fmt::is_formattable<int(s::*)>::value, ""); static_assert(!fmt::is_formattable<int (s::*)()>::value, ""); static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, ""); - static_assert(fmt::is_formattable<test::formattable_scoped_enum>::value, ""); - static_assert(!fmt::is_formattable<test::convertible_to_enum>::value, ""); + static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, ""); } TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } TEST(core_test, format_to) { - std::string s; + auto s = std::string(); fmt::format_to(std::back_inserter(s), "{}", 42); EXPECT_EQ(s, "42"); } -TEST(core_test, format_as) { - EXPECT_EQ(fmt::format("{}", test::formattable_scoped_enum()), "42"); +#ifdef __cpp_lib_byte +TEST(core_test, format_byte) { + EXPECT_EQ(fmt::format("{}", std::byte(42)), "42"); } +#endif struct convertible_to_int { operator int() const { return 42; } }; -struct convertible_to_c_string { +struct convertible_to_cstring { operator const char*() const { return "foo"; } }; FMT_BEGIN_NAMESPACE template <> struct formatter<convertible_to_int> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } - auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) { + auto format(convertible_to_int, format_context& ctx) const + -> decltype(ctx.out()) { return std::copy_n("foo", 3, ctx.out()); } }; -template <> struct formatter<convertible_to_c_string> { +template <> struct formatter<convertible_to_cstring> { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } - auto format(convertible_to_c_string, format_context& ctx) + auto format(convertible_to_cstring, format_context& ctx) const -> decltype(ctx.out()) { return std::copy_n("bar", 3, ctx.out()); } @@ -865,7 +743,7 @@ FMT_END_NAMESPACE TEST(core_test, formatter_overrides_implicit_conversion) { EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo"); - EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar"); + EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "bar"); } // Test that check is not found by ADL. @@ -874,26 +752,25 @@ TEST(core_test, adl_check) { EXPECT_EQ(fmt::format("{}", test_struct()), "test"); } -TEST(core_test, to_string_view_foreign_strings) { - using namespace test_ns; - EXPECT_EQ(to_string_view(test_string<char>("42")), "42"); - fmt::detail::type type = - fmt::detail::mapped_type_constant<test_string<char>, - fmt::format_context>::value; - EXPECT_EQ(type, fmt::detail::type::string_type); -} - -struct implicitly_convertible_to_string { - operator std::string() const { return "foo"; } -}; - struct implicitly_convertible_to_string_view { operator fmt::string_view() const { return "foo"; } }; -TEST(core_test, format_implicitly_convertible_to_string_view) { - EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view())); +TEST(core_test, no_implicit_conversion_to_string_view) { + EXPECT_FALSE( + fmt::is_formattable<implicitly_convertible_to_string_view>::value); +} + +#ifdef FMT_USE_STRING_VIEW +struct implicitly_convertible_to_std_string_view { + operator std::string_view() const { return "foo"; } +}; + +TEST(core_test, no_implicit_conversion_to_std_string_view) { + EXPECT_FALSE( + fmt::is_formattable<implicitly_convertible_to_std_string_view>::value); } +#endif // std::is_constructible is broken in MSVC until version 2015. #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900 @@ -923,25 +800,6 @@ TEST(core_test, format_explicitly_convertible_to_std_string_view) { # endif #endif -struct convertible_to_long_long { - operator long long() const { return 1LL << 32; } -}; - -TEST(format_test, format_convertible_to_long_long) { - EXPECT_EQ("100000000", fmt::format("{:x}", convertible_to_long_long())); -} - -struct disabled_rvalue_conversion { - operator const char*() const& { return "foo"; } - operator const char*() & { return "foo"; } - operator const char*() const&& = delete; - operator const char*() && = delete; -}; - -TEST(core_test, disabled_rvalue_conversion) { - EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion())); -} - namespace adl_test { template <typename... T> void make_format_args(const T&...) = delete; @@ -973,3 +831,28 @@ TEST(core_test, has_const_formatter) { TEST(core_test, format_nonconst) { EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test"); } + +struct its_a_trap { + template <typename T> operator T() const { + auto v = T(); + v.x = 42; + return v; + } +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<its_a_trap> { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + auto format(its_a_trap, format_context& ctx) const -> decltype(ctx.out()) { + auto s = string_view("42"); + return std::copy(s.begin(), s.end(), ctx.out()); + } +}; +FMT_END_NAMESPACE + +TEST(core_test, trappy_conversion) { + EXPECT_EQ(fmt::format("{}", its_a_trap()), "42"); +} diff --git a/externals/fmt/test/enforce-checks-test.cc b/externals/fmt/test/enforce-checks-test.cc index c77cb142..960a7fcd 100644 --- a/externals/fmt/test/enforce-checks-test.cc +++ b/externals/fmt/test/enforce-checks-test.cc @@ -8,12 +8,14 @@ #include <iterator> #include <vector> +#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I #include "fmt/chrono.h" #include "fmt/color.h" #include "fmt/format.h" #include "fmt/ostream.h" #include "fmt/ranges.h" #include "fmt/xchar.h" +#undef I // Exercise the API to verify that everything we expect to can compile. void test_format_api() { diff --git a/externals/fmt/test/find-package-test/CMakeLists.txt b/externals/fmt/test/find-package-test/CMakeLists.txt index 93d686e6..eb2ea25f 100644 --- a/externals/fmt/test/find-package-test/CMakeLists.txt +++ b/externals/fmt/test/find-package-test/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1...3.18) +cmake_minimum_required(VERSION 3.8...3.25) project(fmt-test) diff --git a/externals/fmt/test/format-impl-test.cc b/externals/fmt/test/format-impl-test.cc index b210e979..1c468b43 100644 --- a/externals/fmt/test/format-impl-test.cc +++ b/externals/fmt/test/format-impl-test.cc @@ -196,43 +196,6 @@ TEST(fp_test, multiply) { EXPECT_EQ(v.e, 4 + 8 + 64); } -TEST(fp_test, get_cached_power) { - using limits = std::numeric_limits<double>; - for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) { - int dec_exp = 0; - auto power = fmt::detail::get_cached_power(exp, dec_exp); - bigint exact, cache(power.f); - if (dec_exp >= 0) { - exact.assign_pow10(dec_exp); - if (power.e <= 0) - exact <<= -power.e; - else - cache <<= power.e; - exact.align(cache); - cache.align(exact); - auto exact_str = fmt::to_string(exact); - auto cache_str = fmt::to_string(cache); - EXPECT_EQ(exact_str.size(), cache_str.size()); - EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15)); - int diff = cache_str[15] - exact_str[15]; - if (diff == 1) - EXPECT_GT(exact_str[16], '8'); - else - EXPECT_EQ(diff, 0); - } else { - cache.assign_pow10(-dec_exp); - cache *= power.f + 1; // Inexact check. - exact = 1; - exact <<= -power.e; - exact.align(cache); - auto exact_str = fmt::to_string(exact); - auto cache_str = fmt::to_string(cache); - EXPECT_EQ(exact_str.size(), cache_str.size()); - EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16)); - } - } -} - TEST(fp_test, dragonbox_max_k) { using fmt::detail::dragonbox::floor_log10_pow2; using float_info = fmt::detail::dragonbox::float_info<float>; @@ -242,66 +205,18 @@ TEST(fp_test, dragonbox_max_k) { floor_log10_pow2(std::numeric_limits<float>::min_exponent - fmt::detail::num_significand_bits<float>() - 1)); using double_info = fmt::detail::dragonbox::float_info<double>; - EXPECT_EQ( - fmt::detail::const_check(double_info::max_k), - double_info::kappa - - floor_log10_pow2(std::numeric_limits<double>::min_exponent - - fmt::detail::num_significand_bits<double>() - 1)); -} - -TEST(fp_test, get_round_direction) { - using fmt::detail::get_round_direction; - using fmt::detail::round_direction; - EXPECT_EQ(get_round_direction(100, 50, 0), round_direction::down); - EXPECT_EQ(get_round_direction(100, 51, 0), round_direction::up); - EXPECT_EQ(get_round_direction(100, 40, 10), round_direction::down); - EXPECT_EQ(get_round_direction(100, 60, 10), round_direction::up); - for (size_t i = 41; i < 60; ++i) - EXPECT_EQ(get_round_direction(100, i, 10), round_direction::unknown); - uint64_t max = max_value<uint64_t>(); - EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure); - EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure); - EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure); - // Check that remainder + error doesn't overflow. - EXPECT_EQ(get_round_direction(max, max - 1, 2), round_direction::up); - // Check that 2 * (remainder + error) doesn't overflow. - EXPECT_EQ(get_round_direction(max, max / 2 + 1, max / 2), - round_direction::unknown); - // Check that remainder - error doesn't overflow. - EXPECT_EQ(get_round_direction(100, 40, 41), round_direction::unknown); - // Check that 2 * (remainder - error) doesn't overflow. - EXPECT_EQ(get_round_direction(max, max - 1, 1), round_direction::up); -} - -TEST(fp_test, fixed_handler) { - struct handler : fmt::detail::gen_digits_handler { - char buffer[10]; - handler(int prec = 0) : fmt::detail::gen_digits_handler() { - buf = buffer; - precision = prec; - } - }; - handler().on_digit('0', 100, 99, 0, false); - EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure); - namespace digits = fmt::detail::digits; - EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error); - // Check that divisor - error doesn't overflow. - EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error); - // Check that 2 * error doesn't overflow. - uint64_t max = max_value<uint64_t>(); - EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error); -} - -TEST(fp_test, grisu_format_compiles_with_on_ieee_double) { - auto buf = fmt::memory_buffer(); - format_float(0.42, -1, fmt::detail::float_specs(), buf); + EXPECT_EQ(fmt::detail::const_check(double_info::max_k), + double_info::kappa - + floor_log10_pow2( + std::numeric_limits<double>::min_exponent - + 2 * fmt::detail::num_significand_bits<double>() - 1)); } TEST(format_impl_test, format_error_code) { std::string msg = "error 42", sep = ": "; { auto buffer = fmt::memory_buffer(); - format_to(fmt::appender(buffer), "garbage"); + fmt::format_to(fmt::appender(buffer), "garbage"); fmt::detail::format_error_code(buffer, 42, "test"); EXPECT_EQ(to_string(buffer), "test: " + msg); } @@ -353,6 +268,16 @@ TEST(format_impl_test, count_digits) { test_count_digits<uint64_t>(); } +TEST(format_impl_test, countl_zero) { + constexpr auto num_bits = fmt::detail::num_bits<uint32_t>(); + uint32_t n = 1u; + for (int i = 1; i < num_bits - 1; i++) { + n <<= 1; + EXPECT_EQ(fmt::detail::countl_zero(n - 1), num_bits - i); + EXPECT_EQ(fmt::detail::countl_zero(n), num_bits - i - 1); + } +} + #if FMT_USE_FLOAT128 TEST(format_impl_test, write_float128) { auto s = std::string(); @@ -372,10 +297,22 @@ struct double_double { auto operator-() const -> double_double { return double_double(-a, -b); } }; +auto format_as(double_double d) -> double { return d; } + bool operator>=(const double_double& lhs, const double_double& rhs) { return lhs.a + lhs.b >= rhs.a + rhs.b; } +struct slow_float { + float value; + + explicit constexpr slow_float(float val = 0) : value(val) {} + operator float() const { return value; } + auto operator-() const -> slow_float { return slow_float(-value); } +}; + +auto format_as(slow_float f) -> float { return f; } + namespace std { template <> struct is_floating_point<double_double> : std::true_type {}; template <> struct numeric_limits<double_double> { @@ -383,14 +320,35 @@ template <> struct numeric_limits<double_double> { static constexpr bool is_iec559 = true; static constexpr int digits = 106; }; + +template <> struct is_floating_point<slow_float> : std::true_type {}; +template <> struct numeric_limits<slow_float> : numeric_limits<float> {}; } // namespace std +FMT_BEGIN_NAMESPACE +namespace detail { +template <> struct is_fast_float<slow_float> : std::false_type {}; +namespace dragonbox { +template <> struct float_info<slow_float> { + using carrier_uint = uint32_t; + static const int exponent_bits = 8; +}; +} // namespace dragonbox +} // namespace detail +FMT_END_NAMESPACE + TEST(format_impl_test, write_double_double) { auto s = std::string(); fmt::detail::write<char>(std::back_inserter(s), double_double(42), {}); -#ifndef _MSC_VER // MSVC has an issue with specializing is_floating_point. - EXPECT_EQ(s, "42"); -#endif + // Specializing is_floating_point is broken in MSVC. + if (!FMT_MSC_VERSION) EXPECT_EQ(s, "42"); +} + +TEST(format_impl_test, write_dragon_even) { + auto s = std::string(); + fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {}); + // Specializing is_floating_point is broken in MSVC. + if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450"); } #ifdef _WIN32 @@ -401,3 +359,125 @@ TEST(format_impl_test, write_console_signature) { (void)p; } #endif + +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +constexpr bool unicode_is_surrogate(uint32_t c) { + return c >= 0xD800U && c <= 0xDFFFU; +} + +FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) { + if (c >= (1UL << 16)) { + s[0] = static_cast<char>(0xf0 | (c >> 18)); + s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f)); + s[2] = static_cast<char>(0x80 | ((c >> 6) & 0x3f)); + s[3] = static_cast<char>(0x80 | ((c >> 0) & 0x3f)); + return s + 4; + } else if (c >= (1UL << 11)) { + s[0] = static_cast<char>(0xe0 | (c >> 12)); + s[1] = static_cast<char>(0x80 | ((c >> 6) & 0x3f)); + s[2] = static_cast<char>(0x80 | ((c >> 0) & 0x3f)); + return s + 3; + } else if (c >= (1UL << 7)) { + s[0] = static_cast<char>(0xc0 | (c >> 6)); + s[1] = static_cast<char>(0x80 | ((c >> 0) & 0x3f)); + return s + 2; + } else { + s[0] = static_cast<char>(c); + return s + 1; + } +} + +// Make sure it can decode every character +TEST(format_impl_test, utf8_decode_decode_all) { + for (uint32_t i = 0; i < 0x10ffff; i++) { + if (!unicode_is_surrogate(i)) { + int e; + uint32_t c; + char buf[8] = {0}; + char* end = utf8_encode(buf, i); + const char* res = fmt::detail::utf8_decode(buf, &c, &e); + EXPECT_EQ(end, res); + EXPECT_EQ(c, i); + EXPECT_EQ(e, 0); + } + } +} + +// Reject everything outside of U+0000..U+10FFFF +TEST(format_impl_test, utf8_decode_out_of_range) { + for (uint32_t i = 0x110000; i < 0x1fffff; i++) { + int e; + uint32_t c; + char buf[8] = {0}; + utf8_encode(buf, i); + const char* end = fmt::detail::utf8_decode(buf, &c, &e); + EXPECT_NE(e, 0); + EXPECT_EQ(end - buf, 4); + } +} + +// Does it reject all surrogate halves? +TEST(format_impl_test, utf8_decode_surrogate_halves) { + for (uint32_t i = 0xd800; i <= 0xdfff; i++) { + int e; + uint32_t c; + char buf[8] = {0}; + utf8_encode(buf, i); + fmt::detail::utf8_decode(buf, &c, &e); + EXPECT_NE(e, 0); + } +} + +// How about non-canonical encodings? +TEST(format_impl_test, utf8_decode_non_canonical_encodings) { + int e; + uint32_t c; + const char* end; + + char buf2[8] = {char(0xc0), char(0xA4)}; + end = fmt::detail::utf8_decode(buf2, &c, &e); + EXPECT_NE(e, 0); // non-canonical len 2 + EXPECT_EQ(end, buf2 + 2); // non-canonical recover 2 + + char buf3[8] = {char(0xe0), char(0x80), char(0xA4)}; + end = fmt::detail::utf8_decode(buf3, &c, &e); + EXPECT_NE(e, 0); // non-canonical len 3 + EXPECT_EQ(end, buf3 + 3); // non-canonical recover 3 + + char buf4[8] = {char(0xf0), char(0x80), char(0x80), char(0xA4)}; + end = fmt::detail::utf8_decode(buf4, &c, &e); + EXPECT_NE(e, 0); // non-canonical encoding len 4 + EXPECT_EQ(end, buf4 + 4); // non-canonical recover 4 +} + +// Let's try some bogus byte sequences +TEST(format_impl_test, utf8_decode_bogus_byte_sequences) { + int e; + uint32_t c; + + // Invalid first byte + char buf0[4] = {char(0xff)}; + auto len = fmt::detail::utf8_decode(buf0, &c, &e) - buf0; + EXPECT_NE(e, 0); // "bogus [ff] 0x%02x U+%04lx", e, (unsigned long)c); + EXPECT_EQ(len, 1); // "bogus [ff] recovery %d", len); + + // Invalid first byte + char buf1[4] = {char(0x80)}; + len = fmt::detail::utf8_decode(buf1, &c, &e) - buf1; + EXPECT_NE(e, 0); // "bogus [80] 0x%02x U+%04lx", e, (unsigned long)c); + EXPECT_EQ(len, 1); // "bogus [80] recovery %d", len); + + // Looks like a two-byte sequence but second byte is wrong + char buf2[4] = {char(0xc0), char(0x0a)}; + len = fmt::detail::utf8_decode(buf2, &c, &e) - buf2; + EXPECT_NE(e, 0); // "bogus [c0 0a] 0x%02x U+%04lx", e, (unsigned long)c + EXPECT_EQ(len, 2); // "bogus [c0 0a] recovery %d", len); +} + +TEST(format_impl_test, to_utf8) { + auto s = std::string("ёжик"); + auto u = fmt::detail::to_utf8<wchar_t>(L"\x0451\x0436\x0438\x043A"); + EXPECT_EQ(s, u.str()); + EXPECT_EQ(s.size(), u.size()); +} diff --git a/externals/fmt/test/format-test.cc b/externals/fmt/test/format-test.cc index 45a92624..dd388171 100644 --- a/externals/fmt/test/format-test.cc +++ b/externals/fmt/test/format-test.cc @@ -59,6 +59,8 @@ TEST(uint128_test, shift) { EXPECT_EQ(static_cast<uint64_t>(n), 0x8000000000000000); n = n >> 62; EXPECT_EQ(static_cast<uint64_t>(n), 42); + EXPECT_EQ(uint128_fallback(1) << 112, uint128_fallback(0x1000000000000, 0)); + EXPECT_EQ(uint128_fallback(0x1000000000000, 0) >> 112, uint128_fallback(1)); } TEST(uint128_test, minus) { @@ -99,7 +101,7 @@ template <typename Float> void check_isfinite() { TEST(float_test, isfinite) { check_isfinite<double>(); -#ifdef __SIZEOF_FLOAT128__ +#if FMT_USE_FLOAT128 check_isfinite<fmt::detail::float128>(); #endif } @@ -120,7 +122,7 @@ template <typename Float> void check_isnan() { TEST(float_test, isnan) { check_isnan<double>(); -#ifdef __SIZEOF_FLOAT128__ +#if FMT_USE_FLOAT128 check_isnan<fmt::detail::float128>(); #endif } @@ -234,7 +236,7 @@ TEST(util_test, format_system_error) { throws_on_alloc = true; } if (!throws_on_alloc) { - fmt::print("warning: std::allocator allocates {} chars", max_size); + fmt::print("warning: std::allocator allocates {} chars\n", max_size); return; } } @@ -644,7 +646,7 @@ TEST(format_test, fill) { fmt::format(string_view("{:\0>4}", 6), '*')); EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42)); EXPECT_THROW_MSG((void)fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0), - format_error, "invalid type specifier"); + format_error, "invalid format specifier"); } TEST(format_test, plus_sign) { @@ -652,27 +654,25 @@ TEST(format_test, plus_sign) { EXPECT_EQ("-42", fmt::format("{0:+}", -42)); EXPECT_EQ("+42", fmt::format("{0:+}", 42)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42u), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ("+42", fmt::format("{0:+}", 42l)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ul), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ("+42", fmt::format("{0:+}", 42ll)); #if FMT_USE_INT128 EXPECT_EQ("+42", fmt::format("{0:+}", __int128_t(42))); #endif EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ull), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0)); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l)); - 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, - "invalid format specifier for char"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), "abc"), format_error, - "format specifier requires numeric argument"); + "invalid format specifier"); EXPECT_THROW_MSG( (void)fmt::format(runtime("{0:+}"), reinterpret_cast<void*>(0x42)), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); } TEST(format_test, minus_sign) { @@ -680,24 +680,22 @@ TEST(format_test, minus_sign) { EXPECT_EQ("-42", fmt::format("{0:-}", -42)); EXPECT_EQ("42", fmt::format("{0:-}", 42)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42u), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ("42", fmt::format("{0:-}", 42l)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ul), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ("42", fmt::format("{0:-}", 42ll)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ull), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ("42", fmt::format("{0:-}", 42.0)); EXPECT_EQ("42", fmt::format("{0:-}", 42.0l)); - 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, - "invalid format specifier for char"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), "abc"), format_error, - "format specifier requires numeric argument"); + "invalid format specifier"); EXPECT_THROW_MSG( (void)fmt::format(runtime("{0:-}"), reinterpret_cast<void*>(0x42)), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); } TEST(format_test, space_sign) { @@ -705,24 +703,22 @@ TEST(format_test, space_sign) { EXPECT_EQ("-42", fmt::format("{0: }", -42)); EXPECT_EQ(" 42", fmt::format("{0: }", 42)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42u), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ(" 42", fmt::format("{0: }", 42l)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ul), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ(" 42", fmt::format("{0: }", 42ll)); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ull), format_error, - "format specifier requires signed argument"); + "invalid format specifier"); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0)); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l)); - 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, - "invalid format specifier for char"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), "abc"), format_error, - "format specifier requires numeric argument"); + "invalid format specifier"); EXPECT_THROW_MSG( (void)fmt::format(runtime("{0: }"), reinterpret_cast<void*>(0x42)), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); } TEST(format_test, hash_flag) { @@ -759,8 +755,8 @@ TEST(format_test, hash_flag) { EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ull)); EXPECT_EQ("042", fmt::format("{0:#o}", 042ull)); - EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0)); - EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0l)); + 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)); @@ -770,10 +766,10 @@ TEST(format_test, hash_flag) { EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error, "invalid format specifier for char"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error, - "format specifier requires numeric argument"); + "invalid format specifier"); EXPECT_THROW_MSG( (void)fmt::format(runtime("{0:#}"), reinterpret_cast<void*>(0x42)), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); } TEST(format_test, zero_flag) { @@ -797,6 +793,17 @@ TEST(format_test, zero_flag) { format_error, "format specifier requires numeric argument"); } +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)); +} + TEST(format_test, width) { char format_str[buffer_size]; safe_sprintf(format_str, "{0:%u", UINT_MAX); @@ -829,9 +836,9 @@ TEST(format_test, width) { EXPECT_EQ("str ", fmt::format("{0:12}", "str")); EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**"); EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**"); - EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.0"); + EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42."); EXPECT_EQ(fmt::format("{:6c}", static_cast<int>('x')), "x "); - EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), "000000"); + EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0"); } TEST(format_test, runtime_width) { @@ -893,60 +900,61 @@ TEST(format_test, runtime_width) { 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("{:{}}", 42, short(4)), " 42"); } TEST(format_test, precision) { 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, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.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, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.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, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.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, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error, "number is too big"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0), format_error, - "missing precision specifier"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0), format_error, - "missing precision specifier"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error, + "invalid precision"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error, + "invalid precision"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42u), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42u), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42l), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42l), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ul), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ul), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ll), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ll), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ull), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ull), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.0}"), 'x'), format_error, - "precision not allowed for this argument type"); + "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)); @@ -1025,10 +1033,10 @@ TEST(format_test, precision) { EXPECT_THROW_MSG( (void)fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)), - format_error, "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG( (void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)), - format_error, "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0, fmt::detail::max_value<int>()), format_error, "number is too big"); @@ -1044,97 +1052,86 @@ TEST(format_test, runtime_precision) { char format_str[buffer_size]; safe_sprintf(format_str, "{0:.{%u", UINT_MAX); increment(format_str + 5); - EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.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, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.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, + EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0.0), format_error, "argument not found"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0.0), format_error, "invalid format string"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0.0), format_error, "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0.0), format_error, "invalid format string"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}"), 0, 0), format_error, - "precision not allowed for this argument type"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0), format_error, + "invalid format specifier"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0), format_error, "argument not found"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0), format_error, + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0.0), format_error, "invalid format string"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1), format_error, - "negative precision"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1), + format_error, "negative precision"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)), format_error, "number is too big"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error, - "negative precision"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l), + format_error, "negative precision"); if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { long value = INT_MAX; - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (value + 1)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)), format_error, "number is too big"); } - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)), + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)), format_error, "number is too big"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error, - "precision is not integer"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error, - "precision is not integer"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'), + format_error, "precision is not integer"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0), + format_error, "precision is not integer"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42u, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error, - "precision not allowed for this argument type"); + "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42l, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ul, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ul, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ll, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ll, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ull, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ull, 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.{1}}"), 'x', 0), - format_error, - "precision not allowed for this argument type"); + 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_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), reinterpret_cast<void*>(0xcafe), 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), reinterpret_cast<void*>(0xcafe), 2), - format_error, - "precision not allowed for this argument type"); + format_error, "invalid format specifier"); EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2)); } @@ -1164,7 +1161,7 @@ void check_unknown_types(const T& value, const char* types, const char*) { char c = static_cast<char>(i); if (std::strchr(types, c) || std::strchr(special, c) || !c) continue; safe_sprintf(format_str, "{0:10%c}", c); - const char* message = "invalid type specifier"; + const char* message = "invalid format specifier"; EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), value), format_error, message) << format_str << " " << message; @@ -1173,7 +1170,7 @@ void check_unknown_types(const T& value, const char* types, const char*) { TEST(format_test, format_int) { EXPECT_THROW_MSG((void)fmt::format(runtime("{0:v"), 42), format_error, - "invalid type specifier"); + "invalid format specifier"); check_unknown_types(42, "bBdoxXnLc", "integer"); EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x'))); } @@ -1202,6 +1199,7 @@ constexpr auto uint128_max = ~static_cast<__uint128_t>(0); 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)); @@ -1340,16 +1338,59 @@ TEST(format_test, format_double) { EXPECT_EQ(fmt::format("{:f}", 392.65), "392.650000"); EXPECT_EQ(fmt::format("{:F}", 392.65), "392.650000"); EXPECT_EQ(fmt::format("{:L}", 42.0), "42"); + EXPECT_EQ(fmt::format("{:24a}", 4.2f), " 0x1.0cccccp+2"); EXPECT_EQ(fmt::format("{:24a}", 4.2), " 0x1.0cccccccccccdp+2"); EXPECT_EQ(fmt::format("{:<24a}", 4.2), "0x1.0cccccccccccdp+2 "); 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]; - safe_sprintf(buffer, "%a", -42.0); - EXPECT_EQ(fmt::format("{:a}", -42.0), buffer); - safe_sprintf(buffer, "%A", -42.0); - EXPECT_EQ(fmt::format("{:A}", -42.0), buffer); + +#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); + + if (std::numeric_limits<long double>::digits == 64) { + auto ld = 0xf.ffffffffffp-3l; + EXPECT_EQ(fmt::format("{:a}", ld), "0xf.ffffffffffp-3"); + EXPECT_EQ(fmt::format("{:.10a}", ld), "0xf.ffffffffffp-3"); + EXPECT_EQ(fmt::format("{:.9a}", ld), "0x1.000000000p+1"); + } +#endif + + if (fmt::detail::const_check(std::numeric_limits<double>::is_iec559)) { + double d = (std::numeric_limits<double>::min)(); + EXPECT_EQ(fmt::format("{:a}", d), "0x1p-1022"); + 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); + + d = std::numeric_limits<double>::denorm_min(); + EXPECT_EQ(fmt::format("{:a}", d), "0x0.0000000000001p-1022"); + } + + if (std::numeric_limits<long double>::digits == 64) { + auto ld = (std::numeric_limits<long double>::min)(); + EXPECT_EQ(fmt::format("{:a}", ld), "0x8p-16385"); + + ld = (std::numeric_limits<long double>::max)(); + EXPECT_EQ(fmt::format("{:a}", ld), "0xf.fffffffffffffffp+16380"); + + ld = std::numeric_limits<long double>::denorm_min(); + 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("{:a}", -42.0), "-0x1.5p+5"); + EXPECT_EQ(fmt::format("{:A}", -42.0), "-0X1.5P+5"); + EXPECT_EQ(fmt::format("{:f}", 9223372036854775807.0), "9223372036854775808.000000"); } @@ -1372,9 +1413,6 @@ TEST(format_test, precision_rounding) { EXPECT_EQ("1.9156918820264798e-56", fmt::format("{}", 1.9156918820264798e-56)); EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15)); - - // Trigger a rounding error in Grisu by a specially chosen number. - EXPECT_EQ("3788512123356.985352", fmt::format("{:f}", 3788512123356.985352)); } TEST(format_test, prettify_float) { @@ -1389,7 +1427,6 @@ TEST(format_test, prettify_float) { 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("0.10000000149011612", fmt::format("{}", double(0.1f))); EXPECT_EQ("1.3563156e-19", fmt::format("{}", 1.35631564e-19f)); } @@ -1434,6 +1471,8 @@ TEST(format_test, format_infinity) { 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)); @@ -1443,8 +1482,14 @@ TEST(format_test, format_long_double) { 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)); - safe_sprintf(buffer, "%La", 3.31l); - EXPECT_EQ(buffer, fmt::format("{:a}", 3.31l)); + + auto ld = 3.31l; + if (fmt::detail::is_double_double<decltype(ld)>::value) { + safe_sprintf(buffer, "%a", static_cast<double>(ld)); + EXPECT_EQ(buffer, fmt::format("{:a}", ld)); + } else if (std::numeric_limits<long double>::digits == 64) { + EXPECT_EQ(fmt::format("{:a}", ld), "0xd.3d70a3d70a3d70ap-2"); + } } TEST(format_test, format_char) { @@ -1463,6 +1508,7 @@ TEST(format_test, format_char) { EXPECT_EQ("\n", fmt::format("{}", '\n')); EXPECT_EQ("'\\n'", fmt::format("{:?}", '\n')); + EXPECT_EQ("ff", fmt::format("{:x}", '\xff')); } TEST(format_test, format_volatile_char) { @@ -1504,6 +1550,12 @@ TEST(format_test, format_pointer) { std::unique_ptr<int> up(new int(1)); EXPECT_EQ(fmt::format("{}", fmt::ptr(up.get())), fmt::format("{}", fmt::ptr(up))); + struct custom_deleter { + void operator()(int* p) const { delete p; } + }; + std::unique_ptr<int, custom_deleter> upcd(new int(1)); + EXPECT_EQ(fmt::format("{}", fmt::ptr(upcd.get())), + fmt::format("{}", fmt::ptr(upcd))); std::shared_ptr<int> sp(new int(1)); EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())), fmt::format("{}", fmt::ptr(sp))); @@ -1578,7 +1630,7 @@ struct fmt::formatter<explicitly_convertible_to_std_string_view> : formatter<std::string_view> { auto format(explicitly_convertible_to_std_string_view v, format_context& ctx) -> decltype(ctx.out()) { - return format_to(ctx.out(), "'{}'", std::string_view(v)); + return fmt::format_to(ctx.out(), "'{}'", std::string_view(v)); } }; @@ -1588,27 +1640,6 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) { } #endif -struct converible_to_anything { - template <typename T> operator T() const { return T(); } -}; - -FMT_BEGIN_NAMESPACE -template <> struct formatter<converible_to_anything> { - FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } - - auto format(converible_to_anything, format_context& ctx) - -> decltype(ctx.out()) { - return format_to(ctx.out(), "foo"); - } -}; -FMT_END_NAMESPACE - -TEST(format_test, format_convertible_to_anything) { - EXPECT_EQ("foo", fmt::format("{}", converible_to_anything())); -} - class Answer {}; FMT_BEGIN_NAMESPACE @@ -1621,7 +1652,8 @@ template <> struct formatter<date> { } auto format(const date& d, format_context& ctx) -> decltype(ctx.out()) { - format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day()); + // Namespace-qualify to avoid ambiguity with std::format_to. + fmt::format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day()); return ctx.out(); } }; @@ -1643,8 +1675,7 @@ TEST(format_test, format_custom) { TEST(format_test, format_to_custom) { char buf[10] = {}; - auto end = - &*fmt::format_to(fmt::detail::make_checked(buf, 10), "{}", Answer()); + auto end = fmt::format_to(buf, "{}", Answer()); EXPECT_EQ(end, buf + 2); EXPECT_STREQ(buf, "42"); } @@ -1662,7 +1693,7 @@ TEST(format_test, format_examples) { EXPECT_EQ("42", fmt::format("{}", 42)); memory_buffer out; - format_to(std::back_inserter(out), "The answer is {}.", 42); + fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); EXPECT_EQ("The answer is 42.", to_string(out)); const char* filename = "nonexistent"; @@ -1714,7 +1745,7 @@ TEST(format_test, format_examples) { EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42)); EXPECT_THROW_MSG( (void)fmt::format(runtime("The answer is {:d}"), "forty-two"), - format_error, "invalid type specifier"); + format_error, "invalid format specifier"); EXPECT_WRITE( stdout, fmt::print("{}", std::numeric_limits<double>::infinity()), "inf"); @@ -1724,26 +1755,15 @@ TEST(format_test, print) { EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!"); EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"), "Don't panic!"); + EXPECT_WRITE(stdout, fmt::println("Don't {}!", "panic"), "Don't panic!\n"); + EXPECT_WRITE(stderr, fmt::println(stderr, "Don't {}!", "panic"), + "Don't panic!\n"); } TEST(format_test, variadic) { EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1)); } -TEST(format_test, dynamic) { - using ctx = fmt::format_context; - auto args = std::vector<fmt::basic_format_arg<ctx>>(); - args.emplace_back(fmt::detail::make_arg<ctx>(42)); - args.emplace_back(fmt::detail::make_arg<ctx>("abc1")); - args.emplace_back(fmt::detail::make_arg<ctx>(1.5f)); - - std::string result = fmt::vformat( - "{} and {} and {}", - fmt::format_args(args.data(), static_cast<int>(args.size()))); - - EXPECT_EQ("42 and abc1 and 1.5", result); -} - TEST(format_test, bytes) { auto s = fmt::format("{:10}", fmt::bytes("ёжик")); EXPECT_EQ("ёжик ", s); @@ -1786,12 +1806,6 @@ TEST(format_test, join) { } #ifdef __cpp_lib_byte -TEST(format_test, format_byte) { - using arg_mapper = fmt::detail::arg_mapper<fmt::format_context>; - EXPECT_EQ(arg_mapper().map(std::byte(42)), 42); - EXPECT_EQ(fmt::format("{}", std::byte(42)), "42"); -} - TEST(format_test, join_bytes) { auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)}; EXPECT_EQ(fmt::format("{}", fmt::join(v, ", ")), "1, 2, 3"); @@ -1800,7 +1814,7 @@ TEST(format_test, join_bytes) { std::string vformat_message(int id, const char* format, fmt::format_args args) { auto buffer = fmt::memory_buffer(); - format_to(fmt::appender(buffer), "[{}] ", id); + fmt::format_to(fmt::appender(buffer), "[{}] ", id); vformat_to(fmt::appender(buffer), format, args); return to_string(buffer); } @@ -1829,9 +1843,6 @@ TEST(format_test, unpacked_args) { 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g')); } -struct string_like {}; -fmt::string_view to_string_view(string_like) { return "foo"; } - constexpr char with_null[3] = {'{', '}', '\0'}; constexpr char no_null[2] = {'{', '}'}; static constexpr const char static_with_null[3] = {'{', '}', '\0'}; @@ -1840,7 +1851,6 @@ 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("foo", fmt::format(FMT_STRING("{}"), string_like())); #if FMT_USE_NONTYPE_TEMPLATE_ARGS using namespace fmt::literals; @@ -1849,6 +1859,7 @@ TEST(format_test, compile_time_string) { 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)); #endif (void)static_with_null; @@ -1914,46 +1925,6 @@ TEST(format_test, non_null_terminated_format_string) { EXPECT_EQ("42", fmt::format(string_view("{}foo", 2), 42)); } -struct variant { - enum { int_type, string_type } type; - explicit variant(int) : type(int_type) {} - explicit variant(const char*) : type(string_type) {} -}; - -FMT_BEGIN_NAMESPACE -template <> struct formatter<variant> : dynamic_formatter<> { - auto format(variant value, format_context& ctx) -> decltype(ctx.out()) { - if (value.type == variant::int_type) - return dynamic_formatter<>::format(42, ctx); - return dynamic_formatter<>::format("foo", ctx); - } -}; -FMT_END_NAMESPACE - -TEST(format_test, dynamic_formatter) { - auto num = variant(42); - auto str = variant("foo"); - EXPECT_EQ("42", fmt::format("{:d}", num)); - EXPECT_EQ("foo", fmt::format("{:s}", str)); - EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4)); - EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{}}"), num), format_error, - "cannot switch from manual to automatic argument indexing"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{:{0}}"), num), format_error, - "cannot switch from automatic to manual argument indexing"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{:+}"), str), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{:-}"), str), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{: }"), str), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{:#}"), str), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{:0}"), str), format_error, - "format specifier requires numeric argument"); - EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2}"), num), format_error, - "precision not allowed for this argument type"); -} - namespace adl_test { namespace fmt { namespace detail { @@ -1966,30 +1937,26 @@ template <typename, typename OutputIt> void write(OutputIt, foo) = delete; FMT_BEGIN_NAMESPACE template <> struct formatter<adl_test::fmt::detail::foo> : formatter<std::string> { - template <typename FormatContext> - auto format(adl_test::fmt::detail::foo, FormatContext& ctx) + auto format(adl_test::fmt::detail::foo, format_context& ctx) -> decltype(ctx.out()) { return formatter<std::string>::format("foo", ctx); } }; FMT_END_NAMESPACE -struct convertible_to_int { - operator int() const { return value; } - - int value = 42; -}; - TEST(format_test, to_string) { EXPECT_EQ(fmt::to_string(42), "42"); EXPECT_EQ(fmt::to_string(reinterpret_cast<void*>(0x1234)), "0x1234"); EXPECT_EQ(fmt::to_string(adl_test::fmt::detail::foo()), "foo"); - EXPECT_EQ(fmt::to_string(convertible_to_int()), "42"); EXPECT_EQ(fmt::to_string(foo), "0"); #if FMT_USE_FLOAT128 EXPECT_EQ(fmt::to_string(__float128(0.5)), "0.5"); #endif + +#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L + EXPECT_EQ(fmt::to_string(std::string_view()), ""); +#endif } TEST(format_test, output_iterators) { @@ -2003,6 +1970,7 @@ TEST(format_test, output_iterators) { TEST(format_test, formatted_size) { EXPECT_EQ(2u, fmt::formatted_size("{}", 42)); + EXPECT_EQ(2u, fmt::formatted_size(std::locale(), "{}", 42)); } TEST(format_test, format_to_no_args) { @@ -2105,119 +2073,10 @@ TEST(format_test, format_to_n_output_iterator) { EXPECT_STREQ(buf, "42"); } -#if FMT_USE_CONSTEXPR -struct test_error_handler { - const char*& error; - - FMT_CONSTEXPR test_error_handler(const char*& err) : error(err) {} - - FMT_CONSTEXPR test_error_handler(const test_error_handler& other) - : error(other.error) {} - - FMT_CONSTEXPR void on_error(const char* message) { - if (!error) error = message; - } -}; - -FMT_CONSTEXPR size_t len(const char* s) { - size_t len = 0; - while (*s++) ++len; - return len; -} - -FMT_CONSTEXPR bool equal(const char* s1, const char* s2) { - if (!s1 || !s2) return s1 == s2; - while (*s1 && *s1 == *s2) { - ++s1; - ++s2; - } - return *s1 == *s2; -} - -template <typename... Args> -FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) { - const char* actual_error = nullptr; - auto s = string_view(fmt, len(fmt)); - auto checker = - fmt::detail::format_string_checker<char, test_error_handler, Args...>( - s, test_error_handler(actual_error)); - fmt::detail::parse_format_string<true>(s, checker); - return equal(actual_error, expected_error); -} - -# define EXPECT_ERROR_NOARGS(fmt, error) \ - static_assert(test_error(fmt, error), "") -# define EXPECT_ERROR(fmt, error, ...) \ - static_assert(test_error<__VA_ARGS__>(fmt, error), "") - -TEST(format_test, format_string_errors) { - EXPECT_ERROR_NOARGS("foo", nullptr); - EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string"); - EXPECT_ERROR("{0:s", "unknown format specifier", date); -# if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1916 - // This causes an detail compiler error in MSVC2017. - EXPECT_ERROR("{:{<}", "invalid fill character '{'", int); - EXPECT_ERROR("{:10000000000}", "number is too big", int); - EXPECT_ERROR("{:.10000000000}", "number is too big", int); - EXPECT_ERROR_NOARGS("{:x}", "argument not found"); - EXPECT_ERROR("{:+}", "format specifier requires numeric argument", - const char*); - EXPECT_ERROR("{:-}", "format specifier requires numeric argument", - const char*); - EXPECT_ERROR("{:#}", "format specifier requires numeric argument", - const char*); - EXPECT_ERROR("{: }", "format specifier requires numeric argument", - const char*); - EXPECT_ERROR("{:0}", "format specifier requires numeric argument", - const char*); - EXPECT_ERROR("{:+}", "format specifier requires signed argument", unsigned); - EXPECT_ERROR("{:-}", "format specifier requires signed argument", unsigned); - EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned); - EXPECT_ERROR("{:{}}", "argument not found", int); - EXPECT_ERROR("{:.{}}", "argument not found", double); - EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int); - EXPECT_ERROR("{:s}", "invalid type specifier", int); - EXPECT_ERROR("{:s}", "invalid type specifier", char); - EXPECT_ERROR("{:+}", "invalid format specifier for char", char); - EXPECT_ERROR("{:s}", "invalid type specifier", double); - EXPECT_ERROR("{:d}", "invalid type specifier", const char*); - EXPECT_ERROR("{:d}", "invalid type specifier", std::string); - EXPECT_ERROR("{:s}", "invalid type specifier", void*); -# else - fmt::print("warning: constexpr is broken in this version of MSVC\n"); -# endif -# if FMT_USE_NONTYPE_TEMPLATE_ARGS - using namespace fmt::literals; - EXPECT_ERROR("{foo}", "named argument is not found", decltype("bar"_a = 42)); - EXPECT_ERROR("{foo}", "named argument is not found", - decltype(fmt::arg("foo", 42))); -# else - EXPECT_ERROR("{foo}", - "compile-time checks for named arguments require C++20 support", - int); -# endif - EXPECT_ERROR_NOARGS("{10000000000}", "argument not found"); - EXPECT_ERROR_NOARGS("{0x}", "invalid format string"); - EXPECT_ERROR_NOARGS("{-}", "invalid format string"); - EXPECT_ERROR("{:{0x}}", "invalid format string", int); - EXPECT_ERROR("{:{-}}", "invalid format string", int); - EXPECT_ERROR("{:.{0x}}", "invalid format string", int); - EXPECT_ERROR("{:.{-}}", "invalid format string", int); - EXPECT_ERROR("{:.x}", "missing precision specifier", int); - EXPECT_ERROR_NOARGS("{}", "argument not found"); - EXPECT_ERROR("{1}", "argument not found", int); - EXPECT_ERROR("{1}{}", - "cannot switch from manual to automatic argument indexing", int, - int); - EXPECT_ERROR("{}{1}", - "cannot switch from automatic to manual argument indexing", int, - int); -} - TEST(format_test, vformat_to) { using context = fmt::format_context; - fmt::basic_format_arg<context> arg = fmt::detail::make_arg<context>(42); - auto args = fmt::basic_format_args<context>(&arg, 1); + int n = 42; + auto args = fmt::make_format_args<context>(n); auto s = std::string(); fmt::vformat_to(std::back_inserter(s), "{}", args); EXPECT_EQ("42", s); @@ -2226,8 +2085,6 @@ TEST(format_test, vformat_to) { EXPECT_EQ("42", s); } -#endif // FMT_USE_CONSTEXPR - TEST(format_test, char_traits_is_not_ambiguous) { // Test that we don't inject detail names into the std namespace. using namespace std; @@ -2244,7 +2101,7 @@ struct check_back_appender {}; FMT_BEGIN_NAMESPACE template <> struct formatter<check_back_appender> { - auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } @@ -2263,6 +2120,34 @@ TEST(format_test, back_insert_slicing) { EXPECT_EQ(fmt::format("{}", check_back_appender{}), "y"); } +namespace test { +enum class scoped_enum_as_int {}; +auto format_as(scoped_enum_as_int) -> int { return 42; } + +enum class scoped_enum_as_string_view {}; +auto format_as(scoped_enum_as_string_view) -> fmt::string_view { return "foo"; } + +enum class scoped_enum_as_string {}; +auto format_as(scoped_enum_as_string) -> std::string { return "foo"; } + +struct struct_as_int {}; +auto format_as(struct_as_int) -> int { return 42; } +} // namespace test + +TEST(format_test, format_as) { + EXPECT_EQ(fmt::format("{}", test::scoped_enum_as_int()), "42"); + 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"); +} + +TEST(format_test, format_as_to_string) { + EXPECT_EQ(fmt::to_string(test::scoped_enum_as_int()), "42"); + EXPECT_EQ(fmt::to_string(test::scoped_enum_as_string_view()), "foo"); + EXPECT_EQ(fmt::to_string(test::scoped_enum_as_string()), "foo"); + EXPECT_EQ(fmt::to_string(test::struct_as_int()), "42"); +} + template <typename Char, typename T> bool check_enabled_formatter() { static_assert(std::is_default_constructible<fmt::formatter<T, Char>>::value, ""); @@ -2305,3 +2190,78 @@ TEST(format_int_test, format_int) { os << max_value<int64_t>(); EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str()); } + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + +# include <locale> + +class format_facet : public fmt::format_facet<std::locale> { + protected: + struct int_formatter { + fmt::appender out; + + template <typename T, FMT_ENABLE_IF(fmt::detail::is_integer<T>::value)> + auto operator()(T value) -> bool { + fmt::format_to(out, "[{}]", value); + return true; + } + + template <typename T, FMT_ENABLE_IF(!fmt::detail::is_integer<T>::value)> + auto operator()(T) -> bool { + return false; + } + }; + + auto do_put(fmt::appender out, fmt::loc_value val, + const fmt::format_specs<>&) const -> bool override; +}; + +auto format_facet::do_put(fmt::appender out, fmt::loc_value val, + const fmt::format_specs<>&) const -> bool { + return val.visit(int_formatter{out}); +} + +TEST(format_test, format_facet) { + auto loc = std::locale(std::locale(), new format_facet()); + EXPECT_EQ(fmt::format(loc, "{:L}", 42), "[42]"); + EXPECT_EQ(fmt::format(loc, "{:L}", -42), "[-42]"); +} + +TEST(format_test, format_facet_separator) { + // U+2019 RIGHT SINGLE QUOTATION MARK is a digit separator in the de_CH + // locale. + auto loc = + std::locale({}, new fmt::format_facet<std::locale>("\xe2\x80\x99")); + EXPECT_EQ(fmt::format(loc, "{:L}", 1000), + "1\xe2\x80\x99" + "000"); +} + +TEST(format_test, format_facet_grouping) { + auto loc = + std::locale({}, new fmt::format_facet<std::locale>(",", {1, 2, 3})); + EXPECT_EQ(fmt::format(loc, "{:L}", 1234567890), "1,234,567,89,0"); +} + +TEST(format_test, format_named_arg_with_locale) { + EXPECT_EQ(fmt::format(std::locale(), "{answer}", fmt::arg("answer", 42)), + "42"); +} + +#endif // FMT_STATIC_THOUSANDS_SEPARATOR + +struct convertible_to_nonconst_cstring { + operator char*() const { + static char c[] = "bar"; + return c; + } +}; + +FMT_BEGIN_NAMESPACE +template <> +struct formatter<convertible_to_nonconst_cstring> : formatter<char*> {}; +FMT_END_NAMESPACE + +TEST(format_test, formatter_nonconst_char) { + EXPECT_EQ(fmt::format("{}", convertible_to_nonconst_cstring()), "bar"); +} diff --git a/externals/fmt/test/fuzzing/CMakeLists.txt b/externals/fmt/test/fuzzing/CMakeLists.txt index 0280c5cd..afcf9df1 100644 --- a/externals/fmt/test/fuzzing/CMakeLists.txt +++ b/externals/fmt/test/fuzzing/CMakeLists.txt @@ -22,7 +22,7 @@ function(add_fuzzer source) if (FMT_FUZZ_LDFLAGS) target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS}) endif () - target_compile_features(${name} PRIVATE cxx_generic_lambdas) + target_compile_features(${name} PRIVATE cxx_std_14) endfunction() foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc) diff --git a/externals/fmt/test/gtest-extra-test.cc b/externals/fmt/test/gtest-extra-test.cc index 69e42bc6..42340a2d 100644 --- a/externals/fmt/test/gtest-extra-test.cc +++ b/externals/fmt/test/gtest-extra-test.cc @@ -201,6 +201,7 @@ TEST(gtest_extra_test, expect_write_streaming) { // EXPECT_THROW_MSG macro. TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) { int n = 0; + (void)n; using std::runtime_error; EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, ""); EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), ""); @@ -213,6 +214,7 @@ TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) { // EXPECT_SYSTEM_ERROR macro. TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) { int n = 0; + (void)n; EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test"); EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), ""); EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), ""); diff --git a/externals/fmt/test/gtest-extra.h b/externals/fmt/test/gtest-extra.h index ef2a04e7..03a07a2a 100644 --- a/externals/fmt/test/gtest-extra.h +++ b/externals/fmt/test/gtest-extra.h @@ -12,12 +12,7 @@ #include <string> -#ifdef FMT_MODULE_TEST -import fmt; -#else -# include "fmt/os.h" -#endif // FMG_MODULE_TEST - +#include "fmt/os.h" #include "gmock/gmock.h" #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ diff --git a/externals/fmt/test/gtest/CMakeLists.txt b/externals/fmt/test/gtest/CMakeLists.txt index ed0e59d5..0cccc61f 100644 --- a/externals/fmt/test/gtest/CMakeLists.txt +++ b/externals/fmt/test/gtest/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(gtest STATIC gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h) target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1) target_include_directories(gtest SYSTEM PUBLIC .) +target_compile_features(gtest PUBLIC cxx_std_11) find_package(Threads) if (Threads_FOUND) @@ -17,13 +18,6 @@ else () target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) endif () -# Workaround GTest bug https://github.com/google/googletest/issues/705. -fmt_check_cxx_compiler_flag( - -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) -if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) - target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks) -endif () - if (MSVC) # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) diff --git a/externals/fmt/test/gtest/gmock-gtest-all.cc b/externals/fmt/test/gtest/gmock-gtest-all.cc index b67316c3..7b33134f 100644 --- a/externals/fmt/test/gtest/gmock-gtest-all.cc +++ b/externals/fmt/test/gtest/gmock-gtest-all.cc @@ -2202,7 +2202,7 @@ bool UnitTestOptions::MatchesFilter(const std::string& name_str, // Check if this pattern matches name_str. if (PatternMatchesString(name_str, pattern, pattern_end)) { - return true; + break; } // Give up on this pattern. However, if we found a pattern separator (:), @@ -13778,7 +13778,7 @@ UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith( UntypedActionResultHolderBase* result = nullptr; - auto perform_action = [&] { + auto perform_action = [&, this] { return untyped_action == nullptr ? this->UntypedPerformDefaultAction(untyped_args, ss.str()) : this->UntypedPerformAction(untyped_action, untyped_args); diff --git a/externals/fmt/test/mock-allocator.h b/externals/fmt/test/mock-allocator.h index 6a67e08f..dc4942f1 100644 --- a/externals/fmt/test/mock-allocator.h +++ b/externals/fmt/test/mock-allocator.h @@ -20,8 +20,8 @@ template <typename T> class mock_allocator { mock_allocator() {} mock_allocator(const mock_allocator&) {} using value_type = T; - MOCK_METHOD1_T(allocate, T*(size_t n)); - MOCK_METHOD2_T(deallocate, void(T* p, size_t n)); + MOCK_METHOD(T*, allocate, (size_t)); + MOCK_METHOD(void, deallocate, (T*, size_t)); }; template <typename Allocator> class allocator_ref { diff --git a/externals/fmt/test/module-test.cc b/externals/fmt/test/module-test.cc index 62e5fe8b..bd9624f9 100644 --- a/externals/fmt/test/module-test.cc +++ b/externals/fmt/test/module-test.cc @@ -15,8 +15,6 @@ # define FMT_HIDE_MODULE_BUGS #endif -#define FMT_MODULE_TEST - #include <bit> #include <chrono> #include <exception> @@ -41,7 +39,6 @@ #else # define FMT_POSIX(call) call #endif -#define FMT_OS_H_ // don't pull in os.h directly or indirectly import fmt; @@ -53,13 +50,8 @@ static bool macro_leaked = false; #endif -// Include sources to pick up functions and classes from the module rather than -// from the non-modular library which is baked into the 'test-main' library. -// This averts linker problems: -// - strong ownership model: missing linker symbols -// - weak ownership model: duplicate linker symbols -#include "gtest-extra.cc" -#include "util.cc" +#define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly +#include "gtest-extra.h" // an implicitly exported namespace must be visible [module.interface]/2.2 TEST(module_test, namespace) { @@ -75,9 +67,8 @@ bool oops_detail_namespace_is_visible; namespace fmt { bool namespace_detail_invisible() { #if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ - ((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \ - (_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704)) - // bug in msvc up to 16.11.5 / 17.0-pre5: + _MSC_FULL_VER <= 193700000 + // bug in msvc up to at least 17.7: // the namespace is visible even when it is neither // implicitly nor explicitly exported @@ -111,7 +102,7 @@ TEST(module_test, macros) { // but rather visibility of all client-facing overloads, reachability of // non-exported entities, name lookup and overload resolution within // template instantitions. -// Excercise all exported entities of the API at least once. +// Exercise all exported entities of the API at least once. // Instantiate as many code paths as possible. TEST(module_test, to_string) { @@ -140,7 +131,7 @@ TEST(module_test, format_to) { EXPECT_EQ("42", std::string_view(buffer)); fmt::memory_buffer mb; - fmt::format_to(mb, "{}", 42); + fmt::format_to(std::back_inserter(mb), "{}", 42); EXPECT_EQ("42", std::string_view(buffer)); std::wstring w; @@ -152,7 +143,7 @@ TEST(module_test, format_to) { EXPECT_EQ(L"42", std::wstring_view(wbuffer)); fmt::wmemory_buffer wb; - fmt::format_to(wb, L"{}", 42); + fmt::format_to(std::back_inserter(wb), L"{}", 42); EXPECT_EQ(L"42", std::wstring_view(wbuffer)); } @@ -211,8 +202,8 @@ TEST(module_test, dynamic_format_args) { TEST(module_test, vformat) { EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42))); - EXPECT_EQ(L"42", fmt::vformat(fmt::to_string_view(L"{}"), - fmt::make_wformat_args(42))); + EXPECT_EQ(L"42", + fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(42))); } TEST(module_test, vformat_to) { @@ -245,9 +236,9 @@ TEST(module_test, vformat_to_n) { auto wstore = fmt::make_wformat_args(12345); std::wstring w; auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1, - fmt::to_string_view(L"{}"), wstore); + fmt::wstring_view(L"{}"), wstore); wchar_t wbuffer[4] = {0}; - fmt::vformat_to_n(wbuffer, 3, fmt::to_string_view(L"{:}"), wstore); + fmt::vformat_to_n(wbuffer, 3, fmt::wstring_view(L"{:}"), wstore); } std::string as_string(std::wstring_view text) { @@ -258,22 +249,18 @@ std::string as_string(std::wstring_view text) { TEST(module_test, print) { EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ"); EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ"); - if (false) { - EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ")); - EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ")); - } + EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ")); + EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ")); } TEST(module_test, vprint) { EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ"); EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)), "4.2"); - if (false) { - EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)), - as_string(L"42µ")); - EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)), - as_string(L"42")); - } + EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)), + as_string(L"42µ")); + EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)), + as_string(L"42")); } TEST(module_test, named_args) { @@ -284,9 +271,7 @@ TEST(module_test, named_args) { TEST(module_test, literals) { using namespace fmt::literals; EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = 42)); - EXPECT_EQ("42", "{}"_format(42)); EXPECT_EQ(L"42", fmt::format(L"{answer}", L"answer"_a = 42)); - EXPECT_EQ(L"42", L"{}"_format(42)); } TEST(module_test, locale) { @@ -320,7 +305,7 @@ TEST(module_test, string_view) { TEST(module_test, memory_buffer) { fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer; - fmt::format_to(buffer, "{}", "42"); + fmt::format_to(std::back_inserter(buffer), "{}", "42"); EXPECT_EQ("42", to_string(buffer)); fmt::memory_buffer nbuffer(std::move(buffer)); EXPECT_EQ("42", to_string(nbuffer)); @@ -395,27 +380,20 @@ struct test_formatter : fmt::formatter<char> { bool check() { return true; } }; -struct test_dynamic_formatter : fmt::dynamic_formatter<> { - bool check() { return true; } -}; - -TEST(module_test, formatter) { - EXPECT_TRUE(test_formatter{}.check()); - EXPECT_TRUE(test_dynamic_formatter{}.check()); -} +TEST(module_test, formatter) { EXPECT_TRUE(test_formatter{}.check()); } TEST(module_test, join) { int arr[3] = {1, 2, 3}; std::vector<double> vec{1.0, 2.0, 3.0}; std::initializer_list<int> il{1, 2, 3}; - auto sep = fmt::to_string_view(", "); + auto sep = fmt::string_view(", "); EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep))); - auto wsep = fmt::to_string_view(L", "); + auto wsep = fmt::wstring_view(L", "); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep))); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep))); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep))); @@ -426,7 +404,6 @@ TEST(module_test, time) { EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120); EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120); auto chrono_now = std::chrono::system_clock::now(); - EXPECT_TRUE(fmt::localtime(chrono_now).tm_year > 120); EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120); } @@ -453,34 +430,16 @@ TEST(module_test, weekday) { EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1))); } -TEST(module_test, to_string_view) { - using fmt::to_string_view; - fmt::string_view nsv{to_string_view("42")}; - EXPECT_EQ("42", nsv); - fmt::wstring_view wsv{to_string_view(L"42")}; - EXPECT_EQ(L"42", wsv); -} - TEST(module_test, printf) { EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456"); EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42"); - if (false) { - EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456), - as_string(L"42.123456")); - EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42")); - } + EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456), as_string(L"42.123456")); + EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42")); } TEST(module_test, fprintf) { EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42"); - std::ostringstream os; - fmt::fprintf(os, "%s", "bla"); - EXPECT_EQ("bla", os.str()); - EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42")); - std::wostringstream ws; - fmt::fprintf(ws, L"%s", L"bla"); - EXPECT_EQ(L"bla", ws.str()); } TEST(module_test, sprintf) { @@ -490,25 +449,15 @@ TEST(module_test, sprintf) { TEST(module_test, vprintf) { EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42"); - if (false) { - EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)), - as_string(L"42")); - } + EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)), + as_string(L"42")); } TEST(module_test, vfprintf) { auto args = fmt::make_printf_args(42); EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42"); - std::ostringstream os; - fmt::vfprintf(os, "%d", args); - EXPECT_EQ("42", os.str()); auto wargs = fmt::make_wprintf_args(42); - if (false) { - EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42")); - } - std::wostringstream ws; - fmt::vfprintf(ws, L"%d", wargs); - EXPECT_EQ(L"42", ws.str()); + EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42")); } TEST(module_test, vsprintf) { @@ -538,8 +487,13 @@ TEST(module_test, buffered_file) { } TEST(module_test, output_file) { +#ifdef __clang__ + fmt::println("\033[0;33m[=disabled=] {}\033[0;0m", + "Clang 16.0 emits multiple copies of vtables"); +#else fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1); out.close(); +#endif } struct custom_context { @@ -552,21 +506,15 @@ TEST(module_test, custom_context) { EXPECT_TRUE(!custom_arg); } -struct disabled_formatter {}; - -TEST(module_test, has_formatter) { - EXPECT_FALSE( - (fmt::has_formatter<disabled_formatter, fmt::format_context>::value)); -} - -TEST(module_test, is_formattable) { - EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value); -} - TEST(module_test, compile_format_string) { using namespace fmt::literals; +#ifdef __clang__ + fmt::println("\033[0;33m[=disabled=] {}\033[0;0m", + "Clang 16.0 fails to import user-defined literals"); +#else EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42)); EXPECT_EQ(L"42", fmt::format(L"{:}"_cf, 42)); EXPECT_EQ("4.2", fmt::format("{arg:3.1f}"_cf, "arg"_a = 4.2)); EXPECT_EQ(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42")); +#endif } diff --git a/externals/fmt/test/os-test.cc b/externals/fmt/test/os-test.cc index a1745c8c..1e6c7c4b 100644 --- a/externals/fmt/test/os-test.cc +++ b/externals/fmt/test/os-test.cc @@ -22,60 +22,6 @@ using wstring_view = fmt::basic_string_view<wchar_t>; # include <windows.h> -TEST(util_test, utf16_to_utf8) { - auto s = std::string("ёжик"); - fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A"); - EXPECT_EQ(s, u.str()); - EXPECT_EQ(s.size(), u.size()); -} - -TEST(util_test, utf16_to_utf8_empty_string) { - std::string s = ""; - fmt::detail::utf16_to_utf8 u(L""); - EXPECT_EQ(s, u.str()); - EXPECT_EQ(s.size(), u.size()); -} - -template <typename Converter, typename Char> -void check_utf_conversion_error(const char* message, - fmt::basic_string_view<Char> str = - fmt::basic_string_view<Char>(nullptr, 1)) { - fmt::memory_buffer out; - fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message); - auto error = std::system_error(std::error_code()); - try { - (Converter)(str); - } catch (const std::system_error& e) { - error = e; - } - EXPECT_EQ(ERROR_INVALID_PARAMETER, error.code().value()); - EXPECT_THAT(error.what(), HasSubstr(fmt::to_string(out))); -} - -TEST(util_test, utf16_to_utf8_error) { - check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>( - "cannot convert string from UTF-16 to UTF-8"); -} - -TEST(util_test, utf16_to_utf8_convert) { - fmt::detail::utf16_to_utf8 u; - EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(nullptr, 1))); - EXPECT_EQ(ERROR_INVALID_PARAMETER, - u.convert(wstring_view(L"foo", INT_MAX + 1u))); -} - -TEST(os_test, format_std_error_code) { - EXPECT_EQ("generic:42", - fmt::format(FMT_STRING("{0}"), - std::error_code(42, std::generic_category()))); - EXPECT_EQ("system:42", - fmt::format(FMT_STRING("{0}"), - std::error_code(42, fmt::system_category()))); - EXPECT_EQ("system:-42", - fmt::format(FMT_STRING("{0}"), - std::error_code(-42, fmt::system_category()))); -} - TEST(os_test, format_windows_error) { LPWSTR message = nullptr; auto result = FormatMessageW( @@ -83,7 +29,8 @@ TEST(os_test, format_windows_error) { FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&message), 0, nullptr); - fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2)); + auto utf8_message = + fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2)); LocalFree(message); fmt::memory_buffer actual_message; fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test"); @@ -108,7 +55,8 @@ TEST(os_test, format_long_windows_error) { LocalFree(message); return; } - fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2)); + auto utf8_message = + fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2)); LocalFree(message); fmt::memory_buffer actual_message; fmt::detail::format_windows_error(actual_message, provisioning_not_allowed, @@ -139,6 +87,17 @@ TEST(os_test, report_windows_error) { fmt::to_string(out)); } +# if FMT_USE_FCNTL && !defined(__MINGW32__) +TEST(file_test, open_windows_file) { + using fmt::file; + file out = file::open_windows_file(L"test-file", + file::WRONLY | file::CREATE | file::TRUNC); + out.write("x", 1); + file in = file::open_windows_file(L"test-file", file::RDONLY); + EXPECT_READ(in, "x"); +} +# endif // FMT_USE_FCNTL && !defined(__MINGW32__) + #endif // _WIN32 #if FMT_USE_FCNTL diff --git a/externals/fmt/test/ostream-test.cc b/externals/fmt/test/ostream-test.cc index a3a51d53..b2d15466 100644 --- a/externals/fmt/test/ostream-test.cc +++ b/externals/fmt/test/ostream-test.cc @@ -86,13 +86,13 @@ TEST(ostream_test, format_specs) { EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def"))); EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def"))); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()), - format_error, "format specifier requires numeric argument"); + format_error, "invalid format specifier"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()), format_error, "format specifier requires numeric argument"); EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test"))); @@ -106,9 +106,17 @@ TEST(ostream_test, empty_custom_output) { } TEST(ostream_test, print) { - std::ostringstream os; - fmt::print(os, "Don't {}!", "panic"); - EXPECT_EQ("Don't panic!", os.str()); + { + std::ostringstream os; + fmt::print(os, "Don't {}!", "panic"); + EXPECT_EQ("Don't panic!", os.str()); + } + + { + std::ostringstream os; + fmt::println(os, "Don't {}!", "panic"); + EXPECT_EQ("Don't panic!\n", os.str()); + } } TEST(ostream_test, write_to_ostream) { @@ -132,7 +140,7 @@ TEST(ostream_test, write_to_ostream_max_size) { } buffer(max_size); struct mock_streambuf : std::streambuf { - MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n)); + MOCK_METHOD(std::streamsize, xsputn, (const void*, std::streamsize)); auto xsputn(const char* s, std::streamsize n) -> std::streamsize override { const void* v = s; return xsputn(v, n); @@ -218,45 +226,6 @@ TEST(ostream_test, format_to_n) { EXPECT_EQ("xabx", fmt::string_view(buffer, 4)); } -template <typename T> struct convertible { - T value; - explicit convertible(const T& val) : value(val) {} - operator T() const { return value; } -}; - -TEST(ostream_test, disable_builtin_ostream_operators) { - EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42))); - EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo"))); -} - -struct streamable_and_convertible_to_bool { - operator bool() const { return true; } -}; - -std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) { - return os << "foo"; -} - -TEST(ostream_test, format_convertible_to_bool) { - // operator<< is intentionally not used because of potential ODR violations. - EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true"); -} - -struct streamable_and_convertible_to_string_view { - operator fmt::string_view() const { return "foo"; } -}; - -std::ostream& operator<<(std::ostream& os, - streamable_and_convertible_to_string_view) { - return os << "bar"; -} - -TEST(ostream_test, format_convertible_to_string_vew) { - // operator<< is intentionally not used because of potential ODR violations. - EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_string_view()), - "foo"); -} - struct copyfmt_test {}; std::ostream& operator<<(std::ostream& os, copyfmt_test) { diff --git a/externals/fmt/test/posix-mock-test.cc b/externals/fmt/test/posix-mock-test.cc index 819a94df..32078bf8 100644 --- a/externals/fmt/test/posix-mock-test.cc +++ b/externals/fmt/test/posix-mock-test.cc @@ -72,12 +72,6 @@ int test::open(const char* path, int oflag, int mode) { EMULATE_EINTR(open, -1); return ::open(path, oflag, mode); } -#else -errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag, - int pmode) { - EMULATE_EINTR(open, EINTR); - return _sopen_s(pfh, filename, oflag, shflag, pmode); -} #endif #ifndef _WIN32 @@ -220,11 +214,11 @@ TEST(os_test, getpagesize) { } TEST(file_test, open_retry) { +# ifndef _WIN32 write_file("temp", "there must be something here"); std::unique_ptr<file> f{nullptr}; EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open, "cannot open file temp"); -# ifndef _WIN32 char c = 0; f->read(&c, 1); # endif diff --git a/externals/fmt/test/posix-mock.h b/externals/fmt/test/posix-mock.h index e76af700..4f2a42c1 100644 --- a/externals/fmt/test/posix-mock.h +++ b/externals/fmt/test/posix-mock.h @@ -37,8 +37,6 @@ int fstat(int fd, struct stat* buf); #else typedef unsigned size_t; typedef int ssize_t; -errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag, - int pmode); #endif #ifndef _WIN32 diff --git a/externals/fmt/test/printf-test.cc b/externals/fmt/test/printf-test.cc index 383ffb83..81db9b23 100644 --- a/externals/fmt/test/printf-test.cc +++ b/externals/fmt/test/printf-test.cc @@ -225,10 +225,8 @@ TEST(printf_test, hash_flag) { EXPECT_PRINTF("-42.0000", "%#g", -42.0); EXPECT_PRINTF("-42.0000", "%#G", -42.0); - safe_sprintf(buffer, "%#a", 16.0); - EXPECT_PRINTF(buffer, "%#a", 16.0); - safe_sprintf(buffer, "%#A", 16.0); - EXPECT_PRINTF(buffer, "%#A", 16.0); + EXPECT_PRINTF("0x1.p+4", "%#a", 16.0); + EXPECT_PRINTF("0X1.P+4", "%#A", 16.0); // '#' flag is ignored for non-numeric types. EXPECT_PRINTF("x", "%#c", 'x'); @@ -239,7 +237,7 @@ TEST(printf_test, width) { // Width cannot be specified twice. EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error, - "invalid type specifier"); + "invalid format specifier"); EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error, "number is too big"); @@ -407,10 +405,7 @@ TEST(printf_test, length) { EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max); } -TEST(printf_test, bool) { - EXPECT_PRINTF("1", "%d", true); - EXPECT_PRINTF("true", "%s", true); -} +TEST(printf_test, bool) { EXPECT_PRINTF("1", "%d", true); } TEST(printf_test, int) { EXPECT_PRINTF("-42", "%d", -42); @@ -534,11 +529,12 @@ TEST(printf_test, wide_string) { } TEST(printf_test, vprintf) { - fmt::format_arg_store<fmt::printf_context, int> as{42}; - fmt::basic_format_args<fmt::printf_context> args(as); - EXPECT_EQ(fmt::vsprintf("%d", args), "42"); - EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42"); - EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42"); + int n = 42; + auto store = fmt::format_arg_store<fmt::printf_context, int>(n); + auto args = fmt::basic_format_args<fmt::printf_context>(store); + EXPECT_EQ(fmt::vsprintf(fmt::string_view("%d"), args), "42"); + EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args), + "42"); } template <typename... Args> @@ -554,31 +550,11 @@ TEST(printf_test, fixed_large_exponent) { EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21)); } -TEST(printf_test, vsprintf_make_args_example) { - fmt::format_arg_store<fmt::printf_context, int, const char*> as{42, - "something"}; - fmt::basic_format_args<fmt::printf_context> args(as); - EXPECT_EQ("[42] something happened", fmt::vsprintf("[%d] %s happened", args)); - auto as2 = fmt::make_printf_args(42, "something"); - fmt::basic_format_args<fmt::printf_context> args2(as2); - EXPECT_EQ("[42] something happened", - fmt::vsprintf("[%d] %s happened", args2)); +TEST(printf_test, make_printf_args) { EXPECT_EQ("[42] something happened", - fmt::vsprintf("[%d] %s happened", + fmt::vsprintf(fmt::string_view("[%d] %s happened"), {fmt::make_printf_args(42, "something")})); -} - -TEST(printf_test, vsprintf_make_wargs_example) { - fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{ - 42, L"something"}; - fmt::basic_format_args<fmt::wprintf_context> args(as); - EXPECT_EQ(L"[42] something happened", - fmt::vsprintf(L"[%d] %s happened", args)); - auto as2 = fmt::make_wprintf_args(42, L"something"); - fmt::basic_format_args<fmt::wprintf_context> args2(as2); - EXPECT_EQ(L"[42] something happened", - fmt::vsprintf(L"[%d] %s happened", args2)); EXPECT_EQ(L"[42] something happened", - fmt::vsprintf(L"[%d] %s happened", + fmt::vsprintf(fmt::basic_string_view<wchar_t>(L"[%d] %s happened"), {fmt::make_wprintf_args(42, L"something")})); } diff --git a/externals/fmt/test/ranges-test.cc b/externals/fmt/test/ranges-test.cc index 0804996f..afd3c0f8 100644 --- a/externals/fmt/test/ranges-test.cc +++ b/externals/fmt/test/ranges-test.cc @@ -12,7 +12,10 @@ #include "fmt/ranges.h" #include <map> +#include <queue> +#include <stack> #include <string> +#include <utility> #include <vector> #include "gtest/gtest.h" @@ -40,6 +43,8 @@ TEST(ranges_test, format_2d_array) { TEST(ranges_test, format_array_of_literals) { const char* arr[] = {"1234", "abcd"}; EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]"); + EXPECT_EQ(fmt::format("{:n}", arr), "\"1234\", \"abcd\""); + EXPECT_EQ(fmt::format("{:n:}", arr), "1234, abcd"); } #endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY @@ -47,17 +52,28 @@ TEST(ranges_test, format_vector) { auto v = std::vector<int>{1, 2, 3, 5, 7, 11}; EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]"); EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]"); + EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb"); + + auto vc = std::vector<char>{'a', 'b', 'c'}; + auto vvc = std::vector<std::vector<char>>{vc, vc}; + EXPECT_EQ(fmt::format("{}", vc), "['a', 'b', 'c']"); + EXPECT_EQ(fmt::format("{}", vvc), "[['a', 'b', 'c'], ['a', 'b', 'c']]"); + EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']"); + EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'"); + EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c"); } TEST(ranges_test, format_vector2) { auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}}; EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]"); EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]"); + EXPECT_EQ(fmt::format("{:n:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb"); } TEST(ranges_test, format_map) { auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}}; EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}"); + EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2"); } TEST(ranges_test, format_set) { @@ -65,6 +81,35 @@ TEST(ranges_test, format_set) { "{\"one\", \"two\"}"); } +// Models std::flat_set close enough to test if no ambiguous lookup of a +// formatter happens due to the flat_set type matching is_set and +// is_container_adaptor_like +template <typename T> class flat_set { + public: + using key_type = T; + using container_type = std::vector<T>; + + using iterator = typename std::vector<T>::iterator; + using const_iterator = typename std::vector<T>::const_iterator; + + template <typename... Ts> + explicit flat_set(Ts&&... args) : c{std::forward<Ts>(args)...} {} + + iterator begin() { return c.begin(); } + const_iterator begin() const { return c.begin(); } + + iterator end() { return c.end(); } + const_iterator end() const { return c.end(); } + + private: + std::vector<T> c; +}; + +TEST(ranges_test, format_flat_set) { + EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}), + "{\"one\", \"two\"}"); +} + namespace adl { struct box { int value; @@ -91,6 +136,7 @@ TEST(ranges_test, format_tuple) { auto t = std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i'); EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')"); + EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()"); EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value)); @@ -103,6 +149,25 @@ TEST(ranges_test, format_tuple) { EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value)); } +struct not_default_formattable {}; +struct bad_format {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter<not_default_formattable> { + auto parse(format_parse_context&) -> const char* { throw bad_format(); } + auto format(not_default_formattable, format_context& ctx) + -> format_context::iterator { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + +TEST(ranges_test, tuple_parse_calls_element_parse) { + auto f = fmt::formatter<std::tuple<not_default_formattable>>(); + auto ctx = fmt::format_parse_context(""); + EXPECT_THROW(f.parse(ctx), bad_format); +} + #ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT struct tuple_like { int i; @@ -144,32 +209,24 @@ TEST(ranges_test, format_to) { EXPECT_STREQ(buf, "[1, 2, 3]"); } -struct path_like { +template <typename Char> struct path_like { const path_like* begin() const; const path_like* end() const; - operator std::string() const; -}; - -TEST(ranges_test, path_like) { - EXPECT_FALSE((fmt::is_range<path_like, char>::value)); -} - -#ifdef FMT_USE_STRING_VIEW -struct string_like { - const char* begin(); - const char* end(); - operator fmt::string_view() const { return "foo"; } - operator std::string_view() const { return "foo"; } + operator std::basic_string<Char>() const; }; -TEST(ranges_test, format_string_like) { - EXPECT_EQ(fmt::format("{}", string_like()), "foo"); +TEST(ranges_test, disabled_range_formatting_of_path) { + // Range formatting of path is disabled because of infinite recursion + // (path element is itself a path). + EXPECT_EQ((fmt::range_format_kind<path_like<char>, char>::value), + fmt::range_format::disabled); + EXPECT_EQ((fmt::range_format_kind<path_like<wchar_t>, char>::value), + fmt::range_format::disabled); } -#endif // FMT_USE_STRING_VIEW -// A range that provides non-const only begin()/end() to test fmt::join handles -// that. +// A range that provides non-const only begin()/end() to test fmt::join +// handles that. // // Some ranges (e.g. those produced by range-v3's views::filter()) can cache // information during iteration so they only provide non-const begin()/end(). @@ -193,7 +250,7 @@ template <typename T> class noncopyable_range { std::vector<T> vec; public: - using const_iterator = typename ::std::vector<T>::const_iterator; + using iterator = typename ::std::vector<T>::iterator; template <typename... Args> explicit noncopyable_range(Args&&... args) @@ -202,8 +259,8 @@ template <typename T> class noncopyable_range { noncopyable_range(noncopyable_range const&) = delete; noncopyable_range(noncopyable_range&) = delete; - const_iterator begin() const { return vec.begin(); } - const_iterator end() const { return vec.end(); } + iterator begin() { return vec.begin(); } + iterator end() { return vec.end(); } }; TEST(ranges_test, range) { @@ -232,7 +289,6 @@ TEST(ranges_test, enum_range) { } #if !FMT_MSC_VERSION - TEST(ranges_test, unformattable_range) { EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>, fmt::format_context>::value)); @@ -364,7 +420,7 @@ TEST(ranges_test, is_printable) { EXPECT_FALSE(is_printable(0x110000)); } -TEST(ranges_test, escape_string) { +TEST(ranges_test, escape) { using vec = std::vector<std::string>; EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]"); EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]"); @@ -375,22 +431,22 @@ TEST(ranges_test, escape_string) { EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]"); // Unassigned Unicode code points. EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]"); + // Broken utf-8. EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}), "[\"\\xf4\\x8f\\xbf\\xc0\"]"); - EXPECT_EQ(fmt::format("{}", vec{"понедельник"}), "[\"понедельник\"]"); - } -} + EXPECT_EQ(fmt::format("{}", vec{"\xf0\x28"}), "[\"\\xf0(\"]"); + EXPECT_EQ(fmt::format("{}", vec{"\xe1\x28"}), "[\"\\xe1(\"]"); + EXPECT_EQ(fmt::format("{}", vec{std::string("\xf0\x28\0\0anything", 12)}), + "[\"\\xf0(\\x00\\x00anything\"]"); -#ifdef FMT_USE_STRING_VIEW -struct convertible_to_string_view { - operator std::string_view() const { return "foo"; } -}; + // Correct utf-8. + EXPECT_EQ(fmt::format("{}", vec{"🦄"}), "[\"🦄\"]"); + } -TEST(ranges_test, escape_convertible_to_string_view) { - EXPECT_EQ(fmt::format("{}", std::vector<convertible_to_string_view>(1)), - "[\"foo\"]"); + EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}), + "[['x']]"); + EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])"); } -#endif // FMT_USE_STRING_VIEW template <typename R> struct fmt_ref_view { R* r; @@ -406,3 +462,66 @@ TEST(ranges_test, range_of_range_of_mixed_const) { fmt_ref_view<decltype(v)> r{&v}; EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]"); } + +TEST(ranges_test, vector_char) { + EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']"); +} + +TEST(ranges_test, container_adaptor) { + { + using fmt::detail::is_container_adaptor_like; + using T = std::nullptr_t; + static_assert(is_container_adaptor_like<std::stack<T>>::value, ""); + static_assert(is_container_adaptor_like<std::queue<T>>::value, ""); + static_assert(is_container_adaptor_like<std::priority_queue<T>>::value, ""); + static_assert(!is_container_adaptor_like<std::vector<T>>::value, ""); + } + + { + auto s = std::stack<int>(); + s.push(1); + s.push(2); + EXPECT_EQ(fmt::format("{}", s), "[1, 2]"); + EXPECT_EQ(fmt::format("{}", const_cast<const decltype(s)&>(s)), "[1, 2]"); + } + + { + auto q = std::queue<int>(); + q.push(1); + q.push(2); + EXPECT_EQ(fmt::format("{}", q), "[1, 2]"); + } + + { + auto q = std::priority_queue<int>(); + q.push(3); + q.push(1); + q.push(2); + q.push(4); + EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]"); + } + + { + auto s = std::stack<char, std::string>(); + s.push('a'); + s.push('b'); + // See https://cplusplus.github.io/LWG/issue3881. + EXPECT_EQ(fmt::format("{}", s), "['a', 'b']"); + } + + { + struct my_container_adaptor { + using value_type = int; + using container_type = std::vector<value_type>; + void push(const value_type& v) { c.push_back(v); } + + protected: + container_type c; + }; + + auto m = my_container_adaptor(); + m.push(1); + m.push(2); + EXPECT_EQ(fmt::format("{}", m), "[1, 2]"); + } +} diff --git a/externals/fmt/test/scan-test.cc b/externals/fmt/test/scan-test.cc index 7e327ea3..bec54134 100644 --- a/externals/fmt/test/scan-test.cc +++ b/externals/fmt/test/scan-test.cc @@ -65,7 +65,7 @@ TEST(scan_test, read_string_view) { EXPECT_EQ(s, "foo"); } -#ifndef _WIN32 +#ifdef FMT_HAVE_STRPTIME namespace fmt { template <> struct scanner<tm> { std::string format; diff --git a/externals/fmt/test/scan.h b/externals/fmt/test/scan.h index 41748ae8..a2cb2aa6 100644 --- a/externals/fmt/test/scan.h +++ b/externals/fmt/test/scan.h @@ -42,7 +42,7 @@ struct scan_context { public: using iterator = const char*; - explicit scan_context(string_view input) : input_(input) {} + explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {} iterator begin() const { return input_.data(); } iterator end() const { return begin() + input_.size(); } @@ -83,17 +83,21 @@ class scan_arg { // TODO: more types }; - scan_arg() : type(scan_type::none_type) {} - scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {} - scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {} - scan_arg(long long& value) + 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) {} - scan_arg(unsigned long long& value) + FMT_CONSTEXPR scan_arg(unsigned long long& value) : type(scan_type::ulong_long_type), ulong_long_value(&value) {} - scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {} - scan_arg(fmt::string_view& 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> scan_arg(T& value) : type(scan_type::custom_type) { + template <typename T> + FMT_CONSTEXPR scan_arg(T& value) : type(scan_type::custom_type) { custom.value = &value; custom.scan = scan_custom_arg<T>; } @@ -114,7 +118,7 @@ struct scan_args { const detail::scan_arg* data; template <size_t N> - scan_args(const std::array<detail::scan_arg, N>& store) + FMT_CONSTEXPR scan_args(const std::array<detail::scan_arg, N>& store) : size(N), data(store.data()) { static_assert(N < INT_MAX, "too many arguments"); } @@ -154,7 +158,8 @@ struct scan_handler : error_handler { } public: - scan_handler(string_view format, string_view input, scan_args args) + FMT_CONSTEXPR scan_handler(string_view format, string_view input, + scan_args args) : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {} const char* pos() const { return scan_ctx_.begin(); } @@ -162,10 +167,8 @@ struct scan_handler : error_handler { void on_text(const char* begin, const char* end) { auto size = to_unsigned(end - begin); auto it = scan_ctx_.begin(); - if (it + size > scan_ctx_.end() || - !std::equal(begin, end, make_checked(it, size))) { + if (it + size > scan_ctx_.end() || !std::equal(begin, end, it)) on_error("invalid input"); - } scan_ctx_.advance_to(it + size); } diff --git a/externals/fmt/test/static-export-test/CMakeLists.txt b/externals/fmt/test/static-export-test/CMakeLists.txt index 7690d7b5..492194f9 100644 --- a/externals/fmt/test/static-export-test/CMakeLists.txt +++ b/externals/fmt/test/static-export-test/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1...3.18) +cmake_minimum_required(VERSION 3.8...3.25) project(fmt-link CXX) diff --git a/externals/fmt/test/std-test.cc b/externals/fmt/test/std-test.cc index fc8f72a0..56904cc9 100644 --- a/externals/fmt/test/std-test.cc +++ b/externals/fmt/test/std-test.cc @@ -6,52 +6,110 @@ // For the license information refer to format.h. #include "fmt/std.h" -#include "fmt/ranges.h" +#include <bitset> +#include <stdexcept> #include <string> #include <vector> -#include "gtest/gtest.h" +#include "fmt/os.h" // fmt::system_category +#include "fmt/ranges.h" +#include "gtest-extra.h" // StartsWith +#ifdef __cpp_lib_filesystem TEST(std_test, path) { -// Test ambiguity problem described in #2954. We need to exclude compilers -// where the ambiguity problem cannot be solved for now. -#if defined(__cpp_lib_filesystem) && \ - (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920) - EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" "); - EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")), - "\"foo\\\"bar.txt\""); + using std::filesystem::path; + EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin"); + EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\""); + EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo "); + + EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar"); + EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\""); # ifdef _WIN32 - // File.txt in Russian. - const wchar_t unicode_path[] = {0x424, 0x430, 0x439, 0x43b, 0x2e, - 0x74, 0x78, 0x74, 0}; - const char unicode_u8path[] = {'"', char(0xd0), char(0xa4), char(0xd0), - char(0xb0), char(0xd0), char(0xb9), char(0xd0), - char(0xbb), '.', 't', 'x', - 't', '"', '\0'}; - EXPECT_EQ(fmt::format("{}", std::filesystem::path(unicode_path)), - unicode_u8path); + 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\""); # endif -#endif } +// Test ambiguity problem described in #2954. TEST(ranges_std_test, format_vector_path) { -// Test ambiguity problem described in #2954. We need to exclude compilers -// where the ambiguity problem cannot be solved for now. -#if defined(__cpp_lib_filesystem) && \ - (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920) auto p = std::filesystem::path("foo/bar.txt"); auto c = std::vector<std::string>{"abc", "def"}; EXPECT_EQ(fmt::format("path={}, range={}", p, c), - "path=\"foo/bar.txt\", range=[\"abc\", \"def\"]"); -#endif + "path=foo/bar.txt, range=[\"abc\", \"def\"]"); +} + +// Test that path is not escaped twice in the debug mode. +TEST(ranges_std_test, format_quote_path) { + auto vec = + std::vector<std::filesystem::path>{"path1/file1.txt", "path2/file2.txt"}; + EXPECT_EQ(fmt::format("{}", vec), + "[\"path1/file1.txt\", \"path2/file2.txt\"]"); +# ifdef __cpp_lib_optional + auto o = std::optional<std::filesystem::path>("path/file.txt"); + EXPECT_EQ(fmt::format("{}", o), "optional(\"path/file.txt\")"); + EXPECT_EQ(fmt::format("{:?}", o), "optional(\"path/file.txt\")"); +# endif } +#endif TEST(std_test, thread_id) { EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty()); } +TEST(std_test, optional) { +#ifdef __cpp_lib_optional + EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none"); + EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")"); + EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2}, + std::optional{3}}), + "[optional(1), optional(2), optional(3)]"); + EXPECT_EQ( + fmt::format("{}", std::optional<std::optional<const char*>>{{"nested"}}), + "optional(optional(\"nested\"))"); + EXPECT_EQ( + fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30), + "optional(\"left aligned\" )"); + EXPECT_EQ( + fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}), + "optional([104, 101, 108, 108, 111])"); + EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}), + "optional(\"string\")"); + EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')"); + EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)"); + + struct unformattable {}; + EXPECT_FALSE((fmt::is_formattable<unformattable>::value)); + EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value)); + EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value)); +#endif +} + +struct throws_on_move { + throws_on_move() = default; + + [[noreturn]] throws_on_move(throws_on_move&&) { + throw std::runtime_error("Thrown by throws_on_move"); + } + + throws_on_move(const throws_on_move&) = default; +}; + +namespace fmt { +template <> struct formatter<throws_on_move> : formatter<string_view> { + auto format(const throws_on_move&, format_context& ctx) const + -> decltype(ctx.out()) { + string_view str("<throws_on_move>"); + return formatter<string_view>::format(str, ctx); + } +}; +} // namespace fmt + TEST(std_test, variant) { #ifdef __cpp_lib_variant EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate"); @@ -80,5 +138,120 @@ TEST(std_test, variant) { EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)"); EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")"); + + volatile int i = 42; // Test compile error before GCC 11 described in #3068. + EXPECT_EQ(fmt::format("{}", i), "42"); + + std::variant<std::monostate, throws_on_move> v6; + + try { + throws_on_move thrower; + v6.emplace<throws_on_move>(std::move(thrower)); + } catch (const std::runtime_error&) { + } + // v6 is now valueless by exception + + EXPECT_EQ(fmt::format("{}", v6), "variant(valueless by exception)"); + #endif } + +TEST(std_test, error_code) { + EXPECT_EQ("generic:42", + fmt::format(FMT_STRING("{0}"), + std::error_code(42, std::generic_category()))); + EXPECT_EQ("system:42", + fmt::format(FMT_STRING("{0}"), + std::error_code(42, fmt::system_category()))); + EXPECT_EQ("system:-42", + fmt::format(FMT_STRING("{0}"), + std::error_code(-42, fmt::system_category()))); +} + +template <typename Catch> void exception_test() { + try { + throw std::runtime_error("Test Exception"); + } catch (const Catch& ex) { + EXPECT_EQ("Test Exception", fmt::format("{}", ex)); + EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{:t}", ex)); + } +} + +namespace my_ns1 { +namespace my_ns2 { +struct my_exception : public std::exception { + private: + std::string msg; + + public: + my_exception(const std::string& s) : msg(s) {} + const char* what() const noexcept override; +}; +const char* my_exception::what() const noexcept { return msg.c_str(); } +} // namespace my_ns2 +} // namespace my_ns1 + +TEST(std_test, exception) { + using testing::StartsWith; + exception_test<std::exception>(); + exception_test<std::runtime_error>(); + + try { + using namespace my_ns1::my_ns2; + throw my_exception("My Exception"); + } catch (const std::exception& ex) { + EXPECT_EQ("my_ns1::my_ns2::my_exception: My Exception", + fmt::format("{:t}", ex)); + EXPECT_EQ("My Exception", fmt::format("{:}", ex)); + } + + try { + throw std::system_error(std::error_code(), "message"); + } catch (const std::system_error& ex) { + EXPECT_THAT(fmt::format("{:t}", ex), StartsWith("std::system_error: ")); + } + +#ifdef __cpp_lib_filesystem + // Tests that the inline namespace is stripped out, e.g. + // std::filesystem::__cxx11::* -> std::filesystem::*. + try { + throw std::filesystem::filesystem_error("message", std::error_code()); + } catch (const std::filesystem::filesystem_error& ex) { + EXPECT_THAT(fmt::format("{:t}", ex), + StartsWith("std::filesystem::filesystem_error: ")); + } +#endif +} + +TEST(std_test, format_bit_reference) { + std::bitset<2> bs(1); + EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false"); + std::vector<bool> v = {true, false}; + EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false"); +} + +TEST(std_test, format_const_bit_reference) { + const std::bitset<2> bs(1); + EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false"); + const std::vector<bool> v = {true, false}; + EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false"); +} + +TEST(std_test, format_atomic) { + std::atomic<bool> b(false); + EXPECT_EQ(fmt::format("{}", b), "false"); + + const std::atomic<bool> cb(true); + EXPECT_EQ(fmt::format("{}", cb), "true"); +} + +#ifdef __cpp_lib_atomic_flag_test +TEST(std_test, format_atomic_flag) { + std::atomic_flag f = ATOMIC_FLAG_INIT; + (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 diff --git a/externals/fmt/test/unicode-test.cc b/externals/fmt/test/unicode-test.cc index 63c828dd..e9ea5e5e 100644 --- a/externals/fmt/test/unicode-test.cc +++ b/externals/fmt/test/unicode-test.cc @@ -18,12 +18,12 @@ using testing::Contains; TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); } TEST(unicode_test, legacy_locale) { - auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251"); + auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251"); if (loc == std::locale::classic()) return; auto s = std::string(); try { - s = fmt::format(loc, "День недели: {:L}", fmt::weekday(1)); + s = fmt::format(loc, "Дзень тыдня: {:L}", fmt::weekday(1)); } catch (const fmt::format_error& e) { // Formatting can fail due to an unsupported encoding. fmt::print("Format error: {}\n", e.what()); @@ -38,11 +38,11 @@ TEST(unicode_test, legacy_locale) { os << std::put_time(&tm, "%a"); auto wd = os.str(); if (wd == "??") { - EXPECT_EQ(s, "День недели: ??"); + EXPECT_EQ(s, "Дзень тыдня: ??"); fmt::print("std::locale gives ?? as a weekday.\n"); return; } #endif - EXPECT_THAT((std::vector<std::string>{"День недели: пн", "День недели: Пн"}), + EXPECT_THAT((std::vector<std::string>{"Дзень тыдня: пн", "Дзень тыдня: Пан"}), Contains(s)); } diff --git a/externals/fmt/test/util.h b/externals/fmt/test/util.h index 2e58ad95..9120e22e 100644 --- a/externals/fmt/test/util.h +++ b/externals/fmt/test/util.h @@ -10,11 +10,7 @@ #include <locale> #include <string> -#ifdef FMT_MODULE_TEST -import fmt; -#else -# include "fmt/os.h" -#endif // FMT_MODULE_TEST +#include "fmt/os.h" #ifdef _MSC_VER # define FMT_VSNPRINTF vsprintf_s @@ -81,5 +77,5 @@ class date { int day() const { return day_; } }; -// Returns a locale with the given name if available or classic locale othewise. +// 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); diff --git a/externals/fmt/test/xchar-test.cc b/externals/fmt/test/xchar-test.cc index ea8bc85a..f80997d6 100644 --- a/externals/fmt/test/xchar-test.cc +++ b/externals/fmt/test/xchar-test.cc @@ -7,6 +7,7 @@ #include "fmt/xchar.h" +#include <algorithm> #include <complex> #include <cwchar> #include <vector> @@ -15,31 +16,21 @@ #include "fmt/color.h" #include "fmt/ostream.h" #include "fmt/ranges.h" +#include "fmt/std.h" #include "gtest-extra.h" // Contains #include "util.h" // get_locale using fmt::detail::max_value; using testing::Contains; -namespace test_ns { -template <typename Char> class test_string { - private: - std::basic_string<Char> s_; - - public: - test_string(const Char* s) : s_(s) {} - const Char* data() const { return s_.data(); } - size_t length() const { return s_.size(); } - operator const Char*() const { return s_.c_str(); } -}; - -template <typename Char> -fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) { - return {s.data(), s.length()}; -} +#if defined(__MINGW32__) && !defined(_UCRT) +// Only C89 conversion specifiers when using MSVCRT instead of UCRT +# define FMT_HAS_C99_STRFTIME 0 +#else +# define FMT_HAS_C99_STRFTIME 1 +#endif struct non_string {}; -} // namespace test_ns template <typename T> class is_string_test : public testing::Test {}; @@ -61,8 +52,7 @@ TYPED_TEST(is_string_test, is_string) { using fmt_string_view = fmt::detail::std_string_view<TypeParam>; EXPECT_TRUE(std::is_empty<fmt_string_view>::value != fmt::detail::is_string<fmt_string_view>::value); - EXPECT_TRUE(fmt::detail::is_string<test_ns::test_string<TypeParam>>::value); - EXPECT_FALSE(fmt::detail::is_string<test_ns::non_string>::value); + EXPECT_FALSE(fmt::detail::is_string<non_string>::value); } // std::is_constructible is broken in MSVC until version 2015. @@ -84,7 +74,7 @@ TEST(xchar_test, format) { EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); - EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error); + EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error); EXPECT_EQ(L"true", fmt::format(L"{}", true)); EXPECT_EQ(L"a", fmt::format(L"{0}", 'a')); EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); @@ -98,8 +88,9 @@ TEST(xchar_test, is_formattable) { } TEST(xchar_test, compile_time_string) { + EXPECT_EQ(fmt::format(fmt::wformat_string<int>(L"{}"), 42), L"42"); #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L - EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); + EXPECT_EQ(fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42), L"42"); #endif } @@ -111,10 +102,12 @@ struct custom_char { template <typename T> constexpr custom_char(T val) : value(static_cast<int>(val)) {} - operator int() const { return value; } + operator char() const { + return value <= 0xff ? static_cast<char>(value) : '\0'; + } }; -int to_ascii(custom_char c) { return c; } +auto to_ascii(custom_char c) -> char { return c; } FMT_BEGIN_NAMESPACE template <> struct is_char<custom_char> : std::true_type {}; @@ -153,17 +146,21 @@ TEST(xchar_test, format_to) { } TEST(xchar_test, vformat_to) { - using wcontext = fmt::wformat_context; - fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42); - auto wargs = fmt::basic_format_args<wcontext>(&warg, 1); + auto args = fmt::make_wformat_args(42); auto w = std::wstring(); - fmt::vformat_to(std::back_inserter(w), L"{}", wargs); - EXPECT_EQ(L"42", w); - w.clear(); - fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs); + fmt::vformat_to(std::back_inserter(w), L"{}", args); EXPECT_EQ(L"42", w); } +namespace test { +struct struct_as_wstring_view {}; +auto format_as(struct_as_wstring_view) -> fmt::wstring_view { return L"foo"; } +} // namespace test + +TEST(xchar_test, format_as) { + EXPECT_EQ(fmt::format(L"{}", test::struct_as_wstring_view()), L"foo"); +} + TEST(format_test, wide_format_to_n) { wchar_t buffer[4]; buffer[3] = L'x'; @@ -199,7 +196,10 @@ TEST(xchar_test, named_arg_udl) { TEST(xchar_test, print) { // Check that the wide print overload compiles. - if (fmt::detail::const_check(false)) fmt::print(L"test"); + if (fmt::detail::const_check(false)) { + fmt::print(L"test"); + fmt::println(L"test"); + } } TEST(xchar_test, join) { @@ -229,11 +229,24 @@ TEST(xchar_test, enum) { EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum())); } +struct streamable_and_unformattable {}; + +auto operator<<(std::wostream& os, streamable_and_unformattable) + -> std::wostream& { + return os << L"foo"; +} + +TEST(xchar_test, streamed) { + EXPECT_FALSE(fmt::is_formattable<streamable_and_unformattable>()); + EXPECT_EQ(fmt::format(L"{}", fmt::streamed(streamable_and_unformattable())), + L"foo"); +} + TEST(xchar_test, sign_not_truncated) { wchar_t format_str[] = { L'{', L':', '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0}; - EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error); + EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error); } TEST(xchar_test, chrono) { @@ -269,8 +282,9 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr, #endif } -TEST(chrono_test, time_point) { - auto t1 = std::chrono::system_clock::now(); +TEST(chrono_test_wchar, time_point) { + auto t1 = std::chrono::time_point_cast<std::chrono::seconds>( + std::chrono::system_clock::now()); std::vector<std::wstring> spec_list = { L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C", @@ -278,23 +292,75 @@ TEST(chrono_test, time_point) { L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e", L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH", L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X", - L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"}; - spec_list.push_back(L"%Y-%m-%d %H:%M:%S"); + L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"}; #ifndef _WIN32 // Disabled on Windows, because these formats is not consistent among // platforms. spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"}); +#elif !FMT_HAS_C99_STRFTIME + // Only C89 conversion specifiers when using MSVCRT instead of UCRT + spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U", + L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H", + L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"}; #endif + spec_list.push_back(L"%Y-%m-%d %H:%M:%S"); for (const auto& spec : spec_list) { auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::gmtime(&t); + + auto sys_output = system_wcsftime(spec, &tm); + + auto fmt_spec = fmt::format(L"{{:{}}}", spec); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1)); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); + } + + // Timezone formatters tests makes sense for localtime. +#if FMT_HAS_C99_STRFTIME + spec_list = {L"%z", L"%Z"}; +#else + spec_list = {L"%Z"}; +#endif + for (const auto& spec : spec_list) { + auto t = std::chrono::system_clock::to_time_t(t1); auto tm = *std::localtime(&t); auto sys_output = system_wcsftime(spec, &tm); auto fmt_spec = fmt::format(L"{{:{}}}", spec); - EXPECT_EQ(sys_output, fmt::format(fmt_spec, t1)); - EXPECT_EQ(sys_output, fmt::format(fmt_spec, tm)); + EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); + + if (spec == L"%z") { + sys_output.insert(sys_output.end() - 2, 1, L':'); + EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm)); + EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm)); + } + } + + // Separate tests for UTC, since std::time_put can use local time and ignoring + // the timezone in std::tm (if it presents on platform). + if (fmt::detail::has_member_data_tm_zone<std::tm>::value) { + auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::gmtime(&t); + + std::vector<std::wstring> tz_names = {L"GMT", L"UTC"}; + EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1))); + EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm))); + } + + if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) { + auto t = std::chrono::system_clock::to_time_t(t1); + auto tm = *std::gmtime(&t); + + EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1)); + EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm)); + + EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1)); + EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm)); + + EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1)); + EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm)); } } @@ -305,9 +371,17 @@ TEST(xchar_test, color) { TEST(xchar_test, ostream) { #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409 - std::wostringstream wos; - fmt::print(wos, L"Don't {}!", L"panic"); - EXPECT_EQ(wos.str(), L"Don't panic!"); + { + std::wostringstream wos; + fmt::print(wos, L"Don't {}!", L"panic"); + EXPECT_EQ(wos.str(), L"Don't panic!"); + } + + { + std::wostringstream wos; + fmt::println(wos, L"Don't {}!", L"panic"); + EXPECT_EQ(wos.str(), L"Don't panic!\n"); + } #endif } @@ -325,6 +399,7 @@ TEST(xchar_test, escape_string) { TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); } #ifndef FMT_STATIC_THOUSANDS_SEPARATOR + template <typename Char> struct numpunct : std::numpunct<Char> { protected: Char do_decimal_point() const override { return '?'; } @@ -360,6 +435,9 @@ TEST(locale_test, localized_double) { EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5"); EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000"); EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), " 1~230"); + EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 0.1), " 0?100000"); + EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1.0), " 1?000000"); + EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1e3), " 1~000?000000"); } TEST(locale_test, format) { @@ -368,8 +446,8 @@ TEST(locale_test, format) { EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567)); EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567)); EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256)); - fmt::format_arg_store<fmt::format_context, int> as{1234567}; - EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as))); + auto n = 1234567; + EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::make_format_args(n))); auto s = std::string(); fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567); EXPECT_EQ("1~234~567", s); @@ -402,10 +480,9 @@ TEST(locale_test, wformat) { auto loc = std::locale(std::locale(), new numpunct<wchar_t>()); EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567)); EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567)); - using wcontext = fmt::buffer_context<wchar_t>; - fmt::format_arg_store<wcontext, int> as{1234567}; + int n = 1234567; EXPECT_EQ(L"1~234~567", - fmt::vformat(loc, L"{:L}", fmt::basic_format_args<wcontext>(as))); + fmt::vformat(loc, L"{:L}", fmt::make_wformat_args(n))); EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567)); auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>()); @@ -422,16 +499,16 @@ TEST(locale_test, wformat) { fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>())); } -TEST(locale_test, double_formatter) { +TEST(locale_test, int_formatter) { auto loc = std::locale(std::locale(), new special_grouping<char>()); auto f = fmt::formatter<int>(); auto parse_ctx = fmt::format_parse_context("L"); f.parse(parse_ctx); - char buf[10] = {}; - fmt::basic_format_context<char*, char> format_ctx( - buf, {}, fmt::detail::locale_ref(loc)); - *f.format(12345, format_ctx) = 0; - EXPECT_STREQ("12,345", buf); + auto buf = fmt::memory_buffer(); + fmt::basic_format_context<fmt::appender, char> format_ctx( + fmt::appender(buf), {}, fmt::detail::locale_ref(loc)); + f.format(12345, format_ctx); + EXPECT_EQ(fmt::to_string(buf), "12,345"); } FMT_BEGIN_NAMESPACE @@ -442,13 +519,10 @@ template <class charT> struct formatter<std::complex<double>, charT> { public: FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse( basic_format_parse_context<charT>& ctx) { - using handler_type = - detail::dynamic_specs_handler<basic_format_parse_context<charT>>; - detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), - detail::type::string_type); - auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); - detail::parse_float_type_spec(specs_, ctx.error_handler()); - return it; + auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::float_type); + detail::parse_float_type_spec(specs_, detail::error_handler()); + return end; } template <class FormatContext> @@ -495,4 +569,16 @@ TEST(locale_test, chrono_weekday) { std::locale::global(loc_old); } +TEST(locale_test, sign) { + EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50"); +} + +TEST(std_test_xchar, optional) { +# ifdef __cpp_lib_optional + EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')"); + EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}), + L"optional(\"wide string\")"); +# endif +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR |