early-access version 2511

This commit is contained in:
pineappleEA 2022-02-26 21:58:02 +01:00
parent 4204a097d5
commit c7a582c5a5
81 changed files with 5280 additions and 1921 deletions

View file

@ -1,7 +1,7 @@
yuzu emulator early access yuzu emulator early access
============= =============
This is the source code for early-access 2509. This is the source code for early-access 2511.
## Legal Notice ## Legal Notice

View file

@ -9,7 +9,7 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest] os: [windows-latest, ubuntu-latest, macos-latest]
cpu_detection: [0, 1] cpu_detection: [0, 1]
fail-fast: false fail-fast: false
@ -37,16 +37,19 @@ jobs:
path: externals/ext-boost path: externals/ext-boost
- name: Checkout unicorn repo - name: Checkout unicorn repo
if: ${{matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'}}
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: MerryMage/unicorn repository: MerryMage/unicorn
path: externals/unicorn path: externals/unicorn
- name: Build unicorn - name: Build unicorn
if: ${{matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'}}
working-directory: externals/unicorn working-directory: externals/unicorn
run: UNICORN_ARCHS=aarch64,arm ./make.sh run: UNICORN_ARCHS=aarch64,arm ./make.sh
- name: Configure CMake - name: Configure CMake
if: ${{matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'}}
run: > run: >
cmake cmake
-B ${{github.workspace}}/build -B ${{github.workspace}}/build
@ -59,9 +62,20 @@ jobs:
-DLIBUNICORN_LIBRARY=${{github.workspace}}/externals/unicorn/libunicorn.a -DLIBUNICORN_LIBRARY=${{github.workspace}}/externals/unicorn/libunicorn.a
-G Ninja -G Ninja
- name: Configure CMake
if: ${{matrix.os == 'windows-latest'}}
run: >
cmake
-B ${{github.workspace}}/build
-DBoost_INCLUDE_DIRS=${{github.workspace}}/externals/ext-boost
-DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
-DDYNARMIC_ENABLE_CPU_FEATURE_DETECTION=${{matrix.cpu_detection}}
-G "Visual Studio 17 2022"
-A x64
- name: Build - name: Build
working-directory: ${{github.workspace}}/build working-directory: ${{github.workspace}}/build
run: ninja run: cmake --build . --config Release
- name: Test - name: Test
env: env:

View file

@ -4,3 +4,5 @@ build-*/
docs/Doxygen/ docs/Doxygen/
# Generated files # Generated files
src/dynarmic/backend/x64/mig/ src/dynarmic/backend/x64/mig/
# System files
.DS_Store

View file

@ -131,6 +131,13 @@ if (DYNARMIC_NO_BUNDLED_ROBIN_MAP AND NOT TARGET tsl::robin_map)
find_package(tsl-robin-map REQUIRED) find_package(tsl-robin-map REQUIRED)
endif() endif()
if (DYNARMIC_NO_BUNDLED_VIXL AND ARCHITECTURE STREQUAL "arm64")
find_package(PkgConfig REQUIRED)
pkg_check_modules(vixl REQUIRED IMPORTED_TARGET vixl)
target_include_directories(PkgConfig::vixl INTERFACE "${vixl_INCLUDE_DIRS}/vixl")
add_library(vixl ALIAS PkgConfig::vixl)
endif()
if (DYNARMIC_NO_BUNDLED_XBYAK AND NOT TARGET xbyak) if (DYNARMIC_NO_BUNDLED_XBYAK AND NOT TARGET xbyak)
if (ARCHITECTURE STREQUAL "x86" OR ARCHITECTURE STREQUAL "x86_64") if (ARCHITECTURE STREQUAL "x86" OR ARCHITECTURE STREQUAL "x86_64")
find_package(xbyak REQUIRED) find_package(xbyak REQUIRED)

View file

@ -1,7 +1,7 @@
Dynarmic Dynarmic
======== ========
[![Github Actions Build Status](https://github.com/MerryMage/dynarmic/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/MerryMage/dynarmic/actions/workflows/build-and-test.yml) [![Appveyor CI Build status](https://ci.appveyor.com/api/projects/status/maeiqr41rgm1innm/branch/master?svg=true)](https://ci.appveyor.com/project/MerryMage/dynarmic/branch/master) [![Github Actions Build Status](https://github.com/MerryMage/dynarmic/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/MerryMage/dynarmic/actions/workflows/build-and-test.yml)
A dynamic recompiler for ARM. A dynamic recompiler for ARM.

View file

@ -32,7 +32,7 @@ endif()
# vixl # vixl
if (ARCHITECTURE STREQUAL "arm64") if (ARCHITECTURE STREQUAL "arm64" AND NOT TARGET vixl)
add_subdirectory(vixl EXCLUDE_FROM_ALL) add_subdirectory(vixl EXCLUDE_FROM_ALL)
endif() endif()

View file

@ -12,6 +12,7 @@ jobs:
- name: Create Build Environment - name: Create Build Environment
run: | run: |
sudo apt update
sudo apt install doxygen python3-virtualenv sudo apt install doxygen python3-virtualenv
sudo npm install -g less clean-css sudo npm install -g less clean-css
cmake -E make_directory ${{runner.workspace}}/build cmake -E make_directory ${{runner.workspace}}/build

View file

@ -24,10 +24,11 @@ jobs:
build_type: Debug build_type: Debug
std: 17 std: 17
os: ubuntu-18.04 os: ubuntu-18.04
- cxx: g++-10 - cxx: g++-11
build_type: Debug build_type: Debug
std: 20 std: 20
os: ubuntu-20.04 os: ubuntu-20.04
install: sudo apt install g++-11
- cxx: clang++-9 - cxx: clang++-9
build_type: Debug build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON

View file

@ -17,7 +17,7 @@ endif ()
# Joins arguments and places the results in ${result_var}. # Joins arguments and places the results in ${result_var}.
function(join result_var) function(join result_var)
set(result ) set(result "")
foreach (arg ${ARGN}) foreach (arg ${ARGN})
set(result "${result}${arg}") set(result "${result}${arg}")
endforeach () endforeach ()
@ -81,12 +81,13 @@ option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
option(FMT_MODULE "Build a module instead of a traditional library." OFF) 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) set(FMT_CAN_MODULE OFF)
if (CMAKE_CXX_STANDARD GREATER 17 AND if (CMAKE_CXX_STANDARD GREATER 17 AND
# msvc 16.10-pre4 # msvc 16.10-pre4
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035) MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
set(FMT_CAN_MODULE ON) set(FMT_CAN_MODULE OFF)
endif () endif ()
if (NOT FMT_CAN_MODULE) if (NOT FMT_CAN_MODULE)
set(FMT_MODULE OFF) set(FMT_MODULE OFF)
@ -96,6 +97,10 @@ if (FMT_TEST AND FMT_MODULE)
# The tests require {fmt} to be compiled as traditional library # The tests require {fmt} to be compiled as traditional library
message(STATUS "Testing is incompatible with build mode 'module'.") message(STATUS "Testing is incompatible with build mode 'module'.")
endif () endif ()
set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
if (FMT_SYSTEM_HEADERS)
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
endif ()
# Get version from core.h # Get version from core.h
file(READ include/fmt/core.h core_h) file(READ include/fmt/core.h core_h)
@ -151,7 +156,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
-Wcast-align -Wcast-align
-Wctor-dtor-privacy -Wdisabled-optimization -Wctor-dtor-privacy -Wdisabled-optimization
-Winvalid-pch -Woverloaded-virtual -Winvalid-pch -Woverloaded-virtual
-Wconversion -Wswitch-enum -Wundef -Wconversion -Wundef
-Wno-ctor-dtor-privacy -Wno-format-nonliteral) -Wno-ctor-dtor-privacy -Wno-format-nonliteral)
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
@ -244,7 +249,7 @@ if (HAVE_STRTOD_L)
endif () endif ()
if (MINGW) if (MINGW)
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ) check_cxx_compiler_flag("-Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
if (${FMT_HAS_MBIG_OBJ}) if (${FMT_HAS_MBIG_OBJ})
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj") target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
endif() endif()
@ -262,7 +267,7 @@ endif ()
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
target_include_directories(fmt PUBLIC target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>) $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@ -298,7 +303,7 @@ add_library(fmt::fmt-header-only ALIAS fmt-header-only)
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) target_compile_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 ${FMT_REQUIRED_FEATURES})
target_include_directories(fmt-header-only INTERFACE target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>) $<INSTALL_INTERFACE:${FMT_INC_DIR}>)

View file

@ -1,7 +1,427 @@
8.1.1 - 2022-01-06
------------------
* Restored ABI compatibility with version 8.0.x
(`#2695 <https://github.com/fmtlib/fmt/issues/2695>`_,
`#2696 <https://github.com/fmtlib/fmt/pull/2696>`_).
Thanks `@saraedum (Julian Rüth) <https://github.com/saraedum>`_.
* Fixed chorno formatting on big endian systems
(`#2698 <https://github.com/fmtlib/fmt/issues/2698>`_,
`#2699 <https://github.com/fmtlib/fmt/pull/2699>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
`@xvitaly (Vitaly Zaitsev) <https://github.com/xvitaly>`_.
* Fixed a linkage error with mingw
(`#2691 <https://github.com/fmtlib/fmt/issues/2691>`_,
`#2692 <https://github.com/fmtlib/fmt/pull/2692>`_).
Thanks `@rbberger (Richard Berger) <https://github.com/rbberger>`_.
8.1.0 - 2022-01-02
------------------
* Optimized chrono formatting
(`#2500 <https://github.com/fmtlib/fmt/pull/2500>`_,
`#2537 <https://github.com/fmtlib/fmt/pull/2537>`_,
`#2541 <https://github.com/fmtlib/fmt/issues/2541>`_,
`#2544 <https://github.com/fmtlib/fmt/pull/2544>`_,
`#2550 <https://github.com/fmtlib/fmt/pull/2550>`_,
`#2551 <https://github.com/fmtlib/fmt/pull/2551>`_,
`#2576 <https://github.com/fmtlib/fmt/pull/2576>`_,
`#2577 <https://github.com/fmtlib/fmt/issues/2577>`_,
`#2586 <https://github.com/fmtlib/fmt/pull/2586>`_,
`#2591 <https://github.com/fmtlib/fmt/pull/2591>`_,
`#2594 <https://github.com/fmtlib/fmt/pull/2594>`_,
`#2602 <https://github.com/fmtlib/fmt/pull/2602>`_,
`#2617 <https://github.com/fmtlib/fmt/pull/2617>`_,
`#2628 <https://github.com/fmtlib/fmt/issues/2628>`_,
`#2633 <https://github.com/fmtlib/fmt/pull/2633>`_,
`#2670 <https://github.com/fmtlib/fmt/issues/2670>`_,
`#2671 <https://github.com/fmtlib/fmt/pull/2671>`_).
Processing of some specifiers such as ``%z`` and ``%Y`` is now up to 10-20
times faster, for example on GCC 11 with libstdc++::
----------------------------------------------------------------------------
Benchmark Before After
----------------------------------------------------------------------------
FMTFormatter_z 261 ns 26.3 ns
FMTFormatterCompile_z 246 ns 11.6 ns
FMTFormatter_Y 263 ns 26.1 ns
FMTFormatterCompile_Y 244 ns 10.5 ns
----------------------------------------------------------------------------
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
`@toughengineer (Pavel Novikov) <https://github.com/toughengineer>`_.
* Implemented subsecond formatting for chrono durations
(`#2623 <https://github.com/fmtlib/fmt/pull/2623>`_).
For example (`godbolt <https://godbolt.org/z/es7vWTETe>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
fmt::print("{:%S}", std::chrono::milliseconds(1234));
}
prints "01.234".
Thanks `@matrackif <https://github.com/matrackif>`_.
* Fixed handling of precision 0 when formatting chrono durations
(`#2587 <https://github.com/fmtlib/fmt/issues/2587>`_,
`#2588 <https://github.com/fmtlib/fmt/pull/2588>`_).
Thanks `@lukester1975 <https://github.com/lukester1975>`_.
* Fixed an overflow on invalid inputs in the ``tm`` formatter
(`#2564 <https://github.com/fmtlib/fmt/pull/2564>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added ``fmt::group_digits`` that formats integers with a non-localized digit
separator (comma) for groups of three digits.
For example (`godbolt <https://godbolt.org/z/TxGxG9Poq>`__):
.. code:: c++
#include <fmt/format.h>
int main() {
fmt::print("{} dollars", fmt::group_digits(1000000));
}
prints "1,000,000 dollars".
* Added support for faint, conceal, reverse and blink text styles
(`#2394 <https://github.com/fmtlib/fmt/pull/2394>`_):
https://user-images.githubusercontent.com/576385/147710227-c68f5317-f8fa-42c3-9123-7c4ba3c398cb.mp4
Thanks `@benit8 (Benoît Lormeau) <https://github.com/benit8>`_ and
`@data-man (Dmitry Atamanov) <https://github.com/data-man>`_.
* Added experimental support for compile-time floating point formatting
(`#2426 <https://github.com/fmtlib/fmt/pull/2426>`_,
`#2470 <https://github.com/fmtlib/fmt/pull/2470>`_).
It is currently limited to the header-only mode.
Thanks `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
* Added UDL-based named argument support to compile-time format string checks
(`#2640 <https://github.com/fmtlib/fmt/issues/2640>`_,
`#2649 <https://github.com/fmtlib/fmt/pull/2649>`_).
For example (`godbolt <https://godbolt.org/z/ohGbbvonv>`__):
.. code:: c++
#include <fmt/format.h>
int main() {
using namespace fmt::literals;
fmt::print("{answer:s}", "answer"_a=42);
}
gives a compile-time error on compilers with C++20 ``consteval`` and non-type
template parameter support (gcc 10+) because ``s`` is not a valid format
specifier for an integer.
Thanks `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
* Implemented escaping of string range elements.
For example (`godbolt <https://godbolt.org/z/rKvM1vKf3>`__):
.. code:: c++
#include <fmt/ranges.h>
#include <vector>
int main() {
fmt::print("{}", std::vector<std::string>{"\naan"});
}
is now printed as::
["\naan"]
instead of::
["
aan"]
* Switched to JSON-like representation of maps and sets for consistency with
Python's ``str.format``.
For example (`godbolt <https://godbolt.org/z/seKjoY9W5>`__):
.. code:: c++
#include <fmt/ranges.h>
#include <map>
int main() {
fmt::print("{}", std::map<std::string, int>{{"answer", 42}});
}
is now printed as::
{"answer": 42}
* Extended ``fmt::join`` to support C++20-only ranges
(`#2549 <https://github.com/fmtlib/fmt/pull/2549>`_).
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Optimized handling of non-const-iterable ranges and implemented initial
support for non-const-formattable types.
* Disabled implicit conversions of scoped enums to integers that was
accidentally introduced in earlier versions
(`#1841 <https://github.com/fmtlib/fmt/pull/1841>`_).
* Deprecated implicit conversion of ``[const] signed char*`` and
``[const] unsigned char*`` to C strings.
* Deprecated ``_format``, a legacy UDL-based format API
(`#2646 <https://github.com/fmtlib/fmt/pull/2646>`_).
Thanks `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
* Marked ``format``, ``formatted_size`` and ``to_string`` as ``[[nodiscard]]``
(`#2612 <https://github.com/fmtlib/fmt/pull/2612>`_).
`@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_.
* Added missing diagnostic when trying to format function and member pointers
as well as objects convertible to pointers which is explicitly disallowed
(`#2598 <https://github.com/fmtlib/fmt/issues/2598>`_,
`#2609 <https://github.com/fmtlib/fmt/pull/2609>`_,
`#2610 <https://github.com/fmtlib/fmt/pull/2610>`_).
Thanks `@AlexGuteniev (Alex Guteniev) <https://github.com/AlexGuteniev>`_.
* Optimized writing to a contiguous buffer with ``format_to_n``
(`#2489 <https://github.com/fmtlib/fmt/pull/2489>`_).
Thanks `@Roman-Koshelev <https://github.com/Roman-Koshelev>`_.
* Optimized writing to non-``char`` buffers
(`#2477 <https://github.com/fmtlib/fmt/pull/2477>`_).
Thanks `@Roman-Koshelev <https://github.com/Roman-Koshelev>`_.
* Decimal point is now localized when using the ``L`` specifier.
* Improved floating point formatter implementation
(`#2498 <https://github.com/fmtlib/fmt/pull/2498>`_,
`#2499 <https://github.com/fmtlib/fmt/pull/2499>`_).
Thanks `@Roman-Koshelev <https://github.com/Roman-Koshelev>`_.
* Fixed handling of very large precision in fixed format
(`#2616 <https://github.com/fmtlib/fmt/pull/2616>`_).
* Made a table of cached powers used in FP formatting static
(`#2509 <https://github.com/fmtlib/fmt/pull/2509>`_).
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
* Resolved a lookup ambiguity with C++20 format-related functions due to ADL
(`#2639 <https://github.com/fmtlib/fmt/issues/2639>`_,
`#2641 <https://github.com/fmtlib/fmt/pull/2641>`_).
Thanks `@mkurdej (Marek Kurdej) <https://github.com/mkurdej>`_.
* Removed unnecessary inline namespace qualification
(`#2642 <https://github.com/fmtlib/fmt/issues/2642>`_,
`#2643 <https://github.com/fmtlib/fmt/pull/2643>`_).
Thanks `@mkurdej (Marek Kurdej) <https://github.com/mkurdej>`_.
* Implemented argument forwarding in ``format_to_n``
(`#2462 <https://github.com/fmtlib/fmt/issues/2462>`_,
`#2463 <https://github.com/fmtlib/fmt/pull/2463>`_).
Thanks `@owent (WenTao Ou) <https://github.com/owent>`_.
* Fixed handling of implicit conversions in ``fmt::to_string`` and format string
compilation (`#2565 <https://github.com/fmtlib/fmt/issues/2565>`_).
* Changed the default access mode of files created by ``fmt::output_file`` to
``-rw-r--r--`` for consistency with ``fopen``
(`#2530 <https://github.com/fmtlib/fmt/issues/2530>`_).
* Make ``fmt::ostream::flush`` public
(`#2435 <https://github.com/fmtlib/fmt/issues/2435>`_).
* Improved C++14/17 attribute detection
(`#2615 <https://github.com/fmtlib/fmt/pull/2615>`_).
Thanks `@AlexGuteniev (Alex Guteniev) <https://github.com/AlexGuteniev>`_.
* Improved ``consteval`` detection for MSVC
(`#2559 <https://github.com/fmtlib/fmt/pull/2559>`_).
Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
* Improved documentation
(`#2406 <https://github.com/fmtlib/fmt/issues/2406>`_,
`#2446 <https://github.com/fmtlib/fmt/pull/2446>`_,
`#2493 <https://github.com/fmtlib/fmt/issues/2493>`_,
`#2513 <https://github.com/fmtlib/fmt/issues/2513>`_,
`#2515 <https://github.com/fmtlib/fmt/pull/2515>`_,
`#2522 <https://github.com/fmtlib/fmt/issues/2522>`_,
`#2562 <https://github.com/fmtlib/fmt/pull/2562>`_,
`#2575 <https://github.com/fmtlib/fmt/pull/2575>`_,
`#2606 <https://github.com/fmtlib/fmt/pull/2606>`_,
`#2620 <https://github.com/fmtlib/fmt/pull/2620>`_,
`#2676 <https://github.com/fmtlib/fmt/issues/2676>`_).
Thanks `@sobolevn (Nikita Sobolev) <https://github.com/sobolevn>`_,
`@UnePierre (Max FERGER) <https://github.com/UnePierre>`_,
`@zhsj <https://github.com/zhsj>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@ericcurtin (Eric Curtin) <https://github.com/ericcurtin>`_,
`@Lounarok <https://github.com/Lounarok>`_.
* Improved fuzzers and added a fuzzer for chrono timepoint formatting
(`#2461 <https://github.com/fmtlib/fmt/pull/2461>`_,
`#2469 <https://github.com/fmtlib/fmt/pull/2469>`_).
`@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_,
* Added the ``FMT_SYSTEM_HEADERS`` CMake option setting which marks {fmt}'s
headers as system. It can be used to suppress warnings
(`#2644 <https://github.com/fmtlib/fmt/issues/2644>`_,
`#2651 <https://github.com/fmtlib/fmt/pull/2651>`_).
Thanks `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
* Added the Bazel build system support
(`#2505 <https://github.com/fmtlib/fmt/pull/2505>`_,
`#2516 <https://github.com/fmtlib/fmt/pull/2516>`_).
Thanks `@Vertexwahn <https://github.com/Vertexwahn>`_.
* Improved build configuration and tests
(`#2437 <https://github.com/fmtlib/fmt/issues/2437>`_,
`#2558 <https://github.com/fmtlib/fmt/pull/2558>`_,
`#2648 <https://github.com/fmtlib/fmt/pull/2648>`_,
`#2650 <https://github.com/fmtlib/fmt/pull/2650>`_,
`#2663 <https://github.com/fmtlib/fmt/pull/2663>`_,
`#2677 <https://github.com/fmtlib/fmt/pull/2677>`_).
Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
`@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Fixed various warnings and compilation issues
(`#2353 <https://github.com/fmtlib/fmt/pull/2353>`_,
`#2356 <https://github.com/fmtlib/fmt/pull/2356>`_,
`#2399 <https://github.com/fmtlib/fmt/pull/2399>`_,
`#2408 <https://github.com/fmtlib/fmt/issues/2408>`_,
`#2414 <https://github.com/fmtlib/fmt/pull/2414>`_,
`#2427 <https://github.com/fmtlib/fmt/pull/2427>`_,
`#2432 <https://github.com/fmtlib/fmt/pull/2432>`_,
`#2442 <https://github.com/fmtlib/fmt/pull/2442>`_,
`#2434 <https://github.com/fmtlib/fmt/pull/2434>`_,
`#2439 <https://github.com/fmtlib/fmt/issues/2439>`_,
`#2447 <https://github.com/fmtlib/fmt/pull/2447>`_,
`#2450 <https://github.com/fmtlib/fmt/pull/2450>`_,
`#2455 <https://github.com/fmtlib/fmt/issues/2455>`_,
`#2465 <https://github.com/fmtlib/fmt/issues/2465>`_,
`#2472 <https://github.com/fmtlib/fmt/issues/2472>`_,
`#2474 <https://github.com/fmtlib/fmt/issues/2474>`_,
`#2476 <https://github.com/fmtlib/fmt/pull/2476>`_,
`#2478 <https://github.com/fmtlib/fmt/issues/2478>`_,
`#2479 <https://github.com/fmtlib/fmt/issues/2479>`_,
`#2481 <https://github.com/fmtlib/fmt/issues/2481>`_,
`#2482 <https://github.com/fmtlib/fmt/pull/2482>`_,
`#2483 <https://github.com/fmtlib/fmt/pull/2483>`_,
`#2490 <https://github.com/fmtlib/fmt/issues/2490>`_,
`#2491 <https://github.com/fmtlib/fmt/pull/2491>`_,
`#2510 <https://github.com/fmtlib/fmt/pull/2510>`_,
`#2518 <https://github.com/fmtlib/fmt/pull/2518>`_,
`#2528 <https://github.com/fmtlib/fmt/issues/2528>`_,
`#2529 <https://github.com/fmtlib/fmt/pull/2529>`_,
`#2539 <https://github.com/fmtlib/fmt/pull/2539>`_,
`#2540 <https://github.com/fmtlib/fmt/issues/2540>`_,
`#2545 <https://github.com/fmtlib/fmt/pull/2545>`_,
`#2555 <https://github.com/fmtlib/fmt/pull/2555>`_,
`#2557 <https://github.com/fmtlib/fmt/issues/2557>`_,
`#2570 <https://github.com/fmtlib/fmt/issues/2570>`_,
`#2573 <https://github.com/fmtlib/fmt/pull/2573>`_,
`#2582 <https://github.com/fmtlib/fmt/pull/2582>`_,
`#2605 <https://github.com/fmtlib/fmt/issues/2605>`_,
`#2611 <https://github.com/fmtlib/fmt/pull/2611>`_,
`#2647 <https://github.com/fmtlib/fmt/pull/2647>`_,
`#2627 <https://github.com/fmtlib/fmt/issues/2627>`_,
`#2630 <https://github.com/fmtlib/fmt/pull/2630>`_,
`#2635 <https://github.com/fmtlib/fmt/issues/2635>`_,
`#2638 <https://github.com/fmtlib/fmt/issues/2638>`_,
`#2653 <https://github.com/fmtlib/fmt/issues/2653>`_,
`#2654 <https://github.com/fmtlib/fmt/issues/2654>`_,
`#2661 <https://github.com/fmtlib/fmt/issues/2661>`_,
`#2664 <https://github.com/fmtlib/fmt/pull/2664>`_,
`#2684 <https://github.com/fmtlib/fmt/pull/2684>`_).
Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
`@mwinterb <https://github.com/mwinterb>`_,
`@cdacamar (Cameron DaCamara) <https://github.com/cdacamar>`_,
`@TrebledJ (Johnathan) <https://github.com/TrebledJ>`_,
`@bodomartin (brm) <https://github.com/bodomartin>`_,
`@cquammen (Cory Quammen) <https://github.com/cquammen>`_,
`@white238 (Chris White) <https://github.com/white238>`_,
`@mmarkeloff (Max) <https://github.com/mmarkeloff>`_,
`@palacaze (Pierre-Antoine Lacaze) <https://github.com/palacaze>`_,
`@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_,
`@mborn-adi (Mathias Born) <https://github.com/mborn-adi>`_,
`@BrukerJWD (Jonathan W) <https://github.com/BrukerJWD>`_,
`@spyridon97 (Spiros Tsalikis) <https://github.com/spyridon97>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@oliverlee (Oliver Lee) <https://github.com/oliverlee>`_,
`@joshessman-llnl (Josh Essman) <https://github.com/joshessman-llnl>`_,
`@akohlmey (Axel Kohlmeyer) <https://github.com/akohlmey>`_,
`@timkalu <https://github.com/timkalu>`_,
`@olupton (Olli Lupton) <https://github.com/olupton>`_,
`@Acretock <https://github.com/Acretock>`_,
`@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_,
`@andrewcorrigan (Andrew Corrigan) <https://github.com/andrewcorrigan>`_,
`@lucpelletier <https://github.com/lucpelletier>`_,
`@HazardyKnusperkeks (Björn Schäpers) <https://github.com/HazardyKnusperkeks>`_.
8.0.1 - 2021-07-02
------------------
* Fixed the version number in the inline namespace
(`#2374 <https://github.com/fmtlib/fmt/issues/2374>`_).
* Added a missing presentation type check for ``std::string``
(`#2402 <https://github.com/fmtlib/fmt/issues/2402>`_).
* Fixed a linkage error when mixing code built with clang and gcc
(`#2377 <https://github.com/fmtlib/fmt/issues/2377>`_).
* Fixed documentation issues
(`#2396 <https://github.com/fmtlib/fmt/pull/2396>`_,
`#2403 <https://github.com/fmtlib/fmt/issues/2403>`_,
`#2406 <https://github.com/fmtlib/fmt/issues/2406>`_).
Thanks `@mkurdej (Marek Kurdej) <https://github.com/mkurdej>`_.
* Removed dead code in FP formatter (
`#2398 <https://github.com/fmtlib/fmt/pull/2398>`_).
Thanks `@javierhonduco (Javier Honduvilla Coto)
<https://github.com/javierhonduco>`_.
* Fixed various warnings and compilation issues
(`#2351 <https://github.com/fmtlib/fmt/issues/2351>`_,
`#2359 <https://github.com/fmtlib/fmt/issues/2359>`_,
`#2365 <https://github.com/fmtlib/fmt/pull/2365>`_,
`#2368 <https://github.com/fmtlib/fmt/issues/2368>`_,
`#2370 <https://github.com/fmtlib/fmt/pull/2370>`_,
`#2376 <https://github.com/fmtlib/fmt/pull/2376>`_,
`#2381 <https://github.com/fmtlib/fmt/pull/2381>`_,
`#2382 <https://github.com/fmtlib/fmt/pull/2382>`_,
`#2386 <https://github.com/fmtlib/fmt/issues/2386>`_,
`#2389 <https://github.com/fmtlib/fmt/pull/2389>`_,
`#2395 <https://github.com/fmtlib/fmt/pull/2395>`_,
`#2397 <https://github.com/fmtlib/fmt/pull/2397>`_,
`#2400 <https://github.com/fmtlib/fmt/issues/2400>`_,
`#2401 <https://github.com/fmtlib/fmt/issues/2401>`_,
`#2407 <https://github.com/fmtlib/fmt/pull/2407>`_).
Thanks `@zx2c4 (Jason A. Donenfeld) <https://github.com/zx2c4>`_,
`@AidanSun05 (Aidan Sun) <https://github.com/AidanSun05>`_,
`@mattiasljungstrom (Mattias Ljungström)
<https://github.com/mattiasljungstrom>`_,
`@joemmett (Jonathan Emmett) <https://github.com/joemmett>`_,
`@erengy (Eren Okka) <https://github.com/erengy>`_,
`@patlkli (Patrick Geltinger) <https://github.com/patlkli>`_,
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
8.0.0 - 2021-06-21 8.0.0 - 2021-06-21
------------------ ------------------
* Enabled compile-time format string check by default. * Enabled compile-time format string checks by default.
For example (`godbolt <https://godbolt.org/z/sMxcohGjz>`__): For example (`godbolt <https://godbolt.org/z/sMxcohGjz>`__):
.. code:: c++ .. code:: c++
@ -232,6 +652,9 @@
This doesn't introduce a dependency on ``<locale>`` so there is virtually no This doesn't introduce a dependency on ``<locale>`` so there is virtually no
compile time effect. compile time effect.
* Deprecated an undocumented ``format_to`` overload that takes
``basic_memory_buffer``.
* Made parameter order in ``vformat_to`` consistent with ``format_to`` * Made parameter order in ``vformat_to`` consistent with ``format_to``
(`#2327 <https://github.com/fmtlib/fmt/issues/2327>`_). (`#2327 <https://github.com/fmtlib/fmt/issues/2327>`_).
@ -301,9 +724,9 @@
``make_format_to_n_args``. They have been replaced with ``format_context``, ``make_format_to_n_args``. They have been replaced with ``format_context``,
``format_args` and ``make_format_args`` respectively. ``format_args` and ``make_format_args`` respectively.
* Moved ``wchar_t``-specific functions and types to ``fmt/wchar.h``. * Moved ``wchar_t``-specific functions and types to ``fmt/xchar.h``.
You can define ``FMT_DEPRECATED_INCLUDE_WCHAR`` to automatically include You can define ``FMT_DEPRECATED_INCLUDE_XCHAR`` to automatically include
``fmt/wchar.h`` from ``fmt/format.h`` but this will be disabled in the next ``fmt/xchar.h`` from ``fmt/format.h`` but this will be disabled in the next
major release. major release.
* Fixed handling of the ``'+'`` specifier in localized formatting * Fixed handling of the ``'+'`` specifier in localized formatting
@ -338,6 +761,9 @@
Thanks `@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_. Thanks `@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_.
* The undocumented support for specializing ``formatter`` for pointer types
has been removed.
* Fixed ``fmt::formatted_size`` with format string compilation * Fixed ``fmt::formatted_size`` with format string compilation
(`#2141 <https://github.com/fmtlib/fmt/pull/2141>`_, (`#2141 <https://github.com/fmtlib/fmt/pull/2141>`_,
`#2161 <https://github.com/fmtlib/fmt/pull/2161>`_). `#2161 <https://github.com/fmtlib/fmt/pull/2161>`_).
@ -510,13 +936,13 @@
`#2067 <https://github.com/fmtlib/fmt/pull/2067>`_, `#2067 <https://github.com/fmtlib/fmt/pull/2067>`_,
`#2068 <https://github.com/fmtlib/fmt/pull/2068>`_, `#2068 <https://github.com/fmtlib/fmt/pull/2068>`_,
`#2073 <https://github.com/fmtlib/fmt/pull/2073>`_, `#2073 <https://github.com/fmtlib/fmt/pull/2073>`_,
`#2103 <https://github.com/fmtlib/fmt/issues/2103>`_ `#2103 <https://github.com/fmtlib/fmt/issues/2103>`_,
`#2105 <https://github.com/fmtlib/fmt/issues/2105>`_ `#2105 <https://github.com/fmtlib/fmt/issues/2105>`_,
`#2106 <https://github.com/fmtlib/fmt/pull/2106>`_, `#2106 <https://github.com/fmtlib/fmt/pull/2106>`_,
`#2107 <https://github.com/fmtlib/fmt/pull/2107>`_, `#2107 <https://github.com/fmtlib/fmt/pull/2107>`_,
`#2116 <https://github.com/fmtlib/fmt/issues/2116>`_ `#2116 <https://github.com/fmtlib/fmt/issues/2116>`_,
`#2117 <https://github.com/fmtlib/fmt/pull/2117>`_, `#2117 <https://github.com/fmtlib/fmt/pull/2117>`_,
`#2118 <https://github.com/fmtlib/fmt/issues/2118>`_ `#2118 <https://github.com/fmtlib/fmt/issues/2118>`_,
`#2119 <https://github.com/fmtlib/fmt/pull/2119>`_, `#2119 <https://github.com/fmtlib/fmt/pull/2119>`_,
`#2127 <https://github.com/fmtlib/fmt/issues/2127>`_, `#2127 <https://github.com/fmtlib/fmt/issues/2127>`_,
`#2128 <https://github.com/fmtlib/fmt/pull/2128>`_, `#2128 <https://github.com/fmtlib/fmt/pull/2128>`_,
@ -589,7 +1015,7 @@
`@yeswalrus (Walter Gray) <https://github.com/yeswalrus>`_, `@yeswalrus (Walter Gray) <https://github.com/yeswalrus>`_,
`@Finkman <https://github.com/Finkman>`_, `@Finkman <https://github.com/Finkman>`_,
`@HazardyKnusperkeks (Björn Schäpers) <https://github.com/HazardyKnusperkeks>`_, `@HazardyKnusperkeks (Björn Schäpers) <https://github.com/HazardyKnusperkeks>`_,
`@dkavolis (Daumantas Kavolis) <https://github.com/dkavolis>`_ `@dkavolis (Daumantas Kavolis) <https://github.com/dkavolis>`_,
`@concatime (Issam Maghni) <https://github.com/concatime>`_, `@concatime (Issam Maghni) <https://github.com/concatime>`_,
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_, `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
`@summivox (Yin Zhong) <https://github.com/summivox>`_, `@summivox (Yin Zhong) <https://github.com/summivox>`_,
@ -1046,7 +1472,7 @@
`#1912 <https://github.com/fmtlib/fmt/issues/1912>`_, `#1912 <https://github.com/fmtlib/fmt/issues/1912>`_,
`#1928 <https://github.com/fmtlib/fmt/issues/1928>`_, `#1928 <https://github.com/fmtlib/fmt/issues/1928>`_,
`#1929 <https://github.com/fmtlib/fmt/pull/1929>`_, `#1929 <https://github.com/fmtlib/fmt/pull/1929>`_,
`#1935 <https://github.com/fmtlib/fmt/issues/1935>`_ `#1935 <https://github.com/fmtlib/fmt/issues/1935>`_,
`#1937 <https://github.com/fmtlib/fmt/pull/1937>`_, `#1937 <https://github.com/fmtlib/fmt/pull/1937>`_,
`#1942 <https://github.com/fmtlib/fmt/pull/1942>`_, `#1942 <https://github.com/fmtlib/fmt/pull/1942>`_,
`#1949 <https://github.com/fmtlib/fmt/issues/1949>`_). `#1949 <https://github.com/fmtlib/fmt/issues/1949>`_).

View file

@ -10,7 +10,7 @@
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg .. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v .. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true
:target: https://ci.appveyor.com/project/vitaut/fmt :target: https://ci.appveyor.com/project/vitaut/fmt
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg .. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
@ -143,10 +143,10 @@ Output::
.. code:: c++ .. code:: c++
std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number"); std::string s = fmt::format("{:d}", "I am not a number");
This gives a compile-time error because ``d`` is an invalid format specifier for This gives a compile-time error in C++20 because ``d`` is an invalid format
a string. specifier for a string.
**Write a file from a single thread** **Write a file from a single thread**
@ -205,7 +205,7 @@ The above results were generated by building ``tinyformat_test.cpp`` on macOS
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"`` 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 or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source further details refer to the `source
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_. <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 {fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_) floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
@ -469,7 +469,7 @@ Boost Format
This is a very powerful library which supports both ``printf``-like format This is a very powerful library which supports both ``printf``-like format
strings and positional arguments. Its main drawback is performance. According to strings and positional arguments. Its main drawback is performance. According to
various, benchmarks it is much slower than other methods considered here. Boost various benchmarks, it is much slower than other methods considered here. Boost
Format also has excessive build times and severe code bloat issues (see Format also has excessive build times and severe code bloat issues (see
`Benchmarks`_). `Benchmarks`_).

View file

@ -37,10 +37,12 @@ similar to that of Python's `str.format
<https://docs.python.org/3/library/stdtypes.html#str.format>`_. <https://docs.python.org/3/library/stdtypes.html#str.format>`_.
They take *fmt* and *args* as arguments. They take *fmt* and *args* as arguments.
*fmt* is a format string that contains literal text and replacement *fmt* is a format string that contains literal text and replacement fields
fields surrounded by braces ``{}``. The fields are replaced with formatted surrounded by braces ``{}``. The fields are replaced with formatted arguments
arguments in the resulting string. A function taking *fmt* doesn't in the resulting string. `~fmt::format_string` is a format string which can be
participate in an overload resolution if the latter is not a string. implicitly constructed from a string literal or a ``constexpr`` string and is
checked at compile time in C++20. To pass a runtime format string wrap it in
`fmt::runtime`.
*args* is an argument list representing objects to be formatted. *args* is an argument list representing objects to be formatted.
@ -50,7 +52,7 @@ participate in an overload resolution if the latter is not a string.
.. doxygenfunction:: vformat(string_view fmt, format_args args) -> std::string .. doxygenfunction:: vformat(string_view fmt, format_args args) -> std::string
.. doxygenfunction:: format_to(OutputIt out, format_string<T...> fmt, T&&... args) -> OutputIt .. doxygenfunction:: format_to(OutputIt out, format_string<T...> fmt, T&&... args) -> OutputIt
.. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string<T...> fmt, const T&... args) -> format_to_n_result<OutputIt> .. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string<T...> fmt, T&&... args) -> format_to_n_result<OutputIt>
.. doxygenfunction:: formatted_size(format_string<T...> fmt, T&&... args) -> size_t .. doxygenfunction:: formatted_size(format_string<T...> fmt, T&&... args) -> size_t
.. doxygenstruct:: fmt::format_to_n_result .. doxygenstruct:: fmt::format_to_n_result
@ -59,7 +61,7 @@ participate in an overload resolution if the latter is not a string.
.. _print: .. _print:
.. doxygenfunction:: fmt::print(format_string<T...> fmt, T&&... args) .. doxygenfunction:: fmt::print(format_string<T...> fmt, T&&... args)
.. doxygenfunction:: vprint(string_view fmt, format_args args) .. doxygenfunction:: fmt::vprint(string_view fmt, format_args args)
.. doxygenfunction:: print(std::FILE *f, format_string<T...> fmt, T&&... args) .. doxygenfunction:: print(std::FILE *f, format_string<T...> fmt, T&&... args)
.. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args) .. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args)
@ -70,6 +72,7 @@ Compile-time Format String Checks
Compile-time checks are enabled when using ``FMT_STRING``. They support built-in 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`` and string types as well as user-defined types with ``constexpr`` ``parse``
functions in their ``formatter`` specializations. functions in their ``formatter`` specializations.
Requires C++14 and is a no-op in C++11.
.. doxygendefine:: FMT_STRING .. doxygendefine:: FMT_STRING
@ -78,6 +81,13 @@ To force the use of compile-time checks, define the preprocessor variable
will fail to compile with regular strings. Runtime-checked will fail to compile with regular strings. Runtime-checked
formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc. formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc.
.. doxygenclass:: fmt::basic_format_string
:members:
.. doxygentypedef:: fmt::format_string
.. doxygenfunction:: fmt::runtime(const S&)
Named Arguments Named Arguments
--------------- ---------------
@ -177,7 +187,9 @@ template and implement ``parse`` and ``format`` methods::
#include <fmt/format.h> #include <fmt/format.h>
struct point { double x, y; }; struct point {
double x, y;
};
template <> struct fmt::formatter<point> { template <> struct fmt::formatter<point> {
// Presentation format: 'f' - fixed, 'e' - exponential. // Presentation format: 'f' - fixed, 'e' - exponential.
@ -201,8 +213,7 @@ template and implement ``parse`` and ``format`` methods::
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++; if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
// Check if reached the end of the range: // Check if reached the end of the range:
if (it != end && *it != '}') if (it != end && *it != '}') throw format_error("invalid format");
throw format_error("invalid format");
// Return an iterator past the end of the parsed range: // Return an iterator past the end of the parsed range:
return it; return it;
@ -213,10 +224,9 @@ template and implement ``parse`` and ``format`` methods::
template <typename FormatContext> template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) -> decltype(ctx.out()) { auto format(const point& p, FormatContext& ctx) -> decltype(ctx.out()) {
// ctx.out() is an output iterator to write to. // ctx.out() is an output iterator to write to.
return format_to( return presentation == 'f'
ctx.out(), ? format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})", : format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y);
p.x, p.y);
} }
}; };
@ -314,6 +324,8 @@ Utilities
.. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> .. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel>
.. doxygenfunction:: fmt::group_digits(T value) -> group_digits_view<T>
.. doxygenclass:: fmt::detail::buffer .. doxygenclass:: fmt::detail::buffer
:members: :members:
@ -350,8 +362,8 @@ allocator::
custom_string vformat(custom_allocator alloc, fmt::string_view format_str, custom_string vformat(custom_allocator alloc, fmt::string_view format_str,
fmt::format_args args) { fmt::format_args args) {
custom_memory_buffer buf(alloc); auto buf = custom_memory_buffer(alloc);
fmt::vformat_to(buf, format_str, args); fmt::vformat_to(std::back_inserter(buf), format_str, args);
return custom_string(buf.data(), buf.size(), alloc); return custom_string(buf.data(), buf.size(), alloc);
} }
@ -519,8 +531,8 @@ argument type doesn't match its format specification.
``wchar_t`` Support ``wchar_t`` Support
=================== ===================
The optional header ``fmt/wchar_t.h`` provides support for ``wchar_t`` and The optional header ``fmt/xchar.h`` provides support for ``wchar_t`` and exotic
exotic character types. character types.
.. doxygenstruct:: fmt::is_char .. doxygenstruct:: fmt::is_char

View file

@ -90,12 +90,14 @@
VERSION: '{{ release|e }}', VERSION: '{{ release|e }}',
COLLAPSE_INDEX: false, COLLAPSE_INDEX: false,
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}', FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
LINK_SUFFIX: '{{ link_suffix }}',
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}',
HAS_SOURCE: {{ has_source|lower }}, HAS_SOURCE: {{ has_source|lower }},
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}' SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
}; };
</script> </script>
{%- for scriptfile in script_files %} {%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script> {{ js_tag(scriptfile) }}
{%- endfor %} {%- endfor %}
{%- endmacro %} {%- endmacro %}

View file

@ -4,7 +4,7 @@
import errno, os, re, sys import errno, os, re, sys
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0'] versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1']
class Pip: class Pip:
def __init__(self, venv_dir): def __init__(self, venv_dir):
@ -26,8 +26,10 @@ def create_build_env(venv_dir='virtualenv'):
pip = Pip(venv_dir) pip = Pip(venv_dir)
pip.install('wheel') pip.install('wheel')
pip.install('six') pip.install('six')
# See: https://github.com/sphinx-doc/sphinx/issues/9777
pip.install('docutils==0.17.1')
pip.install('sphinx-doc/sphinx', 'v3.3.0') pip.install('sphinx-doc/sphinx', 'v3.3.0')
pip.install('michaeljones/breathe', 'v4.16.0') pip.install('michaeljones/breathe', 'v4.25.0')
def build_docs(version='dev', **kwargs): def build_docs(version='dev', **kwargs):
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__))) doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
@ -58,6 +60,7 @@ def build_docs(version='dev', **kwargs):
MACRO_EXPANSION = YES MACRO_EXPANSION = YES
PREDEFINED = _WIN32=1 \ PREDEFINED = _WIN32=1 \
__linux__=1 \ __linux__=1 \
FMT_ENABLE_IF(...)= \
FMT_USE_VARIADIC_TEMPLATES=1 \ FMT_USE_VARIADIC_TEMPLATES=1 \
FMT_USE_RVALUE_REFERENCES=1 \ FMT_USE_RVALUE_REFERENCES=1 \
FMT_USE_USER_DEFINED_LITERALS=1 \ FMT_USE_USER_DEFINED_LITERALS=1 \
@ -66,6 +69,8 @@ def build_docs(version='dev', **kwargs):
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_BEGIN_NAMESPACE=namespace fmt {{" \
"FMT_END_NAMESPACE=}}" \ "FMT_END_NAMESPACE=}}" \
"FMT_STRING_ALIAS=1" \ "FMT_STRING_ALIAS=1" \
"FMT_VARIADIC(...)=" \
"FMT_VARIADIC_W(...)=" \
"FMT_DOC=1" "FMT_DOC=1"
EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \ EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \
fmt::basic_format_arg::handle fmt::basic_format_arg::handle

View file

@ -112,18 +112,18 @@ meaning in this case.
The *sign* option is only valid for number types, and can be one of the The *sign* option is only valid for number types, and can be one of the
following: following:
+---------+----------------------------------------------------------+ +---------+------------------------------------------------------------+
| Option | Meaning | | Option | Meaning |
+=========+==========================================================+ +=========+============================================================+
| ``'+'`` | indicates that a sign should be used for both | | ``'+'`` | indicates that a sign should be used for both |
| | positive as well as negative numbers. | | | nonnegative as well as negative numbers. |
+---------+----------------------------------------------------------+ +---------+------------------------------------------------------------+
| ``'-'`` | indicates that a sign should be used only for negative | | ``'-'`` | indicates that a sign should be used only for negative |
| | numbers (this is the default behavior). | | | numbers (this is the default behavior). |
+---------+----------------------------------------------------------+ +---------+------------------------------------------------------------+
| space | indicates that a leading space should be used on | | space | indicates that a leading space should be used on |
| | positive numbers, and a minus sign on negative numbers. | | | nonnegative numbers, and a minus sign on negative numbers. |
+---------+----------------------------------------------------------+ +---------+------------------------------------------------------------+
The ``'#'`` option causes the "alternate form" to be used for the The ``'#'`` option causes the "alternate form" to be used for the
conversion. The alternate form is defined differently for different conversion. The alternate form is defined differently for different
@ -161,7 +161,8 @@ displayed after the decimal point for a floating-point value formatted with
value formatted with ``'g'`` or ``'G'``. For non-number types the field value formatted with ``'g'`` or ``'G'``. For non-number types the field
indicates the maximum field size - in other words, how many characters will be indicates the maximum field size - in other words, how many characters will be
used from the field content. The *precision* is not allowed for integer, used from the field content. The *precision* is not allowed for integer,
character, Boolean, and pointer values. character, Boolean, and pointer values. Note that a C string must be
null-terminated even if precision is specified.
The ``'L'`` option uses the current locale setting to insert the appropriate The ``'L'`` option uses the current locale setting to insert the appropriate
number separator characters. This option is only valid for numeric types. number separator characters. This option is only valid for numeric types.
@ -449,7 +450,7 @@ Using type-specific formatting::
Using the comma as a thousands separator:: Using the comma as a thousands separator::
#include <fmt/locale.h> #include <fmt/format.h>
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
// s == "1,234,567,890" // s == "1,234,567,890"

View file

@ -143,6 +143,8 @@ class dynamic_format_arg_store
} }
public: public:
constexpr dynamic_format_arg_store() = default;
/** /**
\rst \rst
Adds an argument into the dynamic store for later passing to a formatting Adds an argument into the dynamic store for later passing to a formatting

File diff suppressed because it is too large Load diff

View file

@ -185,9 +185,13 @@ enum class terminal_color : uint8_t {
enum class emphasis : uint8_t { enum class emphasis : uint8_t {
bold = 1, bold = 1,
italic = 1 << 1, faint = 1 << 1,
underline = 1 << 2, italic = 1 << 2,
strikethrough = 1 << 3 underline = 1 << 3,
blink = 1 << 4,
reverse = 1 << 5,
conceal = 1 << 6,
strikethrough = 1 << 7,
}; };
// rgb is a struct for red, green and blue colors. // rgb is a struct for red, green and blue colors.
@ -409,16 +413,18 @@ template <typename Char> struct ansi_color_escape {
buffer[19] = static_cast<Char>(0); buffer[19] = static_cast<Char>(0);
} }
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
uint8_t em_codes[4] = {}; uint8_t em_codes[num_emphases] = {};
uint8_t em_bits = static_cast<uint8_t>(em); if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1; if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3; if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4; if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough)) if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
em_codes[3] = 9; if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0; size_t index = 0;
for (int i = 0; i < 4; ++i) { for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue; if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b'); buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('['); buffer[index++] = static_cast<Char>('[');
@ -435,7 +441,8 @@ template <typename Char> struct ansi_color_escape {
} }
private: private:
Char buffer[7u + 3u * 4u + 1u]; static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) FMT_NOEXCEPT { char delimiter) FMT_NOEXCEPT {
@ -444,6 +451,10 @@ template <typename Char> struct ansi_color_escape {
out[2] = static_cast<Char>('0' + c % 10); out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter); out[3] = static_cast<Char>(delimiter);
} }
static FMT_CONSTEXPR bool has_emphasis(emphasis em,
emphasis mask) FMT_NOEXCEPT {
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
}
}; };
template <typename Char> template <typename Char>
@ -507,7 +518,7 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
auto background = detail::make_background_color<Char>(ts.get_background()); auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end()); buf.append(background.begin(), background.end());
} }
detail::vformat_to(buf, format_str, args); detail::vformat_to(buf, format_str, args, {});
if (has_style) detail::reset_color<Char>(buf); if (has_style) detail::reset_color<Char>(buf);
} }

View file

@ -156,7 +156,7 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
std::string s = fmt::format(FMT_COMPILE("{}"), 42); std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst \endrst
*/ */
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) \ # define FMT_COMPILE(s) \
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
#else #else
@ -179,7 +179,7 @@ const T& first(const T& value, const Tail&...) {
return value; return value;
} }
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {}; template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...]. // Returns a reference to the argument at index N from [first, rest...].
@ -190,7 +190,7 @@ constexpr const auto& get([[maybe_unused]] const T& first,
if constexpr (N == 0) if constexpr (N == 0)
return first; return first;
else else
return get<N - 1>(rest...); return detail::get<N - 1>(rest...);
} }
template <typename Char, typename... Args> template <typename Char, typename... Args>
@ -202,7 +202,8 @@ constexpr int get_arg_index_by_name(basic_string_view<Char> name,
template <int N, typename> struct get_type_impl; template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> { template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>; using type =
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
}; };
template <int N, typename T> template <int N, typename T>
@ -242,7 +243,7 @@ template <typename Char> struct code_unit {
// This ensures that the argument type is convertible to `const T&`. // This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args> template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) { constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = get<N>(args...); const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) { if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value; return arg.value;
} else { } else {
@ -289,7 +290,7 @@ template <typename Char> struct runtime_named_field {
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...); bool found = (try_format_argument(out, name, args) || ...);
if (!found) { if (!found) {
throw format_error("argument with specified name is not found"); FMT_THROW(format_error("argument with specified name is not found"));
} }
return out; return out;
} }
@ -399,7 +400,9 @@ template <typename Char> struct arg_id_handler {
return 0; return 0;
} }
constexpr void on_error(const char* message) { throw format_error(message); } constexpr void on_error(const char* message) {
FMT_THROW(format_error(message));
}
}; };
template <typename Char> struct parse_arg_id_result { template <typename Char> struct parse_arg_id_result {
@ -451,7 +454,7 @@ constexpr auto compile_format_string(S format_str) {
constexpr auto str = basic_string_view<char_type>(format_str); constexpr auto str = basic_string_view<char_type>(format_str);
if constexpr (str[POS] == '{') { if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '{' in format string"); FMT_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') { if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
@ -500,7 +503,7 @@ constexpr auto compile_format_string(S format_str) {
} }
} else if constexpr (str[POS] == '}') { } else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '}' in format string"); FMT_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else { } else {
constexpr auto end = parse_text(str, POS + 1); constexpr auto end = parse_text(str, POS + 1);
@ -527,12 +530,12 @@ constexpr auto compile(S format_str) {
return result; return result;
} }
} }
#endif // __cpp_if_constexpr #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail } // namespace detail
FMT_MODULE_EXPORT_BEGIN FMT_MODULE_EXPORT_BEGIN
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type, typename Char = typename CompiledFormat::char_type,

File diff suppressed because it is too large Load diff

View file

@ -40,6 +40,10 @@ FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
std::terminate(); std::terminate();
} }
FMT_FUNC void throw_format_error(const char* message) {
FMT_THROW(format_error(message));
}
#ifndef _MSC_VER #ifndef _MSC_VER
# define FMT_SNPRINTF snprintf # define FMT_SNPRINTF snprintf
#else // _MSC_VER #else // _MSC_VER
@ -145,141 +149,13 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
} }
#if __cplusplus < 201703L // log10(2) = 0x0.4d104d427de7fbcc...
template <typename T> constexpr const char basic_data<T>::digits[][2]; static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc;
template <typename T> constexpr const char basic_data<T>::hex_digits[];
template <typename T> constexpr const char basic_data<T>::signs[];
template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
template <typename T>
constexpr const char basic_data<T>::right_padding_shifts[];
#endif
template <typename T> struct bits { template <typename T = void> struct basic_impl_data {
static FMT_CONSTEXPR_DECL const int value =
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
};
class fp;
template <int SHIFT = 0> fp normalize(fp value);
// Lower (upper) boundary is a value half way between a floating-point value
// and its predecessor (successor). Boundaries have the same exponent as the
// value so only significands are stored.
struct boundaries {
uint64_t lower;
uint64_t upper;
};
// A handmade floating-point number f * pow(2, e).
class fp {
private:
using significand_type = uint64_t;
template <typename Float>
using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
sizeof(Float) == sizeof(uint32_t)>;
public:
significand_type f;
int e;
// All sizes are in bits.
// Subtract 1 to account for an implicit most significant bit in the
// normalized form.
static FMT_CONSTEXPR_DECL const int double_significand_size =
std::numeric_limits<double>::digits - 1;
static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
1ULL << double_significand_size;
static FMT_CONSTEXPR_DECL const int significand_size =
bits<significand_type>::value;
fp() : f(0), e(0) {}
fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
// Constructs fp from an IEEE754 double. It is a template to prevent compile
// errors on platforms where double is not IEEE754.
template <typename Double> explicit fp(Double d) { assign(d); }
// Assigns d to this and return true iff predecessor is closer than successor.
template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)>
bool assign(Float d) {
// Assume float is in the format [sign][exponent][significand].
using limits = std::numeric_limits<Float>;
const int float_significand_size = limits::digits - 1;
const int exponent_size =
bits<Float>::value - float_significand_size - 1; // -1 for sign
const uint64_t float_implicit_bit = 1ULL << float_significand_size;
const uint64_t significand_mask = float_implicit_bit - 1;
const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d);
f = u & significand_mask;
int biased_e =
static_cast<int>((u & exponent_mask) >> float_significand_size);
// Predecessor is closer if d is a normalized power of 2 (f == 0) other than
// the smallest normalized number (biased_e > 1).
bool is_predecessor_closer = f == 0 && biased_e > 1;
if (biased_e != 0)
f += float_implicit_bit;
else
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
e = biased_e - exponent_bias - float_significand_size;
return is_predecessor_closer;
}
template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)>
bool assign(Float) {
*this = fp();
return false;
}
};
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
template <int SHIFT> fp normalize(fp value) {
// Handle subnormals.
const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
while ((value.f & shifted_implicit_bit) == 0) {
value.f <<= 1;
--value.e;
}
// Subtract 1 to account for hidden bit.
const auto offset =
fp::significand_size - fp::double_significand_size - SHIFT - 1;
value.f <<= offset;
value.e -= offset;
return value;
}
inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
#if FMT_USE_INT128
auto product = static_cast<__uint128_t>(lhs) * rhs;
auto f = static_cast<uint64_t>(product >> 64);
return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;
#else
// Multiply 32-bit parts of significands.
uint64_t mask = (1ULL << 32) - 1;
uint64_t a = lhs >> 32, b = lhs & mask;
uint64_t c = rhs >> 32, d = rhs & mask;
uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
// Compute mid 64-bit of result and round.
uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);
#endif
}
inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; }
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
// These are generated by support/compute-powers.py. // These are generated by support/compute-powers.py.
static constexpr const uint64_t pow10_significands[] = { static constexpr uint64_t pow10_significands[87] = {
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
@ -311,9 +187,13 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
}; };
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wnarrowing"
#endif
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
// to significands above. // to significands above.
static constexpr const int16_t pow10_exponents[] = { static constexpr int16_t pow10_exponents[87] = {
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
@ -322,11 +202,137 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
# pragma GCC diagnostic pop
#endif
static constexpr uint64_t power_of_10_64[20] = {
1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
};
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
struct impl_data : basic_impl_data<> {};
#if __cplusplus < 201703L
template <typename T>
constexpr uint64_t basic_impl_data<T>::pow10_significands[];
template <typename T> constexpr int16_t basic_impl_data<T>::pow10_exponents[];
template <typename T> constexpr uint64_t basic_impl_data<T>::power_of_10_64[];
#endif
template <typename T> struct bits {
static FMT_CONSTEXPR_DECL const int value =
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
};
// Returns the number of significand bits in Float excluding the implicit bit.
template <typename Float> constexpr int num_significand_bits() {
// Subtract 1 to account for an implicit most significant bit in the
// normalized form.
return std::numeric_limits<Float>::digits - 1;
}
// A floating-point number f * pow(2, e).
struct fp {
uint64_t f;
int e;
static constexpr const int num_significand_bits = bits<decltype(f)>::value;
constexpr fp() : f(0), e(0) {}
constexpr fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
// Constructs fp from an IEEE754 floating-point number. It is a template to
// prevent compile errors on systems where n is not IEEE754.
template <typename Float> explicit FMT_CONSTEXPR fp(Float n) { assign(n); }
template <typename Float>
using is_supported = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
sizeof(Float) == sizeof(uint32_t)>;
// Assigns d to this and return true iff predecessor is closer than successor.
template <typename Float, FMT_ENABLE_IF(is_supported<Float>::value)>
FMT_CONSTEXPR bool assign(Float n) {
// Assume float is in the format [sign][exponent][significand].
const int num_float_significand_bits =
detail::num_significand_bits<Float>();
const uint64_t implicit_bit = 1ULL << num_float_significand_bits;
const uint64_t significand_mask = implicit_bit - 1;
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(n);
f = u & significand_mask;
const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
int biased_e =
static_cast<int>((u & exponent_mask) >> num_float_significand_bits);
// The predecessor is closer if n is a normalized power of 2 (f == 0) other
// than the smallest normalized number (biased_e > 1).
bool is_predecessor_closer = f == 0 && biased_e > 1;
if (biased_e != 0)
f += implicit_bit;
else
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
const int exponent_bias = std::numeric_limits<Float>::max_exponent - 1;
e = biased_e - exponent_bias - num_float_significand_bits;
return is_predecessor_closer;
}
template <typename Float, FMT_ENABLE_IF(!is_supported<Float>::value)>
bool assign(Float) {
FMT_ASSERT(false, "");
return false;
}
};
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
template <int SHIFT = 0> FMT_CONSTEXPR fp normalize(fp value) {
// Handle subnormals.
const uint64_t implicit_bit = 1ULL << num_significand_bits<double>();
const auto shifted_implicit_bit = implicit_bit << SHIFT;
while ((value.f & shifted_implicit_bit) == 0) {
value.f <<= 1;
--value.e;
}
// Subtract 1 to account for hidden bit.
const auto offset =
fp::num_significand_bits - num_significand_bits<double>() - SHIFT - 1;
value.f <<= offset;
value.e -= offset;
return value;
}
inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
#if FMT_USE_INT128
auto product = static_cast<__uint128_t>(lhs) * rhs;
auto f = static_cast<uint64_t>(product >> 64);
return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;
#else
// Multiply 32-bit parts of significands.
uint64_t mask = (1ULL << 32) - 1;
uint64_t a = lhs >> 32, b = lhs & mask;
uint64_t c = rhs >> 32, d = rhs & mask;
uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
// Compute mid 64-bit of result and round.
uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);
#endif
}
FMT_CONSTEXPR inline fp operator*(fp x, fp y) {
return {multiply(x.f, y.f), x.e + y.e + 64};
}
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
int& pow10_exponent) {
const int shift = 32; const int shift = 32;
const auto significand = static_cast<int64_t>(data::log10_2_significand); const auto significand = static_cast<int64_t>(log10_2_significand);
int index = static_cast<int>( int index = static_cast<int>(
((min_exponent + fp::significand_size - 1) * (significand >> shift) + ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) +
((int64_t(1) << shift) - 1)) // ceil ((int64_t(1) << shift) - 1)) // ceil
>> 32 // arithmetic shift >> 32 // arithmetic shift
); );
@ -336,7 +342,8 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
const int dec_exp_step = 8; const int dec_exp_step = 8;
index = (index - first_dec_exp - 1) / dec_exp_step + 1; index = (index - first_dec_exp - 1) / dec_exp_step + 1;
pow10_exponent = first_dec_exp + index * dec_exp_step; pow10_exponent = first_dec_exp + index * dec_exp_step;
return {pow10_significands[index], pow10_exponents[index]}; return {impl_data::pow10_significands[index],
impl_data::pow10_exponents[index]};
} }
// A simple accumulator to hold the sums of terms in bigint::square if uint128_t // A simple accumulator to hold the sums of terms in bigint::square if uint128_t
@ -345,14 +352,16 @@ struct accumulator {
uint64_t lower; uint64_t lower;
uint64_t upper; uint64_t upper;
accumulator() : lower(0), upper(0) {} constexpr accumulator() : lower(0), upper(0) {}
explicit operator uint32_t() const { return static_cast<uint32_t>(lower); } constexpr explicit operator uint32_t() const {
return static_cast<uint32_t>(lower);
}
void operator+=(uint64_t n) { FMT_CONSTEXPR void operator+=(uint64_t n) {
lower += n; lower += n;
if (lower < n) ++upper; if (lower < n) ++upper;
} }
void operator>>=(int shift) { FMT_CONSTEXPR void operator>>=(int shift) {
FMT_ASSERT(shift == 32, ""); FMT_ASSERT(shift == 32, "");
(void)shift; (void)shift;
lower = (upper << 32) | (lower >> 32); lower = (upper << 32) | (lower >> 32);
@ -370,27 +379,31 @@ class bigint {
basic_memory_buffer<bigit, bigits_capacity> bigits_; basic_memory_buffer<bigit, bigits_capacity> bigits_;
int exp_; int exp_;
bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } FMT_CONSTEXPR20 bigit operator[](int index) const {
bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } return bigits_[to_unsigned(index)];
}
FMT_CONSTEXPR20 bigit& operator[](int index) {
return bigits_[to_unsigned(index)];
}
static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value; static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
friend struct formatter<bigint>; friend struct formatter<bigint>;
void subtract_bigits(int index, bigit other, bigit& borrow) { FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) {
auto result = static_cast<double_bigit>((*this)[index]) - other - borrow; auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
(*this)[index] = static_cast<bigit>(result); (*this)[index] = static_cast<bigit>(result);
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1)); borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
} }
void remove_leading_zeros() { FMT_CONSTEXPR20 void remove_leading_zeros() {
int num_bigits = static_cast<int>(bigits_.size()) - 1; int num_bigits = static_cast<int>(bigits_.size()) - 1;
while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
bigits_.resize(to_unsigned(num_bigits + 1)); bigits_.resize(to_unsigned(num_bigits + 1));
} }
// Computes *this -= other assuming aligned bigints and *this >= other. // Computes *this -= other assuming aligned bigints and *this >= other.
void subtract_aligned(const bigint& other) { FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) {
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
FMT_ASSERT(compare(*this, other) >= 0, ""); FMT_ASSERT(compare(*this, other) >= 0, "");
bigit borrow = 0; bigit borrow = 0;
@ -401,7 +414,7 @@ class bigint {
remove_leading_zeros(); remove_leading_zeros();
} }
void multiply(uint32_t value) { FMT_CONSTEXPR20 void multiply(uint32_t value) {
const double_bigit wide_value = value; const double_bigit wide_value = value;
bigit carry = 0; bigit carry = 0;
for (size_t i = 0, n = bigits_.size(); i < n; ++i) { for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
@ -412,7 +425,7 @@ class bigint {
if (carry != 0) bigits_.push_back(carry); if (carry != 0) bigits_.push_back(carry);
} }
void multiply(uint64_t value) { FMT_CONSTEXPR20 void multiply(uint64_t value) {
const bigit mask = ~bigit(0); const bigit mask = ~bigit(0);
const double_bigit lower = value & mask; const double_bigit lower = value & mask;
const double_bigit upper = value >> bigit_bits; const double_bigit upper = value >> bigit_bits;
@ -430,14 +443,16 @@ class bigint {
} }
public: public:
bigint() : exp_(0) {} FMT_CONSTEXPR20 bigint() : exp_(0) {}
explicit bigint(uint64_t n) { assign(n); } explicit bigint(uint64_t n) { assign(n); }
~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); } FMT_CONSTEXPR20 ~bigint() {
FMT_ASSERT(bigits_.capacity() <= bigits_capacity, "");
}
bigint(const bigint&) = delete; bigint(const bigint&) = delete;
void operator=(const bigint&) = delete; void operator=(const bigint&) = delete;
void assign(const bigint& other) { FMT_CONSTEXPR20 void assign(const bigint& other) {
auto size = other.bigits_.size(); auto size = other.bigits_.size();
bigits_.resize(size); bigits_.resize(size);
auto data = other.bigits_.data(); auto data = other.bigits_.data();
@ -445,7 +460,7 @@ class bigint {
exp_ = other.exp_; exp_ = other.exp_;
} }
void assign(uint64_t n) { FMT_CONSTEXPR20 void assign(uint64_t n) {
size_t num_bigits = 0; size_t num_bigits = 0;
do { do {
bigits_[num_bigits++] = n & ~bigit(0); bigits_[num_bigits++] = n & ~bigit(0);
@ -455,9 +470,11 @@ class bigint {
exp_ = 0; exp_ = 0;
} }
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; } FMT_CONSTEXPR20 int num_bigits() const {
return static_cast<int>(bigits_.size()) + exp_;
}
FMT_NOINLINE bigint& operator<<=(int shift) { FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) {
FMT_ASSERT(shift >= 0, ""); FMT_ASSERT(shift >= 0, "");
exp_ += shift / bigit_bits; exp_ += shift / bigit_bits;
shift %= bigit_bits; shift %= bigit_bits;
@ -472,13 +489,13 @@ class bigint {
return *this; return *this;
} }
template <typename Int> bigint& operator*=(Int value) { template <typename Int> FMT_CONSTEXPR20 bigint& operator*=(Int value) {
FMT_ASSERT(value > 0, ""); FMT_ASSERT(value > 0, "");
multiply(uint32_or_64_or_128_t<Int>(value)); multiply(uint32_or_64_or_128_t<Int>(value));
return *this; return *this;
} }
friend int compare(const bigint& lhs, const bigint& rhs) { friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) {
int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
if (num_lhs_bigits != num_rhs_bigits) if (num_lhs_bigits != num_rhs_bigits)
return num_lhs_bigits > num_rhs_bigits ? 1 : -1; return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
@ -495,7 +512,7 @@ class bigint {
} }
// Returns compare(lhs1 + lhs2, rhs). // Returns compare(lhs1 + lhs2, rhs).
friend int add_compare(const bigint& lhs1, const bigint& lhs2, friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2,
const bigint& rhs) { const bigint& rhs) {
int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits()); int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
int num_rhs_bigits = rhs.num_bigits(); int num_rhs_bigits = rhs.num_bigits();
@ -519,7 +536,7 @@ class bigint {
} }
// Assigns pow(10, exp) to this bigint. // Assigns pow(10, exp) to this bigint.
void assign_pow10(int exp) { FMT_CONSTEXPR20 void assign_pow10(int exp) {
FMT_ASSERT(exp >= 0, ""); FMT_ASSERT(exp >= 0, "");
if (exp == 0) return assign(1); if (exp == 0) return assign(1);
// Find the top bit. // Find the top bit.
@ -538,7 +555,7 @@ class bigint {
*this <<= exp; // Multiply by pow(2, exp) by shifting. *this <<= exp; // Multiply by pow(2, exp) by shifting.
} }
void square() { FMT_CONSTEXPR20 void square() {
int num_bigits = static_cast<int>(bigits_.size()); int num_bigits = static_cast<int>(bigits_.size());
int num_result_bigits = 2 * num_bigits; int num_result_bigits = 2 * num_bigits;
basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_)); basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
@ -563,14 +580,13 @@ class bigint {
(*this)[bigit_index] = static_cast<bigit>(sum); (*this)[bigit_index] = static_cast<bigit>(sum);
sum >>= bits<bigit>::value; sum >>= bits<bigit>::value;
} }
--num_result_bigits;
remove_leading_zeros(); remove_leading_zeros();
exp_ *= 2; exp_ *= 2;
} }
// If this bigint has a bigger exponent than other, adds trailing zero to make // If this bigint has a bigger exponent than other, adds trailing zero to make
// exponents equal. This simplifies some operations such as subtraction. // exponents equal. This simplifies some operations such as subtraction.
void align(const bigint& other) { FMT_CONSTEXPR20 void align(const bigint& other) {
int exp_difference = exp_ - other.exp_; int exp_difference = exp_ - other.exp_;
if (exp_difference <= 0) return; if (exp_difference <= 0) return;
int num_bigits = static_cast<int>(bigits_.size()); int num_bigits = static_cast<int>(bigits_.size());
@ -583,7 +599,7 @@ class bigint {
// Divides this bignum by divisor, assigning the remainder to this and // Divides this bignum by divisor, assigning the remainder to this and
// returning the quotient. // returning the quotient.
int divmod_assign(const bigint& divisor) { FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) {
FMT_ASSERT(this != &divisor, ""); FMT_ASSERT(this != &divisor, "");
if (compare(*this, divisor) < 0) return 0; if (compare(*this, divisor) < 0) return 0;
FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
@ -603,7 +619,8 @@ enum class round_direction { unknown, up, down };
// some number v and the error, returns whether v should be rounded up, down, or // 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. // whether the rounding direction can't be determined due to error.
// error should be less than divisor / 2. // error should be less than divisor / 2.
inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder, FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor,
uint64_t remainder,
uint64_t error) { uint64_t error) {
FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow.
FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow.
@ -627,19 +644,52 @@ enum result {
}; };
} }
inline uint64_t power_of_10_64(int exp) { struct gen_digits_handler {
static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1), char* buf;
FMT_POWERS_OF_10(1000000000ULL), int size;
10000000000000000000ULL}; int precision;
return data[exp]; int exp10;
} bool fixed;
FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor,
uint64_t remainder, uint64_t error,
bool integral) {
FMT_ASSERT(remainder < divisor, "");
buf[size++] = digit;
if (!integral && error >= remainder) return digits::error;
if (size < precision) return digits::more;
if (!integral) {
// Check if error * 2 < divisor with overflow prevention.
// The check is not needed for the integral part because error = 1
// and divisor > (1 << 32) there.
if (error >= divisor || error >= divisor - error) return digits::error;
} else {
FMT_ASSERT(error == 1 && divisor > 2, "");
}
auto dir = get_round_direction(divisor, remainder, error);
if (dir != round_direction::up)
return dir == round_direction::down ? digits::done : digits::error;
++buf[size - 1];
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
buf[i] = '0';
++buf[i - 1];
}
if (buf[0] > '9') {
buf[0] = '1';
if (fixed)
buf[size++] = '0';
else
++exp10;
}
return digits::done;
}
};
// Generates output using the Grisu digit-gen algorithm. // Generates output using the Grisu digit-gen algorithm.
// error: the size of the region (lower, upper) outside of which numbers // error: the size of the region (lower, upper) outside of which numbers
// definitely do not round to value (Delta in Grisu3). // definitely do not round to value (Delta in Grisu3).
template <typename Handler> FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits(
FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, fp value, uint64_t error, int& exp, gen_digits_handler& handler) {
Handler& handler) {
const fp one(1ULL << -value.e, value.e); const fp one(1ULL << -value.e, value.e);
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be // 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 // zero because it contains a product of two 64-bit numbers with MSB set (due
@ -650,10 +700,28 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
// The fractional part of scaled value (p2 in Grisu) c = value % one. // The fractional part of scaled value (p2 in Grisu) c = value % one.
uint64_t fractional = value.f & (one.f - 1); uint64_t fractional = value.f & (one.f - 1);
exp = count_digits(integral); // kappa in Grisu. exp = count_digits(integral); // kappa in Grisu.
// Non-fixed formats require at least one digit and no precision adjustment.
if (handler.fixed) {
// Adjust fixed precision by exponent because it is relative to decimal
// point.
int precision_offset = exp + handler.exp10;
if (precision_offset > 0 &&
handler.precision > max_value<int>() - precision_offset) {
FMT_THROW(format_error("number is too big"));
}
handler.precision += precision_offset;
// Check if precision is satisfied just by leading zeros, e.g.
// format("{:.2f}", 0.001) gives "0.00" without generating any digits.
if (handler.precision <= 0) {
if (handler.precision < 0) return digits::done;
// Divide by 10 to prevent overflow. // Divide by 10 to prevent overflow.
auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e, uint64_t divisor = impl_data::power_of_10_64[exp - 1] << -one.e;
value.f / 10, error * 10, exp); auto dir = get_round_direction(divisor, value.f / 10, error * 10);
if (result != digits::more) return result; 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. // Generate digits for the integral part. This can produce up to 10 digits.
do { do {
uint32_t digit = 0; uint32_t digit = 0;
@ -700,9 +768,9 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
} }
--exp; --exp;
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional; auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
result = handler.on_digit(static_cast<char>('0' + digit), auto result = handler.on_digit(static_cast<char>('0' + digit),
power_of_10_64(exp) << -one.e, remainder, error, impl_data::power_of_10_64[exp] << -one.e,
exp, true); remainder, error, true);
if (result != digits::more) return result; if (result != digits::more) return result;
} while (exp > 0); } while (exp > 0);
// Generate digits for the fractional part. // Generate digits for the fractional part.
@ -712,69 +780,11 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
char digit = static_cast<char>('0' + (fractional >> -one.e)); char digit = static_cast<char>('0' + (fractional >> -one.e));
fractional &= one.f - 1; fractional &= one.f - 1;
--exp; --exp;
result = handler.on_digit(digit, one.f, fractional, error, exp, false); auto result = handler.on_digit(digit, one.f, fractional, error, false);
if (result != digits::more) return result; if (result != digits::more) return result;
} }
} }
// The fixed precision digit handler.
struct fixed_handler {
char* buf;
int size;
int precision;
int exp10;
bool fixed;
digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
int& exp) {
// Non-fixed formats require at least one digit and no precision adjustment.
if (!fixed) return digits::more;
// Adjust fixed precision by exponent because it is relative to decimal
// point.
precision += exp + exp10;
// Check if precision is satisfied just by leading zeros, e.g.
// format("{:.2f}", 0.001) gives "0.00" without generating any digits.
if (precision > 0) return digits::more;
if (precision < 0) return digits::done;
auto dir = get_round_direction(divisor, remainder, error);
if (dir == round_direction::unknown) return digits::error;
buf[size++] = dir == round_direction::up ? '1' : '0';
return digits::done;
}
digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
uint64_t error, int, bool integral) {
FMT_ASSERT(remainder < divisor, "");
buf[size++] = digit;
if (!integral && error >= remainder) return digits::error;
if (size < precision) return digits::more;
if (!integral) {
// Check if error * 2 < divisor with overflow prevention.
// The check is not needed for the integral part because error = 1
// and divisor > (1 << 32) there.
if (error >= divisor || error >= divisor - error) return digits::error;
} else {
FMT_ASSERT(error == 1 && divisor > 2, "");
}
auto dir = get_round_direction(divisor, remainder, error);
if (dir != round_direction::up)
return dir == round_direction::down ? digits::done : digits::error;
++buf[size - 1];
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
buf[i] = '0';
++buf[i - 1];
}
if (buf[0] > '9') {
buf[0] = '1';
if (fixed)
buf[size++] = '0';
else
++exp10;
}
return digits::done;
}
};
// A 128-bit integer type used internally, // A 128-bit integer type used internally,
struct uint128_wrapper { struct uint128_wrapper {
uint128_wrapper() = default; uint128_wrapper() = default;
@ -898,8 +908,7 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT {
inline int floor_log10_pow2(int e) FMT_NOEXCEPT { inline int floor_log10_pow2(int e) FMT_NOEXCEPT {
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const int shift = 22; const int shift = 22;
return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >> return (e * static_cast<int>(log10_2_significand >> (64 - shift))) >> shift;
shift;
} }
// Various fast log computations. // Various fast log computations.
@ -917,8 +926,7 @@ inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT {
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375; const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375;
const int shift_amount = 22; const int shift_amount = 22;
return (e * static_cast<int>(data::log10_2_significand >> return (e * static_cast<int>(log10_2_significand >> (64 - shift_amount)) -
(64 - shift_amount)) -
static_cast<int>(log10_4_over_3_fractional_digits >> static_cast<int>(log10_4_over_3_fractional_digits >>
(64 - shift_amount))) >> (64 - shift_amount))) >>
shift_amount; shift_amount;
@ -1043,7 +1051,7 @@ template <> struct cache_accessor<float> {
static uint64_t get_cached_power(int k) FMT_NOEXCEPT { static uint64_t get_cached_power(int k) FMT_NOEXCEPT {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k, FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range"); "k is out of range");
constexpr const uint64_t pow10_significands[] = { static constexpr const uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
@ -2211,11 +2219,11 @@ small_divisor_case_label:
} }
} // namespace dragonbox } // namespace dragonbox
// Formats value using a variation of the Fixed-Precision Positive // Formats a floating-point number using a variation of the Fixed-Precision
// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
// https://fmt.dev/papers/p372-steele.pdf. // https://fmt.dev/papers/p372-steele.pdf.
template <typename Double> FMT_CONSTEXPR20 inline void format_dragon(fp value, bool is_predecessor_closer,
void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf, int num_digits, buffer<char>& buf,
int& exp10) { int& exp10) {
bigint numerator; // 2 * R in (FPP)^2. bigint numerator; // 2 * R in (FPP)^2.
bigint denominator; // 2 * S in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2.
@ -2223,12 +2231,9 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
bigint lower; // (M^- in (FPP)^2). bigint lower; // (M^- in (FPP)^2).
bigint upper_store; // upper's value if different from lower. bigint upper_store; // upper's value if different from lower.
bigint* upper = nullptr; // (M^+ in (FPP)^2). bigint* upper = nullptr; // (M^+ in (FPP)^2).
fp value;
// Shift numerator and denominator by an extra bit or two (if lower boundary // Shift numerator and denominator by an extra bit or two (if lower boundary
// is closer) to make lower and upper integers. This eliminates multiplication // is closer) to make lower and upper integers. This eliminates multiplication
// by 2 during later computations. // by 2 during later computations.
const bool is_predecessor_closer =
binary32 ? value.assign(static_cast<float>(d)) : value.assign(d);
int shift = is_predecessor_closer ? 2 : 1; int shift = is_predecessor_closer ? 2 : 1;
uint64_t significand = value.f << shift; uint64_t significand = value.f << shift;
if (value.e >= 0) { if (value.e >= 0) {
@ -2298,9 +2303,9 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
// Generate the given number of digits. // Generate the given number of digits.
exp10 -= num_digits - 1; exp10 -= num_digits - 1;
if (num_digits == 0) { if (num_digits == 0) {
buf.try_resize(1);
denominator *= 10; denominator *= 10;
buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
buf.push_back(digit);
return; return;
} }
buf.try_resize(to_unsigned(num_digits)); buf.try_resize(to_unsigned(num_digits));
@ -2331,9 +2336,12 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
buf[num_digits - 1] = static_cast<char>('0' + digit); buf[num_digits - 1] = static_cast<char>('0' + digit);
} }
template <typename T> template <typename Float>
int format_float(T value, int precision, float_specs specs, buffer<char>& buf) { FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
static_assert(!std::is_same<T, float>::value, ""); float_specs specs,
buffer<char>& buf) {
// float is passed as double to reduce the number of instantiations.
static_assert(!std::is_same<Float, float>::value, "");
FMT_ASSERT(value >= 0, "value is negative"); FMT_ASSERT(value >= 0, "value is negative");
const bool fixed = specs.format == float_format::fixed; const bool fixed = specs.format == float_format::fixed;
@ -2343,13 +2351,13 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
return 0; return 0;
} }
buf.try_resize(to_unsigned(precision)); buf.try_resize(to_unsigned(precision));
std::uninitialized_fill_n(buf.data(), precision, '0'); fill_n(buf.data(), precision, '0');
return -precision; return -precision;
} }
if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); if (specs.fallback) return snprintf_float(value, precision, specs, buf);
if (precision < 0) { if (!is_constant_evaluated() && precision < 0) {
// Use Dragonbox for the shortest format. // Use Dragonbox for the shortest format.
if (specs.binary32) { if (specs.binary32) {
auto dec = dragonbox::to_decimal(static_cast<float>(value)); auto dec = dragonbox::to_decimal(static_cast<float>(value));
@ -2361,26 +2369,37 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
return dec.exponent; return dec.exponent;
} }
int exp = 0;
bool use_dragon = true;
if (is_fast_float<Float>()) {
// Use Grisu + Dragon4 for the given precision: // Use Grisu + Dragon4 for the given precision:
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
int exp = 0;
const int min_exp = -60; // alpha in Grisu. const int min_exp = -60; // alpha in Grisu.
int cached_exp10 = 0; // K in Grisu. int cached_exp10 = 0; // K in Grisu.
fp normalized = normalize(fp(value)); fp normalized = normalize(fp(value));
const auto cached_pow = get_cached_power( const auto cached_pow = get_cached_power(
min_exp - (normalized.e + fp::significand_size), cached_exp10); min_exp - (normalized.e + fp::num_significand_bits), cached_exp10);
normalized = normalized * cached_pow; normalized = normalized * cached_pow;
// Limit precision to the maximum possible number of significant digits in an gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
// IEEE754 double because we don't need to generate zeros. if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error &&
const int max_double_digits = 767; !is_constant_evaluated()) {
if (precision > max_double_digits) precision = max_double_digits;
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) {
exp += handler.size - cached_exp10 - 1;
fallback_format(value, handler.precision, specs.binary32, buf, exp);
} else {
exp += handler.exp10; exp += handler.exp10;
buf.try_resize(to_unsigned(handler.size)); buf.try_resize(to_unsigned(handler.size));
use_dragon = false;
} else {
exp += handler.size - cached_exp10 - 1;
precision = handler.precision;
}
}
if (use_dragon) {
auto f = fp();
bool is_predecessor_closer =
specs.binary32 ? f.assign(static_cast<float>(value)) : f.assign(value);
// Limit precision to the maximum possible number of significant digits in
// an IEEE754 double because we don't need to generate zeros.
const int max_double_digits = 767;
if (precision > max_double_digits) precision = max_double_digits;
format_dragon(f, is_predecessor_closer, precision, buf, exp);
} }
if (!fixed && !specs.showpoint) { if (!fixed && !specs.showpoint) {
// Remove trailing zeros. // Remove trailing zeros.
@ -2392,7 +2411,7 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
buf.try_resize(num_digits); buf.try_resize(num_digits);
} }
return exp; return exp;
} // namespace detail }
template <typename T> template <typename T>
int snprintf_float(T value, int precision, float_specs specs, int snprintf_float(T value, int precision, float_specs specs,
@ -2526,8 +2545,8 @@ template <> struct formatter<detail::bigint> {
}; };
FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
for_each_codepoint(s, [this](uint32_t cp, int error) { for_each_codepoint(s, [this](uint32_t cp, string_view) {
if (error != 0) FMT_THROW(std::runtime_error("invalid utf8")); if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8"));
if (cp <= 0xFFFF) { if (cp <= 0xFFFF) {
buffer_.push_back(static_cast<wchar_t>(cp)); buffer_.push_back(static_cast<wchar_t>(cp));
} else { } else {
@ -2535,6 +2554,7 @@ FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10))); buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));
buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF))); buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));
} }
return true;
}); });
buffer_.push_back(0); buffer_.push_back(0);
} }
@ -2550,15 +2570,17 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
format_error_code(out, error_code, message); format_error_code(out, error_code, message);
} }
FMT_FUNC void detail::error_handler::on_error(const char* message) {
FMT_THROW(format_error(message));
}
FMT_FUNC void report_system_error(int error_code, FMT_FUNC void report_system_error(int error_code,
const char* message) FMT_NOEXCEPT { const char* message) FMT_NOEXCEPT {
report_error(format_system_error, error_code, message); report_error(format_system_error, error_code, message);
} }
// DEPRECATED!
// This function is defined here and not inline for ABI compatiblity.
FMT_FUNC void detail::error_handler::on_error(const char* message) {
throw_format_error(message);
}
FMT_FUNC std::string vformat(string_view fmt, format_args args) { FMT_FUNC std::string vformat(string_view fmt, format_args args) {
// Don't optimize the "{}" case to keep the binary size small and because it // Don't optimize the "{}" case to keep the binary size small and because it
// can be better optimized in fmt::format anyway. // can be better optimized in fmt::format anyway.

File diff suppressed because it is too large Load diff

View file

@ -21,17 +21,20 @@
#include "format.h" #include "format.h"
#ifndef FMT_USE_FCNTL
// UWP doesn't provide _pipe. // UWP doesn't provide _pipe.
#if FMT_HAS_INCLUDE("winapifamily.h") # if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h> # include <winapifamily.h>
#endif # endif
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \ # if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \ defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) (!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY # include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1 # define FMT_USE_FCNTL 1
#else # else
# define FMT_USE_FCNTL 0 # define FMT_USE_FCNTL 0
# endif
#endif #endif
#ifndef FMT_POSIX #ifndef FMT_POSIX
@ -390,23 +393,26 @@ struct ostream_params {
: ostream_params(params...) { : ostream_params(params...) {
this->buffer_size = bs.value; this->buffer_size = bs.value;
} }
// Intel has a bug that results in failure to deduce a constructor
// for empty parameter packs.
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
ostream_params(int new_oflag) : oflag(new_oflag) {}
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
# endif
}; };
FMT_END_DETAIL_NAMESPACE FMT_END_DETAIL_NAMESPACE
static constexpr detail::buffer_size buffer_size; // Added {} below to work around default constructor error known to
// occur in Xcode versions 7.2.1 and 8.2.1.
constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */ /** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> { class FMT_API ostream final : private detail::buffer<char> {
private: private:
file file_; file file_;
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
void grow(size_t) override; void grow(size_t) override;
ostream(cstring_view path, const detail::ostream_params& params) ostream(cstring_view path, const detail::ostream_params& params)
@ -426,6 +432,12 @@ class FMT_API ostream final : private detail::buffer<char> {
delete[] data(); delete[] data();
} }
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
template <typename... T> template <typename... T>
friend ostream output_file(cstring_view path, T... params); friend ostream output_file(cstring_view path, T... params);
@ -500,7 +512,7 @@ class locale {
// Converts string to floating-point number and advances str past the end // Converts string to floating-point number and advances str past the end
// of the parsed input. // of the parsed input.
double strtod(const char*& str) const { FMT_DEPRECATED double strtod(const char*& str) const {
char* end = nullptr; char* end = nullptr;
double result = strtod_l(str, &end, locale_); double result = strtod_l(str, &end, locale_);
str = end; str = end;

View file

@ -14,73 +14,20 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> class basic_printf_parse_context;
template <typename OutputIt, typename Char> class basic_printf_context; template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail { namespace detail {
template <class Char> class formatbuf : public std::basic_streambuf<Char> { // Checks if T has a user-defined operator<<.
private: template <typename T, typename Char, typename Enable = void>
using int_type = typename std::basic_streambuf<Char>::int_type; class is_streamable {
using traits_type = typename std::basic_streambuf<Char>::traits_type;
buffer<Char>& buffer_;
public:
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
protected:
// The put-area is actually always empty. This makes the implementation
// simpler and has the advantage that the streambuf and the buffer are always
// in sync and sputc never writes into uninitialized memory. The obvious
// disadvantage is that each call to sputc always results in a (virtual) call
// to overflow. There is no disadvantage here for sputn since this always
// results in a call to xsputn.
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<Char>(ch));
return ch;
}
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
buffer_.append(s, s + count);
return count;
}
};
struct converter {
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
};
template <typename Char> struct test_stream : std::basic_ostream<Char> {
private:
void_t<> operator<<(converter);
};
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
template <typename T, typename Char> class is_streamable {
private: private:
template <typename U> template <typename U>
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>() static auto test(int)
<< std::declval<U>()), -> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
void_t<>>::value> << std::declval<U>()) != 0>;
test(int);
template <typename> static std::false_type test(...); template <typename> static auto test(...) -> std::false_type;
using result = decltype(test<T>(0)); using result = decltype(test<T>(0));
@ -90,7 +37,21 @@ template <typename T, typename Char> class is_streamable {
static const bool value = result::value; 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_same<T, std::basic_string<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 {};
// Write the content of buf to os. // Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char> template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data(); const Char* buf_data = buf.data();
@ -108,8 +69,8 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
template <typename Char, typename T> template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value, void format_value(buffer<Char>& buf, const T& value,
locale_ref loc = locale_ref()) { locale_ref loc = locale_ref()) {
formatbuf<Char> format_buf(buf); auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
std::basic_ostream<Char> output(&format_buf); auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>()); if (loc) output.imbue(loc.get<std::locale>());
#endif #endif
@ -122,29 +83,22 @@ void format_value(buffer<Char>& buf, const T& value,
template <typename T, typename Char> template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: private formatter<basic_string_view<Char>, Char> { : private formatter<basic_string_view<Char>, Char> {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) using formatter<basic_string_view<Char>, Char>::parse;
-> decltype(ctx.begin()) {
return formatter<basic_string_view<Char>, Char>::parse(ctx);
}
template <typename ParseCtx,
FMT_ENABLE_IF(std::is_same<
ParseCtx, basic_printf_parse_context<Char>>::value)>
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename OutputIt> template <typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
-> OutputIt { -> OutputIt {
basic_memory_buffer<Char> buffer; auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale()); format_value(buffer, value, ctx.locale());
basic_string_view<Char> str(buffer.data(), buffer.size()); return formatter<basic_string_view<Char>, Char>::format(
return formatter<basic_string_view<Char>, Char>::format(str, ctx); {buffer.data(), buffer.size()}, ctx);
} }
// DEPRECATED!
template <typename OutputIt> template <typename OutputIt>
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx) auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
-> OutputIt { -> OutputIt {
basic_memory_buffer<Char> buffer; auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale()); format_value(buffer, value, ctx.locale());
return std::copy(buffer.begin(), buffer.end(), ctx.out()); return std::copy(buffer.begin(), buffer.end(), ctx.out());
} }
@ -155,7 +109,7 @@ FMT_MODULE_EXPORT
template <typename Char> template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer; auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args); detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer); detail::write_buffer(os, buffer);
} }

View file

@ -233,7 +233,7 @@ class printf_arg_formatter : public arg_formatter<Char> {
OutputIt write_null_pointer(bool is_string = false) { OutputIt write_null_pointer(bool is_string = false) {
auto s = this->specs; auto s = this->specs;
s.type = 0; s.type = presentation_type::none;
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s); return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
} }
@ -249,8 +249,10 @@ class printf_arg_formatter : public arg_formatter<Char> {
// std::is_same instead. // std::is_same instead.
if (std::is_same<T, Char>::value) { if (std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs; format_specs fmt_specs = this->specs;
if (fmt_specs.type && fmt_specs.type != 'c') if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value)); return (*this)(static_cast<int>(value));
}
fmt_specs.sign = sign::none; fmt_specs.sign = sign::none;
fmt_specs.alt = false; fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
@ -271,13 +273,13 @@ class printf_arg_formatter : public arg_formatter<Char> {
/** Formats a null-terminated C string. */ /** Formats a null-terminated C string. */
OutputIt operator()(const char* value) { OutputIt operator()(const char* value) {
if (value) return base::operator()(value); if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != 'p'); return write_null_pointer(this->specs.type != presentation_type::pointer);
} }
/** Formats a null-terminated wide C string. */ /** Formats a null-terminated wide C string. */
OutputIt operator()(const wchar_t* value) { OutputIt operator()(const wchar_t* value) {
if (value) return base::operator()(value); if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != 'p'); return write_null_pointer(this->specs.type != presentation_type::pointer);
} }
OutputIt operator()(basic_string_view<Char> value) { OutputIt operator()(basic_string_view<Char> value) {
@ -490,13 +492,13 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
// Parse type. // Parse type.
if (it == end) FMT_THROW(format_error("invalid format string")); if (it == end) FMT_THROW(format_error("invalid format string"));
specs.type = static_cast<char>(*it++); char type = static_cast<char>(*it++);
if (arg.is_integral()) { if (arg.is_integral()) {
// Normalize type. // Normalize type.
switch (specs.type) { switch (type) {
case 'i': case 'i':
case 'u': case 'u':
specs.type = 'd'; type = 'd';
break; break;
case 'c': case 'c':
visit_format_arg( visit_format_arg(
@ -505,6 +507,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
break; break;
} }
} }
specs.type = parse_presentation_type(type);
if (specs.type == presentation_type::none)
parse_ctx.on_error("invalid type specifier");
start = it; start = it;

View file

@ -13,37 +13,13 @@
#define FMT_RANGES_H_ #define FMT_RANGES_H_
#include <initializer_list> #include <initializer_list>
#include <tuple>
#include <type_traits> #include <type_traits>
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char, typename Enable = void> struct formatting_range {
#ifdef FMT_DEPRECATED_BRACED_RANGES
Char prefix = '{';
Char postfix = '}';
#else
Char prefix = '[';
Char postfix = ']';
#endif
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
};
template <typename Char, typename Enable = void> struct formatting_tuple {
Char prefix = '(';
Char postfix = ')';
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
};
namespace detail { namespace detail {
template <typename RangeT, typename OutputIterator> template <typename RangeT, typename OutputIterator>
@ -71,7 +47,7 @@ OutputIterator copy(wchar_t ch, OutputIterator out) {
return out; return out;
} }
/// Return true value if T has std::string interface, like std::string_view. // Returns true if T has a std::string-like interface, like std::string_view.
template <typename T> class is_std_string_like { template <typename T> class is_std_string_like {
template <typename U> template <typename U>
static auto check(U* p) static auto check(U* p)
@ -80,12 +56,40 @@ template <typename T> class is_std_string_like {
public: public:
static FMT_CONSTEXPR_DECL const bool value = static FMT_CONSTEXPR_DECL const bool value =
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value; is_string<T>::value ||
std::is_convertible<T, std_string_view<char>>::value ||
!std::is_void<decltype(check<T>(nullptr))>::value;
}; };
template <typename Char> template <typename Char>
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {}; struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
template <typename T> class is_map {
template <typename U> static auto check(U*) -> typename U::mapped_type;
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_MAP_AS_LIST
static FMT_CONSTEXPR_DECL const bool value = false;
#else
static FMT_CONSTEXPR_DECL const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
#endif
};
template <typename T> class is_set {
template <typename U> static auto check(U*) -> typename U::key_type;
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_SET_AS_LIST
static FMT_CONSTEXPR_DECL const bool value = false;
#else
static FMT_CONSTEXPR_DECL const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
#endif
};
template <typename... Ts> struct conditional_helper {}; template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {}; template <typename T, typename _ = void> struct is_range_ : std::false_type {};
@ -143,16 +147,16 @@ struct has_mutable_begin_end : std::false_type {};
template <typename T> template <typename T>
struct has_const_begin_end< struct has_const_begin_end<
T, void_t<decltype(detail::range_begin( T,
std::declval<const remove_cvref_t<T>&>())), void_t<
decltype(detail::range_begin( decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
std::declval<const remove_cvref_t<T>&>()))>> decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {}; : std::true_type {};
template <typename T> template <typename T>
struct has_mutable_begin_end< struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())), T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_begin(std::declval<T>())), decltype(detail::range_end(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>> enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {}; : std::true_type {};
@ -160,34 +164,10 @@ template <typename T>
struct is_range_<T, void> struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value || : std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {}; has_mutable_begin_end<T>::value)> {};
template <typename T, typename Enable = void> struct range_to_view;
template <typename T>
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
struct view_t {
const T* m_range_ptr;
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
};
static auto view(const T& range) -> view_t { return {&range}; }
};
template <typename T>
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
has_mutable_begin_end<T>::value>> {
struct view_t {
T m_range_copy;
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
};
static auto view(const T& range) -> view_t { return {range}; }
};
# undef FMT_DECLTYPE_RETURN # undef FMT_DECLTYPE_RETURN
#endif #endif
/// tuple_size and tuple_element check. // tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ { template <typename T> class is_tuple_like_ {
template <typename U> template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
@ -251,16 +231,295 @@ template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
return out; return out;
} }
template < struct singleton {
typename Char, typename OutputIt, typename Arg, unsigned char upper;
FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)> unsigned char lower_count;
OutputIt write_range_entry(OutputIt out, const Arg& v) { };
inline auto is_printable(uint16_t x, const singleton* singletons,
size_t singletons_size,
const unsigned char* singleton_lowers,
const unsigned char* normal, size_t normal_size)
-> bool {
auto upper = x >> 8;
auto lower_start = 0;
for (size_t i = 0; i < singletons_size; ++i) {
auto s = singletons[i];
auto lower_end = lower_start + s.lower_count;
if (upper < s.upper) break;
if (upper == s.upper) {
for (auto j = lower_start; j < lower_end; ++j) {
if (singleton_lowers[j] == (x & 0xff)) return false;
}
}
lower_start = lower_end;
}
auto xsigned = static_cast<int>(x);
auto current = true;
for (size_t i = 0; i < normal_size; ++i) {
auto v = static_cast<int>(normal[i]);
auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v;
xsigned -= len;
if (xsigned < 0) break;
current = !current;
}
return current;
}
// Returns true iff the code point cp is printable.
// This code is generated by support/printable.py.
inline auto is_printable(uint32_t cp) -> bool {
static constexpr singleton singletons0[] = {
{0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8},
{0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13},
{0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5},
{0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22},
{0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3},
{0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8},
{0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9},
};
static constexpr unsigned char singletons0_lower[] = {
0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90,
0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f,
0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1,
0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04,
0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d,
0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf,
0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d,
0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d,
0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d,
0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5,
0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7,
0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49,
0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7,
0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7,
0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e,
0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16,
0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e,
0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f,
0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf,
0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0,
0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27,
0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91,
0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7,
0xfe, 0xff,
};
static constexpr singleton singletons1[] = {
{0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2},
{0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5},
{0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5},
{0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2},
{0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5},
{0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2},
{0xfa, 2}, {0xfb, 1},
};
static constexpr unsigned char singletons1_lower[] = {
0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07,
0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36,
0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87,
0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b,
0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9,
0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66,
0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27,
0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc,
0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7,
0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6,
0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c,
0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66,
0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0,
0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93,
};
static constexpr unsigned char normal0[] = {
0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04,
0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0,
0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01,
0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03,
0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03,
0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a,
0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15,
0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f,
0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80,
0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07,
0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06,
0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04,
0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac,
0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c,
0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11,
0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c,
0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b,
0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6,
0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03,
0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80,
0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06,
0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c,
0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17,
0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80,
0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80,
0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d,
};
static constexpr unsigned char normal1[] = {
0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f,
0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e,
0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04,
0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09,
0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16,
0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f,
0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36,
0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33,
0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08,
0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e,
0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41,
0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03,
0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22,
0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04,
0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45,
0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03,
0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81,
0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75,
0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1,
0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a,
0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11,
0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09,
0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89,
0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6,
0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09,
0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50,
0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05,
0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83,
0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05,
0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80,
0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80,
0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07,
0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e,
0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07,
0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06,
};
auto lower = static_cast<uint16_t>(cp);
if (cp < 0x10000) {
return is_printable(lower, singletons0,
sizeof(singletons0) / sizeof(*singletons0),
singletons0_lower, normal0, sizeof(normal0));
}
if (cp < 0x20000) {
return is_printable(lower, singletons1,
sizeof(singletons1) / sizeof(*singletons1),
singletons1_lower, normal1, sizeof(normal1));
}
if (0x2a6de <= cp && cp < 0x2a700) return false;
if (0x2b735 <= cp && cp < 0x2b740) return false;
if (0x2b81e <= cp && cp < 0x2b820) return false;
if (0x2cea2 <= cp && cp < 0x2ceb0) return false;
if (0x2ebe1 <= cp && cp < 0x2f800) return false;
if (0x2fa1e <= cp && cp < 0x30000) return false;
if (0x3134b <= cp && cp < 0xe0100) return false;
if (0xe01f0 <= cp && cp < 0x110000) return false;
return cp < 0x110000;
}
inline auto needs_escape(uint32_t cp) -> bool {
return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' ||
!is_printable(cp);
}
template <typename Char> struct find_escape_result {
const Char* begin;
const Char* end;
uint32_t cp;
};
template <typename Char>
auto find_escape(const Char* begin, const Char* end)
-> find_escape_result<Char> {
for (; begin != end; ++begin) {
auto cp = static_cast<typename std::make_unsigned<Char>::type>(*begin);
if (sizeof(Char) == 1 && cp >= 0x80) continue;
if (needs_escape(cp)) return {begin, begin + 1, cp};
}
return {begin, nullptr, 0};
}
inline auto find_escape(const char* begin, const char* end)
-> find_escape_result<char> {
if (!is_utf8()) return find_escape<char>(begin, end);
auto result = find_escape_result<char>{end, nullptr, 0};
for_each_codepoint(string_view(begin, to_unsigned(end - begin)),
[&](uint32_t cp, string_view sv) {
if (needs_escape(cp)) {
result = {sv.begin(), sv.end(), cp};
return false;
}
return true;
});
return result;
}
template <typename Char, typename OutputIt>
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
*out++ = '"'; *out++ = '"';
out = write<Char>(out, v); auto begin = str.begin(), end = str.end();
do {
auto escape = find_escape(begin, end);
out = copy_str<Char>(begin, escape.begin, out);
begin = escape.end;
if (!begin) break;
auto c = static_cast<Char>(escape.cp);
switch (escape.cp) {
case '\n':
*out++ = '\\';
c = 'n';
break;
case '\r':
*out++ = '\\';
c = 'r';
break;
case '\t':
*out++ = '\\';
c = 't';
break;
case '"':
FMT_FALLTHROUGH;
case '\\':
*out++ = '\\';
break;
default:
if (is_utf8()) {
if (escape.cp < 0x100) {
out = format_to(out, "\\x{:02x}", escape.cp);
continue;
}
if (escape.cp < 0x10000) {
out = format_to(out, "\\u{:04x}", escape.cp);
continue;
}
if (escape.cp < 0x110000) {
out = format_to(out, "\\U{:08x}", escape.cp);
continue;
}
}
for (Char escape_char : basic_string_view<Char>(
escape.begin, to_unsigned(escape.end - escape.begin))) {
out = format_to(
out, "\\x{:02x}",
static_cast<typename std::make_unsigned<Char>::type>(escape_char));
}
continue;
}
*out++ = c;
} while (begin != end);
*out++ = '"'; *out++ = '"';
return out; return out;
} }
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, template <typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)> FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg v) { OutputIt write_range_entry(OutputIt out, const Arg v) {
@ -288,43 +547,37 @@ template <typename T> struct is_tuple_like {
template <typename TupleT, typename Char> template <typename TupleT, typename Char>
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> { struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
private: private:
// C++11 generic lambda for format() // C++11 generic lambda for format().
template <typename FormatContext> struct format_each { template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) { template <typename T> void operator()(const T& v) {
if (i > 0) out = detail::write_delimiter(out); if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, v); out = detail::write_range_entry<Char>(out, v);
++i; ++i;
} }
formatting_tuple<Char>& formatting; int i;
size_t& i; typename FormatContext::iterator& out;
typename std::add_lvalue_reference<
decltype(std::declval<FormatContext>().out())>::type out;
}; };
public: public:
formatting_tuple<Char> formatting;
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return formatting.parse(ctx); return ctx.begin();
} }
template <typename FormatContext = format_context> template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out(); auto out = ctx.out();
size_t i = 0; *out++ = '(';
detail::for_each(values, format_each<FormatContext>{0, out});
detail::copy(formatting.prefix, out); *out++ = ')';
detail::for_each(values, format_each<FormatContext>{formatting, i, out}); return out;
detail::copy(formatting.postfix, out);
return ctx.out();
} }
}; };
template <typename T, typename Char> struct is_range { template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value = static FMT_CONSTEXPR_DECL const bool value =
detail::is_range_<T>::value && !detail::is_std_string_like<T>::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_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value; !std::is_constructible<detail::std_string_view<Char>, T>::value;
}; };
@ -334,32 +587,80 @@ struct formatter<
T, Char, T, Char,
enable_if_t< enable_if_t<
fmt::is_range<T, Char>::value fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2017 and earlier. // Workaround a bug in MSVC 2019 and earlier.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 #if !FMT_MSC_VER
&& (has_formatter<detail::value_type<T>, format_context>::value || && (is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value) detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
#endif #endif
>> { >> {
formatting_range<Char> formatting;
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return formatting.parse(ctx); return ctx.begin();
} }
template <typename FormatContext> template <
typename FormatContext::iterator format(const T& values, FormatContext& ctx) { typename FormatContext, typename U,
auto out = detail::copy(formatting.prefix, ctx.out()); FMT_ENABLE_IF(
size_t i = 0; std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
auto view = detail::range_to_view<T>::view(values); const T, T>>::value)>
auto it = view.begin(); auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) {
auto end = view.end(); #ifdef FMT_DEPRECATED_BRACED_RANGES
Char prefix = '{';
Char postfix = '}';
#else
Char prefix = detail::is_set<T>::value ? '{' : '[';
Char postfix = detail::is_set<T>::value ? '}' : ']';
#endif
auto out = ctx.out();
*out++ = prefix;
int i = 0;
auto it = std::begin(range);
auto end = std::end(range);
for (; it != end; ++it) { for (; it != end; ++it) {
if (i > 0) out = detail::write_delimiter(out); if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, *it); out = detail::write_range_entry<Char>(out, *it);
++i; ++i;
} }
return detail::copy(formatting.postfix, out); *out++ = postfix;
return out;
}
};
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<
detail::is_map<T>::value
// Workaround a bug in MSVC 2019 and earlier.
#if !FMT_MSC_VER
&& (is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
#endif
>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
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) -> 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;
} }
}; };
@ -374,45 +675,70 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
template <typename Char, typename... T> template <typename Char, typename... T>
using tuple_arg_join = tuple_join_view<Char, 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.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T> template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> { struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) -> auto format(const tuple_join_view<Char, T...>& value,
typename FormatContext::iterator { FormatContext& ctx) const -> typename FormatContext::iterator {
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{}); return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
} }
private: private:
template <typename FormatContext, size_t... N> std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
detail::index_sequence<N...>) -> template <typename ParseContext>
typename FormatContext::iterator { FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
return format_args(value, ctx, std::get<N>(value.tuple)...); std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
FMT_THROW(format_error("incompatible format specs for tuple elements"));
}
#endif
return end;
} }
template <typename FormatContext> template <typename FormatContext>
auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) -> auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
// NOTE: for compilers that support C++17, this empty function instantiation
// can be replaced with a constexpr branch in the variadic overload.
return ctx.out(); return ctx.out();
} }
template <typename FormatContext, typename Arg, typename... Args> template <typename FormatContext, size_t N>
auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx, auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
const Arg& arg, const Args&... args) -> std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
using base = formatter<typename std::decay<Arg>::type, Char>; auto out = std::get<sizeof...(T) - N>(formatters_)
auto out = base().format(arg, ctx); .format(std::get<sizeof...(T) - N>(value.tuple), ctx);
if (sizeof...(Args) > 0) { if (N > 1) {
out = std::copy(value.sep.begin(), value.sep.end(), out); out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out); ctx.advance_to(out);
return format_args(value, ctx, args...); return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
} }
return out; return out;
} }

View file

@ -5,8 +5,8 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#ifndef FMT_WCHAR_H_ #ifndef FMT_XCHAR_H_
#define FMT_WCHAR_H_ #define FMT_XCHAR_H_
#include <cwchar> #include <cwchar>
#include <tuple> #include <tuple>
@ -142,7 +142,7 @@ FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
const S& format_str, Args&&... args) -> const S& format_str, Args&&... args) ->
typename buffer_context<Char>::iterator { typename buffer_context<Char>::iterator {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
detail::vformat_to(buf, to_string_view(format_str), vargs); detail::vformat_to(buf, to_string_view(format_str), vargs, {});
return detail::buffer_appender<Char>(buf); return detail::buffer_appender<Char>(buf);
} }
@ -217,11 +217,11 @@ inline void vprint(wstring_view fmt, wformat_args args) {
template <typename... T> template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) { void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), make_wformat_args(args...)); return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
} }
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) { template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(args...)); return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
} }
/** /**
@ -233,4 +233,4 @@ template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
FMT_MODULE_EXPORT_END FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_WCHAR_H_ #endif // FMT_XCHAR_H_

View file

@ -79,7 +79,6 @@ export module fmt;
#define FMT_END_DETAIL_NAMESPACE \ #define FMT_END_DETAIL_NAMESPACE \
} \ } \
export { export {
// all library-provided declarations and definitions // all library-provided declarations and definitions
// must be in the module purview to be exported // must be in the module purview to be exported
#include "fmt/args.h" #include "fmt/args.h"

View file

@ -10,6 +10,52 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// DEPRECATED!
template <typename T = void> struct basic_data {
FMT_API static constexpr const char digits[100][2] = {
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
{'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
{'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
{'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
{'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
{'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
{'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
{'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
{'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
{'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
{'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
{'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
{'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
{'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1,
0};
FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1,
0};
FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
0x1000000u | ' '};
};
#ifdef FMT_SHARED
// Required for -flto, -fivisibility=hidden and -shared to work
extern template struct basic_data<void>;
#endif
#if __cplusplus < 201703L
// DEPRECATED! These are here only for ABI compatiblity.
template <typename T> constexpr const char basic_data<T>::digits[][2];
template <typename T> constexpr const char basic_data<T>::hex_digits[];
template <typename T> constexpr const char basic_data<T>::signs[];
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
template <typename T>
constexpr const char basic_data<T>::right_padding_shifts[];
template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
#endif
template <typename T> template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision, int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) { T value) {
@ -47,6 +93,9 @@ template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<char>::append(const char*, const char*); template FMT_API void detail::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 detail::vformat_to( template FMT_API void detail::vformat_to(
detail::buffer<char>&, string_view, detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref); basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);

View file

@ -26,19 +26,17 @@
# endif # endif
# include <io.h> # include <io.h>
# define O_CREAT _O_CREAT
# define O_TRUNC _O_TRUNC
# ifndef S_IRUSR # ifndef S_IRUSR
# define S_IRUSR _S_IREAD # define S_IRUSR _S_IREAD
# endif # endif
# ifndef S_IWUSR # ifndef S_IWUSR
# define S_IWUSR _S_IWRITE # define S_IWUSR _S_IWRITE
# endif # endif
# ifndef S_IRGRP
# ifdef __MINGW32__ # define S_IRGRP 0
# define _SH_DENYNO 0x40 # endif
# ifndef S_IROTH
# define S_IROTH 0
# endif # endif
# endif // _WIN32 # endif // _WIN32
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
@ -213,7 +211,10 @@ int buffered_file::fileno() const {
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
file::file(cstring_view path, int oflag) { file::file(cstring_view path, int oflag) {
int mode = S_IRUSR | S_IWUSR; # ifdef _WIN32
using mode_t = int;
# endif
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
# if defined(_WIN32) && !defined(__MINGW32__) # if defined(_WIN32) && !defined(__MINGW32__)
fd_ = -1; fd_ = -1;
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));

View file

@ -0,0 +1 @@
build --symlink_prefix=/ # Out of source build

View file

@ -0,0 +1 @@
4.2.1

View file

@ -0,0 +1,29 @@
cc_library(
name = "fmt",
srcs = [
#"src/fmt.cc", # No C++ module support
"src/format.cc",
"src/os.cc",
],
hdrs = [
"include/fmt/args.h",
"include/fmt/chrono.h",
"include/fmt/color.h",
"include/fmt/compile.h",
"include/fmt/core.h",
"include/fmt/format.h",
"include/fmt/format-inl.h",
"include/fmt/locale.h",
"include/fmt/os.h",
"include/fmt/ostream.h",
"include/fmt/printf.h",
"include/fmt/ranges.h",
"include/fmt/xchar.h",
],
includes = [
"include",
"src",
],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,73 @@
# Bazel support
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
## Using {fmt} as a dependency
The following minimal example shows how to use {fmt} as a dependency within a Bazel project.
The following file structure is assumed:
```
example
├── BUILD.bazel
├── main.cpp
└── WORKSPACE.bazel
```
*main.cpp*:
```c++
#include "fmt/core.h"
int main() {
fmt::print("The answer is {}\n", 42);
}
```
The expected output of this example is `The answer is 42`.
*WORKSPACE.bazel*:
```python
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "fmt",
branch = "master",
remote = "https://github.com/fmtlib/fmt",
patch_cmds = [
"mv support/bazel/.bazelrc .bazelrc",
"mv support/bazel/.bazelversion .bazelversion",
"mv support/bazel/BUILD.bazel BUILD.bazel",
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
],
# Windows-related patch commands are only needed in the case MSYS2 is not installed.
# More details about the installation process of MSYS2 on Windows systems can be found here:
# https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes
# Even if MSYS2 is installed the Windows related patch commands can still be used.
patch_cmds_win = [
"Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc",
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel",
"Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel",
],
)
```
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
*BUILD.bazel*:
```python
cc_binary(
name = "Demo",
srcs = ["main.cpp"],
deps = ["@fmt"],
)
```
The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}.
To execute the binary you can run `bazel run //:Demo`.

View file

@ -0,0 +1 @@
workspace(name = "fmt")

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
"""Manage site and releases. """Manage site and releases.
@ -163,6 +163,13 @@ def update_site(env):
if version.startswith('7.'): if version.startswith('7.'):
b.data = b.data.replace(', std::size_t', ', size_t') b.data = b.data.replace(', std::size_t', ', size_t')
b.data = b.data.replace('join(It, It', 'join(It, Sentinel') b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
if version.startswith('7.1.'):
b.data = b.data.replace(', std::size_t', ', size_t')
b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
b.data = b.data.replace(
'fmt::format_to(OutputIt, const S&, Args&&...)',
'fmt::format_to(OutputIt, const S&, Args&&...) -> ' +
'typename std::enable_if<enable, OutputIt>::type')
b.data = b.data.replace('aa long', 'a long') b.data = b.data.replace('aa long', 'a long')
b.data = b.data.replace('serveral', 'several') b.data = b.data.replace('serveral', 'several')
if version.startswith('6.2.'): if version.startswith('6.2.'):
@ -233,7 +240,7 @@ def release(args):
# Update the version in the changelog. # Update the version in the changelog.
title_len = 0 title_len = 0
for line in fileinput.input(changelog_path, inplace=True): for line in fileinput.input(changelog_path, inplace=True):
if line.decode('utf-8').startswith(version + ' - TBD'): if line.startswith(version + ' - TBD'):
line = version + ' - ' + datetime.date.today().isoformat() line = version + ' - ' + datetime.date.today().isoformat()
title_len = len(line) title_len = len(line)
line += '\n' line += '\n'
@ -263,9 +270,9 @@ def release(args):
# Create a release on GitHub. # Create a release on GitHub.
fmt_repo.push('origin', 'release') fmt_repo.push('origin', 'release')
params = {'access_token': os.getenv('FMT_TOKEN')} auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
params=params, headers=auth_headers,
data=json.dumps({'tag_name': version, data=json.dumps({'tag_name': version,
'target_commitish': 'release', 'target_commitish': 'release',
'body': changes, 'draft': True})) 'body': changes, 'draft': True}))
@ -276,8 +283,8 @@ def release(args):
package = 'fmt-{}.zip'.format(version) package = 'fmt-{}.zip'.format(version)
r = requests.post( r = requests.post(
'{}/{}/assets?name={}'.format(uploads_url, id, package), '{}/{}/assets?name={}'.format(uploads_url, id, package),
headers={'Content-Type': 'application/zip'}, headers={'Content-Type': 'application/zip'} | auth_headers,
params=params, data=open('build/fmt/' + package, 'rb')) data=open('build/fmt/' + package, 'rb'))
if r.status_code != 201: if r.status_code != 201:
raise Exception('Failed to upload an asset ' + str(r)) raise Exception('Failed to upload an asset ' + str(r))

View file

@ -0,0 +1,201 @@
#!/usr/bin/env python3
# This script is based on
# https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py
# distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT.
# This script uses the following Unicode tables:
# - UnicodeData.txt
from collections import namedtuple
import csv
import os
import subprocess
NUM_CODEPOINTS=0x110000
def to_ranges(iter):
current = None
for i in iter:
if current is None or i != current[1] or i in (0x10000, 0x20000):
if current is not None:
yield tuple(current)
current = [i, i + 1]
else:
current[1] += 1
if current is not None:
yield tuple(current)
def get_escaped(codepoints):
for c in codepoints:
if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '):
yield c.value
def get_file(f):
try:
return open(os.path.basename(f))
except FileNotFoundError:
subprocess.run(["curl", "-O", f], check=True)
return open(os.path.basename(f))
Codepoint = namedtuple('Codepoint', 'value class_')
def get_codepoints(f):
r = csv.reader(f, delimiter=";")
prev_codepoint = 0
class_first = None
for row in r:
codepoint = int(row[0], 16)
name = row[1]
class_ = row[2]
if class_first is not None:
if not name.endswith("Last>"):
raise ValueError("Missing Last after First")
for c in range(prev_codepoint + 1, codepoint):
yield Codepoint(c, class_first)
class_first = None
if name.endswith("First>"):
class_first = class_
yield Codepoint(codepoint, class_)
prev_codepoint = codepoint
if class_first is not None:
raise ValueError("Missing Last after First")
for c in range(prev_codepoint + 1, NUM_CODEPOINTS):
yield Codepoint(c, None)
def compress_singletons(singletons):
uppers = [] # (upper, # items in lowers)
lowers = []
for i in singletons:
upper = i >> 8
lower = i & 0xff
if len(uppers) == 0 or uppers[-1][0] != upper:
uppers.append((upper, 1))
else:
upper, count = uppers[-1]
uppers[-1] = upper, count + 1
lowers.append(lower)
return uppers, lowers
def compress_normal(normal):
# lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f
# lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff
compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)]
prev_start = 0
for start, count in normal:
truelen = start - prev_start
falselen = count
prev_start = start + count
assert truelen < 0x8000 and falselen < 0x8000
entry = []
if truelen > 0x7f:
entry.append(0x80 | (truelen >> 8))
entry.append(truelen & 0xff)
else:
entry.append(truelen & 0x7f)
if falselen > 0x7f:
entry.append(0x80 | (falselen >> 8))
entry.append(falselen & 0xff)
else:
entry.append(falselen & 0x7f)
compressed.append(entry)
return compressed
def print_singletons(uppers, lowers, uppersname, lowersname):
print(" static constexpr singleton {}[] = {{".format(uppersname))
for u, c in uppers:
print(" {{{:#04x}, {}}},".format(u, c))
print(" };")
print(" static constexpr unsigned char {}[] = {{".format(lowersname))
for i in range(0, len(lowers), 8):
print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8])))
print(" };")
def print_normal(normal, normalname):
print(" static constexpr unsigned char {}[] = {{".format(normalname))
for v in normal:
print(" {}".format(" ".join("{:#04x},".format(i) for i in v)))
print(" };")
def main():
file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt")
codepoints = get_codepoints(file)
CUTOFF=0x10000
singletons0 = []
singletons1 = []
normal0 = []
normal1 = []
extra = []
for a, b in to_ranges(get_escaped(codepoints)):
if a > 2 * CUTOFF:
extra.append((a, b - a))
elif a == b - 1:
if a & CUTOFF:
singletons1.append(a & ~CUTOFF)
else:
singletons0.append(a)
elif a == b - 2:
if a & CUTOFF:
singletons1.append(a & ~CUTOFF)
singletons1.append((a + 1) & ~CUTOFF)
else:
singletons0.append(a)
singletons0.append(a + 1)
else:
if a >= 2 * CUTOFF:
extra.append((a, b - a))
elif a & CUTOFF:
normal1.append((a & ~CUTOFF, b - a))
else:
normal0.append((a, b - a))
singletons0u, singletons0l = compress_singletons(singletons0)
singletons1u, singletons1l = compress_singletons(singletons1)
normal0 = compress_normal(normal0)
normal1 = compress_normal(normal1)
print("""\
inline auto is_printable(uint32_t cp) -> bool {\
""")
print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower')
print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower')
print_normal(normal0, 'normal0')
print_normal(normal1, 'normal1')
print("""\
auto lower = static_cast<uint16_t>(cp);
if (cp < 0x10000) {
return is_printable(lower, singletons0,
sizeof(singletons0) / sizeof(*singletons0),
singletons0_lower, normal0, sizeof(normal0));
}
if (cp < 0x20000) {
return is_printable(lower, singletons1,
sizeof(singletons1) / sizeof(*singletons1),
singletons1_lower, normal1, sizeof(normal1));
}\
""")
for a, b in extra:
print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b))
print("""\
return cp < 0x{:x};
}}\
""".format(NUM_CODEPOINTS))
if __name__ == '__main__':
main()

View file

@ -8,24 +8,21 @@ target_link_libraries(test-main gtest)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
# Workaround GTest bug https://github.com/google/googletest/issues/705.
check_cxx_compiler_flag(
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
target_compile_options(test-main PUBLIC -fno-delete-null-pointer-checks)
endif ()
# Use less strict pedantic flags for the tests because GMock doesn't compile
# cleanly with -pedantic and -std=c++98.
if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
#set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros)
endif ()
function(add_fmt_executable name) function(add_fmt_executable name)
add_executable(${name} ${ARGN}) add_executable(${name} ${ARGN})
if (MINGW) if (MINGW)
target_link_libraries(${name} -static-libgcc -static-libstdc++) target_link_libraries(${name} -static-libgcc -static-libstdc++)
endif () endif ()
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
# Bogus -Wstringop-overflow warning
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100395
# [10 Regression] spurious -Wstringop-overflow writing to a trailing array plus offset
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
target_link_libraries(${name} -Wno-stringop-overflow)
endif ()
endfunction() endfunction()
# Adds a test. # Adds a test.
@ -41,7 +38,7 @@ function(add_fmt_test name)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
endif () endif ()
elseif (ADD_FMT_TEST_MODULE) elseif (ADD_FMT_TEST_MODULE)
set(libs test-main test-module) set(libs gtest test-module)
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module) set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
else () else ()
set(libs test-main fmt) set(libs test-main fmt)
@ -74,8 +71,13 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif () endif ()
add_fmt_test(ostream-test) add_fmt_test(ostream-test)
add_fmt_test(compile-test) add_fmt_test(compile-test)
add_fmt_test(compile-fp-test HEADER_ONLY)
if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
endif()
add_fmt_test(printf-test) add_fmt_test(printf-test)
add_fmt_test(ranges-test) add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(scan-test) add_fmt_test(scan-test)
add_fmt_test(unicode-test HEADER_ONLY) add_fmt_test(unicode-test HEADER_ONLY)
if (MSVC) if (MSVC)
@ -97,10 +99,12 @@ if (FMT_CAN_MODULE)
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>) $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
enable_module(test-module) enable_module(test-module)
add_fmt_test(module-test MODULE) add_fmt_test(module-test MODULE test-main.cc)
if (MSVC) if (MSVC)
target_compile_options(test-module PRIVATE /utf-8) target_compile_options(test-module PRIVATE /utf-8 /Zc:__cplusplus
target_compile_options(module-test PRIVATE /utf-8) /Zc:externConstexpr /Zc:inline)
target_compile_options(module-test PRIVATE /utf-8 /Zc:__cplusplus
/Zc:externConstexpr /Zc:inline)
endif () endif ()
endif () endif ()
@ -133,20 +137,14 @@ endif ()
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}") message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
if (FMT_PEDANTIC AND CXX_STANDARD LESS 20) if (FMT_PEDANTIC)
# MSVC fails to compile GMock when C++17 is enabled.
if (FMT_HAS_VARIANT AND NOT MSVC)
add_fmt_test(std-format-test)
set_property(TARGET std-format-test PROPERTY CXX_STANDARD 17)
endif ()
# Test that the library can be compiled with exceptions disabled. # Test that the library can be compiled with exceptions disabled.
# -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822. # -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822.
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG) check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG)
endif () endif ()
if (HAVE_FNO_EXCEPTIONS_FLAG) if (HAVE_FNO_EXCEPTIONS_FLAG)
add_library(noexception-test ../src/format.cc) add_library(noexception-test ../src/format.cc noexception-test.cc)
target_include_directories( target_include_directories(
noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include) noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_compile_options(noexception-test PRIVATE -fno-exceptions) target_compile_options(noexception-test PRIVATE -fno-exceptions)
@ -161,7 +159,11 @@ if (FMT_PEDANTIC AND CXX_STANDARD LESS 20)
nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include) nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_compile_definitions( target_compile_definitions(
nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1) nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1)
endif ()
# These tests are disabled on Windows because they take too long.
if (FMT_PEDANTIC AND NOT WIN32)
# Test if incorrect API usages produce compilation error.
add_test(compile-error-test ${CMAKE_CTEST_COMMAND} add_test(compile-error-test ${CMAKE_CTEST_COMMAND}
--build-and-test --build-and-test
"${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test" "${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test"
@ -170,14 +172,11 @@ if (FMT_PEDANTIC AND CXX_STANDARD LESS 20)
--build-makeprogram ${CMAKE_MAKE_PROGRAM} --build-makeprogram ${CMAKE_MAKE_PROGRAM}
--build-options --build-options
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}" "-DFMT_DIR=${CMAKE_SOURCE_DIR}"
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
"-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}") "-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}")
endif ()
# These tests are disabled on Windows because they take too long.
if (FMT_PEDANTIC AND NOT WIN32)
# Test if the targets are found from the build directory. # Test if the targets are found from the build directory.
add_test(find-package-test ${CMAKE_CTEST_COMMAND} add_test(find-package-test ${CMAKE_CTEST_COMMAND}
-C ${CMAKE_BUILD_TYPE} -C ${CMAKE_BUILD_TYPE}

View file

@ -7,10 +7,12 @@
#include "fmt/args.h" #include "fmt/args.h"
#include <memory>
#include "gtest/gtest.h" #include "gtest/gtest.h"
TEST(args_test, basic) { TEST(args_test, basic) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42); store.push_back(42);
store.push_back("abc1"); store.push_back("abc1");
store.push_back(1.5f); store.push_back(1.5f);
@ -19,7 +21,7 @@ TEST(args_test, basic) {
TEST(args_test, strings_and_refs) { TEST(args_test, strings_and_refs) {
// Unfortunately the tests are compiled with old ABI so strings use COW. // Unfortunately the tests are compiled with old ABI so strings use COW.
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
char str[] = "1234567890"; char str[] = "1234567890";
store.push_back(str); store.push_back(str);
store.push_back(std::cref(str)); store.push_back(std::cref(str));
@ -48,7 +50,7 @@ template <> struct formatter<custom_type> {
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(args_test, custom_format) { TEST(args_test, custom_format) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
auto c = custom_type(); auto c = custom_type();
store.push_back(c); store.push_back(c);
++c.i; ++c.i;
@ -77,7 +79,7 @@ template <> struct formatter<to_stringable> {
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(args_test, to_string_and_formatter) { TEST(args_test, to_string_and_formatter) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
auto s = to_stringable(); auto s = to_stringable();
store.push_back(s); store.push_back(s);
store.push_back(std::cref(s)); store.push_back(std::cref(s));
@ -85,13 +87,13 @@ TEST(args_test, to_string_and_formatter) {
} }
TEST(args_test, named_int) { TEST(args_test, named_int) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("a1", 42)); store.push_back(fmt::arg("a1", 42));
EXPECT_EQ("42", fmt::vformat("{a1}", store)); EXPECT_EQ("42", fmt::vformat("{a1}", store));
} }
TEST(args_test, named_strings) { TEST(args_test, named_strings) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
char str[] = "1234567890"; char str[] = "1234567890";
store.push_back(fmt::arg("a1", str)); store.push_back(fmt::arg("a1", str));
store.push_back(fmt::arg("a2", std::cref(str))); store.push_back(fmt::arg("a2", std::cref(str)));
@ -100,7 +102,7 @@ TEST(args_test, named_strings) {
} }
TEST(args_test, named_arg_by_ref) { TEST(args_test, named_arg_by_ref) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
char band[] = "Rolling Stones"; char band[] = "Rolling Stones";
store.push_back(fmt::arg("band", std::cref(band))); store.push_back(fmt::arg("band", std::cref(band)));
band[9] = 'c'; // Changing band affects the output. band[9] = 'c'; // Changing band affects the output.
@ -108,7 +110,7 @@ TEST(args_test, named_arg_by_ref) {
} }
TEST(args_test, named_custom_format) { TEST(args_test, named_custom_format) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
auto c = custom_type(); auto c = custom_type();
store.push_back(fmt::arg("c1", c)); store.push_back(fmt::arg("c1", c));
++c.i; ++c.i;
@ -121,7 +123,7 @@ TEST(args_test, named_custom_format) {
} }
TEST(args_test, clear) { TEST(args_test, clear) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42); store.push_back(42);
auto result = fmt::vformat("{}", store); auto result = fmt::vformat("{}", store);
@ -138,7 +140,7 @@ TEST(args_test, clear) {
} }
TEST(args_test, reserve) { TEST(args_test, reserve) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
store.reserve(2, 1); store.reserve(2, 1);
store.push_back(1.5f); store.push_back(1.5f);
store.push_back(fmt::arg("a1", 42)); store.push_back(fmt::arg("a1", 42));
@ -163,7 +165,7 @@ template <> struct formatter<copy_throwable> {
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(args_test, throw_on_copy) { TEST(args_test, throw_on_copy) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>(); fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(std::string("foo")); store.push_back(std::string("foo"));
try { try {
store.push_back(copy_throwable()); store.push_back(copy_throwable());
@ -171,3 +173,14 @@ TEST(args_test, throw_on_copy) {
} }
EXPECT_EQ(fmt::vformat("{}", store), "foo"); EXPECT_EQ(fmt::vformat("{}", store), "foo");
} }
TEST(args_test, move_constructor) {
using store_type = fmt::dynamic_format_arg_store<fmt::format_context>;
auto store = std::unique_ptr<store_type>(new store_type());
store->push_back(42);
store->push_back(std::string("foo"));
store->push_back(fmt::arg("a1", "foo"));
auto moved_store = std::move(*store);
store.reset();
EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo");
}

View file

@ -7,6 +7,9 @@
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include <ctime>
#include <vector>
#include "gtest-extra.h" // EXPECT_THROW_MSG #include "gtest-extra.h" // EXPECT_THROW_MSG
#include "util.h" // get_locale #include "util.h" // get_locale
@ -38,6 +41,36 @@ auto make_second(int s) -> std::tm {
return time; return time;
} }
std::string system_strftime(const std::string& format, const std::tm* timeptr,
std::locale* locptr = nullptr) {
auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<char>>(loc);
std::ostringstream os;
os.imbue(loc);
facet.put(os, os, ' ', timeptr, format.c_str(),
format.c_str() + format.size());
#ifdef _WIN32
// Workaround a bug in older versions of Universal CRT.
auto str = os.str();
if (str == "-0000") str = "+0000";
return str;
#else
return os.str();
#endif
}
FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min,
int sec) {
auto tm = std::tm();
tm.tm_sec = sec;
tm.tm_min = min;
tm.tm_hour = hour;
tm.tm_mday = mday;
tm.tm_mon = mon - 1;
tm.tm_year = year - 1900;
return tm;
}
TEST(chrono_test, format_tm) { TEST(chrono_test, format_tm) {
auto tm = std::tm(); auto tm = std::tm();
tm.tm_year = 116; tm.tm_year = 116;
@ -48,14 +81,132 @@ TEST(chrono_test, format_tm) {
tm.tm_sec = 33; tm.tm_sec = 33;
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
"The date is 2016-04-25 11:22:33."); "The date is 2016-04-25 11:22:33.");
EXPECT_EQ(fmt::format("{:%Y}", tm), "2016");
EXPECT_EQ(fmt::format("{:%C}", tm), "20");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
EXPECT_EQ(fmt::format("{:%e}", tm), "25");
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/16");
EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25");
EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33");
// Short year
tm.tm_year = 999 - 1900;
tm.tm_mon = 0; // for %G
tm.tm_mday = 2; // for %G
tm.tm_wday = 3; // for %G
tm.tm_yday = 1; // for %G
EXPECT_EQ(fmt::format("{:%Y}", tm), "0999");
EXPECT_EQ(fmt::format("{:%C%y}", tm), "0999");
EXPECT_EQ(fmt::format("{:%G}", tm), "0999");
tm.tm_year = 27 - 1900;
EXPECT_EQ(fmt::format("{:%Y}", tm), "0027");
EXPECT_EQ(fmt::format("{:%C%y}", tm), "0027");
// Overflow year
tm.tm_year = 2147483647;
EXPECT_EQ(fmt::format("{:%Y}", tm), "2147485547");
tm.tm_year = -2147483648;
EXPECT_EQ(fmt::format("{:%Y}", tm), "-2147481748");
// for week on the year
// https://www.cl.cam.ac.uk/~mgk25/iso-time.html
std::vector<std::tm> tm_list = {
make_tm(1975, 12, 29, 12, 14, 16), // W01
make_tm(1977, 1, 2, 12, 14, 16), // W53
make_tm(1999, 12, 27, 12, 14, 16), // W52
make_tm(1999, 12, 31, 12, 14, 16), // W52
make_tm(2000, 1, 1, 12, 14, 16), // W52
make_tm(2000, 1, 2, 12, 14, 16), // W52
make_tm(2000, 1, 3, 12, 14, 16) // W1
};
const std::string iso_week_spec = "%Y-%m-%d: %G %g %V";
for (auto ctm : tm_list) {
// Calculate tm_yday, tm_wday, etc.
std::time_t t = std::mktime(&ctm);
tm = *std::localtime(&t);
auto fmt_spec = fmt::format("{{:{}}}", iso_week_spec);
EXPECT_EQ(system_strftime(iso_week_spec, &tm),
fmt::format(fmt::runtime(fmt_spec), tm));
}
// Every day from 1970-01-01
std::time_t time_now = std::time(nullptr);
for (std::time_t t = 6 * 3600; t < time_now; t += 86400) {
tm = *std::localtime(&t);
auto fmt_spec = fmt::format("{{:{}}}", iso_week_spec);
EXPECT_EQ(system_strftime(iso_week_spec, &tm),
fmt::format(fmt::runtime(fmt_spec), tm));
}
} }
// MSVC:
// minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(971) : Assertion failed:
// timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099
#ifndef _WIN32
TEST(chrono_test, format_tm_future) {
auto tm = std::tm();
tm.tm_year = 10445; // 10000+ years
tm.tm_mon = 3;
tm.tm_mday = 25;
tm.tm_hour = 11;
tm.tm_min = 22;
tm.tm_sec = 33;
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
"The date is 12345-04-25 11:22:33.");
EXPECT_EQ(fmt::format("{:%Y}", tm), "12345");
EXPECT_EQ(fmt::format("{:%C}", tm), "123");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/45");
EXPECT_EQ(fmt::format("{:%F}", tm), "12345-04-25");
EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33");
}
TEST(chrono_test, format_tm_past) {
auto tm = std::tm();
tm.tm_year = -2001;
tm.tm_mon = 3;
tm.tm_mday = 25;
tm.tm_hour = 11;
tm.tm_min = 22;
tm.tm_sec = 33;
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
"The date is -101-04-25 11:22:33.");
EXPECT_EQ(fmt::format("{:%Y}", tm), "-101");
// macOS %C - "-1"
// Linux %C - "-2"
// fmt %C - "-1"
EXPECT_EQ(fmt::format("{:%C}", tm), "-1");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
// macOS %D - "04/25/01" (%y)
// Linux %D - "04/25/99" (%y)
// fmt %D - "04/25/01" (%y)
EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/01");
EXPECT_EQ(fmt::format("{:%F}", tm), "-101-04-25");
EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33");
tm.tm_year = -1901; // -1
EXPECT_EQ(fmt::format("{:%Y}", tm), "-001");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
tm.tm_year = -1911; // -11
EXPECT_EQ(fmt::format("{:%Y}", tm), "-011");
EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm));
}
#endif
TEST(chrono_test, grow_buffer) { TEST(chrono_test, grow_buffer) {
auto s = std::string("{:"); auto s = std::string("{:");
for (int i = 0; i < 30; ++i) s += "%c"; for (int i = 0; i < 30; ++i) s += "%c";
s += "}\n"; s += "}\n";
auto t = std::time(nullptr); auto t = std::time(nullptr);
fmt::format(fmt::runtime(s), *std::localtime(&t)); (void)fmt::format(fmt::runtime(s), *std::localtime(&t));
} }
TEST(chrono_test, format_to_empty_container) { TEST(chrono_test, format_to_empty_container) {
@ -88,22 +239,45 @@ TEST(chrono_test, gmtime) {
EXPECT_TRUE(equal(tm, fmt::gmtime(t))); EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
} }
template <typename TimePoint> auto strftime(TimePoint tp) -> std::string { template <typename TimePoint> auto strftime_full(TimePoint tp) -> std::string {
auto t = std::chrono::system_clock::to_time_t(tp); auto t = std::chrono::system_clock::to_time_t(tp);
auto tm = *std::localtime(&t); auto tm = *std::localtime(&t);
char output[256] = {}; return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
std::strftime(output, sizeof(output), "%Y-%m-%d %H:%M:%S", &tm);
return output;
} }
TEST(chrono_test, time_point) { TEST(chrono_test, time_point) {
auto t1 = std::chrono::system_clock::now(); auto t1 = std::chrono::system_clock::now();
EXPECT_EQ(strftime(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime(t1), fmt::format("{}", t1)); EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1));
using time_point = using time_point =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>; std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(42)); auto t2 = time_point(std::chrono::seconds(42));
EXPECT_EQ(strftime(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector<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"};
spec_list.push_back("%Y-%m-%d %H:%M:%S");
#ifndef _WIN32
// Disabled on Windows because these formats are not consistent among
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm);
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
} }
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
@ -197,41 +371,41 @@ TEST(chrono_test, format_specs) {
TEST(chrono_test, invalid_specs) { TEST(chrono_test, invalid_specs) {
auto sec = std::chrono::seconds(0); auto sec = std::chrono::seconds(0);
EXPECT_THROW_MSG(fmt::format(runtime("{:%a}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%a}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%A}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%A}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%c}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%c}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%x}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%x}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Ex}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Ex}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%X}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%X}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%EX}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%EX}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%D}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%D}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%F}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%F}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Ec}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Ec}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%w}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%w}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%u}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%u}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%b}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%b}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%B}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%B}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%z}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%z}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Z}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Z}"), sec), fmt::format_error,
"no date"); "no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Eq}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Eq}"), sec), fmt::format_error,
"invalid format"); "invalid format");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Oq}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
"invalid format"); "invalid format");
} }
@ -279,20 +453,33 @@ TEST(chrono_test, format_default_fp) {
} }
TEST(chrono_test, format_precision) { TEST(chrono_test, format_precision) {
EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), std::chrono::seconds(42)), EXPECT_THROW_MSG(
fmt::format_error, (void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
"precision not allowed for this argument type"); fmt::format_error, "precision not allowed for this argument type");
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2)); EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
EXPECT_EQ("13ms", fmt::format("{:.0}", dms(12.56)));
EXPECT_EQ("12.6ms", fmt::format("{:.1}", dms(12.56)));
EXPECT_EQ("12.56ms", fmt::format("{:.2}", dms(12.56)));
} }
TEST(chrono_test, format_full_specs) { TEST(chrono_test, format_full_specs) {
EXPECT_EQ("1ms ", fmt::format("{:6.0}", dms(1.234)));
EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234))); EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2)); EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1)); EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1));
EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8)); EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8));
EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3)); EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3));
EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234))); EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234)));
EXPECT_EQ("13ms ", fmt::format("{:6.0}", dms(12.56)));
EXPECT_EQ(" 13ms", fmt::format("{:>8.{}}", dms(12.56), 0));
EXPECT_EQ(" 13ms ", fmt::format("{:^{}.{}}", dms(12.56), 6, 0));
EXPECT_EQ(" 13ms ", fmt::format("{0:^{2}.{1}}", dms(12.56), 0, 8));
EXPECT_EQ("==13ms===", fmt::format("{:=^{}.{}}", dms(12.56), 9, 0));
EXPECT_EQ("***13ms***", fmt::format("{:*^10.0}", dms(12.56)));
} }
TEST(chrono_test, format_simple_q) { TEST(chrono_test, format_simple_q) {
@ -306,29 +493,37 @@ TEST(chrono_test, format_simple_q) {
} }
TEST(chrono_test, format_precision_q) { TEST(chrono_test, format_precision_q) {
EXPECT_THROW_MSG(fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)), EXPECT_THROW_MSG(
fmt::format_error, (void)fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)),
"precision not allowed for this argument type"); fmt::format_error, "precision not allowed for this argument type");
EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234))); EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2)); EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2));
} }
TEST(chrono_test, format_full_specs_q) { TEST(chrono_test, format_full_specs_q) {
EXPECT_EQ("1 ms ", fmt::format("{:7.0%Q %q}", dms(1.234)));
EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234))); EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234)));
EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2)); EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2));
EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1)); EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1));
EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9)); EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9));
EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3)); EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3));
EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234))); EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234)));
EXPECT_EQ("13 ms ", fmt::format("{:7.0%Q %q}", dms(12.56)));
EXPECT_EQ(" 13 ms", fmt::format("{:>8.{}%Q %q}", dms(12.56), 0));
EXPECT_EQ(" 13 ms ", fmt::format("{:^{}.{}%Q %q}", dms(12.56), 8, 0));
EXPECT_EQ(" 13 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(12.56), 0, 9));
EXPECT_EQ("==13 ms==", fmt::format("{:=^{}.{}%Q %q}", dms(12.56), 9, 0));
EXPECT_EQ("***13 ms***", fmt::format("{:*^11.0%Q %q}", dms(12.56)));
} }
TEST(chrono_test, invalid_width_id) { TEST(chrono_test, invalid_width_id) {
EXPECT_THROW(fmt::format(runtime("{:{o}"), std::chrono::seconds(0)), EXPECT_THROW((void)fmt::format(runtime("{:{o}"), std::chrono::seconds(0)),
fmt::format_error); fmt::format_error);
} }
TEST(chrono_test, invalid_colons) { TEST(chrono_test, invalid_colons) {
EXPECT_THROW(fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)), EXPECT_THROW((void)fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)),
fmt::format_error); fmt::format_error);
} }
@ -348,15 +543,12 @@ TEST(chrono_test, negative_durations) {
} }
TEST(chrono_test, special_durations) { TEST(chrono_test, special_durations) {
EXPECT_EQ( auto value = fmt::format("{:%S}", std::chrono::duration<double>(1e20));
"40.", EXPECT_EQ(value, "40");
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
auto nan = std::numeric_limits<double>::quiet_NaN(); auto nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ( EXPECT_EQ(
"nan nan nan nan nan:nan nan", "nan nan nan nan nan:nan nan",
fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan))); fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan)));
fmt::format("{:%S}",
std::chrono::duration<float, std::atto>(1.79400457e+31f));
EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)), EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)),
"1Es"); "1Es");
EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)), EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)),
@ -375,11 +567,59 @@ TEST(chrono_test, weekday) {
auto loc = get_locale("ru_RU.UTF-8"); auto loc = get_locale("ru_RU.UTF-8");
std::locale::global(loc); std::locale::global(loc);
auto mon = fmt::weekday(1); auto mon = fmt::weekday(1);
auto tm = std::tm();
tm.tm_wday = static_cast<int>(mon.c_encoding());
EXPECT_EQ(fmt::format("{}", mon), "Mon"); EXPECT_EQ(fmt::format("{}", mon), "Mon");
EXPECT_EQ(fmt::format("{:%a}", tm), "Mon");
if (loc != std::locale::classic()) { if (loc != std::locale::classic()) {
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}), EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
Contains(fmt::format(loc, "{:L}", mon))); Contains(fmt::format(loc, "{:L}", mon)));
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
Contains(fmt::format(loc, "{:%a}", tm)));
} }
} }
TEST(chrono_test, cpp20_duration_subsecond_support) {
using attoseconds = std::chrono::duration<long long, std::atto>;
// Check that 18 digits of subsecond precision are supported.
EXPECT_EQ(fmt::format("{:%S}", attoseconds{999999999999999999}),
"00.999999999999999999");
EXPECT_EQ(fmt::format("{:%S}", attoseconds{673231113420148734}),
"00.673231113420148734");
EXPECT_EQ(fmt::format("{:%S}", attoseconds{-673231113420148734}),
"-00.673231113420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{13420148734}),
"13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
"-13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
{
// Check that {:%H:%M:%S} is equivalent to {:%T}.
auto dur = std::chrono::milliseconds{3601234};
auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "01:00:01.234");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
}
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789");
// Verify that only the seconds part is extracted and printed.
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000");
{
// Now the hour is printed, and we also test if negative doubles work.
auto dur = nanoseconds_dbl{-99123456789};
auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
}
// Check that durations with precision greater than std::chrono::seconds have
// fixed precision, and print zeros even if there is no fractional part.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
"07.000000");
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR #endif // FMT_STATIC_THOUSANDS_SEPARATOR

View file

@ -20,10 +20,16 @@ TEST(color_test, format) {
fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"), fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m"); "\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m"); EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::faint, "faint"), "\x1b[2mfaint\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"), EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"),
"\x1b[3mitalic\x1b[0m"); "\x1b[3mitalic\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"), EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"),
"\x1b[4munderline\x1b[0m"); "\x1b[4munderline\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::blink, "blink"), "\x1b[5mblink\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::reverse, "reverse"),
"\x1b[7mreverse\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::conceal, "conceal"),
"\x1b[8mconceal\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"), EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"),
"\x1b[9mstrikethrough\x1b[0m"); "\x1b[9mstrikethrough\x1b[0m");
EXPECT_EQ( EXPECT_EQ(

View file

@ -1,71 +1,158 @@
# Test if compile errors are produced where necessary. # Test if compile errors are produced where necessary.
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.1...3.18)
project(compile-error-test CXX)
include(CheckCXXSourceCompiles) set(fmt_headers "
include(CheckCXXCompilerFlag) #include <fmt/format.h>
#include <fmt/xchar.h>
")
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../../include) set(error_test_names "")
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG} ${PEDANTIC_COMPILE_FLAGS}) set(non_error_test_content "")
function (generate_source result fragment) # For error tests (we expect them to produce compilation error):
set(${result} " # * adds a name of test into `error_test_names` list
#define FMT_HEADER_ONLY 1 # * generates a single source file (with the same name) for each test
#include \"fmt/format.h\" # For non-error tests (we expect them to compile successfully):
int main() { # * adds a code segment as separate function to `non_error_test_content`
${fragment} function (expect_compile name code_fragment)
cmake_parse_arguments(EXPECT_COMPILE "ERROR" "" "" ${ARGN})
string(MAKE_C_IDENTIFIER "${name}" test_name)
if (EXPECT_COMPILE_ERROR)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/${test_name}.cc" "
${fmt_headers}
void ${test_name}() {
${code_fragment}
} }
" PARENT_SCOPE) ")
set(error_test_names_copy "${error_test_names}")
list(APPEND error_test_names_copy "${test_name}")
set(error_test_names "${error_test_names_copy}" PARENT_SCOPE)
else()
set(non_error_test_content "
${non_error_test_content}
void ${test_name}() {
${code_fragment}
}" PARENT_SCOPE)
endif()
endfunction () endfunction ()
function (expect_compile code) # Generates a source file for non-error test with `non_error_test_content` and
generate_source(source "${code}") # CMake project file with all error and single non-error test targets.
check_cxx_source_compiles("${source}" compiles) function (run_tests)
if (NOT compiles) set(cmake_targets "")
set(error_msg "Compile error for: ${code}") foreach(test_name IN LISTS error_test_names)
set(cmake_targets "
${cmake_targets}
add_library(test-${test_name} ${test_name}.cc)
target_link_libraries(test-${test_name} PRIVATE fmt::fmt)
")
endforeach()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/non_error_test.cc" "
${fmt_headers}
${non_error_test_content}
")
set(cmake_targets "
${cmake_targets}
add_library(non-error-test non_error_test.cc)
target_link_libraries(non-error-test PRIVATE fmt::fmt)
")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
cmake_minimum_required(VERSION 3.1...3.18)
project(tests CXX)
add_subdirectory(${FMT_DIR} fmt)
${cmake_targets}
")
set(build_directory "${CMAKE_CURRENT_BINARY_DIR}/test/build")
file(MAKE_DIRECTORY "${build_directory}")
execute_process(
COMMAND
"${CMAKE_COMMAND}"
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DCMAKE_GENERATOR=${CMAKE_GENERATOR}"
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
"-DFMT_DIR=${FMT_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}/test"
WORKING_DIRECTORY "${build_directory}"
RESULT_VARIABLE result_var
OUTPUT_VARIABLE output_var
ERROR_VARIABLE output_var)
if (NOT result_var EQUAL 0)
message(FATAL_ERROR "Unable to configure:\n${output_var}")
endif()
foreach(test_name IN LISTS error_test_names)
execute_process(
COMMAND
"${CMAKE_COMMAND}" --build "${build_directory}" --target "test-${test_name}"
WORKING_DIRECTORY "${build_directory}"
RESULT_VARIABLE result_var
OUTPUT_VARIABLE output_var
ERROR_QUIET)
if (result_var EQUAL 0)
message(SEND_ERROR "No compile error for \"${test_name}\":\n${output_var}")
endif () endif ()
# Unset the CMake cache variable compiles. Otherwise the compile test will endforeach()
# just use cached information next time it runs.
unset(compiles CACHE) execute_process(
if (error_msg) COMMAND
message(FATAL_ERROR ${error_msg}) "${CMAKE_COMMAND}" --build "${build_directory}" --target "non-error-test"
WORKING_DIRECTORY "${build_directory}"
RESULT_VARIABLE result_var
OUTPUT_VARIABLE output_var
ERROR_VARIABLE output_var)
if (NOT result_var EQUAL 0)
message(SEND_ERROR "Compile error for combined non-error test:\n${output_var}")
endif () endif ()
endfunction () endfunction ()
function (expect_compile_error code)
generate_source(source "${code}")
check_cxx_source_compiles("${source}" compiles)
if (compiles)
set(error_msg "No compile error for: ${code}")
endif ()
# Unset the CMake cache variable compiles. Otherwise the compile test will
# just use cached information next time it runs.
unset(compiles CACHE)
if (error_msg)
message(FATAL_ERROR ${error_msg})
endif ()
endfunction ()
# check if the source file skeleton compiles # check if the source file skeleton compiles
expect_compile("") expect_compile(check "")
expect_compile(check-error "compilation_error" ERROR)
# Formatting a wide character with a narrow format string is forbidden. # Formatting a wide character with a narrow format string is forbidden.
expect_compile_error("fmt::format(\"{}\", L'a');") expect_compile(wide-character-narrow-format-string "fmt::format(L\"{}\", L'a');")
expect_compile(wide-character-narrow-format-string-error "fmt::format(\"{}\", L'a');" ERROR)
# Formatting a wide string with a narrow format string is forbidden. # Formatting a wide string with a narrow format string is forbidden.
expect_compile_error("fmt::format(\"{}\", L\"foo\");") expect_compile(wide-string-narrow-format-string "fmt::format(L\"{}\", L\"foo\");")
expect_compile(wide-string-narrow-format-string-error "fmt::format(\"{}\", L\"foo\");" ERROR)
# Formatting a narrow string with a wide format string is forbidden because # Formatting a narrow string with a wide format string is forbidden because
# mixing UTF-8 with UTF-16/32 can result in an invalid output. # mixing UTF-8 with UTF-16/32 can result in an invalid output.
expect_compile_error("fmt::format(L\"{}\", \"foo\");") expect_compile(narrow-string-wide-format-string "fmt::format(L\"{}\", L\"foo\");")
expect_compile(narrow-string-wide-format-string-error "fmt::format(L\"{}\", \"foo\");" ERROR)
# Formatting a wide string with a narrow format string is forbidden. expect_compile(cast-to-string "
expect_compile_error(" struct S {
operator std::string() const { return std::string(); }
};
fmt::format(\"{}\", std::string(S()));
")
expect_compile(cast-to-string-error "
struct S { struct S {
operator std::string() const { return std::string(); } operator std::string() const { return std::string(); }
}; };
fmt::format(\"{}\", S()); fmt::format(\"{}\", S());
" ERROR)
# Formatting a function
expect_compile(format-function "
void (*f)();
fmt::format(\"{}\", fmt::ptr(f));
") ")
expect_compile(format-function-error "
void (*f)();
fmt::format(\"{}\", f);
" ERROR)
# Make sure that compiler features detected in the header # Make sure that compiler features detected in the header
# match the features detected in CMake. # match the features detected in CMake.
@ -74,6 +161,43 @@ if (SUPPORTS_USER_DEFINED_LITERALS)
else () else ()
set(supports_udl 0) set(supports_udl 0)
endif () endif ()
expect_compile("#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl} expect_compile(udl-check "
#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
# error # error
#endif") #endif
")
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
# Compile-time argument type check
expect_compile(format-string-number-spec "
#ifdef FMT_HAS_CONSTEVAL
fmt::format(\"{:d}\", 42);
#endif
")
expect_compile(format-string-number-spec-error "
#ifdef FMT_HAS_CONSTEVAL
fmt::format(\"{:d}\", \"I am not a number\");
#else
#error
#endif
" ERROR)
# Compile-time argument name check
expect_compile(format-string-name "
#if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
using namespace fmt::literals;
fmt::print(\"{foo}\", \"foo\"_a=42);
#endif
")
expect_compile(format-string-name-error "
#if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
using namespace fmt::literals;
fmt::print(\"{foo}\", \"bar\"_a=42);
#else
#error
#endif
" ERROR)
endif ()
# Run all tests
run_tests()

View file

@ -0,0 +1,62 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/compile.h"
#include "gmock/gmock.h"
#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806 && \
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
defined(__cpp_constexpr_dynamic_alloc) && \
__cpp_constexpr_dynamic_alloc >= 201907 && __cplusplus >= 202002L
template <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;
}
Char buffer[max_string_length]{};
};
template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer, format, args...);
return string;
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif

View file

@ -59,7 +59,24 @@ TEST(compile_test, compile_fallback) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
} }
#ifdef __cpp_if_constexpr struct type_with_get {
template <int> friend void get(type_with_get);
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<type_with_get> : formatter<int> {
template <typename FormatContext>
auto format(type_with_get, FormatContext& ctx) -> decltype(ctx.out()) {
return formatter<int>::format(42, ctx);
}
};
FMT_END_NAMESPACE
TEST(compile_test, compile_type_with_get) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), type_with_get()));
}
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
struct test_formattable {}; struct test_formattable {};
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE

View file

@ -151,7 +151,9 @@ TEST(buffer_test, indestructible) {
template <typename T> struct mock_buffer final : buffer<T> { template <typename T> struct mock_buffer final : buffer<T> {
MOCK_METHOD1(do_grow, size_t(size_t capacity)); MOCK_METHOD1(do_grow, size_t(size_t capacity));
void grow(size_t capacity) { this->set(this->data(), do_grow(capacity)); } void grow(size_t capacity) override {
this->set(this->data(), do_grow(capacity));
}
mock_buffer(T* data = nullptr, size_t buf_capacity = 0) { mock_buffer(T* data = nullptr, size_t buf_capacity = 0) {
this->set(data, buf_capacity); this->set(data, buf_capacity);
@ -406,15 +408,7 @@ TYPED_TEST(numeric_arg_test, make_and_visit) {
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max()); CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
} }
namespace fmt { TEST(arg_test, char_arg) { CHECK_ARG(char, 'a', 'a'); }
template <> struct is_char<wchar_t> : std::true_type {};
} // namespace fmt
TEST(arg_test, char_arg) {
CHECK_ARG(char, 'a', 'a');
CHECK_ARG(wchar_t, L'a', 'a');
CHECK_ARG(wchar_t, L'a', L'a');
}
TEST(arg_test, string_arg) { TEST(arg_test, string_arg) {
char str_data[] = "test"; char str_data[] = "test";
@ -452,7 +446,7 @@ struct check_custom {
struct test_buffer final : fmt::detail::buffer<char> { struct test_buffer final : fmt::detail::buffer<char> {
char data[10]; char data[10];
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {} test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
void grow(size_t) {} void grow(size_t) override {}
} buffer; } buffer;
auto parse_ctx = fmt::format_parse_context(""); auto parse_ctx = fmt::format_parse_context("");
auto ctx = fmt::format_context(fmt::detail::buffer_appender<char>(buffer), auto ctx = fmt::format_context(fmt::detail::buffer_appender<char>(buffer),
@ -530,7 +524,7 @@ struct test_format_specs_handler {
fmt::detail::arg_ref<char> width_ref; fmt::detail::arg_ref<char> width_ref;
int precision = 0; int precision = 0;
fmt::detail::arg_ref<char> precision_ref; fmt::detail::arg_ref<char> precision_ref;
char type = 0; fmt::presentation_type type = fmt::presentation_type::none;
// Workaround for MSVC2017 bug that results in "expression did not evaluate // Workaround for MSVC2017 bug that results in "expression did not evaluate
// to a constant" with compiler-generated copy ctor. // to a constant" with compiler-generated copy ctor.
@ -556,14 +550,14 @@ struct test_format_specs_handler {
constexpr void on_dynamic_precision(string_view) {} constexpr void on_dynamic_precision(string_view) {}
constexpr void end_precision() {} constexpr void end_precision() {}
constexpr void on_type(char t) { type = t; } constexpr void on_type(fmt::presentation_type t) { type = t; }
constexpr void on_error(const char*) { res = error; } constexpr void on_error(const char*) { res = error; }
}; };
template <size_t N> template <size_t N>
constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) { constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) {
auto h = test_format_specs_handler(); auto h = test_format_specs_handler();
fmt::detail::parse_format_specs(s, s + N, h); fmt::detail::parse_format_specs(s, s + N - 1, h);
return h; return h;
} }
@ -581,7 +575,7 @@ TEST(core_test, constexpr_parse_format_specs) {
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, ""); static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
static_assert(parse_test_specs(".42").precision == 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(".{42}").precision_ref.val.index == 42, "");
static_assert(parse_test_specs("d").type == 'd', ""); static_assert(parse_test_specs("d").type == fmt::presentation_type::dec, "");
static_assert(parse_test_specs("{<").res == handler::error, ""); static_assert(parse_test_specs("{<").res == handler::error, "");
} }
@ -603,7 +597,7 @@ constexpr fmt::detail::dynamic_format_specs<char> parse_dynamic_specs(
auto specs = fmt::detail::dynamic_format_specs<char>(); auto specs = fmt::detail::dynamic_format_specs<char>();
auto ctx = test_parse_context(); auto ctx = test_parse_context();
auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx); auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx);
parse_format_specs(s, s + N, h); parse_format_specs(s, s + N - 1, h);
return specs; return specs;
} }
@ -621,14 +615,15 @@ TEST(format_test, constexpr_dynamic_specs_handler) {
static_assert(parse_dynamic_specs(".42").precision == 42, ""); static_assert(parse_dynamic_specs(".42").precision == 42, "");
static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, ""); 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(".{42}").precision_ref.val.index == 42, "");
static_assert(parse_dynamic_specs("d").type == 'd', ""); static_assert(parse_dynamic_specs("d").type == fmt::presentation_type::dec,
"");
} }
template <size_t N> template <size_t N>
constexpr test_format_specs_handler check_specs(const char (&s)[N]) { constexpr test_format_specs_handler check_specs(const char (&s)[N]) {
fmt::detail::specs_checker<test_format_specs_handler> checker( fmt::detail::specs_checker<test_format_specs_handler> checker(
test_format_specs_handler(), fmt::detail::type::double_type); test_format_specs_handler(), fmt::detail::type::double_type);
parse_format_specs(s, s + N, checker); parse_format_specs(s, s + N - 1, checker);
return checker; return checker;
} }
@ -645,7 +640,7 @@ TEST(format_test, constexpr_specs_checker) {
static_assert(check_specs("{42}").width_ref.val.index == 42, ""); static_assert(check_specs("{42}").width_ref.val.index == 42, "");
static_assert(check_specs(".42").precision == 42, ""); static_assert(check_specs(".42").precision == 42, "");
static_assert(check_specs(".{42}").precision_ref.val.index == 42, ""); static_assert(check_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(check_specs("d").type == 'd', ""); static_assert(check_specs("d").type == fmt::presentation_type::dec, "");
static_assert(check_specs("{<").res == handler::error, ""); static_assert(check_specs("{<").res == handler::error, "");
} }
@ -709,10 +704,80 @@ TEST(core_test, has_formatter) {
""); "");
} }
struct const_formattable {};
struct nonconst_formattable {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<const_formattable> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(const const_formattable&, format_context& ctx)
-> decltype(ctx.out()) {
auto test = string_view("test");
return std::copy_n(test.data(), test.size(), ctx.out());
}
};
template <> struct formatter<nonconst_formattable> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(nonconst_formattable&, format_context& ctx)
-> decltype(ctx.out()) {
auto test = string_view("test");
return std::copy_n(test.data(), test.size(), ctx.out());
}
};
FMT_END_NAMESPACE
struct convertible_to_pointer {
operator const int*() const { return nullptr; }
};
enum class test_scoped_enum {};
TEST(core_test, is_formattable) { 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<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,
"");
static_assert(fmt::is_formattable<enabled_formatter>::value, ""); static_assert(fmt::is_formattable<enabled_formatter>::value, "");
static_assert(!fmt::is_formattable<disabled_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, "");
static_assert(fmt::is_formattable<nonconst_formattable&>::value, "");
#if !FMT_MSC_VER || FMT_MSC_VER >= 1910
static_assert(!fmt::is_formattable<const nonconst_formattable&>::value, "");
#endif
static_assert(!fmt::is_formattable<convertible_to_pointer>::value, "");
static_assert(!fmt::is_formattable<void (*)()>::value, "");
struct s;
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
static_assert(!fmt::is_formattable<test_scoped_enum>::value, "");
} }
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
@ -838,10 +903,21 @@ TEST(core_test, adl) {
if (fmt::detail::const_check(true)) return; if (fmt::detail::const_check(true)) return;
auto s = adl_test::string(); auto s = adl_test::string();
char buf[10]; char buf[10];
fmt::format("{}", s); (void)fmt::format("{}", s);
fmt::format_to(buf, "{}", s); fmt::format_to(buf, "{}", s);
fmt::format_to_n(buf, 10, "{}", s); fmt::format_to_n(buf, 10, "{}", s);
fmt::formatted_size("{}", s); (void)fmt::formatted_size("{}", s);
fmt::print("{}", s); fmt::print("{}", s);
fmt::print(stdout, "{}", s); fmt::print(stdout, "{}", s);
} }
TEST(core_test, has_const_formatter) {
EXPECT_TRUE((fmt::detail::has_const_formatter<const_formattable,
fmt::format_context>()));
EXPECT_FALSE((fmt::detail::has_const_formatter<nonconst_formattable,
fmt::format_context>()));
}
TEST(core_test, format_nonconst) {
EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test");
}

View file

@ -17,12 +17,12 @@
// Exercise the API to verify that everything we expect to can compile. // Exercise the API to verify that everything we expect to can compile.
void test_format_api() { void test_format_api() {
fmt::format(FMT_STRING("{}"), 42); (void)fmt::format(FMT_STRING("{}"), 42);
fmt::format(FMT_STRING(L"{}"), 42); (void)fmt::format(FMT_STRING(L"{}"), 42);
fmt::format(FMT_STRING("noop")); (void)fmt::format(FMT_STRING("noop"));
fmt::to_string(42); (void)fmt::to_string(42);
fmt::to_wstring(42); (void)fmt::to_wstring(42);
std::vector<char> out; std::vector<char> out;
fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42); fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42);
@ -35,13 +35,14 @@ void test_format_api() {
} }
void test_chrono() { void test_chrono() {
fmt::format(FMT_STRING("{}"), std::chrono::seconds(42)); (void)fmt::format(FMT_STRING("{}"), std::chrono::seconds(42));
fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42)); (void)fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42));
} }
void test_text_style() { void test_text_style() {
fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)"); fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)"); (void)fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"),
"rgb(255,20,30)");
fmt::text_style ts = fg(fmt::rgb(255, 20, 30)); fmt::text_style ts = fg(fmt::rgb(255, 20, 30));
std::string out; std::string out;
@ -51,7 +52,7 @@ void test_text_style() {
void test_range() { void test_range() {
std::vector<char> hello = {'h', 'e', 'l', 'l', 'o'}; std::vector<char> hello = {'h', 'e', 'l', 'l', 'o'};
fmt::format(FMT_STRING("{}"), hello); (void)fmt::format(FMT_STRING("{}"), hello);
} }
int main() { int main() {

View file

@ -1,6 +1,5 @@
#include "fmt/format.h" #include "fmt/format.h"
int main(int argc, char** argv) { int main(int argc, char** argv) {
for(int i = 0; i < argc; ++i) for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]);
fmt::print("{}: {}\n", i, argv[i]);
} }

View file

@ -278,25 +278,22 @@ TEST(fp_test, get_round_direction) {
} }
TEST(fp_test, fixed_handler) { TEST(fp_test, fixed_handler) {
struct handler : fmt::detail::fixed_handler { struct handler : fmt::detail::gen_digits_handler {
char buffer[10]; char buffer[10];
handler(int prec = 0) : fmt::detail::fixed_handler() { handler(int prec = 0) : fmt::detail::gen_digits_handler() {
buf = buffer; buf = buffer;
precision = prec; precision = prec;
} }
}; };
int exp = 0; handler().on_digit('0', 100, 99, 0, false);
handler().on_digit('0', 100, 99, 0, exp, false); EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure);
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false),
assertion_failure);
namespace digits = fmt::detail::digits; namespace digits = fmt::detail::digits;
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error); EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error);
// Check that divisor - error doesn't overflow. // Check that divisor - error doesn't overflow.
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error); EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error);
// Check that 2 * error doesn't overflow. // Check that 2 * error doesn't overflow.
uint64_t max = max_value<uint64_t>(); uint64_t max = max_value<uint64_t>();
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false), EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error);
digits::error);
} }
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) { TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {

View file

@ -370,11 +370,11 @@ TEST(format_test, escape) {
} }
TEST(format_test, unmatched_braces) { TEST(format_test, unmatched_braces) {
EXPECT_THROW_MSG(fmt::format(runtime("{")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{")), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("}")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("}")), format_error,
"unmatched '}' in format string"); "unmatched '}' in format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0{}")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0{}")), format_error,
"invalid format string"); "invalid format string");
} }
@ -391,30 +391,30 @@ TEST(format_test, args_in_different_positions) {
} }
TEST(format_test, arg_errors) { TEST(format_test, arg_errors) {
EXPECT_THROW_MSG(fmt::format(runtime("{")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{")), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{?}")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{?}")), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0")), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0}")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0}")), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{00}"), 42), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{00}"), 42), format_error,
"invalid format string"); "invalid format string");
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{%u", INT_MAX); safe_sprintf(format_str, "{%u", INT_MAX);
EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
"invalid format string"); "invalid format string");
safe_sprintf(format_str, "{%u}", INT_MAX); safe_sprintf(format_str, "{%u}", INT_MAX);
EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
"argument not found"); "argument not found");
safe_sprintf(format_str, "{%u", INT_MAX + 1u); safe_sprintf(format_str, "{%u", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
"invalid format string"); "invalid format string");
safe_sprintf(format_str, "{%u}", INT_MAX + 1u); safe_sprintf(format_str, "{%u}", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(runtime(format_str)), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str)), format_error,
"argument not found"); "argument not found");
} }
@ -458,24 +458,26 @@ TEST(format_test, named_arg) {
fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0), fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0),
fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0), fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0),
fmt::arg("o", 0), fmt::arg("p", 0))); fmt::arg("o", 0), fmt::arg("p", 0)));
EXPECT_THROW_MSG(fmt::format(runtime("{a}")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{a}")), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{a}"), 42), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{a}"), 42), format_error,
"argument not found"); "argument not found");
} }
TEST(format_test, auto_arg_index) { TEST(format_test, auto_arg_index) {
EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c')); EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c'));
EXPECT_THROW_MSG(fmt::format(runtime("{0}{}"), 'a', 'b'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0}{}"), 'a', 'b'), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(runtime("{}{0}"), 'a', 'b'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{}{0}"), 'a', 'b'), format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2)); EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2));
EXPECT_THROW_MSG(fmt::format(runtime("{0}:.{}"), 1.2345, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0}:.{}"), 1.2345, 2),
format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(runtime("{:.{0}}"), 1.2345, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{0}}"), 1.2345, 2),
format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(fmt::format(runtime("{}")), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{}")), format_error,
"argument not found"); "argument not found");
} }
@ -533,9 +535,9 @@ TEST(format_test, center_align) {
} }
TEST(format_test, fill) { TEST(format_test, fill) {
EXPECT_THROW_MSG(fmt::format(runtime("{0:{<5}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{<5}"), 'c'), format_error,
"invalid fill character '{'"); "invalid fill character '{'");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{<5}}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{<5}}"), 'c'), format_error,
"invalid fill character '{'"); "invalid fill character '{'");
EXPECT_EQ("**42", fmt::format("{0:*>4}", 42)); EXPECT_EQ("**42", fmt::format("{0:*>4}", 42));
EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42)); EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42));
@ -554,31 +556,32 @@ TEST(format_test, fill) {
EXPECT_EQ(std::string("\0\0\0*", 4), EXPECT_EQ(std::string("\0\0\0*", 4),
fmt::format(string_view("{:\0>4}", 6), '*')); fmt::format(string_view("{:\0>4}", 6), '*'));
EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42)); EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42));
EXPECT_THROW_MSG(fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0), EXPECT_THROW_MSG((void)fmt::format(runtime("{:\x80\x80\x80\x80\x80>}"), 0),
format_error, "missing '}' in format string"); format_error, "invalid type specifier");
} }
TEST(format_test, plus_sign) { TEST(format_test, plus_sign) {
EXPECT_EQ("+42", fmt::format("{0:+}", 42)); EXPECT_EQ("+42", fmt::format("{0:+}", 42));
EXPECT_EQ("-42", fmt::format("{0:+}", -42)); EXPECT_EQ("-42", fmt::format("{0:+}", -42));
EXPECT_EQ("+42", fmt::format("{0:+}", 42)); EXPECT_EQ("+42", fmt::format("{0:+}", 42));
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42u), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42u), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("+42", fmt::format("{0:+}", 42l)); EXPECT_EQ("+42", fmt::format("{0:+}", 42l));
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42ul), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ul), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("+42", fmt::format("{0:+}", 42ll)); EXPECT_EQ("+42", fmt::format("{0:+}", 42ll));
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 42ull), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 42ull), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0)); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0));
EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l)); EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l));
EXPECT_THROW_MSG(fmt::format(runtime("{0:+"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), "abc"), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:+}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -586,23 +589,24 @@ TEST(format_test, minus_sign) {
EXPECT_EQ("42", fmt::format("{0:-}", 42)); EXPECT_EQ("42", fmt::format("{0:-}", 42));
EXPECT_EQ("-42", fmt::format("{0:-}", -42)); EXPECT_EQ("-42", fmt::format("{0:-}", -42));
EXPECT_EQ("42", fmt::format("{0:-}", 42)); EXPECT_EQ("42", fmt::format("{0:-}", 42));
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42u), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42u), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("42", fmt::format("{0:-}", 42l)); EXPECT_EQ("42", fmt::format("{0:-}", 42l));
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42ul), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ul), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("42", fmt::format("{0:-}", 42ll)); EXPECT_EQ("42", fmt::format("{0:-}", 42ll));
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 42ull), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 42ull), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ("42", fmt::format("{0:-}", 42.0)); EXPECT_EQ("42", fmt::format("{0:-}", 42.0));
EXPECT_EQ("42", fmt::format("{0:-}", 42.0l)); EXPECT_EQ("42", fmt::format("{0:-}", 42.0l));
EXPECT_THROW_MSG(fmt::format(runtime("{0:-"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), "abc"), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:-}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -610,23 +614,24 @@ TEST(format_test, space_sign) {
EXPECT_EQ(" 42", fmt::format("{0: }", 42)); EXPECT_EQ(" 42", fmt::format("{0: }", 42));
EXPECT_EQ("-42", fmt::format("{0: }", -42)); EXPECT_EQ("-42", fmt::format("{0: }", -42));
EXPECT_EQ(" 42", fmt::format("{0: }", 42)); EXPECT_EQ(" 42", fmt::format("{0: }", 42));
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42u), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42u), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ(" 42", fmt::format("{0: }", 42l)); EXPECT_EQ(" 42", fmt::format("{0: }", 42l));
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42ul), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ul), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ(" 42", fmt::format("{0: }", 42ll)); EXPECT_EQ(" 42", fmt::format("{0: }", 42ll));
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 42ull), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 42ull), format_error,
"format specifier requires signed argument"); "format specifier requires signed argument");
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0)); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0));
EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l)); EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l));
EXPECT_THROW_MSG(fmt::format(runtime("{0: "), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0: "), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), "abc"), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0: }"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -670,13 +675,14 @@ TEST(format_test, hash_flag) {
EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01)); EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01));
EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5)); EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5));
EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5)); EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5));
EXPECT_THROW_MSG(fmt::format(runtime("{0:#"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), "abc"), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), reinterpret_cast<void*>(0x42)), EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:#}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -690,14 +696,14 @@ TEST(format_test, zero_flag) {
EXPECT_EQ("00042", fmt::format("{0:05}", 42ull)); EXPECT_EQ("00042", fmt::format("{0:05}", 42ull));
EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0)); EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0));
EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l)); EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l));
EXPECT_THROW_MSG(fmt::format(runtime("{0:0"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error,
"missing '}' in format string"); "missing '}' in format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), 'c'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error,
"invalid format specifier for char"); "invalid format specifier for char");
EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), "abc"), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
fmt::format(runtime("{0:05}"), reinterpret_cast<void*>(0x42)), (void)fmt::format(runtime("{0:05}"), reinterpret_cast<void*>(0x42)),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
} }
@ -705,19 +711,19 @@ TEST(format_test, width) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:%u", UINT_MAX); safe_sprintf(format_str, "{0:%u", UINT_MAX);
increment(format_str + 3); increment(format_str + 3);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:%u", INT_MAX + 1u); safe_sprintf(format_str, "{0:%u", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u); safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
EXPECT_EQ(" -42", fmt::format("{0:4}", -42)); EXPECT_EQ(" -42", fmt::format("{0:4}", -42));
EXPECT_EQ(" 42", fmt::format("{0:5}", 42u)); EXPECT_EQ(" 42", fmt::format("{0:5}", 42u));
@ -742,47 +748,47 @@ TEST(format_test, runtime_width) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:{%u", UINT_MAX); safe_sprintf(format_str, "{0:{%u", UINT_MAX);
increment(format_str + 4); increment(format_str + 4);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"invalid format string"); "invalid format string");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"argument not found"); "argument not found");
format_str[size + 1] = '}'; format_str[size + 1] = '}';
format_str[size + 2] = 0; format_str[size + 2] = 0;
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{}"), 0), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{?}}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{?}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{0:}}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{0:}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, -1), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error,
"negative width"); "negative width");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, -1l), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error,
"negative width"); "negative width");
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX; long value = INT_MAX;
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (value + 1)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)),
format_error, "number is too big"); format_error, "number is too big");
} }
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, '0'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error,
"width is not integer"); "width is not integer");
EXPECT_THROW_MSG(fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error,
"width is not integer"); "width is not integer");
EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4)); EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4));
@ -803,53 +809,53 @@ TEST(format_test, precision) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:.%u", UINT_MAX); safe_sprintf(format_str, "{0:.%u", UINT_MAX);
increment(format_str + 4); increment(format_str + 4);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u); safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u); safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"number is too big"); "number is too big");
EXPECT_THROW_MSG(fmt::format(runtime("{0:."), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0), format_error,
"missing precision specifier"); "missing precision specifier");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0), format_error,
"missing precision specifier"); "missing precision specifier");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42u), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42u), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42u), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42u), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42l), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42l), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42l), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42l), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ul), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ul), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ul), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ul), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ll), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ll), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ll), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ll), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2}"), 42ull), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2}"), 42ull), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.2f}"), 42ull), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2f}"), 42ull), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:3.0}"), 'x'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.0}"), 'x'), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345)); EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345));
EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l)); EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l));
@ -866,6 +872,23 @@ TEST(format_test, precision) {
"117102665855668676818703956031062493194527159149245532930545654440112748" "117102665855668676818703956031062493194527159149245532930545654440112748"
"012970999954193198940908041656332452475714786901472678015935523861155013" "012970999954193198940908041656332452475714786901472678015935523861155013"
"480352649347201937902681071074917033322268447533357208324319361e-324"); "480352649347201937902681071074917033322268447533357208324319361e-324");
EXPECT_EQ(
fmt::format("{:.1074f}", 1.1125369292536e-308),
"0.0000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000111253692925360019747947051741965785554081512200979"
"355021686109411883779182127659725163430929750364498219730822952552570601"
"152163505899912777129583674906301179059298598412303893909188340988729019"
"014361467448914817838555156840459458527907308695109202499990850735085304"
"478476991912072201449236975063640913461919914396877093174125167509869762"
"482369631100360266123742648159508919592746619553246586039571522788247697"
"156360766271842991667238355464496455107749716934387136380536472531224398"
"559833794807213172371254492216255558078524900147957309382830827524104234"
"530961756787819847850302379672357738807808384667004752163416921762619527"
"462847642037420991432005657440259928195996762610375541867198059294212446"
"81962777939941034720757232455434770912461317493580281734466552734375");
std::string outputs[] = { std::string outputs[] = {
"-0X1.41FE3FFE71C9E000000000000000000000000000000000000000000000000000000" "-0X1.41FE3FFE71C9E000000000000000000000000000000000000000000000000000000"
@ -903,13 +926,16 @@ TEST(format_test, precision) {
EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34)); EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34));
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)), (void)fmt::format(runtime("{0:.2}"), reinterpret_cast<void*>(0xcafe)),
format_error, "precision not allowed for this argument type"); format_error, "precision not allowed for this argument type");
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)), (void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
format_error, "precision not allowed for this argument type"); format_error, "precision not allowed for this argument type");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
fmt::format(runtime("{:.{}e}"), 42.0, fmt::detail::max_value<int>()), (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_EQ("st", fmt::format("{0:.2}", "str")); EXPECT_EQ("st", fmt::format("{0:.2}", "str"));
@ -919,86 +945,97 @@ TEST(format_test, runtime_precision) {
char format_str[buffer_size]; char format_str[buffer_size];
safe_sprintf(format_str, "{0:.{%u", UINT_MAX); safe_sprintf(format_str, "{0:.{%u", UINT_MAX);
increment(format_str + 5); increment(format_str + 5);
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"invalid format string"); "invalid format string");
size_t size = std::strlen(format_str); size_t size = std::strlen(format_str);
format_str[size] = '}'; format_str[size] = '}';
format_str[size + 1] = 0; format_str[size + 1] = 0;
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"argument not found"); "argument not found");
format_str[size + 1] = '}'; format_str[size + 1] = '}';
format_str[size + 2] = 0; format_str[size + 2] = 0;
EXPECT_THROW_MSG(fmt::format(runtime(format_str), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), 0), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{}"), 0), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{?}}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{?}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}"), 0, 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}"), 0, 0), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{0:}}"), 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{0:}}"), 0), format_error,
"invalid format string"); "invalid format string");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, -1), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1), format_error,
"negative precision"); "negative precision");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1u)),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, -1l), format_error,
"negative precision"); "negative precision");
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX; long value = INT_MAX;
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (value + 1)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (value + 1)),
format_error, "number is too big"); format_error, "number is too big");
} }
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, (INT_MAX + 1ul)),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, '0'), format_error,
"precision is not integer"); "precision is not integer");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0, 0.0), format_error,
"precision is not integer"); "precision is not integer");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42u, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42u, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42u, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42l, 2), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42l, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42l, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ul, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ul, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ul, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ul, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ll, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ll, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ll, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ll, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}}"), 42ull, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42ull, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:.{1}f}"), 42ull, 2), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"), 42ull, 2),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_THROW_MSG(fmt::format(runtime("{0:3.{1}}"), 'x', 0), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:3.{1}}"), 'x', 0),
format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2)); EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2));
EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l)); EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l));
EXPECT_THROW_MSG( EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"),
fmt::format(runtime("{0:.{1}}"), reinterpret_cast<void*>(0xcafe), 2), reinterpret_cast<void*>(0xcafe), 2),
format_error, "precision not allowed for this argument type"); format_error,
EXPECT_THROW_MSG( "precision not allowed for this argument type");
fmt::format(runtime("{0:.{1}f}"), reinterpret_cast<void*>(0xcafe), 2), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}f}"),
format_error, "precision not allowed for this argument type"); reinterpret_cast<void*>(0xcafe), 2),
format_error,
"precision not allowed for this argument type");
EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2)); EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2));
} }
@ -1029,15 +1066,15 @@ void check_unknown_types(const T& value, const char* types, const char*) {
if (std::strchr(types, c) || std::strchr(special, c) || !c) continue; if (std::strchr(types, c) || std::strchr(special, c) || !c) continue;
safe_sprintf(format_str, "{0:10%c}", c); safe_sprintf(format_str, "{0:10%c}", c);
const char* message = "invalid type specifier"; const char* message = "invalid type specifier";
EXPECT_THROW_MSG(fmt::format(runtime(format_str), value), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime(format_str), value),
message) format_error, message)
<< format_str << " " << message; << format_str << " " << message;
} }
} }
TEST(format_test, format_int) { TEST(format_test, format_int) {
EXPECT_THROW_MSG(fmt::format(runtime("{0:v"), 42), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:v"), 42), format_error,
"missing '}' in format string"); "invalid type specifier");
check_unknown_types(42, "bBdoxXnLc", "integer"); check_unknown_types(42, "bBdoxXnLc", "integer");
EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x'))); EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x')));
} }
@ -1345,26 +1382,10 @@ TEST(format_test, format_cstring) {
char nonconst[] = "nonconst"; char nonconst[] = "nonconst";
EXPECT_EQ("nonconst", fmt::format("{0}", nonconst)); EXPECT_EQ("nonconst", fmt::format("{0}", nonconst));
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
fmt::format(runtime("{0}"), static_cast<const char*>(nullptr)), (void)fmt::format(runtime("{0}"), static_cast<const char*>(nullptr)),
format_error, "string pointer is null"); format_error, "string pointer is null");
} }
TEST(format_test, format_schar_string) {
signed char str[] = "test";
EXPECT_EQ("test", fmt::format("{0:s}", str));
const signed char* const_str = str;
EXPECT_EQ("test", fmt::format("{0:s}", const_str));
}
TEST(format_test, format_uchar_string) {
unsigned char str[] = "test";
EXPECT_EQ("test", fmt::format("{0:s}", str));
const unsigned char* const_str = str;
EXPECT_EQ("test", fmt::format("{0:s}", const_str));
unsigned char* ptr = str;
EXPECT_EQ("test", fmt::format("{0:s}", ptr));
}
void function_pointer_test(int, double, std::string) {} void function_pointer_test(int, double, std::string) {}
TEST(format_test, format_pointer) { TEST(format_test, format_pointer) {
@ -1390,6 +1411,8 @@ TEST(format_test, format_pointer) {
TEST(format_test, format_string) { TEST(format_test, format_string) {
EXPECT_EQ("test", fmt::format("{0}", std::string("test"))); EXPECT_EQ("test", fmt::format("{0}", std::string("test")));
EXPECT_THROW((void)fmt::format(fmt::runtime("{:x}"), std::string("test")),
fmt::format_error);
} }
TEST(format_test, format_string_view) { TEST(format_test, format_string_view) {
@ -1479,7 +1502,7 @@ template <> struct formatter<Answer> : formatter<int> {
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(format_test, format_custom) { TEST(format_test, format_custom) {
EXPECT_THROW_MSG(fmt::format(runtime("{:s}"), date(2012, 12, 9)), EXPECT_THROW_MSG((void)fmt::format(runtime("{:s}"), date(2012, 12, 9)),
format_error, "unknown format specifier"); format_error, "unknown format specifier");
EXPECT_EQ("42", fmt::format("{0}", Answer())); EXPECT_EQ("42", fmt::format("{0}", Answer()));
EXPECT_EQ("0042", fmt::format("{:04}", Answer())); EXPECT_EQ("0042", fmt::format("{:04}", Answer()));
@ -1556,7 +1579,8 @@ TEST(format_test, format_examples) {
fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}", 42)); fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}", 42));
EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42)); EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42));
EXPECT_THROW_MSG(fmt::format(runtime("The answer is {:d}"), "forty-two"), EXPECT_THROW_MSG(
(void)fmt::format(runtime("The answer is {:d}"), "forty-two"),
format_error, "invalid type specifier"); format_error, "invalid type specifier");
EXPECT_WRITE( EXPECT_WRITE(
@ -1593,6 +1617,11 @@ TEST(format_test, bytes) {
EXPECT_EQ(10, s.size()); EXPECT_EQ(10, s.size());
} }
TEST(format_test, group_digits_view) {
EXPECT_EQ(fmt::format("{}", fmt::group_digits(10000000)), "10,000,000");
EXPECT_EQ(fmt::format("{:8}", fmt::group_digits(1000)), " 1,000");
}
enum test_enum { foo, bar }; enum test_enum { foo, bar };
TEST(format_test, join) { TEST(format_test, join) {
@ -1716,6 +1745,21 @@ TEST(format_test, custom_format_compile_time_string) {
using namespace fmt::literals; using namespace fmt::literals;
# if FMT_GCC_VERSION
# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1
# elif FMT_CLANG_VERSION && defined(__has_warning)
# if __has_warning("-Wdeprecated-declarations")
# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1
# endif
# endif
# ifndef FMT_CHECK_DEPRECATED_UDL_FORMAT
# define FMT_CHECK_DEPRECATED_UDL_FORMAT 0
# endif
# if FMT_CHECK_DEPRECATED_UDL_FORMAT
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
TEST(format_test, format_udl) { TEST(format_test, format_udl) {
EXPECT_EQ("{}c{}"_format("ab", 1), fmt::format("{}c{}", "ab", 1)); EXPECT_EQ("{}c{}"_format("ab", 1), fmt::format("{}c{}", "ab", 1));
EXPECT_EQ("foo"_format(), "foo"); EXPECT_EQ("foo"_format(), "foo");
@ -1723,6 +1767,9 @@ TEST(format_test, format_udl) {
EXPECT_EQ("{}"_format(date(2015, 10, 21)), "2015-10-21"); EXPECT_EQ("{}"_format(date(2015, 10, 21)), "2015-10-21");
} }
# pragma GCC diagnostic pop
# endif
TEST(format_test, named_arg_udl) { TEST(format_test, named_arg_udl) {
auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra", auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra",
"second"_a = "cad", "third"_a = 99); "second"_a = "cad", "third"_a = 99);
@ -1775,21 +1822,21 @@ TEST(format_test, dynamic_formatter) {
EXPECT_EQ("42", fmt::format("{:d}", num)); EXPECT_EQ("42", fmt::format("{:d}", num));
EXPECT_EQ("foo", fmt::format("{:s}", str)); EXPECT_EQ("foo", fmt::format("{:s}", str));
EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4)); EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4));
EXPECT_THROW_MSG(fmt::format(runtime("{0:{}}"), num), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{}}"), num), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(fmt::format(runtime("{:{0}}"), num), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:{0}}"), num), format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(fmt::format(runtime("{:+}"), str), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:+}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{:-}"), str), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:-}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{: }"), str), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{: }"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{:#}"), str), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:#}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{:0}"), str), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:0}"), str), format_error,
"format specifier requires numeric argument"); "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), num), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2}"), num), format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
} }
@ -1813,13 +1860,20 @@ struct formatter<adl_test::fmt::detail::foo> : formatter<std::string> {
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(format_test, to_string) { struct convertible_to_int {
EXPECT_EQ("42", fmt::to_string(42)); operator int() const { return value; }
EXPECT_EQ("0x1234", fmt::to_string(reinterpret_cast<void*>(0x1234)));
EXPECT_EQ("foo", fmt::to_string(adl_test::fmt::detail::foo()));
enum test_enum2 : unsigned char { test_value }; int value = 42;
EXPECT_EQ("0", fmt::to_string(test_value)); };
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");
enum foo : unsigned char { zero };
EXPECT_EQ(fmt::to_string(zero), "0");
} }
TEST(format_test, output_iterators) { TEST(format_test, output_iterators) {

View file

@ -25,6 +25,6 @@ function(add_fuzzer source)
target_compile_features(${name} PRIVATE cxx_generic_lambdas) target_compile_features(${name} PRIVATE cxx_generic_lambdas)
endfunction() endfunction()
foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc) foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)
add_fuzzer(${source}) add_fuzzer(${source})
endforeach () endforeach ()

View file

@ -22,6 +22,8 @@ here=$(pwd)
CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17" CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
CLANG=clang++-11
# For performance analysis of the fuzzers. # For performance analysis of the fuzzers.
builddir=$here/build-fuzzers-perfanalysis builddir=$here/build-fuzzers-perfanalysis
mkdir -p $builddir mkdir -p $builddir
@ -37,7 +39,7 @@ cmake --build $builddir
builddir=$here/build-fuzzers-ossfuzz builddir=$here/build-fuzzers-ossfuzz
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir
CXX="clang++" \ CXX=$CLANG \
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \ CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \
cmake $CMAKEFLAGSALL \ cmake $CMAKEFLAGSALL \
-DFMT_FUZZ_LINKMAIN=Off \ -DFMT_FUZZ_LINKMAIN=Off \
@ -50,7 +52,7 @@ cmake --build $builddir
builddir=$here/build-fuzzers-libfuzzer builddir=$here/build-fuzzers-libfuzzer
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir
CXX="clang++" \ CXX=$CLANG \
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \ CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \
cmake $CMAKEFLAGSALL \ cmake $CMAKEFLAGSALL \
-DFMT_FUZZ_LINKMAIN=Off \ -DFMT_FUZZ_LINKMAIN=Off \
@ -62,7 +64,7 @@ cmake --build $builddir
builddir=$here/build-fuzzers-fast builddir=$here/build-fuzzers-fast
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir
CXX="clang++" \ CXX=$CLANG \
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \ CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \
cmake $CMAKEFLAGSALL \ cmake $CMAKEFLAGSALL \
-DFMT_FUZZ_LINKMAIN=Off \ -DFMT_FUZZ_LINKMAIN=Off \

View file

@ -1,9 +1,10 @@
// Copyright (c) 2019, Paul Dreik // Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h. // For the license information refer to format.h.
#include <cstdint>
#include <fmt/chrono.h> #include <fmt/chrono.h>
#include <cstdint>
#include "fuzzer-common.h" #include "fuzzer-common.h"
template <typename Period, typename Rep> template <typename Period, typename Rep>
@ -13,8 +14,8 @@ void invoke_inner(fmt::string_view format_str, Rep rep) {
#if FMT_FUZZ_FORMAT_TO_STRING #if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str, value); std::string message = fmt::format(format_str, value);
#else #else
fmt::memory_buffer buf; auto buf = fmt::memory_buffer();
fmt::format_to(buf, format_str, value); fmt::format_to(std::back_inserter(buf), format_str, value);
#endif #endif
} catch (std::exception&) { } catch (std::exception&) {
} }

View file

@ -0,0 +1,32 @@
// Copyright (c) 2021, Paul Dreik
// For license information refer to format.h.
#include <fmt/chrono.h>
#include "fuzzer-common.h"
/*
* a fuzzer for the chrono timepoints formatters
* C is a clock (std::chrono::system_clock etc)
*/
template <typename C> void doit(const uint8_t* data, size_t size) {
using Rep = typename C::time_point::rep;
constexpr auto N = sizeof(Rep);
if (size < N) return;
const auto x = assign_from_buf<Rep>(data);
typename C::duration dur{x};
typename C::time_point timepoint{dur};
data += N;
size -= N;
data_to_string format_str(data, size);
std::string message = fmt::format(format_str.get(), timepoint);
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
try {
doit<std::chrono::system_clock>(data, size);
} catch (...) {
}
return 0;
}

View file

@ -1,17 +1,18 @@
// A fuzzer for floating-point formatter. // A fuzzer for floating-point formatter.
// For the license information refer to format.h. // For the license information refer to format.h.
#include <fmt/format.h>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <stdexcept>
#include <limits> #include <limits>
#include <fmt/format.h> #include <stdexcept>
#include "fuzzer-common.h" #include "fuzzer-common.h"
void check_round_trip(fmt::string_view format_str, double value) { void check_round_trip(fmt::string_view format_str, double value) {
auto buffer = fmt::memory_buffer(); auto buffer = fmt::memory_buffer();
fmt::format_to(buffer, format_str, value); fmt::format_to(std::back_inserter(buffer), format_str, value);
if (std::isnan(value)) { if (std::isnan(value)) {
auto nan = std::signbit(value) ? "-nan" : "nan"; auto nan = std::signbit(value) ? "-nan" : "nan";
@ -24,8 +25,7 @@ void check_round_trip(fmt::string_view format_str, double value) {
char* ptr = nullptr; char* ptr = nullptr;
if (std::strtod(buffer.data(), &ptr) != value) if (std::strtod(buffer.data(), &ptr) != value)
throw std::runtime_error("round trip failure"); throw std::runtime_error("round trip failure");
if (ptr + 1 != buffer.end()) if (ptr + 1 != buffer.end()) throw std::runtime_error("unparsed output");
throw std::runtime_error("unparsed output");
} }
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {

View file

@ -4,12 +4,12 @@
#ifndef FUZZER_COMMON_H #ifndef FUZZER_COMMON_H
#define FUZZER_COMMON_H #define FUZZER_COMMON_H
#include <fmt/core.h>
#include <cstdint> // std::uint8_t #include <cstdint> // std::uint8_t
#include <cstring> // memcpy #include <cstring> // memcpy
#include <vector> #include <vector>
#include <fmt/core.h>
// One can format to either a string, or a buffer. The latter is faster, but // One can format to either a string, or a buffer. The latter is faster, but
// one may be interested in formatting to a string instead to verify it works // one may be interested in formatting to a string instead to verify it works
// as intended. To avoid a combinatoric explosion, select this at compile time // as intended. To avoid a combinatoric explosion, select this at compile time
@ -56,8 +56,10 @@ struct data_to_string {
data_to_string(const uint8_t* data, size_t size, bool add_terminator = false) data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
: buffer(size + (add_terminator ? 1 : 0)) { : buffer(size + (add_terminator ? 1 : 0)) {
if (size) {
std::memcpy(buffer.data(), data, size); std::memcpy(buffer.data(), data, size);
} }
}
fmt::string_view get() const { return {buffer.data(), buffer.size()}; } fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
#else #else

View file

@ -1,10 +1,11 @@
// Copyright (c) 2019, Paul Dreik // Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h. // For the license information refer to format.h.
#include <fmt/chrono.h>
#include <cstdint> #include <cstdint>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
#include <fmt/chrono.h>
#include "fuzzer-common.h" #include "fuzzer-common.h"
@ -28,7 +29,8 @@ void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
fmt::format(format_str.get(), fmt::arg(arg_name.data(), value)); fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
#else #else
fmt::memory_buffer out; fmt::memory_buffer out;
fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value)); fmt::format_to(std::back_inserter(out), format_str.get(),
fmt::arg(arg_name.data(), value));
#endif #endif
} catch (std::exception&) { } catch (std::exception&) {
} }

View file

@ -1,17 +1,18 @@
// Copyright (c) 2019, Paul Dreik // Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h. // For the license information refer to format.h.
#include <fmt/chrono.h>
#include <cstdint> #include <cstdint>
#include <exception> #include <exception>
#include <fmt/chrono.h>
#include "fuzzer-common.h" #include "fuzzer-common.h"
template <typename T, typename Repr> template <typename T, typename Repr> const T* from_repr(const Repr& r) {
const T* from_repr(const Repr& r) { return &r; } return &r;
}
template <> template <> const std::tm* from_repr<std::tm>(const std::time_t& t) {
const std::tm* from_repr<std::tm>(const std::time_t& t) {
return std::localtime(&t); return std::localtime(&t);
} }

View file

@ -1,10 +1,11 @@
// Copyright (c) 2019, Paul Dreik // Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h. // For the license information refer to format.h.
#include <fmt/format.h>
#include <cstdint> #include <cstdint>
#include <exception> #include <exception>
#include <string> #include <string>
#include <fmt/format.h>
#include "fuzzer-common.h" #include "fuzzer-common.h"

View file

@ -32,11 +32,10 @@ output_redirect::~output_redirect() FMT_NOEXCEPT {
} }
void output_redirect::flush() { void output_redirect::flush() {
# if EOF != -1
# error "FMT_RETRY assumes return value of -1 indicating failure"
# endif
int result = 0; int result = 0;
FMT_RETRY(result, fflush(file_)); do {
result = fflush(file_);
} while (result == EOF && errno == EINTR);
if (result != 0) throw fmt::system_error(errno, "cannot flush stream"); if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
} }

View file

@ -12,7 +12,12 @@
#include <string> #include <string>
#include "fmt/os.h" #ifdef FMT_MODULE_TEST
import fmt;
#else
# include "fmt/os.h"
#endif // FMG_MODULE_TEST
#include "gmock/gmock.h" #include "gmock/gmock.h"
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
@ -129,6 +134,7 @@ class suppress_assert {
~suppress_assert() { ~suppress_assert() {
_set_invalid_parameter_handler(original_handler_); _set_invalid_parameter_handler(original_handler_);
_CrtSetReportMode(_CRT_ASSERT, original_report_mode_); _CrtSetReportMode(_CRT_ASSERT, original_report_mode_);
(void)original_report_mode_;
} }
}; };

View file

@ -17,6 +17,13 @@ else ()
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
endif () endif ()
# Workaround GTest bug https://github.com/google/googletest/issues/705.
check_cxx_compiler_flag(
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks)
endif ()
if (MSVC) if (MSVC)
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)

View file

@ -1,7 +1,11 @@
// Header-only configuration test // Header-only configuration test
#include "fmt/core.h" #include "fmt/core.h"
#include "fmt/ostream.h"
#include "gtest/gtest.h"
#ifndef FMT_HEADER_ONLY #ifndef FMT_HEADER_ONLY
# error "Not in the header-only mode." # error "Not in the header-only mode."
#endif #endif
TEST(header_only_test, format) { EXPECT_EQ(fmt::format("foo"), "foo"); }

View file

@ -15,6 +15,8 @@
# define FMT_HIDE_MODULE_BUGS # define FMT_HIDE_MODULE_BUGS
#endif #endif
#define FMT_MODULE_TEST
#include <bit> #include <bit>
#include <chrono> #include <chrono>
#include <exception> #include <exception>
@ -35,18 +37,30 @@
# define FMT_USE_FCNTL 0 # define FMT_USE_FCNTL 0
#endif #endif
#define FMT_NOEXCEPT noexcept #define FMT_NOEXCEPT noexcept
#if defined(_WIN32) && !defined(__MINGW32__)
# define FMT_POSIX(call) _##call
#else
# define FMT_POSIX(call) call
#endif
#define FMT_OS_H_ // don't pull in os.h directly or indirectly
import fmt; import fmt;
// check for macros leaking from BMI // check for macros leaking from BMI
static bool macro_leaked = static bool macro_leaked =
#if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H) #if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H_)
true; true;
#else #else
false; false;
#endif #endif
#include "gtest-extra.h" // Include sources to pick up functions and classes from the module rather than
// from the non-modular library which is baked into the 'test-main' library.
// This averts linker problems:
// - strong ownership model: missing linker symbols
// - weak ownership model: duplicate linker symbols
#include "gtest-extra.cc"
#include "util.cc"
// an implicitly exported namespace must be visible [module.interface]/2.2 // an implicitly exported namespace must be visible [module.interface]/2.2
TEST(module_test, namespace) { TEST(module_test, namespace) {
@ -62,8 +76,10 @@ bool oops_detail_namespace_is_visible;
namespace fmt { namespace fmt {
bool namespace_detail_invisible() { bool namespace_detail_invisible() {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ #if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
_MSC_FULL_VER <= 192930129 ((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \
// bug in msvc up to 16.11-pre1: (_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704))
// bug in msvc up to 16.11.5 / 17.0-pre5:
// the namespace is visible even when it is neither // the namespace is visible even when it is neither
// implicitly nor explicitly exported // implicitly nor explicitly exported
return true; return true;
@ -83,8 +99,8 @@ TEST(module_test, detail_namespace) {
// macros must not be imported from a *named* module [cpp.import]/5.1 // macros must not be imported from a *named* module [cpp.import]/5.1
TEST(module_test, macros) { TEST(module_test, macros) {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ #if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
_MSC_FULL_VER <= 192930129 _MSC_FULL_VER <= 192930130
// bug in msvc up to 16.11-pre1: // bug in msvc up to 16.11-pre2:
// include-guard macros leak from BMI // include-guard macros leak from BMI
// and even worse: they cannot be #undef-ined // and even worse: they cannot be #undef-ined
macro_leaked = false; macro_leaked = false;
@ -442,8 +458,7 @@ TEST(module_test, time_duration) {
} }
TEST(module_test, weekday) { TEST(module_test, weekday) {
EXPECT_EQ("Monday", EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1)));
std::format(std::locale::classic(), "{:%A}", fmt::weekday(1)));
} }
TEST(module_test, to_string_view) { TEST(module_test, to_string_view) {

View file

@ -0,0 +1,18 @@
// Formatting library for C++ - Noexception tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/args.h"
#include "fmt/chrono.h"
#include "fmt/color.h"
#include "fmt/compile.h"
#include "fmt/core.h"
#include "fmt/format.h"
#include "fmt/os.h"
#include "fmt/ostream.h"
#include "fmt/printf.h"
#include "fmt/ranges.h"
#include "fmt/xchar.h"

View file

@ -41,9 +41,9 @@ TEST(util_test, utf16_to_utf8_empty_string) {
} }
template <typename Converter, typename Char> template <typename Converter, typename Char>
void check_utf_conversion_error( void check_utf_conversion_error(const char* message,
const char* message, fmt::basic_string_view<Char> str =
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) { fmt::basic_string_view<Char>(nullptr, 1)) {
fmt::memory_buffer out; fmt::memory_buffer out;
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message); fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
auto error = std::system_error(std::error_code()); auto error = std::system_error(std::error_code());
@ -63,7 +63,7 @@ TEST(util_test, utf16_to_utf8_error) {
TEST(util_test, utf16_to_utf8_convert) { TEST(util_test, utf16_to_utf8_convert) {
fmt::detail::utf16_to_utf8 u; fmt::detail::utf16_to_utf8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(0, 1))); EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(nullptr, 1)));
EXPECT_EQ(ERROR_INVALID_PARAMETER, EXPECT_EQ(ERROR_INVALID_PARAMETER,
u.convert(wstring_view(L"foo", INT_MAX + 1u))); u.convert(wstring_view(L"foo", INT_MAX + 1u)));
} }
@ -81,12 +81,12 @@ TEST(os_test, format_std_error_code) {
} }
TEST(os_test, format_windows_error) { TEST(os_test, format_windows_error) {
LPWSTR message = 0; LPWSTR message = nullptr;
auto result = FormatMessageW( auto result = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_IGNORE_INSERTS,
0, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0); reinterpret_cast<LPWSTR>(&message), 0, nullptr);
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2)); fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
LocalFree(message); LocalFree(message);
fmt::memory_buffer actual_message; fmt::memory_buffer actual_message;
@ -97,17 +97,17 @@ TEST(os_test, format_windows_error) {
} }
TEST(os_test, format_long_windows_error) { TEST(os_test, format_long_windows_error) {
LPWSTR message = 0; LPWSTR message = nullptr;
// this error code is not available on all Windows platforms and // this error code is not available on all Windows platforms and
// Windows SDKs, so do not fail the test if the error string cannot // Windows SDKs, so do not fail the test if the error string cannot
// be retrieved. // be retrieved.
int provisioning_not_allowed = 0x80284013L; // TBS_E_PROVISIONING_NOT_ALLOWED int provisioning_not_allowed = 0x80284013L; // TBS_E_PROVISIONING_NOT_ALLOWED
auto result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | auto result = FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_IGNORE_INSERTS,
0, static_cast<DWORD>(provisioning_not_allowed), nullptr, static_cast<DWORD>(provisioning_not_allowed),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0); reinterpret_cast<LPWSTR>(&message), 0, nullptr);
if (result == 0) { if (result == 0) {
LocalFree(message); LocalFree(message);
return; return;
@ -336,6 +336,14 @@ TEST(ostream_test, truncate) {
EXPECT_EQ("foo", read(in, 4)); EXPECT_EQ("foo", read(in, 4));
} }
TEST(ostream_test, flush) {
auto out = fmt::output_file("test-file");
out.print("x");
out.flush();
auto in = fmt::file("test-file", file::RDONLY);
EXPECT_READ(in, "x");
}
TEST(file_test, default_ctor) { TEST(file_test, default_ctor) {
file f; file f;
EXPECT_EQ(-1, f.descriptor()); EXPECT_EQ(-1, f.descriptor());
@ -540,13 +548,4 @@ TEST(file_test, fdopen) {
int read_fd = read_end.descriptor(); int read_fd = read_end.descriptor();
EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get()))); EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get())));
} }
# ifdef FMT_LOCALE
TEST(locale_test, strtod) {
fmt::locale loc;
const char *start = "4.2", *ptr = start;
EXPECT_EQ(4.2, loc.strtod(ptr));
EXPECT_EQ(start + 3, ptr);
}
# endif
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL

View file

@ -23,6 +23,7 @@ template <> struct formatter<test> : formatter<int> {
#include <sstream> #include <sstream>
#include "fmt/compile.h"
#include "fmt/ostream.h" #include "fmt/ostream.h"
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "gmock/gmock.h" #include "gmock/gmock.h"
@ -69,16 +70,16 @@ TEST(ostream_test, format_specs) {
EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def"))); EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def")));
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def"))); EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def"))); EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), test_string()), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()),
"format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), test_string()), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()),
"format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), test_string()), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()),
"format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), test_string()), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()),
"format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), test_string()), format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()),
"format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test"))); EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13)); EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13));
EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test"))); EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test")));
@ -115,12 +116,12 @@ TEST(ostream_test, write_to_ostream_max_size) {
struct test_buffer final : fmt::detail::buffer<char> { struct test_buffer final : fmt::detail::buffer<char> {
explicit test_buffer(size_t size) explicit test_buffer(size_t size)
: fmt::detail::buffer<char>(nullptr, size, size) {} : fmt::detail::buffer<char>(nullptr, size, size) {}
void grow(size_t) {} void grow(size_t) override {}
} buffer(max_size); } buffer(max_size);
struct mock_streambuf : std::streambuf { struct mock_streambuf : std::streambuf {
MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n)); MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n));
std::streamsize xsputn(const char* s, std::streamsize n) { std::streamsize xsputn(const char* s, std::streamsize n) override {
const void* v = s; const void* v = s;
return xsputn(v, n); return xsputn(v, n);
} }
@ -257,7 +258,8 @@ std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
} }
TEST(ostream_test, format_convertible_to_bool) { TEST(ostream_test, format_convertible_to_bool) {
EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool())); // operator<< is intentionally not used because of potential ODR violations.
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true");
} }
struct copyfmt_test {}; struct copyfmt_test {};
@ -280,3 +282,20 @@ TEST(ostream_test, range) {
auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")}; auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")};
EXPECT_EQ("[foo, bar]", fmt::format("{}", strs)); EXPECT_EQ("[foo, bar]", fmt::format("{}", strs));
} }
struct abstract {
virtual ~abstract() = default;
virtual void f() = 0;
friend std::ostream& operator<<(std::ostream& os, const abstract&) {
return os;
}
};
void format_abstract_compiles(const abstract& a) {
fmt::format(FMT_COMPILE("{}"), a);
}
TEST(ostream_test, is_formattable) {
EXPECT_TRUE(fmt::is_formattable<std::string>());
EXPECT_TRUE(fmt::is_formattable<fmt::detail::std_string_view<char>>());
}

View file

@ -467,9 +467,6 @@ struct locale_mock {
MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale, MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale,
locale_type base)); locale_type base));
MOCK_METHOD1(freelocale, void(locale_type locale)); MOCK_METHOD1(freelocale, void(locale_type locale));
MOCK_METHOD3(strtod_l,
double(const char* nptr, char** endptr, locale_type locale));
} * locale_mock::instance; } * locale_mock::instance;
# ifdef _MSC_VER # ifdef _MSC_VER
@ -487,10 +484,6 @@ _locale_t _create_locale(int category, const char* locale) {
void _free_locale(_locale_t locale) { void _free_locale(_locale_t locale) {
locale_mock::instance->freelocale(locale); locale_mock::instance->freelocale(locale);
} }
double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
return locale_mock::instance->strtod_l(nptr, endptr, locale);
}
# ifdef __clang__ # ifdef __clang__
# pragma clang diagnostic pop # pragma clang diagnostic pop
# endif # endif
@ -516,11 +509,6 @@ FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW {
return FreeLocaleResult(); return FreeLocaleResult();
} }
double strtod_l(const char* nptr, char** endptr,
locale_type locale) FMT_LOCALE_THROW {
return locale_mock::instance->strtod_l(nptr, endptr, locale);
}
# undef FMT_LOCALE_THROW # undef FMT_LOCALE_THROW
# ifndef _WIN32 # ifndef _WIN32
@ -549,18 +537,4 @@ TEST(locale_test, locale) {
EXPECT_EQ(impl, loc.get()); EXPECT_EQ(impl, loc.get());
} }
TEST(locale_test, strtod) {
scoped_mock<locale_mock> mock;
EXPECT_CALL(mock, newlocale(_, _, _))
.WillOnce(Return(reinterpret_cast<locale_type>(42)));
EXPECT_CALL(mock, freelocale(_));
fmt::locale loc;
const char* str = "4.2";
char end = 'x';
EXPECT_CALL(mock, strtod_l(str, _, loc.get()))
.WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777)));
EXPECT_EQ(777, loc.strtod(str));
EXPECT_EQ(&end, str);
}
#endif // FMT_LOCALE #endif // FMT_LOCALE

View file

@ -481,12 +481,6 @@ TEST(printf_test, string) {
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr); EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
} }
TEST(printf_test, uchar_string) {
unsigned char str[] = "test";
unsigned char* pstr = str;
EXPECT_EQ("test", fmt::sprintf("%s", pstr));
}
TEST(printf_test, pointer) { TEST(printf_test, pointer) {
int n; int n;
void* p = &n; void* p = &n;

View file

@ -0,0 +1,17 @@
// Formatting library for C++ - the core API
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include <vector>
#include "fmt/ranges.h"
#include "gtest/gtest.h"
// call fmt::format from another translation unit to test ODR
TEST(ranges_odr_test, format_vector) {
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
}

View file

@ -55,7 +55,12 @@ TEST(ranges_test, format_vector2) {
TEST(ranges_test, format_map) { TEST(ranges_test, format_map) {
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}}; auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
EXPECT_EQ(fmt::format("{}", m), "[(\"one\", 1), (\"two\", 2)]"); EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
}
TEST(ranges_test, format_set) {
EXPECT_EQ(fmt::format("{}", std::set<std::string>{"one", "two"}),
"{\"one\", \"two\"}");
} }
TEST(ranges_test, format_pair) { TEST(ranges_test, format_pair) {
@ -190,7 +195,14 @@ TEST(ranges_test, range) {
EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]"); EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]");
} }
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 enum test_enum { foo };
TEST(ranges_test, enum_range) {
auto v = std::vector<test_enum>{test_enum::foo};
EXPECT_EQ(fmt::format("{}", v), "[0]");
}
#if !FMT_MSC_VER
struct unformattable {}; struct unformattable {};
TEST(ranges_test, unformattable_range) { TEST(ranges_test, unformattable_range) {
@ -217,6 +229,21 @@ TEST(ranges_test, join_tuple) {
// Single element tuple. // Single element tuple.
auto t4 = std::tuple<float>(4.0f); auto t4 = std::tuple<float>(4.0f);
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
# if FMT_TUPLE_JOIN_SPECIFIERS
// Specs applied to each element.
auto t5 = std::tuple<int, int, long>(-3, 100, 1);
EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01");
auto t6 = std::tuple<float, double, long double>(3, 3.14, 3.1415);
EXPECT_EQ(fmt::format("{:5.5f}", fmt::join(t6, ", ")),
"3.00000, 3.14000, 3.14150");
// Testing lvalue tuple args.
int y = -1;
auto t7 = std::tuple<int, int&, const int&>(3, y, y);
EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01");
# endif
} }
TEST(ranges_test, join_initializer_list) { TEST(ranges_test, join_initializer_list) {
@ -236,6 +263,36 @@ struct zstring {
zstring_sentinel end() const { return {}; } zstring_sentinel end() const { return {}; }
}; };
# ifdef __cpp_lib_ranges
struct cpp20_only_range {
struct iterator {
int val = 0;
using value_type = int;
using difference_type = std::ptrdiff_t;
using iterator_concept = std::input_iterator_tag;
iterator() = default;
iterator(int i) : val(i) {}
int operator*() const { return val; }
iterator& operator++() {
++val;
return *this;
}
void operator++(int) { ++*this; }
bool operator==(const iterator& rhs) const { return val == rhs.val; }
};
int lo;
int hi;
iterator begin() const { return iterator(lo); }
iterator end() const { return iterator(hi); }
};
static_assert(std::input_iterator<cpp20_only_range::iterator>);
# endif
TEST(ranges_test, join_sentinel) { TEST(ranges_test, join_sentinel) {
auto hello = zstring{"hello"}; auto hello = zstring{"hello"};
EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']"); EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']");
@ -260,5 +317,47 @@ TEST(ranges_test, join_range) {
const auto z = std::vector<int>(3u, 0); const auto z = std::vector<int>(3u, 0);
EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0"); EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
# ifdef __cpp_lib_ranges
EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}),
"[0, 1, 2, 3, 4]");
EXPECT_EQ(
fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")),
"0,1,2,3,4");
# endif
} }
#endif // FMT_RANGES_TEST_ENABLE_JOIN #endif // FMT_RANGES_TEST_ENABLE_JOIN
TEST(ranges_test, is_printable) {
using fmt::detail::is_printable;
EXPECT_TRUE(is_printable(0x0323));
EXPECT_FALSE(is_printable(0x0378));
EXPECT_FALSE(is_printable(0x110000));
}
TEST(ranges_test, escape_string) {
using vec = std::vector<std::string>;
EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]");
EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]");
if (fmt::detail::is_utf8()) {
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
// Unassigned Unicode code points.
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}),
"[\"\\xf4\\x8f\\xbf\\xc0\"]");
}
}
#ifdef FMT_USE_STRING_VIEW
struct convertible_to_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(ranges_test, escape_convertible_to_string_view) {
EXPECT_EQ(fmt::format("{}", std::vector<convertible_to_string_view>(1)),
"[\"foo\"]");
}
#endif // FMT_USE_STRING_VIEW

View file

@ -35,6 +35,7 @@ int main(int argc, char** argv) {
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
try { try {
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_death_test_style = "threadsafe";
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} catch (...) { } catch (...) {
// Catch all exceptions to make Coverity happy. // Catch all exceptions to make Coverity happy.

View file

@ -18,7 +18,7 @@ using testing::Contains;
TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); } TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); }
TEST(unicode_test, legacy_locale) { TEST(unicode_test, legacy_locale) {
auto loc = get_locale("ru_RU.CP1251", "Russian.1251"); auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251");
if (loc == std::locale::classic()) return; if (loc == std::locale::classic()) return;
auto s = std::string(); auto s = std::string();

View file

@ -10,7 +10,11 @@
#include <locale> #include <locale>
#include <string> #include <string>
#include "fmt/os.h" #ifdef FMT_MODULE_TEST
import fmt;
#else
# include "fmt/os.h"
#endif // FMT_MODULE_TEST
#ifdef _MSC_VER #ifdef _MSC_VER
# define FMT_VSNPRINTF vsprintf_s # define FMT_VSNPRINTF vsprintf_s
@ -34,7 +38,7 @@ fmt::buffered_file open_buffered_file(FILE** fp = nullptr);
inline FILE* safe_fopen(const char* filename, const char* mode) { inline FILE* safe_fopen(const char* filename, const char* mode) {
#if defined(_WIN32) && !defined(__MINGW32__) #if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen. // Fix MSVC warning about "unsafe" fopen.
FILE* f = 0; FILE* f = nullptr;
errno = fopen_s(&f, filename, mode); errno = fopen_s(&f, filename, mode);
return f; return f;
#else #else

View file

@ -8,14 +8,18 @@
#include "fmt/xchar.h" #include "fmt/xchar.h"
#include <complex> #include <complex>
#include <cwchar>
#include <vector>
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include "fmt/color.h" #include "fmt/color.h"
#include "fmt/ostream.h" #include "fmt/ostream.h"
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "gtest/gtest.h" #include "gtest-extra.h" // Contains
#include "util.h" // get_locale
using fmt::detail::max_value; using fmt::detail::max_value;
using testing::Contains;
namespace test_ns { namespace test_ns {
template <typename Char> class test_string { template <typename Char> class test_string {
@ -87,6 +91,10 @@ TEST(xchar_test, format) {
EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
} }
TEST(xchar_test, is_formattable) {
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
}
TEST(xchar_test, compile_time_string) { TEST(xchar_test, compile_time_string) {
#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L #if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
@ -257,6 +265,55 @@ TEST(xchar_test, chrono) {
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
"The date is 2016-04-25 11:22:33."); "The date is 2016-04-25 11:22:33.");
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
}
std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
std::locale* locptr = nullptr) {
auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
std::wostringstream os;
os.imbue(loc);
facet.put(os, os, L' ', timeptr, format.c_str(),
format.c_str() + format.size());
#ifdef _WIN32
// Workaround a bug in older versions of Universal CRT.
auto str = os.str();
if (str == L"-0000") str = L"+0000";
return str;
#else
return os.str();
#endif
}
TEST(chrono_test, time_point) {
auto t1 = std::chrono::system_clock::now();
std::vector<std::wstring> spec_list = {
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U",
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"};
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
#ifndef _WIN32
// Disabled on Windows, because these formats is not consistent among
// platforms.
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt_spec, t1));
EXPECT_EQ(sys_output, fmt::format(fmt_spec, tm));
}
} }
TEST(xchar_test, color) { TEST(xchar_test, color) {
@ -301,10 +358,12 @@ template <typename Char> struct small_grouping : std::numpunct<Char> {
Char do_thousands_sep() const override { return ','; } Char do_thousands_sep() const override { return ','; }
}; };
TEST(locale_test, double_decimal_point) { TEST(locale_test, localized_double) {
auto loc = std::locale(std::locale(), new numpunct<char>()); auto loc = std::locale(std::locale(), new numpunct<char>());
EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23)); EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23));
EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23)); EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23));
EXPECT_EQ("1~234?5", fmt::format(loc, "{:L}", 1234.5));
EXPECT_EQ("12~000", fmt::format(loc, "{:L}", 12000.0));
} }
TEST(locale_test, format) { TEST(locale_test, format) {
@ -403,7 +462,7 @@ template <class charT> struct formatter<std::complex<double>, charT> {
specs_.precision, specs_.precision_ref, ctx); specs_.precision, specs_.precision_ref, ctx);
auto specs = std::string(); auto specs = std::string();
if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision); if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision);
if (specs_.type) specs += specs_.type; if (specs_.type == presentation_type::fixed_lower) specs += 'f';
auto real = fmt::format(ctx.locale().template get<std::locale>(), auto real = fmt::format(ctx.locale().template get<std::locale>(),
fmt::runtime("{:" + specs + "}"), c.real()); fmt::runtime("{:" + specs + "}"), c.real());
auto imag = fmt::format(ctx.locale().template get<std::locale>(), auto imag = fmt::format(ctx.locale().template get<std::locale>(),
@ -424,4 +483,20 @@ TEST(locale_test, complex) {
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)"); EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)");
} }
TEST(locale_test, chrono_weekday) {
auto loc = get_locale("ru_RU.UTF-8", "Russian_Russia.1251");
auto loc_old = std::locale::global(loc);
auto mon = fmt::weekday(1);
EXPECT_EQ(fmt::format(L"{}", mon), L"Mon");
if (loc != std::locale::classic()) {
// {L"\x43F\x43D", L"\x41F\x43D", L"\x43F\x43D\x434", L"\x41F\x43D\x434"}
// {L"пн", L"Пн", L"пнд", L"Пнд"}
EXPECT_THAT(
(std::vector<std::wstring>{L"\x43F\x43D", L"\x41F\x43D",
L"\x43F\x43D\x434", L"\x41F\x43D\x434"}),
Contains(fmt::format(loc, L"{:L}", mon)));
}
std::locale::global(loc_old);
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR #endif // FMT_STATIC_THOUSANDS_SEPARATOR

View file

@ -172,6 +172,7 @@ u32 A32JitState::Fpscr() const {
FPSCR |= (mxcsr & 0b0000000000001); // IOC = IE FPSCR |= (mxcsr & 0b0000000000001); // IOC = IE
FPSCR |= (mxcsr & 0b0000000111100) >> 1; // IXC, UFC, OFC, DZC = PE, UE, OE, ZE FPSCR |= (mxcsr & 0b0000000111100) >> 1; // IXC, UFC, OFC, DZC = PE, UE, OE, ZE
FPSCR |= fpsr_exc; FPSCR |= fpsr_exc;
FPSCR |= fpsr_qc != 0 ? 1 << 27 : 0;
return FPSCR; return FPSCR;
} }
@ -184,6 +185,7 @@ void A32JitState::SetFpscr(u32 FPSCR) {
upper_location_descriptor |= FPSCR & FPSCR_MODE_MASK; upper_location_descriptor |= FPSCR & FPSCR_MODE_MASK;
fpsr_nzcv = FPSCR & FPSCR_NZCV_MASK; fpsr_nzcv = FPSCR & FPSCR_NZCV_MASK;
fpsr_qc = (FPSCR >> 27) & 1;
guest_MXCSR = 0x00001f80; guest_MXCSR = 0x00001f80;
asimd_MXCSR = 0x00009fc0; asimd_MXCSR = 0x00009fc0;

View file

@ -53,7 +53,7 @@ struct A32JitState {
void ResetRSB(); void ResetRSB();
u32 fpsr_exc = 0; u32 fpsr_exc = 0;
u32 fpsr_qc = 0; // Dummy value u32 fpsr_qc = 0;
u32 fpsr_nzcv = 0; u32 fpsr_nzcv = 0;
u32 Fpscr() const; u32 Fpscr() const;
void SetFpscr(u32 FPSCR); void SetFpscr(u32 FPSCR);

View file

@ -190,8 +190,8 @@ void BlockOfCode::ClearCache() {
size_t BlockOfCode::SpaceRemaining() const { size_t BlockOfCode::SpaceRemaining() const {
ASSERT(prelude_complete); ASSERT(prelude_complete);
const u8* current_near_ptr = in_far_code ? reinterpret_cast<const u8*>(near_code_ptr) : getCode<const u8*>(); const u8* current_near_ptr = in_far_code ? reinterpret_cast<const u8*>(near_code_ptr) : getCurr<const u8*>();
const u8* current_far_ptr = in_far_code ? getCode<const u8*>() : reinterpret_cast<const u8*>(far_code_ptr); const u8* current_far_ptr = in_far_code ? getCurr<const u8*>() : reinterpret_cast<const u8*>(far_code_ptr);
if (current_near_ptr >= far_code_begin) if (current_near_ptr >= far_code_begin)
return 0; return 0;
if (current_far_ptr >= &top_[maxSize_]) if (current_far_ptr >= &top_[maxSize_])

View file

@ -54,11 +54,8 @@ constexpr u64 f64_max_s16 = 0x40dfffc000000000u; // 32767 as a double
constexpr u64 f64_min_u16 = 0x0000000000000000u; // 0 as a double constexpr u64 f64_min_u16 = 0x0000000000000000u; // 0 as a double
constexpr u64 f64_max_u16 = 0x40efffe000000000u; // 65535 as a double constexpr u64 f64_max_u16 = 0x40efffe000000000u; // 65535 as a double
constexpr u64 f64_max_s32 = 0x41dfffffffc00000u; // 2147483647 as a double constexpr u64 f64_max_s32 = 0x41dfffffffc00000u; // 2147483647 as a double
constexpr u64 f64_min_u32 = 0x0000000000000000u; // 0 as a double
constexpr u64 f64_max_u32 = 0x41efffffffe00000u; // 4294967295 as a double constexpr u64 f64_max_u32 = 0x41efffffffe00000u; // 4294967295 as a double
constexpr u64 f64_max_s64_lim = 0x43e0000000000000u; // 2^63 as a double (actual maximum unrepresentable) constexpr u64 f64_max_s64_lim = 0x43e0000000000000u; // 2^63 as a double (actual maximum unrepresentable)
constexpr u64 f64_min_u64 = 0x0000000000000000u; // 0 as a double
constexpr u64 f64_max_u64_lim = 0x43f0000000000000u; // 2^64 as a double (actual maximum unrepresentable)
#define FCODE(NAME) \ #define FCODE(NAME) \
[&code](auto... args) { \ [&code](auto... args) { \
@ -1136,14 +1133,13 @@ static void EmitFPRSqrtEstimate(BlockOfCode& code, EmitContext& ctx, IR::Inst* i
code.SwitchToNearCode(); code.SwitchToNearCode();
ctx.reg_alloc.DefineValue(inst, result); ctx.reg_alloc.DefineValue(inst, result);
return; } else {
}
auto args = ctx.reg_alloc.GetArgumentInfo(inst); auto args = ctx.reg_alloc.GetArgumentInfo(inst);
ctx.reg_alloc.HostCall(inst, args[0]); ctx.reg_alloc.HostCall(inst, args[0]);
code.mov(code.ABI_PARAM2.cvt32(), ctx.FPCR().Value()); code.mov(code.ABI_PARAM2.cvt32(), ctx.FPCR().Value());
code.lea(code.ABI_PARAM3, code.ptr[code.r15 + code.GetJitStateInfo().offsetof_fpsr_exc]); code.lea(code.ABI_PARAM3, code.ptr[code.r15 + code.GetJitStateInfo().offsetof_fpsr_exc]);
code.CallFunction(&FP::FPRSqrtEstimate<FPT>); code.CallFunction(&FP::FPRSqrtEstimate<FPT>);
}
} }
void EmitX64::EmitFPRSqrtEstimate16(EmitContext& ctx, IR::Inst* inst) { void EmitX64::EmitFPRSqrtEstimate16(EmitContext& ctx, IR::Inst* inst) {
@ -1474,9 +1470,11 @@ static void EmitFPToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
if constexpr (fsize != 16) { if constexpr (fsize != 16) {
const auto round_imm = ConvertRoundingModeToX64Immediate(rounding_mode); const auto round_imm = ConvertRoundingModeToX64Immediate(rounding_mode);
if (code.HasHostFeature(HostFeature::SSE41) && round_imm) { // cvttsd2si truncates during operation so rounding (and thus SSE4.1) not required
const bool truncating = rounding_mode == FP::RoundingMode::TowardsZero;
if (round_imm && (truncating || code.HasHostFeature(HostFeature::SSE41))) {
const Xbyak::Xmm src = ctx.reg_alloc.UseScratchXmm(args[0]); const Xbyak::Xmm src = ctx.reg_alloc.UseScratchXmm(args[0]);
const Xbyak::Xmm scratch = ctx.reg_alloc.ScratchXmm();
const Xbyak::Reg64 result = ctx.reg_alloc.ScratchGpr().cvt64(); const Xbyak::Reg64 result = ctx.reg_alloc.ScratchGpr().cvt64();
if constexpr (fsize == 64) { if constexpr (fsize == 64) {
@ -1485,59 +1483,86 @@ static void EmitFPToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
code.mulsd(src, code.MConst(xword, scale_factor)); code.mulsd(src, code.MConst(xword, scale_factor));
} }
if (!truncating) {
code.roundsd(src, src, *round_imm); code.roundsd(src, src, *round_imm);
}
} else { } else {
if (fbits != 0) { if (fbits != 0) {
const u32 scale_factor = static_cast<u32>((fbits + 127) << 23); const u32 scale_factor = static_cast<u32>((fbits + 127) << 23);
code.mulss(src, code.MConst(xword, scale_factor)); code.mulss(src, code.MConst(xword, scale_factor));
} }
if (!truncating) {
code.roundss(src, src, *round_imm); code.roundss(src, src, *round_imm);
}
code.cvtss2sd(src, src); code.cvtss2sd(src, src);
} }
ZeroIfNaN<64>(code, src, scratch);
if constexpr (isize == 64) { if constexpr (isize == 64) {
const Xbyak::Xmm scratch = ctx.reg_alloc.ScratchXmm();
Xbyak::Label saturate_max, end; Xbyak::Label saturate_max, end;
if (unsigned_) { if (!unsigned_) {
code.maxsd(src, code.MConst(xword, f64_min_u64)); ZeroIfNaN<64>(code, src, scratch);
}
code.movsd(scratch, code.MConst(xword, unsigned_ ? f64_max_u64_lim : f64_max_s64_lim));
code.comisd(scratch, src);
code.jna(saturate_max, code.T_NEAR);
if (unsigned_) {
Xbyak::Label below_max;
code.movsd(scratch, code.MConst(xword, f64_max_s64_lim)); code.movsd(scratch, code.MConst(xword, f64_max_s64_lim));
code.comisd(src, scratch); code.comisd(scratch, src);
code.jb(below_max); code.jna(saturate_max, code.T_NEAR);
code.subsd(src, scratch);
code.cvttsd2si(result, src);
code.btc(result, 63);
code.jmp(end);
code.L(below_max);
}
code.cvttsd2si(result, src); // 64 bit gpr code.cvttsd2si(result, src); // 64 bit gpr
code.L(end); code.L(end);
code.SwitchToFarCode(); code.SwitchToFarCode();
code.L(saturate_max); code.L(saturate_max);
code.mov(result, unsigned_ ? 0xFFFF'FFFF'FFFF'FFFF : 0x7FFF'FFFF'FFFF'FFFF); code.mov(result, 0x7FFF'FFFF'FFFF'FFFF);
code.jmp(end, code.T_NEAR); code.jmp(end, code.T_NEAR);
code.SwitchToNearCode(); code.SwitchToNearCode();
} else if constexpr (isize == 32) {
code.minsd(src, code.MConst(xword, unsigned_ ? f64_max_u32 : f64_max_s32));
if (unsigned_) {
code.maxsd(src, code.MConst(xword, f64_min_u32));
code.cvttsd2si(result, src); // 64 bit gpr
} else { } else {
code.cvttsd2si(result.cvt32(), src); Xbyak::Label below_max;
const Xbyak::Reg64 result2 = ctx.reg_alloc.ScratchGpr().cvt64();
code.pxor(xmm0, xmm0);
code.movaps(scratch, src);
code.subsd(scratch, code.MConst(xword, f64_max_s64_lim));
// these both result in zero if src/scratch are NaN
code.maxsd(src, xmm0);
code.maxsd(scratch, xmm0);
code.cvttsd2si(result, src);
code.cvttsd2si(result2, scratch);
code.or_(result, result2);
// when src < 2^63, result2 == 0, and result contains the final result
// when src >= 2^63, result contains 0x800.... and result2 contains the non-MSB bits
// MSB if result2 is 1 when src >= 2^64
code.sar(result2, 63);
code.or_(result, result2);
}
} else if constexpr (isize == 32) {
if (!unsigned_) {
const Xbyak::Xmm scratch = ctx.reg_alloc.ScratchXmm();
ZeroIfNaN<64>(code, src, scratch);
code.minsd(src, code.MConst(xword, f64_max_s32));
// maxsd not required as cvttsd2si results in 0x8000'0000 when out of range
code.cvttsd2si(result.cvt32(), src); // 32 bit gpr
} else {
code.pxor(xmm0, xmm0);
code.maxsd(src, xmm0); // results in a zero if src is NaN
code.minsd(src, code.MConst(xword, f64_max_u32));
code.cvttsd2si(result, src); // 64 bit gpr
} }
} else { } else {
code.minsd(src, code.MConst(xword, unsigned_ ? f64_max_u16 : f64_max_s16)); const Xbyak::Xmm scratch = ctx.reg_alloc.ScratchXmm();
ZeroIfNaN<64>(code, src, scratch);
code.maxsd(src, code.MConst(xword, unsigned_ ? f64_min_u16 : f64_min_s16)); code.maxsd(src, code.MConst(xword, unsigned_ ? f64_min_u16 : f64_min_s16));
code.minsd(src, code.MConst(xword, unsigned_ ? f64_max_u16 : f64_max_s16));
code.cvttsd2si(result, src); // 64 bit gpr code.cvttsd2si(result, src); // 64 bit gpr
} }

View file

@ -194,6 +194,7 @@ std::vector<u16> GenRandomThumbInst(u32 pc, bool is_last_inst, A32::ITState it_s
// Unicorn is incorrect? // Unicorn is incorrect?
"thumb32_MRS_reg", "thumb32_MRS_reg",
"thumb32_MSR_reg",
// Unicorn has incorrect implementation (incorrect rounding and unsets CPSR.T??) // Unicorn has incorrect implementation (incorrect rounding and unsets CPSR.T??)
"vfp_VCVT_to_fixed", "vfp_VCVT_to_fixed",
@ -285,6 +286,7 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit,
const u32 initial_pc = regs[15]; const u32 initial_pc = regs[15];
const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType); const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType);
const u32 code_mem_size = num_words + static_cast<u32>(instructions.size()); const u32 code_mem_size = num_words + static_cast<u32>(instructions.size());
const u32 expected_end_pc = code_mem_size * sizeof(typename TestEnv::InstructionType);
jit_env.code_mem.resize(code_mem_size); jit_env.code_mem.resize(code_mem_size);
uni_env.code_mem.resize(code_mem_size); uni_env.code_mem.resize(code_mem_size);
@ -393,10 +395,18 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit,
uni.SetPC(new_uni_pc); uni.SetPC(new_uni_pc);
} }
if (uni.GetRegisters()[15] > jit.Regs()[15]) {
const u32 final_pc = jit.Regs()[15];
if (final_pc >= initial_pc && final_pc < expected_end_pc) {
fmt::print("Warning: Possible unicorn overrrun, attempt recovery\n");
jit.Step();
}
}
REQUIRE(uni.GetRegisters() == jit.Regs()); REQUIRE(uni.GetRegisters() == jit.Regs());
REQUIRE(uni.GetExtRegs() == jit.ExtRegs()); REQUIRE(uni.GetExtRegs() == jit.ExtRegs());
REQUIRE((uni.GetCpsr() & 0xFFFFFDDF) == (jit.Cpsr() & 0xFFFFFDDF)); REQUIRE((uni.GetCpsr() & 0xFFFFFDDF) == (jit.Cpsr() & 0xFFFFFDDF));
REQUIRE((uni.GetFpscr() & 0xF0000000) == (jit.Fpscr() & 0xF0000000)); REQUIRE((uni.GetFpscr() & 0xF8000000) == (jit.Fpscr() & 0xF8000000));
REQUIRE(uni_env.modified_memory == jit_env.modified_memory); REQUIRE(uni_env.modified_memory == jit_env.modified_memory);
REQUIRE(uni_env.interrupts.empty()); REQUIRE(uni_env.interrupts.empty());
} }

View file

@ -559,3 +559,25 @@ TEST_CASE("arm: Memory access (fastmem)", "[arm][A32]") {
jit.Run(); jit.Run();
REQUIRE(strncmp(backing_memory + 0x100, backing_memory + 0x1F0, 4) == 0); REQUIRE(strncmp(backing_memory + 0x100, backing_memory + 0x1F0, 4) == 0);
} }
TEST_CASE("arm: vmsr, vcmp, vmrs", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xeee10a10, // vmsr fpscr, r0
0xeeb48a4a, // vcmp.f32 s16, s20
0xeef1fa10, // vmrs apsr_nzcv, fpscr
0xe12fff1e, // bx lr
};
jit.ExtRegs()[16] = 0xFF7FFFFF;
jit.ExtRegs()[20] = 0xFF7FFFFF;
jit.Regs()[0] = 0x60000000;
jit.SetFpscr(0x3ee22ac0);
jit.SetCpsr(0x60000000); // User-mode
test_env.ticks_left = 4;
jit.Run();
}

View file

@ -51,7 +51,8 @@ void A32Unicorn<TestEnvironment>::Run() {
return; return;
} }
if (auto cerr_ = uc_emu_start(uc, pc, END_ADDRESS, 0, 1)) { if (auto cerr_ = uc_emu_start(uc, pc, END_ADDRESS, 0, 1)) {
ASSERT_MSG(false, "uc_emu_start failed @ {:08x} (code = {:08x}) with error {} ({})", pc, testenv.MemoryReadCode(pc), cerr_, uc_strerror(cerr_)); fmt::print("uc_emu_start failed @ {:08x} (code = {:08x}) with error {} ({})", pc, testenv.MemoryReadCode(pc), cerr_, uc_strerror(cerr_));
throw "A32Unicorn::Run() failure";
} }
testenv.ticks_left--; testenv.ticks_left--;
if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) { if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) {