├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── cmake.yml │ ├── docs.yml │ └── tidy.yml ├── .gitignore ├── .issuetracker ├── .readthedocs.yaml ├── CMakeLists.txt ├── CMakePresets.json ├── Config.cmake.in ├── LICENSE.md ├── NEWs.md ├── README.md ├── cmake └── fetch_doctest.cmake ├── docs ├── CMakeLists.txt ├── api.rst ├── benchmarks.rst ├── conf.py ├── design.rst ├── faq.rst ├── index.rst ├── license.rst └── requirements.txt ├── include └── nanofmt │ ├── CMakeLists.txt │ ├── charconv.h │ ├── config.h │ ├── dragonbox.h │ ├── format.h │ ├── format.inl │ ├── forward.h │ └── std_string.h ├── source ├── CMakeLists.txt ├── charconv.cpp ├── format.cpp ├── numeric_utils.h └── parse_utils.h └── tests ├── CMakeLists.txt ├── fwd_only_type.h ├── test_charconv.cpp ├── test_format.cpp ├── test_format_args.cpp └── test_utils.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | ContinuationIndentWidth: 4 4 | TabWidth: 4 5 | UseTab: Never 6 | ColumnLimit: 120 7 | --- 8 | Language: Cpp 9 | Standard: c++20 10 | AccessModifierOffset: -4 11 | AlignAfterOpenBracket: AlwaysBreak 12 | AlignConsecutiveAssignments: false 13 | AlignConsecutiveDeclarations: false 14 | AlignEscapedNewlines: DontAlign 15 | #AlignOperands: AlignAfterOperator 16 | AlignOperands: false 17 | AlignTrailingComments: false 18 | AllowAllArgumentsOnNextLine: false 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | AllowAllConstructorInitializersOnNextLine: false 21 | AllowShortBlocksOnASingleLine: Empty 22 | AllowShortCaseLabelsOnASingleLine: false 23 | #AllowShortEnumsOnASingleLine: true 24 | AllowShortFunctionsOnASingleLine: Empty 25 | AllowShortIfStatementsOnASingleLine: Never 26 | AllowShortLambdasOnASingleLine: Inline 27 | AllowShortLoopsOnASingleLine: false 28 | AlwaysBreakAfterReturnType: None 29 | AlwaysBreakBeforeMultilineStrings: true 30 | AlwaysBreakTemplateDeclarations: Yes 31 | BinPackArguments: false 32 | BinPackParameters: false 33 | BraceWrapping: 34 | AfterCaseLabel: true 35 | AfterClass: false 36 | AfterControlStatement: Never 37 | AfterEnum: false 38 | AfterFunction: false 39 | AfterNamespace: false 40 | AfterStruct: false 41 | AfterUnion: false 42 | AfterExternBlock: false 43 | BeforeCatch: true 44 | BeforeElse: true 45 | #BeforeLambdaBody: false 46 | #BeforeWhile: true 47 | IndentBraces: false 48 | SplitEmptyFunction: true 49 | SplitEmptyRecord: true 50 | SplitEmptyNamespace: true 51 | BreakBeforeBinaryOperators: None 52 | BreakBeforeBraces: Custom 53 | BreakBeforeTernaryOperators: true 54 | BreakConstructorInitializers: BeforeComma 55 | BreakInheritanceList: BeforeComma 56 | BreakStringLiterals: true 57 | CompactNamespaces: false 58 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 59 | Cpp11BracedListStyle: true 60 | DerivePointerAlignment: false 61 | FixNamespaceComments: true 62 | IncludeBlocks: Regroup 63 | IncludeCategories: 64 | - Regex: '^"[^/]+"' 65 | Priority: 0 66 | SortPriority: 10 67 | - Regex: '^["<]nanofmt/' 68 | Priority: 50 69 | SortPriority: 70 70 | - Regex: '^".*"' 71 | Priority: 0 72 | SortPriority: 20 73 | - Regex: '/' 74 | Priority: 90 75 | SortPriority: 80 76 | - Regex: '[.]' 77 | Priority: 90 78 | SortPriority: 90 79 | - Regex: '.*' 80 | Priority: 100 81 | SortPriority: 100 82 | IncludeIsMainRegex: '([.][a-z]+)?$' 83 | IndentCaseBlocks: true 84 | IndentCaseLabels: true 85 | #IndentExternBlock: Indent 86 | IndentGotoLabels: false 87 | IndentPPDirectives: AfterHash 88 | IndentWrappedFunctionNames: false 89 | KeepEmptyLinesAtTheStartOfBlocks: false 90 | MaxEmptyLinesToKeep: 1 91 | NamespaceIndentation: All 92 | PointerAlignment: Left 93 | PenaltyReturnTypeOnItsOwnLine: 100000 94 | ReflowComments: true 95 | SortIncludes: true 96 | SortUsingDeclarations: true 97 | SpaceAfterCStyleCast: false 98 | SpaceAfterLogicalNot: false 99 | SpaceAfterTemplateKeyword: true 100 | SpaceBeforeAssignmentOperators: true 101 | SpaceBeforeCpp11BracedList: false 102 | SpaceBeforeInheritanceColon: true 103 | SpaceBeforeParens: ControlStatements 104 | SpaceBeforeRangeBasedForLoopColon: true 105 | SpaceBeforeSquareBrackets: false 106 | SpaceInEmptyBlock: false 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInCStyleCastParentheses: false 111 | SpacesInConditionalStatement: false 112 | SpacesInContainerLiterals: false 113 | SpacesInParentheses: false 114 | SpacesInSquareBrackets: false 115 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | FormatStyle: "file" 2 | Checks: >- 3 | -*, 4 | bugprone-*, 5 | cppcoreguidelines-*, 6 | modernize-*, 7 | performance-*, 8 | portability-*, 9 | readability-*, 10 | -cppcoreguidelines-avoid-c-arrays, 11 | -cppcoreguidelines-avoid-magic-numbers, 12 | -cppcoreguidelines-macro-usage, 13 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 14 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 15 | -cppcoreguidelines-pro-bounds-constant-array-index, 16 | -cppcoreguidelines-pro-type-const-cast, 17 | -cppcoreguidelines-pro-type-member-init, 18 | -cppcoreguidelines-pro-type-reinterpret-cast, 19 | -cppcoreguidelines-pro-type-union-access, 20 | -cppcoreguidelines-pro-type-vararg, 21 | -cppcoreguidelines-owning-memory, 22 | -modernize-avoid-c-arrays, 23 | -modernize-raw-string-literal, 24 | -modernize-unary-static-assert, 25 | -modernize-use-nodiscard, 26 | -modernize-use-trailing-return-type, 27 | -readability-braces-around-statements, 28 | -readability-convert-member-functions-to-static, 29 | -readability-else-after-return, 30 | -readability-implicit-bool-conversion, 31 | -readability-magic-numbers, 32 | -readability-named-parameter, 33 | -readability-uppercase-literal-suffix, 34 | HeaderFilterRegex: nanofmt/.*[.]h 35 | WarningsAsErrors: "*" 36 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | [*.rst] 12 | indent_size = 2 13 | 14 | [*.{cpp,c,h}] 15 | vc_generate_documentation_comments = doxygen_triple_slash 16 | 17 | [*.{json,yml,natvis,xml,json.in,vsconfig}] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bat text eol=crlf 3 | *.sh text eol=lf 4 | *.png filter=lfs diff=lfs merge=lfs -text 5 | *.obj filter=lfs diff=lfs merge=lfs -text 6 | *.dll filter=lfs diff=lfs merge=lfs -text 7 | *.o filter=lfs diff=lfs merge=lfs -text 8 | *.lib filter=lfs diff=lfs merge=lfs -text 9 | *.a filter=lfs diff=lfs merge=lfs -text 10 | *.svg filter=lfs diff=lfs merge=lfs -text 11 | *.tga filter=lfs diff=lfs merge=lfs -text 12 | *.bin filter=lfs diff=lfs merge=lfs -text 13 | *.jpg filter=lfs diff=lfs merge=lfs -text 14 | *.exr filter=lfs diff=lfs merge=lfs -text 15 | *.zip filter=lfs diff=lfs merge=lfs -text 16 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'releases/**' 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | runs-on: ['ubuntu-latest'] 15 | cxx: ['g++', 'clang++'] 16 | config: ['DEBUG', 'RELEASE'] 17 | cxxflags: ['-Wall -Werror -pedantic'] 18 | cmake-defines: [''] 19 | include: 20 | - runs-on: 'ubuntu-latest' 21 | cxx: 'g++' 22 | config: 'DEBUG' 23 | cxxflags: '-Wall -Werror -pedantic' 24 | cmake-defines: '-DNANOFMT_FLOAT=OFF' 25 | - runs-on: 'windows-latest' 26 | cxx: cl.exe 27 | config: 'DEBUG' 28 | cxxflags: /W4 /WX 29 | - runs-on: 'windows-latest' 30 | cxx: cl.exe 31 | config: 'RELEASE' 32 | cxxflags: /W4 /WX 33 | 34 | runs-on: ${{ matrix.runs-on }} 35 | name: Build (${{ matrix.cxx }}, ${{ matrix.config }}) 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - name: Configure 41 | env: 42 | CXX: ${{ matrix.cxx }} 43 | CXXFLAGS: ${{ matrix.cxxflags }} 44 | run: cmake -B ${{ github.workspace }}/build ${{ matrix.cmake-defines }} -DCMAKE_BUILD_TYPE=${{ matrix.config }} 45 | 46 | - name: Build 47 | run: cmake --build ${{ github.workspace }}/build --config ${{ matrix.config }} 48 | 49 | - name: Test 50 | working-directory: ${{ github.workspace }}/build 51 | run: ctest --no-tests=error -C ${{ matrix.config }} 52 | 53 | - name: Install 54 | working-directory: ${{ github.workspace }}/build 55 | if: ${{ matrix.config }} == 'RELEASE' 56 | run: cmake --install ${{ github.workspace }}/build --prefix ${{ github.workspace }}/install --config ${{ matrix.config }} 57 | 58 | - name: Archive 59 | uses: actions/upload-artifact@v2 60 | if: ${{ matrix.config }} == 'RELEASE' 61 | with: 62 | name: build-${{ matrix.compiler }} 63 | path: ${{ github.workspace }}/install 64 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'releases/**' 8 | pull_request: 9 | 10 | jobs: 11 | build-docs: 12 | runs-on: 'ubuntu-latest' 13 | name: Sphinx 14 | 15 | env: 16 | BUILD_TYPE: RELEASE 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Install 22 | working-directory: ${{ github.workspace }}/docs 23 | run: | 24 | sudo pip3 install -r requirements.txt 25 | 26 | - name: Mkdir 27 | run: mkdir -p ${{ github.workspace }}/build 28 | 29 | - name: Sphinx 30 | working-directory: ${{ github.workspace }}/build 31 | run: sphinx-build -W ${{ github.workspace }}/docs ${{ github.workspace }}/build/html 32 | 33 | - name: Artifact 34 | uses: actions/upload-artifact@v2 35 | with: 36 | name: html-docs 37 | path: build/html 38 | -------------------------------------------------------------------------------- /.github/workflows/tidy.yml: -------------------------------------------------------------------------------- 1 | name: Tidy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'releases/**' 8 | pull_request: 9 | 10 | jobs: 11 | clang-tidy: 12 | runs-on: 'ubuntu-latest' 13 | name: Tidy 14 | 15 | env: 16 | CXX: clang++ 17 | BUILD_TYPE: RELEASE 18 | CLANG_TIDY: clang-tidy-11 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Install 24 | run: sudo apt-get install -y ${{ env.CLANG_TIDY }} 25 | 26 | - name: Configure 27 | run: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON 28 | 29 | - name: Tidy 30 | run: | 31 | for file in ${{ github.workspace }}/source/*.cpp ; do 32 | ${{ env.CLANG_TIDY }} --quiet -p ${{ github.workspace }}/build ${file} 33 | done 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build output directory 2 | /out/ 3 | 4 | # Application directories 5 | /.vs/ 6 | /.vscode/ 7 | 8 | # Generated files 9 | /tasks.vs.json 10 | /launch.vs.json 11 | enc_temp_folder 12 | docs/build 13 | docs/doxyoutput 14 | CMakeUserPresets.json 15 | -------------------------------------------------------------------------------- /.issuetracker: -------------------------------------------------------------------------------- 1 | # Integration with Issue Tracker 2 | # 3 | # (note that '\' need to be escaped). 4 | 5 | [issuetracker "GitHub PRs"] 6 | regex = "\\s\\(#(\\d+)\\)$" 7 | url = "https://github.com/potatoengine/nanofmt/pull/$1" 8 | 9 | [issuetracker "GitHub Issues"] 10 | regex = "#(\\d+)" 11 | url = "https://github.com/potatoengine/nanofmt/issues/$1" 12 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.readthedocs.io/en/stable/config-file/v2.html 2 | version: 2 3 | 4 | build: 5 | os: ubuntu-22.04 6 | tools: 7 | python: "3.11" 8 | 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | formats: 13 | - pdf 14 | 15 | python: 16 | install: 17 | - requirements: docs/requirements.txt 18 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | project(nanofmt VERSION 0.2 LANGUAGES CXX) 3 | 4 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 5 | 6 | set(NANOFMT_IS_TOPLEVEL FALSE) 7 | if (${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME}) 8 | set(NANOFMT_IS_TOPLEVEL TRUE) 9 | endif() 10 | 11 | set(NANOFMT_NS "" CACHE STRING "Namespace for nanofmt API and implementation") 12 | 13 | option(NANOFMT_TESTS "Build nanofmt tests" ${NANOFMT_IS_TOPLEVEL}) 14 | option(NANOFMT_DOCS "Build sphinx and doxygen documentation" OFF) 15 | option(NANOFMT_INSTALL "Add install targets for nanofmt" ON) 16 | option(NANOFMT_FLOAT "Enable support for float and double" ON) 17 | 18 | add_library(nanofmt STATIC) 19 | add_library(nanofmt::nanofmt ALIAS nanofmt) 20 | target_include_directories(nanofmt 21 | PRIVATE 22 | include 23 | INTERFACE 24 | $ 25 | $ 26 | ) 27 | target_compile_features(nanofmt PUBLIC cxx_std_17) 28 | 29 | if (NANOFMT_FLOAT) 30 | target_compile_definitions(nanofmt PUBLIC -DNANOFMT_FLOAT=1) 31 | endif() 32 | 33 | if (NANOFMT_NS) 34 | target_compile_definitions(nanofmt PUBLIC -DNANOFMT_NS=${NANOFMT_NS}) 35 | endif() 36 | 37 | add_subdirectory(source) 38 | add_subdirectory(include/nanofmt) 39 | 40 | if (NANOFMT_TESTS) 41 | enable_testing() 42 | add_subdirectory(tests) 43 | endif() 44 | 45 | if (NANOFMT_DOCS) 46 | add_subdirectory(docs) 47 | endif() 48 | 49 | if (NANOFMT_INSTALL) 50 | install(TARGETS nanofmt DESTINATION lib EXPORT nanofmtTargets) 51 | install(DIRECTORY include/nanofmt 52 | TYPE INCLUDE 53 | FILES_MATCHING 54 | PATTERN "*.h" 55 | ) 56 | install(EXPORT nanofmtTargets 57 | NAMESPACE nanofmt 58 | FILE nanofmtTargets.cmake 59 | DESTINATION lib/cmake/nanofmt 60 | ) 61 | 62 | include(CMakePackageConfigHelpers) 63 | configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in 64 | "${CMAKE_CURRENT_BINARY_DIR}/nanofmtConfig.cmake" 65 | INSTALL_DESTINATION "lib/cmake/example" 66 | NO_SET_AND_CHECK_MACRO 67 | NO_CHECK_REQUIRED_COMPONENTS_MACRO 68 | ) 69 | write_basic_package_version_file( 70 | "${CMAKE_CURRENT_BINARY_DIR}/nanofmtConfigVersion.cmake" 71 | VERSION "${nanofmt_VERSION_MAJOR}.${nanofmt_VERSION_MINOR}" 72 | COMPATIBILITY AnyNewerVersion 73 | ) 74 | 75 | install(FILES 76 | ${CMAKE_CURRENT_BINARY_DIR}/nanofmtConfig.cmake 77 | ${CMAKE_CURRENT_BINARY_DIR}/nanofmtConfigVersion.cmake 78 | DESTINATION lib/cmake/nanofmt 79 | ) 80 | endif() 81 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 21, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "msvc-debug-x64", 11 | "displayName": "Windows x64 (Debug)", 12 | "description": "Build with current MSC compiler for Windows x64 (Debug)", 13 | "generator": "Ninja", 14 | "binaryDir": "${sourceDir}/out/build/${presetName}", 15 | "toolset": "x64", 16 | "architecture": { 17 | "value": "x64", 18 | "strategy": "external" 19 | }, 20 | "environment": { 21 | "CXXFLAGS": "/W4 /WX" 22 | }, 23 | "cacheVariables": { 24 | "CMAKE_BUILD_TYPE": "Debug", 25 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 26 | }, 27 | "vendor": { 28 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 29 | "hostOS": [ "Windows" ] 30 | } 31 | } 32 | }, 33 | { 34 | "name": "wsl-gcc-debug-x64", 35 | "displayName": "WSL-GCC x64 (Debug)", 36 | "description": "Build with installed GCC compiler on WSL for x64 (Debug)", 37 | "generator": "Ninja", 38 | "binaryDir": "${sourceDir}/out/build/${presetName}", 39 | "architecture": { 40 | "value": "x64", 41 | "strategy": "external" 42 | }, 43 | "cacheVariables": { 44 | "CMAKE_BUILD_TYPE": "Debug", 45 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 46 | }, 47 | "environment": { 48 | "CXX": "g++", 49 | "CC": "gcc", 50 | "CXXFLAGS": "-Wall -Werror -pedantic" 51 | }, 52 | "vendor": { 53 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 54 | "hostOS": [ "Linux" ], 55 | "intelliSenseMode": "linux-gcc-x64" 56 | } 57 | } 58 | }, 59 | { 60 | "name": "wsl-clang-debug-x64", 61 | "displayName": "WSL-Clang x64 Debug", 62 | "description": "Build with installed clang compiler on WSL for x64 (Debug)", 63 | "generator": "Ninja", 64 | "binaryDir": "${sourceDir}/out/build/${presetName}", 65 | "architecture": { 66 | "value": "x64", 67 | "strategy": "external" 68 | }, 69 | "cacheVariables": { 70 | "CMAKE_BUILD_TYPE": "Debug", 71 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", 72 | "CMAKE_EXPORT_COMPILE_COMMANDS": true 73 | }, 74 | "environment": { 75 | "CXX": "clang++", 76 | "CC": "clang", 77 | "CXXFLAGS": "-Wall -Werror -pedantic" 78 | }, 79 | "vendor": { 80 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 81 | "hostOS": [ "Linux" ], 82 | "intelliSenseMode": "linux-gcc-x64" 83 | } 84 | } 85 | } 86 | ] 87 | } 88 | -------------------------------------------------------------------------------- /Config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/nanofmtTargets.cmake") 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Sean Middleditch and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /NEWs.md: -------------------------------------------------------------------------------- 1 | nanofmt 2 | ======= 3 | 4 | Release 0.3 (WIP) 5 | ----------------- 6 | 7 | ### Breaking Changes 8 | 9 | - Renamed `format_output` to `format_context`. 10 | - Custom formatter parsing now uses `format_parse_context`. 11 | 12 | ### Features 13 | 14 | ### Bug Fixes 15 | 16 | ### Infrastructure 17 | 18 | - Switch from Catch2 to doctest for testing framework. 19 | 20 | Release 0.2 21 | ----------- 22 | 23 | ### Features 24 | 25 | - Added `NANOFMT_FLOAT` option to disable float support. 26 | - Support implicit conversion to `format_string`. 27 | 28 | ### Bug Fixes 29 | 30 | - Correct `format_length` with integer format args (#54). 31 | 32 | Release 0.1 33 | ----------- 34 | 35 | - Initial release. 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nanofmt 2 | ======= 3 | 4 | https://github.com/potatoengine/nanofmt 5 | 6 | License 7 | ------- 8 | 9 | Copyright (c) Sean Middleditch and contributors 10 | 11 | nanofmt is released under the [MIT license][mitlic]. 12 | 13 | nanofmt uses the [Dragonbox][drgbox] reference implementation which released 14 | under either the [Apache License Version 2.0 with LLVM Exceptions][apclic] or 15 | the [Boost Software License Version 1.0][boolic]. 16 | 17 | [Documentation][apidoc] 18 | 19 | [apidoc]: https://nanofmt.readthedocs.io/ 20 | [mitlic]: https://mit-license.org/ 21 | [drgbox]: https://github.com/jk-jeon/dragonbox/ 22 | [apclic]: https://raw.githubusercontent.com/jk-jeon/dragonbox/master/LICENSE-Apache2-LLVM 23 | [boolic]: https://raw.githubusercontent.com/jk-jeon/dragonbox/master/LICENSE-Boost 24 | 25 | Exmaple 26 | ------- 27 | 28 | ```c++ 29 | char buffer[128]; 30 | 31 | char const* const end = nanofmt::format_to_n( 32 | buffer, 33 | sizeof buffer, 34 | "Hello, {0}! You are visitor {1}.", 35 | user_name, 36 | visitor_count); 37 | 38 | // void write_log(char const* str, size_t length); 39 | write_log(buffer, end - buffer); 40 | ``` 41 | 42 | About 43 | ----- 44 | 45 | nanofmt aims to provide a lite-weight semi-compatible implementation of the 46 | excellent [fmtlib][fmtlib]. This can be used in environments or team cultures 47 | where neither [`std::format`][stdfmt] nor fmtlib are available for use. 48 | 49 | [fmtlib]: https://github.com/fmtlib/fmt 50 | [stdfmt]: https://en.cppreference.com/w/cpp/utility/format/format 51 | 52 | The primary motivation for nanofmt is to minimize dependencies on standard C++ 53 | headers and to minimize compile time. nanofmt does _not_ aim to offer the 54 | fastest runtime efficiency, the most features, fmtlib or `std::format` 55 | compatibility, nor the most portable implementation. 56 | 57 | C++17 is required. 58 | 59 | Most notably, nanofmt _only_ supports the `(v)format_to_n` interfaces to basic 60 | `char*` buffers and does not support any other character type or output 61 | iterators. There are custom `(v)format_to` wrappers that work only with the 62 | provided `buffer` type, which itself is just a wrapper around `char*` buffers. 63 | 64 | nanofmt does _not_ aim to be a true drop-in replacement of fmtlib, even for 65 | the interfaces found in both libraries. Some interfaces have been modified 66 | to appeal more to "C with Classes" programmers; this is not a judgement of 67 | modern C++, just an acquiescence to the prevalent tastes and opinions in 68 | the industries targetted by nanofmt (primarily the AAA game industry). 69 | 70 | Support for floating-point types can optionally be disabled via the 71 | `NANOFMT_FLOAT` CMake option. Pass `-DNANOFMT_FLOAT=OFF` on the CMake 72 | command line to disable. 73 | 74 | When to Use nanofmt 75 | ------------------- 76 | 77 | If you're in doubt, use fmtlib! It is in almost every conceivable way more 78 | complete, more runtime efficient, more reliable, better maintained, more 79 | portable, and otherwise superior. 80 | 81 | Use nanofmt if your organization or team has decided they won't use fmtlib 82 | because of its standard library dependencies or its large-ish header sizes. 83 | 84 | Use nanofmt if your organization or team believes that `snprintf` is the 85 | superior formatting library, other than limitations created by C varargs. 86 | 87 | When NOT to Use nanofmt 88 | ----------------------- 89 | 90 | Literally anytime that you have the option of using `std::format` or fmtlib, 91 | you should prefer using those instead! 92 | 93 | If you're not sure if nanofmt is the right fit for your project, then 94 | it isn't. :) 95 | -------------------------------------------------------------------------------- /cmake/fetch_doctest.cmake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | doctest 5 | SYSTEM 6 | GIT_REPOSITORY https://github.com/doctest/doctest.git 7 | GIT_TAG v2.4.11 8 | ) 9 | 10 | FetchContent_MakeAvailable(doctest) 11 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_program(NANOFMT_SPHINX_PATH "sphinx-build" REQUIRED DOC "Path to sphinx-build") 2 | 3 | add_custom_target(nanofmt_docs 4 | COMMAND ${NANOFMT_SPHINX_PATH} -W ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/html 5 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 6 | USES_TERMINAL 7 | SOURCES 8 | conf.py 9 | api.rst 10 | benchmarks.rst 11 | design.rst 12 | faq.rst 13 | index.rst 14 | license.rst 15 | requirements.txt 16 | ) 17 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | :github_url: https://github.com/seanmiddleditch/nanofmt/blob/main/docs/api.rst 2 | 3 | .. _api: 4 | 5 | API 6 | === 7 | 8 | .. contents:: 9 | 10 | .. _format-api: 11 | 12 | Formatting 13 | ---------- 14 | 15 | The format API is available in the header ``nanofmt/format.h``. 16 | 17 | The header ``nanofmt/forward.h`` offers forward declarations of 18 | nanofmt types, including the ``formatter`` template that users 19 | must specialize to support custom types. 20 | 21 | Extensions for C++ standard library string types are in the header 22 | ``nanofmt/std_string.h``. 23 | 24 | Format to Array 25 | ^^^^^^^^^^^^^^^ 26 | 27 | The :cpp:func:`nanofmt::format_to` functions format a given format string 28 | and arguments into the target buffer. The result will be NUL-terminated. 29 | The return value is a pointer to the terminating NUL character. 30 | 31 | The :cpp:func:`nanofmt::format_append_to` functions format a given format 32 | string and arguments onto the end of target buffer. The result will be NUL- 33 | terminated. The return value is a pointer to the terminating NUL character. 34 | 35 | .. cpp:function:: char* nanofmt::format_to(char (&dest)[N], format_string format_str, Args const&... args) 36 | 37 | .. cpp:function:: char* nanofmt::vformat_to(char (&dest)[N], format_string format_str, format_args args) 38 | 39 | .. cpp:function:: char* nanofmt::format_append_to(char (&dest)[N], format_string format_str, Args const&... args) 40 | 41 | .. cpp:function:: char* nanofmt::vformat_append_to(char (&dest)[N], format_string format_str, format_args args) 42 | 43 | Length-Delimited Formatting 44 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 45 | 46 | The :cpp:func:`nanofmt::format_to_n` functions format a given format string 47 | and arguments into the target buffer, up to the given number of characters. 48 | The result will **NOT** be NUL-terminated. The return value is a pointer to 49 | one past the last character written. 50 | 51 | The :cpp:func:`nanofmt::format_append_to_n` functions format a given format 52 | string and arguments onto the end of the target buffer, up to the given 53 | number of characters. The result will **NOT** be NUL-terminated. The return 54 | value is a pointer to one past the last character written. 55 | 56 | .. cpp:function:: char* nanofmt::format_to_n(char* dest, std::size_t count, format_string format_str, Args const&... args) 57 | 58 | .. cpp:function:: char* nanofmt::vformat_to_n(char* dest, std::size_t count, format_string format_str, format_args&&) 59 | 60 | .. cpp:function:: char* nanofmt::format_append_to_n(char* dest, std::size_t count, format_string format_str, Args const&... args) 61 | 62 | .. cpp:function:: char* nanofmt::vformat_append_to_n(char* dest, std::size_t count, format_string format_str, format_args&&) 63 | 64 | Custom Formatters 65 | ^^^^^^^^^^^^^^^^^ 66 | 67 | The :cpp:struct:`nanofmt::formatter` template must be specialized to add 68 | support for user-provided types. 69 | 70 | Two member functions, ``parse`` and ``format``, must be implemented on the 71 | specialized structure for nanofmt to work. 72 | 73 | .. cpp:struct:: template nanofmt::formatter 74 | 75 | Custom formatter. May include any member variables necesasry to convey 76 | format information from ``parse`` to ``format``. 77 | 78 | .. cpp:function:: char const* parse(char const* in, char const* end) 79 | 80 | Consumes characters from ``in`` up to, but not including, ``end``. 81 | Returns a pointer to one past the last character consumed. 82 | 83 | .. cpp:function:: void format(T const& value, format_output& out) const 84 | 85 | Formats ``value`` to ``out``. 86 | 87 | A header implementing a custom formatter may choose to only depend on 88 | ``nanofmt/foward.h`` header. This header does not offer any of the 89 | implementations, nor does it provide declarations of the formatting 90 | functions. A formatter may work around this by specifying the 91 | ``format_output&`` parameter of ``format`` as a template, as in: 92 | 93 | .. code-block:: c++ 94 | 95 | #include 96 | 97 | namespace nanofmt { 98 | template<> 99 | struct formatter { 100 | constexpr char const* parse(char const* in, char const*) noexcept; 101 | 102 | template 103 | void format(my_type const& value, OutputT& output); 104 | } 105 | } 106 | 107 | Format Length 108 | ^^^^^^^^^^^^^ 109 | 110 | The :cpp:func:`nanofmt::format_length` function returns the length of result 111 | of formatting the given format string and arguments, excluding any 112 | terminating NUL character. 113 | 114 | .. cpp:function:: size_t nanofmt::format_length(format_string format_str, Args const&... args) 115 | 116 | .. cpp:function:: size_t nanofmt::vformat_length(format_string format_str, format_args args) 117 | 118 | Output Buffers 119 | ^^^^^^^^^^^^^^ 120 | 121 | .. cpp:struct:: nanofmt::format_output 122 | 123 | Format to Buffer 124 | ^^^^^^^^^^^^^^^^ 125 | 126 | The ``nanofmt::format_output&`` overloads of :cpp:func:`nanofmt::format_to` 127 | format a given format string and arguments into the target buffer. The result 128 | will **not** be NUL-terminated. The return value is the buffer object itself. 129 | 130 | .. cpp:function:: format_output& format(format_string fmt, Args const&... args) 131 | 132 | Formats the given format string and argument into the buffer. 133 | 134 | .. cpp:function:: format_output& vformat(format_string fmt, format_args args) 135 | 136 | Formats the given format string and argument into the buffer. 137 | 138 | .. cpp:function:: constexpr format_output& append(char const* const zstr) noexcept 139 | 140 | Appends the contents of ``zstr`` to the buffer. 141 | 142 | .. cpp:function:: constexpr format_output& append(char const* source, std::size_t length) noexcept 143 | 144 | Appends ``length`` characters from ``source`` to the buffer. 145 | 146 | .. cpp:function:: constexpr format_output& put(char ch) noexcept 147 | 148 | Appends the character ``ch`` to the buffer. 149 | 150 | .. cpp:function:: constexpr format_output& fill_n(char ch, std::size_t count) noexcept 151 | 152 | Appends ``count`` copies of the character ``ch`` to the buffer. 153 | 154 | .. cpp:function:: constexpr format_output& advance_to(char* const p) noexcept 155 | 156 | Updates the buffer position to ``p`` and adjusts the ``advance`` member appropriately. 157 | 158 | .. cpp:member:: char* pos = nullptr 159 | 160 | Current output position of the buffer. For custom formatting operations, 161 | use this value for the output position. The :cpp:func:`advance_to` 162 | function should always be preferred for mutating the ``pos`` member. 163 | 164 | .. cpp:member:: char const* end = nullptr 165 | 166 | The end pointer for the buffer. Custom formatting code should never 167 | advance ``pos`` past the ``end`` pointer, and should never dereference 168 | ``end``. 169 | 170 | .. cpp:member:: std::size_t advance = 0 171 | 172 | The number of characters that were written to the buffer, ignoring any 173 | truncation. Even when ``pos`` equals ``end``, operations on the buffer 174 | will still increment ``advance``. 175 | 176 | The :cpp:func:`advance_to` member function should be preferred over 177 | directly mutating ``advance``. 178 | 179 | String Utilities 180 | ^^^^^^^^^^^^^^^^ 181 | 182 | General string utiltities that are useful in implementing formatting. 183 | 184 | .. cpp:function:: char* copy_to(char* dest, char const* end, char const* source) noexcept 185 | 186 | Copy the source string to the destination buffer, but not extending past 187 | the provided buffer end pointer. Returns the pointer one past the last 188 | character written. 189 | 190 | .. cpp:function:: char* copy_to_n(char* dest, char const* end, char const* source, std::size_t length) noexcept 191 | 192 | Copies up to ``length`` characters of source string to the destination 193 | buffer, but not extending past the provided buffer end pointer. Returns 194 | the pointer past the last character written. 195 | 196 | .. cpp:function:: char* put(char* dest, char const* end, char ch) noexcept 197 | 198 | Copies the provided character ``ch`` to the destination buffer, but not 199 | extending past the provided buffer end pointer. Returns the pointer past 200 | the last character written. 201 | 202 | .. cpp:function:: char* fill_n(char* dest, char const* end, char ch, std::size_t count) noexcept 203 | 204 | Copies ``count`` copies of the charcter ``ch`` to the destination buffer, 205 | but not extending past the provided buffer end pointer. Returns the 206 | pointer past the last character written. 207 | 208 | .. cpp:function:: std::size_t strnlen(char const* buffer, std::size_t count) noexcept 209 | 210 | Returns the length of the string in buffer, to a maximum of count. 211 | 212 | Format Strings 213 | ^^^^^^^^^^^^^^ 214 | 215 | nanofmt uses a :cpp:struct:`nanofmt::format_string` structure for receiving 216 | its format strings, to decouple from and support various string types and 217 | classes. Many string types should automatically convert to ``format_string``; 218 | for string types that don't already support conversion to ``format_string``, 219 | a :cpp:func:`nanofmt::to_format_string` function can be implemented. 220 | 221 | .. cpp:struct:: nanofmt::format_string 222 | 223 | Receiver for format strings. Only implicitly constructs from string 224 | literals (constant character arrays). Can be explicitly constructed 225 | from other string types. 226 | 227 | .. cpp:function:: template format_string nanofmt::to_format_string(T const& value) noexcept 228 | 229 | Converts a given string type to a :cpp:struct:`nanofmt::format_string`. 230 | Works on most standard string types with ``data()`` and ``size()`` 231 | member functions. 232 | 233 | Overload to add support for other string types that require different 234 | means of converted to a ``format_string``. 235 | 236 | .. cpp:struct:: nanofmt::format_string_view 237 | 238 | A very simple wrapper around a pointer and length. This is provided to make 239 | it easier to write :cpp:struct:`nanofmt::formatter` specializations that 240 | work on length-delimited string views, by deriving from 241 | ``nanofmt::formatter``. 242 | 243 | .. cpp:member:: char const* string = nullptr 244 | 245 | .. cpp:member:: std::size_t length = 0 246 | 247 | Variadic Arguments 248 | ^^^^^^^^^^^^^^^^^^ 249 | 250 | .. cpp:struct:: nanofmt::format_args 251 | 252 | List of format args. Typically only constructed from 253 | :cpp:func:`nanofmt::make_format_args`. Does not take ownership of any 254 | internal data. 255 | 256 | .. warning:: Storing an instance of ``format_args`` can result 257 | in dangling references. 258 | 259 | .. cpp:function:: auto nanofmt::make_format_args(Args const&... args) 260 | 261 | .. danger:: This function should usually only be used directly in 262 | a call to a function accepting a :cpp:struct:`nanofmt::format_args` 263 | parameter. 264 | 265 | .. _to-char-api: 266 | 267 | Character Conversion 268 | -------------------- 269 | 270 | The character conversion API is available in the header ``nanofmt/charconv.h``. 271 | 272 | .. cpp:function:: char* nanofmt::to_chars(char* dest, char const* end, IntegerT value, int_format fmt = int_format::decimal) noexcept 273 | 274 | Formats ``value`` into the buffer using the base specified in ``fmt``. 275 | 276 | .. cpp:function:: char* nanofmt::to_chars(char* dest, char const* end, FloatT value, float_format fmt) noexcept 277 | 278 | Formats ``value`` into the buffer using the base specified in ``fmt``. Uses 279 | the shortest precision. 280 | 281 | .. cpp:function:: char* nanofmt::to_chars(char* dest, char const* end, FloatT value, float_format fmt, int precision) noexcept 282 | 283 | Formats ``value`` into the buffer using the base specified in ``fmt``. Uses 284 | the given ``precision``, whose meaning depends on the specified format. 285 | 286 | .. cpp:enum-class:: nanofmt::int_format 287 | 288 | Specify whether to use base 10, base 16, or base 2. Base 16 has an uppercase 289 | variant. 290 | 291 | .. cpp:enumerator:: decimal 292 | 293 | Base 10. 294 | 295 | .. cpp:enumerator:: hex 296 | 297 | Base 16. 298 | 299 | .. cpp:enumerator:: hex_upper 300 | .. cpp:enumerator:: binary 301 | 302 | Base 2. 303 | 304 | .. cpp:enum-class:: nanofmt::float_format 305 | 306 | Specify whether to use scientific, fixed, or general precision formatting. 307 | Scientific and general also have uppercase variants. 308 | 309 | .. cpp:enumerator:: scientific 310 | 311 | Scientific notation formats floating point values as ``[-]d.de[+-]dd``. 312 | 313 | .. cpp:enumerator:: scientific_upper 314 | 315 | .. cpp:enumerator:: fixed 316 | 317 | Fixed-point notation formats floating point values as ``[-]d.dddd``. 318 | 319 | .. cpp:enumerator:: general 320 | 321 | General precision notation formats in either scientific or fixed-point 322 | notation, depending on the exponent of the value. 323 | 324 | .. cpp:enumerator:: general_upper 325 | -------------------------------------------------------------------------------- /docs/benchmarks.rst: -------------------------------------------------------------------------------- 1 | :github_url: https://github.com/seanmiddleditch/nanofmt/blob/main/docs/benchmarks.rst 2 | 3 | Benchmarks 4 | ========== 5 | 6 | Compilation 7 | ----------- 8 | 9 | TBD 10 | 11 | Execution 12 | --------- 13 | 14 | TBD 15 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'nanofmt' 23 | copyright = '2021, Sean Middleditch and contributors' 24 | author = 'Sean Middleditch and contributors' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | #extensions = [] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix(es) of source filenames. 47 | # You can specify multiple suffix as a list of string: 48 | # 49 | # source_suffix = ['.rst', '.md'] 50 | source_suffix = '.rst' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | # 58 | # This is also used if you do content translation via gettext catalogs. 59 | # Usually you set "language" from the command line for these cases. 60 | language = 'en' 61 | 62 | # List of patterns, relative to source directory, that match files and 63 | # directories to ignore when looking for source files. 64 | # This pattern also affects html_static_path and html_extra_path. 65 | exclude_patterns = [] 66 | 67 | # The name of the Pygments (syntax highlighting) style to use. 68 | pygments_style = 'sphinx' 69 | 70 | 71 | # -- Options for HTML output ------------------------------------------------- 72 | 73 | # The theme to use for HTML and HTML Help pages. See the documentation for 74 | # a list of builtin themes. 75 | # 76 | html_theme = 'sphinx_rtd_theme' 77 | 78 | # Theme options are theme-specific and customize the look and feel of a theme 79 | # further. For a list of options available for each theme, see the 80 | # documentation. 81 | html_theme_options = { 82 | 'canonical_url': 'https://seanmiddleditch.github.io/nanofmt', 83 | 'style_external_links': True 84 | } 85 | 86 | # Add any paths that contain custom static files (such as style sheets) here, 87 | # relative to this directory. They are copied after the builtin static files, 88 | # so a file named "default.css" will overwrite the builtin "default.css". 89 | # html_static_path = ['_static'] 90 | 91 | # Custom sidebar templates, must be a dictionary that maps document names 92 | # to template names. 93 | # 94 | # The default sidebars (for documents that don't match any pattern) are 95 | # defined by theme itself. Builtin themes are using these templates by 96 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 97 | # 'searchbox.html']``. 98 | # 99 | # html_sidebars = {} 100 | 101 | 102 | # -- Options for HTMLHelp output --------------------------------------------- 103 | 104 | # Output file base name for HTML help builder. 105 | htmlhelp_basename = 'nanofmtdoc' 106 | 107 | 108 | # -- Options for LaTeX output ------------------------------------------------ 109 | 110 | latex_elements = { 111 | # The paper size ('letterpaper' or 'a4paper'). 112 | # 113 | # 'papersize': 'letterpaper', 114 | 115 | # The font size ('10pt', '11pt' or '12pt'). 116 | # 117 | # 'pointsize': '10pt', 118 | 119 | # Additional stuff for the LaTeX preamble. 120 | # 121 | # 'preamble': '', 122 | 123 | # Latex figure (float) alignment 124 | # 125 | # 'figure_align': 'htbp', 126 | } 127 | 128 | # Grouping the document tree into LaTeX files. List of tuples 129 | # (source start file, target name, title, 130 | # author, documentclass [howto, manual, or own class]). 131 | latex_documents = [ 132 | (master_doc, 'nanofmt.tex', 'nanofmt Documentation', 133 | 'Sean Middleditch and contributors', 'manual'), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [ 142 | (master_doc, 'nanofmt', 'nanofmt Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'nanofmt', 'nanofmt Documentation', 154 | author, 'nanofmt', 'One line description of project.', 155 | 'Miscellaneous'), 156 | ] 157 | 158 | 159 | # -- Options for Epub output ------------------------------------------------- 160 | 161 | # Bibliographic Dublin Core info. 162 | epub_title = project 163 | 164 | # The unique identifier of the text. This can be a ISBN number 165 | # or the project homepage. 166 | # 167 | # epub_identifier = '' 168 | 169 | # A unique identification for the text. 170 | # 171 | # epub_uid = '' 172 | 173 | # A list of files that should not be packed into the epub file. 174 | epub_exclude_files = ['search.html'] 175 | 176 | primary_domain = 'cpp' 177 | highlight_language = 'cpp' 178 | 179 | -------------------------------------------------------------------------------- /docs/design.rst: -------------------------------------------------------------------------------- 1 | :github_url: https://github.com/seanmiddleditch/nanofmt/blob/main/docs/design.rst 2 | 3 | Design 4 | ====== 5 | 6 | .. contents:: 7 | 8 | Origins 9 | ------- 10 | 11 | The design of nanofmt started by trying to closely match the interface of 12 | ``std::format`` and fmtlib. This includes the general naming of functions 13 | and types as well as the design of the ``make_format_args`` and related 14 | utilities. 15 | 16 | .. _design-output-iterators: 17 | 18 | Output Iterators 19 | ---------------- 20 | 21 | A major design note of nanofmt is that the only output target supported 22 | are ``char`` arrays. This is one of the largest simplifying factors in 23 | nanofmt vs either ``std::format`` or fmtlib. 24 | 25 | This is feasible since fixed-size buffers and the complete avoidance 26 | of any allocating routines is exceedingly common in nanofmt's target 27 | domains. In the rare cases that arbitrary-length formatting is 28 | required, the use of ``format_length`` allows pre-allocating any 29 | necessary buffer. 30 | 31 | For one, arbitrary output iterator support effectively requires all 32 | formatting code to live in headers, with only the occassional pieces 33 | living in individual TUs. This is because the ``format_context<>`` 34 | in ``std::format``/fmtlib is itself a type template. 35 | 36 | Supporting arbitrary output iterators requires having full output 37 | iterator machinery in the first place. Output iterators that need to 38 | functions with fixed-length outputs are non-trivial to write. 39 | 40 | More importantly, making such generalized output iterator support 41 | reasonably fast requires *even more* complexity in terms of temporary 42 | buffers, buffer flushing, and so on. Such buffering mechanisms are 43 | also required to enable any kind of efficient type-erasure of the output 44 | iterators. 45 | 46 | There's even further machinery necessary for making common output iterators 47 | efficient. Consider ``back_inserter``. An efficient implementation of 48 | ``std::format`` needs to detect that its output iterator is a ``back_inserter`` 49 | and transparently replace calls to ``push_back`` to more efficient ``append`` or 50 | ``insert`` invocations. 51 | 52 | The use of these output iterators is mostly to support things like streaming 53 | to console IO, to support ``push_back`` into containers like ``std::string``, or 54 | esoteric filtering mechanisms. None of these are essential to nanofmt's 55 | target use cases. 56 | 57 | The design and implementation pioneered by Victor Zverovich for fmtlib is some 58 | honestly amazing engineering! Victor and other fmtlib contributors deserve 59 | nothing but praise and respect for the incredible amount of work done to make 60 | fmtlib (and by extension ``std::format``) feel natural, intuitive, and 61 | unsurprising in C++ while still having exceptionally good runtime efficiency. 62 | 63 | nanofmt however is more for teams that feel that C-like APIs like ``snprintf`` 64 | are already the epitome of being natural, intuitive, and unsurprising; 65 | except of course for the limitations imposed by C's varargs vs C++'s 66 | variadic tempaltes. 67 | 68 | Ultimately, the complexity cost of supporting other kinds of output iterators 69 | is high, and the benefit for nanofmt users is low. 70 | 71 | Localization 72 | ------------ 73 | 74 | nanofmt only supports ``char`` and does not bother with any of ``wchar_t``/ 75 | ``char8_t``, ``char16_t``, or ``char32_t``. 76 | 77 | Additionally, the ``L`` format specification flag is parsed but 78 | ignored. 79 | 80 | nanofmt only supports ``char`` because that is, by and large, the only 81 | character type in active use in its target domains. Target systems can and do 82 | assume that all ``char*`` strings are UTF-8. The type-system bifurcation 83 | created by ``char8_t`` has caused problems for the few projects that took to 84 | using ``u8""`` literals, as it required every function taking a ``char*`` to offer 85 | a second overload that also accepted ``char8_t*``. The result has mostly been 86 | projects starting to use ``u8`` literals in C++17 and abandoning them soon 87 | after since C++20 compatibility changed the types in a very incompatible and 88 | gratuitous way. 89 | 90 | The localization flags are unsupported as the target uses of nanofmt tend not 91 | to ever bother with localization. Logging can actually be harmed by 92 | localization as it makes log parsers and alert systems far more difficult to 93 | deploy and maintain. "User-facing" formatting in nanofmt's target domains is 94 | generally just developer tools and utilities, which are effectively never 95 | localized due both to the cost of localizing propriety in-house tools and to 96 | the rapid rate of change in such tools; keeping localization up-to-date is 97 | not especially feasible in those environments. 98 | 99 | While nanofmt targets games developers in particular, and games *are* heavily 100 | and frequently localized, it is not expected that nanofmt would be used for 101 | player-facing text. Game UI text tends to use heavily specialized toolkits 102 | and rely on iconography, layout, color and style, and other factors to convey 103 | information; nanofmt-like text formatting is exceptionally rare in such UI. 104 | 105 | .. _design-char-conv: 106 | 107 | Character Conversion 108 | -------------------- 109 | 110 | nanofmt includes implementations of ``to_chars`` mostly because there are 111 | shipping "C++17" implementations that are very much in common use in target 112 | industries but which do not offer complete ``to_chars`` implementations. This 113 | is especially true for ``float``/``double``. 114 | 115 | nanofmt uses the Dragonbox reference implementation in its floating-point 116 | ``to_chars`` implementations. There are no fallbacks to other implementations 117 | as found in fmtlib or ``std::to_chars``. This in particular limits the 118 | precision of fixed-point formats in nanofmt. 119 | 120 | The precision limitation is not currently believed to be a showstopper, but 121 | may be revisited if use cases from nanofmt users illustrates a strong need 122 | for more intricate fixed-point formatting. 123 | 124 | The `Dragonbox`_ reference implementation is used for the work-horse portions 125 | of floating-point to decimal conversion. 126 | 127 | constexpr 128 | --------- 129 | 130 | Most of nanofmt is not constexpr. This is an intentional choice. 131 | 132 | Making a constexpr-friendly formatting library unfortunately requires that 133 | most of the implementation of the formatters and all supporting machinery 134 | also be constexpr, which in turn means it all has to live in headers. 135 | 136 | This might be a much smaller issue once we're all living with C++20 137 | modules, but today, the cost paid by every user for constexpr capabilties 138 | is very high. 139 | 140 | That said, constexpr formatting is a generally useful feature. nanofmt may, 141 | if use cases arise, offer a second ``const_format.h`` header or the like 142 | which includes/imports constexpr definitions of the format implementations. 143 | Such an approach would allow individual TUs to opt-in to pulling in all the 144 | machinery if and only if they actually need it. 145 | 146 | Note that we have thus far kept the parsing* part of nanofmt all constexpr 147 | capable, since we may wish to enable compile-time format string checking 148 | capabilities for projects that (wisely) prefer such a feature. All known 149 | potential users of nanofmt are not yet using C++20 so compile-time checking 150 | isn't a priority. We may instead opt to just entirely drop the constexpr 151 | parsing support if we decide we're not going to support it anytime soon. 152 | 153 | Alignment, Width, Alternate Forms, and Other Specifiers 154 | ------------------------------------------------------- 155 | 156 | nanofmt implements are relatively limited set of the fmtlib/``std::format`` 157 | specifiers. All are parsed, but most are ignored. 158 | 159 | This isn't a "design" so much as just not having had the use cases made 160 | for supporting all of them yet. Alternate form for integers is high on the 161 | list, though. 162 | 163 | The goal isn't to be feature-complete, and some of these specifiers are 164 | *juuust* annoying enough to implement that it'll only be done on-demand. 165 | 166 | .. _Dragonbox: https://github.com/jk-jeon/dragonbox/ 167 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | :github_url: https://github.com/seanmiddleditch/nanofmt/blob/main/docs/api.rst 2 | 3 | FAQ 4 | === 5 | 6 | .. contents:: 7 | 8 | Who is This For? 9 | ---------------- 10 | 11 | Developers and teams who are still choosing to use `snprintf`_ or related 12 | technologies instead of `fmtlib`_ or `std::format`_. 13 | 14 | Developers and teams who are using ``snprintf`` and have explicitly 15 | rejected `fmtlib`_, but still want to get its two biggest features: 16 | type-aware formatting and user-extensible type support. 17 | 18 | Said developers are still okay with accepting ``snprintf``'s other 19 | limitation of only being able to write to character buffers. 20 | 21 | Why?? 22 | ----- 23 | 24 | The author has worked on multiples teams in the AAA games industry that have 25 | strong cultural tendencies to prefering ``snprintf`` for all string 26 | formatting. Reasons cited are usually something like: 27 | 28 | - Minimizing dependencies, as a reason not to use `fmtlib`_. 29 | - Supporting older compilers, as a reason not to use ``std::format``. 30 | - Deep distrust of standard headers, as a reason to avoid ``std::format``; 31 | and any library that uses many standard headers, as a reason not to use 32 | `fmtlib`_. 33 | - Dislike of namespaces. Note that nanofmt at this time doesn't "help" here 34 | but it's a very possible/probable future evolution. 35 | - Compile times, as a reason to reinvent every wheel. 36 | 37 | Compile Times? Really? 38 | ---------------------- 39 | 40 | That's two questions, but yes and yes. 41 | 42 | It is common (though not universal!) in industries like AAA game development 43 | to heavily optimize for compilation times. The primary reason for this is 44 | *iteration speed*. 45 | 46 | This is a large, complex, and nuanced topic. It is very inaccurate to say that 47 | "all game developers" need or care about X. 48 | 49 | The best summary for this FAQ, though: some developers critically value having 50 | very fast edit-run-test cycles and will spend considerable time and effort 51 | up-front to make those cycles faster later. 52 | 53 | nanofmt is meant for teams who include "minimizing C++ compilation times" in 54 | their efforts to achieve the fastest edit-run-test cycles. 55 | 56 | Why Avoid Standard Headears? 57 | ---------------------------- 58 | 59 | Historically, many standard headers have been very "heavy," introducing 60 | tens or hundreds of thousands of lines of complex C++ into any translation 61 | unit including them. Compile times have suffered. 62 | 63 | Beyond the headers themselves, standard library implementations are generally 64 | written to a level of completeness and foolproof-ness that imposes costs 65 | some developers don't wish to pay for. 66 | 67 | For example, the `checked iterators_` machinery in MSVC and the equivalents 68 | in libstdc++ and libc++ impose a cost. Even when disabled, the cost 69 | is found in numerous small wrapper functions and other utilities that tend 70 | to decrease compiler throughput for an otherwise disabled feature. 71 | 72 | Teams that care about these items will often write code that is more like 73 | C, with plenty of raw pointers and thin abstractions, simply because it's 74 | easier and faster for the compiler to process. 75 | 76 | How Does nanofmt Help With Compile Times? 77 | ----------------------------------------- 78 | 79 | To be clear, nanofmt at this point has not been extensively benchmarked with 80 | any rigor, and we're only _assuming_ it helps. 81 | 82 | The design around not using :ref:`output iterators` 83 | is one way nanofmt aims to improve over `fmtlib`_ or ``std::format`` for teams 84 | that deeply care about compile times. 85 | 86 | In general, nanofmt is written to be more C-like in the sense that 87 | abstractions are minimal, plain values are used where possible, and raw 88 | pointers are used exclusively in place RAII containers, smart pointers, or 89 | iterator wrappers. 90 | 91 | The result is simply a lot less code to do (some of) the same stuff. The 92 | loss of abstractions necessarily comes with a large loss of functionality 93 | and flexibility. nanofmt is the result when the choice is made to lean in 94 | favor of simpler (and faster to compile) code rather than more featureful 95 | but complicated code. 96 | 97 | No IO Support For Real? 98 | ----------------------- 99 | 100 | Not at the time of writing, no. The author's experience is that actual 101 | direct-to-console writing is (relatively) rare, even with developer tools, 102 | in target domains. Direct IO to files is very rare, and directly IO to socket 103 | streams is close to unheard of, in target domains. 104 | 105 | Where console IO does happen, these are usually either tools that are far 106 | more open to using standard libraries or librarieies like `fmtlib`_. The few 107 | remaining cases can make do just fine with using ``char`` arrays as a 108 | temporary buffer. 109 | 110 | Yes, it's slower and more constricting to writing to a buffer before writing 111 | to (already buffered) standard output facilities. These aren't the areas of 112 | performance that the kinds of teams who might use nanofmt really care about, 113 | though. 114 | 115 | .. _faq-modules: 116 | 117 | Won't C++20 Modules Make This Obsolute? 118 | --------------------------------------- 119 | 120 | Maybe? Hopefully? Less duplicate code to maintain is only a good thing. I 121 | will only be happy if I never have to personally think about a floating- 122 | point string conversion routine again. 123 | 124 | The reality is likely a bit more murky. For one, nanofmt exists *now* but 125 | C++20 modules are possibly still years away from even being a viable option 126 | for new projects. At the time of writing, compiler support is incomplete and 127 | very buggy; build system support is nearly non-existant; module-aware linters 128 | and doc generators and like is also non-existant; and the general user and 129 | ecosystem support can best be described as nascent. 130 | 131 | Further, note that modules only help *part* of the compile time overhead. 132 | At best, we can expect modules to reduce the cost of parsing large header 133 | hierarchies. While that is a significant amount of the time incurred with 134 | compiling libraries like `fmtlib`_ or ``std::format``, another large chunk of 135 | the time goes into instantiating templates, resolving function overloads, 136 | evaluating constexpr functions, and so on. 137 | 138 | nanofmt, by virtue of steeply limiting its feature set and general 139 | applicability, aims to reduce the need for as much of that "use time" 140 | overhead as possible. While it's almost certainly impossible to hit the 141 | minimal compile-time of ``snprintf``, the goal is to keep the difference 142 | small enough that the "developer time" benefits of a type-safe user- 143 | extensible format library outweighs the compile time costs. 144 | 145 | What Does nanofmt Support? 146 | -------------------------- 147 | 148 | In general, it supports type-aware and user-extensible formatting using 149 | the `standard format specification`, mostly. 150 | 151 | It supports writing to length-delimited ``char*`` arrays. 152 | 153 | Support exists for formatting most standard built-in C++ types, including 154 | typical integer and floating-point types, booleans, characters, raw 155 | pointers, and C-style strings. 156 | 157 | The ``std_string.h`` header may be included for ``std::basic_string`` and 158 | ``std::basic_string_view`` support. 159 | 160 | What Does nanofmt Not Support? 161 | ------------------------------ 162 | 163 | There is no support for output iterators other than ``char*``. 164 | 165 | There is no support for character types other than ``char``. 166 | 167 | There is no support for locales. 168 | 169 | There is no formatter support for standard library types. The 170 | ``std_string.h`` header enables support for standard string types. 171 | 172 | There is no support for console or file IO. 173 | 174 | There is no support for versions of the language older than C++17. 175 | 176 | There is no drop-in API compatibility with either `fmtlib`_ or ``std::format``. 177 | 178 | There is no support for ``long double`` and no suport for ``(u)int128_t``. 179 | 180 | Any feature of `fmtlib`_ or ``std::format`` not explicitly named in this or 181 | the prior section should likely be considered unsupported. 182 | 183 | Does it Support Floating-Point Types? 184 | ------------------------------------- 185 | 186 | Yes, nanofmt has support for both ``float`` and ``double``. 187 | 188 | The `Dragonbox`_ reference implementation is used for the work-horse portions 189 | of float to decimal conversion. 190 | 191 | Was it Worth It? 192 | ---------------- 193 | 194 | Probably not. 195 | 196 | The nanofmt author has implemented several `fmtlib`_ replacements for work. 197 | 198 | In comparison, the author has been working on nanofmt for 12+ hours/day 199 | for about a week; and that doesn't include all the time the author spent 200 | building the precursors to nanofmt in personal projects, going all the way 201 | back to `formatxx`_ (an "ancient" C++11 library), and including 202 | re-writing formatxx to incorporate into commercial game codebases with 203 | specialized requirements (like drop-in `Boost.Format`_ compatibility). 204 | 205 | Will This be Maintained? 206 | ------------------------ 207 | 208 | Excellent question. 209 | 210 | ... too soon to tell. If having a dedicated maintainer is important to you, 211 | this library might be a little too new and untested for your needs. 212 | 213 | As stated in the :ref:`C++ modules FAQ question`, there's a very 214 | real future where this entire library is obsolete. To that end, while nanofmt 215 | is not a direct drop-in replacement for ``std::format``, it aims to be "close 216 | enough" that migrating from nanofmt to the standard equivalent is meant to be 217 | straightforward. 218 | 219 | .. _snprintf: https://en.cppreference.com/w/c/io/fprintf 220 | .. _std::format: https://en.cppreference.com/w/cpp/utility/format/format 221 | .. _fmtlib: https://github.com/fmtlib/fmt 222 | .. _`formatxx`: https://github.com/seanmiddleditch/formatxx/ 223 | .. _`Boost.Format`: https://www.boost.org/doc/libs/1_77_0/libs/format/doc/format.html 224 | .. _`checked iterators`: https://docs.microsoft.com/en-us/cpp/standard-library/checked-iterators 225 | .. _Dragonbox: https://github.com/jk-jeon/dragonbox/ 226 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :github_url: https://github.com/seanmiddleditch/nanofmt/blob/main/docs/index.rst 2 | 3 | nanofmt 4 | ======= 5 | 6 | **nanofmt** aims to provide a lite-weight semi-compatible implementation of the 7 | excellent `fmtlib`_. This can be used in environments or team cultures where 8 | neither `std::format`_ nor fmtlib are available for use. 9 | 10 | .. contents:: 11 | 12 | .. toctree:: 13 | :hidden: 14 | :maxdepth: 2 15 | 16 | self 17 | api 18 | design 19 | faq 20 | benchmarks 21 | license 22 | 23 | .. _basic-example: 24 | 25 | Basic Usage 26 | ----------- 27 | 28 | Example usage of writing a string with two variables formatted into the output. 29 | 30 | .. code-block:: c++ 31 | 32 | char buffer[128]; 33 | nanofmt::format_to(buffer, "Hello, {0}! You are visitor {1}.", 34 | UserName, 35 | VisitorCount); 36 | 37 | See also our :ref:`detailed-examples`. 38 | 39 | .. _api-overview: 40 | 41 | API Overview 42 | ============ 43 | 44 | The crux of the API is writing to a ``char`` arrays. Writing directly to an array 45 | just works: 46 | 47 | .. code-block:: c++ 48 | 49 | char buffer[128]; 50 | format_to(buffer, "Format this {}", 128); 51 | 52 | A pointer to a buffer and a length can also be provided for safe writing. 53 | 54 | .. code-block:: c++ 55 | 56 | format_to_n(buffer, size, "Format this {}", 128); 57 | 58 | The format functions all return a ``char*`` pointing to the terminating NUL that 59 | is always written to the buffer. 60 | 61 | .. code-block:: c++ 62 | 63 | char* end = format_to(buffer, "{} is {}", seven, 7); 64 | size_t const size = end - buffer; 65 | 66 | The nanofmt functions understand the same positional arguments and most format 67 | flags of `std::format`_. 68 | 69 | .. code-block:: c++ 70 | 71 | format_to(buffer, "{1} then {0}", "Second", "First"); 72 | // buffer: First then Second 73 | 74 | If the length of formatted text is required, e.g. for allocating buffer space, 75 | the :cpp:func:`nanofmt::format_length` function can be used: 76 | 77 | .. code-block:: c++ 78 | 79 | size_t const length = format_length("{} plus {}", 7, 7); 80 | 81 | char* dest = (char*)malloc(length + 1/*NUL byte*/); 82 | format_to_n(buffer, length + 1, "{} plus {}", 7, 7); 83 | 84 | nanofmt also includes implementation of the C++17 standard `to_chars`_ 85 | functions, for codebases that are unable or unwilling to use the standard 86 | versions, or who are using older compilers that lack support for 87 | floating-point ``std::to_chars`` 88 | 89 | See the :ref:`api` for more in-depth coverage of the nanofmt facilities. 90 | 91 | The Case for nanofmt 92 | ==================== 93 | 94 | nanofmt may be a good fit for teams or codebases which are unable or unwilling 95 | to use `fmtlib`_ or `std::format`_, particularly if the reasons involve 96 | compilation time or standard library header dependencies. 97 | 98 | The only headers nanofmt relies on are ````, ````, 99 | ````, and ````. 100 | 101 | Teams that might otherwise continue to prefer using ``snprintf`` may find 102 | that nanofmt is far more to their tastes. 103 | 104 | Anyone unsure of whether they should use nanofmt as their should almost 105 | certainly consider using fmtlib or ``std::format`` instead. 106 | 107 | Both fmtlib and the standard formatting facilities offer far more features, 108 | far more idiomatic C++ support, and integrate far better with both the 109 | rest of the C++ ecosystem and core IO. 110 | 111 | .. _detailed-examples: 112 | 113 | Detailed Examples 114 | ================= 115 | 116 | Custom Types 117 | ------------ 118 | 119 | Provide a specialization of :cpp:struct:`nanofmt::formatter` to enable nanofmt to consume 120 | values of a custom type. 121 | 122 | .. code-block:: c++ 123 | 124 | struct MyType { 125 | std::string FirstName; 126 | std::string LastName; 127 | }; 128 | 129 | namespace nanofmt { 130 | template <> 131 | struct formatter { 132 | bool reverse = false; 133 | 134 | constexpr char const* parse(char const* in, char const* end) noexcept; 135 | inline void format(MyType const& value, buffer& output) noexcept; 136 | }; 137 | } 138 | 139 | char buffer[128]; 140 | format_into(buffer, "Greetings {:r}!", MyType{"Bob", "Bobson"); 141 | 142 | // buffer would contain: 143 | // Greetings Bobson, Bob 144 | 145 | What does that ``:r`` do? That's a custom formatter flag supported by the 146 | ``MyType`` formatter. A possible implementation: 147 | 148 | .. code-block:: c++ 149 | 150 | constexpr char const* nanofmt::formatter::parse(char const* in, char const* end) noexcept { 151 | if (in != end && *in == 'r') 152 | ++in; 153 | reverse = true; 154 | } 155 | return reverse; 156 | } 157 | 158 | void nanofmt::formatter::format(MyType const& value, buffer& output) noexcept { 159 | if (reverse) 160 | format_into(output, "{}, {}", LastName, FirstName); 161 | else 162 | format_into(output, "{} {}", FirstName, LastName); 163 | } 164 | 165 | Length-Delimited Buffers 166 | ------------------------ 167 | 168 | nanofmt automatically length-delimits any attempt to write to a `char[]` 169 | array. When using raw pointers, or to futher constrain the output length, 170 | the `format_into_n` functions may be used. 171 | 172 | The format functions return a pointer to the last character written (excluding 173 | the NUL byte). This can be used to chain format calls, or to calculate the 174 | written length. 175 | 176 | .. code-block:: c++ 177 | 178 | char* ptr = GetBufferData(); 179 | size_t size = GetBufferSize(); 180 | 181 | char* end = format_into_n(ptr, size, "Format this! {}", value); 182 | 183 | size_t const length = end - ptr; 184 | 185 | .. _std::format: https://en.cppreference.com/w/cpp/utility/format/format 186 | .. _fmtlib: https://github.com/fmtlib/fmt 187 | .. _to_chars: https://en.cppreference.com/w/cpp/utility/to_chars 188 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | :github_url: https://github.com/seanmiddleditch/nanofmt/blob/main/docs/license.rst 2 | 3 | License 4 | ======= 5 | 6 | Copyright © Sean Middleditch and contributors 7 | 8 | nanofmt is released under the `MIT license`_. 9 | 10 | nanofmt uses the `Dragonbox`_ reference implementation by Junekey Jeon which 11 | is released under either the `Apache License Version 2.0 with LLVM Exceptions`_ or 12 | the `Boost Software License Version 1.0`_. 13 | 14 | .. _MIT license: https://github.com/seanmiddleditch/nanofmt/blob/main/LICENSE.md 15 | .. _Dragonbox: https://github.com/jk-jeon/dragonbox/ 16 | .. _Apache License Version 2.0 with LLVM Exceptions: https://github.com/jk-jeon/dragonbox/blob/master/LICENSE-Apache2-LLVM 17 | .. _Boost Software License Version 1.0: https://github.com/jk-jeon/dragonbox/blob/master/LICENSE-Boost 18 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | docutils==0.16 2 | sphinx>=4.2 3 | sphinx-rtd-theme 4 | -------------------------------------------------------------------------------- /include/nanofmt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(nanofmt PRIVATE 2 | "charconv.h" 3 | "config.h" 4 | "format.h" 5 | "format.inl" 6 | "forward.h" 7 | "std_string.h" 8 | ) 9 | -------------------------------------------------------------------------------- /include/nanofmt/charconv.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #pragma once 4 | 5 | #include "config.h" 6 | 7 | namespace NANOFMT_NS { 8 | // clang-format off 9 | /// @brief Format options for integral values. 10 | enum class int_format { 11 | decimal = 1, ///< Format in base 10 12 | hex = 2, ///< Format in base 16 13 | hex_upper = 3, ///< Format in base 16, with uppercase letters 14 | binary = 4, ///< Format in base 2 15 | }; 16 | 17 | /// Format options for floating-point values. 18 | enum class float_format { 19 | /// Format in scientific notation: [-]d.de[+-]dd 20 | scientific = 0b0001, 21 | /// Format in scientific notation, with uppercase letters: [-]d.dE[+-]dd 22 | scientific_upper = scientific | 0b1000, 23 | /// Format in fixed precision: [-]d.dddd 24 | fixed = 0b0010, 25 | /// Format in hexadecimal: [-]1.xxxxp[+-]dd 26 | hex = 0b0100, 27 | /// Format in hexadecimal: [-]1.XXXXP[+-]dd 28 | hex_upper = hex | 0b1000, 29 | /// Format in either scientific or fixed precision, depending on the exponent 30 | general = fixed | scientific, 31 | /// Format in either uppercase scientific or fixed precision, depending on the exponent 32 | general_upper = fixed | scientific_upper 33 | }; 34 | // clang-format on 35 | 36 | #if defined(DOXYGEN_SHOULD_SKIP_THIS) 37 | /// @brief Format an integer value to the target buffer. 38 | /// @param buffer target buffer to write characters to. 39 | /// @param end the end of the target buffer. 40 | /// @param value the value to format. 41 | /// @param fmt formatting options. 42 | /// @return one past the last character written. 43 | template 44 | char* to_chars(char* dest, char const* end, IntegerT value, int_format fmt = int_format::decimal) noexcept; 45 | #else 46 | char* to_chars(char* dest, char const* end, signed char value, int_format fmt = int_format::decimal) noexcept; 47 | char* to_chars(char* dest, char const* end, unsigned char value, int_format fmt = int_format::decimal) noexcept; 48 | char* to_chars(char* dest, char const* end, signed short value, int_format fmt = int_format::decimal) noexcept; 49 | char* to_chars(char* dest, char const* end, unsigned short value, int_format fmt = int_format::decimal) noexcept; 50 | char* to_chars(char* dest, char const* end, signed int value, int_format fmt = int_format::decimal) noexcept; 51 | char* to_chars(char* dest, char const* end, unsigned int value, int_format fmt = int_format::decimal) noexcept; 52 | char* to_chars(char* dest, char const* end, signed long value, int_format fmt = int_format::decimal) noexcept; 53 | char* to_chars(char* dest, char const* end, unsigned long value, int_format fmt = int_format::decimal) noexcept; 54 | char* to_chars(char* dest, char const* end, signed long long value, int_format fmt = int_format::decimal) noexcept; 55 | char* to_chars( 56 | char* dest, 57 | char const* end, 58 | unsigned long long value, 59 | int_format fmt = int_format::decimal) noexcept; 60 | #endif 61 | 62 | // plain char is disallowed, cast to signed or unsigned char for integer formatting 63 | char* to_chars(char* dest, char const* end, char value, int_format) noexcept = delete; 64 | 65 | // bools are disallowed, cast to an integer type if a 0 or 1 format is required 66 | char* to_chars(char* dest, char const* end, bool value, int_format) noexcept = delete; 67 | 68 | #if defined(DOXYGEN_SHOULD_SKIP_THIS) 69 | /// @brief Format a single-precision floating point value to the target buffer. 70 | /// @param buffer target buffer to write characters to. 71 | /// @param end the end of the target buffer. 72 | /// @param value the value to format. 73 | /// @param fmt formatting options. 74 | /// @return one past the last character written. 75 | template 76 | char* to_chars(char* dest, char const* end, FloatT value, float_format fmt) noexcept; 77 | 78 | /// @brief Format a single-precision floating point value to the target buffer. 79 | /// @param buffer target buffer to write characters to. 80 | /// @param end the end of the target buffer. 81 | /// @param value the value to format. 82 | /// @param fmt formatting options. 83 | /// @param precision target precision for the output. 84 | /// @return one past the last character written. 85 | template 86 | char* to_chars(char* dest, char const* end, FloatT value, float_format fmt, int precision) noexcept; 87 | #else 88 | char* to_chars(char* dest, char const* end, float value, float_format fmt) noexcept; 89 | char* to_chars(char* dest, char const* end, double value, float_format fmt) noexcept; 90 | 91 | char* to_chars(char* dest, char const* end, float value, float_format fmt, int precision) noexcept; 92 | char* to_chars(char* dest, char const* end, double value, float_format fmt, int precision) noexcept; 93 | #endif 94 | /// @} 95 | 96 | // to_chars for floating-point types requires explicit use of float_format 97 | char* to_chars(char* dest, char const* end, float value) noexcept = delete; 98 | char* to_chars(char* dest, char const* end, double value) noexcept = delete; 99 | 100 | // long doubles are not supported 101 | char* to_chars(char* dest, char const* end, long double value) noexcept = delete; 102 | char* to_chars(char* dest, char const* end, long double value, float_format fmt) noexcept = delete; 103 | char* to_chars(char* dest, char const* end, long double value, float_format fmt, int precision) noexcept = delete; 104 | } // namespace NANOFMT_NS 105 | -------------------------------------------------------------------------------- /include/nanofmt/config.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #ifndef NANOFMT_CONFIG_H_ 4 | #define NANOFMT_CONFIG_H_ 1 5 | #pragma once 6 | 7 | #if !defined(NANOFMT_FLOAT) 8 | # define NANOFMT_FLOAT 1 9 | #endif 10 | 11 | #if !defined(NANOFMT_NS) 12 | # define NANOFMT_NS nanofmt 13 | #endif 14 | 15 | // wow this is annoying; see https://github.com/isocpp/CppCoreGuidelines/issues/1173 16 | // 17 | #if defined(__has_cpp_attribute) 18 | # if __has_cpp_attribute(gsl::suppress) 19 | # if defined(__clang__) 20 | // clang-format off 21 | # define NANOFMT_GSL_SUPPRESS(rule) [[gsl::suppress(#rule)]] 22 | // clang-format on 23 | # endif 24 | # elif defined(_MSC_VER) 25 | # define NANOFMT_GSL_SUPPRESS(rule) [[gsl::suppress(rule)]] 26 | # endif 27 | #endif 28 | #if !defined(NANOFMT_GSL_SUPPRESS) 29 | # define NANOFMT_GSL_SUPPRESS(rule) 30 | #endif 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /include/nanofmt/format.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #ifndef NANOFMT_FORMAT_H_ 4 | #define NANOFMT_FORMAT_H_ 1 5 | #pragma once 6 | 7 | #include "config.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace NANOFMT_NS { 13 | // ---------------------- 14 | // Types 15 | // ---------------------- 16 | 17 | /// Type-erased wrapper for a formattable value. 18 | struct format_arg; 19 | 20 | /// List of format args. 21 | /// 22 | /// Only use this type as a temporary value! 23 | struct format_args; 24 | 25 | /// Wrapper for format strings. 26 | struct format_string; 27 | 28 | /// Small wrapper to assist in formatting types like std::string_view. 29 | struct format_string_view; 30 | 31 | /// Wrapper around a destination sequence of characters. 32 | /// 33 | /// Counts the number of characters that are written to the buffer, 34 | /// excluding truncating, and stores them in the advance member. 35 | /// 36 | /// Use advance_to to update the pos pointer to ensure the advance field 37 | /// is updated appropriately. 38 | struct format_output; 39 | 40 | /// Holds a list of N format_value objects. 41 | /// 42 | /// This is primarily meant to be an intermediate that holds onto values 43 | /// as a temporary object, and will usually be converted to format_args. 44 | template 45 | struct format_arg_store; 46 | 47 | /// Specialize to implement format support for a type. 48 | /// 49 | /// Two member functions must be defined: 50 | /// 51 | /// constexpr char const* parse(char const* in, char const* end) noexcept; 52 | /// 53 | /// void format(T const& value, format_output& out); 54 | template 55 | struct formatter; 56 | 57 | /// Overload to support converting user-defined string types to format_string. 58 | template 59 | constexpr format_string to_format_string(StringT const& value) noexcept; 60 | 61 | // ---------------------- 62 | // String Utilities 63 | // ---------------------- 64 | 65 | /// Copy the source string to the destination buffer, but not extending 66 | /// past the provided buffer end pointer. Returns the pointer past the 67 | /// last character written. 68 | [[nodiscard]] constexpr char* copy_to(char* dest, char const* end, char const* source) noexcept; 69 | 70 | /// Copies length characters of source string to the destination 71 | /// buffer, but not extending past the provided buffer end pointer. 72 | /// Returns the pointer past the last character written. 73 | [[nodiscard]] constexpr char* copy_to_n( 74 | char* dest, 75 | char const* end, 76 | char const* source, 77 | std::size_t length) noexcept; 78 | 79 | /// Copies the provided character ch to the destination buffer, but not 80 | /// extending past the provided buffer end pointer. Returns the pointer 81 | /// past the last character written. 82 | [[nodiscard]] constexpr char* put(char* dest, char const* end, char ch) noexcept; 83 | 84 | /// Copies count copies of the charcter ch to the destination buffer, but 85 | /// not extending past the provided buffer end pointer. Returns the 86 | /// pointer past the last character written. 87 | [[nodiscard]] constexpr char* fill_n(char* dest, char const* end, char ch, std::size_t count) noexcept; 88 | 89 | /// Finds the first NUL character in the target buffer. Returns the length 90 | /// of the buffer if no NUL is found. 91 | [[nodiscard]] constexpr std::size_t strnlen(char const* buffer, std::size_t count) noexcept; 92 | 93 | // ---------------------- 94 | // Format API 95 | // ---------------------- 96 | 97 | /// Formats a string and arguments into dest, writing no more than count 98 | /// bytes. The destination will **NOT** be NUL-terminated. Returns a 99 | /// pointer to the last character written. 100 | template 101 | [[nodiscard]] char* format_to_n(char* dest, std::size_t count, format_string format_str, Args const&... args); 102 | 103 | /// Formats a string and arguments into dest, writing no more than count 104 | /// bytes. The destination will **NOT** be NUL-terminated. Returns a 105 | /// pointer to the last character written. 106 | inline char* vformat_to_n(char* dest, std::size_t count, format_string format_str, format_args args); 107 | 108 | template 109 | char* format_to(char (&dest)[N], format_string format_str, Args const&... args); 110 | 111 | /// Formats a string and arguments into dest, writing no more than N 112 | /// bytes. The output will be NUL-terminated. Returns a pointer to the 113 | /// last character written, which will be the NUL byte itself. 114 | template 115 | char* vformat_to(char (&dest)[N], format_string format_str, format_args args); 116 | 117 | /// Returns the number of characters that would be written to a 118 | /// destination buffer (_excluding_ any terminating NUL) for the 119 | /// given format string and arguments 120 | template 121 | [[nodiscard]] std::size_t format_length(format_string format_str, Args const&... args); 122 | 123 | [[nodiscard]] inline std::size_t vformat_length(format_string format_str, format_args args); 124 | 125 | template 126 | [[nodiscard]] char* format_append_to(char* dest, std::size_t count, format_string format_str, Args const&... args); 127 | 128 | template 129 | [[nodiscard]] char* vformat_append_to(char* dest, std::size_t count, format_string format_str, format_args args); 130 | 131 | template 132 | char* format_append_to(char (&dest)[N], format_string format_str, Args const&... args); 133 | 134 | template 135 | char* vformat_append_to(char (&dest)[N], format_string format_str, format_args args); 136 | 137 | // ---------------------- 138 | // Format Args 139 | // ---------------------- 140 | 141 | /// Constructs a format_args from a list of values. 142 | /// 143 | template 144 | [[nodiscard]] constexpr auto make_format_args(Args const&... args) noexcept; 145 | 146 | // ---------------------- 147 | // Default Formatters 148 | // ---------------------- 149 | 150 | namespace detail { 151 | struct format_spec { 152 | int width = -1; 153 | int precision = -1; 154 | signed char align = -1; // -1 left, 0 center, +1 right 155 | char sign = '-'; // -, +, or space 156 | char fill = ' '; 157 | char type = '\0'; 158 | bool zero_pad = false; 159 | bool alt_form = false; 160 | }; 161 | 162 | struct char_buffer; 163 | 164 | template 165 | struct default_formatter { 166 | format_spec spec; 167 | char const* parse(char const* in, char const* end) noexcept; 168 | void format(T value, format_output& out) noexcept; 169 | }; 170 | } // namespace detail 171 | 172 | template 173 | struct formatter { 174 | formatter() = delete; // formatters must be default-constructible 175 | }; 176 | 177 | template <> 178 | struct formatter : detail::default_formatter {}; 179 | template <> 180 | struct formatter : detail::default_formatter {}; 181 | 182 | template <> 183 | struct formatter : detail::default_formatter {}; 184 | template <> 185 | struct formatter : detail::default_formatter {}; 186 | template 187 | struct formatter : detail::default_formatter {}; 188 | template <> 189 | struct formatter : detail::default_formatter {}; 190 | 191 | template <> 192 | struct formatter : detail::default_formatter {}; 193 | template <> 194 | struct formatter : detail::default_formatter {}; 195 | template <> 196 | struct formatter : detail::default_formatter {}; 197 | template <> 198 | struct formatter : detail::default_formatter {}; 199 | template <> 200 | struct formatter : detail::default_formatter {}; 201 | template <> 202 | struct formatter : detail::default_formatter {}; 203 | template <> 204 | struct formatter : detail::default_formatter {}; 205 | template <> 206 | struct formatter : detail::default_formatter {}; 207 | template <> 208 | struct formatter : detail::default_formatter {}; 209 | template <> 210 | struct formatter : detail::default_formatter {}; 211 | 212 | #if NANOFMT_FLOAT 213 | template <> 214 | struct formatter : detail::default_formatter {}; 215 | template <> 216 | struct formatter : detail::default_formatter {}; 217 | #endif 218 | 219 | template <> 220 | struct formatter : detail::default_formatter {}; 221 | template <> 222 | struct formatter : detail::default_formatter {}; 223 | template <> 224 | struct formatter : detail::default_formatter {}; 225 | } // namespace NANOFMT_NS 226 | 227 | #include "format.inl" 228 | 229 | #endif // NANOFMT_FORMAT_H_ 230 | -------------------------------------------------------------------------------- /include/nanofmt/format.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #ifndef NANOFMT_FORMAT_H_ 4 | # error "format.inl is a private header; inglure format.h instead" 5 | #endif 6 | 7 | #ifndef DOXYGEN_SHOULD_SKIP_THIS 8 | 9 | namespace NANOFMT_NS { 10 | namespace detail { 11 | format_output vformat(format_output out, format_string format_str, format_args args); 12 | 13 | // avoid explicitly pulling in 14 | template 15 | const T& declval() noexcept; 16 | 17 | template 18 | constexpr format_arg make_format_arg(ValueT const& value) noexcept; 19 | 20 | template 21 | struct value_type_map; 22 | 23 | template 24 | using enable_if_format_string = 25 | std::enable_if_t()))>>; 26 | 27 | struct char_buffer { 28 | char const* chars = nullptr; 29 | std::size_t max_length = 0; 30 | }; 31 | 32 | } // namespace detail 33 | 34 | struct format_arg { 35 | enum class type { 36 | t_mono, 37 | t_int, 38 | t_uint, 39 | t_long, 40 | t_ulong, 41 | t_longlong, 42 | t_ulonglong, 43 | t_char, 44 | t_float, 45 | t_double, 46 | t_bool, 47 | t_cstring, 48 | t_voidptr, 49 | t_custom 50 | }; 51 | 52 | struct custom { 53 | void (*thunk)(void const* value, char const** spec, char const* end, format_output& out) = nullptr; 54 | void const* value = nullptr; 55 | }; 56 | 57 | constexpr format_arg() noexcept : v_int(0) {} 58 | constexpr format_arg(int value) noexcept : v_int(value), tag(type::t_int) {} 59 | constexpr format_arg(unsigned value) noexcept : v_unsigned(value), tag(type::t_uint) {} 60 | constexpr format_arg(long value) noexcept : v_long(value), tag(type::t_long) {} 61 | constexpr format_arg(unsigned long value) noexcept : v_ulong(value), tag(type::t_ulong) {} 62 | constexpr format_arg(long long value) noexcept : v_longlong(value), tag(type::t_longlong) {} 63 | constexpr format_arg(unsigned long long value) noexcept : v_ulonglong(value), tag(type::t_ulonglong) {} 64 | constexpr format_arg(char value) noexcept : v_char(value), tag(type::t_char) {} 65 | constexpr format_arg(float value) noexcept : v_float(value), tag(type::t_float) {} 66 | constexpr format_arg(double value) noexcept : v_double(value), tag(type::t_double) {} 67 | constexpr format_arg(bool value) noexcept : v_bool(value), tag(type::t_bool) {} 68 | constexpr format_arg(char const* value) noexcept : v_cstring(value), tag(type::t_cstring) {} 69 | constexpr format_arg(void const* value) noexcept : v_voidptr(value), tag(type::t_voidptr) {} 70 | constexpr format_arg(custom value) noexcept : v_custom(value), tag(type::t_custom) {} 71 | 72 | union { 73 | int v_int; 74 | unsigned v_unsigned; 75 | long v_long; 76 | unsigned long v_ulong; 77 | long long v_longlong; 78 | unsigned long long v_ulonglong; 79 | char v_char; 80 | float v_float; 81 | double v_double; 82 | bool v_bool; 83 | char const* v_cstring; 84 | void const* v_voidptr; 85 | custom v_custom; 86 | }; 87 | 88 | type tag = type::t_mono; 89 | }; 90 | 91 | template 92 | struct format_arg_store { 93 | static constexpr size_t size = N; 94 | format_arg values[N + 1 /* avoid size 0 */]; 95 | }; 96 | 97 | struct format_args { 98 | template 99 | constexpr /*implicit*/ format_args(format_arg_store&& values) noexcept : values(values.values) 100 | , count(N) {} 101 | 102 | void format(unsigned index, char const** in, char const* end, format_output& out) const; 103 | 104 | format_arg const* values = nullptr; 105 | size_t count = 0; 106 | }; 107 | 108 | template 109 | [[nodiscard]] constexpr auto make_format_args(Args const&... args) noexcept { 110 | return format_arg_store{detail::make_format_arg(args)...}; 111 | } 112 | 113 | struct format_string { 114 | constexpr format_string() noexcept = default; 115 | constexpr format_string(char const* string, std::size_t length) noexcept; 116 | template 117 | constexpr /*implicit*/ format_string(char const (&str)[N]) noexcept; 118 | constexpr explicit format_string(char const* const zstr) noexcept; 119 | 120 | template > 121 | constexpr /*implicit*/ format_string(StringT const& string) noexcept; 122 | 123 | char const* begin = nullptr; 124 | char const* end = nullptr; 125 | }; 126 | 127 | struct format_output { 128 | char* pos = nullptr; 129 | char const* end = nullptr; 130 | std::size_t advance = 0; 131 | 132 | constexpr format_output& append(char const* zstr) noexcept; 133 | constexpr format_output& append(char const* source, std::size_t length) noexcept; 134 | 135 | constexpr format_output& put(char ch) noexcept; 136 | 137 | constexpr format_output& fill_n(char ch, std::size_t count) noexcept; 138 | 139 | template 140 | format_output& format(format_string fmt, Args const&... args); 141 | 142 | inline format_output& vformat(format_string fmt, format_args args); 143 | 144 | constexpr format_output& advance_to(char* p) noexcept; 145 | }; 146 | 147 | constexpr char* copy_to(char* dest, char const* end, char const* source) noexcept { 148 | char const* ptr = source; 149 | while (*ptr != 0 && dest != end) 150 | *dest++ = *ptr++; 151 | 152 | return dest; 153 | } 154 | 155 | constexpr char* copy_to_n(char* dest, char const* end, char const* source, std::size_t length) noexcept { 156 | char const* source_end = source + length; 157 | while (source != source_end && dest != end) 158 | *dest++ = *source++; 159 | 160 | return dest; 161 | } 162 | 163 | constexpr char* put(char* dest, char const* end, char ch) noexcept { 164 | if (dest != end) { 165 | *dest++ = ch; 166 | } 167 | return dest; 168 | } 169 | 170 | constexpr char* fill_n(char* dest, char const* end, char ch, std::size_t count) noexcept { 171 | char const pad_buffer[] = {ch, ch, ch, ch, ch, ch, ch, ch}; 172 | constexpr std::size_t pad_length = sizeof pad_buffer; 173 | 174 | while (count >= pad_length) { 175 | dest = copy_to_n(dest, end, pad_buffer, pad_length); 176 | count -= pad_length; 177 | } 178 | return copy_to_n(dest, end, pad_buffer, count); 179 | } 180 | 181 | constexpr std::size_t strnlen(char const* buffer, std::size_t count) noexcept { 182 | char const* const end = buffer + count; 183 | for (char const* pos = buffer; pos != end; ++pos) 184 | if (*pos == '\0') 185 | return pos - buffer; 186 | return count; 187 | } 188 | 189 | constexpr format_output& format_output::append(char const* const zstr) noexcept { 190 | char* const p = copy_to(pos, end, zstr); 191 | std::size_t const consumed = p - pos; 192 | pos = p; 193 | advance += consumed; 194 | advance += __builtin_strlen(zstr + consumed); 195 | return *this; 196 | } 197 | 198 | constexpr format_output& format_output::append(char const* source, std::size_t length) noexcept { 199 | pos = copy_to_n(pos, end, source, length); 200 | advance += length; 201 | return *this; 202 | } 203 | 204 | constexpr format_output& format_output::put(char ch) noexcept { 205 | pos = ::NANOFMT_NS::put(pos, end, ch); 206 | ++advance; 207 | return *this; 208 | } 209 | 210 | constexpr format_output& format_output::fill_n(char ch, std::size_t count) noexcept { 211 | pos = ::NANOFMT_NS::fill_n(pos, end, ch, count); 212 | advance += count; 213 | return *this; 214 | } 215 | 216 | template 217 | format_output& format_output::format(format_string fmt, Args const&... args) { 218 | return *this = detail::vformat(*this, fmt, ::NANOFMT_NS::make_format_args(args...)); 219 | } 220 | 221 | format_output& format_output::vformat(format_string fmt, format_args args) { 222 | return *this = detail::vformat(*this, fmt, static_cast(args)); 223 | } 224 | 225 | constexpr format_output& format_output::advance_to(char* const p) noexcept { 226 | size_t const diff = p - pos; 227 | pos = p; 228 | advance += diff; 229 | return *this; 230 | } 231 | 232 | struct format_string_view { 233 | char const* string = nullptr; 234 | std::size_t length = 0; 235 | }; 236 | 237 | constexpr format_string::format_string(char const* string, std::size_t length) noexcept 238 | : begin(string) 239 | , end(string + length) {} 240 | 241 | template 242 | constexpr format_string::format_string(char const (&str)[N]) noexcept 243 | : begin(str) 244 | , end(begin + __builtin_strlen(begin)) {} 245 | 246 | constexpr format_string::format_string(char const* const zstr) noexcept 247 | : begin(zstr) 248 | , end(begin + __builtin_strlen(begin)) {} 249 | 250 | template 251 | constexpr format_string::format_string(StringT const& string) noexcept : format_string(to_format_string(string)) {} 252 | 253 | template 254 | constexpr format_string to_format_string(StringT const& value) noexcept { 255 | return {value.data(), value.size()}; 256 | } 257 | 258 | [[nodiscard]] char* vformat_to_n(char* dest, std::size_t count, format_string format_str, format_args args) { 259 | return detail::vformat(format_output{dest, dest + count}, format_str, static_cast(args)).pos; 260 | } 261 | 262 | template 263 | [[nodiscard]] char* format_to_n(char* dest, std::size_t count, format_string format_str, Args const&... args) { 264 | return detail::vformat(format_output{dest, dest + count}, format_str, ::NANOFMT_NS::make_format_args(args...)) 265 | .pos; 266 | } 267 | 268 | template 269 | format_output& format_to(format_output& out, format_string format_str, Args const&... args) { 270 | return out = detail::vformat(out, format_str, ::NANOFMT_NS::make_format_args(args...)); 271 | } 272 | 273 | template 274 | char* format_to(char (&dest)[N], format_string format_str, Args const&... args) { 275 | char* const pos = detail::vformat( 276 | format_output{dest, dest + (N - 1 /*NUL*/)}, 277 | format_str, 278 | ::NANOFMT_NS::make_format_args(args...)) 279 | .pos; 280 | *pos = '\0'; 281 | return pos; 282 | } 283 | 284 | template 285 | char* vformat_to(char (&dest)[N], format_string format_str, format_args args) { 286 | char* const pos = detail::vformat(format_output{dest, dest + (N - 1 /*NUL*/)}, format_str, args).pos; 287 | *pos = '\0'; 288 | return pos; 289 | } 290 | 291 | template 292 | [[nodiscard]] char* format_append_to_n( 293 | char* dest, 294 | std::size_t count, 295 | format_string format_str, 296 | Args const&... args) { 297 | std::size_t const start = ::NANOFMT_NS::strnlen(dest, N); 298 | return detail::vformat( 299 | format_output{dest + start, dest + count}, 300 | format_str, 301 | ::NANOFMT_NS::make_format_args(args...)) 302 | .pos; 303 | } 304 | 305 | template 306 | [[nodiscard]] char* vformat_append_to_n(char* dest, std::size_t count, format_string format_str, format_args args) { 307 | std::size_t const start = ::NANOFMT_NS::strnlen(dest, N); 308 | return detail::vformat(format_output{dest + start, dest + count}, format_str, args).pos; 309 | } 310 | 311 | template 312 | char* format_append_to(char (&dest)[N], format_string format_str, Args const&... args) { 313 | return vformat_append_to(dest, format_str, ::NANOFMT_NS::make_format_args(args...)); 314 | } 315 | 316 | template 317 | char* vformat_append_to(char (&dest)[N], format_string format_str, format_args args) { 318 | std::size_t const start = ::NANOFMT_NS::strnlen(dest, N); 319 | if (start == N) { 320 | return dest + N; 321 | } 322 | char* const pos = detail::vformat(format_output{dest + start, dest + (N - 1 /*NUL*/)}, format_str, args).pos; 323 | *pos = '\0'; 324 | return pos; 325 | } 326 | 327 | template 328 | [[nodiscard]] std::size_t format_length(format_string format_str, Args const&... args) { 329 | return detail::vformat(format_output{}, format_str, ::NANOFMT_NS::make_format_args(args...)).advance; 330 | } 331 | 332 | [[nodiscard]] std::size_t vformat_length(format_string format_str, format_args args) { 333 | return detail::vformat(format_output{}, format_str, static_cast(args)).advance; 334 | } 335 | 336 | namespace detail { 337 | template 338 | using has_formatter = std::is_default_constructible<::NANOFMT_NS::formatter>; 339 | 340 | template 341 | constexpr bool always_false_v = false; 342 | 343 | template 344 | struct value_type_map { 345 | using type = T; 346 | }; 347 | template <> 348 | struct value_type_map { 349 | using type = signed int; 350 | }; 351 | template <> 352 | struct value_type_map { 353 | using type = unsigned int; 354 | }; 355 | template <> 356 | struct value_type_map { 357 | using type = signed int; 358 | }; 359 | template <> 360 | struct value_type_map { 361 | using type = unsigned int; 362 | }; 363 | template <> 364 | struct value_type_map { 365 | using type = void const*; 366 | }; 367 | 368 | template 369 | constexpr format_arg make_format_arg(ValueT const& value) noexcept { 370 | using MappedT = typename detail::value_type_map>::type; 371 | if constexpr (std::is_constructible_v) { 372 | return (MappedT)(value); 373 | } 374 | else if constexpr (detail::has_formatter::value) { 375 | format_arg::custom custom; 376 | custom.thunk = +[](void const* value, char const** in, char const* end, format_output& out) { 377 | formatter fmt; 378 | if (in != nullptr) { 379 | *in = fmt.parse(*in, end); 380 | } 381 | fmt.format(*static_cast(value), out); 382 | }; 383 | // this is basically std::addressof, but we want to avoid pulling in as a dependency 384 | custom.value = 385 | reinterpret_cast(&const_cast(reinterpret_cast(value))); 386 | return custom; 387 | } 388 | else if constexpr (std::is_enum_v) { 389 | return static_cast>::type>(value); 390 | } 391 | else { 392 | static_assert(always_false_v, "Type has no nanofmt::formatter<> specialization"); 393 | } 394 | } 395 | } // namespace detail 396 | } // namespace NANOFMT_NS 397 | 398 | #endif 399 | -------------------------------------------------------------------------------- /include/nanofmt/forward.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #ifndef NANOFMT_FORWARD_H_ 4 | #define NANOFMT_FORWARD_H_ 1 5 | #pragma once 6 | 7 | #include "config.h" 8 | 9 | namespace NANOFMT_NS { 10 | struct format_arg; 11 | struct format_args; 12 | struct format_string; 13 | struct format_string_view; 14 | struct format_output; 15 | template 16 | struct formatter; 17 | } // namespace NANOFMT_NS 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /include/nanofmt/std_string.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #pragma once 4 | 5 | #include "config.h" 6 | #include "format.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace NANOFMT_NS { 12 | template 13 | struct formatter> : formatter { 14 | constexpr void format(std::basic_string const& value, format_output& out) { 15 | formatter::format({value.data(), value.size()}, out); 16 | } 17 | }; 18 | 19 | template 20 | struct formatter> : formatter { 21 | constexpr void format(std::basic_string_view const& value, format_output& out) { 22 | formatter::format({value.data(), value.size()}, out); 23 | } 24 | }; 25 | } // namespace NANOFMT_NS 26 | -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(nanofmt PRIVATE 2 | "charconv.cpp" 3 | "format.cpp" 4 | "numeric_utils.h" 5 | "parse_utils.h" 6 | ) 7 | target_include_directories(nanofmt SYSTEM PRIVATE ${nanofmt_dragonbox_SOURCE_DIR}/include) 8 | -------------------------------------------------------------------------------- /source/charconv.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #include "nanofmt/charconv.h" 4 | #include "numeric_utils.h" 5 | #if NANOFMT_FLOAT 6 | # include "nanofmt/dragonbox.h" 7 | #endif 8 | #include "nanofmt/format.h" 9 | 10 | #include 11 | 12 | namespace NANOFMT_NS::detail { 13 | template 14 | static char* to_chars_impl(char* dest, char const* end, IntegerT value, int_format fmt) noexcept; 15 | 16 | template 17 | static char* to_chars_impl(char* dest, char const* end, FloatT value, float_format fmt, int precision) noexcept; 18 | 19 | template 20 | static char* to_chars_impl_decimal(char* dest, char const* end, UnsignedIntT value) noexcept; 21 | 22 | template 23 | static char* to_chars_impl_hex(char* dest, char const* end, UnsignedIntT value) noexcept; 24 | 25 | template 26 | static char* to_chars_impl_binary(char* dest, char const* end, UnsignedIntT value) noexcept; 27 | 28 | template 29 | static char* to_chars_n_round(char* dest, char const* end, UnsignedIntT value, std::size_t count) noexcept; 30 | 31 | template 32 | char* to_chars_impl_scientific( 33 | char* dest, 34 | char const* end, 35 | CarrierT significand, 36 | int exponent, 37 | int precision) noexcept; 38 | 39 | template 40 | static char* to_chars_impl_fixed( 41 | char* dest, 42 | char const* end, 43 | CarrierT significand, 44 | int exponent, 45 | int precision) noexcept; 46 | 47 | template 48 | static char* to_chars_impl_general( 49 | char* dest, 50 | char const* end, 51 | CarrierT significand, 52 | int exponent, 53 | int precision) noexcept; 54 | 55 | #if NANOFMT_FLOAT 56 | static char* to_chars_impl_nonfinite( 57 | char* dest, 58 | char const* end, 59 | bool negative, 60 | bool infinite, 61 | bool upper) noexcept; 62 | #endif 63 | 64 | // maximum significand for double is 17 decimal digits 65 | static constexpr size_t significand_max_digits10 = 17; 66 | } // namespace NANOFMT_NS::detail 67 | 68 | namespace NANOFMT_NS { 69 | char* to_chars(char* dest, char const* end, signed char value, int_format fmt) noexcept { 70 | return detail::to_chars_impl(dest, end, value, fmt); 71 | } 72 | 73 | char* to_chars(char* dest, char const* end, unsigned char value, int_format fmt) noexcept { 74 | return detail::to_chars_impl(dest, end, value, fmt); 75 | } 76 | 77 | char* to_chars(char* dest, char const* end, signed short value, int_format fmt) noexcept { 78 | return detail::to_chars_impl(dest, end, value, fmt); 79 | } 80 | 81 | char* to_chars(char* dest, char const* end, unsigned short value, int_format fmt) noexcept { 82 | return detail::to_chars_impl(dest, end, value, fmt); 83 | } 84 | 85 | char* to_chars(char* dest, char const* end, signed int value, int_format fmt) noexcept { 86 | return detail::to_chars_impl(dest, end, value, fmt); 87 | } 88 | 89 | char* to_chars(char* dest, char const* end, unsigned int value, int_format fmt) noexcept { 90 | return detail::to_chars_impl(dest, end, value, fmt); 91 | } 92 | 93 | char* to_chars(char* dest, char const* end, signed long value, int_format fmt) noexcept { 94 | return detail::to_chars_impl(dest, end, value, fmt); 95 | } 96 | 97 | char* to_chars(char* dest, char const* end, unsigned long value, int_format fmt) noexcept { 98 | return detail::to_chars_impl(dest, end, value, fmt); 99 | } 100 | 101 | char* to_chars(char* dest, char const* end, signed long long value, int_format fmt) noexcept { 102 | return detail::to_chars_impl(dest, end, value, fmt); 103 | } 104 | 105 | char* to_chars(char* dest, char const* end, unsigned long long value, int_format fmt) noexcept { 106 | return detail::to_chars_impl(dest, end, value, fmt); 107 | } 108 | 109 | #if NANOFMT_FLOAT 110 | char* to_chars(char* dest, char const* end, float value, float_format fmt) noexcept { 111 | return detail::to_chars_impl(dest, end, value, fmt, -1); 112 | } 113 | 114 | char* to_chars(char* dest, char const* end, double value, float_format fmt) noexcept { 115 | return detail::to_chars_impl(dest, end, value, fmt, -1); 116 | } 117 | 118 | char* to_chars(char* dest, char const* end, float value, float_format fmt, int precision) noexcept { 119 | return detail::to_chars_impl(dest, end, value, fmt, precision); 120 | } 121 | 122 | char* to_chars(char* dest, char const* end, double value, float_format fmt, int precision) noexcept { 123 | return detail::to_chars_impl(dest, end, value, fmt, precision); 124 | } 125 | #endif 126 | 127 | template 128 | char* detail::to_chars_impl(char* dest, char const* end, IntegerT value, int_format fmt) noexcept { 129 | if (value < 0 && dest != end) { 130 | *dest++ = '-'; 131 | } 132 | 133 | if (value == 0) { 134 | *dest++ = '0'; 135 | return dest; 136 | } 137 | 138 | auto const abs_value = detail::abs(value); 139 | 140 | switch (fmt) { 141 | case int_format::decimal: 142 | return detail::to_chars_impl_decimal(dest, end, abs_value); 143 | case int_format::hex: 144 | return detail::to_chars_impl_hex<'a'>(dest, end, abs_value); 145 | case int_format::hex_upper: 146 | return detail::to_chars_impl_hex<'A'>(dest, end, abs_value); 147 | case int_format::binary: 148 | return detail::to_chars_impl_binary(dest, end, abs_value); 149 | default: 150 | return dest; 151 | } 152 | } 153 | 154 | template 155 | char* detail::to_chars_impl(char* dest, char const* end, FloatT value, float_format fmt, int precision) noexcept { 156 | static_assert(sizeof(CarrierT) == sizeof(FloatT)); 157 | 158 | if (!std::isfinite(value)) { 159 | switch (fmt) { 160 | case float_format::scientific_upper: 161 | case float_format::general_upper: 162 | return to_chars_impl_nonfinite(dest, end, std::signbit(value), std::isinf(value), /*upper=*/true); 163 | default: 164 | return to_chars_impl_nonfinite( 165 | dest, 166 | end, 167 | std::signbit(value), 168 | std::isinf(value), 169 | /*upper=*/false); 170 | } 171 | } 172 | 173 | CarrierT significand = 0; 174 | int exponent = 0; 175 | 176 | if (value != 0) { 177 | auto const db_result = nanofmt::dragonbox::to_decimal( 178 | value, 179 | nanofmt::dragonbox::policy::sign::ignore, 180 | nanofmt::dragonbox::policy::cache::compact, 181 | nanofmt::dragonbox::policy::trailing_zero::remove, 182 | nanofmt::dragonbox::policy::binary_to_decimal_rounding::to_even); 183 | significand = db_result.significand; 184 | exponent = db_result.exponent; 185 | } 186 | 187 | // sign 188 | if (std::signbit(value)) { 189 | dest = put(dest, end, '-'); 190 | } 191 | 192 | switch (fmt) { 193 | case float_format::scientific: 194 | return detail::to_chars_impl_scientific(dest, end, significand, exponent, precision); 195 | case float_format::scientific_upper: 196 | return detail::to_chars_impl_scientific<'E'>(dest, end, significand, exponent, precision); 197 | case float_format::fixed: 198 | return detail::to_chars_impl_fixed(dest, end, significand, exponent, precision); 199 | case float_format::general: 200 | return detail::to_chars_impl_general(dest, end, significand, exponent, precision); 201 | case float_format::general_upper: 202 | return detail::to_chars_impl_general<'E'>(dest, end, significand, exponent, precision); 203 | case float_format::hex: // FIXME: implement 204 | case float_format::hex_upper: // FIXME: implement 205 | default: 206 | return dest; 207 | } 208 | } 209 | 210 | template 211 | char* detail::to_chars_impl_decimal(char* dest, char const* end, UnsignedIntT value) noexcept { 212 | static_assert(std::is_unsigned_v); 213 | 214 | // pathological case of an empty buffer; 215 | // checking this here simplifies following code 216 | // by assuring we can write at least one character 217 | // 218 | if (dest == end) { 219 | return dest; 220 | } 221 | 222 | int const total_digits = count_digits(value); 223 | size_t const available = end - dest; 224 | int const digits = min(total_digits, static_cast(available)); 225 | 226 | // trim any trailing digits if our value exceeds our buffer size 227 | // 228 | if (digits != total_digits) { 229 | value = rshift10(value, total_digits - digits); 230 | } 231 | 232 | // write digits; FIXME add table to do this in two-digit 233 | // batches to reduce divisions 234 | // 235 | char* ptr = dest + digits; 236 | do { 237 | *--ptr = '0' + (value % 10); 238 | value /= 10; 239 | } while (value != 0); 240 | 241 | return dest + digits; 242 | } 243 | 244 | template 245 | char* detail::to_chars_impl_hex(char* dest, char const* end, UnsignedIntT value) noexcept { 246 | static_assert(std::is_unsigned_v); 247 | 248 | // constants to process nibbles (half-bytes, or 4 bits) 249 | // 250 | constexpr int bits_per_nibble = 4; 251 | constexpr int nibbles_per_byte = 2; 252 | constexpr int total_nibbles = sizeof(value) * nibbles_per_byte; 253 | constexpr UnsignedIntT nibble_mask = 0b1111; 254 | 255 | // find the first non-zero nibble for our initial position 256 | // 257 | int const zero_nibbles = countl_zero(value) >> nibbles_per_byte; 258 | int shift = (total_nibbles - zero_nibbles - 1) * bits_per_nibble; 259 | UnsignedIntT mask = nibble_mask << shift; 260 | 261 | // continously "chop" nibbles off the integer for generating 262 | // the appropriate hex digit 263 | // 264 | // works MSB to LSB so that we can early-terminate when the 265 | // output buffer is full 266 | // 267 | // we slide the mask as we chop nibbles and have finished 268 | // when the mask completely slides off to zero 269 | // 270 | while (mask != 0 && dest != end) { 271 | UnsignedIntT const nibble = (value & mask) >> shift; 272 | 273 | if (nibble < 10) { 274 | *dest++ = static_cast('0' + nibble); 275 | } 276 | else { 277 | *dest++ = static_cast(A + nibble - 10); 278 | } 279 | 280 | shift -= bits_per_nibble; 281 | mask >>= bits_per_nibble; 282 | } 283 | 284 | return dest; 285 | } 286 | 287 | template 288 | char* detail::to_chars_impl_binary(char* dest, char const* end, UnsignedIntT value) noexcept { 289 | static_assert(std::is_unsigned_v); 290 | 291 | // find the initial non-zero bit in the input 292 | // 293 | int const zeroes = countl_zero(value); 294 | UnsignedIntT mask = 1ull << ((sizeof(value) * 8 - zeroes) - 1); 295 | 296 | // iterate the bits from MSB to LSB, so that we 297 | // can terminate if the buffer becomes full 298 | // 299 | while (mask != 0 && dest != end) { 300 | *dest++ = '0' + ((value & mask) != 0); 301 | mask >>= 1; 302 | } 303 | 304 | return dest; 305 | } 306 | 307 | template 308 | char* detail::to_chars_n_round(char* dest, char const* end, UnsignedIntT value, std::size_t count) noexcept { 309 | static_assert(std::is_unsigned_v); 310 | 311 | if (count == 0) { 312 | return dest; 313 | } 314 | 315 | char tmp[significand_max_digits10] = {}; 316 | 317 | // FIXME: we could use a much faster to_chars here; e.g. we could 318 | // keep this in reversed order, and we know it'll never truncate 319 | // 320 | size_t const tmp_len = to_chars(tmp, tmp + sizeof tmp, value) - tmp; 321 | 322 | // if we have as many or fewer digits as our round position, we're done (nothing 323 | // to round from) 324 | // 325 | if (tmp_len <= count) { 326 | return copy_to_n(dest, end, tmp, tmp_len); 327 | } 328 | 329 | char const remainder_digit = tmp[count]; 330 | 331 | // if the remainder is less than 1/2 then we have no rounding to do 332 | if (remainder_digit < '5') { 333 | return copy_to_n(dest, end, tmp, count); 334 | } 335 | 336 | // if our remainder is exactly 1/2 we round to nearest even; 337 | // the following loop handles rounding _up_, so we only need 338 | // to handle the special case of rounding _down_ here 339 | // 340 | // we identify "exactly 1/2" as having exactly one remaining 341 | // digit equal to 5; this only works because we have dragonbox 342 | // strip trialing zeroes! 343 | // 344 | if (remainder_digit == '5' && tmp_len - 1 == count) { 345 | char const last_digit = tmp[count - 1]; 346 | bool const is_even = ((last_digit - '0') & 1) == 0; 347 | if (is_even) { 348 | return copy_to_n(dest, end, tmp, count); 349 | } 350 | } 351 | 352 | // we're rounding up; walk backwards and round up characters so long 353 | // as we have a 9 to round up, and stop otherwise 354 | // 355 | char* ptr = tmp + count; 356 | while (ptr-- > tmp) { 357 | if (*ptr == '9') { 358 | *ptr = '0'; 359 | continue; 360 | } 361 | ++*ptr; 362 | break; 363 | } 364 | 365 | return copy_to_n(dest, end, tmp, count); 366 | } 367 | 368 | template 369 | char* detail::to_chars_impl_scientific( 370 | char* dest, 371 | char const* end, 372 | CarrierT significand, 373 | int exponent, 374 | int precision) noexcept { 375 | static_assert(std::is_unsigned_v); 376 | 377 | // generate significand digits, with truncation/rounding when appropriate 378 | // 379 | char digits[significand_max_digits10] = {}; 380 | char const* const digits_end = precision < 0 381 | ? to_chars(digits, digits + sizeof digits, significand) 382 | : to_chars_n_round(digits, digits + sizeof digits, significand, precision + 1); 383 | int const digits_count = static_cast(digits_end - digits); 384 | 385 | // calculate our adjusted exponent, which shifts the decimal point to be just after 386 | // the most significant digit (based on the actual significand, not the truncated/ 387 | // rounded version) 388 | // 389 | int const adjusted_exp = exponent + count_digits(significand) - 1; 390 | auto const absolute_exp = abs(adjusted_exp); 391 | 392 | // calculate the length of our fractional portion after the decimal point. 393 | // if a precision is provided, that specifies the precise number of digits 394 | // in the fractional portion; otherwise, it's just whatever is "leftover" 395 | // from the significand 396 | // 397 | int const fract_digits = precision < 0 ? digits_count - 1 : precision; 398 | 399 | // write most significant digit 400 | dest = put(dest, end, digits[0]); 401 | 402 | // write fractional part, if any 403 | if (fract_digits > 0) { 404 | dest = put(dest, end, '.'); 405 | 406 | int const fract_available = digits_count - 1; 407 | int const fract_buffer_size = min(fract_available, fract_digits); 408 | dest = copy_to_n(dest, end, digits + 1, fract_buffer_size); 409 | 410 | // fill in any trailing zeroes only if our mode allows it; this is effectively 411 | // always going to be the precision minus the number of fractional digits pulled 412 | // from the significand 413 | // 414 | if constexpr (TrailingZeroes) { 415 | dest = fill_n(dest, end, '0', static_cast(fract_digits) - fract_buffer_size); 416 | } 417 | } 418 | 419 | // write exponent, format [eE][+-][d]dd (zero-padded if exponent if less than two digits) 420 | // 421 | dest = put(dest, end, E); 422 | dest = put(dest, end, adjusted_exp < 0 ? '-' : '+'); 423 | if (absolute_exp < 10) { 424 | dest = put(dest, end, '0'); 425 | } 426 | return to_chars(dest, end, absolute_exp); 427 | } 428 | 429 | template 430 | char* detail::to_chars_impl_fixed( 431 | char* dest, 432 | char const* end, 433 | CarrierT significand, 434 | int exponent, 435 | int precision) noexcept { 436 | static_assert(std::is_unsigned_v); 437 | 438 | int const sig_digits = count_digits(significand); 439 | 440 | // find the location of the decimmal point 441 | // 442 | int const decimal_pos = sig_digits + exponent; 443 | 444 | // in the case that the decimal position is outside our 445 | // precision, we're just going to print zeroes and don't 446 | // even need to turn our significand into decimals 447 | // 448 | if (precision >= 0 && -decimal_pos > precision) { 449 | // we will only display zeroes due to truncation 450 | if constexpr (TrailingZeroes) { 451 | if (precision > 0) { 452 | dest = copy_to_n(dest, end, "0.", 2); 453 | return fill_n(dest, end, '0', precision); 454 | } 455 | } 456 | return put(dest, end, '0'); 457 | } 458 | 459 | // calculate how many of the significant digits _might_ be visible, 460 | // and how many _are_ visible 461 | int const max_visible_digits = precision < 0 ? sig_digits : decimal_pos + precision; 462 | int const visible_digits = min(sig_digits, max_visible_digits); 463 | 464 | // calculate the length of the integer (pre-decimal) part 465 | // 466 | int const integer_length = max(decimal_pos, 0); 467 | 468 | // calculate the visible integer (pre-decimal) and fractional (post-decimal) digits 469 | // of the significand 470 | // 471 | int const integer_digits = min(visible_digits, integer_length); 472 | int const fract_digits = visible_digits - integer_digits; 473 | 474 | // generate significand digits; we know the full buffer will always be used since 475 | // max_visible_digits is always _at least_ the actual digits we will actually display 476 | // 477 | char digits[significand_max_digits10]; 478 | to_chars_n_round(digits, digits + sizeof digits, significand, visible_digits); 479 | 480 | // write the integer portion of the significand and enough zeroes to reach 481 | // the decimal point; if there is no integer portion of the significand, 482 | // just write a zero 483 | if (decimal_pos > 0) { 484 | dest = copy_to_n(dest, end, digits, integer_digits); 485 | if (integer_length > integer_digits) { 486 | dest = fill_n(dest, end, '0', static_cast(integer_length) - integer_digits); 487 | } 488 | } 489 | else { 490 | dest = put(dest, end, '0'); 491 | } 492 | 493 | // write the fractional portion of the signifand and any appropriate trailing zeroes 494 | // 495 | if (fract_digits > 0 || (precision > 0 && TrailingZeroes)) { 496 | int const fract_offset = max(-decimal_pos, 0); 497 | 498 | dest = put(dest, end, '.'); 499 | dest = fill_n(dest, end, '0', fract_offset); 500 | dest = copy_to_n(dest, end, digits + integer_digits, fract_digits); 501 | 502 | if constexpr (TrailingZeroes) { 503 | int const fract_length_unused = fract_digits + fract_offset; 504 | int const fract_tail_zeroes = max(precision - fract_length_unused, 0); 505 | dest = fill_n(dest, end, '0', fract_tail_zeroes); 506 | } 507 | } 508 | 509 | return dest; 510 | } 511 | 512 | template 513 | char* detail::to_chars_impl_general( 514 | char* dest, 515 | char const* end, 516 | CarrierT significand, 517 | int exponent, 518 | int precision) noexcept { 519 | int const sig_digits = count_digits(significand); 520 | 521 | // C11 spec (see also https://en.cppreference.com/w/c/io/fprintf) 522 | // 523 | // For the g conversion style conversion with style e or f will be performed. 524 | // For the G conversion style conversion with style E or F will be performed. 525 | // 526 | // Let P equal the precision if nonzero, 6 if the precision is not specified, 527 | // or 1 if the precision is 0. 528 | // 529 | // Then, if a conversion with style E would have an exponent of X: 530 | // 531 | // if P > X >= -4, the conversion is with style f or F and precision P - 1 - X. 532 | // otherwise, the conversion is with style e or E and precision P - 1. 533 | // 534 | 535 | int const P = (precision < 0) ? 6 : (precision == 0) ? 1 : precision; 536 | int const X = exponent + sig_digits - 1; 537 | 538 | if (P > X && X >= -4) { 539 | return to_chars_impl_fixed(dest, end, significand, exponent, P - 1 - exponent); 540 | } 541 | return to_chars_impl_scientific(dest, end, significand, exponent, P - 1); 542 | } 543 | 544 | #if NANOFMT_FLOAT 545 | char* detail::to_chars_impl_nonfinite( 546 | char* dest, 547 | char const* end, 548 | bool negative, 549 | bool infinite, 550 | bool upper) noexcept { 551 | if (negative) { 552 | dest = put(dest, end, '-'); 553 | } 554 | if (infinite) { 555 | return copy_to(dest, end, upper ? "INF" : "inf"); 556 | } 557 | return copy_to(dest, end, upper ? "NAN" : "nan"); 558 | } 559 | #endif 560 | } // namespace NANOFMT_NS 561 | -------------------------------------------------------------------------------- /source/format.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #include "parse_utils.h" 4 | #include "nanofmt/charconv.h" 5 | #include "nanofmt/format.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace NANOFMT_NS { 11 | namespace detail { 12 | static constexpr char const* parse_spec( 13 | char const* in, 14 | char const* end, 15 | format_spec& spec, 16 | char const* allowed_types) noexcept; 17 | static void format_int_chars( 18 | format_output& out, 19 | char const* digits, 20 | size_t count, 21 | bool negative, 22 | format_spec const& spec) noexcept; 23 | static constexpr int_format select_int_format(char type) noexcept; 24 | static constexpr char const* parse_int_spec(char const* in, char const* end, format_spec& spec) noexcept; 25 | #if NANOFMT_FLOAT 26 | static constexpr char const* parse_float_spec(char const* in, char const* end, format_spec& spec) noexcept; 27 | #endif 28 | static constexpr std::size_t strnlen(char const* string, std::size_t max_length) noexcept; 29 | static void format_char_impl(char value, format_output& out, format_spec const& spec) noexcept; 30 | template 31 | static void format_int_impl(IntT value, format_output& out, format_spec const& spec) noexcept; 32 | static void format_string_impl( 33 | char const* value, 34 | std::size_t length, 35 | format_output& out, 36 | format_spec const& spec) noexcept; 37 | template 38 | static void format_float_impl(FloatT value, format_output& out, format_spec const& spec) noexcept; 39 | } // namespace detail 40 | 41 | template <> 42 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 43 | return parse_int_spec(in, end, spec); 44 | } 45 | 46 | template <> 47 | void detail::default_formatter::format(char value, format_output& out) noexcept { 48 | format_char_impl(value, out, spec); 49 | } 50 | 51 | template <> 52 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 53 | return parse_int_spec(in, end, spec); 54 | } 55 | 56 | template <> 57 | void detail::default_formatter::format(signed int value, format_output& out) noexcept { 58 | return format_int_impl(value, out, spec); 59 | } 60 | 61 | template <> 62 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 63 | return parse_int_spec(in, end, spec); 64 | } 65 | 66 | template <> 67 | void detail::default_formatter::format(unsigned int value, format_output& out) noexcept { 68 | return format_int_impl(value, out, spec); 69 | } 70 | 71 | template <> 72 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 73 | return parse_int_spec(in, end, spec); 74 | } 75 | 76 | template <> 77 | void detail::default_formatter::format(signed long value, format_output& out) noexcept { 78 | return format_int_impl(value, out, spec); 79 | } 80 | 81 | template <> 82 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 83 | return parse_int_spec(in, end, spec); 84 | } 85 | 86 | template <> 87 | void detail::default_formatter::format(unsigned long value, format_output& out) noexcept { 88 | return format_int_impl(value, out, spec); 89 | } 90 | 91 | template <> 92 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 93 | return parse_int_spec(in, end, spec); 94 | } 95 | 96 | template <> 97 | void detail::default_formatter::format(signed long long value, format_output& out) noexcept { 98 | return format_int_impl(value, out, spec); 99 | } 100 | 101 | template <> 102 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 103 | return parse_int_spec(in, end, spec); 104 | } 105 | 106 | template <> 107 | void detail::default_formatter::format(unsigned long long value, format_output& out) noexcept { 108 | return format_int_impl(value, out, spec); 109 | } 110 | 111 | #if NANOFMT_FLOAT 112 | template <> 113 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 114 | return parse_float_spec(in, end, spec); 115 | } 116 | 117 | template <> 118 | void detail::default_formatter::format(float value, format_output& out) noexcept { 119 | return format_float_impl(value, out, spec); 120 | } 121 | 122 | template <> 123 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 124 | return parse_float_spec(in, end, spec); 125 | } 126 | 127 | template <> 128 | void detail::default_formatter::format(double value, format_output& out) noexcept { 129 | return format_float_impl(value, out, spec); 130 | } 131 | #endif 132 | 133 | template <> 134 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 135 | return parse_spec(in, end, spec, "sbBcdoxX"); 136 | } 137 | 138 | template <> 139 | void detail::default_formatter::format(bool value, format_output& out) noexcept { 140 | switch (spec.type) { 141 | case '\0': 142 | case 's': 143 | out.append(value ? "true" : "false"); 144 | break; 145 | default: 146 | format_int_impl(static_cast(value), out, spec); 147 | return; 148 | } 149 | } 150 | 151 | template <> 152 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 153 | return parse_spec(in, end, spec, "p"); 154 | } 155 | 156 | template <> 157 | void detail::default_formatter::format(void const* value, format_output& out) noexcept { 158 | // hex encoding is 2 chars per octet 159 | char chars[sizeof(value) * 2]; 160 | char const* const end = 161 | to_chars(chars, chars + sizeof chars, reinterpret_cast(value), int_format::hex); 162 | out.append("0x"); 163 | out.append(chars, end - chars); 164 | } 165 | 166 | template <> 167 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 168 | return parse_spec(in, end, spec, "s"); 169 | } 170 | 171 | template <> 172 | void detail::default_formatter::format(char const* value, format_output& out) noexcept { 173 | if (value != nullptr) { 174 | format_string_impl(value, __builtin_strlen(value), out, spec); 175 | } 176 | } 177 | 178 | template <> 179 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 180 | return parse_spec(in, end, spec, "s"); 181 | } 182 | 183 | template <> 184 | void detail::default_formatter::format(char_buffer value, format_output& out) noexcept { 185 | format_string_impl(value.chars, strnlen(value.chars, value.max_length), out, spec); 186 | } 187 | 188 | template <> 189 | char const* detail::default_formatter::parse(char const* in, char const* end) noexcept { 190 | return parse_spec(in, end, spec, "s"); 191 | } 192 | 193 | template <> 194 | void detail::default_formatter::format(format_string_view value, format_output& out) noexcept { 195 | format_string_impl(value.string, value.length, out, spec); 196 | } 197 | 198 | format_output detail::vformat(format_output out, format_string format_str, format_args args) { 199 | int arg_next_index = 0; 200 | bool arg_auto_index = true; 201 | 202 | char const* input = format_str.begin; 203 | char const* input_begin = input; 204 | char const* const input_end = format_str.end; 205 | 206 | while (input != input_end) { 207 | if (*input != '{') { 208 | ++input; 209 | continue; 210 | } 211 | 212 | // write out the string so far, since we don't write characters immediately 213 | out.append(input_begin, input - input_begin); 214 | 215 | ++input; // swallow the { 216 | 217 | // if we hit the end of the input, we have an incomplete format, and nothing else we can do 218 | if (input == input_end) { 219 | return out; 220 | } 221 | 222 | // if we just have another { then take it as a literal character by starting our next begin here, 223 | // so it'll get written next time we write out the begin; nothing else to do for formatting here 224 | if (*input == '{') { 225 | input_begin = input++; 226 | continue; 227 | } 228 | 229 | // determine argument 230 | int arg_index = 0; 231 | if (arg_index = detail::parse_nonnegative(input, input_end); arg_index != -1) { 232 | arg_auto_index = false; 233 | } 234 | else if (arg_auto_index) { 235 | arg_index = arg_next_index++; 236 | } 237 | else { 238 | // we received a non-explicit index after an explicit index 239 | return out; 240 | } 241 | 242 | // extract formatter specification/arguments 243 | char const** spec = nullptr; 244 | if (input != input_end && *input == ':') { 245 | spec = &++input; 246 | } 247 | 248 | // format the value 249 | args.format(arg_index, spec, input_end, out); 250 | if (spec != nullptr) { 251 | input = *spec; 252 | } 253 | 254 | // consume parse specification, and any trailing } 255 | if (input != input_end && *input == '}') { 256 | ++input; 257 | } 258 | 259 | // mark where the next text run will begin 260 | input_begin = input; 261 | } 262 | 263 | // write out tail end of format string 264 | return out.append(input_begin, input - input_begin); 265 | } 266 | 267 | void format_args::format(unsigned index, char const** in, char const* end, format_output& out) const { 268 | using types = format_arg::type; 269 | 270 | if (index >= count) { 271 | return; 272 | } 273 | 274 | format_arg const& value = values[index]; 275 | 276 | auto invoke = [in, end, &out](auto value) { 277 | formatter fmt; 278 | if (in != nullptr) { 279 | *in = fmt.parse(*in, end); 280 | } 281 | fmt.format(value, out); 282 | }; 283 | 284 | switch (value.tag) { 285 | case types::t_mono: 286 | return; 287 | case types::t_char: 288 | return invoke(value.v_char); 289 | case types::t_int: 290 | return invoke(value.v_int); 291 | case types::t_uint: 292 | return invoke(value.v_unsigned); 293 | case types::t_long: 294 | return invoke(value.v_long); 295 | case types::t_ulong: 296 | return invoke(value.v_ulong); 297 | case types::t_longlong: 298 | return invoke(value.v_longlong); 299 | case types::t_ulonglong: 300 | return invoke(value.v_ulonglong); 301 | #if NANOFMT_FLOAT 302 | case types::t_float: 303 | return invoke(value.v_float); 304 | case types::t_double: 305 | return invoke(value.v_double); 306 | #endif 307 | case types::t_bool: 308 | return invoke(value.v_bool); 309 | case types::t_cstring: 310 | return invoke(value.v_cstring); 311 | case types::t_voidptr: 312 | return invoke(value.v_voidptr); 313 | case types::t_custom: 314 | return value.v_custom.thunk(value.v_custom.value, in, end, out); 315 | default: 316 | break; 317 | } 318 | } 319 | 320 | static constexpr char const* detail::parse_spec( 321 | char const* in, 322 | char const* end, 323 | format_spec& spec, 324 | char const* allowed_types) noexcept { 325 | if (in == end) { 326 | return in; 327 | } 328 | 329 | auto const is_align = [](char const* c, char const* e) noexcept { 330 | return c != e && (*c == '<' || *c == '>' || *c == '^'); 331 | }; 332 | 333 | // -- parse fill - 334 | // 335 | if (*in != '{' && *in != '}' && is_align(in + 1, end)) { 336 | spec.fill = *in; 337 | ++in; 338 | 339 | if (in == end) { 340 | return in; 341 | } 342 | } 343 | 344 | // -- parse alignment -- 345 | // 346 | switch (*in) { 347 | case '<': 348 | spec.align = -1; 349 | ++in; 350 | break; 351 | case '>': 352 | spec.align = +1; 353 | ++in; 354 | break; 355 | case '^': 356 | spec.align = 0; 357 | ++in; 358 | break; 359 | default: 360 | break; 361 | } 362 | if (in == end) { 363 | return in; 364 | } 365 | 366 | // -- parse sign -- 367 | // 368 | switch (*in) { 369 | case '+': 370 | case '-': 371 | case ' ': 372 | spec.sign = *in++; 373 | if (in == end) { 374 | return in; 375 | } 376 | break; 377 | default: 378 | break; 379 | } 380 | 381 | // -- parse alternate form flag -- 382 | // 383 | if (*in == '#') { 384 | spec.alt_form = true; 385 | ++in; 386 | if (in == end) { 387 | return in; 388 | } 389 | } 390 | 391 | // -- parse zero pad flag -- 392 | // 393 | if (*in == '0') { 394 | spec.zero_pad = true; 395 | ++in; 396 | if (in == end) { 397 | return in; 398 | } 399 | } 400 | 401 | // -- parse width 402 | // 403 | if (int const width = detail::parse_nonnegative(in, end); width >= 0) { 404 | spec.width = width; 405 | if (in == end) { 406 | return in; 407 | } 408 | 409 | // a width of 0 is not allowed 410 | if (width == 0) { 411 | return --in; 412 | } 413 | } 414 | 415 | // -- parse precision 416 | // 417 | if (*in == '.') { 418 | ++in; 419 | int const precision = detail::parse_nonnegative(in, end); 420 | 421 | if (precision < 0 || in == end) { 422 | return in; 423 | } 424 | 425 | spec.precision = precision; 426 | } 427 | 428 | // -- parse locale flag (ignored) 429 | // 430 | if (*in == 'L') { 431 | ++in; 432 | if (in == end) { 433 | return in; 434 | } 435 | } 436 | 437 | // -- parse type 438 | // 439 | if (allowed_types != nullptr) { 440 | for (char const* t = allowed_types; *t != 0; ++t) { 441 | if (*in == *t) { 442 | spec.type = *in++; 443 | break; 444 | } 445 | } 446 | } 447 | 448 | return in; 449 | } 450 | 451 | void detail::format_int_chars( 452 | format_output& out, 453 | char const* digits, 454 | size_t count, 455 | bool negative, 456 | format_spec const& spec) noexcept { 457 | if (*digits == '-') { 458 | ++digits; 459 | --count; 460 | } 461 | 462 | char const sign_char = negative ? '-' : (spec.sign == '+') ? '+' : (spec.sign == ' ') ? ' ' : '\0'; 463 | 464 | size_t const zero_padding = 465 | spec.zero_pad && ((spec.width > 0 && static_cast(spec.width) > count + (sign_char != '\0'))) 466 | ? spec.width - count - (sign_char != '\0') 467 | : 0; 468 | 469 | size_t const total_length = (sign_char != '\0') + count + zero_padding; 470 | 471 | if (spec.width > 0 && spec.align > 0 && static_cast(spec.width) > total_length) { 472 | out.fill_n(spec.fill, static_cast(spec.width) - total_length); 473 | } 474 | 475 | if (sign_char != '\0') { 476 | out.put(sign_char); 477 | } 478 | out.fill_n('0', zero_padding); 479 | out.append(digits, count); 480 | } 481 | 482 | constexpr int_format detail::select_int_format(char type) noexcept { 483 | switch (type) { 484 | default: 485 | case 'd': 486 | return int_format::decimal; 487 | case 'x': 488 | return int_format::hex; 489 | case 'X': 490 | return int_format::hex_upper; 491 | case 'b': 492 | return int_format::binary; 493 | } 494 | } 495 | 496 | constexpr char const* detail::parse_int_spec(char const* in, char const* end, format_spec& spec) noexcept { 497 | spec.align = +1; /* right-align by default */ 498 | return parse_spec(in, end, spec, "bBcdoxX"); 499 | } 500 | 501 | #if NANOFMT_FLOAT 502 | constexpr char const* detail::parse_float_spec(char const* in, char const* end, format_spec& spec) noexcept { 503 | return parse_spec(in, end, spec, "aAeEfFgG"); 504 | } 505 | #endif 506 | 507 | constexpr std::size_t detail::strnlen(char const* string, std::size_t max_length) noexcept { 508 | for (std::size_t length = 0; length != max_length; ++length) { 509 | if (string[length] == '\0') { 510 | return length; 511 | } 512 | } 513 | return max_length; 514 | } 515 | 516 | void detail::format_char_impl(char value, format_output& out, format_spec const& spec) noexcept { 517 | switch (spec.type) { 518 | case '\0': 519 | case 'c': 520 | out.put(value); 521 | break; 522 | default: 523 | return format_int_impl(static_cast(value), out, spec); 524 | } 525 | } 526 | 527 | template 528 | void detail::format_int_impl(IntT value, format_output& out, format_spec const& spec) noexcept { 529 | if (spec.type == 'c') { 530 | return format_char_impl(static_cast(value), out, spec); 531 | } 532 | 533 | // binary encoding is the widest; FIXME: this is icky 534 | char chars[sizeof(value) * 8] = { 535 | 0, 536 | }; 537 | 538 | size_t const length = (spec.precision >= 0 && static_cast(spec.precision) < sizeof(chars)) 539 | ? spec.precision 540 | : sizeof(chars); 541 | 542 | const char* const end = to_chars(chars, chars + length, value, select_int_format(spec.type)); 543 | format_int_chars(out, chars, end - chars, value < 0, spec); 544 | } 545 | 546 | void detail::format_string_impl( 547 | char const* value, 548 | std::size_t length, 549 | format_output& out, 550 | format_spec const& spec) noexcept { 551 | if (spec.width < 0 || length >= static_cast(spec.width)) { 552 | out.append(value, length); 553 | return; 554 | } 555 | 556 | auto const padding = static_cast(spec.width) - length; 557 | if (spec.align < 0) { 558 | out.append(value, length); 559 | out.fill_n(' ', padding); 560 | } 561 | else if (spec.align > 0) { 562 | out.fill_n(' ', padding); 563 | out.append(value, length); 564 | } 565 | else { 566 | auto const front_padding = padding / 2; 567 | auto const back_padding = padding - front_padding; 568 | out.fill_n(' ', front_padding); 569 | out.append(value, length); 570 | out.fill_n(' ', back_padding); 571 | } 572 | } 573 | 574 | template 575 | void detail::format_float_impl(FloatT value, format_output& out, format_spec const& spec) noexcept { 576 | if (!std::signbit(value)) { 577 | if (spec.sign == '+') { 578 | out.put('+'); 579 | } 580 | else if (spec.sign == ' ') { 581 | out.put(' '); 582 | } 583 | } 584 | 585 | switch (spec.type) { 586 | default: 587 | case 'g': 588 | case 'G': 589 | out.advance_to(to_chars( 590 | out.pos, 591 | out.end, 592 | value, 593 | spec.type == 'G' ? float_format::general_upper : float_format::general, 594 | spec.precision)); 595 | break; 596 | case 'e': 597 | case 'E': 598 | out.advance_to(to_chars( 599 | out.pos, 600 | out.end, 601 | value, 602 | spec.type == 'E' ? float_format::scientific_upper : float_format::scientific, 603 | spec.precision < 0 ? 6 : spec.precision)); 604 | break; 605 | case 'f': 606 | case 'F': 607 | out.advance_to( 608 | to_chars(out.pos, out.end, value, float_format::fixed, spec.precision < 0 ? 6 : spec.precision)); 609 | break; 610 | } 611 | } 612 | } // namespace NANOFMT_NS 613 | -------------------------------------------------------------------------------- /source/numeric_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #pragma once 4 | 5 | #include "nanofmt/config.h" 6 | 7 | #include 8 | 9 | namespace NANOFMT_NS::detail { 10 | template 11 | constexpr int count_digits(UnsignedIntT value) noexcept; 12 | 13 | template 14 | constexpr UnsignedIntT rshift10(UnsignedIntT value, int shift) noexcept; 15 | 16 | template 17 | constexpr int countl_zero(UnsignedIntT value) noexcept; 18 | 19 | template 20 | constexpr auto abs(IntT value) noexcept; 21 | 22 | template 23 | constexpr auto(min)(T lhs, T rhs) noexcept -> T { 24 | return lhs < rhs ? lhs : rhs; 25 | } 26 | 27 | template 28 | constexpr auto(max)(T lhs, T rhs) noexcept -> T { 29 | return lhs < rhs ? rhs : lhs; 30 | } 31 | 32 | template 33 | constexpr int count_digits(UnsignedIntT value) noexcept { 34 | static_assert(std::is_unsigned_v); 35 | 36 | int result = 1; 37 | for (;;) { 38 | if (value < 10) { 39 | return result; 40 | } 41 | if (value < 100) { 42 | return result + 1; 43 | } 44 | if constexpr (sizeof(UnsignedIntT) > 1) { 45 | if (value < 1'000) { 46 | return result + 2; 47 | } 48 | if (value < 10'000) { 49 | return result + 3; 50 | } 51 | value /= 10'000u; 52 | result += 4; 53 | } 54 | else { 55 | return result + 2; 56 | } 57 | } 58 | } 59 | 60 | // chop off any digits that cannot fit in the destination 61 | // 62 | template 63 | constexpr UnsignedIntT rshift10(UnsignedIntT value, int shift) noexcept { 64 | if constexpr (sizeof(UnsignedIntT) > 1) { 65 | while (shift >= 4) { 66 | value /= 10'000u; 67 | shift -= 4; 68 | } 69 | if (shift == 3) { 70 | return value / 1'000u; 71 | } 72 | } 73 | if (shift == 2) { 74 | return value / 100u; 75 | } 76 | if (shift == 1) { 77 | return value / 10u; 78 | } 79 | return value; 80 | } 81 | 82 | #if defined(__has_builtin) 83 | # if __has_builtin(__builtin_clz) 84 | # define NANOFMT_HAS_BUILTIN_CLZ 1 85 | # endif 86 | #endif 87 | #if defined(__clang__) || defined(__GNUC__) 88 | # define NANOFMT_CLANG_OR_GCC 1 89 | #endif 90 | #if defined(_WIN32) 91 | # define NANOFMT_HAS_BSR 1 92 | #endif 93 | 94 | template 95 | constexpr int countl_zero(UnsignedIntT value) noexcept { 96 | #if NANOFMT_HAS_BUILTIN_CLZ || NANOFMT_CLANG_OR_GCC 97 | if (value == 0) { 98 | return sizeof(value) * 8; 99 | } 100 | if constexpr (sizeof value <= 4) { 101 | return __builtin_clz(value); 102 | } 103 | else { 104 | return __builtin_clzll(value); 105 | } 106 | #elif NANOFMT_HAS_BSR 107 | // __builtin_clz / __builtin_clzll 108 | if constexpr (sizeof value <= 4) { 109 | unsigned long index = 0; 110 | return _BitScanReverse(&index, value) ? 31 - index : 32; 111 | } 112 | else { 113 | unsigned long index = 0; 114 | return _BitScanReverse64(&index, value) ? 63 - index : 64; 115 | } 116 | #else 117 | # error "nanofmt::detail::countl_zero not implemented for this compiler/platform" 118 | return -1; 119 | #endif 120 | } 121 | 122 | template 123 | constexpr auto abs(IntT value) noexcept { 124 | using Unsigned = std::make_unsigned_t; 125 | 126 | if constexpr (std::is_signed_v) { 127 | if (value < 0) { 128 | return static_cast(0 - static_cast(value)); 129 | } 130 | } 131 | return static_cast(value); 132 | } 133 | } // namespace NANOFMT_NS::detail 134 | -------------------------------------------------------------------------------- /source/parse_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #pragma once 4 | 5 | #include "nanofmt/config.h" 6 | 7 | namespace NANOFMT_NS::detail { 8 | constexpr int parse_nonnegative(char const*& start, char const* end) noexcept { 9 | if (start == end) { 10 | return -1; 11 | } 12 | 13 | if (*start == '0') { 14 | ++start; 15 | return 0; 16 | } 17 | 18 | // there must be at least one non-zero digit 19 | if (!(*start >= '1' && *start <= '9')) { 20 | return -1; 21 | } 22 | 23 | int result = 0; 24 | while (start != end && *start >= '0' && *start <= '9') { 25 | result *= 10; 26 | result += *start - '0'; 27 | ++start; 28 | } 29 | return result; 30 | } 31 | } // namespace NANOFMT_NS::detail 32 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(fetch_doctest) 2 | 3 | add_executable(nanofmt_test) 4 | target_sources(nanofmt_test PRIVATE 5 | "fwd_only_type.h" 6 | "test_charconv.cpp" 7 | "test_format.cpp" 8 | "test_format_args.cpp" 9 | "test_utils.h" 10 | ) 11 | target_link_libraries(nanofmt_test PRIVATE 12 | nanofmt 13 | doctest::doctest_with_main 14 | ) 15 | 16 | add_test(NAME nanofmt_test COMMAND nanofmt_test) 17 | -------------------------------------------------------------------------------- /tests/fwd_only_type.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #pragma once 4 | 5 | #if defined(NANOFMT_FORMAT_H_) 6 | # error "fwd_only_type.h should be included before format.h for the test to be correct" 7 | #endif 8 | 9 | #include "nanofmt/forward.h" 10 | 11 | struct fwd_only_type {}; 12 | 13 | namespace NANOFMT_NS { 14 | template <> 15 | struct formatter { 16 | constexpr char const* parse(char const* in, char const*) noexcept { 17 | return in; 18 | } 19 | 20 | template 21 | void format(fwd_only_type, OutputT& output) { 22 | output.append("fwd_only_type"); 23 | } 24 | }; 25 | } // namespace NANOFMT_NS 26 | -------------------------------------------------------------------------------- /tests/test_charconv.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #include "test_utils.h" 4 | 5 | #include "nanofmt/charconv.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | TEST_CASE("nanofmt.to_chars.integers") { 13 | using namespace NANOFMT_NS::test; 14 | using namespace NANOFMT_NS; 15 | 16 | SUBCASE("decimal") { 17 | CHECK(to_string(0) == "0"); 18 | CHECK(to_string(1) == "1"); 19 | CHECK(to_string(10) == "10"); 20 | CHECK(to_string(100) == "100"); 21 | CHECK(to_string(1'000) == "1000"); 22 | } 23 | 24 | SUBCASE("hex") { 25 | CHECK(to_string(0x0, int_format::hex) == "0"); 26 | CHECK(to_string(0x1, int_format::hex) == "1"); 27 | 28 | CHECK(to_string(0xdeadc0de, int_format::hex) == "deadc0de"); 29 | CHECK(to_string(0xdeadc0de, int_format::hex_upper) == "DEADC0DE"); 30 | } 31 | 32 | SUBCASE("binary") { 33 | CHECK(to_string(0b0, int_format::binary) == "0"); 34 | CHECK(to_string(0b1, int_format::binary) == "1"); 35 | 36 | CHECK(to_string(0b10011001, int_format::binary) == "10011001"); 37 | 38 | CHECK(to_string(-0b01011010, int_format::binary) == "-1011010"); 39 | } 40 | 41 | SUBCASE("negatives") { 42 | CHECK(to_string(-0) == "0"); 43 | CHECK(to_string(-1) == "-1"); 44 | CHECK(to_string(-10) == "-10"); 45 | CHECK(to_string(-100) == "-100"); 46 | CHECK(to_string(-1'000) == "-1000"); 47 | } 48 | 49 | SUBCASE("bounds") { 50 | CHECK(to_string(std::numeric_limits::max()) == "127"); 51 | CHECK(to_string(std::numeric_limits::max()) == "32767"); 52 | CHECK(to_string(std::numeric_limits::max()) == "2147483647"); 53 | CHECK(to_string(std::numeric_limits::max()) == "9223372036854775807"); 54 | 55 | CHECK(to_string(std::numeric_limits::min()) == "-128"); 56 | CHECK(to_string(std::numeric_limits::min()) == "-32768"); 57 | CHECK(to_string(std::numeric_limits::min()) == "-2147483648"); 58 | CHECK(to_string(std::numeric_limits::min()) == "-9223372036854775808"); 59 | 60 | CHECK(to_string(std::numeric_limits::max()) == "255"); 61 | CHECK(to_string(std::numeric_limits::max()) == "65535"); 62 | CHECK(to_string(std::numeric_limits::max()) == "4294967295"); 63 | CHECK(to_string(std::numeric_limits::max()) == "18446744073709551615"); 64 | } 65 | } 66 | 67 | TEST_CASE("nanofmt.to_chars.fixed") { 68 | using namespace NANOFMT_NS::test; 69 | using namespace NANOFMT_NS; 70 | 71 | SUBCASE("whole numbers") { 72 | CHECK(to_string(0e0f, float_format::fixed) == "0"); 73 | CHECK(to_string(1e0f, float_format::fixed) == "1"); 74 | CHECK(to_string(1e1f, float_format::fixed) == "10"); 75 | CHECK(to_string(1e2f, float_format::fixed) == "100"); 76 | CHECK(to_string(1e3f, float_format::fixed) == "1000"); 77 | CHECK(to_string(1e4f, float_format::fixed) == "10000"); 78 | } 79 | 80 | SUBCASE("fractional numbers") { 81 | CHECK(to_string(1e-1f, float_format::fixed) == "0.1"); 82 | CHECK(to_string(1e-2f, float_format::fixed) == "0.01"); 83 | CHECK(to_string(1e-3f, float_format::fixed) == "0.001"); 84 | CHECK(to_string(1e-4f, float_format::fixed) == "0.0001"); 85 | } 86 | 87 | SUBCASE("bounds") { 88 | CHECK( 89 | to_string(std::numeric_limits::max(), float_format::fixed) == 90 | "340282350000000000000000000000000000000"); 91 | CHECK( 92 | to_string(std::numeric_limits::max(), float_format::fixed) == 93 | "179769313486231570000000000000000000000000000000000000000000000000000000000" 94 | "0000000000000000000000000000000000000000000000000000000000000000000000000000" 95 | "0000000000000000000000000000000000000000000000000000000000000000000000000000" 96 | "0000000000000000000000000000000000000000000000000000000000000000000000000000" 97 | "000000"); 98 | 99 | CHECK( 100 | to_string(std::numeric_limits::min(), float_format::fixed) == 101 | "0.000000000000000000000000000000000000011754944"); 102 | CHECK( 103 | to_string(std::numeric_limits::min(), float_format::fixed) == 104 | "0.0000000000000000000000000000000000000000000000000000000000000000000000000000" 105 | "0000000000000000000000000000000000000000000000000000000000000000000000000000" 106 | "0000000000000000000000000000000000000000000000000000000000000000000000000000" 107 | "0000000000000000000000000000000000000000000000000000000000000000000000000000" 108 | "00022250738585072014"); 109 | } 110 | 111 | SUBCASE("rounding") { 112 | CHECK(to_string(.001, float_format::fixed, 0) == "0"); 113 | CHECK(to_string(1.55, float_format::fixed, 0) == "2"); 114 | CHECK(to_string(1.55, float_format::fixed, 1) == "1.6"); 115 | CHECK(to_string(1.35, float_format::fixed, 1) == "1.4"); 116 | CHECK(to_string(1.25, float_format::fixed, 1) == "1.2"); 117 | CHECK(to_string(1.351, float_format::fixed, 1) == "1.4"); 118 | CHECK(to_string(1.251, float_format::fixed, 1) == "1.3"); 119 | } 120 | 121 | SUBCASE("nonfinite") { 122 | CHECK(to_string(std::numeric_limits::infinity(), float_format::fixed) == "inf"); 123 | CHECK(to_string(-std::numeric_limits::infinity(), float_format::fixed) == "-inf"); 124 | CHECK(to_string(std::numeric_limits::quiet_NaN(), float_format::fixed) == "nan"); 125 | CHECK(to_string(-std::numeric_limits::quiet_NaN(), float_format::fixed) == "-nan"); 126 | } 127 | } 128 | 129 | TEST_CASE("nanofmt.to_chars.scientifix") { 130 | using namespace NANOFMT_NS::test; 131 | using namespace NANOFMT_NS; 132 | 133 | SUBCASE("whole numbers") { 134 | CHECK(to_string(0e0f, float_format::scientific) == "0e+00"); 135 | CHECK(to_string(1e0f, float_format::scientific) == "1e+00"); 136 | CHECK(to_string(1e1f, float_format::scientific) == "1e+01"); 137 | CHECK(to_string(1e2f, float_format::scientific) == "1e+02"); 138 | CHECK(to_string(1e3f, float_format::scientific) == "1e+03"); 139 | CHECK(to_string(1e4f, float_format::scientific) == "1e+04"); 140 | } 141 | 142 | SUBCASE("fractional numbers") { 143 | CHECK(to_string(1e-1f, float_format::scientific) == "1e-01"); 144 | CHECK(to_string(1e-2f, float_format::scientific) == "1e-02"); 145 | CHECK(to_string(1e-3f, float_format::scientific) == "1e-03"); 146 | CHECK(to_string(1e-4f, float_format::scientific) == "1e-04"); 147 | } 148 | 149 | SUBCASE("mixed numbers") { 150 | CHECK(to_string(1.55e-1f, float_format::scientific) == "1.55e-01"); 151 | CHECK(to_string(12.55e-2f, float_format::scientific) == "1.255e-01"); 152 | CHECK(to_string(12'000.55'001f, float_format::scientific) == "1.200055e+04"); 153 | CHECK(to_string(1.55e-4f, float_format::scientific) == "1.55e-04"); 154 | } 155 | 156 | SUBCASE("bounds") { 157 | CHECK(to_string(std::numeric_limits::max(), float_format::scientific) == "3.4028235e+38"); 158 | CHECK(to_string(std::numeric_limits::max(), float_format::scientific) == "1.7976931348623157e+308"); 159 | 160 | CHECK(to_string(std::numeric_limits::min(), float_format::scientific) == "1.1754944e-38"); 161 | CHECK(to_string(std::numeric_limits::min(), float_format::scientific) == "2.2250738585072014e-308"); 162 | } 163 | 164 | SUBCASE("rounding") { 165 | CHECK(to_string(1.55e-4f, float_format::scientific, 0) == "2e-04"); 166 | CHECK(to_string(1.35e-4f, float_format::scientific, 1) == "1.4e-04"); 167 | CHECK(to_string(1.25e-4f, float_format::scientific, 1) == "1.2e-04"); 168 | CHECK(to_string(1.351e-4f, float_format::scientific, 1) == "1.4e-04"); 169 | CHECK(to_string(1.251e-4f, float_format::scientific, 1) == "1.3e-04"); 170 | } 171 | 172 | SUBCASE("nonfinite") { 173 | CHECK(to_string(std::numeric_limits::infinity(), float_format::scientific) == "inf"); 174 | CHECK(to_string(-std::numeric_limits::infinity(), float_format::scientific) == "-inf"); 175 | CHECK(to_string(std::numeric_limits::quiet_NaN(), float_format::scientific) == "nan"); 176 | CHECK(to_string(-std::numeric_limits::quiet_NaN(), float_format::scientific) == "-nan"); 177 | } 178 | } 179 | 180 | TEST_CASE("nanofmt.to_chars.general") { 181 | using namespace NANOFMT_NS::test; 182 | using namespace NANOFMT_NS; 183 | 184 | SUBCASE("bounds") { 185 | CHECK(to_string(std::numeric_limits::max(), float_format::general) == "3.40282e+38"); 186 | CHECK(to_string(std::numeric_limits::max(), float_format::general) == "1.79769e+308"); 187 | 188 | CHECK(to_string(std::numeric_limits::min(), float_format::general) == "1.17549e-38"); 189 | CHECK(to_string(std::numeric_limits::min(), float_format::general) == "2.22507e-308"); 190 | } 191 | 192 | SUBCASE("nonfinite") { 193 | CHECK(to_string(std::numeric_limits::infinity(), float_format::general) == "inf"); 194 | CHECK(to_string(-std::numeric_limits::infinity(), float_format::general) == "-inf"); 195 | CHECK(to_string(std::numeric_limits::quiet_NaN(), float_format::general) == "nan"); 196 | CHECK(to_string(-std::numeric_limits::quiet_NaN(), float_format::general) == "-nan"); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/test_format.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #include "fwd_only_type.h" 4 | #include "test_utils.h" 5 | 6 | #include "nanofmt/format.h" 7 | #include "nanofmt/std_string.h" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | enum class standard_enum { one, two }; 15 | enum class custom_enum { foo, bar }; 16 | 17 | struct custom_type { 18 | int value = 0; 19 | }; 20 | 21 | struct unknown {}; 22 | 23 | namespace NANOFMT_NS { 24 | template <> 25 | struct formatter : formatter { 26 | void format(custom_enum value, format_output& out) { 27 | switch (value) { 28 | case custom_enum::foo: 29 | formatter::format("foo", out); 30 | break; 31 | case custom_enum::bar: 32 | formatter::format("bar", out); 33 | break; 34 | } 35 | } 36 | }; 37 | 38 | template <> 39 | struct formatter { 40 | constexpr char const* parse(char const* in, char const*) noexcept { 41 | return in; 42 | } 43 | 44 | void format(custom_type custom, format_output& out) { 45 | out.format("custom{{{}}", custom.value); 46 | } 47 | }; 48 | } // namespace NANOFMT_NS 49 | 50 | static_assert(NANOFMT_NS::detail::has_formatter::value, "has_formatter failed"); 51 | static_assert(NANOFMT_NS::detail::has_formatter::value, "has_formatter failed"); 52 | 53 | TEST_CASE("nanofmt.format.core") { 54 | using namespace NANOFMT_NS; 55 | 56 | SUBCASE("format_to overflow") { 57 | char buffer[12]; 58 | std::memset(buffer, 0xfe, sizeof buffer); 59 | 60 | char const* const end = format_to(buffer, "Hello, {}! {:09d}", "World", 9001); 61 | REQUIRE(*end == '\0'); 62 | 63 | CHECK(std::strcmp(buffer, "Hello, Worl") == 0); 64 | } 65 | 66 | SUBCASE("format_to_n overflow") { 67 | char buffer[12]; 68 | char* const end = format_to_n(buffer, sizeof buffer, "Hello, {}! {:09d}", "World", 9001); 69 | 70 | CHECK((end - buffer) == 12); 71 | CHECK(std::strncmp(buffer, "Hello, World", sizeof buffer) == 0); 72 | } 73 | } 74 | 75 | TEST_CASE("nanofmt.format.append") { 76 | using namespace NANOFMT_NS; 77 | 78 | char buffer[12] = {}; 79 | format_to(buffer, "Hello"); 80 | format_append_to(buffer, "{} ", ','); 81 | char* const end = format_append_to(buffer, "World! {:09d}", 9001); 82 | 83 | CHECK((end - buffer) == 11); 84 | CHECK(std::strcmp(buffer, "Hello, Worl") == 0); 85 | } 86 | 87 | TEST_CASE("nanofmt.format.integers") { 88 | using namespace NANOFMT_NS::test; 89 | 90 | SUBCASE("width and fill") { 91 | CHECK(sformat("{:6d}", 1234) == " 1234"); 92 | CHECK(sformat("{:6d}", -1234) == " -1234"); 93 | } 94 | 95 | SUBCASE("zero pad") { 96 | CHECK(sformat("{:06}", 1234) == "001234"); 97 | CHECK(sformat("{:06}", -1234) == "-01234"); 98 | } 99 | 100 | SUBCASE("precision") { 101 | // BUG? -- fmtlib/std::format doesn't support precision for integral types, should we nuke this? 102 | CHECK(sformat("{:.4}", 123456) == "1234"); 103 | CHECK(sformat("{:.4b}", 0b1001'0110) == "1001"); 104 | } 105 | 106 | SUBCASE("char") { 107 | CHECK(sformat("{:d}", ' ') == "32"); 108 | } 109 | 110 | SUBCASE("hex") { 111 | CHECK(sformat("{:x}", ~16u) == "ffffffef"); 112 | CHECK(sformat("{:08x}", 0xDEAD) == "0000dead"); 113 | CHECK(sformat("{:08X}", 0xDEAD) == "0000DEAD"); 114 | } 115 | 116 | SUBCASE("binary") { 117 | CHECK(sformat("{:b}", 0) == "0"); 118 | CHECK(sformat("{:b}", 0b11011) == "11011"); 119 | CHECK(sformat("{:b}", 5) == "101"); 120 | CHECK(sformat("{:b}", -256) == "-100000000"); 121 | } 122 | } 123 | 124 | TEST_CASE("nanofmt.format.floating") { 125 | using namespace NANOFMT_NS::test; 126 | 127 | SUBCASE("precision") { 128 | CHECK(sformat("{:.1f}", 1.55) == "1.6"); 129 | CHECK(sformat("{:.1e}", 1.45) == "1.4e+00"); 130 | CHECK(sformat("{}", std::numeric_limits::max()) == "3.40282e+38"); 131 | } 132 | 133 | SUBCASE("signs") { 134 | CHECK(sformat("{:+.3}", 1.0) == "+1"); 135 | CHECK(sformat("{:+.3}", -1.0) == "-1"); 136 | CHECK(sformat("{:-.3}", 1.0) == "1"); 137 | CHECK(sformat("{:-.3}", -1.0) == "-1"); 138 | CHECK(sformat("{: .3}", 1.0) == " 1"); 139 | CHECK(sformat("{: .3}", -1.0) == "-1"); 140 | } 141 | 142 | SUBCASE("nonfinite") { 143 | CHECK(sformat("{:f}", std::numeric_limits::infinity()) == "inf"); 144 | CHECK(sformat("{:f}", -std::numeric_limits::infinity()) == "-inf"); 145 | 146 | CHECK(sformat("{:f}", std::numeric_limits::quiet_NaN()) == "nan"); 147 | CHECK(sformat("{:f}", -std::numeric_limits::quiet_NaN()) == "-nan"); 148 | } 149 | 150 | SUBCASE("casing") { 151 | CHECK(sformat("{:.2E}", -12.99) == "-1.30E+01"); 152 | CHECK(sformat("{:E}", -std::numeric_limits::infinity()) == "-INF"); 153 | 154 | CHECK(sformat("{:G}", -.000'123) == "-0.000123"); 155 | CHECK(sformat("{:G}", std::numeric_limits::quiet_NaN()) == "NAN"); 156 | } 157 | } 158 | 159 | TEST_CASE("nanofmt.format.strings") { 160 | using namespace NANOFMT_NS::test; 161 | 162 | SUBCASE("char arrays") { 163 | char const s[] = "array"; 164 | 165 | CHECK(sformat("{}", "test") == "test"); 166 | CHECK(sformat("{}", s) == "array"); 167 | } 168 | 169 | SUBCASE("stdlib") { 170 | CHECK(sformat("{}", std::string("test")) == "test"); 171 | CHECK(sformat("{}", std::string_view("test")) == "test"); 172 | 173 | CHECK(sformat("{}{}{}", std::string_view("ab"), std::string("cd"), "ef") == "abcdef"); 174 | 175 | CHECK(sformat(nanofmt::format_string(std::string("a{}c")), "b") == "abc"); 176 | } 177 | 178 | SUBCASE("width and fill") { 179 | CHECK(sformat("{:<8}{:05}", "value", 42) == "value 00042"); 180 | } 181 | } 182 | 183 | TEST_CASE("nanofmt.format.bools") { 184 | using namespace NANOFMT_NS::test; 185 | 186 | SUBCASE("default") { 187 | CHECK(sformat("{}", true) == "true"); 188 | CHECK(sformat("{}", false) == "false"); 189 | } 190 | 191 | SUBCASE("integer") { 192 | CHECK(sformat("{:d}", true) == "1"); 193 | CHECK(sformat("{:d}", false) == "0"); 194 | } 195 | } 196 | 197 | TEST_CASE("nanofmt.format.pointers") { 198 | using namespace NANOFMT_NS::test; 199 | 200 | SUBCASE("nullptr") { 201 | CHECK(sformat("{}", nullptr) == "0x0"); 202 | } 203 | 204 | SUBCASE("raw") { 205 | void const* ptr = reinterpret_cast(static_cast(0xDEADC0DE)); 206 | int const* iptr = reinterpret_cast(static_cast(0xFEFEFEFE)); 207 | 208 | CHECK(sformat("{}", ptr) == "0xdeadc0de"); 209 | CHECK(sformat("{}", iptr) == "0xfefefefe"); 210 | } 211 | } 212 | 213 | TEST_CASE("nanofmt.format.enums") { 214 | using namespace NANOFMT_NS::test; 215 | 216 | SUBCASE("standard") { 217 | CHECK(sformat("{0}", standard_enum::two) == "1"); 218 | } 219 | 220 | SUBCASE("custom") { 221 | CHECK(sformat("{}", custom_enum::bar) == "bar"); 222 | } 223 | } 224 | 225 | TEST_CASE("nanofmt.format.custom") { 226 | using namespace NANOFMT_NS::test; 227 | 228 | custom_type local{7}; 229 | custom_type& ref = local; 230 | 231 | custom_type const clocal{7}; 232 | custom_type const& cref = clocal; 233 | 234 | CHECK(sformat("{}", custom_type{1}) == "custom{1}"); 235 | CHECK(sformat("{}", local) == "custom{7}"); 236 | CHECK(sformat("{}", ref) == "custom{7}"); 237 | CHECK(sformat("{}", clocal) == "custom{7}"); 238 | CHECK(sformat("{}", cref) == "custom{7}"); 239 | 240 | CHECK(sformat("{}", fwd_only_type{}) == "fwd_only_type"); 241 | } 242 | 243 | TEST_CASE("nanofmt.format.length") { 244 | using namespace NANOFMT_NS; 245 | 246 | CHECK(format_length("{}") == 0); 247 | CHECK(format_length("{}", 777) == 3); 248 | 249 | // https://github.com/seanmiddleditch/nanofmt/issues/49 250 | CHECK(format_length("{0} = 0x{0:X} = 0b{0:b}", 28) == 19); 251 | } 252 | 253 | // TEST_CASE("nanofmt.format.compile_error") { 254 | // using namespace NANOFMT_NS::test; 255 | // 256 | // CHECK(sformat("{}", unknown{}) == ""); 257 | //} 258 | -------------------------------------------------------------------------------- /tests/test_format_args.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #include "test_utils.h" 4 | 5 | #include "nanofmt/format.h" 6 | 7 | #include 8 | 9 | namespace { 10 | template 11 | constexpr auto to_arg(T const& value) noexcept { 12 | return ::NANOFMT_NS::make_format_args(value).values[0]; 13 | } 14 | } // namespace 15 | 16 | TEST_CASE("nanofmt.format_arg.misc") { 17 | using namespace NANOFMT_NS; 18 | 19 | CHECK(to_arg(true).tag == format_arg::type::t_bool); 20 | CHECK(to_arg('c').tag == format_arg::type::t_char); 21 | } 22 | 23 | TEST_CASE("nanofmt.format_arg.integers") { 24 | using namespace NANOFMT_NS; 25 | 26 | SUBCASE("misc") { 27 | CHECK(to_arg(true).tag == format_arg::type::t_bool); 28 | CHECK(to_arg('c').tag == format_arg::type::t_char); 29 | } 30 | 31 | SUBCASE("signed") { 32 | using signed_char = signed char; 33 | 34 | CHECK(to_arg(signed_char{}).tag == format_arg::type::t_int); 35 | CHECK(to_arg(short{}).tag == format_arg::type::t_int); 36 | CHECK(to_arg(0).tag == format_arg::type::t_int); 37 | CHECK(to_arg(0l).tag == format_arg::type::t_long); 38 | CHECK(to_arg(0ll).tag == format_arg::type::t_longlong); 39 | } 40 | 41 | SUBCASE("unsigned") { 42 | using unsigned_char = unsigned char; 43 | using unsigned_short = unsigned short; 44 | 45 | CHECK(to_arg(unsigned_char{}).tag == format_arg::type::t_uint); 46 | CHECK(to_arg(unsigned_short{}).tag == format_arg::type::t_uint); 47 | CHECK(to_arg(0u).tag == format_arg::type::t_uint); 48 | CHECK(to_arg(0ul).tag == format_arg::type::t_ulong); 49 | CHECK(to_arg(0ull).tag == format_arg::type::t_ulonglong); 50 | } 51 | } 52 | 53 | TEST_CASE("nanofmt.format_arg.floats") { 54 | using namespace NANOFMT_NS; 55 | 56 | CHECK(to_arg(0.f).tag == format_arg::type::t_float); 57 | CHECK(to_arg(0.0).tag == format_arg::type::t_double); 58 | } 59 | 60 | TEST_CASE("nanofmt.format_arg.pointers") { 61 | using namespace NANOFMT_NS; 62 | 63 | SUBCASE("pointers") { 64 | void const* cptr = nullptr; 65 | void* ptr = nullptr; 66 | 67 | CHECK(to_arg(cptr).tag == format_arg::type::t_voidptr); 68 | CHECK(to_arg(ptr).tag == format_arg::type::t_voidptr); 69 | CHECK(to_arg(nullptr).tag == format_arg::type::t_voidptr); 70 | } 71 | 72 | SUBCASE("C strings") { 73 | char chars[16] = {}; 74 | char const* cstr = nullptr; 75 | char* str = nullptr; 76 | 77 | CHECK(to_arg(chars).tag == format_arg::type::t_cstring); 78 | CHECK(to_arg("literal").tag == format_arg::type::t_cstring); 79 | CHECK(to_arg(cstr).tag == format_arg::type::t_cstring); 80 | CHECK(to_arg(str).tag == format_arg::type::t_cstring); 81 | } 82 | } 83 | 84 | namespace { 85 | // clang-format off 86 | enum NANOFMT_GSL_SUPPRESS(enum.3) cenum { cenum_value }; 87 | // clang-format on 88 | enum class enum_class { value }; 89 | enum class chonky_enum_class : long long { value }; 90 | } // namespace 91 | 92 | TEST_CASE("nanofmt.format_arg.enums") { 93 | using namespace NANOFMT_NS; 94 | 95 | CHECK(to_arg(cenum_value).tag == format_arg::type::t_int); 96 | CHECK(to_arg(enum_class::value).tag == format_arg::type::t_int); 97 | CHECK(to_arg(chonky_enum_class::value).tag == format_arg::type::t_longlong); 98 | } 99 | 100 | namespace custom { 101 | enum class enum_class { value }; 102 | struct struct_type {}; 103 | class class_type {}; 104 | } // namespace custom 105 | 106 | namespace NANOFMT_NS { 107 | struct custom_formatter_base { 108 | constexpr char const* parse(char const* in, char const*) noexcept { 109 | return in; 110 | } 111 | template 112 | void format(T const&, format_output&) {} 113 | }; 114 | 115 | template <> 116 | struct formatter : custom_formatter_base {}; 117 | template <> 118 | struct formatter : custom_formatter_base {}; 119 | template <> 120 | struct formatter : custom_formatter_base {}; 121 | } // namespace NANOFMT_NS 122 | 123 | TEST_CASE("nanofmt.format_arg.custom") { 124 | using namespace NANOFMT_NS; 125 | 126 | SUBCASE("enums") { 127 | CHECK(to_arg(custom::enum_class::value).tag == format_arg::type::t_custom); 128 | } 129 | 130 | SUBCASE("objects") { 131 | custom::struct_type st; 132 | const custom::struct_type cst; 133 | custom::class_type ct; 134 | const custom::class_type cct; 135 | 136 | CHECK(to_arg(st).tag == format_arg::type::t_custom); 137 | CHECK(to_arg(cst).tag == format_arg::type::t_custom); 138 | CHECK(to_arg(ct).tag == format_arg::type::t_custom); 139 | CHECK(to_arg(cct).tag == format_arg::type::t_custom); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/test_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Sean Middleditch and contributors. See accompanying LICENSE.md for copyright details. 2 | 3 | #pragma once 4 | 5 | #include "nanofmt/charconv.h" 6 | #include "nanofmt/format.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace NANOFMT_NS::test { 12 | template 13 | struct string_result { 14 | char buffer[N] = {}; 15 | size_t size = 0; 16 | 17 | friend bool operator==(string_result const& lhs, char const* zstr) noexcept { 18 | size_t const zlen = std::strlen(zstr); 19 | if (zlen != lhs.size) 20 | return false; 21 | return std::memcmp(lhs.buffer, zstr, lhs.size) == 0; 22 | } 23 | 24 | friend std::ostream& operator<<(std::ostream& os, string_result const& val) { 25 | return os.put('"').write(val.buffer, val.size).put('"'); 26 | } 27 | }; 28 | 29 | template 30 | auto sformat(format_string fmt, ArgsT&&... args) { 31 | string_result result; 32 | char const* const end = 33 | ::NANOFMT_NS::format_to_n(result.buffer, sizeof result.buffer, fmt, std::forward(args)...); 34 | result.size = end - result.buffer; 35 | return result; 36 | } 37 | 38 | template 39 | auto to_string(ValueT const& value, ArgsT&&... args) { 40 | string_result result; 41 | char const* const end = 42 | ::NANOFMT_NS::to_chars(result.buffer, result.buffer + sizeof result.buffer, value, args...); 43 | result.size = end - result.buffer; 44 | return result; 45 | } 46 | } // namespace NANOFMT_NS::test 47 | --------------------------------------------------------------------------------