Compare commits

..

6 Commits
7.1.3 ... 6.2.1

Author SHA1 Message Date
Victor Zverovich
19bd751020 Update version 2020-05-09 09:13:20 -07:00
Victor Zverovich
572d2f6a7a Update changelog 2020-05-09 09:11:53 -07:00
Victor Zverovich
1425e72d5c Bump version 2020-05-09 09:01:59 -07:00
Victor Zverovich
b149bb202c Update changelog 2020-05-09 08:41:51 -07:00
Victor Zverovich
cfcafcc23c Fix inconsistent type detection (#1662) 2020-05-09 08:38:10 -07:00
Victor Zverovich
3a52c6ae31 Fix ostream support in sprintf (#1631) 2020-05-09 08:33:01 -07:00
85 changed files with 5175 additions and 8197 deletions

View File

@@ -1,44 +0,0 @@
name: windows
on: [push, pull_request]
jobs:
build:
runs-on: ${{matrix.os}}
strategy:
matrix:
# windows-2016 and windows-2019 have MSVC 2017 and 2019 installed
# respectively: https://github.com/actions/virtual-environments.
os: [windows-2016, windows-2019]
platform: [Win32, x64]
build_type: [Debug, Release]
include:
- os: windows-2016
platform: Win32
build_type: Debug
shared: -DBUILD_SHARED_LIBS=ON
exclude:
- os: windows-2016
platform: Win32
steps:
- uses: actions/checkout@v2
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Configure
# Use a bash shell for $GITHUB_WORKSPACE.
shell: bash
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
-A ${{matrix.platform}} $GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config ${{matrix.build_type}}
- name: Test
working-directory: ${{runner.workspace}}/build
run: ctest -C ${{matrix.build_type}}

1
.gitignore vendored
View File

@@ -15,7 +15,6 @@ bin/
/CMakeScripts
/doc/doxyxml
/doc/html
/doc/node_modules
virtualenv
/Testing
/install_manifest.txt

View File

@@ -1,8 +1,10 @@
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.1.0)
# Fallback for using newer policies on CMake <3.12.
if(${CMAKE_VERSION} VERSION_LESS 3.12)
# Use newer policies if available, up to most recent tested version of CMake.
if(${CMAKE_VERSION} VERSION_LESS 3.11)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.11)
endif()
# Determine if fmt is built as a subproject (using add_subdirectory)
@@ -22,23 +24,15 @@ function(join result_var)
set(${result_var} "${result}" PARENT_SCOPE)
endfunction()
include(CMakeParseArguments)
# Sets a cache variable with a docstring joined from multiple arguments:
# set(<variable> <value>... CACHE <type> <docstring>...)
# This allows splitting a long docstring for readability.
function(set_verbose)
# cmake_parse_arguments is broken in CMake 3.4 (cannot parse CACHE) so use
# list instead.
list(GET ARGN 0 var)
list(REMOVE_AT ARGN 0)
list(GET ARGN 0 val)
list(REMOVE_AT ARGN 0)
list(REMOVE_AT ARGN 0)
list(GET ARGN 0 type)
list(REMOVE_AT ARGN 0)
join(doc ${ARGN})
set(${var} ${val} CACHE ${type} ${doc})
cmake_parse_arguments(SET_VERBOSE "" "" "CACHE" ${ARGN})
list(GET SET_VERBOSE_CACHE 0 type)
list(REMOVE_AT SET_VERBOSE_CACHE 0)
join(doc ${SET_VERBOSE_CACHE})
set(${SET_VERBOSE_UNPARSED_ARGUMENTS} CACHE ${type} ${doc})
endfunction()
# Set the default CMAKE_BUILD_TYPE to Release.
@@ -50,12 +44,6 @@ if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
endif ()
project(FMT CXX)
include(GNUInstallDirs)
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
"Installation directory for include files, a relative path that "
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.")
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
OFF)
@@ -66,7 +54,8 @@ option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT})
option(FMT_TEST "Generate the test target." ${MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
project(FMT CXX)
# Get version from core.h
file(READ include/fmt/core.h core_h)
@@ -92,7 +81,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
include(cxx14)
include(CheckCXXCompilerFlag)
include(JoinPaths)
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
if (${index} GREATER -1)
@@ -107,11 +95,11 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
-Wold-style-cast -Wundef
-Wredundant-decls -Wwrite-strings -Wpointer-arith
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
-Wcast-align
-Wcast-align -Wnon-virtual-dtor
-Wctor-dtor-privacy -Wdisabled-optimization
-Winvalid-pch -Woverloaded-virtual
-Wconversion -Wswitch-enum -Wundef
-Wno-ctor-dtor-privacy -Wno-format-nonliteral)
-Wconversion -Wswitch-enum
-Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow)
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept
-Wno-dangling-else -Wno-unused-local-typedefs)
@@ -129,8 +117,8 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
endif ()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef
-Wdeprecated -Wweak-vtables)
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion
-Wno-sign-conversion -Wdeprecated -Wweak-vtables)
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
if (HAS_NULLPTR_WARNING)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
@@ -185,11 +173,7 @@ endfunction()
# Define the fmt library, its includes and the needed defines.
add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h
locale.h os.h ostream.h posix.h printf.h ranges.h)
if (FMT_OS)
set(FMT_SOURCES src/format.cc src/os.cc)
else()
set(FMT_SOURCES src/format.cc)
endif ()
set(FMT_SOURCES src/format.cc src/os.cc)
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
add_library(fmt::fmt ALIAS fmt)
@@ -198,13 +182,6 @@ if (HAVE_STRTOD_L)
target_compile_definitions(fmt PUBLIC FMT_LOCALE)
endif ()
if (MINGW)
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
if (${FMT_HAS_MBIG_OBJ})
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
endif()
endif ()
if (FMT_WERROR)
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
endif ()
@@ -216,7 +193,7 @@ target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
target_include_directories(fmt PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
$<INSTALL_INTERFACE:include>)
set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
@@ -232,8 +209,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
endif ()
if (BUILD_SHARED_LIBS)
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
NOT EMSCRIPTEN)
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
# Fix rpmlint warning:
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
target_link_libraries(fmt -Wl,--as-needed)
@@ -252,38 +228,42 @@ target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
target_include_directories(fmt-header-only INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
$<INSTALL_INTERFACE:include>)
# Install targets.
if (FMT_INSTALL)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
"Installation directory for cmake files, a relative path that "
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute "
"path.")
"Installation directory for cmake files, relative to "
"${CMAKE_INSTALL_PREFIX}.")
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
set(targets_export_name fmt-targets)
set (INSTALL_TARGETS fmt)
if (TARGET fmt-header-only)
set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only)
endif ()
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
"Installation directory for libraries, a relative path that "
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
"Installation directory for libraries, relative to "
"${CMAKE_INSTALL_PREFIX}.")
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING
"Installation directory for include files, relative to "
"${CMAKE_INSTALL_PREFIX}.")
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
"Installation directory for pkgconfig (.pc) files, a relative "
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
"absolute path.")
"Installation directory for pkgconfig (.pc) files, relative to "
"${CMAKE_INSTALL_PREFIX}.")
# Generate the version, config and target files into the build directory.
write_basic_package_version_file(
${version_config}
VERSION ${FMT_VERSION}
COMPATIBILITY AnyNewerVersion)
join_paths(libdir_for_pc_file "\${exec_prefix}" "${FMT_LIB_DIR}")
join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}")
configure_file(
"${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in"
"${pkgconfig}"
@@ -292,8 +272,6 @@ if (FMT_INSTALL)
${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in
${project_config}
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
set(INSTALL_TARGETS fmt fmt-header-only)
# Use a namespace because CMake provides better diagnostics for namespaced
# imported targets.
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
@@ -314,7 +292,7 @@ if (FMT_INSTALL)
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
install(FILES ${FMT_HEADERS} DESTINATION ${FMT_INC_DIR})
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
endif ()
@@ -330,13 +308,6 @@ endif ()
# Control fuzzing independent of the unit tests.
if (FMT_FUZZ)
add_subdirectory(test/fuzzing)
# The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing
# mode and make fuzzing practically possible. It is similar to
# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to
# avoid interfering with fuzzing of projects that use {fmt}.
# See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode.
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
endif ()
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)

View File

@@ -14,7 +14,4 @@ exceptions:
* snake_case should be used instead of UpperCamelCase for function and type
names
All documentation must adhere to the [Google Developer Documentation Style
Guide](https://developers.google.com/style).
Thanks for contributing!

View File

@@ -1,764 +1,3 @@
7.1.3 - 2020-11-24
------------------
* Fixed handling of buffer boundaries in ``format_to_n``
(`#1996 <https://github.com/fmtlib/fmt/issues/1996>`_,
`#2029 <https://github.com/fmtlib/fmt/issues/2029>`_).
* Fixed linkage errors when linking with a shared library
(`#2011 <https://github.com/fmtlib/fmt/issues/2011>`_).
* Reintroduced ostream support to range formatters
(`#2014 <https://github.com/fmtlib/fmt/issues/2014>`_).
* Worked around an issue with mixing std versions in gcc
(`#2017 <https://github.com/fmtlib/fmt/issues/2017>`_).
7.1.2 - 2020-11-04
------------------
* Fixed floating point formatting with large precision
(`#1976 <https://github.com/fmtlib/fmt/issues/1976>`_).
7.1.1 - 2020-11-01
------------------
* Fixed ABI compatibility with 7.0.x
(`#1961 <https://github.com/fmtlib/fmt/issues/1961>`_).
* Added the ``FMT_ARM_ABI_COMPATIBILITY`` macro to work around ABI
incompatibility between GCC and Clang on ARM
(`#1919 <https://github.com/fmtlib/fmt/issues/1919>`_).
* Worked around a SFINAE bug in GCC 8
(`#1957 <https://github.com/fmtlib/fmt/issues/1957>`_).
* Fixed linkage errors when building with GCC's LTO
(`#1955 <https://github.com/fmtlib/fmt/issues/1955>`_).
* Fixed a compilation error when building without ``__builtin_clz`` or equivalent
(`#1968 <https://github.com/fmtlib/fmt/pull/1968>`_).
Thanks `@tohammer (Tobias Hammer) <https://github.com/tohammer>`_.
* Fixed a sign conversion warning
(`#1964 <https://github.com/fmtlib/fmt/pull/1964>`_).
Thanks `@OptoCloud <https://github.com/OptoCloud>`_.
7.1.0 - 2020-10-25
------------------
* Switched from `Grisu3
<https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf>`_
to `Dragonbox <https://github.com/jk-jeon/dragonbox>`_ for the default
floating-point formatting which gives the shortest decimal representation
with round-trip guarantee and correct rounding
(`#1882 <https://github.com/fmtlib/fmt/pull/1882>`_,
`#1887 <https://github.com/fmtlib/fmt/pull/1887>`_,
`#1894 <https://github.com/fmtlib/fmt/pull/1894>`_). This makes {fmt} up to
20-30x faster than common implementations of ``std::ostringstream`` and
``sprintf`` on `dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_
and faster than double-conversion and Ryū:
.. image:: https://user-images.githubusercontent.com/576385/
95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
It is possible to get even better performance at the cost of larger binary
size by compiling with the ``FMT_USE_FULL_CACHE_DRAGONBOX`` macro set to 1.
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
* Added an experimental unsynchronized file output API which, together with
`format string compilation <https://fmt.dev/latest/api.html#compile-api>`_,
can give `5-9 times speed up compared to fprintf
<https://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_
on common platforms (`godbolt <https://godbolt.org/z/nsTcG8>`__):
.. code:: c++
#include <fmt/os.h>
int main() {
auto f = fmt::output_file("guide");
f.print("The answer is {}.", 42);
}
* Added a formatter for ``std::chrono::time_point<system_clock>``
(`#1819 <https://github.com/fmtlib/fmt/issues/1819>`_,
`#1837 <https://github.com/fmtlib/fmt/pull/1837>`_). For example
(`godbolt <https://godbolt.org/z/c4M6fh>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
auto now = std::chrono::system_clock::now();
fmt::print("The time is {:%H:%M:%S}.\n", now);
}
Thanks `@adamburgess (Adam Burgess) <https://github.com/adamburgess>`_.
* Added support for ranges with non-const ``begin``/``end`` to ``fmt::join``
(`#1784 <https://github.com/fmtlib/fmt/issues/1784>`_,
`#1786 <https://github.com/fmtlib/fmt/pull/1786>`_). For example
(`godbolt <https://godbolt.org/z/jP63Tv>`__):
.. code:: c++
#include <fmt/ranges.h>
#include <range/v3/view/filter.hpp>
int main() {
using std::literals::string_literals::operator""s;
auto strs = std::array{"a"s, "bb"s, "ccc"s};
auto range = strs | ranges::views::filter(
[] (const std::string &x) { return x.size() != 2; }
);
fmt::print("{}\n", fmt::join(range, ""));
}
prints "accc".
Thanks `@tonyelewis (Tony E Lewis) <https://github.com/tonyelewis>`_.
* Added a ``memory_buffer::append`` overload that takes a range
(`#1806 <https://github.com/fmtlib/fmt/pull/1806>`_).
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Improved handling of single code units in ``FMT_COMPILE``. For example:
.. code:: c++
#include <fmt/compile.h>
char* f(char* buf) {
return fmt::format_to(buf, FMT_COMPILE("x{}"), 42);
}
compiles to just (`godbolt <https://godbolt.org/z/5vncz3>`__):
.. code:: asm
_Z1fPc:
movb $120, (%rdi)
xorl %edx, %edx
cmpl $42, _ZN3fmt2v76detail10basic_dataIvE23zero_or_powers_of_10_32E+8(%rip)
movl $3, %eax
seta %dl
subl %edx, %eax
movzwl _ZN3fmt2v76detail10basic_dataIvE6digitsE+84(%rip), %edx
cltq
addq %rdi, %rax
movw %dx, -2(%rax)
ret
Here a single ``mov`` instruction writes ``'x'`` (``$120``) to the output
buffer.
* Added dynamic width support to format string compilation
(`#1809 <https://github.com/fmtlib/fmt/issues/1809>`_).
* Improved error reporting for unformattable types: now you'll get the type name
directly in the error message instead of the note:
.. code:: c++
#include <fmt/core.h>
struct how_about_no {};
int main() {
fmt::print("{}", how_about_no());
}
Error (`godbolt <https://godbolt.org/z/GoxM4e>`__):
``fmt/core.h:1438:3: error: static_assert failed due to requirement
'fmt::v7::formattable<how_about_no>()' "Cannot format an argument.
To make type T formattable provide a formatter<T> specialization:
https://fmt.dev/latest/api.html#udt"
...``
* Added the `make_args_checked <https://fmt.dev/7.1.0/api.html#argument-lists>`_
function template that allows you to write formatting functions with
compile-time format string checks and avoid binary code bloat
(`godbolt <https://godbolt.org/z/PEf9qr>`__):
.. code:: c++
void vlog(const char* file, int line, fmt::string_view format,
fmt::format_args args) {
fmt::print("{}: {}: ", file, line);
fmt::vprint(format, args);
}
template <typename S, typename... Args>
void log(const char* file, int line, const S& format, Args&&... args) {
vlog(file, line, format,
fmt::make_args_checked<Args...>(format, args...));
}
#define MY_LOG(format, ...) \
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
MY_LOG("invalid squishiness: {}", 42);
* Replaced ``snprintf`` fallback with a faster internal IEEE 754 ``float`` and
``double`` formatter for arbitrary precision. For example
(`godbolt <https://godbolt.org/z/dPhWvj>`__):
.. code:: c++
#include <fmt/core.h>
int main() {
fmt::print("{:.500}\n", 4.9406564584124654E-324);
}
prints
``4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208324319360923829e-324``.
* Made ``format_to_n`` and ``formatted_size`` part of the `core API
<https://fmt.dev/latest/api.html#core-api>`__
(`godbolt <https://godbolt.org/z/sPjY1K>`__):
.. code:: c++
#include <fmt/core.h>
int main() {
char buffer[10];
auto result = fmt::format_to_n(buffer, sizeof(buffer), "{}", 42);
}
* Added ``fmt::format_to_n`` overload with format string compilation
(`#1764 <https://github.com/fmtlib/fmt/issues/1764>`_,
`#1767 <https://github.com/fmtlib/fmt/pull/1767>`_,
`#1869 <https://github.com/fmtlib/fmt/pull/1869>`_). For example
(`godbolt <https://godbolt.org/z/93h86q>`__):
.. code:: c++
#include <fmt/compile.h>
int main() {
char buffer[8];
fmt::format_to_n(buffer, sizeof(buffer), FMT_COMPILE("{}"), 42);
}
Thanks `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_,
`@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
* Added ``fmt::format_to`` overload that take ``text_style``
(`#1593 <https://github.com/fmtlib/fmt/issues/1593>`_,
`#1842 <https://github.com/fmtlib/fmt/issues/1842>`_,
`#1843 <https://github.com/fmtlib/fmt/pull/1843>`_). For example
(`godbolt <https://godbolt.org/z/91153r>`__):
.. code:: c++
#include <fmt/color.h>
int main() {
std::string out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red),
"The answer is {}.", 42);
}
Thanks `@Naios (Denis Blank) <https://github.com/Naios>`_.
* Made the ``#`` specifier emit trailing zeros in addition to the decimal point
(`#1797 <https://github.com/fmtlib/fmt/issues/1797>`_). For example
(`godbolt <https://godbolt.org/z/bhdcW9>`__):
.. code:: c++
#include <fmt/core.h>
int main() {
fmt::print("{:#.2g}", 0.5);
}
prints ``0.50``.
* Changed the default floating point format to not include ``.0`` for
consistency with ``std::format`` and ``std::to_chars``
(`#1893 <https://github.com/fmtlib/fmt/issues/1893>`_,
`#1943 <https://github.com/fmtlib/fmt/issues/1943>`_). It is possible to get
the decimal point and trailing zero with the ``#`` specifier.
* Fixed an issue with floating-point formatting that could result in addition of
a non-significant trailing zero in rare cases e.g. ``1.00e-34`` instead of
``1.0e-34`` (`#1873 <https://github.com/fmtlib/fmt/issues/1873>`_,
`#1917 <https://github.com/fmtlib/fmt/issues/1917>`_).
* Made ``fmt::to_string`` fallback on ``ostream`` insertion operator if
the ``formatter`` specialization is not provided
(`#1815 <https://github.com/fmtlib/fmt/issues/1815>`_,
`#1829 <https://github.com/fmtlib/fmt/pull/1829>`_).
Thanks `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
* Added support for the append mode to the experimental file API and
improved ``fcntl.h`` detection.
(`#1847 <https://github.com/fmtlib/fmt/pull/1847>`_,
`#1848 <https://github.com/fmtlib/fmt/pull/1848>`_).
Thanks `@t-wiser <https://github.com/t-wiser>`_.
* Fixed handling of types that have both an implicit conversion operator and
an overloaded ``ostream`` insertion operator
(`#1766 <https://github.com/fmtlib/fmt/issues/1766>`_).
* Fixed a slicing issue in an internal iterator type
(`#1822 <https://github.com/fmtlib/fmt/pull/1822>`_).
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Fixed an issue in locale-specific integer formatting
(`#1927 <https://github.com/fmtlib/fmt/issues/1927>`_).
* Fixed handling of exotic code unit types
(`#1870 <https://github.com/fmtlib/fmt/issues/1870>`_,
`#1932 <https://github.com/fmtlib/fmt/issues/1932>`_).
* Improved ``FMT_ALWAYS_INLINE``
(`#1878 <https://github.com/fmtlib/fmt/pull/1878>`_).
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
* Removed dependency on ``windows.h``
(`#1900 <https://github.com/fmtlib/fmt/pull/1900>`_).
Thanks `@bernd5 (Bernd Baumanns) <https://github.com/bernd5>`_.
* Optimized counting of decimal digits on MSVC
(`#1890 <https://github.com/fmtlib/fmt/pull/1890>`_).
Thanks `@mwinterb <https://github.com/mwinterb>`_.
* Improved documentation
(`#1772 <https://github.com/fmtlib/fmt/issues/1772>`_,
`#1775 <https://github.com/fmtlib/fmt/pull/1775>`_,
`#1792 <https://github.com/fmtlib/fmt/pull/1792>`_,
`#1838 <https://github.com/fmtlib/fmt/pull/1838>`_,
`#1888 <https://github.com/fmtlib/fmt/pull/1888>`_,
`#1918 <https://github.com/fmtlib/fmt/pull/1918>`_,
`#1939 <https://github.com/fmtlib/fmt/pull/1939>`_).
Thanks `@leolchat (Léonard Gérard) <https://github.com/leolchat>`_,
`@pepsiman (Malcolm Parsons) <https://github.com/pepsiman>`_,
`@Klaim (Joël Lamotte) <https://github.com/Klaim>`_,
`@ravijanjam (Ravi J) <https://github.com/ravijanjam>`_,
`@francesco-st <https://github.com/francesco-st>`_,
`@udnaan (Adnan) <https://github.com/udnaan>`_.
* Added the ``FMT_REDUCE_INT_INSTANTIATIONS`` CMake option that reduces the
binary code size at the cost of some integer formatting performance. This can
be useful for extremely memory-constrained embedded systems
(`#1778 <https://github.com/fmtlib/fmt/issues/1778>`_,
`#1781 <https://github.com/fmtlib/fmt/pull/1781>`_).
Thanks `@kammce (Khalil Estell) <https://github.com/kammce>`_.
* Added the ``FMT_USE_INLINE_NAMESPACES`` macro to control usage of inline
namespaces (`#1945 <https://github.com/fmtlib/fmt/pull/1945>`_).
Thanks `@darklukee <https://github.com/darklukee>`_.
* Improved build configuration
(`#1760 <https://github.com/fmtlib/fmt/pull/1760>`_,
`#1770 <https://github.com/fmtlib/fmt/pull/1770>`_,
`#1779 <https://github.com/fmtlib/fmt/issues/1779>`_,
`#1783 <https://github.com/fmtlib/fmt/pull/1783>`_,
`#1823 <https://github.com/fmtlib/fmt/pull/1823>`_).
Thanks `@dvetutnev (Dmitriy Vetutnev) <https://github.com/dvetutnev>`_,
`@xvitaly (Vitaly Zaitsev) <https://github.com/xvitaly>`_,
`@tambry (Raul Tambre) <https://github.com/tambry>`_,
`@medithe <https://github.com/medithe>`_,
`@martinwuehrer (Martin Wührer) <https://github.com/martinwuehrer>`_.
* Fixed various warnings and compilation issues
(`#1790 <https://github.com/fmtlib/fmt/pull/1790>`_,
`#1802 <https://github.com/fmtlib/fmt/pull/1802>`_,
`#1808 <https://github.com/fmtlib/fmt/pull/1808>`_,
`#1810 <https://github.com/fmtlib/fmt/issues/1810>`_,
`#1811 <https://github.com/fmtlib/fmt/issues/1811>`_,
`#1812 <https://github.com/fmtlib/fmt/pull/1812>`_,
`#1814 <https://github.com/fmtlib/fmt/pull/1814>`_,
`#1816 <https://github.com/fmtlib/fmt/pull/1816>`_,
`#1817 <https://github.com/fmtlib/fmt/pull/1817>`_,
`#1818 <https://github.com/fmtlib/fmt/pull/1818>`_,
`#1825 <https://github.com/fmtlib/fmt/issues/1825>`_,
`#1836 <https://github.com/fmtlib/fmt/pull/1836>`_,
`#1855 <https://github.com/fmtlib/fmt/pull/1855>`_,
`#1856 <https://github.com/fmtlib/fmt/pull/1856>`_,
`#1860 <https://github.com/fmtlib/fmt/pull/1860>`_,
`#1877 <https://github.com/fmtlib/fmt/pull/1877>`_,
`#1879 <https://github.com/fmtlib/fmt/pull/1879>`_,
`#1880 <https://github.com/fmtlib/fmt/pull/1880>`_,
`#1896 <https://github.com/fmtlib/fmt/issues/1896>`_,
`#1897 <https://github.com/fmtlib/fmt/pull/1897>`_,
`#1898 <https://github.com/fmtlib/fmt/pull/1898>`_,
`#1904 <https://github.com/fmtlib/fmt/issues/1904>`_,
`#1908 <https://github.com/fmtlib/fmt/pull/1908>`_,
`#1911 <https://github.com/fmtlib/fmt/issues/1911>`_,
`#1912 <https://github.com/fmtlib/fmt/issues/1912>`_,
`#1928 <https://github.com/fmtlib/fmt/issues/1928>`_,
`#1929 <https://github.com/fmtlib/fmt/pull/1929>`_,
`#1935 <https://github.com/fmtlib/fmt/issues/1935>`_
`#1937 <https://github.com/fmtlib/fmt/pull/1937>`_,
`#1942 <https://github.com/fmtlib/fmt/pull/1942>`_,
`#1949 <https://github.com/fmtlib/fmt/issues/1949>`_).
Thanks `@TheQwertiest <https://github.com/TheQwertiest>`_,
`@medithe <https://github.com/medithe>`_,
`@martinwuehrer (Martin Wührer) <https://github.com/martinwuehrer>`_,
`@n16h7hunt3r <https://github.com/n16h7hunt3r>`_,
`@Othereum (Seokjin Lee) <https://github.com/Othereum>`_,
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
`@AlexanderLanin (Alexander Lanin) <https://github.com/AlexanderLanin>`_,
`@gcerretani (Giovanni Cerretani) <https://github.com/gcerretani>`_,
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
`@noizefloor (Jan Schwers) <https://github.com/noizefloor>`_,
`@akohlmey (Axel Kohlmeyer) <https://github.com/akohlmey>`_,
`@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_,
`@rimathia <https://github.com/rimathia>`_,
`@rglarix (Riccardo Ghetta (larix)) <https://github.com/rglarix>`_,
`@moiwi <https://github.com/moiwi>`_,
`@heckad (Kazantcev Andrey) <https://github.com/heckad>`_,
`@MarcDirven <https://github.com/MarcDirven>`_.
`@BartSiwek (Bart Siwek) <https://github.com/BartSiwek>`_,
`@darklukee <https://github.com/darklukee>`_.
7.0.3 - 2020-08-06
------------------
* Worked around broken ``numeric_limits`` for 128-bit integers
(`#1787 <https://github.com/fmtlib/fmt/issues/1787>`_).
* Added error reporting on missing named arguments
(`#1796 <https://github.com/fmtlib/fmt/issues/1796>`_).
* Stopped using 128-bit integers with clang-cl
(`#1800 <https://github.com/fmtlib/fmt/pull/1800>`_).
Thanks `@Kingcom <https://github.com/Kingcom>`_.
* Fixed issues in locale-specific integer formatting
(`#1782 <https://github.com/fmtlib/fmt/issues/1782>`_,
`#1801 <https://github.com/fmtlib/fmt/issues/1801>`_).
7.0.2 - 2020-07-29
------------------
* Worked around broken ``numeric_limits`` for 128-bit integers
(`#1725 <https://github.com/fmtlib/fmt/issues/1725>`_).
* Fixed compatibility with CMake 3.4
(`#1779 <https://github.com/fmtlib/fmt/issues/1779>`_).
* Fixed handling of digit separators in locale-specific formatting
(`#1782 <https://github.com/fmtlib/fmt/issues/1782>`_).
7.0.1 - 2020-07-07
------------------
* Updated the inline version namespace name.
* Worked around a gcc bug in mangling of alias templates
(`#1753 <https://github.com/fmtlib/fmt/issues/1753>`_).
* Fixed a linkage error on Windows
(`#1757 <https://github.com/fmtlib/fmt/issues/1757>`_).
Thanks `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_.
* Fixed minor issues with the documentation.
7.0.0 - 2020-07-05
------------------
* Reduced the library size. For example, on macOS a stripped test binary
statically linked with {fmt} `shrank from ~368k to less than 100k
<http://www.zverovich.net/2020/05/21/reducing-library-size.html>`_.
* Added a simpler and more efficient `format string compilation API
<https://fmt.dev/7.0.0/api.html#compile-api>`_:
.. code:: c++
#include <fmt/compile.h>
// Converts 42 into std::string using the most efficient method and no
// runtime format string processing.
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
The old ``fmt::compile`` API is now deprecated.
* Optimized integer formatting: ``format_to`` with format string compilation
and a stack-allocated buffer is now `faster than to_chars on both
libc++ and libstdc++
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
* Optimized handling of small format strings. For example,
.. code:: c++
fmt::format("Result: {}: ({},{},{},{})", str1, str2, str3, str4, str5)
is now ~40% faster (`#1685 <https://github.com/fmtlib/fmt/issues/1685>`_).
* Applied extern templates to improve compile times when using the core API
and ``fmt/format.h`` (`#1452 <https://github.com/fmtlib/fmt/issues/1452>`_).
For example, on macOS with clang the compile time of a test translation unit
dropped from 2.3s to 0.3s with ``-O2`` and from 0.6s to 0.3s with the default
settings (``-O0``).
Before (``-O2``)::
% time c++ -c test.cc -I include -std=c++17 -O2
c++ -c test.cc -I include -std=c++17 -O2 2.22s user 0.08s system 99% cpu 2.311 total
After (``-O2``)::
% time c++ -c test.cc -I include -std=c++17 -O2
c++ -c test.cc -I include -std=c++17 -O2 0.26s user 0.04s system 98% cpu 0.303 total
Before (default)::
% time c++ -c test.cc -I include -std=c++17
c++ -c test.cc -I include -std=c++17 0.53s user 0.06s system 98% cpu 0.601 total
After (default)::
% time c++ -c test.cc -I include -std=c++17
c++ -c test.cc -I include -std=c++17 0.24s user 0.06s system 98% cpu 0.301 total
It is still recommended to use ``fmt/core.h`` instead of ``fmt/format.h`` but
the compile time difference is now smaller. Thanks
`@alex3d <https://github.com/alex3d>`_ for the suggestion.
* Named arguments are now stored on stack (no dynamic memory allocations) and
the compiled code is more compact and efficient. For example
.. code:: c++
#include <fmt/core.h>
int main() {
fmt::print("The answer is {answer}\n", fmt::arg("answer", 42));
}
compiles to just (`godbolt <https://godbolt.org/z/NcfEp_>`__)
.. code:: asm
.LC0:
.string "answer"
.LC1:
.string "The answer is {answer}\n"
main:
sub rsp, 56
mov edi, OFFSET FLAT:.LC1
mov esi, 23
movabs rdx, 4611686018427387905
lea rax, [rsp+32]
lea rcx, [rsp+16]
mov QWORD PTR [rsp+8], 1
mov QWORD PTR [rsp], rax
mov DWORD PTR [rsp+16], 42
mov QWORD PTR [rsp+32], OFFSET FLAT:.LC0
mov DWORD PTR [rsp+40], 0
call fmt::v6::vprint(fmt::v6::basic_string_view<char>,
fmt::v6::format_args)
xor eax, eax
add rsp, 56
ret
.L.str.1:
.asciz "answer"
* Implemented compile-time checks for dynamic width and precision
(`#1614 <https://github.com/fmtlib/fmt/issues/1614>`_):
.. code:: c++
#include <fmt/format.h>
int main() {
fmt::print(FMT_STRING("{0:{1}}"), 42);
}
now gives a compilation error because argument 1 doesn't exist::
In file included from test.cc:1:
include/fmt/format.h:2726:27: error: constexpr variable 'invalid_format' must be
initialized by a constant expression
FMT_CONSTEXPR_DECL bool invalid_format =
^
...
include/fmt/core.h:569:26: note: in call to
'&checker(s, {}).context_->on_error(&"argument not found"[0])'
if (id >= num_args_) on_error("argument not found");
^
* Added sentinel support to ``fmt::join``
(`#1689 <https://github.com/fmtlib/fmt/pull/1689>`_)
.. code:: c++
struct zstring_sentinel {};
bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }
struct zstring {
const char* p;
const char* begin() const { return p; }
zstring_sentinel end() const { return {}; }
};
auto s = fmt::format("{}", fmt::join(zstring{"hello"}, "_"));
// s == "h_e_l_l_o"
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Added support for named arguments, ``clear`` and ``reserve`` to
``dynamic_format_arg_store``
(`#1655 <https://github.com/fmtlib/fmt/issues/1655>`_,
`#1663 <https://github.com/fmtlib/fmt/pull/1663>`_,
`#1674 <https://github.com/fmtlib/fmt/pull/1674>`_,
`#1677 <https://github.com/fmtlib/fmt/pull/1677>`_).
Thanks `@vsolontsov-ll (Vladimir Solontsov)
<https://github.com/vsolontsov-ll>`_.
* Added support for the ``'c'`` format specifier to integral types for
compatibility with ``std::format``
(`#1652 <https://github.com/fmtlib/fmt/issues/1652>`_).
* Replaced the ``'n'`` format specifier with ``'L'`` for compatibility with
``std::format`` (`#1624 <https://github.com/fmtlib/fmt/issues/1624>`_).
The ``'n'`` specifier can be enabled via the ``FMT_DEPRECATED_N_SPECIFIER``
macro.
* The ``'='`` format specifier is now disabled by default for compatibility with
``std::format``. It can be enabled via the ``FMT_DEPRECATED_NUMERIC_ALIGN``
macro.
* Removed the following deprecated APIs:
* ``FMT_STRING_ALIAS`` and ``fmt`` macros - replaced by ``FMT_STRING``
* ``fmt::basic_string_view::char_type`` - replaced by
``fmt::basic_string_view::value_type``
* ``convert_to_int``
* ``format_arg_store::types``
* ``*parse_context`` - replaced by ``*format_parse_context``
* ``FMT_DEPRECATED_INCLUDE_OS``
* ``FMT_DEPRECATED_PERCENT`` - incompatible with ``std::format``
* ``*writer`` - replaced by compiled format API
* Renamed the ``internal`` namespace to ``detail``
(`#1538 <https://github.com/fmtlib/fmt/issues/1538>`_). The former is still
provided as an alias if the ``FMT_USE_INTERNAL`` macro is defined.
* Improved compatibility between ``fmt::printf`` with the standard specs
(`#1595 <https://github.com/fmtlib/fmt/issues/1595>`_,
`#1682 <https://github.com/fmtlib/fmt/pull/1682>`_,
`#1683 <https://github.com/fmtlib/fmt/pull/1683>`_,
`#1687 <https://github.com/fmtlib/fmt/pull/1687>`_,
`#1699 <https://github.com/fmtlib/fmt/pull/1699>`_).
Thanks `@rimathia <https://github.com/rimathia>`_.
* Fixed handling of ``operator<<`` overloads that use ``copyfmt``
(`#1666 <https://github.com/fmtlib/fmt/issues/1666>`_).
* Added the ``FMT_OS`` CMake option to control inclusion of OS-specific APIs
in the fmt target. This can be useful for embedded platforms
(`#1654 <https://github.com/fmtlib/fmt/issues/1654>`_,
`#1656 <https://github.com/fmtlib/fmt/pull/1656>`_).
Thanks `@kwesolowski (Krzysztof Wesolowski)
<https://github.com/kwesolowski>`_.
* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ``
macro to prevent interferring with fuzzing of projects using {fmt}
(`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_).
Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_.
* Fixed compatibility with emscripten
(`#1636 <https://github.com/fmtlib/fmt/issues/1636>`_,
`#1637 <https://github.com/fmtlib/fmt/pull/1637>`_).
Thanks `@ArthurSonzogni (Arthur Sonzogni)
<https://github.com/ArthurSonzogni>`_.
* Improved documentation
(`#704 <https://github.com/fmtlib/fmt/issues/704>`_,
`#1643 <https://github.com/fmtlib/fmt/pull/1643>`_,
`#1660 <https://github.com/fmtlib/fmt/pull/1660>`_,
`#1681 <https://github.com/fmtlib/fmt/pull/1681>`_,
`#1691 <https://github.com/fmtlib/fmt/pull/1691>`_,
`#1706 <https://github.com/fmtlib/fmt/pull/1706>`_,
`#1714 <https://github.com/fmtlib/fmt/pull/1714>`_,
`#1721 <https://github.com/fmtlib/fmt/pull/1721>`_,
`#1739 <https://github.com/fmtlib/fmt/pull/1739>`_,
`#1740 <https://github.com/fmtlib/fmt/pull/1740>`_,
`#1741 <https://github.com/fmtlib/fmt/pull/1741>`_,
`#1751 <https://github.com/fmtlib/fmt/pull/1751>`_).
Thanks `@senior7515 (Alexander Gallego) <https://github.com/senior7515>`_,
`@lsr0 (Lindsay Roberts) <https://github.com/lsr0>`_,
`@puetzk (Kevin Puetz) <https://github.com/puetzk>`_,
`@fpelliccioni (Fernando Pelliccioni) <https://github.com/fpelliccioni>`_,
Alexey Kuzmenko, `@jelly (jelle van der Waa) <https://github.com/jelly>`_,
`@claremacrae (Clare Macrae) <https://github.com/claremacrae>`_,
`@jiapengwen (文佳鹏) <https://github.com/jiapengwen>`_,
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
`@alexey-milovidov <https://github.com/alexey-milovidov>`_.
* Implemented various build configuration fixes and improvements
(`#1603 <https://github.com/fmtlib/fmt/pull/1603>`_,
`#1657 <https://github.com/fmtlib/fmt/pull/1657>`_,
`#1702 <https://github.com/fmtlib/fmt/pull/1702>`_,
`#1728 <https://github.com/fmtlib/fmt/pull/1728>`_).
Thanks `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_,
`@jtojnar (Jan Tojnar) <https://github.com/jtojnar>`_,
`@orivej (Orivej Desh) <https://github.com/orivej>`_,
`@flagarde <https://github.com/flagarde>`_.
* Fixed various warnings and compilation issues
(`#1616 <https://github.com/fmtlib/fmt/pull/1616>`_,
`#1620 <https://github.com/fmtlib/fmt/issues/1620>`_,
`#1622 <https://github.com/fmtlib/fmt/issues/1622>`_,
`#1625 <https://github.com/fmtlib/fmt/issues/1625>`_,
`#1627 <https://github.com/fmtlib/fmt/pull/1627>`_,
`#1628 <https://github.com/fmtlib/fmt/issues/1628>`_,
`#1629 <https://github.com/fmtlib/fmt/pull/1629>`_,
`#1631 <https://github.com/fmtlib/fmt/issues/1631>`_,
`#1633 <https://github.com/fmtlib/fmt/pull/1633>`_,
`#1649 <https://github.com/fmtlib/fmt/pull/1649>`_,
`#1658 <https://github.com/fmtlib/fmt/issues/1658>`_,
`#1661 <https://github.com/fmtlib/fmt/pull/1661>`_,
`#1667 <https://github.com/fmtlib/fmt/pull/1667>`_,
`#1668 <https://github.com/fmtlib/fmt/issues/1668>`_,
`#1669 <https://github.com/fmtlib/fmt/pull/1669>`_,
`#1692 <https://github.com/fmtlib/fmt/issues/1692>`_,
`#1696 <https://github.com/fmtlib/fmt/pull/1696>`_,
`#1697 <https://github.com/fmtlib/fmt/pull/1697>`_,
`#1707 <https://github.com/fmtlib/fmt/issues/1707>`_,
`#1712 <https://github.com/fmtlib/fmt/pull/1712>`_,
`#1716 <https://github.com/fmtlib/fmt/pull/1716>`_,
`#1722 <https://github.com/fmtlib/fmt/pull/1722>`_,
`#1724 <https://github.com/fmtlib/fmt/issues/1724>`_,
`#1729 <https://github.com/fmtlib/fmt/pull/1729>`_,
`#1738 <https://github.com/fmtlib/fmt/pull/1738>`_,
`#1742 <https://github.com/fmtlib/fmt/issues/1742>`_,
`#1743 <https://github.com/fmtlib/fmt/issues/1743>`_,
`#1744 <https://github.com/fmtlib/fmt/pull/1744>`_,
`#1747 <https://github.com/fmtlib/fmt/issues/1747>`_,
`#1750 <https://github.com/fmtlib/fmt/pull/1750>`_).
Thanks `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
`@gabime (Gabi Melman) <https://github.com/gabime>`_,
`@johnor (Johan) <https://github.com/johnor>`_,
`@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_,
`@invexed (James Beach) <https://github.com/invexed>`_,
`@peterbell10 <https://github.com/peterbell10>`_,
`@daixtrose (Markus Werle) <https://github.com/daixtrose>`_,
`@petrutlucian94 (Lucian Petrut) <https://github.com/petrutlucian94>`_,
`@Neargye (Daniil Goncharov) <https://github.com/Neargye>`_,
`@ambitslix (Attila M. Szilagyi) <https://github.com/ambitslix>`_,
`@gabime (Gabi Melman) <https://github.com/gabime>`_,
`@erthink (Leonid Yuriev) <https://github.com/erthink>`_,
`@tohammer (Tobias Hammer) <https://github.com/tohammer>`_,
`@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_.
6.2.1 - 2020-05-09
------------------
@@ -795,7 +34,7 @@
if ``S`` is not formattable.
* Reduced the library size by ~10%.
* Reduced library size by ~10%.
* Always print decimal point if ``#`` is specified
(`#1476 <https://github.com/fmtlib/fmt/issues/1476>`_,
@@ -1358,16 +597,16 @@
#include <fmt/compile.h>
auto f = fmt::compile<int>("{}");
std::string s = fmt::format(f, 42); // can be called multiple times to
// format different values
std::string s = fmt::format(f, 42); // can be called multiple times to format
// different values
// s == "42"
It moves the cost of parsing a format string outside of the format function
which can be beneficial when identically formatting many objects of the same
types. Thanks `@stryku (Mateusz Janek) <https://github.com/stryku>`_.
* Added experimental ``%`` format specifier that formats floating-point values
as percentages (`#1060 <https://github.com/fmtlib/fmt/pull/1060>`_,
* Added the ``%`` format specifier that formats floating-point values as
percentages (`#1060 <https://github.com/fmtlib/fmt/pull/1060>`_,
`#1069 <https://github.com/fmtlib/fmt/pull/1069>`_,
`#1071 <https://github.com/fmtlib/fmt/pull/1071>`_):
@@ -2130,7 +1369,7 @@
* Implemented ``constexpr`` parsing of format strings and `compile-time format
string checks
<https://fmt.dev/latest/api.html#compile-time-format-string-checks>`_. For
<https://fmt.dev/dev/api.html#compile-time-format-string-checks>`_. For
example
.. code:: c++
@@ -2191,7 +1430,7 @@
throw format_error("invalid specifier");
* Added `iterator support
<https://fmt.dev/latest/api.html#output-iterator-support>`_:
<https://fmt.dev/dev/api.html#output-iterator-support>`_:
.. code:: c++
@@ -2202,7 +1441,7 @@
fmt::format_to(std::back_inserter(out), "{}", 42);
* Added the `format_to_n
<https://fmt.dev/latest/api.html#_CPPv2N3fmt11format_to_nE8OutputItNSt6size_tE11string_viewDpRK4Args>`_
<https://fmt.dev/dev/api.html#_CPPv2N3fmt11format_to_nE8OutputItNSt6size_tE11string_viewDpRK4Args>`_
function that restricts the output to the specified number of characters
(`#298 <https://github.com/fmtlib/fmt/issues/298>`_):
@@ -2213,7 +1452,7 @@
// out == "1234" (without terminating '\0')
* Added the `formatted_size
<https://fmt.dev/latest/api.html#_CPPv2N3fmt14formatted_sizeE11string_viewDpRK4Args>`_
<https://fmt.dev/dev/api.html#_CPPv2N3fmt14formatted_sizeE11string_viewDpRK4Args>`_
function for computing the output size:
.. code:: c++
@@ -2223,7 +1462,7 @@
auto size = fmt::formatted_size("{}", 12345); // size == 5
* Improved compile times by reducing dependencies on standard headers and
providing a lightweight `core API <https://fmt.dev/latest/api.html#core-api>`_:
providing a lightweight `core API <https://fmt.dev/dev/api.html#core-api>`_:
.. code:: c++
@@ -2235,7 +1474,7 @@
<https://github.com/fmtlib/fmt#compile-time-and-code-bloat>`_.
* Added the `make_format_args
<https://fmt.dev/latest/api.html#_CPPv2N3fmt16make_format_argsEDpRK4Args>`_
<https://fmt.dev/dev/api.html#_CPPv2N3fmt16make_format_argsEDpRK4Args>`_
function for capturing formatting arguments:
.. code:: c++
@@ -2317,7 +1556,7 @@
fmt::format("{} {two}", 1, fmt::arg("two", 2));
* Removed the write API in favor of the `format API
<https://fmt.dev/latest/api.html#format-api>`_ with compile-time handling of
<https://fmt.dev/dev/api.html#format-api>`_ with compile-time handling of
format strings.
* Disallowed formatting of multibyte strings into a wide character target

View File

@@ -7,174 +7,153 @@
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
:target: https://ci.appveyor.com/project/vitaut/fmt
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
:alt: fmt is continuously fuzzed at oss-fuzz
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
Summary&q=proj%3Dfmt&can=1
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
:alt: fmt is continuously fuzzed att oss-fuzz
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dlibfmt&can=1
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
:alt: Ask questions at StackOverflow with the tag fmt
:target: https://stackoverflow.com/questions/tagged/fmt
**{fmt}** is an open-source formatting library providing a fast and safe
alternative to C stdio and C++ iostreams.
**{fmt}** is an open-source formatting library for C++.
It can be used as a safe and fast alternative to (s)printf and iostreams.
If you like this project, please consider donating to BYSOL,
an initiative to help victims of political repressions in Belarus:
https://www.facebook.com/donate/759400044849707/108388587646909/.
`Documentation <https://fmt.dev/latest/>`__
`Documentation <https://fmt.dev>`__
Q&A: ask questions on `StackOverflow with the tag fmt
<https://stackoverflow.com/questions/tagged/fmt>`_.
Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
Q&A: ask questions on `StackOverflow with the tag fmt <https://stackoverflow.com/questions/tagged/fmt>`_.
Features
--------
* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
for localization
* Implementation of `C++20 std::format
<https://en.cppreference.com/w/cpp/utility/format>`__
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
round-trip guarantees
* Replacement-based `format API <https://fmt.dev/dev/api.html>`_ with
positional arguments for localization.
* `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one
of `str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
in Python.
* Safe `printf implementation
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
extension for positional arguments
* Extensibility: `support for user-defined types
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
<https://fmt.dev/latest/api.html#printf-formatting>`_ including
the POSIX extension for positional arguments.
* Implementation of `C++20 std::format <https://en.cppreference.com/w/cpp/utility/format>`__.
* Support for user-defined types.
* High performance: faster than common standard library implementations of
``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
and `Converting a hundred million integers to strings per second
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
* Small code size both in terms of source code with the minimum configuration
consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
and compiled code; see `Compile time and code bloat`_
* Reliability: the library has an extensive set of `tests
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
`printf <https://en.cppreference.com/w/cpp/io/c/fprintf>`_ and
iostreams. See `Speed tests`_ and `Fast integer to string conversion in C++
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
* Small code size both in terms of source code (the minimum configuration
consists of just three header files, ``core.h``, ``format.h`` and
``format-inl.h``) and compiled code. See `Compile time and code bloat`_.
* Reliability: the library has an extensive set of `unit tests
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed.
* Safety: the library is fully type safe, errors in format strings can be
reported at compile time, automatic memory management prevents buffer overflow
errors
errors.
* Ease of use: small self-contained code base, no external dependencies,
permissive MIT `license
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
consistent output across platforms and support for older compilers
* Clean warning-free codebase even on high warning levels such as
``-Wall -Wextra -pedantic``
* Locale-independence by default
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
consistent output across platforms and support for older compilers.
* Clean warning-free codebase even on high warning levels
(``-Wall -Wextra -pedantic``).
* Support for wide strings.
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro.
See the `documentation <https://fmt.dev>`_ for more details.
See the `documentation <https://fmt.dev/latest/>`_ for more details.
Examples
--------
**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
Print ``Hello, world!`` to ``stdout``:
.. code:: c++
#include <fmt/core.h>
int main() {
fmt::print("Hello, world!\n");
}
fmt::print("Hello, {}!", "world"); // Python-like format string syntax
fmt::printf("Hello, %s!", "world"); // printf format string syntax
**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
.. code:: c++
std::string s = fmt::format("The answer is {}.", 42);
// s == "The answer is 42."
**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
Format a string and use positional arguments:
.. code:: c++
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
Check a format string at compile time:
.. code:: c++
#include <fmt/chrono.h>
// test.cc
#include <fmt/format.h>
std::string s = format(FMT_STRING("{2}"), 42);
int main() {
using namespace std::literals::chrono_literals;
fmt::print("Default format: {} {}\n", 42s, 100ms);
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
.. code::
$ c++ -Iinclude -std=c++14 test.cc
...
test.cc:4:17: note: in instantiation of function template specialization 'fmt::v5::format<S, int>' requested here
std::string s = format(FMT_STRING("{2}"), 42);
^
include/fmt/core.h:778:19: note: non-constexpr function 'on_error' cannot be used in a constant expression
ErrorHandler::on_error(message);
^
include/fmt/format.h:2226:16: note: in call to '&checker.context_->on_error(&"argument index out of range"[0])'
context_.on_error("argument index out of range");
^
Use {fmt} as a safe portable replacement for ``itoa``
(`godbolt <https://godbolt.org/g/NXmpU4>`_):
.. code:: c++
fmt::memory_buffer buf;
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
// access the string with to_string(buf) or buf.data()
Format objects of user-defined types via a simple `extension API
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_:
.. code:: c++
#include "fmt/format.h"
struct date {
int year, month, day;
};
template <>
struct fmt::formatter<date> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const date& d, FormatContext& ctx) {
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
}
};
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
// s == "The date is 2012-12-9"
Create your own functions similar to `format
<https://fmt.dev/latest/api.html#format>`_ and
`print <https://fmt.dev/latest/api.html#print>`_
which take arbitrary arguments (`godbolt <https://godbolt.org/g/MHjHVf>`_):
.. code:: c++
// Prints formatted error message.
void vreport_error(const char* format, fmt::format_args args) {
fmt::print("Error: ");
fmt::vprint(format, args);
}
template <typename... Args>
void report_error(const char* format, const Args & ... args) {
vreport_error(format, fmt::make_format_args(args...));
}
Output::
report_error("file not found: {}", path);
Default format: 42s 100ms
strftime-like format: 03:15:30
**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_)
.. code:: c++
#include <vector>
#include <fmt/ranges.h>
int main() {
std::vector<int> v = {1, 2, 3};
fmt::print("{}\n", v);
}
Output::
{1, 2, 3}
**Check a format string at compile time**
.. code:: c++
std::string s = fmt::format(FMT_STRING("{:d}"), "don't panic");
This gives a compile-time error because ``d`` is an invalid format specifier for
a string.
**Write a file from a single thread**
.. code:: c++
#include <fmt/os.h>
int main() {
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");
}
This can be `5 to 9 times faster than fprintf
<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
**Print with colors and text styles**
.. code:: c++
#include <fmt/color.h>
int main() {
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
"Hello, {}!\n", "world");
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
fmt::emphasis::underline, "Hello, {}!\n", "мир");
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
"Hello, {}!\n", "世界");
}
Output on a modern terminal:
.. image:: https://user-images.githubusercontent.com/
576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
Note that ``vreport_error`` is not parameterized on argument types which can
improve compile times and reduce code size compared to a fully parameterized
version.
Benchmarks
----------
@@ -195,20 +174,18 @@ Folly Format folly::format 2.23
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
The above results were generated by building ``tinyformat_test.cpp`` on macOS
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
10.14.6 with ``clang++ -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of
three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
`ryu <https://github.com/ulfjack/ryu>`_:
{fmt} is 10x faster than ``std::ostringstream`` and ``sprintf`` on floating-point
formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
and as fast as `double-conversion <https://github.com/google/double-conversion>`_:
.. image:: https://user-images.githubusercontent.com/576385/
95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
:target: https://fmt.dev/unknown_mac64_clang12.0.html
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
:target: https://fmt.dev/unknown_mac64_clang10.0.html
Compile time and code bloat
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -287,33 +264,36 @@ or the bloat test::
Projects using this library
---------------------------
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
real-time strategy game
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform real-time
strategy game
* `AMPL/MP <https://github.com/ampl/mp>`_:
an open-source library for mathematical programming
* `Aseprite <https://github.com/aseprite/aseprite>`_:
animated sprite editor & pixel art tool
* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
An open-source library for mathematical programming
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft
operations suite
* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
* `Celestia <https://celestia.space/>`_: Real-time 3D visualization of space
* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
* `ccache <https://ccache.dev/>`_: a compiler cache
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
management system
* `ccache <https://ccache.dev/>`_: A compiler cache
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle
* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
Player vs Player Gaming Network with tweaks
* `KBEngine <https://kbengine.org/>`_: An open-source MMOG server engine
* `Keypirinha <https://keypirinha.com/>`_: A semantic launcher for Windows
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
* `Drake <https://drake.mit.edu/>`_: A planning, control, and analysis toolbox
for nonlinear dynamical systems (MIT)
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
@@ -321,76 +301,49 @@ Projects using this library
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
* `MongoDB <https://mongodb.com/>`_: Distributed document database
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
Player vs Player Gaming Network with tweaks
* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
engine
* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
research programming language for concurrent ownership
* `MongoDB <https://mongodb.com/>`_: distributed document database
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
generate randomized datasets
* `OpenSpace <https://openspaceproject.com/>`_: an open-source
astrovisualization framework
* `OpenSpace <https://openspaceproject.com/>`_: An open-source astrovisualization
framework
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
an MMO server, compatible with most Ultima Online clients
An MMO server, compatible with most Ultima Online clients
* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
learning library
* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
associative database
* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
navigation, and executing complex multi-line terminal command sequences
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster
proxy
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
for mission critical systems written in C++
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
* `rpclib <http://rpclib.net/>`_: A modern C++ msgpack-RPC server and client
library
* `Salesforce Analytics Cloud
<https://www.salesforce.com/analytics-cloud/overview/>`_:
business intelligence software
* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_:
Small crossplatform 2D graphic engine
* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
* `Salesforce Analytics Cloud <https://www.salesforce.com/analytics-cloud/overview/>`_:
Business intelligence software
* `Scylla <https://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store
that can handle 1 million transactions per second on a single server
* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++
framework for high-performance server applications on modern hardware
* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library
* `Stellar <https://www.stellar.org/>`_: financial platform
* `Stellar <https://www.stellar.org/>`_: Financial platform
* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
MMORPG framework
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
terminal
`More... <https://github.com/search?q=fmtlib&type=Code>`_
If you are aware of other projects using this library, please let me know
@@ -447,15 +400,15 @@ Boost Format
This is a very powerful library which supports both ``printf``-like format
strings and positional arguments. Its main drawback is performance. According to
various, benchmarks it is much slower than other methods considered here. Boost
various benchmarks it is much slower than other methods considered here. Boost
Format also has excessive build times and severe code bloat issues (see
`Benchmarks`_).
FastFormat
~~~~~~~~~~
This is an interesting library which is fast, safe and has positional arguments.
However, it has significant limitations, citing its author:
This is an interesting library which is fast, safe and has positional
arguments. However it has significant limitations, citing its author:
Three features that have no hope of being accommodated within the
current design are:
@@ -464,8 +417,8 @@ However, it has significant limitations, citing its author:
* Octal/hexadecimal encoding
* Runtime width/alignment specification
It is also quite big and has a heavy dependency, STLSoft, which might be too
restrictive for using it in some projects.
It is also quite big and has a heavy dependency, STLSoft, which might be
too restrictive for using it in some projects.
Boost Spirit.Karma
~~~~~~~~~~~~~~~~~~
@@ -473,9 +426,32 @@ Boost Spirit.Karma
This is not really a formatting library but I decided to include it here for
completeness. As iostreams, it suffers from the problem of mixing verbatim text
with arguments. The library is pretty fast, but slower on integer formatting
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
see `Converting a hundred million integers to strings per second
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
than ``fmt::format_int`` on Karma's own benchmark,
see `Fast integer to string conversion in C++
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
FAQ
---
Q: how can I capture formatting arguments and format them later?
A: use ``std::tuple``:
.. code:: c++
template <typename... Args>
auto capture(const Args&... args) {
return std::make_tuple(args...);
}
auto print_message = [](const auto&... args) {
fmt::print(args...);
};
// Capture and store arguments:
auto args = capture("{} {}", 42, "foo");
// Do formatting:
std::apply(print_message, args);
License
-------
@@ -483,19 +459,18 @@ License
{fmt} is distributed under the MIT `license
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
Documentation License
---------------------
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
The `Format String Syntax
<https://fmt.dev/latest/syntax.html>`_
section in the documentation is based on the one from Python `string module
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
For this reason the documentation is distributed under the Python Software
Foundation license available in `doc/python-license.txt
documentation <https://docs.python.org/3/library/string.html#module-string>`_
adapted for the current library. For this reason the documentation is
distributed under the Python Software Foundation license available in
`doc/python-license.txt
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
It only applies if you distribute the documentation of {fmt}.
It only applies if you distribute the documentation of fmt.
Maintainers
-----------
Acknowledgments
---------------
The {fmt} library is maintained by Victor Zverovich (`vitaut
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
@@ -504,3 +479,23 @@ See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
Let us know if your contribution is not listed or mentioned incorrectly and
we'll make it right.
The benchmark section of this readme file and the performance tests are taken
from the excellent `tinyformat <https://github.com/c42f/tinyformat>`_ library
written by Chris Foster. Boost Format library is acknowledged transitively
since it had some influence on tinyformat.
Some ideas used in the implementation are borrowed from `Loki
<http://loki-lib.sourceforge.net/>`_ SafeFormat and `Diagnostic API
<https://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in
`Clang <https://clang.llvm.org/>`_.
Format string syntax and the documentation are based on Python's `str.format
<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
Thanks `Doug Turnbull <https://github.com/softwaredoug>`_ for his valuable
comments and contribution to the design of the type-safe API and
`Gregory Czajkowski <https://github.com/gcflymoto>`_ for implementing binary
formatting. Thanks `Ruslan Baratov <https://github.com/ruslo>`_ for comprehensive
`comparison of integer formatting algorithms <https://github.com/ruslo/int-dec-format-tests>`_
and useful comments regarding performance, `Boris Kaul <https://github.com/localvoid>`_ for
`C++ counting digits benchmark <https://github.com/localvoid/cxx-benchmark-count-digits>`_.
Thanks to `CarterLi <https://github.com/CarterLi>`_ for contributing various
improvements to the code.

View File

@@ -4,14 +4,9 @@ if (NOT DOXYGEN)
return ()
endif ()
find_package(PythonInterp QUIET REQUIRED)
add_custom_target(doc
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
${FMT_VERSION}
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION}
SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL
PATTERN ".doctrees" EXCLUDE)
DESTINATION share/doc/fmt OPTIONAL)

View File

@@ -6,13 +6,14 @@
<meta name="author" content="Victor Zverovich">
<link rel="stylesheet" href="_static/fmt.css">
{# Google Analytics #}
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20116650-4"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-20116650-4');
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();
a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;
a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-20116650-4', 'fmtlib.net');
ga('send', 'pageview');
</script>
{% endblock %}

View File

@@ -9,13 +9,10 @@ The {fmt} library API consists of the following parts:
* :ref:`fmt/core.h <core-api>`: the core API providing argument handling
facilities and a lightweight subset of formatting functions
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
format string checks, wide string, output iterator and user-defined type
support
format string checks, output iterator and user-defined type support
* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
and tuples
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
* :ref:`fmt/compile.h <compile-api>`: format string compilation
* :ref:`fmt/color.h <color-api>`: terminal color and text style
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
@@ -46,11 +43,7 @@ participate in an overload resolution if the latter is not a string.
.. _format:
.. doxygenfunction:: format(const S&, Args&&...)
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<type_identity_t<Char>>>)
.. doxygenfunction:: fmt::format_to(OutputIt, const S&, Args&&...)
.. doxygenfunction:: fmt::format_to_n(OutputIt, size_t, const S&, const Args&...)
.. doxygenfunction:: fmt::formatted_size(string_view, Args&&...)
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<Char>>)
.. _print:
@@ -70,43 +63,11 @@ Named arguments are not supported in compile-time checks at the moment.
Argument Lists
--------------
You can create your own formatting function with compile-time checks and small
binary footprint, for example (https://godbolt.org/z/oba4Mc):
.. code:: c++
#include <fmt/format.h>
void vlog(const char* file, int line, fmt::string_view format,
fmt::format_args args) {
fmt::print("{}: {}: ", file, line);
fmt::vprint(format, args);
}
template <typename S, typename... Args>
void log(const char* file, int line, const S& format, Args&&... args) {
vlog(file, line, format,
fmt::make_args_checked<Args...>(format, args...));
}
#define MY_LOG(format, ...) \
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
MY_LOG("invalid squishiness: {}", 42);
Note that ``vlog`` is not parameterized on argument types which improves compile
times and reduces binary code size compared to a fully parameterized version.
.. doxygenfunction:: fmt::make_args_checked(const S&, const remove_reference_t<Args>&...)
.. doxygenfunction:: fmt::make_format_args(const Args&...)
.. doxygenclass:: fmt::format_arg_store
:members:
.. doxygenclass:: fmt::dynamic_format_arg_store
:members:
.. doxygenclass:: fmt::basic_format_args
:members:
@@ -127,7 +88,7 @@ Compatibility
Locale
------
All formatting is locale-independent by default. Use the ``'L'`` format
All formatting is locale-independent by default. Use the ``'n'`` format
specifier to insert the appropriate number separator characters from the
locale::
@@ -135,7 +96,7 @@ locale::
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
auto s = fmt::format("{:n}", 1000000); // s == "1,000,000"
.. _format-api:
@@ -143,19 +104,17 @@ Format API
==========
``fmt/format.h`` defines the full format API providing compile-time format
string checks, wide string, output iterator and user-defined type support.
string checks, output iterator and user-defined type support.
Compile-time Format String Checks
---------------------------------
Compile-time checks are enabled when using ``FMT_STRING``. They support built-in
and string types as well as user-defined types with ``constexpr`` ``parse``
functions in their ``formatter`` specializations.
Compile-time checks are supported for built-in and string types as well as
user-defined types with ``constexpr`` ``parse`` functions in their ``formatter``
specializations.
.. doxygendefine:: FMT_STRING
.. _udt:
Formatting User-defined Types
-----------------------------
@@ -173,7 +132,6 @@ template and implement ``parse`` and ``format`` methods::
// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) {
// auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
@@ -201,7 +159,6 @@ template and implement ``parse`` and ``format`` methods::
// stored in this formatter.
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) {
// auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11
// ctx.out() is an output iterator to write to.
return format_to(
ctx.out(),
@@ -221,7 +178,8 @@ example::
enum class color {red, green, blue};
template <> struct fmt::formatter<color>: formatter<string_view> {
template <>
struct fmt::formatter<color>: formatter<string_view> {
// parse is inherited from formatter<string_view>.
template <typename FormatContext>
auto format(color c, FormatContext& ctx) {
@@ -235,15 +193,6 @@ example::
}
};
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
all string format specifications, for example
.. code-block:: c++
fmt::format("{:>10}", color::blue)
will return ``" blue"``.
You can also write a formatter for a hierarchy of classes::
#include <type_traits>
@@ -273,10 +222,6 @@ You can also write a formatter for a hierarchy of classes::
fmt::print("{}", a); // prints "B"
}
If a type provides both a ``formatter`` specialization and an implicit
conversion to a formattable type, the specialization takes precedence over the
conversion.
.. doxygenclass:: fmt::basic_format_parse_context
:members:
@@ -284,7 +229,7 @@ Output Iterator Support
-----------------------
.. doxygenfunction:: fmt::format_to(OutputIt, const S&, Args&&...)
.. doxygenfunction:: fmt::format_to_n(OutputIt, size_t, const S&, const Args&...)
.. doxygenfunction:: fmt::format_to_n(OutputIt, std::size_t, string_view, Args&&...)
.. doxygenstruct:: fmt::format_to_n_result
:members:
@@ -293,9 +238,9 @@ Literal-based API
The following user-defined literals are defined in ``fmt/format.h``.
.. doxygenfunction:: operator""_format(const char *, size_t)
.. doxygenfunction:: operator""_format(const char *, std::size_t)
.. doxygenfunction:: operator""_a(const char *, size_t)
.. doxygenfunction:: operator""_a(const char *, std::size_t)
Utilities
---------
@@ -304,9 +249,7 @@ Utilities
.. doxygentypedef:: fmt::char_t
.. doxygenfunction:: fmt::ptr(const T *)
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T>&)
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T>&)
.. doxygenfunction:: fmt::formatted_size(string_view, const Args&...)
.. doxygenfunction:: fmt::to_string(const T&)
@@ -314,12 +257,9 @@ Utilities
.. doxygenfunction:: fmt::to_string_view(const Char *)
.. doxygenfunction:: fmt::join(Range&&, string_view)
.. doxygenfunction:: fmt::join(const Range&, string_view)
.. doxygenfunction:: fmt::join(It, Sentinel, string_view)
.. doxygenclass:: fmt::detail::buffer
:members:
.. doxygenfunction:: fmt::join(It, It, string_view)
.. doxygenclass:: fmt::basic_memory_buffer
:protected-members:
@@ -340,6 +280,8 @@ the value of ``errno`` being preserved by library functions.
.. doxygenclass:: fmt::windows_error
:members:
.. _formatstrings:
Custom Allocators
-----------------
@@ -370,10 +312,54 @@ allocator::
return vformat(alloc, format_str, fmt::make_format_args(args...));
}
The allocator will be used for the output container only. Formatting functions
normally don't do any allocations for built-in and string types except for
non-default floating-point formatting that occasionally falls back on
``sprintf``.
The allocator will be used for the output container only. If you are using named
arguments, the container that stores pointers to them will be allocated using
the default allocator. Also floating-point formatting falls back on ``sprintf``
which may do allocations.
Custom Formatting of Built-in Types
-----------------------------------
It is possible to change the way arguments are formatted by providing a
custom argument formatter class::
using arg_formatter = fmt::arg_formatter<fmt::buffer_range<char>>;
// A custom argument formatter that formats negative integers as unsigned
// with the ``x`` format specifier.
class custom_arg_formatter : public arg_formatter {
public:
custom_arg_formatter(fmt::format_context& ctx,
fmt::format_parse_context* parse_ctx = nullptr,
fmt::format_specs* spec = nullptr)
: arg_formatter(ctx, parse_ctx, spec) {}
using arg_formatter::operator();
auto operator()(int value) {
if (specs() && specs()->type == 'x')
return (*this)(static_cast<unsigned>(value)); // convert to unsigned and format
return arg_formatter::operator()(value);
}
};
std::string custom_vformat(fmt::string_view format_str, fmt::format_args args) {
fmt::memory_buffer buffer;
// Pass custom argument formatter as a template arg to vformat_to.
fmt::vformat_to<custom_arg_formatter>(buffer, format_str, args);
return fmt::to_string(buffer);
}
template <typename ...Args>
inline std::string custom_format(
fmt::string_view format_str, const Args&... args) {
return custom_vformat(format_str, fmt::make_format_args(args...));
}
std::string s = custom_format("{:x}", -42); // s == "ffffffd6"
.. doxygenclass:: fmt::arg_formatter
:members:
.. _ranges-api:
@@ -413,36 +399,11 @@ formatting::
std::time_t t = std::time(nullptr);
// Prints "The date is 2016-04-29." (with the current date)
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t));
The format string syntax is described in the documentation of
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
.. _compile-api:
Format string compilation
=========================
``fmt/compile.h`` provides format string compilation support when using
``FMT_COMPILE``. Format strings are parsed, checked and converted
into efficient formatting code at compile-time.
This supports arguments of built-in and string types as well as user-defined types
with ``constexpr`` ``parse`` functions in their ``formatter`` specializations.
Format string compilation can generate more binary code compared to the default
API and is only recommended in places where formatting is a performance
bottleneck.
.. doxygendefine:: FMT_COMPILE
.. _color-api:
Terminal color and text style
=============================
``fmt/color.h`` provides support for terminal color and text style output.
.. doxygenfunction:: print(const text_style&, const S&, const Args&...)
.. _ostream-api:
``std::ostream`` Support
@@ -487,19 +448,3 @@ argument type doesn't match its format specification.
.. doxygenfunction:: fprintf(std::basic_ostream<Char>&, const S&, const Args&...)
.. doxygenfunction:: sprintf(const S&, const Args&...)
Compatibility with C++20 ``std::format``
========================================
{fmt} implements nearly all of the `C++20 formatting library
<https://en.cppreference.com/w/cpp/utility/format>`_ with the following
differences:
* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid
collisions with standard library implementations.
* The ``'L'`` format specifier cannot be combined with presentation specifiers
yet.
* Width calculation doesn't use grapheme clusterization. The latter has been
implemented in a separate branch but hasn't been integrated yet.
* Chrono formatting doesn't support C++20 date types since they are not provided
by standard library implementations.

View File

@@ -6,10 +6,20 @@ import errno, os, shutil, sys, tempfile
from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
from distutils.version import LooseVersion
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']
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']
def pip_install(package, commit=None, **kwargs):
"Install package using pip."
min_version = kwargs.get('min_version')
if min_version:
from pkg_resources import get_distribution, DistributionNotFound
try:
installed_version = get_distribution(os.path.basename(package)).version
if LooseVersion(installed_version) >= min_version:
print('{} {} already installed'.format(package, min_version))
return
except DistributionNotFound:
pass
if commit:
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
print('Installing {0}'.format(package))
@@ -42,11 +52,12 @@ def create_build_env(dirname='virtualenv'):
check_call(['pip', 'install', '--upgrade', 'distribute'])
except DistributionNotFound:
pass
# Install Sphinx and Breathe. Require the exact version of Sphinx which is
# compatible with Breathe.
pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee')
# Install Sphinx and Breathe.
pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee',
min_version='1.4.1.dev20160531')
pip_install('michaeljones/breathe',
'129222318f7c8f865d2631e7da7b033567e7f56a')
'129222318f7c8f865d2631e7da7b033567e7f56a',
min_version='4.2.0')
def build_docs(version='dev', **kwargs):
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
@@ -63,8 +74,8 @@ def build_docs(version='dev', **kwargs):
GENERATE_MAN = NO
GENERATE_RTF = NO
CASE_SENSE_NAMES = NO
INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \
{0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h
INPUT = {0}/core.h {0}/format.h {0}/os.h {0}/ostream.h \
{0}/printf.h {0}/time.h
QUIET = YES
JAVADOC_AUTOBRIEF = YES
AUTOLINK_SUPPORT = NO
@@ -84,8 +95,7 @@ def build_docs(version='dev', **kwargs):
"FMT_END_NAMESPACE=}}" \
"FMT_STRING_ALIAS=1" \
"FMT_ENABLE_IF(B)="
EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \
fmt::basic_format_arg::handle
EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str
'''.format(include_dir, doxyxml_dir).encode('UTF-8'))
if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd)

View File

@@ -22,8 +22,8 @@ Format API
----------
The format API is similar in spirit to the C ``printf`` family of function but
is safer, simpler and several times `faster
<https://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
is safer, simpler and serveral times `faster
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
than common standard library implementations.
The `format string syntax <syntax.html>`_ is similar to the one used by
`str.format <http://docs.python.org/3/library/stdtypes.html#str.format>`_ in
@@ -48,19 +48,21 @@ The ``fmt::print`` function performs formatting and writes the result to a strea
fmt::print(stderr, "System error code = {}\n", errno);
If you omit the file argument the function will print to ``stdout``:
The file argument can be omitted in which case the function prints to
``stdout``:
.. code:: c++
fmt::print("Don't {}\n", "panic");
The format API also supports positional arguments useful for localization:
The Format API also supports positional arguments useful for localization:
.. code:: c++
fmt::print("I'd rather be {1} than {0}.", "right", "happy");
You can pass named arguments with ``fmt::arg``:
Named arguments can be created with ``fmt::arg``. This makes it easier to track
what goes where when multiple arguments are being formatted:
.. code:: c++
@@ -89,17 +91,16 @@ time. For example, the code
fmt::format("The answer is {:d}", "forty-two");
throws the ``format_error`` exception because the argument ``"forty-two"`` is a
string while the format code ``d`` only applies to integers.
The code
throws a ``format_error`` exception with description "unknown format code 'd' for
string", because the argument ``"forty-two"`` is a string while the format code
``d`` only applies to integers, while
.. code:: c++
format(FMT_STRING("The answer is {:d}"), "forty-two");
reports a compile-time error on compilers that support relaxed ``constexpr``.
See `here <api.html#c.fmt>`_ for details.
reports a compile-time error for the same reason on compilers that support
relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for details.
The following code
@@ -116,13 +117,13 @@ formatted into a narrow string. You can use a wide format string instead:
For comparison, writing a wide character to ``std::ostream`` results in
its numeric value being written to the stream (i.e. 1070 instead of letter 'ю'
which is represented by ``L'\x42e'`` if we use Unicode) which is rarely
desirable.
which is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is
needed.
Compact Binary Code
-------------------
The library produces compact per-call compiled code. For example
The library is designed to produce compact per-call compiled code. For example
(`godbolt <https://godbolt.org/g/TZU4KF>`_),
.. code:: c++
@@ -143,8 +144,8 @@ compiles to just
mov rcx, rsp
mov edi, offset .L.str
mov esi, 17
mov edx, 1
call fmt::v7::vprint(fmt::v7::basic_string_view<char>, fmt::v7::format_args)
mov edx, 2
call fmt::v5::vprint(fmt::v5::basic_string_view<char>, fmt::v5::format_args)
xor eax, eax
add rsp, 24
ret
@@ -166,19 +167,20 @@ The library is highly portable and relies only on a small set of C++11 features:
* deleted functions
* alias templates
These are available in GCC 4.8, Clang 3.0, MSVC 19.0 (2015) and more recent
compiler version. For older compilers use {fmt} `version 4.x
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which is maintained and
only requires C++98.
These are available since GCC 4.8, Clang 3.0 and MSVC 19.0 (2015). For older
compilers use {fmt} `version 4.x
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be
maintained and only requires C++98.
The output of all formatting functions is consistent across platforms.
For example,
The output of all formatting functions is consistent across platforms. In
particular, formatting a floating-point infinity always gives ``inf`` while the
output of ``printf`` is platform-dependent. For example,
.. code::
fmt::print("{}", std::numeric_limits<double>::infinity());
always prints ``inf`` while the output of ``printf`` is platform-dependent.
always prints ``inf``.
.. _ease-of-use:

View File

@@ -79,8 +79,8 @@ The general form of a *standard format specifier* is:
fill: <a character other than '{' or '}'>
align: "<" | ">" | "^"
sign: "+" | "-" | " "
width: `integer` | "{" [`arg_id`] "}"
precision: `integer` | "{" [`arg_id`] "}"
width: `integer` | "{" `arg_id` "}"
precision: `integer` | "{" `arg_id` "}"
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s"
int_type: "b" | "B" | "d" | "o" | "x" | "X"

View File

@@ -52,14 +52,6 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
To build a `static library` with position independent code (required if the main
consumer of the fmt library is a shared library i.e. a Python extension) set the
``CMAKE_POSITION_INDEPENDENT_CODE`` CMake variable to ``TRUE``::
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ...
Installing the Library
======================
@@ -91,49 +83,6 @@ Setting up your target to use a header-only version of ``fmt`` is equally easy::
target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only)
Usage with build2
=================
You can use `build2 <https://build2.org>`_, a dependency manager and a
build-system combined, to use ``fmt``.
Currently this package is available in these package repositories:
- **https://cppget.org/fmt/** for released and published versions.
- `The git repository with the sources of the build2 package of fmt <https://github.com/build2-packaging/fmt.git>`_
for unreleased or custom revisions of ``fmt``.
**Usage:**
- ``build2`` package name: ``fmt``
- Library target name : ``lib{fmt}``
For example, to make your ``build2`` project depend on ``fmt``:
- Add one of the repositories to your configurations, or in your
``repositories.manifest``, if not already there::
:
role: prerequisite
location: https://pkg.cppget.org/1/stable
- Add this package as a dependency to your ``./manifest`` file
(example for ``v7.0.x``)::
depends: fmt ~7.0.0
- Import the target and use it as a prerequisite to your own target
using `fmt` in the appropriate ``buildfile``::
import fmt = fmt%lib{fmt}
lib{mylib} : cxx{**} ... $fmt
Then build your project as usual with `b` or `bdep update`.
For ``build2`` newcomers or to get more details and use cases, you can read the
``build2``
`toolchain introduction <https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml>`_.
Building the Documentation
==========================
@@ -181,18 +130,6 @@ The fmt port in vcpkg is kept up to date by Microsoft team members and community
contributors. If the version is out of date, please `create an issue or pull
request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository.
LHelper
=======
You can download and install fmt using
`lhelper <https://github.com/franko/lhelper>`__ dependency manager::
lhelper activate <some-environment>
lhelper install fmt
All the recipes for lhelper are kept in the
`lhelper's recipe <https://github.com/franko/lhelper-recipes>`__ repository.
Android NDK
===========

View File

@@ -48,7 +48,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
// From fits in To without any problem.
} else {
// From does not always fit in To, resort to a dynamic check.
if (from < (T::min)() || from > (T::max)()) {
if (from < T::min() || from > T::max()) {
// outside range.
ec = 1;
return {};
@@ -72,27 +72,43 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
static_assert(F::is_integer, "From must be integral");
static_assert(T::is_integer, "To must be integral");
if (detail::const_check(F::is_signed && !T::is_signed)) {
if (F::is_signed && !T::is_signed) {
// From may be negative, not allowed!
if (fmt::detail::is_negative(from)) {
if (fmt::internal::is_negative(from)) {
ec = 1;
return {};
}
// From is positive. Can it always fit in To?
if (F::digits > T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
if (F::digits <= T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>(T::max())) {
ec = 1;
return {};
}
}
}
if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
if (!F::is_signed && T::is_signed) {
// can from be held in To?
if (F::digits < T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>(T::max())) {
// outside range.
ec = 1;
return {};
}
}
}
return static_cast<To>(from); // Lossless conversion.
}
// reaching here means all is ok for lossless conversion.
return static_cast<To>(from);
} // function
template <typename To, typename From,
FMT_ENABLE_IF(std::is_same<From, To>::value)>
@@ -125,7 +141,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
// catch the only happy case
if (std::isfinite(from)) {
if (from >= T::lowest() && from <= (T::max)()) {
if (from >= T::lowest() && from <= T::max()) {
return static_cast<To>(from);
}
// not within range.
@@ -174,16 +190,17 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// safe conversion to IntermediateRep
IntermediateRep count =
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
if (ec) return {};
if (ec) {
return {};
}
// multiply with Factor::num without overflow or underflow
if (detail::const_check(Factor::num != 1)) {
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
if (Factor::num != 1) {
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
if (count > max1) {
ec = 1;
return {};
}
const auto min1 =
(std::numeric_limits<IntermediateRep>::min)() / Factor::num;
const auto min1 = std::numeric_limits<IntermediateRep>::min() / Factor::num;
if (count < min1) {
ec = 1;
return {};
@@ -191,9 +208,17 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
count *= Factor::num;
}
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
return ec ? To() : To(tocount);
// this can't go wrong, right? den>0 is checked earlier.
if (Factor::den != 1) {
count /= Factor::den;
}
// convert to the to type, safely
using ToRep = typename To::rep;
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
if (ec) {
return {};
}
return To{tocount};
}
/**
@@ -244,7 +269,7 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// multiply with Factor::num without overflow or underflow
if (Factor::num != 1) {
constexpr auto max1 = detail::max_value<IntermediateRep>() /
constexpr auto max1 = internal::max_value<IntermediateRep>() /
static_cast<IntermediateRep>(Factor::num);
if (count > max1) {
ec = 1;
@@ -281,12 +306,12 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// Usage: f FMT_NOMACRO()
#define FMT_NOMACRO
namespace detail {
namespace internal {
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
inline null<> localtime_s(...) { return null<>(); }
inline null<> gmtime_r(...) { return null<>(); }
inline null<> gmtime_s(...) { return null<>(); }
} // namespace detail
} // namespace internal
// Thread-safe replacement for std::localtime
inline std::tm localtime(std::time_t time) {
@@ -297,22 +322,22 @@ inline std::tm localtime(std::time_t time) {
dispatcher(std::time_t t) : time_(t) {}
bool run() {
using namespace fmt::detail;
using namespace fmt::internal;
return handle(localtime_r(&time_, &tm_));
}
bool handle(std::tm* tm) { return tm != nullptr; }
bool handle(detail::null<>) {
using namespace fmt::detail;
bool handle(internal::null<>) {
using namespace fmt::internal;
return fallback(localtime_s(&tm_, &time_));
}
bool fallback(int res) { return res == 0; }
#if !FMT_MSC_VER
bool fallback(detail::null<>) {
using namespace fmt::detail;
bool fallback(internal::null<>) {
using namespace fmt::internal;
std::tm* tm = std::localtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
@@ -325,11 +350,6 @@ inline std::tm localtime(std::time_t time) {
return lt.tm_;
}
inline std::tm localtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return localtime(std::chrono::system_clock::to_time_t(time_point));
}
// Thread-safe replacement for std::gmtime
inline std::tm gmtime(std::time_t time) {
struct dispatcher {
@@ -339,21 +359,21 @@ inline std::tm gmtime(std::time_t time) {
dispatcher(std::time_t t) : time_(t) {}
bool run() {
using namespace fmt::detail;
using namespace fmt::internal;
return handle(gmtime_r(&time_, &tm_));
}
bool handle(std::tm* tm) { return tm != nullptr; }
bool handle(detail::null<>) {
using namespace fmt::detail;
bool handle(internal::null<>) {
using namespace fmt::internal;
return fallback(gmtime_s(&tm_, &time_));
}
bool fallback(int res) { return res == 0; }
#if !FMT_MSC_VER
bool fallback(detail::null<>) {
bool fallback(internal::null<>) {
std::tm* tm = std::gmtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
@@ -366,33 +386,17 @@ inline std::tm gmtime(std::time_t time) {
return gt.tm_;
}
inline std::tm gmtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return gmtime(std::chrono::system_clock::to_time_t(time_point));
}
namespace detail {
inline size_t strftime(char* str, size_t count, const char* format,
const std::tm* time) {
namespace internal {
inline std::size_t strftime(char* str, std::size_t count, const char* format,
const std::tm* time) {
return std::strftime(str, count, format, time);
}
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
const std::tm* time) {
inline std::size_t strftime(wchar_t* str, std::size_t count,
const wchar_t* format, const std::tm* time) {
return std::wcsftime(str, count, format, time);
}
} // namespace detail
template <typename Char>
struct formatter<std::chrono::time_point<std::chrono::system_clock>, Char>
: formatter<std::tm, Char> {
template <typename FormatContext>
auto format(std::chrono::time_point<std::chrono::system_clock> val,
FormatContext& ctx) -> decltype(ctx.out()) {
std::tm time = localtime(val);
return formatter<std::tm, Char>::format(time, ctx);
}
};
} // namespace internal
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext>
@@ -401,7 +405,7 @@ template <typename Char> struct formatter<std::tm, Char> {
if (it != ctx.end() && *it == ':') ++it;
auto end = it;
while (end != ctx.end() && *end != '}') ++end;
tm_format.reserve(detail::to_unsigned(end - it + 1));
tm_format.reserve(internal::to_unsigned(end - it + 1));
tm_format.append(it, end);
tm_format.push_back('\0');
return end;
@@ -410,10 +414,11 @@ template <typename Char> struct formatter<std::tm, Char> {
template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
basic_memory_buffer<Char> buf;
size_t start = buf.size();
std::size_t start = buf.size();
for (;;) {
size_t size = buf.capacity() - start;
size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm);
std::size_t size = buf.capacity() - start;
std::size_t count =
internal::strftime(&buf[start], size, &tm_format[0], &tm);
if (count != 0) {
buf.resize(start + count);
break;
@@ -425,7 +430,7 @@ template <typename Char> struct formatter<std::tm, Char> {
// https://github.com/fmtlib/fmt/issues/367
break;
}
const size_t MIN_GROWTH = 10;
const std::size_t MIN_GROWTH = 10;
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
}
return std::copy(buf.begin(), buf.end(), ctx.out());
@@ -434,7 +439,7 @@ template <typename Char> struct formatter<std::tm, Char> {
basic_memory_buffer<Char> tm_format;
};
namespace detail {
namespace internal {
template <typename Period> FMT_CONSTEXPR const char* get_units() {
return nullptr;
}
@@ -763,25 +768,19 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
val);
}
template <typename Char, typename OutputIt>
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
return std::copy(unit.begin(), unit.end(), out);
}
template <typename OutputIt>
OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
// This works when wchar_t is UTF-32 because units only contain characters
// that have the same representation in UTF-16 and UTF-32.
utf8_to_utf16 u(unit);
return std::copy(u.c_str(), u.c_str() + u.size(), out);
}
template <typename Char, typename Period, typename OutputIt>
OutputIt format_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>())
return copy_unit(string_view(unit), out, Char());
if (const char* unit = get_units<Period>()) {
string_view s(unit);
if (const_check(std::is_same<Char, wchar_t>())) {
utf8_to_utf16 u(s);
return std::copy(u.c_str(), u.c_str() + u.size(), out);
}
return std::copy(s.begin(), s.end(), out);
}
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num);
if (Period::den == 1) return format_to(out, num_f, Period::num);
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
return format_to(out, num_def_f, Period::num, Period::den);
}
@@ -875,9 +874,9 @@ struct chrono_formatter {
if (isnan(value)) return write_nan();
uint32_or_64_or_128_t<int> n =
to_unsigned(to_nonnegative_int(value, max_value<int>()));
int num_digits = detail::count_digits(n);
int num_digits = internal::count_digits(n);
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
out = format_decimal<char_type>(out, n, num_digits).end;
out = format_decimal<char_type>(out, n, num_digits);
}
void write_nan() { std::copy_n("nan", 3, out); }
@@ -1005,14 +1004,14 @@ struct chrono_formatter {
out = format_duration_unit<char_type, Period>(out);
}
};
} // namespace detail
} // namespace internal
template <typename Rep, typename Period, typename Char>
struct formatter<std::chrono::duration<Rep, Period>, Char> {
private:
basic_format_specs<Char> specs;
int precision;
using arg_ref_type = detail::arg_ref<Char>;
using arg_ref_type = internal::arg_ref<Char>;
arg_ref_type width_ref;
arg_ref_type precision_ref;
mutable basic_string_view<Char> format_str;
@@ -1033,7 +1032,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
return arg_ref_type(arg_id);
}
FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) {
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
return arg_ref_type(context.next_arg_id());
}
@@ -1063,17 +1062,17 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
auto begin = ctx.begin(), end = ctx.end();
if (begin == end || *begin == '}') return {begin, begin};
spec_handler handler{*this, ctx, format_str};
begin = detail::parse_align(begin, end, handler);
begin = internal::parse_align(begin, end, handler);
if (begin == end) return {begin, begin};
begin = detail::parse_width(begin, end, handler);
begin = internal::parse_width(begin, end, handler);
if (begin == end) return {begin, begin};
if (*begin == '.') {
if (std::is_floating_point<Rep>::value)
begin = detail::parse_precision(begin, end, handler);
begin = internal::parse_precision(begin, end, handler);
else
handler.on_error("precision not allowed for this argument type");
}
end = parse_chrono_format(begin, end, detail::chrono_format_checker());
end = parse_chrono_format(begin, end, internal::chrono_format_checker());
return {begin, end};
}
@@ -1084,7 +1083,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
-> decltype(ctx.begin()) {
auto range = do_parse(ctx);
format_str = basic_string_view<Char>(
&*range.begin, detail::to_unsigned(range.end - range.begin));
&*range.begin, internal::to_unsigned(range.end - range.begin));
return range.end;
}
@@ -1095,21 +1094,23 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
// is not specified.
basic_memory_buffer<Char> buf;
auto out = std::back_inserter(buf);
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref,
ctx);
detail::handle_dynamic_spec<detail::precision_checker>(precision,
precision_ref, ctx);
using range = internal::output_range<decltype(ctx.out()), Char>;
internal::basic_writer<range> w(range(ctx.out()));
internal::handle_dynamic_spec<internal::width_checker>(specs.width,
width_ref, ctx);
internal::handle_dynamic_spec<internal::precision_checker>(
precision, precision_ref, ctx);
if (begin == end || *begin == '}') {
out = detail::format_duration_value<Char>(out, d.count(), precision);
detail::format_duration_unit<Char, Period>(out);
out = internal::format_duration_value<Char>(out, d.count(), precision);
internal::format_duration_unit<Char, Period>(out);
} else {
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
ctx, out, d);
f.precision = precision;
parse_chrono_format(begin, end, f);
}
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
w.write(buf.data(), buf.size(), specs);
return w.out();
}
};

View File

@@ -198,7 +198,7 @@ struct rgb {
uint8_t b;
};
namespace detail {
namespace internal {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
@@ -221,7 +221,7 @@ struct color_type {
uint32_t rgb_color;
} value;
};
} // namespace detail
} // namespace internal
// Experimental text formatting support.
class text_style {
@@ -298,11 +298,11 @@ class text_style {
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
FMT_ASSERT(has_background(), "no background specified for this style");
return background_color;
}
@@ -313,7 +313,7 @@ class text_style {
private:
FMT_CONSTEXPR text_style(bool is_foreground,
detail::color_type text_color) FMT_NOEXCEPT
internal::color_type text_color) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems() {
@@ -326,23 +326,23 @@ class text_style {
}
}
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
FMT_NOEXCEPT;
detail::color_type foreground_color;
detail::color_type background_color;
internal::color_type foreground_color;
internal::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/true, foreground);
}
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/false, background);
}
@@ -350,21 +350,21 @@ FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
return text_style(lhs) | rhs;
}
namespace detail {
namespace internal {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color,
const char* esc) FMT_NOEXCEPT {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == detail::data::background_color;
bool is_background = esc == internal::data::background_color;
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
size_t index = 0;
std::size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
@@ -398,7 +398,7 @@ template <typename Char> struct ansi_color_escape {
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
em_codes[3] = 9;
size_t index = 0;
std::size_t index = 0;
for (int i = 0; i < 4; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
@@ -429,14 +429,14 @@ template <typename Char> struct ansi_color_escape {
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
detail::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
internal::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, internal::data::foreground_color);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
detail::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, detail::data::background_color);
internal::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, internal::data::background_color);
}
template <typename Char>
@@ -455,71 +455,71 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
}
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::reset_color, stream);
fputs(internal::data::reset_color, stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::wreset_color, stream);
fputs(internal::data::wreset_color, stream);
}
template <typename Char>
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color;
const char* end = begin + sizeof(data::reset_color) - 1;
buffer.append(begin, end);
}
template <typename Char>
void vformat_to(buffer<Char>& buf, const text_style& ts,
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_format_args<buffer_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
auto emphasis = internal::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
auto foreground =
internal::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background = detail::make_background_color<Char>(ts.get_background());
auto background =
internal::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
detail::vformat_to(buf, format_str, args);
if (has_style) detail::reset_color<Char>(buf);
internal::vformat_to(buf, format_str, args);
if (has_style) internal::reset_color<Char>(buf);
}
} // namespace detail
} // namespace internal
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format), args);
internal::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
detail::fputs(buf.data(), f);
internal::fputs(buf.data(), f);
}
/**
\rst
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
**Example**::
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
FMT_ENABLE_IF(internal::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
vprint(f, ts, format_str,
fmt::make_args_checked<Args...>(format_str, args...));
internal::check_format_string<Args...>(format_str);
using context = buffer_context<char_t<S>>;
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
}
/**
@@ -530,7 +530,7 @@ void print(std::FILE* f, const text_style& ts, const S& format_str,
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
FMT_ENABLE_IF(internal::is_string<S>::value)>
void print(const text_style& ts, const S& format_str, const Args&... args) {
return print(stdout, ts, format_str, args...);
}
@@ -540,7 +540,7 @@ inline std::basic_string<Char> vformat(
const text_style& ts, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format_str), args);
internal::vformat_to(buf, ts, to_string_view(format_str), args);
return fmt::to_string(buf);
}
@@ -560,42 +560,7 @@ template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename OutputIt, typename Char,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
OutputIt vformat_to(
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
detail::is_string<S>::value>
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
internal::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@@ -13,33 +13,7 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
// A compile-time string which is compiled into fast formatting code.
class compiled_string {};
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
/**
\rst
Converts a string literal *s* into a format string that will be parsed at
compile time and converted into efficient formatting code. Requires C++17
``constexpr if`` compiler support.
**Example**::
// Converts 42 into std::string using the most efficient method and no
// runtime format string processing.
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst
*/
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
template <typename T, typename... Tail>
const T& first(const T& value, const Tail&...) {
return value;
}
namespace internal {
// Part of a compiled format string. It can be either literal text or a
// replacement field.
@@ -88,15 +62,13 @@ template <typename Char> struct part_counter {
if (begin != end) ++num_parts;
}
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
return ++num_parts, 0;
}
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; }
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
const Char* end) {
// Find the matching brace.
unsigned brace_counter = 0;
@@ -144,28 +116,25 @@ class format_string_compiler : public error_handler {
handler_(part::make_text({begin, to_unsigned(end - begin)}));
}
FMT_CONSTEXPR int on_arg_id() {
FMT_CONSTEXPR void on_arg_id() {
part_ = part::make_arg_index(parse_context_.next_arg_id());
return 0;
}
FMT_CONSTEXPR int on_arg_id(int id) {
FMT_CONSTEXPR void on_arg_id(int id) {
parse_context_.check_arg_id(id);
part_ = part::make_arg_index(id);
return 0;
}
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
part_ = part::make_arg_name(id);
return 0;
}
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
part_.arg_id_end = ptr;
handler_(part_);
}
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
const Char* end) {
auto repl = typename part::replacement();
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
@@ -191,24 +160,23 @@ FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
format_string_compiler<Char, PartHandler>(format_str, handler));
}
template <typename OutputIt, typename Context, typename Id>
template <typename Range, typename Context, typename Id>
void format_arg(
basic_format_parse_context<typename Context::char_type>& parse_ctx,
basic_format_parse_context<typename Range::value_type>& parse_ctx,
Context& ctx, Id arg_id) {
ctx.advance_to(visit_format_arg(
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
ctx.arg(arg_id)));
ctx.advance_to(
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
}
// vformat_to is defined in a subnamespace to prevent ADL.
namespace cf {
template <typename Context, typename OutputIt, typename CompiledFormat>
auto vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) -> typename Context::iterator {
template <typename Context, typename Range, typename CompiledFormat>
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
-> typename Context::iterator {
using char_type = typename Context::char_type;
basic_format_parse_context<char_type> parse_ctx(
to_string_view(cf.format_str_));
Context ctx(out, args);
Context ctx(out.begin(), args);
const auto& parts = cf.parts();
for (auto part_it = std::begin(parts); part_it != std::end(parts);
@@ -229,12 +197,12 @@ auto vformat_to(OutputIt out, CompiledFormat& cf,
case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
break;
case format_part_t::kind::arg_name:
advance_to(parse_ctx, part.arg_id_end);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
internal::format_arg<Range>(parse_ctx, ctx, value.str);
break;
case format_part_t::kind::replacement: {
@@ -258,9 +226,7 @@ auto vformat_to(OutputIt out, CompiledFormat& cf,
advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to(
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
ctx, nullptr, &specs),
arg));
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
break;
}
}
@@ -274,7 +240,7 @@ struct basic_compiled_format {};
template <typename S, typename = void>
struct compiled_format_base : basic_compiled_format {
using char_type = char_t<S>;
using parts_container = std::vector<detail::format_part<char_type>>;
using parts_container = std::vector<internal::format_part<char_type>>;
parts_container compiled_parts;
@@ -339,7 +305,7 @@ struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const auto compiled_parts =
compile_to_parts<char_type, num_format_parts>(
detail::to_string_view(S()));
internal::to_string_view(S()));
return compiled_parts.data;
}
};
@@ -352,8 +318,8 @@ class compiled_format : private compiled_format_base<S> {
private:
basic_string_view<char_type> format_str_;
template <typename Context, typename OutputIt, typename CompiledFormat>
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
template <typename Context, typename Range, typename CompiledFormat>
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
basic_format_args<Context> args) ->
typename Context::iterator;
@@ -368,8 +334,7 @@ template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
constexpr const auto& get(const T& first, const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
@@ -394,7 +359,8 @@ template <typename Char> struct text {
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data);
// TODO: reserve
return copy_str<Char>(data.begin(), data.end(), out);
}
};
@@ -407,18 +373,32 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename Char, typename OutputIt, typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
OutputIt format_default(OutputIt out, T value) {
// TODO: reserve
format_int fi(value);
return std::copy(fi.data(), fi.data() + fi.size(), out);
}
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value);
}
};
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, double value) {
writer w(out);
w.write(value);
return w.out();
}
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, Char value) {
*out++ = value;
return out;
}
template <typename Char, typename OutputIt>
OutputIt format_default(OutputIt out, const Char* value) {
auto length = std::char_traits<Char>::length(value);
return copy_str<Char>(value, value + length, out);
}
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
@@ -428,32 +408,13 @@ template <typename Char, typename T, int N> struct field {
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
return write<Char>(out, arg);
return format_default<Char>(out, arg);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
mutable formatter<T, Char> fmt;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
const auto& vargs =
make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(arg, ctx);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
@@ -489,8 +450,7 @@ constexpr auto compile_format_string(S format_str);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) {
if constexpr (POS !=
basic_string_view<typename S::char_type>(format_str).size()) {
if constexpr (POS != to_string_view(format_str).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
@@ -502,22 +462,6 @@ constexpr auto parse_tail(T head, S format_str) {
}
}
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int arg_id) {
str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str, {}, arg_id + 1);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + (end - str.data()) + 1, ctx.next_arg_id()};
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
@@ -531,13 +475,12 @@ constexpr auto compile_format_string(S format_str) {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') {
using type = get_type<ID, Args>;
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
format_str);
} else if constexpr (str[POS + 1] == ':') {
using type = get_type<ID, Args>;
constexpr auto result = parse_specs<type>(str, POS + 2, ID);
return parse_tail<Args, result.end, result.next_arg_id>(
spec_field<char_type, type, ID>{result.fmt}, format_str);
if constexpr (std::is_same<type, int>::value) {
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
format_str);
} else {
return unknown_format();
}
} else {
return unknown_format();
}
@@ -547,153 +490,104 @@ constexpr auto compile_format_string(S format_str) {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
}
}
#endif // __cpp_if_constexpr
} // namespace internal
#if FMT_USE_CONSTEXPR
# ifdef __cpp_if_constexpr
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value ||
detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) {
constexpr basic_string_view<typename S::char_type> str = format_str;
if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0);
return internal::make_text(str, 0, 0);
} else {
constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
detail::unknown_format>()) {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
internal::unknown_format>()) {
return internal::compiled_format<S, Args...>(to_string_view(format_str));
} else {
return result;
}
}
}
#else
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
}
#endif // __cpp_if_constexpr
// Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N])
-> detail::compiled_format<const Char*, Args...> {
return detail::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
}
} // namespace detail
// DEPRECATED! use FMT_COMPILE instead.
template <typename... Args>
FMT_DEPRECATED auto compile(const Args&... args)
-> decltype(detail::compile(args...)) {
return detail::compile(args...);
}
#if FMT_USE_CONSTEXPR
# ifdef __cpp_if_constexpr
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
cf.format(detail::buffer_appender<Char>(buffer), args...);
cf.format(std::back_inserter(buffer), args...);
return to_string(buffer);
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
# else
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
return internal::compiled_format<S, Args...>(to_string_view(format_str));
}
# endif // __cpp_if_constexpr
#endif // FMT_USE_CONSTEXPR
// Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N])
-> internal::compiled_format<const Char*, Args...> {
return internal::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
}
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
using range = buffer_range<Char>;
using context = buffer_context<Char>;
detail::cf::vformat_to<context>(detail::buffer_appender<Char>(buffer), cf,
make_format_args<context>(args...));
internal::cf::vformat_to<context>(range(buffer), cf,
make_format_args<context>(args...));
return to_string(buffer);
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
#ifdef __cpp_if_constexpr
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr basic_string_view<typename S::char_type> str = S();
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
return fmt::to_string(detail::first(args...));
}
#endif
constexpr auto compiled = detail::compile<Args...>(S());
return format(compiled, std::forward<Args>(args)...);
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
using char_type = typename CompiledFormat::char_type;
using range = internal::output_range<OutputIt, char_type>;
using context = format_context_t<OutputIt, char_type>;
return detail::cf::vformat_to<context>(out, cf,
make_format_args<context>(args...));
return internal::cf::vformat_to<context>(range(out), cf,
make_format_args<context>(args...));
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
return format_to(out, compiled, args...);
}
template <typename OutputIt, typename CompiledFormat, typename... Args>
auto format_to_n(OutputIt out, size_t n, const CompiledFormat& cf,
const Args&... args) ->
typename std::enable_if<
detail::is_output_iterator<OutputIt,
typename CompiledFormat::char_type>::value &&
std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value,
format_to_n_result<OutputIt>>::type {
auto it =
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
return {it.base(), it.count()};
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, const S&,
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf,
const Args&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), compiled,
args...);
auto it =
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...);
return {it.base(), it.count()};
}
template <typename CompiledFormat, typename... Args>
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(detail::counting_iterator(), cf, args...).count();
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(internal::counting_iterator(), cf, args...).count();
}
FMT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -14,49 +14,63 @@
FMT_BEGIN_NAMESPACE
namespace detail {
namespace internal {
template <typename Char>
typename buffer_context<Char>::iterator vformat_to(
const std::locale& loc, buffer<Char>& buf,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
using range = buffer_range<Char>;
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args,
internal::locale_ref(loc));
}
template <typename Char>
std::basic_string<Char> vformat(
const std::locale& loc, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc));
internal::vformat_to(loc, buffer, format_str, args);
return fmt::to_string(buffer);
}
} // namespace detail
} // namespace internal
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const std::locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
return detail::vformat(loc, to_string_view(format_str), args);
return internal::vformat(loc, to_string_view(format_str), args);
}
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const std::locale& loc,
const S& format_str, Args&&... args) {
return detail::vformat(loc, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
return internal::vformat(
loc, to_string_view(format_str),
internal::make_args_checked<Args...>(format_str, args...));
}
template <typename S, typename OutputIt, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
typename Char = enable_if_t<
internal::is_output_iterator<OutputIt>::value, char_t<S>>>
inline OutputIt vformat_to(
OutputIt out, const std::locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
return detail::get_iterator(buf);
format_args_t<type_identity_t<OutputIt>, Char> args) {
using range = internal::output_range<OutputIt, Char>;
return vformat_to<arg_formatter<range>>(
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
}
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
inline auto format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, loc, to_string_view(format_str), vargs);
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&&
internal::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) {
internal::check_format_string<Args...>(format_str);
using context = format_context_t<OutputIt, char_t<S>>;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),
basic_format_args<context>(as));
}
FMT_END_NAMESPACE

View File

@@ -29,8 +29,7 @@
#if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
#endif
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
#if FMT_HAS_INCLUDE("fcntl.h") && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
@@ -51,7 +50,7 @@
#ifdef FMT_SYSTEM
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
#else
# define FMT_SYSTEM(call) ::call
# define FMT_SYSTEM(call) call
# ifdef _WIN32
// Fix warnings about deprecated symbols.
# define FMT_POSIX_CALL(call) ::_##call
@@ -134,7 +133,7 @@ class error_code {
};
#ifdef _WIN32
namespace detail {
namespace internal {
// A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 {
@@ -157,7 +156,7 @@ class utf16_to_utf8 {
FMT_API void format_windows_error(buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT;
} // namespace detail
} // namespace internal
/** A Windows error. */
class windows_error : public system_error {
@@ -278,9 +277,7 @@ class file {
enum {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMT_POSIX(O_APPEND) // Open in append mode.
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
};
// Constructs a file object which doesn't represent any file.
@@ -316,10 +313,10 @@ class file {
FMT_API long long size() const;
// Attempts to read count bytes from the file into the specified buffer.
FMT_API size_t read(void* buffer, size_t count);
FMT_API std::size_t read(void* buffer, std::size_t count);
// Attempts to write count bytes from the specified buffer to the file.
FMT_API size_t write(const void* buffer, size_t count);
FMT_API std::size_t write(const void* buffer, std::size_t count);
// Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object.
@@ -344,91 +341,6 @@ class file {
// Returns the memory page size.
long getpagesize();
namespace detail {
struct buffer_size {
size_t value = 0;
buffer_size operator=(size_t val) const {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
struct ostream_params {
int oflag = file::WRONLY | file::CREATE;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int oflag) : ostream_params(params...) {
this->oflag = oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
};
} // namespace detail
static constexpr detail::buffer_size buffer_size;
// A fast output stream which is not thread-safe.
class ostream final : private detail::buffer<char> {
private:
file file_;
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
FMT_API void grow(size_t) override final;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
public:
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() {
flush();
file_.close();
}
template <typename S, typename... Args>
void print(const S& format_str, const Args&... args) {
format_to(detail::buffer_appender<char>(*this), format_str, args...);
}
};
/**
Opens a file for writing. Supported parameters passed in `params`:
* ``<integer>``: Output flags (``file::WRONLY | file::CREATE`` by default)
* ``buffer_size=<integer>``: Output buffer size
*/
template <typename... T>
inline ostream output_file(cstring_view path, T... params) {
return {path, detail::ostream_params(params...)};
}
#endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE

View File

@@ -14,10 +14,10 @@
FMT_BEGIN_NAMESPACE
template <typename Char> class basic_printf_parse_context;
template <typename CHar> class basic_printf_parse_context;
template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail {
namespace internal {
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
private:
@@ -49,26 +49,16 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
}
};
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 all operator<< from std::basic_ostream<Char>.
void_t<> operator<<(null<>);
void_t<> operator<<(const Char*);
// 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);
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
!std::is_enum<T>::value)>
void_t<> operator<<(T);
};
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
@@ -90,7 +80,7 @@ template <typename T, typename Char> class is_streamable {
// Write the content of buf to os.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
@@ -111,17 +101,16 @@ void format_value(buffer<Char>& buf, const T& value,
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>());
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.try_resize(buf.size());
output << value;
buf.resize(buf.size());
}
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: private formatter<basic_string_view<Char>, Char> {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto parse(basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin()) {
return formatter<basic_string_view<Char>, Char>::parse(ctx);
}
template <typename ParseCtx,
@@ -147,14 +136,14 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
return std::copy(buffer.begin(), buffer.end(), ctx.out());
}
};
} // namespace detail
} // namespace internal
template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
internal::vformat_to(buffer, format_str, args);
internal::write(os, buffer);
}
/**
@@ -167,10 +156,10 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
vprint(os, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
internal::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@@ -1,2 +1,2 @@
#include "os.h"
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"

View File

@@ -14,7 +14,7 @@
#include "ostream.h"
FMT_BEGIN_NAMESPACE
namespace detail {
namespace internal {
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
@@ -90,11 +90,11 @@ template <typename T, typename Context> class arg_converter {
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
arg_ = detail::make_arg<Context>(
arg_ = internal::make_arg<Context>(
static_cast<int>(static_cast<target_type>(value)));
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
arg_ = detail::make_arg<Context>(
arg_ = internal::make_arg<Context>(
static_cast<unsigned>(static_cast<unsigned_type>(value)));
}
} else {
@@ -102,9 +102,9 @@ template <typename T, typename Context> class arg_converter {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
} else {
arg_ = detail::make_arg<Context>(
arg_ = internal::make_arg<Context>(
static_cast<typename make_unsigned_or_bool<U>::type>(value));
}
}
@@ -133,7 +133,7 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
arg_ = detail::make_arg<Context>(
arg_ = internal::make_arg<Context>(
static_cast<typename Context::char_type>(value));
}
@@ -141,13 +141,6 @@ template <typename Context> class char_converter {
void operator()(T) {} // No conversion needed for non-integral types.
};
// An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise.
template <typename Char> struct get_cstring {
template <typename T> const Char* operator()(T) { return nullptr; }
const Char* operator()(const Char* s) { return s; }
};
// Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative.
template <typename Char> class printf_width_handler {
@@ -162,7 +155,7 @@ template <typename Char> class printf_width_handler {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) {
if (internal::is_negative(value)) {
specs_.align = align::left;
width = 0 - width;
}
@@ -179,20 +172,22 @@ template <typename Char> class printf_width_handler {
};
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(buffer_appender<Char>(buf), format, args).format();
void printf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format();
}
} // namespace detail
// For printing into memory_buffer.
template <typename Char, typename Context>
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
basic_string_view<Char> format,
basic_format_args<Context> args) {
return detail::vprintf(buf, format, args);
template <typename OutputIt, typename Char, typename Context>
internal::truncating_iterator<OutputIt> printf(
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format,
basic_format_args<Context> args) {
return Context(it, format, args).format();
}
using detail::vprintf;
} // namespace internal
using internal::printf; // For printing into memory_buffer.
template <typename Range> class printf_arg_formatter;
template <typename Char>
class basic_printf_parse_context : public basic_format_parse_context<Char> {
@@ -205,15 +200,15 @@ template <typename OutputIt, typename Char> class basic_printf_context;
The ``printf`` argument formatter.
\endrst
*/
template <typename OutputIt, typename Char>
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
template <typename Range>
class printf_arg_formatter : public internal::arg_formatter_base<Range> {
public:
using iterator = OutputIt;
using iterator = typename Range::iterator;
private:
using char_type = Char;
using base = detail::arg_formatter_base<OutputIt, Char>;
using context_type = basic_printf_context<OutputIt, Char>;
using char_type = typename Range::value_type;
using base = internal::arg_formatter_base<Range>;
using context_type = basic_printf_context<iterator, char_type>;
context_type& context_;
@@ -238,9 +233,9 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
\endrst
*/
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
iterator operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and char_type so
// use std::is_same instead.
@@ -255,11 +250,7 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
return (*this)(static_cast<int>(value));
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
fmt_specs.align = align::right;
return base::operator()(value);
} else {
return base::operator()(value);
@@ -325,14 +316,12 @@ template <typename T> struct printf_formatter {
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
detail::format_value(detail::get_container(ctx.out()), value);
internal::format_value(internal::get_container(ctx.out()), value);
return ctx.out();
}
};
/**
This template formats data and writes the output through an output iterator.
*/
/** This template formats data and writes the output to a writer. */
template <typename OutputIt, typename Char> class basic_printf_context {
public:
/** The character type for the output. */
@@ -362,8 +351,9 @@ template <typename OutputIt, typename Char> class basic_printf_context {
public:
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
Constructs a ``printf_context`` object. References to the arguments and
the writer are stored in the context object so make sure they have
appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
@@ -373,7 +363,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
detail::locale_ref locale() { return {}; }
internal::locale_ref locale() { return {}; }
format_arg arg(int id) const { return args_.get(id); }
@@ -384,7 +374,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
}
/** Formats stored arguments and writes the output to the range. */
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
OutputIt format();
};
@@ -404,9 +394,7 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
specs.fill[0] = '0';
break;
case ' ':
if (specs.sign != sign::plus) {
specs.sign = sign::space;
}
specs.sign = sign::space;
break;
case '#':
specs.alt = true;
@@ -424,7 +412,7 @@ basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
arg_index = parse_ctx_.next_arg_id();
else
parse_ctx_.check_arg_id(--arg_index);
return detail::get_arg(*this, arg_index);
return internal::get_arg(*this, arg_index);
}
template <typename OutputIt, typename Char>
@@ -436,7 +424,7 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s).
detail::error_handler eh;
internal::error_handler eh;
int value = parse_nonnegative_int(it, end, eh);
if (it != end && *it == '$') { // value is an argument index
++it;
@@ -455,12 +443,12 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
// Parse width.
if (it != end) {
if (*it >= '0' && *it <= '9') {
detail::error_handler eh;
internal::error_handler eh;
specs.width = parse_nonnegative_int(it, end, eh);
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
detail::printf_width_handler<char_type>(specs), get_arg()));
internal::printf_width_handler<char_type>(specs), get_arg()));
}
}
return arg_index;
@@ -488,52 +476,38 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs);
if (arg_index == 0) on_error("argument not found");
if (arg_index == 0) on_error("argument index out of range");
// Parse precision.
if (it != end && *it == '.') {
++it;
c = it != end ? *it : 0;
if ('0' <= c && c <= '9') {
detail::error_handler eh;
internal::error_handler eh;
specs.precision = parse_nonnegative_int(it, end, eh);
} else if (c == '*') {
++it;
specs.precision = static_cast<int>(
visit_format_arg(detail::printf_precision_handler(), get_arg()));
visit_format_arg(internal::printf_precision_handler(), get_arg()));
} else {
specs.precision = 0;
}
}
format_arg arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral())
specs.fill[0] =
' '; // Ignore '0' flag for non-numeric types or if '-' present.
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
str,
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
}
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
specs.alt = false;
if (specs.fill[0] == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
if (arg.is_arithmetic())
specs.align = align::numeric;
else
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
// flag is also present.
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types.
}
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
char_type t = it != end ? *it : 0;
using detail::convert_arg;
using internal::convert_arg;
switch (c) {
case 'h':
if (t == 'h') {
@@ -557,7 +531,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<size_t>(arg, t);
convert_arg<std::size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
@@ -582,7 +556,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
specs.type = 'd';
break;
case 'c':
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
visit_format_arg(internal::char_converter<basic_printf_context>(arg),
arg);
break;
}
@@ -591,14 +565,15 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
start = it;
// Format argument.
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
visit_format_arg(ArgFormatter(out, specs, *this), arg);
}
return std::copy(start, it, out);
}
template <typename Char>
using basic_printf_context_t =
basic_printf_context<detail::buffer_appender<Char>, Char>;
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>,
Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;
@@ -635,7 +610,7 @@ inline std::basic_string<Char> vsprintf(
const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args);
printf(buffer, to_string_view(format), args);
return to_string(buffer);
}
@@ -649,7 +624,7 @@ inline std::basic_string<Char> vsprintf(
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
using context = basic_printf_context_t<Char>;
return vsprintf(to_string_view(format), make_format_args<context>(args...));
@@ -660,8 +635,8 @@ inline int vfprintf(
std::FILE* f, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args);
size_t size = buffer.size();
printf(buffer, to_string_view(format), args);
std::size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
@@ -677,7 +652,7 @@ inline int vfprintf(
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
using context = basic_printf_context_t<Char>;
return vfprintf(f, to_string_view(format),
@@ -701,7 +676,7 @@ inline int vprintf(
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
FMT_ENABLE_IF(internal::is_string<S>::value)>
inline int printf(const S& format_str, const Args&... args) {
using context = basic_printf_context_t<char_t<S>>;
return vprintf(to_string_view(format_str),
@@ -713,8 +688,8 @@ inline int vfprintf(
std::basic_ostream<Char>& os, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args);
detail::write_buffer(os, buffer);
printf(buffer, to_string_view(format), args);
internal::write(os, buffer);
return static_cast<int>(buffer.size());
}
@@ -723,7 +698,7 @@ template <typename ArgFormatter, typename Char,
typename Context =
basic_printf_context<typename ArgFormatter::iterator, Char>>
typename ArgFormatter::iterator vprintf(
detail::buffer<Char>& out, basic_string_view<Char> format_str,
internal::buffer<Char>& out, basic_string_view<Char> format_str,
basic_format_args<type_identity_t<Context>> args) {
typename ArgFormatter::iterator iter(out);
Context(iter, format_str, args).template format<ArgFormatter>();

View File

@@ -33,7 +33,7 @@ template <typename Char> struct formatting_base {
template <typename Char, typename Enable = void>
struct formatting_range : formatting_base<Char> {
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
// range.
Char prefix;
@@ -54,7 +54,7 @@ struct formatting_tuple : formatting_base<Char> {
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
};
namespace detail {
namespace internal {
template <typename RangeT, typename OutputIterator>
OutputIterator copy(const RangeT& range, OutputIterator out) {
@@ -118,24 +118,26 @@ template <typename T> class is_tuple_like_ {
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
template <typename T, T... N>
using integer_sequence = std::integer_sequence<T, N...>;
template <size_t... N> using index_sequence = std::index_sequence<N...>;
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
template <std::size_t... N> using index_sequence = std::index_sequence<N...>;
template <std::size_t N>
using make_index_sequence = std::make_index_sequence<N>;
#else
template <typename T, T... N> struct integer_sequence {
using value_type = T;
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); }
};
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
template <std::size_t... N>
using index_sequence = integer_sequence<std::size_t, N...>;
template <typename T, size_t N, T... Ns>
template <typename T, std::size_t N, T... Ns>
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
template <typename T, T... Ns>
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
template <size_t N>
using make_index_sequence = make_integer_sequence<size_t, N>;
template <std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
#endif
template <class Tuple, class F, size_t... Is>
@@ -157,9 +159,6 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
}
template <typename Range>
using value_type = remove_cvref_t<decltype(*std::declval<Range>().begin())>;
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
typename std::decay<Arg>::type>::value)>
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
@@ -185,11 +184,12 @@ FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
return add_space ? L" '{}'" : L"'{}'";
}
} // namespace detail
} // namespace internal
template <typename T> struct is_tuple_like {
static FMT_CONSTEXPR_DECL const bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
};
template <typename TupleT, typename Char>
@@ -202,17 +202,17 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
out = detail::copy(formatting.delimiter, out);
out = internal::copy(formatting.delimiter, out);
}
out = format_to(out,
detail::format_str_quoted(
internal::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), v),
v);
++i;
}
formatting_tuple<Char>& formatting;
size_t& i;
std::size_t& i;
typename std::add_lvalue_reference<decltype(
std::declval<FormatContext>().out())>::type out;
};
@@ -228,14 +228,14 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out();
size_t i = 0;
detail::copy(formatting.prefix, out);
std::size_t i = 0;
internal::copy(formatting.prefix, out);
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
detail::copy(formatting.postfix, out);
internal::copy(formatting.postfix, out);
return ctx.out();
}
@@ -243,23 +243,15 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value =
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value &&
internal::is_range_<T>::value &&
!internal::is_like_std_string<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value;
!std::is_constructible<internal::std_string_view<Char>, T>::value;
};
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2017 and earlier.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
&&
(has_formatter<detail::value_type<T>, format_context>::value ||
detail::has_fallback_formatter<detail::value_type<T>,
format_context>::value)
#endif
>> {
template <typename RangeT, typename Char>
struct formatter<RangeT, Char,
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
formatting_range<Char> formatting;
template <typename ParseContext>
@@ -268,18 +260,17 @@ struct formatter<
}
template <typename FormatContext>
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
auto out = detail::copy(formatting.prefix, ctx.out());
size_t i = 0;
auto it = values.begin();
auto end = values.end();
for (; it != end; ++it) {
typename FormatContext::iterator format(const RangeT& values,
FormatContext& ctx) {
auto out = internal::copy(formatting.prefix, ctx.out());
std::size_t i = 0;
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
if (i > 0) {
if (formatting.add_prepostfix_space) *out++ = ' ';
out = detail::copy(formatting.delimiter, out);
out = internal::copy(formatting.delimiter, out);
}
out = format_to(out,
detail::format_str_quoted(
internal::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), *it),
*it);
if (++i > formatting.range_length_limit) {
@@ -288,11 +279,11 @@ struct formatter<
}
}
if (formatting.add_prepostfix_space) *out++ = ' ';
return detail::copy(formatting.postfix, out);
return internal::copy(formatting.postfix, out);
}
};
template <typename Char, typename... T> struct tuple_arg_join : detail::view {
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
@@ -310,14 +301,14 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
template <typename FormatContext>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
}
private:
template <typename FormatContext, size_t... N>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
detail::index_sequence<N...>) {
internal::index_sequence<N...>) {
return format_args(value, ctx, std::get<N>(value.tuple)...);
}
@@ -380,14 +371,14 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
\endrst
*/
template <typename T>
arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
string_view sep) {
arg_join<internal::iterator_t<const std::initializer_list<T>>, char> join(
std::initializer_list<T> list, string_view sep) {
return join(std::begin(list), std::end(list), sep);
}
template <typename T>
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
wstring_view sep) {
arg_join<internal::iterator_t<const std::initializer_list<T>>, wchar_t> join(
std::initializer_list<T> list, wstring_view sep) {
return join(std::begin(list), std::end(list), sep);
}

View File

@@ -8,12 +8,12 @@
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
namespace detail {
namespace internal {
template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) {
#ifdef FMT_FUZZ
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (precision > 100000)
throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf");
@@ -23,77 +23,154 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
struct sprintf_specs {
int precision;
char type;
bool alt : 1;
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
FMT_NOEXCEPT;
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
FMT_NOEXCEPT;
template <typename Char>
constexpr sprintf_specs(basic_format_specs<Char> specs)
: precision(specs.precision), type(specs.type), alt(specs.alt) {}
// DEPRECATED! This function exists for ABI compatibility.
template <typename Char>
typename basic_format_context<std::back_insert_iterator<buffer<Char>>,
Char>::iterator
vformat_to(buffer<Char>& buf, basic_string_view<Char> format_str,
basic_format_args<basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
type_identity_t<Char>>>
args) {
using iterator = std::back_insert_iterator<buffer<char>>;
using context = basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
type_identity_t<Char>>;
auto out = iterator(buf);
format_handler<iterator, Char, context> h(out, format_str, args, {});
parse_format_string<false>(format_str, h);
return out;
constexpr bool has_precision() const { return precision >= 0; }
};
// This is deprecated and is kept only to preserve ABI compatibility.
template <typename Double>
char* sprintf_format(Double value, internal::buffer<char>& buf,
sprintf_specs specs) {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buf.capacity() != 0, "empty buffer");
// Build format string.
enum { max_format_size = 10 }; // longest format: %#-*.*Lg
char format[max_format_size];
char* format_ptr = format;
*format_ptr++ = '%';
if (specs.alt || !specs.type) *format_ptr++ = '#';
if (specs.precision >= 0) {
*format_ptr++ = '.';
*format_ptr++ = '*';
}
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L';
char type = specs.type;
if (type == '%')
type = 'f';
else if (type == 0 || type == 'n')
type = 'g';
#if FMT_MSC_VER
if (type == 'F') {
// MSVC's printf doesn't support 'F'.
type = 'f';
}
#endif
*format_ptr++ = type;
*format_ptr = '\0';
// Format using snprintf.
char* start = nullptr;
char* decimal_point_pos = nullptr;
for (;;) {
std::size_t buffer_size = buf.capacity();
start = &buf[0];
int result =
format_float(start, buffer_size, format, specs.precision, value);
if (result >= 0) {
unsigned n = internal::to_unsigned(result);
if (n < buf.capacity()) {
// Find the decimal point.
auto p = buf.data(), end = p + n;
if (*p == '+' || *p == '-') ++p;
if (specs.type != 'a' && specs.type != 'A') {
while (p < end && *p >= '0' && *p <= '9') ++p;
if (p < end && *p != 'e' && *p != 'E') {
decimal_point_pos = p;
if (!specs.type) {
// Keep only one trailing zero after the decimal point.
++p;
if (*p == '0') ++p;
while (p != end && *p >= '1' && *p <= '9') ++p;
char* where = p;
while (p != end && *p == '0') ++p;
if (p == end || *p < '0' || *p > '9') {
if (p != end) std::memmove(where, p, to_unsigned(end - p));
n -= static_cast<unsigned>(p - where);
}
}
}
}
buf.resize(n);
break; // The buffer is large enough - continue with formatting.
}
buf.reserve(n + 1);
} else {
// If result is negative we ask to increase the capacity by at least 1,
// but as std::vector, the buffer grows exponentially.
buf.reserve(buf.capacity() + 1);
}
}
return decimal_point_pos;
}
template basic_format_context<std::back_insert_iterator<buffer<char>>,
char>::iterator
vformat_to(buffer<char>&, string_view,
basic_format_args<basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<char>>>,
type_identity_t<char>>>);
} // namespace detail
} // namespace internal
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
sprintf_specs);
template FMT_API char* internal::sprintf_format(long double,
internal::buffer<char>&,
sprintf_specs);
template struct FMT_INSTANTIATION_DEF_API internal::basic_data<void>;
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, detail::float_specs,
detail::buffer<char>&) = detail::format_float;
int (*instantiate_format_float)(double, int, internal::float_specs,
internal::buffer<char>&) =
internal::format_float;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
#endif
// Explicit instantiations for char.
template FMT_API std::string detail::grouping_impl<char>(locale_ref);
template FMT_API char detail::thousands_sep_impl(locale_ref);
template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API std::string internal::grouping_impl<char>(locale_ref);
template FMT_API char internal::thousands_sep_impl(locale_ref);
template FMT_API char internal::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<char>::append(const char*, const char*);
template FMT_API void internal::buffer<char>::append(const char*, const char*);
template FMT_API void detail::vformat_to(
detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
template FMT_API void internal::arg_map<format_context>::init(
const basic_format_args<format_context>& args);
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::snprintf_float(long double, int,
detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::format_float(double, int, detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::format_float(long double, int, detail::float_specs,
detail::buffer<char>&);
template FMT_API std::string internal::vformat<char>(
string_view, basic_format_args<format_context>);
template FMT_API format_context::iterator internal::vformat_to(
internal::buffer<char>&, string_view, basic_format_args<format_context>);
template FMT_API int internal::snprintf_float(double, int,
internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::snprintf_float(long double, int,
internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(double, int, internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(long double, int,
internal::float_specs,
internal::buffer<char>&);
// Explicit instantiations for wchar_t.
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
const wchar_t*);
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
const wchar_t*);
template FMT_API std::wstring internal::vformat<wchar_t>(
wstring_view, basic_format_args<wformat_context>);
FMT_END_NAMESPACE

View File

@@ -62,7 +62,7 @@ using RWResult = int;
inline unsigned convert_rwcount(std::size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
}
#elif FMT_USE_FCNTL
#else
// Return type of read and write functions.
using RWResult = ssize_t;
@@ -73,14 +73,14 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
FMT_BEGIN_NAMESPACE
#ifdef _WIN32
detail::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
if (int error_code = convert(s)) {
FMT_THROW(windows_error(error_code,
"cannot convert string from UTF-16 to UTF-8"));
}
}
int detail::utf16_to_utf8::convert(wstring_view s) {
int internal::utf16_to_utf8::convert(wstring_view s) {
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size());
if (s_size == 0) {
@@ -105,13 +105,13 @@ void windows_error::init(int err_code, string_view format_str,
format_args args) {
error_code_ = err_code;
memory_buffer buffer;
detail::format_windows_error(buffer, err_code, vformat(format_str, args));
internal::format_windows_error(buffer, err_code, vformat(format_str, args));
std::runtime_error& base = *this;
base = std::runtime_error(to_string(buffer));
}
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT {
void internal::format_windows_error(internal::buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT {
FMT_TRY {
wmemory_buffer buf;
buf.resize(inline_buffer_size);
@@ -124,8 +124,10 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
if (result != 0) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
format_to(buffer_appender<char>(out), "{}: {}", message,
utf8_message);
internal::writer w(out);
w.write(message);
w.write(": ");
w.write(utf8_message);
return;
}
break;
@@ -141,7 +143,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
void report_windows_error(int error_code,
fmt::string_view message) FMT_NOEXCEPT {
report_error(detail::format_windows_error, error_code, message);
report_error(internal::format_windows_error, error_code, message);
}
#endif // _WIN32
@@ -232,14 +234,14 @@ std::size_t file::read(void* buffer, std::size_t count) {
RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
return detail::to_unsigned(result);
return internal::to_unsigned(result);
}
std::size_t file::write(const void* buffer, std::size_t count) {
RWResult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
return detail::to_unsigned(result);
return internal::to_unsigned(result);
}
file file::dup(int fd) {
@@ -289,12 +291,8 @@ void file::pipe(file& read_end, file& write_end) {
}
buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR.
# if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode);
# else
// Don't retry as fdopen doesn't return EINTR.
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));
@@ -314,9 +312,5 @@ long getpagesize() {
return size;
# endif
}
FMT_API void ostream::grow(size_t) {
if (this->size() == this->capacity()) flush();
}
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

View File

@@ -2,3 +2,5 @@ This directory contains build support files such as
* CMake modules
* Build scripts
* qmake (static build with dynamic libc only)

1
support/Vagrantfile vendored
View File

@@ -4,7 +4,6 @@
# A vagrant config for testing against gcc-4.8.
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"
config.disksize.size = '15GB'
config.vm.provider "virtualbox" do |vb|
vb.memory = "4096"

View File

@@ -1,26 +0,0 @@
# This module provides function for joining paths
# known from from most languages
#
# Original license:
# SPDX-License-Identifier: (MIT OR CC0-1.0)
# Explicit permission given to distribute this module under
# the terms of the project as described in /LICENSE.rst.
# Copyright 2020 Jan Tojnar
# https://github.com/jtojnar/cmake-snips
#
# Modelled after Pythons os.path.join
# https://docs.python.org/3.7/library/os.path.html#os.path.join
# Windows not supported
function(join_paths joined_path first_path_segment)
set(temp_path "${first_path_segment}")
foreach(current_segment IN LISTS ARGN)
if(NOT ("${current_segment}" STREQUAL ""))
if(IS_ABSOLUTE "${current_segment}")
set(temp_path "${current_segment}")
else()
set(temp_path "${temp_path}/${current_segment}")
endif()
endif()
endforeach()
set(${joined_path} "${temp_path}" PARENT_SCOPE)
endfunction()

View File

@@ -1,7 +1,7 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=@libdir_for_pc_file@
includedir=@includedir_for_pc_file@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: fmt
Description: A modern formatting library

27
support/fmt.pro Normal file
View File

@@ -0,0 +1,27 @@
# Staticlib configuration for qmake builds
# For some reason qmake 3.1 fails to identify source dependencies and excludes format.cc and printf.cc
# from compilation so it _MUST_ be called as qmake -nodepend
# A workaround is implemented below: a custom compiler is defined which does not track dependencies
TEMPLATE = lib
TARGET = fmt
QMAKE_EXT_CPP = .cc
CONFIG = staticlib warn_on c++11
FMT_SOURCES = \
../src/format.cc \
../src/posix.cc
fmt.name = libfmt
fmt.input = FMT_SOURCES
fmt.output = ${QMAKE_FILE_BASE}$$QMAKE_EXT_OBJ
fmt.clean = ${QMAKE_FILE_BASE}$$QMAKE_EXT_OBJ
fmt.depends = ${QMAKE_FILE_IN}
# QMAKE_RUN_CXX will not be expanded
fmt.commands = $$QMAKE_CXX -c $$QMAKE_CXXFLAGS $$QMAKE_CXXFLAGS_WARN_ON $$QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO $$QMAKE_CXXFLAGS_CXX11 ${QMAKE_FILE_IN}
fmt.variable_out = OBJECTS
fmt.CONFIG = no_dependencies no_link
QMAKE_EXTRA_COMPILERS += fmt

View File

@@ -144,33 +144,9 @@ def update_site(env):
b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data)
b.data = b.data.replace('std::FILE*', 'std::FILE *')
b.data = b.data.replace('unsigned int', 'unsigned')
#b.data = b.data.replace('operator""_', 'operator"" _')
b.data = b.data.replace(
'format_to_n(OutputIt, size_t, string_view, Args&&',
'format_to_n(OutputIt, size_t, const S&, const Args&')
b.data = b.data.replace(
'format_to_n(OutputIt, std::size_t, string_view, Args&&',
'format_to_n(OutputIt, std::size_t, const S&, const Args&')
if version == ('3.0.2'):
b.data = b.data.replace(
'fprintf(std::ostream&', 'fprintf(std::ostream &')
if version == ('5.3.0'):
b.data = b.data.replace(
'format_to(OutputIt, const S&, const Args&...)',
'format_to(OutputIt, const S &, const Args &...)')
if version.startswith('5.') or version.startswith('6.'):
b.data = b.data.replace(', size_t', ', std::size_t')
if version.startswith('7.'):
b.data = b.data.replace(', std::size_t', ', size_t')
b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
b.data = b.data.replace('operator""_', 'operator"" _')
b.data = b.data.replace(', size_t', ', std::size_t')
b.data = b.data.replace('aa long', 'a long')
b.data = b.data.replace('serveral', 'several')
if version.startswith('6.2.'):
b.data = b.data.replace(
'vformat(const S&, basic_format_args<' +
'buffer_context<Char>>)',
'vformat(const S&, basic_format_args<' +
'buffer_context<type_identity_t<Char>>>)')
# Fix a broken link in index.rst.
index = os.path.join(target_doc_dir, 'index.rst')
with rewrite(index) as b:

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python
# Update the coverity branch from the master branch.
# It is not done automatically because Coverity Scan limits
# the number of submissions per day.
from __future__ import print_function
import shutil, tempfile
from subprocess import check_output, STDOUT
class Git:
def __init__(self, dir):
self.dir = dir
def __call__(self, *args):
output = check_output(['git'] + list(args), cwd=self.dir, stderr=STDOUT)
print(output)
return output
dir = tempfile.mkdtemp()
try:
git = Git(dir)
git('clone', '-b', 'coverity', 'git@github.com:fmtlib/fmt.git', dir)
output = git('merge', '-X', 'theirs', '--no-commit', 'origin/master')
if 'Fast-forward' not in output:
git('reset', 'HEAD', '.travis.yml')
git('checkout', '--', '.travis.yml')
git('commit', '-m', 'Update coverity branch')
git('push')
finally:
shutil.rmtree(dir)

View File

@@ -91,6 +91,8 @@ add_fmt_test(assert-test)
add_fmt_test(chrono-test)
add_fmt_test(color-test)
add_fmt_test(core-test)
add_fmt_test(grisu-test)
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
add_fmt_test(gtest-extra-test)
add_fmt_test(format-test mock-allocator.h)
if (MSVC)
@@ -103,21 +105,11 @@ add_fmt_test(locale-test)
add_fmt_test(ostream-test)
add_fmt_test(compile-test)
add_fmt_test(printf-test)
add_fmt_test(custom-formatter-test)
add_fmt_test(ranges-test)
add_fmt_test(scan-test)
if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
foreach (flag_var
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if (${flag_var} MATCHES "^(/|-)(MT|MTd)")
set(MSVC_STATIC_RUNTIME ON)
break()
endif()
endforeach()
endif()
if (NOT MSVC_STATIC_RUNTIME)
if (NOT MSVC_BUILD_STATIC)
add_fmt_executable(posix-mock-test
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
target_include_directories(

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.1.0)
project(fmt-test)

View File

@@ -1,8 +1,4 @@
// Formatting library for C++ - FMT_ASSERT test
//
// It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks
// which are slow on some platforms. In other tests FMT_ASSERT is made to throw
// an exception which is much faster and easier to check.
// Formatting library for C++ - assertion tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.

View File

@@ -10,11 +10,10 @@
#endif
#include "fmt/chrono.h"
#include "gtest-extra.h"
#include <iomanip>
#include "gtest-extra.h"
std::tm make_tm() {
auto time = std::tm();
time.tm_mday = 1;
@@ -95,17 +94,6 @@ TEST(TimeTest, GMTime) {
EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t)));
}
TEST(TimeTest, TimePoint) {
std::chrono::system_clock::time_point point = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(point);
std::tm tm = *std::localtime(&t);
char strftime_output[256];
std::strftime(strftime_output, sizeof(strftime_output), "It is %Y-%m-%d %H:%M:%S", &tm);
EXPECT_EQ(strftime_output, fmt::format("It is {:%Y-%m-%d %H:%M:%S}", point));
}
#define EXPECT_TIME(spec, time, duration) \
{ \
std::locale loc("ja_JP.utf8"); \

View File

@@ -6,11 +6,6 @@
// For the license information refer to format.h.
#include "fmt/color.h"
#include <iterator>
#include <string>
#include <utility>
#include "gtest-extra.h"
TEST(ColorsTest, ColorsPrint) {
@@ -88,12 +83,3 @@ TEST(ColorsTest, Format) {
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
"\x1b[31mfoo\x1b[0m");
}
TEST(ColorsTest, FormatToOutAcceptsTextStyle) {
fmt::text_style ts = fg(fmt::rgb(255, 20, 30));
std::string out;
fmt::format_to(std::back_inserter(out), ts, "rgb(255,20,30){}{}{}", 1, 2, 3);
EXPECT_EQ(fmt::to_string(out),
"\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m");
}

View File

@@ -1,6 +1,6 @@
# Test if compile errors are produced where necessary.
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.1.0)
include(CheckCXXSourceCompiles)
include(CheckCXXCompilerFlag)

View File

@@ -5,10 +5,18 @@
//
// For the license information refer to format.h.
#include <stdint.h>
#include <cctype>
#include <cfloat>
#include <climits>
#include <cmath>
#include <cstring>
#include <deque>
#include <list>
#include <memory>
#include <string>
#include <type_traits>
// Check that fmt/compile.h compiles with windows.h included before it.
// Check if fmt/compile.h compiles with windows.h included before it.
#ifdef _WIN32
# include <windows.h>
#endif
@@ -16,15 +24,23 @@
#include "fmt/compile.h"
#include "gmock.h"
#include "gtest-extra.h"
#include "mock-allocator.h"
#include "util.h"
#undef ERROR
#undef min
#undef max
using testing::Return;
using testing::StrictMock;
// compiletime_prepared_parts_type_provider is useful only with relaxed
// constexpr.
#if FMT_USE_CONSTEXPR
template <unsigned EXPECTED_PARTS_COUNT, typename Format>
void check_prepared_parts_type(Format format) {
typedef fmt::detail::compiled_format_base<decltype(format)> provider;
typedef fmt::detail::format_part<char>
typedef fmt::internal::compiled_format_base<decltype(format)> provider;
typedef fmt::internal::format_part<char>
expected_parts_type[EXPECTED_PARTS_COUNT];
static_assert(std::is_same<typename provider::parts_container,
expected_parts_type>::value,
@@ -50,37 +66,46 @@ TEST(CompileTest, CompileTimePreparedPartsTypeProvider) {
#endif
TEST(CompileTest, PassStringLiteralFormat) {
const auto prepared = fmt::detail::compile<int>("test {}");
const auto prepared = fmt::compile<int>("test {}");
EXPECT_EQ("test 42", fmt::format(prepared, 42));
const auto wprepared = fmt::detail::compile<int>(L"test {}");
const auto wprepared = fmt::compile<int>(L"test {}");
EXPECT_EQ(L"test 42", fmt::format(wprepared, 42));
}
#if FMT_USE_CONSTEXPR
TEST(CompileTest, PassCompileString) {
const auto prepared = fmt::compile<int>(FMT_STRING("test {}"));
EXPECT_EQ("test 42", fmt::format(prepared, 42));
const auto wprepared = fmt::compile<int>(FMT_STRING(L"test {}"));
EXPECT_EQ(L"test 42", fmt::format(wprepared, 42));
}
#endif
TEST(CompileTest, FormatToArrayOfChars) {
char buffer[32] = {0};
const auto prepared = fmt::detail::compile<int>("4{}");
fmt::format_to(fmt::detail::make_checked(buffer, 32), prepared, 2);
const auto prepared = fmt::compile<int>("4{}");
fmt::format_to(fmt::internal::make_checked(buffer, 32), prepared, 2);
EXPECT_EQ(std::string("42"), buffer);
wchar_t wbuffer[32] = {0};
const auto wprepared = fmt::detail::compile<int>(L"4{}");
fmt::format_to(fmt::detail::make_checked(wbuffer, 32), wprepared, 2);
const auto wprepared = fmt::compile<int>(L"4{}");
fmt::format_to(fmt::internal::make_checked(wbuffer, 32), wprepared, 2);
EXPECT_EQ(std::wstring(L"42"), wbuffer);
}
TEST(CompileTest, FormatToIterator) {
std::string s(2, ' ');
const auto prepared = fmt::detail::compile<int>("4{}");
const auto prepared = fmt::compile<int>("4{}");
fmt::format_to(s.begin(), prepared, 2);
EXPECT_EQ("42", s);
std::wstring ws(2, L' ');
const auto wprepared = fmt::detail::compile<int>(L"4{}");
const auto wprepared = fmt::compile<int>(L"4{}");
fmt::format_to(ws.begin(), wprepared, 2);
EXPECT_EQ(L"42", ws);
}
TEST(CompileTest, FormatToN) {
char buf[5];
auto f = fmt::detail::compile<int>("{:10}");
auto f = fmt::compile<int>("{:10}");
auto result = fmt::format_to_n(buf, 5, f, 42);
EXPECT_EQ(result.size, 10);
EXPECT_EQ(result.out, buf + 5);
@@ -88,86 +113,31 @@ TEST(CompileTest, FormatToN) {
}
TEST(CompileTest, FormattedSize) {
auto f = fmt::detail::compile<int>("{:10}");
auto f = fmt::compile<int>("{:10}");
EXPECT_EQ(fmt::formatted_size(f, 42), 10);
}
TEST(CompileTest, MultipleTypes) {
auto f = fmt::detail::compile<int, int>("{} {}");
auto f = fmt::compile<int, int>("{} {}");
EXPECT_EQ(fmt::format(f, 42, 42), "42 42");
}
struct test_formattable {};
struct formattable {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<test_formattable> : formatter<const char*> {
template <typename FormatContext>
auto format(test_formattable, FormatContext& ctx) -> decltype(ctx.out()) {
template <> struct formatter<formattable> : formatter<const char*> {
auto format(formattable, format_context& ctx) -> decltype(ctx.out()) {
return formatter<const char*>::format("foo", ctx);
}
};
FMT_END_NAMESPACE
TEST(CompileTest, FormatUserDefinedType) {
auto f = fmt::detail::compile<test_formattable>("{}");
EXPECT_EQ(fmt::format(f, test_formattable()), "foo");
auto f = fmt::compile<formattable>("{}");
EXPECT_EQ(fmt::format(f, formattable()), "foo");
}
TEST(CompileTest, EmptyFormatString) {
auto f = fmt::detail::compile<>("");
auto f = fmt::compile<>("");
EXPECT_EQ(fmt::format(f), "");
}
#ifdef __cpp_if_constexpr
TEST(CompileTest, FormatDefault) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u));
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll));
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ull));
EXPECT_EQ("true", fmt::format(FMT_COMPILE("{}"), true));
EXPECT_EQ("x", fmt::format(FMT_COMPILE("{}"), 'x'));
EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo"));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo")));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable()));
}
TEST(CompileTest, FormatWideString) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(CompileTest, FormatSpecs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
}
TEST(CompileTest, DynamicWidth) {
EXPECT_EQ(" 42foo ",
fmt::format(FMT_COMPILE("{:{}}{:{}}"), 42, 4, "foo", 5));
}
TEST(CompileTest, FormatTo) {
char buf[8];
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
*end = '\0';
EXPECT_STREQ("42", buf);
end = fmt::format_to(buf, FMT_COMPILE("{:x}"), 42);
*end = '\0';
EXPECT_STREQ("2a", buf);
}
TEST(CompileTest, FormatToNWithCompileMacro) {
constexpr auto buffer_size = 8;
char buffer[buffer_size];
auto res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{}"), 42);
*res.out = '\0';
EXPECT_STREQ("42", buffer);
res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{:x}"), 42);
*res.out = '\0';
EXPECT_STREQ("2a", buffer);
}
TEST(CompileTest, TextAndArg) {
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
}
#endif

View File

@@ -30,17 +30,22 @@
using fmt::basic_format_arg;
using fmt::string_view;
using fmt::detail::buffer;
using fmt::detail::make_arg;
using fmt::detail::value;
using fmt::internal::buffer;
using fmt::internal::value;
using testing::_;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;
namespace {
struct test_struct {};
template <typename Context, typename T>
basic_format_arg<Context> make_arg(const T& value) {
return fmt::internal::make_arg<Context>(value);
}
} // namespace
FMT_BEGIN_NAMESPACE
template <typename Char> struct formatter<test_struct, Char> {
template <typename ParseContext>
@@ -48,7 +53,10 @@ template <typename Char> struct formatter<test_struct, Char> {
return ctx.begin();
}
auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) {
typedef std::back_insert_iterator<buffer<Char>> iterator;
auto format(test_struct, basic_format_context<iterator, char>& ctx)
-> decltype(ctx.out()) {
const Char* test = "test";
return std::copy_n(test, std::strlen(test), ctx.out());
}
@@ -73,22 +81,22 @@ TEST(BufferTest, Nonmoveable) {
}
#endif
TEST(BufferTest, Indestructible) {
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
"buffer's destructor is protected");
}
// A test buffer with a dummy grow method.
template <typename T> struct test_buffer : buffer<T> {
void grow(std::size_t capacity) { this->set(nullptr, capacity); }
};
template <typename T> struct mock_buffer final : buffer<T> {
MOCK_METHOD1(do_grow, size_t(size_t capacity));
template <typename T> struct mock_buffer : buffer<T> {
MOCK_METHOD1(do_grow, void(std::size_t capacity));
void grow(size_t capacity) { this->set(this->data(), do_grow(capacity)); }
mock_buffer(T* data = nullptr, size_t capacity = 0) {
this->set(data, capacity);
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
return capacity;
}));
void grow(std::size_t capacity) {
this->set(this->data(), capacity);
do_grow(capacity);
}
mock_buffer() {}
mock_buffer(T* data) { this->set(data, 0); }
mock_buffer(T* data, std::size_t capacity) { this->set(data, capacity); }
};
TEST(BufferTest, Ctor) {
@@ -107,7 +115,7 @@ TEST(BufferTest, Ctor) {
}
{
int dummy;
size_t capacity = std::numeric_limits<size_t>::max();
std::size_t capacity = std::numeric_limits<std::size_t>::max();
mock_buffer<int> buffer(&dummy, capacity);
EXPECT_EQ(&dummy, &buffer[0]);
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
@@ -115,6 +123,24 @@ TEST(BufferTest, Ctor) {
}
}
struct dying_buffer : test_buffer<int> {
MOCK_METHOD0(die, void());
~dying_buffer() { die(); }
private:
virtual void avoid_weak_vtable();
};
void dying_buffer::avoid_weak_vtable() {}
TEST(BufferTest, VirtualDtor) {
typedef StrictMock<dying_buffer> stict_mock_buffer;
stict_mock_buffer* mock_buffer = new stict_mock_buffer();
EXPECT_CALL(*mock_buffer, die());
buffer<int>* buffer = mock_buffer;
delete buffer;
}
TEST(BufferTest, Access) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
@@ -122,44 +148,34 @@ TEST(BufferTest, Access) {
EXPECT_EQ(11, buffer[0]);
buffer[3] = 42;
EXPECT_EQ(42, *(&buffer[0] + 3));
const fmt::detail::buffer<char>& const_buffer = buffer;
const fmt::internal::buffer<char>& const_buffer = buffer;
EXPECT_EQ(42, const_buffer[3]);
}
TEST(BufferTest, TryResize) {
TEST(BufferTest, Resize) {
char data[123];
mock_buffer<char> buffer(data, sizeof(data));
buffer[10] = 42;
EXPECT_EQ(42, buffer[10]);
buffer.try_resize(20);
buffer.resize(20);
EXPECT_EQ(20u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
EXPECT_EQ(42, buffer[10]);
buffer.try_resize(5);
buffer.resize(5);
EXPECT_EQ(5u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
EXPECT_EQ(42, buffer[10]);
// Check if try_resize calls grow.
// Check if resize calls grow.
EXPECT_CALL(buffer, do_grow(124));
buffer.try_resize(124);
buffer.resize(124);
EXPECT_CALL(buffer, do_grow(200));
buffer.try_resize(200);
}
TEST(BufferTest, TryResizePartial) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15));
buffer.try_resize(20);
EXPECT_EQ(buffer.capacity(), 15);
EXPECT_EQ(buffer.size(), 15);
buffer.resize(200);
}
TEST(BufferTest, Clear) {
mock_buffer<char> buffer;
EXPECT_CALL(buffer, do_grow(20));
buffer.try_resize(20);
buffer.try_resize(0);
test_buffer<char> buffer;
buffer.resize(20);
buffer.resize(0);
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(20u, buffer.capacity());
}
@@ -167,11 +183,11 @@ TEST(BufferTest, Clear) {
TEST(BufferTest, Append) {
char data[15];
mock_buffer<char> buffer(data, 10);
auto test = "test";
const char* test = "test";
buffer.append(test, test + 5);
EXPECT_STREQ(test, &buffer[0]);
EXPECT_EQ(5u, buffer.size());
buffer.try_resize(10);
buffer.resize(10);
EXPECT_CALL(buffer, do_grow(12));
buffer.append(test, test + 2);
EXPECT_EQ('t', buffer[10]);
@@ -179,31 +195,17 @@ TEST(BufferTest, Append) {
EXPECT_EQ(12u, buffer.size());
}
TEST(BufferTest, AppendPartial) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
testing::InSequence seq;
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10));
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) {
EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789");
buffer.clear();
return 10;
}));
auto test = "0123456789abcde";
buffer.append(test, test + 15);
}
TEST(BufferTest, AppendAllocatesEnoughStorage) {
char data[19];
mock_buffer<char> buffer(data, 10);
auto test = "abcdefgh";
buffer.try_resize(10);
const char* test = "abcdefgh";
buffer.resize(10);
EXPECT_CALL(buffer, do_grow(19));
buffer.append(test, test + 9);
}
TEST(ArgTest, FormatArgs) {
auto args = fmt::format_args();
fmt::format_args args;
EXPECT_FALSE(args.get(1));
}
@@ -231,21 +233,21 @@ struct custom_context {
};
TEST(ArgTest, MakeValueWithCustomContext) {
auto t = test_struct();
fmt::detail::value<custom_context> arg(
fmt::detail::arg_mapper<custom_context>().map(t));
test_struct t;
fmt::internal::value<custom_context> arg(
fmt::internal::arg_mapper<custom_context>().map(t));
custom_context ctx = {false, fmt::format_parse_context("")};
arg.custom.format(&t, ctx.parse_context(), ctx);
EXPECT_TRUE(ctx.called);
}
FMT_BEGIN_NAMESPACE
namespace detail {
namespace internal {
template <typename Char>
bool operator==(custom_value<Char> lhs, custom_value<Char> rhs) {
return lhs.value == rhs.value;
}
} // namespace detail
} // namespace internal
FMT_END_NAMESPACE
// Use a unique result type to make sure that there are no undesirable
@@ -253,10 +255,10 @@ FMT_END_NAMESPACE
struct test_result {};
template <typename T> struct mock_visitor {
template <typename U> struct result { using type = test_result; };
template <typename U> struct result { typedef test_result type; };
mock_visitor() {
ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
ON_CALL(*this, visit(_)).WillByDefault(testing::Return(test_result()));
}
MOCK_METHOD1_T(visit, test_result(T value));
@@ -270,10 +272,10 @@ template <typename T> struct mock_visitor {
}
};
template <typename T> struct visit_type { using type = T; };
template <typename T> struct visit_type { typedef T Type; };
#define VISIT_TYPE(type_, visit_type_) \
template <> struct visit_type<type_> { using type = visit_type_; }
#define VISIT_TYPE(Type_, visit_type_) \
template <> struct visit_type<Type_> { typedef visit_type_ Type; }
VISIT_TYPE(signed char, int);
VISIT_TYPE(unsigned char, unsigned);
@@ -292,34 +294,36 @@ VISIT_TYPE(unsigned long, unsigned long long);
{ \
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
EXPECT_CALL(visitor, visit(expected)); \
using iterator = std::back_insert_iterator<buffer<Char>>; \
typedef std::back_insert_iterator<buffer<Char>> iterator; \
fmt::visit_format_arg( \
visitor, make_arg<fmt::basic_format_context<iterator, Char>>(value)); \
}
#define CHECK_ARG(value, typename_) \
{ \
using value_type = decltype(value); \
typename_ visit_type<value_type>::type expected = value; \
typedef decltype(value) value_type; \
typename_ visit_type<value_type>::Type expected = value; \
CHECK_ARG_(char, expected, value) \
CHECK_ARG_(wchar_t, expected, value) \
}
template <typename T> class NumericArgTest : public testing::Test {};
using types =
::testing::Types<bool, signed char, unsigned char, signed, unsigned short,
int, unsigned, long, unsigned long, long long,
unsigned long long, float, double, long double>;
TYPED_TEST_CASE(NumericArgTest, types);
typedef ::testing::Types<bool, signed char, unsigned char, signed,
unsigned short, int, unsigned, long, unsigned long,
long long, unsigned long long, float, double,
long double>
Types;
TYPED_TEST_CASE(NumericArgTest, Types);
template <typename T>
fmt::enable_if_t<std::is_integral<T>::value, T> test_value() {
typename std::enable_if<std::is_integral<T>::value, T>::type test_value() {
return static_cast<T>(42);
}
template <typename T>
fmt::enable_if_t<std::is_floating_point<T>::value, T> test_value() {
typename std::enable_if<std::is_floating_point<T>::value, T>::type
test_value() {
return static_cast<T>(4.2);
}
@@ -341,7 +345,7 @@ TEST(ArgTest, StringArg) {
const char* cstr = str;
CHECK_ARG_(char, cstr, str);
auto sref = string_view(str);
string_view sref(str);
CHECK_ARG_(char, sref, std::string(str));
}
@@ -368,14 +372,14 @@ TEST(ArgTest, PointerArg) {
struct check_custom {
test_result operator()(
fmt::basic_format_arg<fmt::format_context>::handle h) const {
struct test_buffer final : fmt::detail::buffer<char> {
struct test_buffer : fmt::internal::buffer<char> {
char data[10];
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
void grow(size_t) {}
test_buffer() : fmt::internal::buffer<char>(data, 0, 10) {}
void grow(std::size_t) {}
} buffer;
fmt::internal::buffer<char>& base = buffer;
fmt::format_parse_context parse_ctx("");
fmt::format_context ctx{fmt::detail::buffer_appender<char>(buffer),
fmt::format_args()};
fmt::format_context ctx(std::back_inserter(base), fmt::format_args());
h.format(parse_ctx, ctx);
EXPECT_EQ("test", std::string(buffer.data, buffer.size()));
return test_result();
@@ -384,10 +388,10 @@ struct check_custom {
TEST(ArgTest, CustomArg) {
test_struct test;
using visitor =
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
typedef mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>
visitor;
testing::StrictMock<visitor> v;
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
EXPECT_CALL(v, visit(_)).WillOnce(testing::Invoke(check_custom()));
fmt::visit_format_arg(v, make_arg<fmt::format_context>(test));
}
@@ -403,7 +407,9 @@ TEST(FormatDynArgsTest, Basic) {
store.push_back(42);
store.push_back("abc1");
store.push_back(1.5f);
EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store));
std::string result = fmt::vformat("{} and {} and {}", store);
EXPECT_EQ("42 and abc1 and 1.5", result);
}
TEST(FormatDynArgsTest, StringsAndRefs) {
@@ -445,25 +451,11 @@ TEST(FormatDynArgsTest, CustomFormat) {
++c.i;
store.push_back(std::cref(c));
++c.i;
std::string result = fmt::vformat("{} and {} and {}", store);
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
}
TEST(FormatDynArgsTest, NamedInt) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("a1", 42));
EXPECT_EQ("42", fmt::vformat("{a1}", store));
}
TEST(FormatDynArgsTest, NamedStrings) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
char str[]{"1234567890"};
store.push_back(fmt::arg("a1", str));
store.push_back(fmt::arg("a2", std::cref(str)));
str[0] = 'X';
EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store));
}
TEST(FormatDynArgsTest, NamedArgByRef) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
@@ -478,51 +470,12 @@ TEST(FormatDynArgsTest, NamedArgByRef) {
// storages.
int a1_val{42};
auto a1 = fmt::arg("a1_", a1_val);
store.push_back("abc");
store.push_back(1.5f);
store.push_back(std::cref(a1));
std::string result = fmt::vformat("{a1_} and {} and {} and {}", store);
EXPECT_EQ("42 and abc and 1.5 and 42", result);
}
std::string result = fmt::vformat("{a1_}", // and {} and {}",
store);
TEST(FormatDynArgsTest, NamedCustomFormat) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
custom_type c{};
store.push_back(fmt::arg("c1", c));
++c.i;
store.push_back(fmt::arg("c2", c));
++c.i;
store.push_back(fmt::arg("c_ref", std::cref(c)));
++c.i;
std::string result = fmt::vformat("{c1} and {c2} and {c_ref}", store);
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
}
TEST(FormatDynArgsTest, Clear) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42);
std::string result = fmt::vformat("{}", store);
EXPECT_EQ("42", result);
store.push_back(43);
result = fmt::vformat("{} and {}", store);
EXPECT_EQ("42 and 43", result);
store.clear();
store.push_back(44);
result = fmt::vformat("{}", store);
EXPECT_EQ("44", result);
}
TEST(FormatDynArgsTest, Reserve) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.reserve(2, 1);
store.push_back(1.5f);
store.push_back(fmt::arg("a1", 42));
std::string result = fmt::vformat("{a1} and {}", store);
EXPECT_EQ("42 and 1.5", result);
}
struct copy_throwable {
@@ -565,9 +518,9 @@ TEST(StringViewTest, Length) {
// Check string_view's comparison operator.
template <template <typename> class Op> void check_op() {
const char* inputs[] = {"foo", "fop", "fo"};
size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
for (size_t i = 0; i < num_inputs; ++i) {
for (size_t j = 0; j < num_inputs; ++j) {
std::size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
for (std::size_t i = 0; i < num_inputs; ++i) {
for (std::size_t j = 0; j < num_inputs; ++j) {
string_view lhs(inputs[i]), rhs(inputs[j]);
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs));
}
@@ -650,14 +603,14 @@ TEST(CoreTest, FormatterOverridesImplicitConversion) {
namespace my_ns {
template <typename Char> class my_string {
private:
std::basic_string<Char> s_;
public:
my_string(const Char* s) : s_(s) {}
const Char* data() const FMT_NOEXCEPT { return s_.data(); }
size_t length() const FMT_NOEXCEPT { return s_.size(); }
std::size_t length() const FMT_NOEXCEPT { return s_.size(); }
operator const Char*() const { return s_.c_str(); }
private:
std::basic_string<Char> s_;
};
template <typename Char>
@@ -669,6 +622,23 @@ inline fmt::basic_string_view<Char> to_string_view(const my_string<Char>& s)
struct non_string {};
} // namespace my_ns
namespace FakeQt {
class QString {
public:
QString(const wchar_t* s) : s_(std::make_shared<std::wstring>(s)) {}
const wchar_t* utf16() const FMT_NOEXCEPT { return s_->data(); }
int size() const FMT_NOEXCEPT { return static_cast<int>(s_->size()); }
private:
std::shared_ptr<std::wstring> s_;
};
inline fmt::basic_string_view<wchar_t> to_string_view(const QString& s)
FMT_NOEXCEPT {
return {s.utf16(), static_cast<std::size_t>(s.size())};
}
} // namespace FakeQt
template <typename T> class IsStringTest : public testing::Test {};
typedef ::testing::Types<char, wchar_t, char16_t, char32_t> StringCharTypes;
@@ -680,19 +650,21 @@ struct derived_from_string_view : fmt::basic_string_view<Char> {};
} // namespace
TYPED_TEST(IsStringTest, IsString) {
EXPECT_TRUE(fmt::detail::is_string<TypeParam*>::value);
EXPECT_TRUE(fmt::detail::is_string<const TypeParam*>::value);
EXPECT_TRUE(fmt::detail::is_string<TypeParam[2]>::value);
EXPECT_TRUE(fmt::detail::is_string<const TypeParam[2]>::value);
EXPECT_TRUE(fmt::detail::is_string<std::basic_string<TypeParam>>::value);
EXPECT_TRUE(fmt::detail::is_string<fmt::basic_string_view<TypeParam>>::value);
EXPECT_TRUE(fmt::internal::is_string<TypeParam*>::value);
EXPECT_TRUE(fmt::internal::is_string<const TypeParam*>::value);
EXPECT_TRUE(fmt::internal::is_string<TypeParam[2]>::value);
EXPECT_TRUE(fmt::internal::is_string<const TypeParam[2]>::value);
EXPECT_TRUE(fmt::internal::is_string<std::basic_string<TypeParam>>::value);
EXPECT_TRUE(
fmt::detail::is_string<derived_from_string_view<TypeParam>>::value);
using string_view = fmt::detail::std_string_view<TypeParam>;
fmt::internal::is_string<fmt::basic_string_view<TypeParam>>::value);
EXPECT_TRUE(
fmt::internal::is_string<derived_from_string_view<TypeParam>>::value);
using string_view = fmt::internal::std_string_view<TypeParam>;
EXPECT_TRUE(std::is_empty<string_view>::value !=
fmt::detail::is_string<string_view>::value);
EXPECT_TRUE(fmt::detail::is_string<my_ns::my_string<TypeParam>>::value);
EXPECT_FALSE(fmt::detail::is_string<my_ns::non_string>::value);
fmt::internal::is_string<string_view>::value);
EXPECT_TRUE(fmt::internal::is_string<my_ns::my_string<TypeParam>>::value);
EXPECT_FALSE(fmt::internal::is_string<my_ns::non_string>::value);
EXPECT_TRUE(fmt::internal::is_string<FakeQt::QString>::value);
}
TEST(CoreTest, Format) {
@@ -715,16 +687,33 @@ TEST(CoreTest, FormatTo) {
TEST(CoreTest, ToStringViewForeignStrings) {
using namespace my_ns;
using namespace FakeQt;
EXPECT_EQ(to_string_view(my_string<char>("42")), "42");
fmt::detail::type type =
fmt::detail::mapped_type_constant<my_string<char>,
fmt::format_context>::value;
EXPECT_EQ(type, fmt::detail::type::string_type);
EXPECT_EQ(to_string_view(my_string<wchar_t>(L"42")), L"42");
EXPECT_EQ(to_string_view(QString(L"42")), L"42");
fmt::internal::type type =
fmt::internal::mapped_type_constant<my_string<char>,
fmt::format_context>::value;
EXPECT_EQ(type, fmt::internal::type::string_type);
type = fmt::internal::mapped_type_constant<my_string<wchar_t>,
fmt::wformat_context>::value;
EXPECT_EQ(type, fmt::internal::type::string_type);
type =
fmt::internal::mapped_type_constant<QString, fmt::wformat_context>::value;
EXPECT_EQ(type, fmt::internal::type::string_type);
// Does not compile: only wide format contexts are compatible with QString!
// type = fmt::internal::mapped_type_constant<QString,
// fmt::format_context>::value;
}
TEST(CoreTest, FormatForeignStrings) {
using namespace my_ns;
using namespace FakeQt;
EXPECT_EQ(fmt::format(my_string<char>("{}"), 42), "42");
EXPECT_EQ(fmt::format(my_string<wchar_t>(L"{}"), 42), L"42");
EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
EXPECT_EQ(fmt::format(QString(L"{}"), my_string<wchar_t>(L"42")), L"42");
EXPECT_EQ(fmt::format(my_string<wchar_t>(L"{}"), QString(L"42")), L"42");
}
struct implicitly_convertible_to_string {
@@ -735,7 +724,7 @@ struct implicitly_convertible_to_string_view {
operator fmt::string_view() const { return "foo"; }
};
TEST(CoreTest, FormatImplicitlyConvertibleToStringView) {
TEST(FormatterTest, FormatImplicitlyConvertibleToStringView) {
EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view()));
}
@@ -745,7 +734,7 @@ struct explicitly_convertible_to_string_view {
explicit operator fmt::string_view() const { return "foo"; }
};
TEST(CoreTest, FormatExplicitlyConvertibleToStringView) {
TEST(FormatterTest, FormatExplicitlyConvertibleToStringView) {
EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_view()));
}
@@ -754,11 +743,20 @@ struct explicitly_convertible_to_std_string_view {
explicit operator std::string_view() const { return "foo"; }
};
TEST(CoreTest, FormatExplicitlyConvertibleToStdStringView) {
TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
EXPECT_EQ("foo",
fmt::format("{}", explicitly_convertible_to_std_string_view()));
}
# endif
struct explicitly_convertible_to_wstring_view {
explicit operator fmt::wstring_view() const { return L"foo"; }
};
TEST(FormatterTest, FormatExplicitlyConvertibleToWStringView) {
EXPECT_EQ(L"foo",
fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
}
#endif
struct disabled_rvalue_conversion {
@@ -768,6 +766,6 @@ struct disabled_rvalue_conversion {
operator const char*() && = delete;
};
TEST(CoreTest, DisabledRValueConversion) {
TEST(FormatterTest, DisabledRValueConversion) {
EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
}

View File

@@ -0,0 +1,56 @@
// Formatting library for C++ - custom argument formatter tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "fmt/format.h"
#include "gtest-extra.h"
// MSVC 2013 is known to be broken.
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
// A custom argument formatter that doesn't print `-` for floating-point values
// rounded to 0.
class custom_arg_formatter
: public fmt::arg_formatter<fmt::buffer_range<char>> {
public:
using range = fmt::buffer_range<char>;
typedef fmt::arg_formatter<range> base;
custom_arg_formatter(fmt::format_context& ctx,
fmt::format_parse_context* parse_ctx,
fmt::format_specs* s = nullptr)
: base(ctx, parse_ctx, s) {}
using base::operator();
iterator operator()(double value) {
// Comparing a float to 0.0 is safe.
if (round(value * pow(10, specs()->precision)) == 0.0) value = 0;
return base::operator()(value);
}
};
std::string custom_vformat(fmt::string_view format_str, fmt::format_args args) {
fmt::memory_buffer buffer;
// Pass custom argument formatter as a template arg to vwrite.
fmt::vformat_to<custom_arg_formatter>(buffer, format_str, args);
return std::string(buffer.data(), buffer.size());
}
template <typename... Args>
std::string custom_format(const char* format_str, const Args&... args) {
auto va = fmt::make_format_args(args...);
return custom_vformat(format_str, va);
}
TEST(CustomFormatterTest, Format) {
EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001));
}
#endif

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.1.0)
project(fmt-test)

View File

@@ -38,9 +38,9 @@ namespace std {
template<class Out, class charT> class basic_format_context;
using format_context = basic_format_context<
/* unspecified */ fmt::detail::buffer_appender<char>, char>;
/* unspecified */ std::back_insert_iterator<fmt::internal::buffer<char>>, char>;
using wformat_context = basic_format_context<
/* unspecified */ fmt::detail::buffer_appender<wchar_t>, wchar_t>;
/* unspecified */ std::back_insert_iterator<fmt::internal::buffer<wchar_t>>, wchar_t>;
template<class T, class charT = char> struct formatter {
formatter() = delete;
@@ -219,7 +219,7 @@ namespace std {
// Implementation details:
using format_arg = basic_format_arg<basic_format_context>;
basic_format_context(Out out, basic_format_args<basic_format_context> args, fmt::detail::locale_ref)
basic_format_context(Out out, basic_format_args<basic_format_context> args, fmt::internal::locale_ref)
: args_(args), out_(out) {}
detail::error_handler error_handler() const { return {}; }
basic_format_arg<basic_format_context> arg(fmt::basic_string_view<charT>) const {
@@ -488,21 +488,22 @@ template<class... Args>
namespace std {
namespace detail {
template <typename OutputIt, typename Char>
template <typename Range>
class arg_formatter
: public fmt::detail::arg_formatter_base<OutputIt, Char, error_handler> {
: public fmt::internal::arg_formatter_base<Range, error_handler> {
private:
using char_type = Char;
using base = fmt::detail::arg_formatter_base<OutputIt, Char, error_handler>;
using format_context = std::basic_format_context<OutputIt, Char>;
using parse_context = basic_format_parse_context<Char>;
using char_type = typename Range::value_type;
using base = fmt::internal::arg_formatter_base<Range, error_handler>;
using format_context = std::basic_format_context<typename base::iterator, char_type>;
using parse_context = basic_format_parse_context<char_type>;
parse_context* parse_ctx_;
format_context& ctx_;
public:
using iterator = OutputIt;
using format_specs = typename base::format_specs;
typedef Range range;
typedef typename base::iterator iterator;
typedef typename base::format_specs format_specs;
/**
\rst
@@ -512,7 +513,7 @@ class arg_formatter
\endrst
*/
arg_formatter(format_context& ctx, parse_context* parse_ctx = nullptr, fmt::format_specs* spec = nullptr)
: base(ctx.out(), spec, {}), parse_ctx_(parse_ctx), ctx_(ctx) {}
: base(Range(ctx.out()), spec, {}), parse_ctx_(parse_ctx), ctx_(ctx) {}
using base::operator();
@@ -528,36 +529,36 @@ class arg_formatter
};
template <typename Context>
inline fmt::detail::type get_type(basic_format_arg<Context> arg) {
inline fmt::internal::type get_type(basic_format_arg<Context> arg) {
return visit_format_arg([&] (auto val) {
using char_type = typename Context::char_type;
using T = decltype(val);
if (std::is_same_v<T, monostate>)
return fmt::detail::type::none_type;
return fmt::internal::type::none_type;
if (std::is_same_v<T, bool>)
return fmt::detail::type::bool_type;
return fmt::internal::type::bool_type;
if (std::is_same_v<T, char_type>)
return fmt::detail::type::char_type;
return fmt::internal::type::char_type;
if (std::is_same_v<T, int>)
return fmt::detail::type::int_type;
return fmt::internal::type::int_type;
if (std::is_same_v<T, unsigned int>)
return fmt::detail::type::uint_type;
return fmt::internal::type::uint_type;
if (std::is_same_v<T, long long int>)
return fmt::detail::type::long_long_type;
return fmt::internal::type::long_long_type;
if (std::is_same_v<T, unsigned long long int>)
return fmt::detail::type::ulong_long_type;
return fmt::internal::type::ulong_long_type;
if (std::is_same_v<T, double>)
return fmt::detail::type::double_type;
return fmt::internal::type::double_type;
if (std::is_same_v<T, long double>)
return fmt::detail::type::long_double_type;
return fmt::internal::type::long_double_type;
if (std::is_same_v<T, const char_type*>)
return fmt::detail::type::cstring_type;
return fmt::internal::type::cstring_type;
if (std::is_same_v<T, basic_string_view<char_type>>)
return fmt::detail::type::string_type;
return fmt::internal::type::string_type;
if (std::is_same_v<T, const void*>)
return fmt::detail::type::pointer_type;
return fmt::internal::type::pointer_type;
assert(get_value(arg).index() == 12);
return fmt::detail::type::custom_type;
return fmt::internal::type::custom_type;
}, arg);
}
@@ -581,42 +582,45 @@ class custom_formatter {
template <typename ArgFormatter, typename Char, typename Context>
struct format_handler : detail::error_handler {
using iterator = typename ArgFormatter::iterator;
typedef typename ArgFormatter::range range;
format_handler(iterator out, basic_string_view<Char> str,
format_handler(range r, basic_string_view<Char> str,
basic_format_args<Context> format_args,
fmt::detail::locale_ref loc)
: parse_ctx(str), context(out, format_args, loc) {}
fmt::internal::locale_ref loc)
: parse_ctx(str), context(r.begin(), format_args, loc) {}
void on_text(const Char* begin, const Char* end) {
auto size = fmt::detail::to_unsigned(end - begin);
auto size = fmt::internal::to_unsigned(end - begin);
auto out = context.out();
auto&& it = fmt::detail::reserve(out, size);
auto&& it = fmt::internal::reserve(out, size);
it = std::copy_n(begin, size, it);
context.advance_to(out);
}
int on_arg_id() { return parse_ctx.next_arg_id(); }
int on_arg_id(unsigned id) { return parse_ctx.check_arg_id(id), id; }
int on_arg_id(fmt::basic_string_view<Char>) { return 0; }
void on_arg_id() {
arg = context.arg(parse_ctx.next_arg_id());
}
void on_arg_id(unsigned id) {
parse_ctx.check_arg_id(id);
arg = context.arg(id);
}
void on_arg_id(fmt::basic_string_view<Char>) {}
void on_replacement_field(int id, const Char* p) {
auto arg = context.arg(id);
parse_ctx.advance_to(parse_ctx.begin() + (p - &*parse_ctx.begin()));
void on_replacement_field(const Char* p) {
parse_ctx.advance_to(parse_ctx.begin() + (p - &*parse_ctx.begin()));
custom_formatter<Context> f(parse_ctx, context);
if (!visit_format_arg(f, arg))
context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx), arg));
}
const Char* on_format_specs(int id, const Char* begin, const Char* end) {
auto arg = context.arg(id);
const Char* on_format_specs(const Char* begin, const Char* end) {
parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin()));
custom_formatter<Context> f(parse_ctx, context);
if (visit_format_arg(f, arg)) return &*parse_ctx.begin();
fmt::basic_format_specs<Char> specs;
using fmt::detail::specs_handler;
using fmt::internal::specs_handler;
using parse_context = basic_format_parse_context<Char>;
fmt::detail::specs_checker<specs_handler<parse_context, Context>> handler(
fmt::internal::specs_checker<specs_handler<parse_context, Context>> handler(
specs_handler<parse_context, Context>(specs, parse_ctx, context), get_type(arg));
begin = parse_format_specs(begin, end, handler);
if (begin == end || *begin != '}') on_error("missing '}' in format string");
@@ -627,6 +631,7 @@ struct format_handler : detail::error_handler {
basic_format_parse_context<Char> parse_ctx;
Context context;
basic_format_arg<Context> arg;
};
template <typename T, typename Char>
@@ -635,45 +640,46 @@ struct formatter {
// terminating '}'.
template <typename ParseContext>
FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext& ctx) {
namespace detail = fmt::detail;
typedef detail::dynamic_specs_handler<ParseContext> handler_type;
auto type = detail::mapped_type_constant<T, fmt::buffer_context<Char>>::value;
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
namespace internal = fmt::internal;
typedef internal::dynamic_specs_handler<ParseContext> handler_type;
auto type = internal::mapped_type_constant<T, fmt::buffer_context<Char>>::value;
internal::specs_checker<handler_type> handler(handler_type(specs_, ctx),
type);
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
auto type_spec = specs_.type;
auto eh = ctx.error_handler();
switch (type) {
case detail::type::none_type:
case internal::type::none_type:
case internal::type::named_arg_type:
FMT_ASSERT(false, "invalid argument type");
break;
case detail::type::int_type:
case detail::type::uint_type:
case detail::type::long_long_type:
case detail::type::ulong_long_type:
case detail::type::bool_type:
case internal::type::int_type:
case internal::type::uint_type:
case internal::type::long_long_type:
case internal::type::ulong_long_type:
case internal::type::bool_type:
handle_int_type_spec(type_spec,
detail::int_type_checker<decltype(eh)>(eh));
internal::int_type_checker<decltype(eh)>(eh));
break;
case detail::type::char_type:
case internal::type::char_type:
handle_char_specs(
&specs_, detail::char_specs_checker<decltype(eh)>(type_spec, eh));
&specs_, internal::char_specs_checker<decltype(eh)>(type_spec, eh));
break;
case detail::type::double_type:
case detail::type::long_double_type:
detail::parse_float_type_spec(specs_, eh);
case internal::type::double_type:
case internal::type::long_double_type:
internal::parse_float_type_spec(specs_, eh);
break;
case detail::type::cstring_type:
detail::handle_cstring_type_spec(
type_spec, detail::cstring_type_checker<decltype(eh)>(eh));
case internal::type::cstring_type:
internal::handle_cstring_type_spec(
type_spec, internal::cstring_type_checker<decltype(eh)>(eh));
break;
case detail::type::string_type:
detail::check_string_type_spec(type_spec, eh);
case internal::type::string_type:
internal::check_string_type_spec(type_spec, eh);
break;
case detail::type::pointer_type:
detail::check_pointer_type_spec(type_spec, eh);
case internal::type::pointer_type:
internal::check_pointer_type_spec(type_spec, eh);
break;
case detail::type::custom_type:
case internal::type::custom_type:
// Custom format specifiers should be checked in parse functions of
// formatter specializations.
break;
@@ -683,18 +689,18 @@ struct formatter {
template <typename FormatContext>
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
fmt::detail::handle_dynamic_spec<fmt::detail::width_checker>(
fmt::internal::handle_dynamic_spec<fmt::internal::width_checker>(
specs_.width, specs_.width_ref, ctx);
fmt::detail::handle_dynamic_spec<fmt::detail::precision_checker>(
fmt::internal::handle_dynamic_spec<fmt::internal::precision_checker>(
specs_.precision, specs_.precision_ref, ctx);
using af = arg_formatter<typename FormatContext::iterator,
typename FormatContext::char_type>;
return visit_format_arg(af(ctx, nullptr, &specs_),
using range_type = fmt::internal::output_range<typename FormatContext::iterator,
typename FormatContext::char_type>;
return visit_format_arg(arg_formatter<range_type>(ctx, nullptr, &specs_),
basic_format_arg<FormatContext>(val));
}
private:
fmt::detail::dynamic_format_specs<Char> specs_;
fmt::internal::dynamic_format_specs<Char> specs_;
};
} // namespace detail
@@ -711,11 +717,11 @@ template<class... Args>
string vformat(string_view fmt, format_args args) {
fmt::memory_buffer mbuf;
fmt::detail::buffer<char>& buf = mbuf;
using af = detail::arg_formatter<fmt::format_context::iterator, char>;
detail::format_handler<af, char, format_context>
h(fmt::detail::buffer_appender<char>(buf), fmt, args, {});
fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
fmt::internal::buffer<char>& buf = mbuf;
using range = fmt::buffer_range<char>;
detail::format_handler<detail::arg_formatter<range>, char, format_context>
h(range(std::back_inserter(buf)), fmt, args, {});
fmt::internal::parse_format_string<false>(fmt::to_string_view(fmt), h);
return to_string(mbuf);
}
@@ -735,10 +741,10 @@ template<class Out, class... Args>
template<class Out>
Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args) {
using af = detail::arg_formatter<Out, char>;
detail::format_handler<af, char, basic_format_context<Out, char>>
h(out, fmt, args, {});
fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
using range = fmt::internal::output_range<Out, char>;
detail::format_handler<detail::arg_formatter<range>, char, basic_format_context<Out, char>>
h(range(out), fmt, args, {});
fmt::internal::parse_format_string<false>(fmt::to_string_view(fmt), h);
return h.context.out();
}

View File

@@ -0,0 +1,6 @@
// Copyright (c) 2020 Vladimir Solontsov
// SPDX-License-Identifier: MIT Licence
#include <fmt/core.h>
#include "gtest-extra.h"

View File

@@ -19,14 +19,11 @@
#include "gtest-extra.h"
#include "util.h"
#ifdef _WIN32
# include <windows.h>
# undef max
#endif
#undef max
using fmt::detail::bigint;
using fmt::detail::fp;
using fmt::detail::max_value;
using fmt::internal::bigint;
using fmt::internal::fp;
using fmt::internal::max_value;
static_assert(!std::is_copy_constructible<bigint>::value, "");
static_assert(!std::is_copy_assignable<bigint>::value, "");
@@ -105,7 +102,7 @@ TEST(BigIntTest, Multiply) {
}
TEST(BigIntTest, Accumulator) {
fmt::detail::accumulator acc;
fmt::internal::accumulator acc;
EXPECT_EQ(acc.lower, 0);
EXPECT_EQ(acc.upper, 0);
acc.upper = 12;
@@ -113,7 +110,7 @@ TEST(BigIntTest, Accumulator) {
EXPECT_EQ(static_cast<uint32_t>(acc), 34);
acc += 56;
EXPECT_EQ(acc.lower, 90);
acc += fmt::detail::max_value<uint64_t>();
acc += fmt::internal::max_value<uint64_t>();
EXPECT_EQ(acc.upper, 13);
EXPECT_EQ(acc.lower, 89);
acc >>= 32;
@@ -189,6 +186,29 @@ template <bool is_iec559> void run_double_tests() {
template <> void run_double_tests<true>() {
// Construct from double.
EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52));
// Compute boundaries:
fp value;
// Normalized & not power of 2 - equidistant boundaries:
auto b = value.assign_with_boundaries(1.23);
EXPECT_EQ(value, fp(0x0013ae147ae147ae, -52));
EXPECT_EQ(b.lower, 0x9d70a3d70a3d6c00);
EXPECT_EQ(b.upper, 0x9d70a3d70a3d7400);
// Normalized power of 2 - lower boundary is closer:
b = value.assign_with_boundaries(1.9807040628566084e+28); // 2**94
EXPECT_EQ(value, fp(0x0010000000000000, 42));
EXPECT_EQ(b.lower, 0x7ffffffffffffe00);
EXPECT_EQ(b.upper, 0x8000000000000400);
// Smallest normalized double - equidistant boundaries:
b = value.assign_with_boundaries(2.2250738585072014e-308);
EXPECT_EQ(value, fp(0x0010000000000000, -1074));
EXPECT_EQ(b.lower, 0x7ffffffffffffc00);
EXPECT_EQ(b.upper, 0x8000000000000400);
// Subnormal - equidistant boundaries:
b = value.assign_with_boundaries(4.9406564584124654e-324);
EXPECT_EQ(value, fp(0x0000000000000001, -1074));
EXPECT_EQ(b.lower, 0x4000000000000000);
EXPECT_EQ(b.upper, 0xc000000000000000);
}
TEST(FPTest, DoubleTests) {
@@ -202,6 +222,33 @@ TEST(FPTest, Normalize) {
EXPECT_EQ(-6, normalized.e);
}
TEST(FPTest, ComputeFloatBoundaries) {
struct {
double x, lower, upper;
} tests[] = {
// regular
{1.5f, 1.4999999403953552, 1.5000000596046448},
// boundary
{1.0f, 0.9999999701976776, 1.0000000596046448},
// min normal
{1.1754944e-38f, 1.1754942807573643e-38, 1.1754944208872107e-38},
// max subnormal
{1.1754942e-38f, 1.1754941406275179e-38, 1.1754942807573643e-38},
// min subnormal
{1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45},
};
for (auto test : tests) {
fp vlower = normalize(fp(test.lower));
fp vupper = normalize(fp(test.upper));
vlower.f >>= vupper.e - vlower.e;
vlower.e = vupper.e;
fp value;
auto b = value.assign_float_with_boundaries(test.x);
EXPECT_EQ(vlower.f, b.lower);
EXPECT_EQ(vupper.f, b.upper);
}
}
TEST(FPTest, Multiply) {
auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7);
EXPECT_EQ(v.f, 123u * 56u);
@@ -212,58 +259,20 @@ TEST(FPTest, Multiply) {
}
TEST(FPTest, GetCachedPower) {
using limits = std::numeric_limits<double>;
typedef std::numeric_limits<double> limits;
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
int dec_exp = 0;
auto fp = fmt::detail::get_cached_power(exp, dec_exp);
bigint exact, cache(fp.f);
if (dec_exp >= 0) {
exact.assign_pow10(dec_exp);
if (fp.e <= 0)
exact <<= -fp.e;
else
cache <<= fp.e;
exact.align(cache);
cache.align(exact);
auto exact_str = fmt::format("{}", exact);
auto cache_str = fmt::format("{}", cache);
EXPECT_EQ(exact_str.size(), cache_str.size());
EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15));
int diff = cache_str[15] - exact_str[15];
if (diff == 1)
EXPECT_GT(exact_str[16], '8');
else
EXPECT_EQ(diff, 0);
} else {
cache.assign_pow10(-dec_exp);
cache *= fp.f + 1; // Inexact check.
exact.assign(1);
exact <<= -fp.e;
exact.align(cache);
auto exact_str = fmt::format("{}", exact);
auto cache_str = fmt::format("{}", cache);
EXPECT_EQ(exact_str.size(), cache_str.size());
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
}
auto fp = fmt::internal::get_cached_power(exp, dec_exp);
EXPECT_LE(exp, fp.e);
int dec_exp_step = 8;
EXPECT_LE(fp.e, exp + dec_exp_step * log2(10));
EXPECT_DOUBLE_EQ(pow(10, dec_exp), ldexp(static_cast<double>(fp.f), fp.e));
}
}
TEST(FPTest, DragonboxMaxK) {
using fmt::detail::dragonbox::floor_log10_pow2;
using float_info = fmt::detail::dragonbox::float_info<float>;
EXPECT_EQ(fmt::detail::const_check(float_info::max_k),
float_info::kappa - floor_log10_pow2(float_info::min_exponent -
float_info::significand_bits));
using double_info = fmt::detail::dragonbox::float_info<double>;
EXPECT_EQ(
fmt::detail::const_check(double_info::max_k),
double_info::kappa - floor_log10_pow2(double_info::min_exponent -
double_info::significand_bits));
}
TEST(FPTest, GetRoundDirection) {
using fmt::detail::get_round_direction;
using fmt::detail::round_direction;
using fmt::internal::get_round_direction;
using fmt::internal::round_direction;
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
EXPECT_EQ(round_direction::up, get_round_direction(100, 51, 0));
EXPECT_EQ(round_direction::down, get_round_direction(100, 40, 10));
@@ -286,9 +295,9 @@ TEST(FPTest, GetRoundDirection) {
}
TEST(FPTest, FixedHandler) {
struct handler : fmt::detail::fixed_handler {
struct handler : fmt::internal::fixed_handler {
char buffer[10];
handler(int prec = 0) : fmt::detail::fixed_handler() {
handler(int prec = 0) : fmt::internal::fixed_handler() {
buf = buffer;
precision = prec;
}
@@ -297,8 +306,8 @@ TEST(FPTest, FixedHandler) {
handler().on_digit('0', 100, 99, 0, exp, false);
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false),
assertion_failure);
namespace digits = fmt::detail::digits;
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error);
namespace digits = fmt::internal::digits;
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::done);
// Check that divisor - error doesn't overflow.
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
// Check that 2 * error doesn't overflow.
@@ -309,7 +318,7 @@ TEST(FPTest, FixedHandler) {
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
fmt::memory_buffer buf;
format_float(0.42, -1, fmt::detail::float_specs(), buf);
format_float(0.42, -1, fmt::internal::float_specs(), buf);
}
template <typename T> struct value_extractor {
@@ -321,11 +330,11 @@ template <typename T> struct value_extractor {
#if FMT_USE_INT128
// Apple Clang does not define typeid for __int128_t and __uint128_t.
FMT_NORETURN T operator()(fmt::detail::int128_t) {
FMT_NORETURN T operator()(fmt::internal::int128_t) {
throw std::runtime_error("invalid type __int128_t");
}
FMT_NORETURN T operator()(fmt::detail::uint128_t) {
FMT_NORETURN T operator()(fmt::internal::uint128_t) {
throw std::runtime_error("invalid type __uint128_t");
}
#endif
@@ -333,19 +342,27 @@ template <typename T> struct value_extractor {
TEST(FormatTest, ArgConverter) {
long long value = max_value<long long>();
auto arg = fmt::detail::make_arg<fmt::format_context>(value);
auto arg = fmt::internal::make_arg<fmt::format_context>(value);
fmt::visit_format_arg(
fmt::detail::arg_converter<long long, fmt::format_context>(arg, 'd'),
fmt::internal::arg_converter<long long, fmt::format_context>(arg, 'd'),
arg);
EXPECT_EQ(value, fmt::visit_format_arg(value_extractor<long long>(), arg));
}
TEST(FormatTest, FormatNegativeNaN) {
double nan = std::numeric_limits<double>::quiet_NaN();
if (std::signbit(-nan))
EXPECT_EQ("-nan", fmt::format("{}", -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
}
TEST(FormatTest, StrError) {
char* message = nullptr;
char buffer[BUFFER_SIZE];
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = nullptr, 0),
EXPECT_ASSERT(fmt::internal::safe_strerror(EDOM, message = nullptr, 0),
"invalid buffer");
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = buffer, 0),
EXPECT_ASSERT(fmt::internal::safe_strerror(EDOM, message = buffer, 0),
"invalid buffer");
buffer[0] = 'x';
#if defined(_GNU_SOURCE) && !defined(__COVERITY__)
@@ -357,18 +374,18 @@ TEST(FormatTest, StrError) {
#endif
int result =
fmt::detail::safe_strerror(error_code, message = buffer, BUFFER_SIZE);
fmt::internal::safe_strerror(error_code, message = buffer, BUFFER_SIZE);
EXPECT_EQ(result, 0);
size_t message_size = std::strlen(message);
std::size_t message_size = std::strlen(message);
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
EXPECT_EQ(get_system_error(error_code), message);
// safe_strerror never uses buffer on MinGW.
#if !defined(__MINGW32__) && !defined(__sun)
result =
fmt::detail::safe_strerror(error_code, message = buffer, message_size);
fmt::internal::safe_strerror(error_code, message = buffer, message_size);
EXPECT_EQ(ERANGE, result);
result = fmt::detail::safe_strerror(error_code, message = buffer, 1);
result = fmt::internal::safe_strerror(error_code, message = buffer, 1);
EXPECT_EQ(buffer, message); // Message should point to buffer.
EXPECT_EQ(ERANGE, result);
EXPECT_STREQ("", message);
@@ -380,48 +397,51 @@ TEST(FormatTest, FormatErrorCode) {
{
fmt::memory_buffer buffer;
format_to(buffer, "garbage");
fmt::detail::format_error_code(buffer, 42, "test");
fmt::internal::format_error_code(buffer, 42, "test");
EXPECT_EQ("test: " + msg, to_string(buffer));
}
{
fmt::memory_buffer buffer;
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size() + 1,
'x');
fmt::detail::format_error_code(buffer, 42, prefix);
fmt::internal::format_error_code(buffer, 42, prefix);
EXPECT_EQ(msg, to_string(buffer));
}
int codes[] = {42, -1};
for (size_t i = 0, n = sizeof(codes) / sizeof(*codes); i < n; ++i) {
for (std::size_t i = 0, n = sizeof(codes) / sizeof(*codes); i < n; ++i) {
// Test maximum buffer size.
msg = fmt::format("error {}", codes[i]);
fmt::memory_buffer buffer;
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
fmt::detail::format_error_code(buffer, codes[i], prefix);
fmt::internal::format_error_code(buffer, codes[i], prefix);
EXPECT_EQ(prefix + sep + msg, to_string(buffer));
size_t size = fmt::inline_buffer_size;
std::size_t size = fmt::inline_buffer_size;
EXPECT_EQ(size, buffer.size());
buffer.resize(0);
// Test with a message that doesn't fit into the buffer.
prefix += 'x';
fmt::detail::format_error_code(buffer, codes[i], prefix);
fmt::internal::format_error_code(buffer, codes[i], prefix);
EXPECT_EQ(msg, to_string(buffer));
}
}
TEST(FormatTest, CountCodePoints) {
EXPECT_EQ(4,
fmt::detail::count_code_points(
fmt::basic_string_view<fmt::detail::char8_type>(
reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
#ifndef __cpp_char8_t
using fmt::char8_t;
#endif
EXPECT_EQ(
4, fmt::internal::count_code_points(
fmt::basic_string_view<fmt::internal::char8_type>(
reinterpret_cast<const fmt::internal::char8_type*>("ёжик"))));
}
// Tests fmt::detail::count_digits for integer type Int.
// Tests fmt::internal::count_digits for integer type Int.
template <typename Int> void test_count_digits() {
for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i));
for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::internal::count_digits(i));
for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) {
n *= 10;
EXPECT_EQ(i, fmt::detail::count_digits(n - 1));
EXPECT_EQ(i + 1, fmt::detail::count_digits(n));
EXPECT_EQ(i, fmt::internal::count_digits(n - 1));
EXPECT_EQ(i + 1, fmt::internal::count_digits(n));
}
}
@@ -430,17 +450,11 @@ TEST(UtilTest, CountDigits) {
test_count_digits<uint64_t>();
}
TEST(UtilTest, WriteFallbackUIntPtr) {
std::string s;
fmt::detail::write_ptr<char>(
std::back_inserter(s),
fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr);
EXPECT_EQ(s, "0xface");
TEST(UtilTest, WriteUIntPtr) {
fmt::memory_buffer buf;
fmt::internal::writer writer(buf);
writer.write_pointer(
fmt::internal::fallback_uintptr(reinterpret_cast<void*>(0xface)),
nullptr);
EXPECT_EQ("0xface", to_string(buf));
}
#ifdef _WIN32
TEST(UtilTest, WriteConsoleSignature) {
decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW;
(void)p;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +1,38 @@
# Copyright (c) 2019, Paul Dreik
# License: see LICENSE.rst in the fmt root directory
# Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind.
# (Note that libFuzzer can also reproduce, just pass it the files.)
option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On)
# settings this links in a main. useful for reproducing,
# kcov, gdb, afl, valgrind.
# (note that libFuzzer can also reproduce, just pass it the files)
option(FMT_FUZZ_LINKMAIN "enables the reproduce mode, instead of libFuzzer" On)
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
# the fuzz targets, otherwise the CMake configuration step fails.
# the fuzz targets, otherwise the cmake configuration step fails.
set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
# Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data
# through the fuzzers.
function(add_fuzzer source)
get_filename_component(basename ${source} NAME_WE)
set(name ${basename}-fuzzer)
add_executable(${name} ${source} fuzzer-common.h)
# Find all fuzzers.
set(SOURCES
chrono_duration.cpp
named_arg.cpp
one_arg.cpp
sprintf.cpp
two_args.cpp
)
macro(implement_fuzzer sourcefile)
get_filename_component(basename ${sourcefile} NAME_WE)
set(name fuzzer_${basename})
add_executable(${name} ${sourcefile} fuzzer_common.h)
if (FMT_FUZZ_LINKMAIN)
target_sources(${name} PRIVATE main.cc)
target_sources(${name} PRIVATE main.cpp)
endif ()
target_link_libraries(${name} PRIVATE fmt)
if (FMT_FUZZ_LDFLAGS)
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
endif ()
if (FMT_FUZZ_LDFLAGS)
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
endif ()
target_compile_features(${name} PRIVATE cxx_generic_lambdas)
endfunction()
endmacro ()
foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc)
add_fuzzer(${source})
foreach (X IN ITEMS ${SOURCES})
implement_fuzzer(${X})
endforeach ()

View File

@@ -1,4 +1,22 @@
# Running the fuzzers locally
# FMT Fuzzer
Fuzzing has revealed [several bugs](https://github.com/fmtlib/fmt/issues?&q=is%3Aissue+fuzz)
in fmt. It is a part of the continous fuzzing at
[oss-fuzz](https://github.com/google/oss-fuzz).
The source code is modified to make the fuzzing possible without locking up on
resource exhaustion:
```cpp
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if(spec.precision>100000) {
throw std::runtime_error("fuzz mode - avoiding large precision");
}
#endif
```
This macro is the defacto standard for making fuzzing practically possible, see
[the libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode).
## Running the fuzzers locally
There is a [helper script](build.sh) to build the fuzzers, which has only been
tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on
@@ -11,7 +29,7 @@ mkdir build
cd build
export CXX=clang++
export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
cmake --build .
```
should work to build the fuzzers for all platforms which clang supports.
@@ -21,5 +39,5 @@ Execute a fuzzer with for instance
cd build
export UBSAN_OPTIONS=halt_on_error=1
mkdir out_chrono
bin/fuzzer_chrono_duration out_chrono
bin/fuzzer_chrono_duration out_chrono
```

View File

@@ -1,6 +1,7 @@
#!/bin/sh
#
# Creates fuzzer builds of various kinds
# - reproduce mode (no fuzzing, just enables replaying data through the fuzzers)
# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works)
# - libFuzzer build (you will need clang)
# - afl build (you will need afl)
@@ -8,7 +9,7 @@
#
# Copyright (c) 2019 Paul Dreik
#
# For the license information refer to format.h.
# License: see LICENSE.rst in the fmt root directory
set -e
me=$(basename $0)
@@ -22,7 +23,16 @@ here=$(pwd)
CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
# For performance analysis of the fuzzers.
#builds the fuzzers as one would do if using afl or just making
#binaries for reproducing.
builddir=$here/build-fuzzers-reproduce
mkdir -p $builddir
cd $builddir
CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL" cmake \
$CMAKEFLAGSALL
cmake --build $builddir
#for performance analysis of the fuzzers
builddir=$here/build-fuzzers-perfanalysis
mkdir -p $builddir
cd $builddir
@@ -33,7 +43,7 @@ $CMAKEFLAGSALL \
cmake --build $builddir
# Builds the fuzzers as oss-fuzz does.
#builds the fuzzers as oss-fuzz does
builddir=$here/build-fuzzers-ossfuzz
mkdir -p $builddir
cd $builddir
@@ -46,7 +56,7 @@ cmake $CMAKEFLAGSALL \
cmake --build $builddir
# Builds fuzzers for local fuzzing with libfuzzer with asan+usan.
#builds fuzzers for local fuzzing with libfuzzer with asan+usan
builddir=$here/build-fuzzers-libfuzzer
mkdir -p $builddir
cd $builddir
@@ -58,7 +68,19 @@ cmake $CMAKEFLAGSALL \
cmake --build $builddir
# Builds a fast fuzzer for making coverage fast.
#builds fuzzers for local fuzzing with libfuzzer with asan only
builddir=$here/build-fuzzers-libfuzzer-addr
mkdir -p $builddir
cd $builddir
CXX="clang++" \
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,undefined" cmake \
cmake $CMAKEFLAGSALL \
-DFMT_FUZZ_LINKMAIN=Off \
-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
cmake --build $builddir
#builds a fast fuzzer for making coverage fast
builddir=$here/build-fuzzers-fast
mkdir -p $builddir
cd $builddir
@@ -72,7 +94,7 @@ cmake $CMAKEFLAGSALL \
cmake --build $builddir
# Builds fuzzers for local fuzzing with afl.
#builds fuzzers for local fuzzing with afl
builddir=$here/build-fuzzers-afl
mkdir -p $builddir
cd $builddir

View File

@@ -1,135 +0,0 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <fmt/chrono.h>
#include "fuzzer-common.h"
template <typename Period, typename Rep>
void invoke_inner(fmt::string_view format_str, Rep rep) {
auto value = std::chrono::duration<Rep, Period>(rep);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str, value);
#else
fmt::memory_buffer buf;
fmt::format_to(buf, format_str, value);
#endif
} catch (std::exception&) {
}
}
// Rep is a duration's representation type.
template <typename Rep>
void invoke_outer(const uint8_t* data, size_t size, int period) {
// Always use a fixed location of the data.
static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small");
if (size <= fixed_size + 1) return;
const Rep rep = assign_from_buf<Rep>(data);
data += fixed_size;
size -= fixed_size;
// data is already allocated separately in libFuzzer so reading past the end
// will most likely be detected anyway.
const auto format_str = fmt::string_view(as_chars(data), size);
// yocto, zepto, zetta and yotta are not handled.
switch (period) {
case 1:
invoke_inner<std::atto>(format_str, rep);
break;
case 2:
invoke_inner<std::femto>(format_str, rep);
break;
case 3:
invoke_inner<std::pico>(format_str, rep);
break;
case 4:
invoke_inner<std::nano>(format_str, rep);
break;
case 5:
invoke_inner<std::micro>(format_str, rep);
break;
case 6:
invoke_inner<std::milli>(format_str, rep);
break;
case 7:
invoke_inner<std::centi>(format_str, rep);
break;
case 8:
invoke_inner<std::deci>(format_str, rep);
break;
case 9:
invoke_inner<std::deca>(format_str, rep);
break;
case 10:
invoke_inner<std::kilo>(format_str, rep);
break;
case 11:
invoke_inner<std::mega>(format_str, rep);
break;
case 12:
invoke_inner<std::giga>(format_str, rep);
break;
case 13:
invoke_inner<std::tera>(format_str, rep);
break;
case 14:
invoke_inner<std::peta>(format_str, rep);
break;
case 15:
invoke_inner<std::exa>(format_str, rep);
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 4) return 0;
const auto representation = data[0];
const auto period = data[1];
data += 2;
size -= 2;
switch (representation) {
case 1:
invoke_outer<char>(data, size, period);
break;
case 2:
invoke_outer<signed char>(data, size, period);
break;
case 3:
invoke_outer<unsigned char>(data, size, period);
break;
case 4:
invoke_outer<short>(data, size, period);
break;
case 5:
invoke_outer<unsigned short>(data, size, period);
break;
case 6:
invoke_outer<int>(data, size, period);
break;
case 7:
invoke_outer<unsigned int>(data, size, period);
break;
case 8:
invoke_outer<long>(data, size, period);
break;
case 9:
invoke_outer<unsigned long>(data, size, period);
break;
case 10:
invoke_outer<float>(data, size, period);
break;
case 11:
invoke_outer<double>(data, size, period);
break;
case 12:
invoke_outer<long double>(data, size, period);
break;
}
return 0;
}

View File

@@ -0,0 +1,152 @@
// Copyright (c) 2019, Paul Dreik
// License: see LICENSE.rst in the fmt root directory
#include <fmt/chrono.h>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include "fuzzer_common.h"
template <typename Item, typename Ratio>
void invoke_inner(fmt::string_view formatstring, const Item item) {
const std::chrono::duration<Item, Ratio> value(item);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(formatstring, value);
#else
fmt::memory_buffer buf;
fmt::format_to(buf, formatstring, value);
#endif
} catch (std::exception& /*e*/) {
}
}
// Item is the underlying type for duration (int, long etc)
template <typename Item>
void invoke_outer(const uint8_t* Data, std::size_t Size, const int scaling) {
// always use a fixed location of the data
using fmt_fuzzer::Nfixed;
constexpr auto N = sizeof(Item);
static_assert(N <= Nfixed, "fixed size is too small");
if (Size <= Nfixed + 1) {
return;
}
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
// fast forward
Data += Nfixed;
Size -= Nfixed;
// Data is already allocated separately in libFuzzer so reading past
// the end will most likely be detected anyway
const auto formatstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
// doit_impl<Item,std::yocto>(buf.data(),item);
// doit_impl<Item,std::zepto>(buf.data(),item);
switch (scaling) {
case 1:
invoke_inner<Item, std::atto>(formatstring, item);
break;
case 2:
invoke_inner<Item, std::femto>(formatstring, item);
break;
case 3:
invoke_inner<Item, std::pico>(formatstring, item);
break;
case 4:
invoke_inner<Item, std::nano>(formatstring, item);
break;
case 5:
invoke_inner<Item, std::micro>(formatstring, item);
break;
case 6:
invoke_inner<Item, std::milli>(formatstring, item);
break;
case 7:
invoke_inner<Item, std::centi>(formatstring, item);
break;
case 8:
invoke_inner<Item, std::deci>(formatstring, item);
break;
case 9:
invoke_inner<Item, std::deca>(formatstring, item);
break;
case 10:
invoke_inner<Item, std::kilo>(formatstring, item);
break;
case 11:
invoke_inner<Item, std::mega>(formatstring, item);
break;
case 12:
invoke_inner<Item, std::giga>(formatstring, item);
break;
case 13:
invoke_inner<Item, std::tera>(formatstring, item);
break;
case 14:
invoke_inner<Item, std::peta>(formatstring, item);
break;
case 15:
invoke_inner<Item, std::exa>(formatstring, item);
}
// doit_impl<Item,std::zeta>(buf.data(),item);
// doit_impl<Item,std::yotta>(buf.data(),item);
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size) {
if (Size <= 4) {
return 0;
}
const auto representation = Data[0];
const auto scaling = Data[1];
Data += 2;
Size -= 2;
switch (representation) {
case 1:
invoke_outer<char>(Data, Size, scaling);
break;
case 2:
invoke_outer<unsigned char>(Data, Size, scaling);
break;
case 3:
invoke_outer<signed char>(Data, Size, scaling);
break;
case 4:
invoke_outer<short>(Data, Size, scaling);
break;
case 5:
invoke_outer<unsigned short>(Data, Size, scaling);
break;
case 6:
invoke_outer<int>(Data, Size, scaling);
break;
case 7:
invoke_outer<unsigned int>(Data, Size, scaling);
break;
case 8:
invoke_outer<long>(Data, Size, scaling);
break;
case 9:
invoke_outer<unsigned long>(Data, Size, scaling);
break;
case 10:
invoke_outer<float>(Data, Size, scaling);
break;
case 11:
invoke_outer<double>(Data, Size, scaling);
break;
case 12:
invoke_outer<long double>(Data, Size, scaling);
break;
default:
break;
}
return 0;
}

View File

@@ -1,34 +0,0 @@
// A fuzzer for floating-point formatter.
// For the license information refer to format.h.
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <limits>
#include <fmt/format.h>
#include "fuzzer-common.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559)
return 0;
auto value = assign_from_buf<double>(data);
auto buffer = fmt::memory_buffer();
fmt::format_to(buffer, "{}", value);
// Check a round trip.
if (std::isnan(value)) {
auto nan = std::signbit(value) ? "-nan" : "nan";
if (fmt::string_view(buffer.data(), buffer.size()) != nan)
throw std::runtime_error("round trip failure");
return 0;
}
buffer.push_back('\0');
char* ptr = nullptr;
if (std::strtod(buffer.data(), &ptr) != value)
throw std::runtime_error("round trip failure");
if (ptr + 1 != buffer.end())
throw std::runtime_error("unparsed output");
return 0;
}

View File

@@ -1,75 +0,0 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#ifndef FUZZER_COMMON_H
#define FUZZER_COMMON_H
#include <cstdint> // std::uint8_t
#include <cstring> // memcpy
#include <vector>
#include <fmt/core.h>
// One can format to either a string, or a buffer. The latter is faster, but
// one may be interested in formatting to a string instead to verify it works
// as intended. To avoid a combinatoric explosion, select this at compile time
// instead of dynamically from the fuzz data.
#define FMT_FUZZ_FORMAT_TO_STRING 0
// If {fmt} is given a buffer that is separately allocated, chances that address
// sanitizer detects out of bound reads is much higher. However, it slows down
// the fuzzing.
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
// The size of the largest possible type in use.
// To let the the fuzzer mutation be efficient at cross pollinating between
// different types, use a fixed size format. The same bit pattern, interpreted
// as another type, is likely interesting.
constexpr auto fixed_size = 16;
// Casts data to a char pointer.
template <typename T> inline const char* as_chars(const T* data) {
return reinterpret_cast<const char*>(data);
}
// Casts data to a byte pointer.
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
return reinterpret_cast<const std::uint8_t*>(data);
}
// Blits bytes from data to form an (assumed trivially constructible) object
// of type Item.
template <class Item> inline Item assign_from_buf(const std::uint8_t* data) {
auto item = Item();
std::memcpy(&item, data, sizeof(Item));
return item;
}
// Reads a boolean value by looking at the first byte from data.
template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) {
return *data != 0;
}
struct data_to_string {
#if FMT_FUZZ_SEPARATE_ALLOCATION
std::vector<char> buffer;
data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
: buffer(size + (add_terminator ? 1 : 0)) {
std::memcpy(buffer.data(), data, size);
}
fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
#else
fmt::string_view sv;
data_to_string(const uint8_t* data, size_t size, bool = false)
: str(as_chars(data), size) {}
fmt::string_view get() const { return sv; }
#endif
const char* data() const { return get().data(); }
};
#endif // FUZZER_COMMON_H

View File

@@ -0,0 +1,67 @@
#ifndef FUZZER_COMMON_H
#define FUZZER_COMMON_H
// Copyright (c) 2019, Paul Dreik
// License: see LICENSE.rst in the fmt root directory
#include <cstdint> // std::uint8_t
#include <cstring> // memcpy
#include <type_traits> // trivially copyable
// one can format to either a string, or a buf. buf is faster,
// but one may be interested in formatting to a string instead to
// verify it works as intended. to avoid a combinatoric explosion,
// select this at compile time instead of dynamically from the fuzz data
#define FMT_FUZZ_FORMAT_TO_STRING 0
// if fmt is given a buffer that is separately allocated,
// chances that address sanitizer detects out of bound reads is
// much higher. However, it slows down the fuzzing.
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
// To let the the fuzzer mutation be efficient at cross pollinating
// between different types, use a fixed size format.
// The same bit pattern, interpreted as another type,
// is likely interesting.
// For this, we must know the size of the largest possible type in use.
// There are some problems on travis, claiming Nfixed is not a constant
// expression which seems to be an issue with older versions of libstdc++
#if _GLIBCXX_RELEASE >= 7
# include <algorithm>
namespace fmt_fuzzer {
constexpr auto Nfixed = std::max(sizeof(long double), sizeof(std::intmax_t));
}
#else
namespace fmt_fuzzer {
constexpr auto Nfixed = 16;
}
#endif
namespace fmt_fuzzer {
// view data as a c char pointer.
template <typename T> inline const char* as_chars(const T* data) {
return static_cast<const char*>(static_cast<const void*>(data));
}
// view data as a byte pointer
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
return static_cast<const std::uint8_t*>(static_cast<const void*>(data));
}
// blits bytes from Data to form an (assumed trivially constructible) object
// of type Item
template <class Item> inline Item assignFromBuf(const std::uint8_t* Data) {
Item item{};
std::memcpy(&item, Data, sizeof(Item));
return item;
}
// reads a boolean value by looking at the first byte from Data
template <> inline bool assignFromBuf<bool>(const std::uint8_t* Data) {
return !!Data[0];
}
} // namespace fmt_fuzzer
#endif // FUZZER_COMMON_H

View File

@@ -1,22 +0,0 @@
#include <cassert>
#include <fstream>
#include <vector>
#include "fuzzer-common.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
int main(int argc, char** argv) {
for (int i = 1; i < argc; ++i) {
std::ifstream in(argv[i]);
assert(in);
in.seekg(0, std::ios_base::end);
const auto size = in.tellg();
assert(size >= 0);
in.seekg(0, std::ios_base::beg);
std::vector<char> buf(static_cast<size_t>(size));
in.read(buf.data(), size);
assert(in.gcount() == size);
LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
}
}

21
test/fuzzing/main.cpp Normal file
View File

@@ -0,0 +1,21 @@
#include <cassert>
#include <fstream>
#include <sstream>
#include <vector>
#include "fuzzer_common.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size);
int main(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
std::ifstream in(argv[i]);
assert(in);
in.seekg(0, std::ios_base::end);
const auto pos = in.tellg();
assert(pos >= 0);
in.seekg(0, std::ios_base::beg);
std::vector<char> buf(static_cast<std::size_t>(pos));
in.read(buf.data(), static_cast<long>(buf.size()));
assert(in.gcount() == pos);
LLVMFuzzerTestOneInput(fmt_fuzzer::as_bytes(buf.data()), buf.size());
}
}

View File

@@ -1,100 +0,0 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <type_traits>
#include <vector>
#include <fmt/chrono.h>
#include "fuzzer-common.h"
template <typename T>
void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
static_assert(sizeof(T) <= fixed_size, "fixed_size too small");
if (size <= fixed_size) return;
const T value = assign_from_buf<T>(data);
data += fixed_size;
size -= fixed_size;
if (arg_name_size <= 0 || arg_name_size >= size) return;
data_to_string arg_name(data, arg_name_size, true);
data += arg_name_size;
size -= arg_name_size;
data_to_string format_str(data, size);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message =
fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
#else
fmt::memory_buffer out;
fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value));
#endif
} catch (std::exception&) {
}
}
// For dynamic dispatching to an explicit instantiation.
template <typename Callback> void invoke(int type, Callback callback) {
switch (type) {
case 0:
callback(bool());
break;
case 1:
callback(char());
break;
case 2:
using sc = signed char;
callback(sc());
break;
case 3:
using uc = unsigned char;
callback(uc());
break;
case 4:
callback(short());
break;
case 5:
using us = unsigned short;
callback(us());
break;
case 6:
callback(int());
break;
case 7:
callback(unsigned());
break;
case 8:
callback(long());
break;
case 9:
using ul = unsigned long;
callback(ul());
break;
case 10:
callback(float());
break;
case 11:
callback(double());
break;
case 12:
using LD = long double;
callback(LD());
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 3) return 0;
// Switch types depending on the first byte of the input.
const auto type = data[0] & 0x0F;
const unsigned arg_name_size = (data[0] & 0xF0) >> 4;
data++;
size--;
invoke(type, [=](auto arg) {
invoke_fmt<decltype(arg)>(data, size, arg_name_size);
});
return 0;
}

128
test/fuzzing/named_arg.cpp Normal file
View File

@@ -0,0 +1,128 @@
// Copyright (c) 2019, Paul Dreik
// License: see LICENSE.rst in the fmt root directory
#include <fmt/chrono.h>
#include <fmt/core.h>
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include "fuzzer_common.h"
template <typename Item1>
void invoke_fmt(const uint8_t* Data, std::size_t Size, unsigned int argsize) {
constexpr auto N1 = sizeof(Item1);
static_assert(N1 <= fmt_fuzzer::Nfixed, "Nfixed too small");
if (Size <= fmt_fuzzer::Nfixed) {
return;
}
const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
Data += fmt_fuzzer::Nfixed;
Size -= fmt_fuzzer::Nfixed;
// how many chars should be used for the argument name?
if (argsize <= 0 || argsize >= Size) {
return;
}
// allocating buffers separately is slower, but increases chances
// of detecting memory errors
#if FMT_FUZZ_SEPARATE_ALLOCATION
std::vector<char> argnamebuffer(argsize);
std::memcpy(argnamebuffer.data(), Data, argsize);
auto argname = fmt::string_view(argnamebuffer.data(), argsize);
#else
auto argname = fmt::string_view(fmt_fuzzer::as_chars(Data), argsize);
#endif
Data += argsize;
Size -= argsize;
#if FMT_FUZZ_SEPARATE_ALLOCATION
// allocates as tight as possible, making it easier to catch buffer overruns.
std::vector<char> fmtstringbuffer(Size);
std::memcpy(fmtstringbuffer.data(), Data, Size);
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
#else
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
#endif
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(fmtstring, fmt::arg(argname, item1));
#else
fmt::memory_buffer outbuf;
fmt::format_to(outbuf, fmtstring, fmt::arg(argname, item1));
#endif
}
// for dynamic dispatching to an explicit instantiation
template <typename Callback> void invoke(int index, Callback callback) {
switch (index) {
case 0:
callback(bool{});
break;
case 1:
callback(char{});
break;
case 2:
using sc = signed char;
callback(sc{});
break;
case 3:
using uc = unsigned char;
callback(uc{});
break;
case 4:
callback(short{});
break;
case 5:
using us = unsigned short;
callback(us{});
break;
case 6:
callback(int{});
break;
case 7:
callback(unsigned{});
break;
case 8:
callback(long{});
break;
case 9:
using ul = unsigned long;
callback(ul{});
break;
case 10:
callback(float{});
break;
case 11:
callback(double{});
break;
case 12:
using LD = long double;
callback(LD{});
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size) {
if (Size <= 3) {
return 0;
}
// switch types depending on the first byte of the input
const auto first = Data[0] & 0x0F;
const unsigned int second = (Data[0] & 0xF0) >> 4;
Data++;
Size--;
auto outerfcn = [=](auto param1) {
invoke_fmt<decltype(param1)>(Data, Size, second);
};
try {
invoke(first, outerfcn);
} catch (std::exception& /*e*/) {
}
return 0;
}

View File

@@ -1,91 +0,0 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <exception>
#include <fmt/chrono.h>
#include "fuzzer-common.h"
template <typename T, typename Repr>
const T* from_repr(const Repr& r) { return &r; }
template <>
const std::tm* from_repr<std::tm>(const std::time_t& t) {
return std::localtime(&t);
}
template <typename T, typename Repr = T>
void invoke_fmt(const uint8_t* data, size_t size) {
static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small");
if (size <= fixed_size) return;
auto repr = assign_from_buf<Repr>(data);
const T* value = from_repr<T>(repr);
if (!value) return;
data += fixed_size;
size -= fixed_size;
data_to_string format_str(data, size);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str.get(), *value);
#else
fmt::memory_buffer message;
fmt::format_to(message, format_str.get(), *value);
#endif
} catch (std::exception&) {
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 3) return 0;
const auto first = data[0];
data++;
size--;
switch (first) {
case 0:
invoke_fmt<bool>(data, size);
break;
case 1:
invoke_fmt<char>(data, size);
break;
case 2:
invoke_fmt<unsigned char>(data, size);
break;
case 3:
invoke_fmt<signed char>(data, size);
break;
case 4:
invoke_fmt<short>(data, size);
break;
case 5:
invoke_fmt<unsigned short>(data, size);
break;
case 6:
invoke_fmt<int>(data, size);
break;
case 7:
invoke_fmt<unsigned int>(data, size);
break;
case 8:
invoke_fmt<long>(data, size);
break;
case 9:
invoke_fmt<unsigned long>(data, size);
break;
case 10:
invoke_fmt<float>(data, size);
break;
case 11:
invoke_fmt<double>(data, size);
break;
case 12:
invoke_fmt<long double>(data, size);
break;
case 13:
invoke_fmt<std::tm, std::time_t>(data, size);
break;
}
return 0;
}

131
test/fuzzing/one_arg.cpp Normal file
View File

@@ -0,0 +1,131 @@
// Copyright (c) 2019, Paul Dreik
// License: see LICENSE.rst in the fmt root directory
#include <fmt/core.h>
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include <fmt/chrono.h>
#include "fuzzer_common.h"
using fmt_fuzzer::Nfixed;
template <typename Item>
void invoke_fmt(const uint8_t* Data, std::size_t Size) {
constexpr auto N = sizeof(Item);
static_assert(N <= Nfixed, "Nfixed is too small");
if (Size <= Nfixed) {
return;
}
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
Data += Nfixed;
Size -= Nfixed;
#if FMT_FUZZ_SEPARATE_ALLOCATION
// allocates as tight as possible, making it easier to catch buffer overruns.
std::vector<char> fmtstringbuffer(Size);
std::memcpy(fmtstringbuffer.data(), Data, Size);
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
#else
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
#endif
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(fmtstring, item);
#else
fmt::memory_buffer message;
fmt::format_to(message, fmtstring, item);
#endif
}
void invoke_fmt_time(const uint8_t* Data, std::size_t Size) {
using Item = std::time_t;
constexpr auto N = sizeof(Item);
static_assert(N <= Nfixed, "Nfixed too small");
if (Size <= Nfixed) {
return;
}
const Item item = fmt_fuzzer::assignFromBuf<Item>(Data);
Data += Nfixed;
Size -= Nfixed;
#if FMT_FUZZ_SEPARATE_ALLOCATION
// allocates as tight as possible, making it easier to catch buffer overruns.
std::vector<char> fmtstringbuffer(Size);
std::memcpy(fmtstringbuffer.data(), Data, Size);
auto fmtstring = fmt::string_view(fmtstringbuffer.data(), Size);
#else
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
#endif
auto* b = std::localtime(&item);
if (b) {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(fmtstring, *b);
#else
fmt::memory_buffer message;
fmt::format_to(message, fmtstring, *b);
#endif
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size) {
if (Size <= 3) {
return 0;
}
const auto first = Data[0];
Data++;
Size--;
try {
switch (first) {
case 0:
invoke_fmt<bool>(Data, Size);
break;
case 1:
invoke_fmt<char>(Data, Size);
break;
case 2:
invoke_fmt<unsigned char>(Data, Size);
break;
case 3:
invoke_fmt<signed char>(Data, Size);
break;
case 4:
invoke_fmt<short>(Data, Size);
break;
case 5:
invoke_fmt<unsigned short>(Data, Size);
break;
case 6:
invoke_fmt<int>(Data, Size);
break;
case 7:
invoke_fmt<unsigned int>(Data, Size);
break;
case 8:
invoke_fmt<long>(Data, Size);
break;
case 9:
invoke_fmt<unsigned long>(Data, Size);
break;
case 10:
invoke_fmt<float>(Data, Size);
break;
case 11:
invoke_fmt<double>(Data, Size);
break;
case 12:
invoke_fmt<long double>(Data, Size);
break;
case 13:
invoke_fmt_time(Data, Size);
break;
default:
break;
}
} catch (std::exception& /*e*/) {
}
return 0;
}

116
test/fuzzing/sprintf.cpp Normal file
View File

@@ -0,0 +1,116 @@
// Copyright (c) 2019, Paul Dreik
// License: see LICENSE.rst in the fmt root directory
#include <fmt/format.h>
#include <fmt/printf.h>
#include <cstdint>
#include <stdexcept>
#include "fuzzer_common.h"
using fmt_fuzzer::Nfixed;
template <typename Item1, typename Item2>
void invoke_fmt(const uint8_t* Data, std::size_t Size) {
constexpr auto N1 = sizeof(Item1);
constexpr auto N2 = sizeof(Item2);
static_assert(N1 <= Nfixed, "size1 exceeded");
static_assert(N2 <= Nfixed, "size2 exceeded");
if (Size <= Nfixed + Nfixed) {
return;
}
Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
Data += Nfixed;
Size -= Nfixed;
Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data);
Data += Nfixed;
Size -= Nfixed;
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(fmtstring, item1, item2);
#else
fmt::memory_buffer message;
fmt::format_to(message, fmtstring, item1, item2);
#endif
}
// for dynamic dispatching to an explicit instantiation
template <typename Callback> void invoke(int index, Callback callback) {
switch (index) {
case 0:
callback(bool{});
break;
case 1:
callback(char{});
break;
case 2:
using sc = signed char;
callback(sc{});
break;
case 3:
using uc = unsigned char;
callback(uc{});
break;
case 4:
callback(short{});
break;
case 5:
using us = unsigned short;
callback(us{});
break;
case 6:
callback(int{});
break;
case 7:
callback(unsigned{});
break;
case 8:
callback(long{});
break;
case 9:
using ul = unsigned long;
callback(ul{});
break;
case 10:
callback(float{});
break;
case 11:
callback(double{});
break;
case 12:
using LD = long double;
callback(LD{});
break;
case 13:
using ptr = void*;
callback(ptr{});
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size) {
if (Size <= 3) {
return 0;
}
// switch types depending on the first byte of the input
const auto first = Data[0] & 0x0F;
const auto second = (Data[0] & 0xF0) >> 4;
Data++;
Size--;
auto outer = [=](auto param1) {
auto inner = [=](auto param2) {
invoke_fmt<decltype(param1), decltype(param2)>(Data, Size);
};
invoke(second, inner);
};
try {
invoke(first, outer);
} catch (std::exception& /*e*/) {
}
return 0;
}

View File

@@ -1,105 +0,0 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <exception>
#include <string>
#include <fmt/format.h>
#include "fuzzer-common.h"
template <typename Item1, typename Item2>
void invoke_fmt(const uint8_t* data, size_t size) {
static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded");
static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded");
if (size <= fixed_size + fixed_size) return;
const Item1 item1 = assign_from_buf<Item1>(data);
data += fixed_size;
size -= fixed_size;
const Item2 item2 = assign_from_buf<Item2>(data);
data += fixed_size;
size -= fixed_size;
auto format_str = fmt::string_view(as_chars(data), size);
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str, item1, item2);
#else
fmt::memory_buffer message;
fmt::format_to(message, format_str, item1, item2);
#endif
}
// For dynamic dispatching to an explicit instantiation.
template <typename Callback> void invoke(int index, Callback callback) {
switch (index) {
case 0:
callback(bool());
break;
case 1:
callback(char());
break;
case 2:
using sc = signed char;
callback(sc());
break;
case 3:
using uc = unsigned char;
callback(uc());
break;
case 4:
callback(short());
break;
case 5:
using us = unsigned short;
callback(us());
break;
case 6:
callback(int());
break;
case 7:
callback(unsigned());
break;
case 8:
callback(long());
break;
case 9:
using ul = unsigned long;
callback(ul());
break;
case 10:
callback(float());
break;
case 11:
callback(double());
break;
case 12:
using LD = long double;
callback(LD());
break;
case 13:
using ptr = void*;
callback(ptr());
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 3) return 0;
// Switch types depending on the first byte of the input.
const auto type1 = data[0] & 0x0F;
const auto type2 = (data[0] & 0xF0) >> 4;
data++;
size--;
try {
invoke(type1, [=](auto param1) {
invoke(type2, [=](auto param2) {
invoke_fmt<decltype(param1), decltype(param2)>(data, size);
});
});
} catch (std::exception&) {
}
return 0;
}

112
test/fuzzing/two_args.cpp Normal file
View File

@@ -0,0 +1,112 @@
// Copyright (c) 2019, Paul Dreik
// License: see LICENSE.rst in the fmt root directory
#include <fmt/format.h>
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#include "fuzzer_common.h"
constexpr auto Nfixed = fmt_fuzzer::Nfixed;
template <typename Item1, typename Item2>
void invoke_fmt(const uint8_t* Data, std::size_t Size) {
constexpr auto N1 = sizeof(Item1);
constexpr auto N2 = sizeof(Item2);
static_assert(N1 <= Nfixed, "size1 exceeded");
static_assert(N2 <= Nfixed, "size2 exceeded");
if (Size <= Nfixed + Nfixed) {
return;
}
const Item1 item1 = fmt_fuzzer::assignFromBuf<Item1>(Data);
Data += Nfixed;
Size -= Nfixed;
const Item2 item2 = fmt_fuzzer::assignFromBuf<Item2>(Data);
Data += Nfixed;
Size -= Nfixed;
auto fmtstring = fmt::string_view(fmt_fuzzer::as_chars(Data), Size);
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(fmtstring, item1, item2);
#else
fmt::memory_buffer message;
fmt::format_to(message, fmtstring, item1, item2);
#endif
}
// for dynamic dispatching to an explicit instantiation
template <typename Callback> void invoke(int index, Callback callback) {
switch (index) {
case 0:
callback(bool{});
break;
case 1:
callback(char{});
break;
case 2:
using sc = signed char;
callback(sc{});
break;
case 3:
using uc = unsigned char;
callback(uc{});
break;
case 4:
callback(short{});
break;
case 5:
using us = unsigned short;
callback(us{});
break;
case 6:
callback(int{});
break;
case 7:
callback(unsigned{});
break;
case 8:
callback(long{});
break;
case 9:
using ul = unsigned long;
callback(ul{});
break;
case 10:
callback(float{});
break;
case 11:
callback(double{});
break;
case 12:
using LD = long double;
callback(LD{});
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, std::size_t Size) {
if (Size <= 3) {
return 0;
}
// switch types depending on the first byte of the input
const auto first = Data[0] & 0x0F;
const auto second = (Data[0] & 0xF0) >> 4;
Data++;
Size--;
auto outer = [=](auto param1) {
auto inner = [=](auto param2) {
invoke_fmt<decltype(param1), decltype(param2)>(Data, Size);
};
invoke(second, inner);
};
try {
invoke(first, outer);
} catch (std::exception& /*e*/) {
}
return 0;
}

View File

@@ -344,7 +344,6 @@ class GTEST_API_ SingleFailureChecker {
# include <sys/mman.h> // NOLINT
# include <sys/time.h> // NOLINT
# include <unistd.h> // NOLINT
# include <string>
#elif GTEST_OS_SYMBIAN
@@ -7729,7 +7728,6 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() {
# include <sys/syslimits.h>
#else
# include <limits.h>
# include <climits> // Some Linux distributions define PATH_MAX here.
#endif // GTEST_OS_WINDOWS_MOBILE
@@ -8901,7 +8899,6 @@ const char* StringFromGTestEnv(const char* flag, const char* default_value) {
#include <ctype.h>
#include <stdio.h>
#include <ostream> // NOLINT
#include <string>
@@ -9497,7 +9494,6 @@ const char* TypedTestCasePState::VerifyRegisteredTestNames(
// This file implements cardinalities.
#include <limits.h>
#include <ostream> // NOLINT
#include <sstream>
#include <string>
@@ -9650,7 +9646,6 @@ GTEST_API_ Cardinality Exactly(int n) { return Between(n, n); }
// USE THEM IN USER CODE.
#include <ctype.h>
#include <ostream> // NOLINT
#include <string>
@@ -9813,7 +9808,6 @@ GTEST_API_ void Log(LogSeverity severity, const string& message,
// utilities for defining matchers.
#include <string.h>
#include <sstream>
#include <string>
@@ -10297,7 +10291,6 @@ bool UnorderedElementsAreMatcherImplBase::
// EXPECT_CALL).
#include <stdlib.h>
#include <iostream> // NOLINT
#include <map>
#include <set>

75
test/grisu-test.cc Normal file
View File

@@ -0,0 +1,75 @@
// Formatting library for C++ - Grisu tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/format.h"
#include "gtest.h"
static bool reported_skipped;
#undef TEST
#define TEST(test_fixture, test_name) \
void test_fixture##test_name(); \
GTEST_TEST(test_fixture, test_name) { \
if (FMT_USE_GRISU) { \
test_fixture##test_name(); \
} else if (!reported_skipped) { \
reported_skipped = true; \
fmt::print("Skipping Grisu tests.\n"); \
} \
} \
void test_fixture##test_name()
TEST(GrisuTest, NaN) {
auto nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", fmt::format("{}", nan));
EXPECT_EQ("-nan", fmt::format("{}", -nan));
}
TEST(GrisuTest, Inf) {
auto inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", fmt::format("{}", inf));
EXPECT_EQ("-inf", fmt::format("{}", -inf));
}
TEST(GrisuTest, Zero) { EXPECT_EQ("0.0", fmt::format("{}", 0.0)); }
TEST(GrisuTest, Round) {
EXPECT_EQ("1.9156918820264798e-56",
fmt::format("{}", 1.9156918820264798e-56));
EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15));
}
TEST(GrisuTest, Prettify) {
EXPECT_EQ("0.0001", fmt::format("{}", 1e-4));
EXPECT_EQ("1e-05", fmt::format("{}", 1e-5));
EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5));
EXPECT_EQ("10000000000.0", fmt::format("{}", 1e10));
EXPECT_EQ("100000000000.0", fmt::format("{}", 1e11));
EXPECT_EQ("12340000000.0", fmt::format("{}", 1234e7));
EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
}
TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }
TEST(GrisuTest, Fallback) {
EXPECT_EQ("1e+23", fmt::format("{}", 1e23));
EXPECT_EQ("9e-265", fmt::format("{}", 9e-265));
EXPECT_EQ("5.423717798060526e+125",
fmt::format("{}", 5.423717798060526e+125));
EXPECT_EQ("1.372371880954233e-288",
fmt::format("{}", 1.372371880954233e-288));
EXPECT_EQ("55388492.622190244", fmt::format("{}", 55388492.622190244));
EXPECT_EQ("2.2506787569811123e-253",
fmt::format("{}", 2.2506787569811123e-253));
EXPECT_EQ("1103618912042992.8", fmt::format("{}", 1103618912042992.8));
// pow(2, -25) - assymetric boundaries:
EXPECT_EQ("2.9802322387695312e-08",
fmt::format("{}", 2.9802322387695312e-08));
}

View File

@@ -8,7 +8,6 @@
#include "gtest-extra.h"
#include <gtest/gtest-spi.h>
#include <algorithm>
#include <cstring>
#include <memory>
@@ -168,24 +167,22 @@ TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
// Tests that assertion arguments are evaluated exactly once.
TEST_F(SingleEvaluationTest, WriteTests) {
// successful EXPECT_WRITE
EXPECT_WRITE(
stdout,
{ // NOLINT
a_++;
std::printf("test");
},
(b_++, "test"));
EXPECT_WRITE(stdout,
{ // NOLINT
a_++;
std::printf("test");
},
(b_++, "test"));
EXPECT_EQ(1, a_);
EXPECT_EQ(1, b_);
// failed EXPECT_WRITE
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(
stdout,
{ // NOLINT
a_++;
std::printf("test");
},
(b_++, "other")),
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout,
{ // NOLINT
a_++;
std::printf("test");
},
(b_++, "other")),
"Actual: test");
EXPECT_EQ(2, a_);
EXPECT_EQ(2, b_);
@@ -420,17 +417,16 @@ TEST(OutputRedirectTest, ErrorInDtor) {
std::unique_ptr<OutputRedirect> redir(new OutputRedirect(f.get()));
// Put a character in a file buffer.
EXPECT_EQ('x', fputc('x', f.get()));
EXPECT_WRITE(
stderr,
{
// The close function must be called inside EXPECT_WRITE,
// otherwise the system may recycle closed file descriptor when
// redirecting the output in EXPECT_STDERR and the second close
// will break output redirection.
FMT_POSIX(close(write_fd));
SUPPRESS_ASSERT(redir.reset(nullptr));
},
format_system_error(EBADF, "cannot flush stream"));
EXPECT_WRITE(stderr,
{
// The close function must be called inside EXPECT_WRITE,
// otherwise the system may recycle closed file descriptor when
// redirecting the output in EXPECT_STDERR and the second close
// will break output redirection.
FMT_POSIX(close(write_fd));
SUPPRESS_ASSERT(redir.reset(nullptr));
},
format_system_error(EBADF, "cannot flush stream"));
write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail
}

View File

@@ -57,7 +57,7 @@ std::string OutputRedirect::restore_and_read() {
if (read_end_.descriptor() == -1) return content; // Already read.
enum { BUFFER_SIZE = 4096 };
char buffer[BUFFER_SIZE];
size_t count = 0;
std::size_t count = 0;
do {
count = read_end_.read(buffer, BUFFER_SIZE);
content.append(buffer, count);
@@ -66,9 +66,9 @@ std::string OutputRedirect::restore_and_read() {
return content;
}
std::string read(file& f, size_t count) {
std::string read(file& f, std::size_t count) {
std::string buffer(count, '\0');
size_t n = 0, offset = 0;
std::size_t n = 0, offset = 0;
do {
n = f.read(&buffer[offset], count - offset);
// We can't read more than size_t bytes since count has type size_t.

View File

@@ -9,9 +9,8 @@
#define FMT_GTEST_EXTRA_H_
#include <string>
#include "fmt/os.h"
#include "gmock.h"
#include "fmt/os.h"
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
@@ -138,20 +137,13 @@ class SuppressAssert {
EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message)
// Attempts to read count characters from a file.
std::string read(fmt::file& f, size_t count);
std::string read(fmt::file& f, std::size_t count);
# define EXPECT_READ(file, expected_content) \
EXPECT_EQ(expected_content, \
read(file, fmt::string_view(expected_content).size()))
EXPECT_EQ(expected_content, read(file, std::strlen(expected_content)))
#else
# define EXPECT_WRITE(file, statement, expected_output) \
do { \
(void)(file); \
(void)(statement); \
(void)(expected_output); \
SUCCEED(); \
} while (false)
# define EXPECT_WRITE(file, statement, expected_output) SUCCEED()
#endif // FMT_USE_FCNTL
template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> {

View File

@@ -6,12 +6,9 @@
// For the license information refer to format.h.
#include "fmt/locale.h"
#include <complex>
#include "gmock.h"
using fmt::detail::max_value;
using fmt::internal::max_value;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Char> struct numpunct : std::numpunct<Char> {
@@ -44,117 +41,49 @@ template <typename Char> struct small_grouping : std::numpunct<Char> {
TEST(LocaleTest, DoubleDecimalPoint) {
std::locale loc(std::locale(), new numpunct<char>());
EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23));
EXPECT_EQ("1?23", fmt::format(loc, "{:n}", 1.23));
}
TEST(LocaleTest, Format) {
std::locale loc(std::locale(), new numpunct<char>());
EXPECT_EQ("1234567", fmt::format(std::locale(), "{:L}", 1234567));
EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
EXPECT_EQ("1234567", fmt::format(std::locale(), "{:n}", 1234567));
EXPECT_EQ("1~234~567", fmt::format(loc, "{:n}", 1234567));
fmt::format_arg_store<fmt::format_context, int> as{1234567};
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as)));
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:n}", fmt::format_args(as)));
std::string s;
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
fmt::format_to(std::back_inserter(s), loc, "{:n}", 1234567);
EXPECT_EQ("1~234~567", s);
std::locale no_grouping_loc(std::locale(), new no_grouping<char>());
EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:L}", 1234567));
EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:n}", 1234567));
std::locale special_grouping_loc(std::locale(), new special_grouping<char>());
EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:L}", 12345678));
EXPECT_EQ("12,345", fmt::format(special_grouping_loc, "{:L}", 12345));
EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:n}", 12345678));
std::locale small_grouping_loc(std::locale(), new small_grouping<char>());
EXPECT_EQ("4,2,9,4,9,6,7,2,9,5",
fmt::format(small_grouping_loc, "{:L}", max_value<uint32_t>()));
}
TEST(LocaleTest, FormatDetaultAlign) {
std::locale special_grouping_loc(std::locale(), new special_grouping<char>());
EXPECT_EQ(" 12,345", fmt::format(special_grouping_loc, "{:8L}", 12345));
fmt::format(small_grouping_loc, "{:n}", max_value<uint32_t>()));
}
TEST(LocaleTest, WFormat) {
std::locale loc(std::locale(), new numpunct<wchar_t>());
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:n}", 1234567));
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:n}", 1234567));
fmt::format_arg_store<fmt::wformat_context, int> as{1234567};
EXPECT_EQ(L"1~234~567", fmt::vformat(loc, L"{:L}", fmt::wformat_args(as)));
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
EXPECT_EQ(L"1~234~567", fmt::vformat(loc, L"{:n}", fmt::wformat_args(as)));
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:n}", 1234567));
std::locale no_grouping_loc(std::locale(), new no_grouping<wchar_t>());
EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:L}", 1234567));
EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:n}", 1234567));
std::locale special_grouping_loc(std::locale(),
new special_grouping<wchar_t>());
EXPECT_EQ(L"1,23,45,678",
fmt::format(special_grouping_loc, L"{:L}", 12345678));
fmt::format(special_grouping_loc, L"{:n}", 12345678));
std::locale small_grouping_loc(std::locale(), new small_grouping<wchar_t>());
EXPECT_EQ(L"4,2,9,4,9,6,7,2,9,5",
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
}
TEST(LocaleTest, DoubleFormatter) {
auto loc = std::locale(std::locale(), new special_grouping<char>());
auto f = fmt::formatter<int>();
auto parse_ctx = fmt::format_parse_context("L");
f.parse(parse_ctx);
char buf[10] = {};
fmt::basic_format_context<char*, char> format_ctx(
buf, {}, fmt::detail::locale_ref(loc));
*f.format(12345, format_ctx) = 0;
EXPECT_STREQ("12,345", buf);
}
FMT_BEGIN_NAMESPACE
template <class charT> struct formatter<std::complex<double>, charT> {
private:
detail::dynamic_format_specs<char> specs_;
public:
typename basic_format_parse_context<charT>::iterator parse(
basic_format_parse_context<charT>& ctx) {
using handler_type =
detail::dynamic_specs_handler<basic_format_parse_context<charT>>;
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
detail::type::string_type);
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
detail::parse_float_type_spec(specs_, ctx.error_handler());
return it;
}
template <class FormatContext>
typename FormatContext::iterator format(const std::complex<double>& c,
FormatContext& ctx) {
detail::handle_dynamic_spec<detail::precision_checker>(
specs_.precision, specs_.precision_ref, ctx);
auto format_specs = std::string();
if (specs_.precision > 0)
format_specs = fmt::format(".{}", specs_.precision);
if (specs_.type)
format_specs += specs_.type;
auto real = fmt::format(ctx.locale().template get<std::locale>(),
"{:" + format_specs + "}", c.real());
auto imag = fmt::format(ctx.locale().template get<std::locale>(),
"{:" + format_specs + "}", c.imag());
auto fill_align_width = std::string();
if (specs_.width > 0)
fill_align_width = fmt::format(">{}", specs_.width);
return format_to(
ctx.out(), "{:" + fill_align_width + "}",
fmt::format(c.real() != 0 ? "({0}+{1}i)" : "{1}i", real, imag));
}
};
FMT_END_NAMESPACE
TEST(FormatTest, Complex) {
std::string s = fmt::format("{}", std::complex<double>(1, 2));
EXPECT_EQ(s, "(1+2i)");
EXPECT_EQ(fmt::format("{:.2f}", std::complex<double>(1, 2)), "(1.00+2.00i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)");
fmt::format(small_grouping_loc, L"{:n}", max_value<uint32_t>()));
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR

View File

@@ -16,8 +16,8 @@ template <typename T> class mock_allocator {
mock_allocator() {}
mock_allocator(const mock_allocator&) {}
typedef T value_type;
MOCK_METHOD1_T(allocate, T*(size_t n));
MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
MOCK_METHOD1_T(allocate, T*(std::size_t n));
MOCK_METHOD2_T(deallocate, void(T* p, std::size_t n));
};
template <typename Allocator> class allocator_ref {
@@ -51,10 +51,10 @@ template <typename Allocator> class allocator_ref {
public:
Allocator* get() const { return alloc_; }
value_type* allocate(size_t n) {
value_type* allocate(std::size_t n) {
return std::allocator_traits<Allocator>::allocate(*alloc_, n);
}
void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); }
void deallocate(value_type* p, std::size_t n) { alloc_->deallocate(p, n); }
};
#endif // FMT_MOCK_ALLOCATOR_H_

View File

@@ -5,12 +5,11 @@
//
// For the license information refer to format.h.
#include "fmt/os.h"
#include <cstdlib> // std::exit
#include <cstring>
#include <memory>
#include "fmt/os.h"
#include "gtest-extra.h"
#include "util.h"
@@ -27,14 +26,14 @@ using fmt::error_code;
TEST(UtilTest, UTF16ToUTF8) {
std::string s = "ёжик";
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
fmt::internal::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}
TEST(UtilTest, UTF16ToUTF8EmptyString) {
std::string s = "";
fmt::detail::utf16_to_utf8 u(L"");
fmt::internal::utf16_to_utf8 u(L"");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}
@@ -44,7 +43,7 @@ void check_utf_conversion_error(
const char* message,
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) {
fmt::memory_buffer out;
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
fmt::internal::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
fmt::system_error error(0, "");
try {
(Converter)(str);
@@ -56,12 +55,12 @@ void check_utf_conversion_error(
}
TEST(UtilTest, UTF16ToUTF8Error) {
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
check_utf_conversion_error<fmt::internal::utf16_to_utf8, wchar_t>(
"cannot convert string from UTF-16 to UTF-8");
}
TEST(UtilTest, UTF16ToUTF8Convert) {
fmt::detail::utf16_to_utf8 u;
fmt::internal::utf16_to_utf8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1)));
EXPECT_EQ(ERROR_INVALID_PARAMETER,
u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u)));
@@ -74,16 +73,17 @@ TEST(UtilTest, FormatWindowsError) {
0, ERROR_FILE_EXISTS,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0);
fmt::detail::utf16_to_utf8 utf8_message(message);
fmt::internal::utf16_to_utf8 utf8_message(message);
LocalFree(message);
fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS,
"test");
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
fmt::to_string(actual_message));
actual_message.resize(0);
auto max_size = fmt::detail::max_value<size_t>() / 2;
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS,
fmt::string_view(nullptr, max_size));
auto max_size = fmt::internal::max_value<size_t>();
fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS,
fmt::string_view(0, max_size));
EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS),
fmt::to_string(actual_message));
}
@@ -103,11 +103,11 @@ TEST(UtilTest, FormatLongWindowsError) {
reinterpret_cast<LPWSTR>(&message), 0, 0) == 0) {
return;
}
fmt::detail::utf16_to_utf8 utf8_message(message);
fmt::internal::utf16_to_utf8 utf8_message(message);
LocalFree(message);
fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
"test");
fmt::internal::format_windows_error(actual_message, provisioning_not_allowed,
"test");
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
fmt::to_string(actual_message));
}
@@ -120,14 +120,14 @@ TEST(UtilTest, WindowsError) {
error = e;
}
fmt::memory_buffer message;
fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error");
fmt::internal::format_windows_error(message, ERROR_FILE_EXISTS, "test error");
EXPECT_EQ(to_string(message), error.what());
EXPECT_EQ(ERROR_FILE_EXISTS, error.error_code());
}
TEST(UtilTest, ReportWindowsError) {
fmt::memory_buffer out;
fmt::detail::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
fmt::internal::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
out.push_back('\n');
EXPECT_WRITE(stderr,
fmt::report_windows_error(ERROR_FILE_EXISTS, "test error"),
@@ -164,10 +164,10 @@ static file open_file() {
// Attempts to write a string to a file.
static void write(file& f, fmt::string_view s) {
size_t num_chars_left = s.size();
std::size_t num_chars_left = s.size();
const char* ptr = s.data();
do {
size_t count = f.write(ptr, num_chars_left);
std::size_t count = f.write(ptr, num_chars_left);
ptr += count;
// We can't write more than size_t bytes since num_chars_left
// has type size_t.
@@ -238,17 +238,16 @@ TEST(BufferedFileTest, CloseFileInDtor) {
TEST(BufferedFileTest, CloseErrorInDtor) {
std::unique_ptr<buffered_file> f(new buffered_file(open_buffered_file()));
EXPECT_WRITE(
stderr,
{
// The close function must be called inside EXPECT_WRITE,
// otherwise the system may recycle closed file descriptor when
// redirecting the output in EXPECT_STDERR and the second close
// will break output redirection.
FMT_POSIX(close(f->fileno()));
SUPPRESS_ASSERT(f.reset(nullptr));
},
format_system_error(EBADF, "cannot close file") + "\n");
EXPECT_WRITE(stderr,
{
// The close function must be called inside EXPECT_WRITE,
// otherwise the system may recycle closed file descriptor when
// redirecting the output in EXPECT_STDERR and the second close
// will break output redirection.
FMT_POSIX(close(f->fileno()));
SUPPRESS_ASSERT(f.reset(nullptr));
},
format_system_error(EBADF, "cannot close file") + "\n");
}
TEST(BufferedFileTest, Close) {
@@ -287,38 +286,6 @@ TEST(BufferedFileTest, Fileno) {
EXPECT_READ(copy, FILE_CONTENT);
}
TEST(OStreamTest, Move) {
fmt::ostream out = fmt::output_file("test-file");
fmt::ostream moved(std::move(out));
moved.print("hello");
}
TEST(OStreamTest, Print) {
fmt::ostream out = fmt::output_file("test-file");
out.print("The answer is {}.\n", 42);
out.close();
file in("test-file", file::RDONLY);
EXPECT_READ(in, "The answer is 42.\n");
}
TEST(OStreamTest, BufferBoundary) {
auto str = std::string(4096, 'x');
fmt::ostream out = fmt::output_file("test-file");
out.print("{}", str);
out.print("{}", str);
out.close();
file in("test-file", file::RDONLY);
EXPECT_READ(in, str + str);
}
TEST(OStreamTest, BufferSize) {
fmt::ostream out = fmt::output_file("test-file", fmt::buffer_size=1);
out.print("{}", "foo");
out.close();
file in("test-file", file::RDONLY);
EXPECT_READ(in, "foo");
}
TEST(FileTest, DefaultCtor) {
file f;
EXPECT_EQ(-1, f.descriptor());
@@ -402,17 +369,16 @@ TEST(FileTest, CloseFileInDtor) {
TEST(FileTest, CloseErrorInDtor) {
std::unique_ptr<file> f(new file(open_file()));
EXPECT_WRITE(
stderr,
{
// The close function must be called inside EXPECT_WRITE,
// otherwise the system may recycle closed file descriptor when
// redirecting the output in EXPECT_STDERR and the second close
// will break output redirection.
FMT_POSIX(close(f->descriptor()));
SUPPRESS_ASSERT(f.reset(nullptr));
},
format_system_error(EBADF, "cannot close file") + "\n");
EXPECT_WRITE(stderr,
{
// The close function must be called inside EXPECT_WRITE,
// otherwise the system may recycle closed file descriptor when
// redirecting the output in EXPECT_STDERR and the second close
// will break output redirection.
FMT_POSIX(close(f->descriptor()));
SUPPRESS_ASSERT(f.reset(nullptr));
},
format_system_error(EBADF, "cannot close file") + "\n");
}
TEST(FileTest, Close) {
@@ -529,4 +495,4 @@ TEST(LocaleTest, Strtod) {
EXPECT_EQ(start + 3, ptr);
}
# endif
#endif // FMT_USE_FCNTL
#endif // FMT_USE_FCNTL

View File

@@ -5,6 +5,7 @@
//
// For the license information refer to format.h.
#define FMT_STRING_ALIAS 1
#include "fmt/format.h"
struct test {};
@@ -20,10 +21,9 @@ template <> struct formatter<test> : formatter<int> {
};
} // namespace fmt
#include <sstream>
#include "fmt/ostream.h"
#include "fmt/ranges.h"
#include <sstream>
#include "gmock.h"
#include "gtest-extra.h"
#include "util.h"
@@ -64,23 +64,22 @@ TEST(OStreamTest, Enum) {
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
}
struct test_arg_formatter
: fmt::detail::arg_formatter<fmt::format_context::iterator, char> {
using range = fmt::buffer_range<char>;
struct test_arg_formatter : fmt::arg_formatter<range> {
fmt::format_parse_context parse_ctx;
test_arg_formatter(fmt::format_context& ctx, fmt::format_specs& s)
: fmt::detail::arg_formatter<fmt::format_context::iterator, char>(
ctx, &parse_ctx, &s),
parse_ctx("") {}
: fmt::arg_formatter<range>(ctx, &parse_ctx, &s), parse_ctx("") {}
};
TEST(OStreamTest, CustomArg) {
fmt::memory_buffer buffer;
fmt::format_context ctx(fmt::detail::buffer_appender<char>{buffer},
fmt::format_args());
fmt::internal::buffer<char>& base = buffer;
fmt::format_context ctx(std::back_inserter(base), fmt::format_args());
fmt::format_specs spec;
test_arg_formatter af(ctx, spec);
fmt::visit_format_arg(
af, fmt::detail::make_arg<fmt::format_context>(streamable_enum()));
af, fmt::internal::make_arg<fmt::format_context>(streamable_enum()));
EXPECT_EQ("streamable_enum", std::string(buffer.data(), buffer.size()));
}
@@ -96,7 +95,7 @@ TEST(OStreamTest, Format) {
TEST(OStreamTest, FormatSpecs) {
EXPECT_EQ("def ", format("{0:<5}", TestString("def")));
EXPECT_EQ(" def", format("{0:>5}", TestString("def")));
#if FMT_DEPRECATED_NUMERIC_ALIGN
#if FMT_NUMERIC_ALIGN
EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), format_error,
"format specifier requires numeric argument");
#endif
@@ -141,19 +140,18 @@ TEST(OStreamTest, WriteToOStream) {
fmt::memory_buffer buffer;
const char* foo = "foo";
buffer.append(foo, foo + std::strlen(foo));
fmt::detail::write_buffer(os, buffer);
fmt::internal::write(os, buffer);
EXPECT_EQ("foo", os.str());
}
TEST(OStreamTest, WriteToOStreamMaxSize) {
size_t max_size = fmt::detail::max_value<size_t>();
std::streamsize max_streamsize = fmt::detail::max_value<std::streamsize>();
if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return;
std::size_t max_size = fmt::internal::max_value<std::size_t>();
std::streamsize max_streamsize = fmt::internal::max_value<std::streamsize>();
if (max_size <= fmt::internal::to_unsigned(max_streamsize)) return;
struct test_buffer final : fmt::detail::buffer<char> {
explicit test_buffer(size_t size)
: fmt::detail::buffer<char>(nullptr, size, size) {}
void grow(size_t) {}
struct test_buffer : fmt::internal::buffer<char> {
explicit test_buffer(std::size_t size) { resize(size); }
void grow(std::size_t) {}
} buffer(max_size);
struct mock_streambuf : std::streambuf {
@@ -173,13 +171,13 @@ TEST(OStreamTest, WriteToOStreamMaxSize) {
typedef std::make_unsigned<std::streamsize>::type ustreamsize;
ustreamsize size = max_size;
do {
auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize));
auto n = std::min(size, fmt::internal::to_unsigned(max_streamsize));
EXPECT_CALL(streambuf, xsputn(data, static_cast<std::streamsize>(n)))
.WillOnce(testing::Return(max_streamsize));
data += n;
size -= n;
} while (size != 0);
fmt::detail::write_buffer(os, buffer);
fmt::internal::write(os, buffer);
}
TEST(OStreamTest, Join) {
@@ -260,7 +258,7 @@ TEST(OStreamTest, DisableBuiltinOStreamOperators) {
struct explicitly_convertible_to_string_like {
template <typename String,
typename = typename std::enable_if<std::is_constructible<
String, const char*, size_t>::value>::type>
String, const char*, std::size_t>::value>::type>
explicit operator String() const {
return String("foo", 3u);
}
@@ -271,13 +269,13 @@ std::ostream& operator<<(std::ostream& os,
return os << "bar";
}
TEST(OStreamTest, FormatExplicitlyConvertibleToStringLike) {
TEST(FormatterTest, FormatExplicitlyConvertibleToStringLike) {
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
}
#ifdef FMT_USE_STRING_VIEW
struct explicitly_convertible_to_std_string_view {
explicit operator fmt::detail::std_string_view<char>() const {
explicit operator fmt::internal::std_string_view<char>() const {
return {"foo", 3u};
}
};
@@ -287,44 +285,8 @@ std::ostream& operator<<(std::ostream& os,
return os << "bar";
}
TEST(OStreamTest, FormatExplicitlyConvertibleToStdStringView) {
TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
}
#endif // FMT_USE_STRING_VIEW
struct streamable_and_convertible_to_bool {
operator bool() const { return true; }
};
std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
return os << "foo";
}
TEST(OStreamTest, FormatConvertibleToBool) {
EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool()));
}
struct copyfmt_test {};
std::ostream& operator<<(std::ostream& os, copyfmt_test) {
std::ios ios(nullptr);
ios.copyfmt(os);
return os << "foo";
}
TEST(OStreamTest, CopyFmt) {
EXPECT_EQ("foo", fmt::format("{}", copyfmt_test()));
}
TEST(OStreamTest, CompileTimeString) {
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
}
TEST(OStreamTest, ToString) {
EXPECT_EQ("ABC", fmt::to_string(fmt_test::ABC()));
}
TEST(OStreamTest, Range) {
auto strs = std::vector<TestString>{TestString("foo"), TestString("bar")};
EXPECT_EQ("{foo, bar}", format("{}", strs));
}

View File

@@ -49,8 +49,8 @@ int pipe_count;
int fopen_count;
int fclose_count;
int fileno_count;
size_t read_nbyte;
size_t write_nbyte;
std::size_t read_nbyte;
std::size_t write_nbyte;
bool sysconf_error;
enum { NONE, MAX_SIZE, ERROR } fstat_sim;
@@ -261,8 +261,8 @@ TEST(FileTest, Size) {
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
# ifdef _WIN32
fmt::memory_buffer message;
fmt::detail::format_windows_error(message, ERROR_ACCESS_DENIED,
"cannot get file size");
fmt::internal::format_windows_error(message, ERROR_ACCESS_DENIED,
"cannot get file size");
fstat_sim = ERROR;
EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message));
fstat_sim = NONE;
@@ -288,7 +288,7 @@ TEST(FileTest, ReadRetry) {
write_end.write("test", SIZE);
write_end.close();
char buffer[SIZE];
size_t count = 0;
std::size_t count = 0;
EXPECT_RETRY(count = read_end.read(buffer, SIZE), read,
"cannot read from file");
EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
@@ -298,7 +298,7 @@ TEST(FileTest, WriteRetry) {
file read_end, write_end;
file::pipe(read_end, write_end);
enum { SIZE = 4 };
size_t count = 0;
std::size_t count = 0;
EXPECT_RETRY(count = write_end.write("test", SIZE), write,
"cannot write to file");
write_end.close();
@@ -316,8 +316,8 @@ TEST(FileTest, ConvertReadCount) {
file read_end, write_end;
file::pipe(read_end, write_end);
char c;
size_t size = UINT_MAX;
if (sizeof(unsigned) != sizeof(size_t)) ++size;
std::size_t size = UINT_MAX;
if (sizeof(unsigned) != sizeof(std::size_t)) ++size;
read_count = 1;
read_nbyte = 0;
EXPECT_THROW(read_end.read(&c, size), fmt::system_error);
@@ -329,8 +329,8 @@ TEST(FileTest, ConvertWriteCount) {
file read_end, write_end;
file::pipe(read_end, write_end);
char c;
size_t size = UINT_MAX;
if (sizeof(unsigned) != sizeof(size_t)) ++size;
std::size_t size = UINT_MAX;
if (sizeof(unsigned) != sizeof(std::size_t)) ++size;
write_count = 1;
write_nbyte = 0;
EXPECT_THROW(write_end.write(&c, size), fmt::system_error);

View File

@@ -17,7 +17,7 @@
using fmt::format;
using fmt::format_error;
using fmt::detail::max_value;
using fmt::internal::max_value;
const unsigned BIG_NUM = INT_MAX + 1u;
@@ -113,13 +113,14 @@ TEST(PrintfTest, SwitchArgIndexing) {
TEST(PrintfTest, InvalidArgIndex) {
EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error,
"argument not found");
"argument index out of range");
EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error,
"argument not found");
"argument index out of range");
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", INT_MAX), 42), format_error,
"argument not found");
"argument index out of range");
EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error, "argument not found");
EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error,
"argument index out of range");
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM), 42), format_error,
"number is too big");
}
@@ -139,11 +140,8 @@ TEST(PrintfTest, ZeroFlag) {
EXPECT_PRINTF("+00042", "%00+6d", 42);
EXPECT_PRINTF(" 42", "%05.d", 42);
EXPECT_PRINTF(" 0042", "%05.4d", 42);
// '0' flag is ignored for non-numeric types.
EXPECT_PRINTF(" x", "%05c", 'x');
EXPECT_PRINTF("0000x", "%05c", 'x');
}
TEST(PrintfTest, PlusFlag) {
@@ -154,38 +152,11 @@ TEST(PrintfTest, PlusFlag) {
// '+' flag is ignored for non-numeric types.
EXPECT_PRINTF("x", "%+c", 'x');
// '+' flag wins over space flag
EXPECT_PRINTF("+42", "%+ d", 42);
EXPECT_PRINTF("-42", "%+ d", -42);
EXPECT_PRINTF("+42", "% +d", 42);
EXPECT_PRINTF("-42", "% +d", -42);
EXPECT_PRINTF("+0042", "% +05d", 42);
EXPECT_PRINTF("+0042", "%0+ 5d", 42);
// '+' flag and space flag are both ignored for non-numeric types.
EXPECT_PRINTF("x", "%+ c", 'x');
EXPECT_PRINTF("x", "% +c", 'x');
}
TEST(PrintfTest, MinusFlag) {
EXPECT_PRINTF("abc ", "%-5s", "abc");
EXPECT_PRINTF("abc ", "%0--5s", "abc");
EXPECT_PRINTF("7 ", "%-5d", 7);
EXPECT_PRINTF("97 ", "%-5hhi", 'a');
EXPECT_PRINTF("a ", "%-5c", 'a');
// '0' flag is ignored if '-' flag is given
EXPECT_PRINTF("7 ", "%-05d", 7);
EXPECT_PRINTF("7 ", "%0-5d", 7);
EXPECT_PRINTF("a ", "%-05c", 'a');
EXPECT_PRINTF("a ", "%0-5c", 'a');
EXPECT_PRINTF("97 ", "%-05hhi", 'a');
EXPECT_PRINTF("97 ", "%0-5hhi", 'a');
// '-' and space flag don't interfere
EXPECT_PRINTF(" 42", "%- d", 42);
}
TEST(PrintfTest, SpaceFlag) {
@@ -251,7 +222,8 @@ TEST(PrintfTest, DynamicWidth) {
EXPECT_EQ("42 ", test_sprintf("%*d", -5, 42));
EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
"width is not integer");
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, "argument not found");
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error,
"argument index out of range");
EXPECT_THROW_MSG(test_sprintf("%*d", BIG_NUM, 42), format_error,
"number is too big");
}
@@ -287,11 +259,6 @@ TEST(PrintfTest, FloatPrecision) {
EXPECT_PRINTF(buffer, "%.3a", 1234.5678);
}
TEST(PrintfTest, StringPrecision) {
char test[] = {'H', 'e', 'l', 'l', 'o'};
EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell");
}
TEST(PrintfTest, IgnorePrecisionForNonNumericArg) {
EXPECT_PRINTF("abc", "%.5s", "abc");
}
@@ -301,7 +268,8 @@ TEST(PrintfTest, DynamicPrecision) {
EXPECT_EQ("42", test_sprintf("%.*d", -5, 42));
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
"precision is not integer");
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found");
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error,
"argument index out of range");
EXPECT_THROW_MSG(test_sprintf("%.*d", BIG_NUM, 42), format_error,
"number is too big");
if (sizeof(long long) != sizeof(int)) {
@@ -330,7 +298,7 @@ void TestLength(const char* length_spec, U value) {
unsigned long long unsigned_value = 0;
// Apply integer promotion to the argument.
unsigned long long max = max_value<U>();
using fmt::detail::const_check;
using fmt::internal::const_check;
if (const_check(max <= static_cast<unsigned>(max_value<int>()))) {
signed_value = static_cast<int>(value);
unsigned_value = static_cast<unsigned long long>(value);
@@ -399,7 +367,7 @@ TEST(PrintfTest, Length) {
TestLength<long long>("ll");
TestLength<unsigned long long>("ll");
TestLength<intmax_t>("j");
TestLength<size_t>("z");
TestLength<std::size_t>("z");
TestLength<std::ptrdiff_t>("t");
long double max = max_value<long double>();
EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
@@ -479,12 +447,6 @@ TEST(PrintfTest, String) {
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
}
TEST(PrintfTest, UCharString) {
unsigned char str[] = "test";
unsigned char* pstr = str;
EXPECT_EQ("test", fmt::sprintf("%s", pstr));
}
TEST(PrintfTest, Pointer) {
int n;
void* p = &n;
@@ -607,22 +569,51 @@ TEST(PrintfTest, VSPrintfMakeWArgsExample) {
#endif
}
TEST(PrintfTest, PrintfDetermineOutputSize) {
using backit = std::back_insert_iterator<std::vector<char>>;
using truncated_printf_context =
fmt::basic_printf_context<fmt::detail::truncating_iterator<backit>, char>;
typedef fmt::printf_arg_formatter<fmt::buffer_range<char>> formatter_t;
typedef fmt::basic_printf_context<formatter_t::iterator, char> context_t;
auto v = std::vector<char>{};
auto it = std::back_inserter(v);
// A custom printf argument formatter that doesn't print `-` for floating-point
// values rounded to 0.
class custom_printf_arg_formatter : public formatter_t {
public:
using formatter_t::iterator;
const auto format_string = "%s";
const auto format_arg = "Hello";
const auto expected_size = fmt::sprintf(format_string, format_arg).size();
custom_printf_arg_formatter(formatter_t::iterator iter,
formatter_t::format_specs& specs, context_t& ctx)
: formatter_t(iter, specs, ctx) {}
EXPECT_EQ((truncated_printf_context(
fmt::detail::truncating_iterator<backit>(it, 0), format_string,
fmt::make_format_args<truncated_printf_context>(format_arg))
.format()
.count()),
expected_size);
using formatter_t::operator();
#if FMT_MSC_VER > 0 && FMT_MSC_VER <= 1804
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
iterator operator()(T value){
#else
iterator operator()(double value) {
#endif
// Comparing a float to 0.0 is safe.
if (round(value * pow(10, specs()->precision)) == 0.0) value = 0;
return formatter_t::operator()(value);
}
}
;
typedef fmt::basic_format_args<context_t> format_args_t;
std::string custom_vformat(fmt::string_view format_str, format_args_t args) {
fmt::memory_buffer buffer;
fmt::vprintf<custom_printf_arg_formatter>(buffer, format_str, args);
return std::string(buffer.data(), buffer.size());
}
template <typename... Args>
std::string custom_format(const char* format_str, const Args&... args) {
auto va = fmt::make_printf_args(args...);
return custom_vformat(format_str, {va});
}
TEST(PrintfTest, CustomFormat) {
EXPECT_EQ("0.00", custom_format("%.2f", -.00001));
EXPECT_EQ("0.00", custom_format("%.2f", .00001));
EXPECT_EQ("1.00", custom_format("%.2f", 1.00001));
EXPECT_EQ("-1.00", custom_format("%.2f", -1.00001));
}

View File

@@ -54,7 +54,7 @@ TEST(RangesTest, FormatTuple) {
TEST(RangesTest, JoinTuple) {
// Value tuple args
std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f);
EXPECT_EQ("(a, 1, 2)", fmt::format("({})", fmt::join(t1, ", ")));
EXPECT_EQ("(a, 1, 2.0)", fmt::format("({})", fmt::join(t1, ", ")));
// Testing lvalue tuple args
int x = 4;
@@ -67,7 +67,7 @@ TEST(RangesTest, JoinTuple) {
// Single element tuple
std::tuple<float> t4{4.0f};
EXPECT_EQ("4", fmt::format("{}", fmt::join(t4, "/")));
EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/")));
}
TEST(RangesTest, JoinInitializerList) {
@@ -79,7 +79,7 @@ TEST(RangesTest, JoinInitializerList) {
struct my_struct {
int32_t i;
std::string str; // can throw
template <size_t N> decltype(auto) get() const noexcept {
template <std::size_t N> decltype(auto) get() const noexcept {
if constexpr (N == 0)
return i;
else if constexpr (N == 1)
@@ -87,15 +87,16 @@ struct my_struct {
}
};
template <size_t N> decltype(auto) get(const my_struct& s) noexcept {
template <std::size_t N> decltype(auto) get(const my_struct& s) noexcept {
return s.get<N>();
}
namespace std {
template <> struct tuple_size<my_struct> : std::integral_constant<size_t, 2> {};
template <>
struct tuple_size<my_struct> : std::integral_constant<std::size_t, 2> {};
template <size_t N> struct tuple_element<N, my_struct> {
template <std::size_t N> struct tuple_element<N, my_struct> {
using type = decltype(std::declval<my_struct>().get<N>());
};
@@ -139,65 +140,3 @@ TEST(RangesTest, FormatStringLike) {
EXPECT_EQ("foo", fmt::format("{}", string_like()));
}
#endif // FMT_USE_STRING_VIEW
struct zstring_sentinel {};
bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }
struct zstring {
const char* p;
const char* begin() const { return p; }
zstring_sentinel end() const { return {}; }
};
TEST(RangesTest, JoinSentinel) {
zstring hello{"hello"};
EXPECT_EQ("{'h', 'e', 'l', 'l', 'o'}", fmt::format("{}", hello));
EXPECT_EQ("h_e_l_l_o", fmt::format("{}", fmt::join(hello, "_")));
}
// A range that provides non-const only begin()/end() to test fmt::join handles
// that
//
// Some ranges (eg those produced by range-v3's views::filter()) can cache
// information during iteration so they only provide non-const begin()/end().
template <typename T> class non_const_only_range {
private:
std::vector<T> vec;
public:
using const_iterator = typename ::std::vector<T>::const_iterator;
template <typename... Args>
explicit non_const_only_range(Args&&... args)
: vec(::std::forward<Args>(args)...) {}
const_iterator begin() { return vec.begin(); }
const_iterator end() { return vec.end(); }
};
TEST(RangesTest, JoinRange) {
non_const_only_range<int> x(3u, 0);
EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(x, ",")));
EXPECT_EQ(
"0,0,0",
fmt::format("{}", fmt::join(non_const_only_range<int>(3u, 0), ",")));
std::vector<int> y(3u, 0);
EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(y, ",")));
EXPECT_EQ("0,0,0",
fmt::format("{}", fmt::join(std::vector<int>(3u, 0), ",")));
const std::vector<int> z(3u, 0);
EXPECT_EQ("0,0,0", fmt::format("{}", fmt::join(z, ",")));
}
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
struct unformattable {};
TEST(RangesTest, UnformattableRange) {
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
fmt::format_context>::value));
}
#endif

View File

@@ -5,14 +5,12 @@
//
// For the license information refer to format.h.
#include "scan.h"
#include <time.h>
#include <climits>
#include "gmock.h"
#include "gtest-extra.h"
#include "scan.h"
TEST(ScanTest, ReadText) {
fmt::string_view s = "foo";
@@ -75,7 +73,7 @@ template <> struct scanner<tm> {
if (it != ctx.end() && *it == ':') ++it;
auto end = it;
while (end != ctx.end() && *end != '}') ++end;
format.reserve(detail::to_unsigned(end - it + 1));
format.reserve(internal::to_unsigned(end - it + 1));
format.append(it, end);
format.push_back('\0');
return end;

View File

@@ -31,7 +31,7 @@ class scan_parse_context {
FMT_CONSTEXPR iterator end() const { return format_.end(); }
void advance_to(iterator it) {
format_.remove_prefix(detail::to_unsigned(it - begin()));
format_.remove_prefix(internal::to_unsigned(it - begin()));
}
};
@@ -48,11 +48,11 @@ struct scan_context {
iterator end() const { return begin() + input_.size(); }
void advance_to(iterator it) {
input_.remove_prefix(detail::to_unsigned(it - begin()));
input_.remove_prefix(internal::to_unsigned(it - begin()));
}
};
namespace detail {
namespace internal {
enum class scan_type {
none_type,
int_type,
@@ -107,20 +107,20 @@ class scan_arg {
ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx));
}
};
} // namespace detail
} // namespace internal
struct scan_args {
int size;
const detail::scan_arg* data;
const internal::scan_arg* data;
template <size_t N>
scan_args(const std::array<detail::scan_arg, N>& store)
scan_args(const std::array<internal::scan_arg, N>& store)
: size(N), data(store.data()) {
static_assert(N < INT_MAX, "too many arguments");
}
};
namespace detail {
namespace internal {
struct scan_handler : error_handler {
private:
@@ -169,15 +169,14 @@ struct scan_handler : error_handler {
scan_ctx_.advance_to(it + size);
}
int on_arg_id() { return on_arg_id(next_arg_id_++); }
int on_arg_id(int id) {
void on_arg_id() { on_arg_id(next_arg_id_++); }
void on_arg_id(int id) {
if (id >= args_.size) on_error("argument index out of range");
arg_ = args_.data[id];
return id;
}
int on_arg_id(string_view) { return on_error("invalid format"), 0; }
void on_arg_id(string_view) { on_error("invalid format"); }
void on_replacement_field(int, const char*) {
void on_replacement_field(const char*) {
auto it = scan_ctx_.begin(), end = scan_ctx_.end();
switch (arg_.type) {
case scan_type::int_type:
@@ -209,24 +208,24 @@ struct scan_handler : error_handler {
}
}
const char* on_format_specs(int, const char* begin, const char*) {
const char* on_format_specs(const char* begin, const char*) {
if (arg_.type != scan_type::custom_type) return begin;
parse_ctx_.advance_to(begin);
arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_);
return parse_ctx_.begin();
}
};
} // namespace detail
} // namespace internal
template <typename... Args>
std::array<detail::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) {
std::array<internal::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) {
return {{args...}};
}
string_view::iterator vscan(string_view input, string_view format_str,
scan_args args) {
detail::scan_handler h(format_str, input, args);
detail::parse_format_string<false>(format_str, h);
internal::scan_handler h(format_str, input, args);
internal::parse_format_string<false>(format_str, h);
return input.begin() + (h.pos() - &*input.begin());
}

View File

@@ -1,5 +1,4 @@
#include <format>
#include "gtest.h"
TEST(StdFormatTest, Escaping) {
@@ -62,7 +61,7 @@ TEST(StdFormatTest, Int) {
string s0 = format("{}", 42); // s0 == "42"
string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a"
string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A"
string s3 = format("{:L}", 1234); // s3 == "1234" (depends on the locale)
string s3 = format("{:n}", 1234); // s3 == "1234" (depends on the locale)
EXPECT_EQ(s0, "42");
EXPECT_EQ(s1, "101010 42 52 2a");
EXPECT_EQ(s2, "0x2a 0X2A");
@@ -124,7 +123,7 @@ template <> struct std::formatter<S> {
if constexpr (!is_integral_v<type> || is_same_v<type, bool>)
throw format_error("width is not integral");
// else if (value < 0 || value > numeric_limits<int>::max())
else if (fmt::detail::is_negative(value) ||
else if (fmt::internal::is_negative(value) ||
value > numeric_limits<int>::max())
throw format_error("invalid width");
else

View File

@@ -9,7 +9,6 @@
#define FMT_TEST_ASSERT_H_
#include <stdexcept>
#include "gtest.h"
class assertion_failure : public std::logic_error {

View File

@@ -6,7 +6,6 @@
// For the license information refer to format.h.
#include <cstdlib>
#include "gtest.h"
#ifdef _WIN32

View File

@@ -6,7 +6,6 @@
// For the license information refer to format.h.
#include "util.h"
#include <cstring>
void increment(char* s) {

View File

@@ -19,7 +19,7 @@ enum { BUFFER_SIZE = 256 };
# define FMT_VSNPRINTF vsnprintf
#endif
template <size_t SIZE>
template <std::size_t SIZE>
void safe_sprintf(char (&buffer)[SIZE], const char* format, ...) {
std::va_list args;
va_start(args, format);