// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause /** * @file vtkStringFormatter.h * @brief Optimized C++ utilities for formatting values to strings and files. * * This header provides efficient, alternatives to common C/C++ string handling * functions such as `printf`, `strtol` etc. * * It includes utilities for converting strings to numbers and scanning values from * strings and files. * * Refer to the documentation for guidance on replacing standard C functions with * their modern, type-safe counterparts provided here. * * 1. C/C++ has the following functions to convert one/many numbers to a char* or string. * 1. itoa/_itoa, ltoa/_ltoa, lltoa/_i64toa, ultoa/_ultoa, ulltoa/_ulltoa/_ui64toa * 2. sprintf, sprintf_s, vsprintf, vsprintf_s, * 3. snprintf, snprintf_s, vsnprintf, vsnprintf_s, * 4. strftime * 5. std::to_chars, std::to_string * 6. std::put_time * These functions should be replaced by: * 1. vtk::to_chars or vtk::to_string, if one number needs to be converted * 2. vtk::format, vtk::format_to, or vtk::format_to_n, if one/many * numbers need to be converted with a specific format * * 2. C/C++ has the following functions to print one/many numbers to stdout/file. * 1. printf, printf_s, vprintf, vprintf_s, * 2. fprintf, fprintf_s, vfprintf, vfprintf_s, * 3. std::cout, std::ofstream, vtksys::ofstream * These functions should be replaced by: * 1. vtk::print, vtk::println */ #ifndef vtkStringFormatter_h #define vtkStringFormatter_h #include "vtkCharConvCompatibility.h" // For std::chars_format, std::to_chars_result #include "vtkCommonCoreModule.h" // For export macro #include "vtkLogger.h" // For vtkLogF #include "vtk_fmt.h" // For fmt // clang-format off #include VTK_FMT(fmt/args.h) // For fmt's args #include VTK_FMT(fmt/chrono.h) // For fmt's chrono #include VTK_FMT(fmt/compile.h) // For fmt's compile #include VTK_FMT(fmt/format.h) // For fmt's format #include VTK_FMT(fmt/ranges.h) // For fmt's ranges // clang-format on #include // For std::string #include // For std::string_view namespace vtk { VTK_ABI_NAMESPACE_BEGIN ///@{ /** * Given a number, convert it to a string within char* first and char* last, and return a * to_chars_result; * * @note The standard library provides these functions as of C++17, but they aren't as fast, * or fully implemented. */ template && !std::is_same_v>> VTK_ALWAYS_INLINE auto to_chars(char* first, char* last, const T& value, int base = 10) -> std::to_chars_result { const std::size_t buffer_size = std::distance(first, last); if (buffer_size == 0) { return { first, std::errc::value_too_large }; } const std::size_t buffer_size_1 = buffer_size - 1; fmt::format_to_n_result result; switch (base) { case 2: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:b}"), value); break; case 8: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:o}"), value); break; case 16: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:x}"), value); break; case 10: default: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:d}"), value); } if (result.size >= buffer_size_1) { return { first + buffer_size_1, std::errc::value_too_large }; } *result.out = '\0'; // Null-terminate return { result.out, std::errc{} }; // Success } template >> VTK_ALWAYS_INLINE auto to_chars(char* first, char* last, const T& value, std::chars_format format) -> std::to_chars_result { const std::size_t buffer_size = std::distance(first, last); if (buffer_size == 0) { return { first, std::errc::value_too_large }; } const std::size_t buffer_size_1 = buffer_size - 1; fmt::format_to_n_result result; switch (format) { case std::chars_format::scientific: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:e}"), value); break; case std::chars_format::fixed: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:f}"), value); break; case std::chars_format::hex: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:a}"), value); break; case std::chars_format::general: default: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:g}"), value); } if (result.size >= buffer_size_1) { return { first + buffer_size_1, std::errc::value_too_large }; } *result.out = '\0'; // Null-terminate return { result.out, std::errc{} }; // Success } template >> VTK_ALWAYS_INLINE auto to_chars(char* first, char* last, const T& value, std::chars_format format, int precision) -> std::to_chars_result { const std::size_t buffer_size = std::distance(first, last); if (buffer_size == 0) { return { first, std::errc::value_too_large }; } const std::size_t buffer_size_1 = buffer_size - 1; fmt::format_to_n_result result; switch (format) { case std::chars_format::scientific: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:.{}e}"), value, precision); break; case std::chars_format::fixed: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:.{}f}"), value, precision); break; case std::chars_format::hex: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:.{}a}"), value, precision); break; case std::chars_format::general: default: result = fmt::format_to_n(first, buffer_size_1, FMT_COMPILE("{:.{}g}"), value, precision); } if (result.size >= buffer_size_1) { return { first + buffer_size_1, std::errc::value_too_large }; } *result.out = '\0'; // Null-terminate return { result.out, std::errc{} }; // Success } ///@} // Create a macro that evaluates the result of to_chars, checks for errors, and executes a command #define VTK_TO_CHARS_RESULT_IF_ERROR_COMMAND(to_chars_result, value, command) \ switch (to_chars_result.ec) \ { \ case std::errc::invalid_argument: \ { \ vtkLogF(ERROR, "The given argument was invalid, failed to get the converted " #value "."); \ command; \ } \ case std::errc::value_too_large: \ { \ vtkLogF(ERROR, "The given buffer was too small, failed to get the converted " #value "."); \ command; \ } \ default: \ { \ } \ } // Create a macro that evaluates the result of to_chars, checks for errors, and breaks #define VTK_TO_CHARS_RESULT_IF_ERROR_BREAK(to_chars_result, value) \ VTK_TO_CHARS_RESULT_IF_ERROR_COMMAND(to_chars_result, value, break) // Create a macro that evaluates the result of to_chars, checks for errors, and returns a value #define VTK_TO_CHARS_RESULT_IF_ERROR_RETURN(to_chars_result, value, returnValue) \ VTK_TO_CHARS_RESULT_IF_ERROR_COMMAND(to_chars_result, value, return returnValue) /** * Given a number, convert it to a string. * * @note The standard library provides this function as of C++11, but it's not as fast. */ using fmt::to_string; /** * Given a format and a set of numbers/variables return the size of the formatting string * * @note The standard library provides this function as of C++20, but we support up to C++17. */ using fmt::formatted_size; /** * Given a format and a set of numbers/variables convert it to a string. * * @note The standard library provides this function as of C++20, but we support up to C++17. */ using fmt::format; #define VTK_FORMAT_IF_ERROR_COMMAND(formatCommand, command) \ try \ { \ formatCommand; \ } \ catch (const std::exception& e) \ { \ vtkLog(ERROR, "Formatting error: " << e.what()); \ command; \ } \ static_assert(true) #define VTK_FORMAT_IF_ERROR_RETURN(formatCommand, returnValue) \ VTK_FORMAT_IF_ERROR_COMMAND(formatCommand, return returnValue) #define VTK_FORMAT_IF_ERROR_BREAK(formatCommand) VTK_FORMAT_IF_ERROR_COMMAND(formatCommand, break) /** * The result type of a format_to operation. */ using fmt::format_to_result; /** * Given a format and a set of numbers/variables convert it to a string within out. * * @remark `format_to` does not append a terminating null character. * @note The standard library provides this function as of C++20, but we support up to C++17. */ using fmt::format_to; /** * The result type of a format_to_n operation. */ using fmt::format_to_n_result; /** * Given a format and a set of numbers/variables convert it to a string within out using up to n * characters. It returns a format_to_n_result. * * @remark `format_to_n` does not append a terminating null character. * @note The standard library provides this function as of C++20, but we support up to C++17. */ using fmt::format_to_n; /** * Given a format and a set of numbers/variables print it to a file/stdout. * * @note The standard library provides this function as of C++23, but we support up to C++17. */ using fmt::print; /** * Given a format and a set of numbers/variables print it to file/stdout with a newline. * * @note The standard library provides this function as of C++23, but we support up to C++17. */ using fmt::println; /** * Check if the given string is a printf style format. * * @note The standard library does not provide this function. */ VTKCOMMONCORE_EXPORT bool is_printf_format(const std::string& format); /** * Convert a printf style format to a std::format style format. * * @note The standard library does not provide this function. */ VTKCOMMONCORE_EXPORT std::string printf_to_std_format(const std::string& printf_format); /** * Convert printf and std::format style format strings. If a printf style * string is passed in, convert it and return a std::format string. */ VTKCOMMONCORE_EXPORT std::string to_std_format(const std::string& format); VTK_ABI_NAMESPACE_END } #endif // vtkStringFormatter_h // VTK-HeaderTest-Exclude: vtkStringFormatter.h