diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 000000000..51b34029a --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +test/gtest-1.11.0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..dc2380610 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + github-actions: + patterns: + - "*" + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0565fe18a..3249c72e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,36 +5,137 @@ on: pull_request: branches: [ master ] workflow_dispatch: +permissions: read-all +defaults: + run: + shell: bash jobs: - build: + cmake-build: strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] + cxx_standard: [11, 17, 20] + build: [static, shared] + googletest: [build, system] + generator: ["Default Generator", "MinGW Makefiles"] + exclude: + - os: ubuntu-latest + cxx_standard: 11 + googletest: system + - os: macos-latest + build: shared + - os: macos-latest + generator: "MinGW Makefiles" + - os: ubuntu-latest + generator: "MinGW Makefiles" + - os: macos-latest + googletest: system + - os: windows-latest + googletest: system + env: + YAML_BUILD_SHARED_LIBS: ${{ matrix.build == 'shared' && 'ON' || 'OFF' }} + YAML_USE_SYSTEM_GTEST: ${{ matrix.googletest == 'system' && 'ON' || 'OFF' }} + CMAKE_GENERATOR: >- + ${{format(matrix.generator != 'Default Generator' && '-G "{0}"' || '', matrix.generator)}} + CMAKE_INSTALL_PREFIX: "${{ github.workspace }}/install-prefix" + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: ${{ matrix.googletest == 'build' && '-g -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC' || '-g' }} runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - name: Build static - shell: bash + - uses: awalsh128/cache-apt-pkgs-action@latest + if: matrix.os == 'ubuntu-latest' + with: + packages: googletest libgmock-dev libgtest-dev + version: 1.0 + + - uses: actions/checkout@v4 + + - name: Configure run: | - mkdir -p build && cd build - cmake .. - cmake --build . --parallel 4 + cmake \ + ${{ env.CMAKE_GENERATOR }} \ + -S "${{ github.workspace }}" \ + -B build \ + -D CMAKE_CXX_STANDARD=${{ matrix.cxx_standard }} \ + -D CMAKE_INSTALL_PREFIX="${{ env.CMAKE_INSTALL_PREFIX }}" \ + -D CMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} \ + -D CMAKE_CXX_FLAGS_DEBUG="${{ env.CMAKE_CXX_FLAGS_DEBUG }}" \ + -D YAML_BUILD_SHARED_LIBS=${{ env.YAML_BUILD_SHARED_LIBS }} \ + -D YAML_USE_SYSTEM_GTEST=${{ env.YAML_USE_SYSTEM_GTEST }} \ + -D YAML_CPP_BUILD_TESTS=ON - - name: Test static + - name: Build + run: | + cmake \ + --build build \ + --config ${{ env.CMAKE_BUILD_TYPE }} \ + --verbose \ + --parallel + + - name: Run Tests shell: bash - run: cd build && ctest --output-on-failure + run: | + ctest \ + --test-dir build \ + --build-config ${{ env.CMAKE_BUILD_TYPE }} \ + --output-on-failure \ + --verbose + + - name: Install + run: cmake --install build --config ${{ env.CMAKE_BUILD_TYPE }} + + - name: Configure CMake package test + run: | + cmake \ + ${{ env.CMAKE_GENERATOR }} \ + -S "${{ github.workspace }}/test/cmake" \ + -B consumer-build \ + -D CMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} \ + -D CMAKE_PREFIX_PATH="${{ env.CMAKE_INSTALL_PREFIX }}" + + - name: Build CMake package test + run: | + cmake \ + --build consumer-build \ + --config ${{ env.CMAKE_BUILD_TYPE }} \ + --verbose + + bazel-build: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Build + run: | + cd "${{ github.workspace }}" + bazel build :all + + - name: Test + run: | + cd "${{ github.workspace }}" + bazel test test - - name: Build shared + bzlmod-build: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Build shell: bash run: | - rm -rf build && mkdir -p build && cd build - cmake .. -DYAML_BUILD_SHARED_LIBS=ON - cmake --build . --parallel 4 + cd "${{ github.workspace }}" + bazel build --enable_bzlmod :all - # tests are failing for unknown reasons - - if: matrix.os == 'ubuntu-latest' - name: Test shared + - name: Test shell: bash - run: cd build && ctest --output-on-failure - # test all ASAP + run: | + cd "${{ github.workspace }}" + bazel test --enable_bzlmod test diff --git a/.github/workflows/bzlmod-archive.yml b/.github/workflows/bzlmod-archive.yml new file mode 100644 index 000000000..cdb008fe7 --- /dev/null +++ b/.github/workflows/bzlmod-archive.yml @@ -0,0 +1,19 @@ +name: Bazel Release + +on: + release: + types: [published] + +jobs: + # A release archive is required for bzlmod + # See: https://blog.bazel.build/2023/02/15/github-archive-checksum.html + bazel-release-archive: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - run: git archive $GITHUB_REF -o "yaml-cpp-${GITHUB_REF:10}.tar.gz" + - run: gh release upload ${GITHUB_REF:10} "yaml-cpp-${GITHUB_REF:10}.tar.gz" + env: + GH_TOKEN: ${{ github.token }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 46beb1d12..000000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: c++ - -matrix: - include: - - os: linux - compiler: gcc - - os: osx - compiler: clang - - os: osx - compiler: gcc -env: - - CTEST_OUTPUT_ON_FAILURE=1 - -before_script: - - mkdir build - - cd build - - cmake .. - - cd .. -script: - - cmake --build build - - cmake --build build --target test - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.9 - packages: - - g++-4.9 - - clang-3.9 - update: true - homebrew: - packages: - - ccache - - gcc@4.9 - - llvm@4 - update: true diff --git a/BUILD.bazel b/BUILD.bazel index b0b901606..23e847e77 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,3 +1,9 @@ +yaml_cpp_defines = select({ + # On Windows, ensure static linking is used. + "@platforms//os:windows": ["YAML_CPP_STATIC_DEFINE", "YAML_CPP_NO_CONTRIB"], + "//conditions:default": [], +}) + cc_library( name = "yaml-cpp_internal", visibility = ["//:__subpackages__"], @@ -11,4 +17,5 @@ cc_library( includes = ["include"], hdrs = glob(["include/**/*.h"]), srcs = glob(["src/**/*.cpp", "src/**/*.h"]), + defines = yaml_cpp_defines, ) diff --git a/CMakeLists.txt b/CMakeLists.txt index f65efefab..7e8a528d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ -# 3.5 is actually available almost everywhere, but this a good minimum -cmake_minimum_required(VERSION 3.4) +# 3.5 is actually available almost everywhere, but this a good minimum. +# 3.14 as the upper policy limit avoids CMake deprecation warnings. +cmake_minimum_required(VERSION 3.4...3.14) # enable MSVC_RUNTIME_LIBRARY target property # see https://cmake.org/cmake/help/latest/policy/CMP0091.html @@ -7,7 +8,12 @@ if(POLICY CMP0091) cmake_policy(SET CMP0091 NEW) endif() -project(YAML_CPP VERSION 0.7.0 LANGUAGES CXX) +project(YAML_CPP VERSION 0.8.0 LANGUAGES CXX) + +set(YAML_CPP_MAIN_PROJECT OFF) +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + set(YAML_CPP_MAIN_PROJECT ON) +endif() include(CMakePackageConfigHelpers) include(CMakeDependentOption) @@ -15,21 +21,27 @@ include(CheckCXXCompilerFlag) include(GNUInstallDirs) include(CTest) -find_program(YAML_CPP_CLANG_FORMAT_EXE NAMES clang-format) - option(YAML_CPP_BUILD_CONTRIB "Enable yaml-cpp contrib in library" ON) option(YAML_CPP_BUILD_TOOLS "Enable parse tools" ON) option(YAML_BUILD_SHARED_LIBS "Build yaml-cpp shared library" ${BUILD_SHARED_LIBS}) +option(YAML_CPP_INSTALL "Enable generation of yaml-cpp install targets" ${YAML_CPP_MAIN_PROJECT}) +option(YAML_CPP_FORMAT_SOURCE "Format source" ${YAML_CPP_MAIN_PROJECT}) +option(YAML_CPP_DISABLE_UNINSTALL "Disable uninstallation of yaml-cpp" OFF) +option(YAML_USE_SYSTEM_GTEST "Use system googletest if found" OFF) +option(YAML_ENABLE_PIC "Use Position-Independent Code " ON) cmake_dependent_option(YAML_CPP_BUILD_TESTS - "Enable yaml-cpp tests" ON - "BUILD_TESTING;CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) -cmake_dependent_option(YAML_CPP_INSTALL - "Enable generation of yaml-cpp install targets" ON - "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) + "Enable yaml-cpp tests" OFF + "BUILD_TESTING;YAML_CPP_MAIN_PROJECT" OFF) cmake_dependent_option(YAML_MSVC_SHARED_RT "MSVC: Build yaml-cpp with shared runtime libs (/MD)" ON - "MSVC" OFF) + "CMAKE_SYSTEM_NAME MATCHES Windows" OFF) +set(YAML_CPP_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/yaml-cpp" + CACHE STRING "Path to install the CMake package to") + +if (YAML_CPP_FORMAT_SOURCE) + find_program(YAML_CPP_CLANG_FORMAT_EXE NAMES clang-format) +endif() if (YAML_BUILD_SHARED_LIBS) set(yaml-cpp-type SHARED) @@ -37,7 +49,6 @@ if (YAML_BUILD_SHARED_LIBS) else() set(yaml-cpp-type STATIC) set(yaml-cpp-label-postfix "static") - add_definitions(-DYAML_CPP_STATIC_DEFINE) endif() set(build-shared $) @@ -80,6 +91,10 @@ set_property(TARGET yaml-cpp PROPERTY CXX_STANDARD_REQUIRED ON) +if (NOT YAML_BUILD_SHARED_LIBS) + set_property(TARGET yaml-cpp PROPERTY POSITION_INDEPENDENT_CODE ${YAML_ENABLE_PIC}) +endif() + target_include_directories(yaml-cpp PUBLIC $ @@ -93,11 +108,15 @@ if (NOT DEFINED CMAKE_CXX_STANDARD) CXX_STANDARD 11) endif() +if(YAML_CPP_MAIN_PROJECT) + target_compile_options(yaml-cpp + PRIVATE + $<${not-msvc}:-Wall -Wextra -Wshadow -Weffc++ -Wno-long-long> + $<${not-msvc}:-pedantic -pedantic-errors>) +endif() + target_compile_options(yaml-cpp PRIVATE - $<${not-msvc}:-Wall -Wextra -Wshadow -Weffc++ -Wno-long-long> - $<${not-msvc}:-pedantic -pedantic-errors> - $<$:-MTd> $<$:-MT> $<$:-MDd> @@ -110,6 +129,8 @@ target_compile_options(yaml-cpp $<$:/W3 /wd4127 /wd4355>) target_compile_definitions(yaml-cpp + PUBLIC + $<$>:YAML_CPP_STATIC_DEFINE> PRIVATE $<${build-windows-dll}:${PROJECT_NAME}_DLL> $<$>:YAML_CPP_NO_CONTRIB>) @@ -129,10 +150,13 @@ set_target_properties(yaml-cpp PROPERTIES PROJECT_LABEL "yaml-cpp ${yaml-cpp-label-postfix}" DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}") +set(EXPORT_TARGETS yaml-cpp::yaml-cpp) configure_package_config_file( "${PROJECT_SOURCE_DIR}/yaml-cpp-config.cmake.in" "${PROJECT_BINARY_DIR}/yaml-cpp-config.cmake" - INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/yaml-cpp") + INSTALL_DESTINATION "${YAML_CPP_INSTALL_CMAKEDIR}" + PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR) +unset(EXPORT_TARGETS) write_basic_package_version_file( "${PROJECT_BINARY_DIR}/yaml-cpp-config-version.cmake" @@ -141,36 +165,49 @@ write_basic_package_version_file( configure_file(yaml-cpp.pc.in yaml-cpp.pc @ONLY) if (YAML_CPP_INSTALL) - install(TARGETS yaml-cpp + install(TARGETS yaml-cpp EXPORT yaml-cpp-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - FILES_MATCHING PATTERN "*.h") + FILES_MATCHING PATTERN "*.h") install(EXPORT yaml-cpp-targets - DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/yaml-cpp") - install(FILES - "${PROJECT_BINARY_DIR}/yaml-cpp-config.cmake" - "${PROJECT_BINARY_DIR}/yaml-cpp-config-version.cmake" - DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/yaml-cpp") + NAMESPACE yaml-cpp:: + DESTINATION "${YAML_CPP_INSTALL_CMAKEDIR}") + install(FILES + "${PROJECT_BINARY_DIR}/yaml-cpp-config.cmake" + "${PROJECT_BINARY_DIR}/yaml-cpp-config-version.cmake" + DESTINATION "${YAML_CPP_INSTALL_CMAKEDIR}") install(FILES "${PROJECT_BINARY_DIR}/yaml-cpp.pc" - DESTINATION ${CMAKE_INSTALL_DATADIR}/pkgconfig) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) endif() if(YAML_CPP_BUILD_TESTS) - add_subdirectory(test) + add_subdirectory(test) endif() if(YAML_CPP_BUILD_TOOLS) - add_subdirectory(util) + add_subdirectory(util) endif() -if (YAML_CPP_CLANG_FORMAT_EXE) +if (YAML_CPP_FORMAT_SOURCE AND YAML_CPP_CLANG_FORMAT_EXE) add_custom_target(format COMMAND clang-format --style=file -i $ COMMAND_EXPAND_LISTS COMMENT "Running clang-format" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM) endif() + +# uninstall target +if(YAML_CPP_INSTALL AND NOT YAML_CPP_DISABLE_UNINSTALL AND NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd09a1aca..e4ca076dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,9 +17,9 @@ Commit messages should be in the imperative mood, as described in the [Git contr # Tests -Please verify the tests pass by running the target `tests/run_tests`. +Please verify the tests pass by configuring CMake with `-D YAML_CPP_BUILD_TESTS=ON` and running the target `test/yaml-cpp-tests`. -If you are adding functionality, add tests accordingly. +If you are adding functionality, add tests accordingly. Note that the "spec tests" are reserved for examples directly from the YAML spec, so if you have new examples, put them in other test files. # Pull request process diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000..7119060bf --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,14 @@ +""" +yaml-cpp is a YAML parser and emitter in c++ matching the YAML specification. +""" + +module( + name = "yaml-cpp", + compatibility_level = 1, + version = "0.8.0", +) + +bazel_dep(name = "platforms", version = "0.0.7") +bazel_dep(name = "rules_cc", version = "0.0.8") + +bazel_dep(name = "googletest", version = "1.14.0", dev_dependency = True) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 000000000..28100e7e0 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,114 @@ +{ + "lockFileVersion": 11, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/source.json": "06cc0842d241da0c5edc755edb3c7d0d008d304330e57ecf2d6449fb0b633a82", + "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", + "https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/source.json": "082ed5f9837901fada8c68c2f3ddc958bb22b6d654f71dd73f3df30d45d4b749", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.14.0/source.json": "2478949479000fdd7de9a3d0107ba2c85bb5f961c3ecb1aa448f52549ce310b5", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1", + "https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "https://bcr.bazel.build/modules/rules_python/0.22.1/source.json": "57226905e783bae7c37c2dd662be078728e48fa28ee4324a7eabcafb5a43d014", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=", + "usagesDigest": "+hz7IHWN6A1oVJJWNDB6yZRG+RYhF76wAYItpAeIUIg=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc_toolchains": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf_toolchains", + "attributes": {} + }, + "local_config_apple_cc": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@platforms//host:extension.bzl%host_platform": { + "general": { + "bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=", + "usagesDigest": "pCYpDQmqMbmiiPI1p2Kd3VLm5T48rRAht5WdW0X2GlA=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "host_platform": { + "bzlFile": "@@platforms//host:extension.bzl", + "ruleClassName": "host_platform_repo", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + } + } +} diff --git a/README.md b/README.md index 70c231445..ec72636d1 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,17 @@ cmake [-G generator] [-DYAML_BUILD_SHARED_LIBS=on|OFF] .. * `yaml-cpp` builds a static library by default, you may want to build a shared library by specifying `-DYAML_BUILD_SHARED_LIBS=ON`. + * [Debug mode of the GNU standard C++ + library](https://gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html) + can be used when both `yaml-cpp` and client code is compiled with the + `_GLIBCXX_DEBUG` flag (e.g. by calling CMake with `-D + CMAKE_CXX_FLAGS_DEBUG='-g -D_GLIBCXX_DEBUG'` option). + + Note that for `yaml-cpp` unit tests to run successfully, the _GoogleTest_ + library also must be built with this flag, i.e. the system one cannot be + used (the _YAML_USE_SYSTEM_GTEST_ CMake option must be _OFF_, which is the + default). + * For more options on customizing the build, see the [CMakeLists.txt](https://github.com/jbeder/yaml-cpp/blob/master/CMakeLists.txt) file. #### 2. Build it! @@ -38,13 +49,30 @@ cmake [-G generator] [-DYAML_BUILD_SHARED_LIBS=on|OFF] .. **Note:** To clean up, just remove the `build` directory. +## How to Integrate it within your project using CMake + +You can use for example FetchContent : + +```cmake +include(FetchContent) + +FetchContent_Declare( + yaml-cpp + GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git + GIT_TAG # Can be a tag (yaml-cpp-x.x.x), a commit hash, or a branch name (master) +) +FetchContent_MakeAvailable(yaml-cpp) + +target_link_libraries(YOUR_LIBRARY PUBLIC yaml-cpp::yaml-cpp) # The library or executable that require yaml-cpp library +``` + ## Recent Releases -[yaml-cpp 0.6.0](https://github.com/jbeder/yaml-cpp/releases/tag/yaml-cpp-0.6.0) released! This release requires C++11, and no longer depends on Boost. +[yaml-cpp 0.8.0](https://github.com/jbeder/yaml-cpp/releases/tag/0.8.0) released! [yaml-cpp 0.3.0](https://github.com/jbeder/yaml-cpp/releases/tag/release-0.3.0) is still available if you want the old API. -**The old API will continue to be supported, and will still receive bugfixes!** The 0.3.x and 0.4.x versions will be old API releases, and 0.5.x and above will all be new API releases. +**The old API will stop receiving bugfixes in 2026.** The 0.3.x versions provide the old API, and 0.5.x and above all provide the new API. # API Documentation diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..06a17511b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/jbeder/yaml-cpp/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, vulnerabilities will be disclosed in a best effort base. diff --git a/WORKSPACE b/WORKSPACE deleted file mode 100644 index d5ecc0b54..000000000 --- a/WORKSPACE +++ /dev/null @@ -1,10 +0,0 @@ -workspace(name = "com_github_jbeder_yaml_cpp") - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -http_archive( - name = "com_google_googletest", - strip_prefix = "googletest-release-1.8.1", - url = "https://github.com/google/googletest/archive/release-1.8.1.tar.gz", - sha256 = "9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c", -) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 54bcc80c5..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: 1.0.{build} - -environment: - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - CMAKE_GENERATOR: Visual Studio 14 2015 - CMAKE_PLATFORM: win32 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - CMAKE_GENERATOR: Visual Studio 14 2015 - CMAKE_PLATFORM: x64 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - CMAKE_GENERATOR: Visual Studio 15 2017 - CMAKE_PLATFORM: win32 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - CMAKE_GENERATOR: Visual Studio 15 2017 - CMAKE_PLATFORM: x64 - -before_build: - - cmd: mkdir build - - cmd: cd build - - cmd: cmake .. -G "%CMAKE_GENERATOR%" -DCMAKE_GENERATOR_PLATFORM=%CMAKE_PLATFORM% - - cmd: cd .. - -build_script: - - cmake --build build -test_script: - - cmd: cd build - - ctest diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 000000000..c2d34d479 --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() diff --git a/docs/How-To-Emit-YAML.md b/docs/How-To-Emit-YAML.md index 934070199..6e6f9f58e 100644 --- a/docs/How-To-Emit-YAML.md +++ b/docs/How-To-Emit-YAML.md @@ -154,16 +154,11 @@ produces # STL Containers, and Other Overloads # We overload `operator <<` for `std::vector`, `std::list`, and `std::map`, so you can write stuff like: +{% raw %} ```cpp -std::vector squares; -squares.push_back(1); -squares.push_back(4); -squares.push_back(9); -squares.push_back(16); +std::vector squares = {1, 4, 9, 16}; -std::map ages; -ages["Daniel"] = 26; -ages["Jesse"] = 24; +std::map ages = {{"Daniel", 26}, {"Jesse", 24}}; YAML::Emitter out; out << YAML::BeginSeq; @@ -171,6 +166,7 @@ out << YAML::Flow << squares; out << ages; out << YAML::EndSeq; ``` +{% endraw %} produces @@ -227,4 +223,4 @@ assert(out.good()); out << YAML::Key; assert(!out.good()); std::cout << "Emitter error: " << out.GetLastError() << "\n"; -``` \ No newline at end of file +``` diff --git a/include/yaml-cpp/dll.h b/include/yaml-cpp/dll.h index eabdda1d9..4e55ab8c2 100644 --- a/include/yaml-cpp/dll.h +++ b/include/yaml-cpp/dll.h @@ -15,11 +15,9 @@ # ifndef YAML_CPP_API # ifdef yaml_cpp_EXPORTS /* We are building this library */ -# pragma message( "Defining YAML_CPP_API for DLL export" ) # define YAML_CPP_API __declspec(dllexport) # else /* We are using this library */ -# pragma message( "Defining YAML_CPP_API for DLL import" ) # define YAML_CPP_API __declspec(dllimport) # endif # endif diff --git a/include/yaml-cpp/emitfromevents.h b/include/yaml-cpp/emitfromevents.h index 1f389c5a1..c00d26e74 100644 --- a/include/yaml-cpp/emitfromevents.h +++ b/include/yaml-cpp/emitfromevents.h @@ -23,6 +23,7 @@ class Emitter; class EmitFromEvents : public EventHandler { public: EmitFromEvents(Emitter& emitter); + ~EmitFromEvents() override = default; void OnDocumentStart(const Mark& mark) override; void OnDocumentEnd() override; diff --git a/include/yaml-cpp/emitter.h b/include/yaml-cpp/emitter.h index 210b1ec97..70413e2f4 100644 --- a/include/yaml-cpp/emitter.h +++ b/include/yaml-cpp/emitter.h @@ -9,18 +9,24 @@ #include #include +#include #include #include #include #include #include +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +#include +#endif + #include "yaml-cpp/binary.h" #include "yaml-cpp/dll.h" #include "yaml-cpp/emitterdef.h" #include "yaml-cpp/emittermanip.h" #include "yaml-cpp/null.h" #include "yaml-cpp/ostream_wrapper.h" +#include "yaml-cpp/fptostring.h" namespace YAML { class Binary; @@ -67,6 +73,7 @@ class YAML_CPP_API Emitter { Emitter& SetLocalPrecision(const _Precision& precision); // overloads of write + Emitter& Write(const char* str, std::size_t size); Emitter& Write(const std::string& str); Emitter& Write(bool b); Emitter& Write(char ch); @@ -141,6 +148,7 @@ inline Emitter& Emitter::WriteIntegralType(T value) { PrepareNode(EmitterNodeType::Scalar); std::stringstream stream; + stream.imbue(std::locale("C")); PrepareIntegralStream(stream); stream << value; m_stream << stream.str(); @@ -158,6 +166,7 @@ inline Emitter& Emitter::WriteStreamable(T value) { PrepareNode(EmitterNodeType::Scalar); std::stringstream stream; + stream.imbue(std::locale("C")); SetStreamablePrecision(stream); bool special = false; @@ -178,7 +187,7 @@ inline Emitter& Emitter::WriteStreamable(T value) { } if (!special) { - stream << value; + stream << FpToString(value, stream.precision()); } m_stream << stream.str(); @@ -198,8 +207,13 @@ inline void Emitter::SetStreamablePrecision(std::stringstream& stream) { } // overloads of insertion +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +inline Emitter& operator<<(Emitter& emitter, const std::string_view& v) { + return emitter.Write(v.data(), v.size()); +} +#endif inline Emitter& operator<<(Emitter& emitter, const std::string& v) { - return emitter.Write(v); + return emitter.Write(v.data(), v.size()); } inline Emitter& operator<<(Emitter& emitter, bool v) { return emitter.Write(v); @@ -230,7 +244,7 @@ inline Emitter& operator<<(Emitter& emitter, const Binary& b) { } inline Emitter& operator<<(Emitter& emitter, const char* v) { - return emitter.Write(std::string(v)); + return emitter.Write(v, std::strlen(v)); } inline Emitter& operator<<(Emitter& emitter, int v) { diff --git a/include/yaml-cpp/emitterstyle.h b/include/yaml-cpp/emitterstyle.h index 67bb3981b..5a6355fa2 100644 --- a/include/yaml-cpp/emitterstyle.h +++ b/include/yaml-cpp/emitterstyle.h @@ -8,9 +8,10 @@ #endif namespace YAML { -struct EmitterStyle { - enum value { Default, Block, Flow }; -}; +namespace EmitterStyle { +enum value { Default, Block, Flow }; +} + } #endif // EMITTERSTYLE_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/include/yaml-cpp/exceptions.h b/include/yaml-cpp/exceptions.h index f6b2602ae..99e06b24e 100644 --- a/include/yaml-cpp/exceptions.h +++ b/include/yaml-cpp/exceptions.h @@ -48,6 +48,8 @@ const char* const UNKNOWN_TOKEN = "unknown token"; const char* const DOC_IN_SCALAR = "illegal document indicator in scalar"; const char* const EOF_IN_SCALAR = "illegal EOF in scalar"; const char* const CHAR_IN_SCALAR = "illegal character in scalar"; +const char* const UNEXPECTED_SCALAR = "unexpected scalar"; +const char* const UNEXPECTED_FLOW = "plain value cannot start with flow indicator character"; const char* const TAB_IN_INDENTATION = "illegal tab when looking for indentation"; const char* const FLOW_END = "illegal flow end"; diff --git a/include/yaml-cpp/fptostring.h b/include/yaml-cpp/fptostring.h new file mode 100644 index 000000000..051053be9 --- /dev/null +++ b/include/yaml-cpp/fptostring.h @@ -0,0 +1,15 @@ +#ifndef YAML_H_FPTOSTRING +#define YAML_H_FPTOSTRING + +#include "yaml-cpp/dll.h" + +#include + +namespace YAML { +// "precision = 0" refers to shortest known unique representation of the value +YAML_CPP_API std::string FpToString(float v, size_t precision = 0); +YAML_CPP_API std::string FpToString(double v, size_t precision = 0); +YAML_CPP_API std::string FpToString(long double v, size_t precision = 0); +} + +#endif diff --git a/include/yaml-cpp/node/convert.h b/include/yaml-cpp/node/convert.h index 596898da6..9ccbf5384 100644 --- a/include/yaml-cpp/node/convert.h +++ b/include/yaml-cpp/node/convert.h @@ -12,16 +12,23 @@ #include #include #include +#include #include #include +#include #include +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +#include +#endif + #include "yaml-cpp/binary.h" #include "yaml-cpp/node/impl.h" #include "yaml-cpp/node/iterator.h" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/type.h" #include "yaml-cpp/null.h" +#include "yaml-cpp/fptostring.h" namespace YAML { @@ -87,6 +94,20 @@ struct convert { static Node encode(const char* rhs) { return Node(rhs); } }; +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +template <> +struct convert { + static Node encode(std::string_view rhs) { return Node(std::string(rhs)); } + + static bool decode(const Node& node, std::string_view& rhs) { + if (!node.IsScalar()) + return false; + rhs = node.Scalar(); + return true; + } +}; +#endif + template <> struct convert<_Null> { static Node encode(const _Null& /* rhs */) { return Node(); } @@ -109,7 +130,7 @@ inner_encode(const T& rhs, std::stringstream& stream){ stream << ".inf"; } } else { - stream << rhs; + stream << FpToString(rhs, stream.precision()); } } @@ -127,7 +148,7 @@ ConvertStreamTo(std::stringstream& stream, T& rhs) { if ((stream >> std::noskipws >> num) && (stream >> std::ws).eof()) { if (num >= (std::numeric_limits::min)() && num <= (std::numeric_limits::max)()) { - rhs = (T)num; + rhs = static_cast(num); return true; } } @@ -151,6 +172,7 @@ ConvertStreamTo(std::stringstream& stream, T& rhs) { \ static Node encode(const type& rhs) { \ std::stringstream stream; \ + stream.imbue(std::locale("C")); \ stream.precision(std::numeric_limits::max_digits10); \ conversion::inner_encode(rhs, stream); \ return Node(stream.str()); \ @@ -162,6 +184,7 @@ ConvertStreamTo(std::stringstream& stream, T& rhs) { } \ const std::string& input = node.Scalar(); \ std::stringstream stream(input); \ + stream.imbue(std::locale("C")); \ stream.unsetf(std::ios::dec); \ if ((stream.peek() == '-') && std::is_unsigned::value) { \ return false; \ @@ -251,6 +274,32 @@ struct convert> { } }; +// std::unordered_map +template +struct convert> { + static Node encode(const std::unordered_map& rhs) { + Node node(NodeType::Map); + for (const auto& element : rhs) + node.force_insert(element.first, element.second); + return node; + } + + static bool decode(const Node& node, std::unordered_map& rhs) { + if (!node.IsMap()) + return false; + + rhs.clear(); + for (const auto& element : node) +#if defined(__GNUC__) && __GNUC__ < 4 + // workaround for GCC 3: + rhs[element.first.template as()] = element.second.template as(); +#else + rhs[element.first.as()] = element.second.as(); +#endif + return true; + } +}; + // std::vector template struct convert> { @@ -336,6 +385,37 @@ struct convert> { } }; + +// std::valarray +template +struct convert> { + static Node encode(const std::valarray& rhs) { + Node node(NodeType::Sequence); + for (const auto& element : rhs) { + node.push_back(element); + } + return node; + } + + static bool decode(const Node& node, std::valarray& rhs) { + if (!node.IsSequence()) { + return false; + } + + rhs.resize(node.size()); + for (auto i = 0u; i < node.size(); ++i) { +#if defined(__GNUC__) && __GNUC__ < 4 + // workaround for GCC 3: + rhs[i] = node[i].template as(); +#else + rhs[i] = node[i].as(); +#endif + } + return true; + } +}; + + // std::pair template struct convert> { diff --git a/include/yaml-cpp/node/detail/iterator.h b/include/yaml-cpp/node/detail/iterator.h index 997c69a14..04d2da4fc 100644 --- a/include/yaml-cpp/node/detail/iterator.h +++ b/include/yaml-cpp/node/detail/iterator.h @@ -41,7 +41,7 @@ class iterator_base { using value_type = V; using difference_type = std::ptrdiff_t; using pointer = V*; - using reference = V; + using reference = V&; public: iterator_base() : m_iterator(), m_pMemory() {} diff --git a/include/yaml-cpp/node/detail/node_iterator.h b/include/yaml-cpp/node/detail/node_iterator.h index 49dcf958d..6eb4ddc13 100644 --- a/include/yaml-cpp/node/detail/node_iterator.h +++ b/include/yaml-cpp/node/detail/node_iterator.h @@ -69,7 +69,7 @@ class node_iterator_base { using value_type = node_iterator_value; using difference_type = std::ptrdiff_t; using pointer = node_iterator_value*; - using reference = node_iterator_value; + using reference = node_iterator_value&; using SeqIter = typename node_iterator_type::seq; using MapIter = typename node_iterator_type::map; diff --git a/include/yaml-cpp/node/impl.h b/include/yaml-cpp/node/impl.h index 312281f18..f5d622b25 100644 --- a/include/yaml-cpp/node/impl.h +++ b/include/yaml-cpp/node/impl.h @@ -97,7 +97,7 @@ struct as_if { if (!node.m_pNode) return fallback; - T t; + T t = fallback; if (convert::decode(node, t)) return t; return fallback; @@ -124,8 +124,8 @@ struct as_if { const Node& node; T operator()() const { - if (!node.m_pNode) - throw TypedBadConversion(node.Mark()); + if (!node.m_pNode) // no fallback + throw InvalidNode(node.m_invalidKey); T t; if (convert::decode(node, t)) @@ -140,6 +140,8 @@ struct as_if { const Node& node; std::string operator()() const { + if (node.Type() == NodeType::Undefined) // no fallback + throw InvalidNode(node.m_invalidKey); if (node.Type() == NodeType::Null) return "null"; if (node.Type() != NodeType::Scalar) diff --git a/include/yaml-cpp/node/ptr.h b/include/yaml-cpp/node/ptr.h index 062d77ea0..f55d95ed9 100644 --- a/include/yaml-cpp/node/ptr.h +++ b/include/yaml-cpp/node/ptr.h @@ -7,7 +7,6 @@ #pragma once #endif -#include "yaml-cpp/dll.h" #include namespace YAML { diff --git a/include/yaml-cpp/node/type.h b/include/yaml-cpp/node/type.h index 9d55ca966..b1237670c 100644 --- a/include/yaml-cpp/node/type.h +++ b/include/yaml-cpp/node/type.h @@ -8,9 +8,10 @@ #endif namespace YAML { -struct NodeType { - enum value { Undefined, Null, Scalar, Sequence, Map }; -}; +namespace NodeType { +enum value { Undefined, Null, Scalar, Sequence, Map }; +} + } #endif // VALUE_TYPE_H_62B23520_7C8E_11DE_8A39_0800200C9A66 diff --git a/include/yaml-cpp/null.h b/include/yaml-cpp/null.h index b9521d488..472e961ea 100644 --- a/include/yaml-cpp/null.h +++ b/include/yaml-cpp/null.h @@ -8,7 +8,7 @@ #endif #include "yaml-cpp/dll.h" -#include +#include namespace YAML { class Node; @@ -18,7 +18,7 @@ inline bool operator==(const _Null&, const _Null&) { return true; } inline bool operator!=(const _Null&, const _Null&) { return false; } YAML_CPP_API bool IsNull(const Node& node); // old API only -YAML_CPP_API bool IsNullString(const std::string& str); +YAML_CPP_API bool IsNullString(const char* str, std::size_t size); extern YAML_CPP_API _Null Null; } diff --git a/include/yaml-cpp/traits.h b/include/yaml-cpp/traits.h index 9a62db257..7c4cdd900 100644 --- a/include/yaml-cpp/traits.h +++ b/include/yaml-cpp/traits.h @@ -107,9 +107,9 @@ struct disable_if : public disable_if_c {}; template struct is_streamable { - template + template static auto test(int) - -> decltype(std::declval() << std::declval(), std::true_type()); + -> decltype(std::declval() << std::declval(), std::true_type()); template static auto test(...) -> std::false_type; @@ -121,6 +121,7 @@ template struct streamable_to_string { static std::string impl(const Key& key) { std::stringstream ss; + ss.imbue(std::locale("C")); ss << key; return ss.str(); } diff --git a/src/binary.cpp b/src/binary.cpp index 5949dd356..d27762a24 100644 --- a/src/binary.cpp +++ b/src/binary.cpp @@ -75,7 +75,7 @@ std::vector DecodeBase64(const std::string &input) { unsigned value = 0; for (std::size_t i = 0, cnt = 0; i < input.size(); i++) { - if (std::isspace(input[i])) { + if (std::isspace(static_cast(input[i]))) { // skip newlines continue; } diff --git a/src/contrib/dragonbox.h b/src/contrib/dragonbox.h new file mode 100644 index 000000000..499a1576e --- /dev/null +++ b/src/contrib/dragonbox.h @@ -0,0 +1,4196 @@ +// SPDX-FileCopyrightText: 2020-2024 Junekey Jeon +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-License-Identifier: BSL-1.0 +// SPDX-License-Identifier: MIT + +// This file is an adjusted copy of https://github.com/jk-jeon/dragonbox +// Junekey Jeon agreed to license this file under MIT license explicitly for +// usage in yaml-cpp. +// +// Changes to the original dragonbox library: +// - Added additional license information and information at top +// - Wrapped the 'jkj' namespace in an additional 'YAML' namespace + +#ifndef JKJ_HEADER_DRAGONBOX +#define JKJ_HEADER_DRAGONBOX + +// Attribute for storing static data into a dedicated place, e.g. flash memory. Every ODR-used +// static data declaration will be decorated with this macro. The users may define this macro, +// before including the library headers, into whatever they want. +#ifndef JKJ_STATIC_DATA_SECTION + #define JKJ_STATIC_DATA_SECTION +#else + #define JKJ_STATIC_DATA_SECTION_DEFINED 1 +#endif + +// To use the library with toolchains without standard C++ headers, the users may define this macro +// into their custom namespace which contains the defintions of all the standard C++ library +// features used in this header. (The list can be found below.) +#ifndef JKJ_STD_REPLACEMENT_NAMESPACE + #define JKJ_STD_REPLACEMENT_NAMESPACE std + #include + #include + #include + #include + #include + + #ifdef __has_include + #if __has_include() + #include + #endif + #endif +#else + #define JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED 1 +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// Language feature detections. +//////////////////////////////////////////////////////////////////////////////////////// + +// C++14 constexpr +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304L + #define JKJ_HAS_CONSTEXPR14 1 +#elif __cplusplus >= 201402L + #define JKJ_HAS_CONSTEXPR14 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1910 && _MSVC_LANG >= 201402L + #define JKJ_HAS_CONSTEXPR14 1 +#else + #define JKJ_HAS_CONSTEXPR14 0 +#endif + +#if JKJ_HAS_CONSTEXPR14 + #define JKJ_CONSTEXPR14 constexpr +#else + #define JKJ_CONSTEXPR14 +#endif + +// C++17 constexpr lambdas +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201603L + #define JKJ_HAS_CONSTEXPR17 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_CONSTEXPR17 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L + #define JKJ_HAS_CONSTEXPR17 1 +#else + #define JKJ_HAS_CONSTEXPR17 0 +#endif + +// C++17 inline variables +#if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L + #define JKJ_HAS_INLINE_VARIABLE 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_INLINE_VARIABLE 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1912 && _MSVC_LANG >= 201703L + #define JKJ_HAS_INLINE_VARIABLE 1 +#else + #define JKJ_HAS_INLINE_VARIABLE 0 +#endif + +#if JKJ_HAS_INLINE_VARIABLE + #define JKJ_INLINE_VARIABLE inline constexpr +#else + #define JKJ_INLINE_VARIABLE static constexpr +#endif + +// C++17 if constexpr +#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L + #define JKJ_HAS_IF_CONSTEXPR 1 +#elif __cplusplus >= 201703L + #define JKJ_HAS_IF_CONSTEXPR 1 +#elif defined(_MSC_VER) && _MSC_VER >= 1911 && _MSVC_LANG >= 201703L + #define JKJ_HAS_IF_CONSTEXPR 1 +#else + #define JKJ_HAS_IF_CONSTEXPR 0 +#endif + +#if JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEXPR if constexpr +#else + #define JKJ_IF_CONSTEXPR if +#endif + +// C++20 std::bit_cast +#if JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED + #if JKJ_STD_REPLACEMENT_HAS_BIT_CAST + #define JKJ_HAS_BIT_CAST 1 + #else + #define JKJ_HAS_BIT_CAST 0 + #endif +#elif defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L + #include + #define JKJ_HAS_BIT_CAST 1 +#else + #define JKJ_HAS_BIT_CAST 0 +#endif + +// C++23 if consteval or C++20 std::is_constant_evaluated +#if defined(__cpp_if_consteval) && __cpp_is_consteval >= 202106L + #define JKJ_IF_CONSTEVAL if consteval + #define JKJ_IF_NOT_CONSTEVAL if !consteval + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 +#elif JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED + #if JKJ_STD_REPLACEMENT_HAS_IS_CONSTANT_EVALUATED + #define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated()) + #define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated()) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 1 + #elif JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEVAL if constexpr (false) + #define JKJ_IF_NOT_CONSTEVAL if constexpr (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #else + #define JKJ_IF_CONSTEVAL if (false) + #define JKJ_IF_NOT_CONSTEVAL if (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #endif +#else + #if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L + #define JKJ_IF_CONSTEVAL if (stdr::is_constant_evaluated()) + #define JKJ_IF_NOT_CONSTEVAL if (!stdr::is_constant_evaluated()) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 1 + #define JKJ_USE_IS_CONSTANT_EVALUATED 1 + #elif JKJ_HAS_IF_CONSTEXPR + #define JKJ_IF_CONSTEVAL if constexpr (false) + #define JKJ_IF_NOT_CONSTEVAL if constexpr (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #else + #define JKJ_IF_CONSTEVAL if (false) + #define JKJ_IF_NOT_CONSTEVAL if (true) + #define JKJ_CAN_BRANCH_ON_CONSTEVAL 0 + #define JKJ_USE_IS_CONSTANT_EVALUATED 0 + #endif +#endif + +#if JKJ_CAN_BRANCH_ON_CONSTEVAL && JKJ_HAS_BIT_CAST + #define JKJ_CONSTEXPR20 constexpr +#else + #define JKJ_CONSTEXPR20 +#endif + +// Suppress additional buffer overrun check. +// I have no idea why MSVC thinks some functions here are vulnerable to the buffer overrun +// attacks. No, they aren't. +#if defined(__GNUC__) || defined(__clang__) + #define JKJ_SAFEBUFFERS + #define JKJ_FORCEINLINE inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define JKJ_SAFEBUFFERS __declspec(safebuffers) + #define JKJ_FORCEINLINE __forceinline +#else + #define JKJ_SAFEBUFFERS + #define JKJ_FORCEINLINE inline +#endif + +#if defined(__has_builtin) + #define JKJ_HAS_BUILTIN(x) __has_builtin(x) +#else + #define JKJ_HAS_BUILTIN(x) false +#endif + +#if defined(_MSC_VER) + #include +#elif defined(__INTEL_COMPILER) + #include +#endif + +namespace YAML { +namespace jkj { + namespace dragonbox { + //////////////////////////////////////////////////////////////////////////////////////// + // The Compatibility layer for toolchains without standard C++ headers. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { + namespace stdr { + // +#if JKJ_HAS_BIT_CAST + using JKJ_STD_REPLACEMENT_NAMESPACE::bit_cast; +#endif + + // + // We need assert() macro, but it is not namespaced anyway, so nothing to do here. + + // + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_least32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::int_fast32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least32_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_least64_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast8_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast16_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::uint_fast32_t; + // We need INT32_C, UINT32_C and UINT64_C macros too, but again there is nothing to do + // here. + + // + using JKJ_STD_REPLACEMENT_NAMESPACE::size_t; + using JKJ_STD_REPLACEMENT_NAMESPACE::memcpy; + + // + template + using numeric_limits = JKJ_STD_REPLACEMENT_NAMESPACE::numeric_limits; + + // + template + using enable_if = JKJ_STD_REPLACEMENT_NAMESPACE::enable_if; + template + using add_rvalue_reference = JKJ_STD_REPLACEMENT_NAMESPACE::add_rvalue_reference; + template + using conditional = JKJ_STD_REPLACEMENT_NAMESPACE::conditional; +#if JKJ_USE_IS_CONSTANT_EVALUATED + using JKJ_STD_REPLACEMENT_NAMESPACE::is_constant_evaluated; +#endif + template + using is_same = JKJ_STD_REPLACEMENT_NAMESPACE::is_same; +#if !JKJ_HAS_BIT_CAST + template + using is_trivially_copyable = JKJ_STD_REPLACEMENT_NAMESPACE::is_trivially_copyable; +#endif + template + using is_integral = JKJ_STD_REPLACEMENT_NAMESPACE::is_integral; + template + using is_signed = JKJ_STD_REPLACEMENT_NAMESPACE::is_signed; + template + using is_unsigned = JKJ_STD_REPLACEMENT_NAMESPACE::is_unsigned; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////// + // Some general utilities for C++11-compatibility. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { +#if !JKJ_HAS_CONSTEXPR17 + template + struct index_sequence {}; + + template + struct make_index_sequence_impl { + using type = typename make_index_sequence_impl::type; + }; + + template + struct make_index_sequence_impl { + using type = index_sequence; + }; + + template + using make_index_sequence = typename make_index_sequence_impl<0, N, void>::type; +#endif + + // Available since C++11, but including just for this is an overkill. + template + typename stdr::add_rvalue_reference::type declval() noexcept; + + // Similarly, including is an overkill. + template + struct array { + T data_[N]; + constexpr T operator[](stdr::size_t idx) const noexcept { return data_[idx]; } + JKJ_CONSTEXPR14 T& operator[](stdr::size_t idx) noexcept { return data_[idx]; } + }; + } + + + //////////////////////////////////////////////////////////////////////////////////////// + // Some basic features for encoding/decoding IEEE-754 formats. + //////////////////////////////////////////////////////////////////////////////////////// + namespace detail { + template + struct physical_bits { + static constexpr stdr::size_t value = + sizeof(T) * stdr::numeric_limits::digits; + }; + template + struct value_bits { + static constexpr stdr::size_t value = stdr::numeric_limits< + typename stdr::enable_if::value, T>::type>::digits; + }; + + template + JKJ_CONSTEXPR20 To bit_cast(const From& from) { +#if JKJ_HAS_BIT_CAST + return stdr::bit_cast(from); +#else + static_assert(sizeof(From) == sizeof(To), ""); + static_assert(stdr::is_trivially_copyable::value, ""); + static_assert(stdr::is_trivially_copyable::value, ""); + To to; + stdr::memcpy(&to, &from, sizeof(To)); + return to; +#endif + } + } + + // These classes expose encoding specs of IEEE-754-like floating-point formats. + // Currently available formats are IEEE-754 binary32 & IEEE-754 binary64. + + struct ieee754_binary32 { + static constexpr int total_bits = 32; + static constexpr int significand_bits = 23; + static constexpr int exponent_bits = 8; + static constexpr int min_exponent = -126; + static constexpr int max_exponent = 127; + static constexpr int exponent_bias = -127; + static constexpr int decimal_significand_digits = 9; + static constexpr int decimal_exponent_digits = 2; + }; + struct ieee754_binary64 { + static constexpr int total_bits = 64; + static constexpr int significand_bits = 52; + static constexpr int exponent_bits = 11; + static constexpr int min_exponent = -1022; + static constexpr int max_exponent = 1023; + static constexpr int exponent_bias = -1023; + static constexpr int decimal_significand_digits = 17; + static constexpr int decimal_exponent_digits = 3; + }; + + // A floating-point format traits class defines ways to interpret a bit pattern of given size as + // an encoding of floating-point number. This is an implementation of such a traits class, + // supporting ways to interpret IEEE-754 binary floating-point numbers. + template + struct ieee754_binary_traits { + // CarrierUInt needs to have enough size to hold the entire contents of floating-point + // numbers. The actual bits are assumed to be aligned to the LSB, and every other bits are + // assumed to be zeroed. + static_assert(detail::value_bits::value >= Format::total_bits, + "jkj::dragonbox: insufficient number of bits"); + static_assert(detail::stdr::is_unsigned::value, ""); + + // ExponentUInt needs to be large enough to hold (unsigned) exponent bits as well as the + // (signed) actual exponent. + // TODO: static overflow guard against intermediate computations. + static_assert(detail::value_bits::value >= Format::exponent_bits + 1, + "jkj::dragonbox: insufficient number of bits"); + static_assert(detail::stdr::is_signed::value, ""); + + using format = Format; + using carrier_uint = CarrierUInt; + static constexpr int carrier_bits = int(detail::value_bits::value); + using exponent_int = ExponentInt; + + // Extract exponent bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. This function does not do bias adjustment. + static constexpr exponent_int extract_exponent_bits(carrier_uint u) noexcept { + return exponent_int((u >> format::significand_bits) & + ((exponent_int(1) << format::exponent_bits) - 1)); + } + + // Extract significand bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. The result does not contain the implicit bit. + static constexpr carrier_uint extract_significand_bits(carrier_uint u) noexcept { + return carrier_uint(u & ((carrier_uint(1) << format::significand_bits) - 1u)); + } + + // Remove the exponent bits and extract significand bits together with the sign bit. + static constexpr carrier_uint remove_exponent_bits(carrier_uint u) noexcept { + return carrier_uint(u & ~(((carrier_uint(1) << format::exponent_bits) - 1u) + << format::significand_bits)); + } + + // Shift the obtained signed significand bits to the left by 1 to remove the sign bit. + static constexpr carrier_uint remove_sign_bit_and_shift(carrier_uint u) noexcept { + return carrier_uint((carrier_uint(u) << 1) & + ((((carrier_uint(1) << (Format::total_bits - 1)) - 1u) << 1) | 1u)); + } + + // Obtain the actual value of the binary exponent from the extracted exponent bits. + static constexpr exponent_int binary_exponent(exponent_int exponent_bits) noexcept { + return exponent_int(exponent_bits == 0 ? format::min_exponent + : exponent_bits + format::exponent_bias); + } + + // Obtain the actual value of the binary significand from the extracted significand bits + // and exponent bits. + static constexpr carrier_uint binary_significand(carrier_uint significand_bits, + exponent_int exponent_bits) noexcept { + return carrier_uint( + exponent_bits == 0 + ? significand_bits + : (significand_bits | (carrier_uint(1) << format::significand_bits))); + } + + /* Various boolean observer functions */ + + static constexpr bool is_nonzero(carrier_uint u) noexcept { + return (u & ((carrier_uint(1) << (format::significand_bits + format::exponent_bits)) - + 1u)) != 0; + } + static constexpr bool is_positive(carrier_uint u) noexcept { + return u < (carrier_uint(1) << (format::significand_bits + format::exponent_bits)); + } + static constexpr bool is_negative(carrier_uint u) noexcept { return !is_positive(u); } + static constexpr bool is_finite(exponent_int exponent_bits) noexcept { + return exponent_bits != ((exponent_int(1) << format::exponent_bits) - 1); + } + static constexpr bool has_all_zero_significand_bits(carrier_uint u) noexcept { + return ((u << 1) & + ((((carrier_uint(1) << (Format::total_bits - 1)) - 1u) << 1) | 1u)) == 0; + } + static constexpr bool has_even_significand_bits(carrier_uint u) noexcept { + return u % 2 == 0; + } + }; + + // Convert between bit patterns stored in carrier_uint and instances of an actual + // floating-point type. Depending on format and carrier_uint, this operation might not + // be possible for some specific bit patterns. However, the contract is that u always + // denotes a valid bit pattern, so the functions here are assumed to be noexcept. + // Users might specialize this class to change the behavior for certain types. + // The default provided by the library is to treat the given floating-point type Float as either + // IEEE-754 binary32 or IEEE-754 binary64, depending on the bitwise size of Float. + template + struct default_float_bit_carrier_conversion_traits { + // Guards against types that have different internal representations than IEEE-754 + // binary32/64. I don't know if there is a truly reliable way of detecting IEEE-754 binary + // formats. I just did my best here. Note that in some cases + // numeric_limits::is_iec559 may report false even if the internal representation is + // IEEE-754 compatible. In such a case, the user can specialize this traits template and + // remove this static sanity check in order to make Dragonbox work for Float. + static_assert(detail::stdr::numeric_limits::is_iec559 && + detail::stdr::numeric_limits::radix == 2 && + (detail::physical_bits::value == 32 || + detail::physical_bits::value == 64), + "jkj::dragonbox: Float may not be of IEEE-754 binary32/binary64"); + + // Specifies the unsigned integer type to hold bitwise value of Float. + using carrier_uint = + typename detail::stdr::conditional::value == 32, + detail::stdr::uint_least32_t, + detail::stdr::uint_least64_t>::type; + + // Specifies the floating-point format. + using format = typename detail::stdr::conditional::value == 32, + ieee754_binary32, ieee754_binary64>::type; + + // Converts the floating-point type into the bit-carrier unsigned integer type. + static JKJ_CONSTEXPR20 carrier_uint float_to_carrier(Float x) noexcept { + return detail::bit_cast(x); + } + + // Converts the bit-carrier unsigned integer type into the floating-point type. + static JKJ_CONSTEXPR20 Float carrier_to_float(carrier_uint x) noexcept { + return detail::bit_cast(x); + } + }; + + // Convenient wrappers for floating-point traits classes. + // In order to reduce the argument passing overhead, these classes should be as simple as + // possible (e.g., no inheritance, no private non-static data member, etc.; this is an + // unfortunate fact about common ABI convention). + + template + struct signed_significand_bits { + using format_traits = FormatTraits; + using carrier_uint = typename format_traits::carrier_uint; + + carrier_uint u; + + signed_significand_bits() = default; + constexpr explicit signed_significand_bits(carrier_uint bit_pattern) noexcept + : u{bit_pattern} {} + + // Shift the obtained signed significand bits to the left by 1 to remove the sign bit. + constexpr carrier_uint remove_sign_bit_and_shift() const noexcept { + return format_traits::remove_sign_bit_and_shift(u); + } + + constexpr bool is_positive() const noexcept { return format_traits::is_positive(u); } + constexpr bool is_negative() const noexcept { return format_traits::is_negative(u); } + constexpr bool has_all_zero_significand_bits() const noexcept { + return format_traits::has_all_zero_significand_bits(u); + } + constexpr bool has_even_significand_bits() const noexcept { + return format_traits::has_even_significand_bits(u); + } + }; + + template + struct float_bits { + using format_traits = FormatTraits; + using carrier_uint = typename format_traits::carrier_uint; + using exponent_int = typename format_traits::exponent_int; + + carrier_uint u; + + float_bits() = default; + constexpr explicit float_bits(carrier_uint bit_pattern) noexcept : u{bit_pattern} {} + + // Extract exponent bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. This function does not do bias adjustment. + constexpr exponent_int extract_exponent_bits() const noexcept { + return format_traits::extract_exponent_bits(u); + } + + // Extract significand bits from a bit pattern. + // The result must be aligned to the LSB so that there is no additional zero paddings + // on the right. The result does not contain the implicit bit. + constexpr carrier_uint extract_significand_bits() const noexcept { + return format_traits::extract_significand_bits(u); + } + + // Remove the exponent bits and extract significand bits together with the sign bit. + constexpr signed_significand_bits remove_exponent_bits() const noexcept { + return signed_significand_bits(format_traits::remove_exponent_bits(u)); + } + + // Obtain the actual value of the binary exponent from the extracted exponent bits. + static constexpr exponent_int binary_exponent(exponent_int exponent_bits) noexcept { + return format_traits::binary_exponent(exponent_bits); + } + constexpr exponent_int binary_exponent() const noexcept { + return binary_exponent(extract_exponent_bits()); + } + + // Obtain the actual value of the binary exponent from the extracted significand bits + // and exponent bits. + static constexpr carrier_uint binary_significand(carrier_uint significand_bits, + exponent_int exponent_bits) noexcept { + return format_traits::binary_significand(significand_bits, exponent_bits); + } + constexpr carrier_uint binary_significand() const noexcept { + return binary_significand(extract_significand_bits(), extract_exponent_bits()); + } + + constexpr bool is_nonzero() const noexcept { return format_traits::is_nonzero(u); } + constexpr bool is_positive() const noexcept { return format_traits::is_positive(u); } + constexpr bool is_negative() const noexcept { return format_traits::is_negative(u); } + constexpr bool is_finite(exponent_int exponent_bits) const noexcept { + return format_traits::is_finite(exponent_bits); + } + constexpr bool is_finite() const noexcept { + return format_traits::is_finite(extract_exponent_bits()); + } + constexpr bool has_even_significand_bits() const noexcept { + return format_traits::has_even_significand_bits(u); + } + }; + + template , + class FormatTraits = ieee754_binary_traits> + JKJ_CONSTEXPR20 float_bits make_float_bits(Float x) noexcept { + return float_bits(ConversionTraits::float_to_carrier(x)); + } + + namespace detail { + //////////////////////////////////////////////////////////////////////////////////////// + // Bit operation intrinsics. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace bits { + // Most compilers should be able to optimize this into the ROR instruction. + // n is assumed to be at most of bit_width bits. + template + JKJ_CONSTEXPR14 UInt rotr(UInt n, unsigned int r) noexcept { + static_assert(bit_width > 0, "jkj::dragonbox: rotation bit-width must be positive"); + static_assert(bit_width <= value_bits::value, + "jkj::dragonbox: rotation bit-width is too large"); + r &= (bit_width - 1); + return (n >> r) | (n << ((bit_width - r) & (bit_width - 1))); + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Utilities for wide unsigned integer arithmetic. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace wuint { + // Compilers might support built-in 128-bit integer types. However, it seems that + // emulating them with a pair of 64-bit integers actually produces a better code, + // so we avoid using those built-ins. That said, they are still useful for + // implementing 64-bit x 64-bit -> 128-bit multiplication. + + // clang-format off +#if defined(__SIZEOF_INT128__) + // To silence "error: ISO C++ does not support '__int128' for 'type name' + // [-Wpedantic]" +#if defined(__GNUC__) + __extension__ +#endif + using builtin_uint128_t = unsigned __int128; +#endif + // clang-format on + + struct uint128 { + uint128() = default; + + stdr::uint_least64_t high_; + stdr::uint_least64_t low_; + + constexpr uint128(stdr::uint_least64_t high, stdr::uint_least64_t low) noexcept + : high_{high}, low_{low} {} + + constexpr stdr::uint_least64_t high() const noexcept { return high_; } + constexpr stdr::uint_least64_t low() const noexcept { return low_; } + + JKJ_CONSTEXPR20 uint128& operator+=(stdr::uint_least64_t n) & noexcept { + auto const generic_impl = [&] { + auto const sum = (low_ + n) & UINT64_C(0xffffffffffffffff); + high_ += (sum < low_ ? 1 : 0); + low_ = sum; + }; + // To suppress warning. + static_cast(generic_impl); + + JKJ_IF_CONSTEXPR(value_bits::value > 64) { + generic_impl(); + return *this; + } + + JKJ_IF_CONSTEVAL { + generic_impl(); + return *this; + } + + // See https://github.com/fmtlib/fmt/pull/2985. +#if JKJ_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR( + stdr::is_same::value) { + unsigned long long carry{}; + low_ = stdr::uint_least64_t(__builtin_addcll(low_, n, 0, &carry)); + high_ = stdr::uint_least64_t(__builtin_addcll(high_, 0, carry, &carry)); + return *this; + } +#endif +#if JKJ_HAS_BUILTIN(__builtin_addcl) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR(stdr::is_same::value) { + unsigned long carry{}; + low_ = stdr::uint_least64_t( + __builtin_addcl(static_cast(low_), + static_cast(n), 0, &carry)); + high_ = stdr::uint_least64_t( + __builtin_addcl(static_cast(high_), 0, carry, &carry)); + return *this; + } +#endif +#if JKJ_HAS_BUILTIN(__builtin_addc) && !defined(__ibmxl__) + JKJ_IF_CONSTEXPR(stdr::is_same::value) { + unsigned int carry{}; + low_ = stdr::uint_least64_t(__builtin_addc(static_cast(low_), + static_cast(n), 0, + &carry)); + high_ = stdr::uint_least64_t( + __builtin_addc(static_cast(high_), 0, carry, &carry)); + return *this; + } +#endif + +#if JKJ_HAS_BUILTIN(__builtin_ia32_addcarry_u64) + // __builtin_ia32_addcarry_u64 is not documented, but it seems it takes unsigned + // long long arguments. + unsigned long long result{}; + auto const carry = __builtin_ia32_addcarry_u64(0, low_, n, &result); + low_ = stdr::uint_least64_t(result); + __builtin_ia32_addcarry_u64(carry, high_, 0, &result); + high_ = stdr::uint_least64_t(result); +#elif defined(_MSC_VER) && defined(_M_X64) + // On MSVC, uint_least64_t and __int64 must be unsigned long long; see + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/standard-types + // and https://learn.microsoft.com/en-us/cpp/cpp/int8-int16-int32-int64. + static_assert(stdr::is_same::value, + ""); + auto const carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); +#elif defined(__INTEL_COMPILER) && (defined(_M_X64) || defined(__x86_64)) + // Cannot find any documentation on how things are defined, but hopefully this + // is always true... + static_assert(stdr::is_same::value, ""); + auto const carry = _addcarry_u64(0, low_, n, &low_); + _addcarry_u64(carry, high_, 0, &high_); +#else + generic_impl(); +#endif + return *this; + } + }; + + inline JKJ_CONSTEXPR20 stdr::uint_least64_t umul64(stdr::uint_least32_t x, + stdr::uint_least32_t y) noexcept { +#if defined(_MSC_VER) && defined(_M_IX86) + JKJ_IF_NOT_CONSTEVAL { return __emulu(x, y); } +#endif + return x * stdr::uint_least64_t(y); + } + + // Get 128-bit result of multiplication of two 64-bit unsigned integers. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 + umul128(stdr::uint_least64_t x, stdr::uint_least64_t y) noexcept { + auto const generic_impl = [=]() -> uint128 { + auto const a = stdr::uint_least32_t(x >> 32); + auto const b = stdr::uint_least32_t(x); + auto const c = stdr::uint_least32_t(y >> 32); + auto const d = stdr::uint_least32_t(y); + + auto const ac = umul64(a, c); + auto const bc = umul64(b, c); + auto const ad = umul64(a, d); + auto const bd = umul64(b, d); + + auto const intermediate = + (bd >> 32) + stdr::uint_least32_t(ad) + stdr::uint_least32_t(bc); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + stdr::uint_least32_t(bd)}; + }; + // To silence warning. + static_cast(generic_impl); + +#if defined(__SIZEOF_INT128__) + auto const result = builtin_uint128_t(x) * builtin_uint128_t(y); + return {stdr::uint_least64_t(result >> 64), stdr::uint_least64_t(result)}; +#elif defined(_MSC_VER) && defined(_M_X64) + JKJ_IF_CONSTEVAL { + // This redundant variable is to workaround MSVC's codegen bug caused by the + // interaction of NRVO and intrinsics. + auto const result = generic_impl(); + return result; + } + uint128 result; + #if defined(__AVX2__) + result.low_ = _mulx_u64(x, y, &result.high_); + #else + result.low_ = _umul128(x, y, &result.high_); + #endif + return result; +#else + return generic_impl(); +#endif + } + + // Get high half of the 128-bit result of multiplication of two 64-bit unsigned + // integers. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 stdr::uint_least64_t + umul128_upper64(stdr::uint_least64_t x, stdr::uint_least64_t y) noexcept { + auto const generic_impl = [=]() -> stdr::uint_least64_t { + auto const a = stdr::uint_least32_t(x >> 32); + auto const b = stdr::uint_least32_t(x); + auto const c = stdr::uint_least32_t(y >> 32); + auto const d = stdr::uint_least32_t(y); + + auto const ac = umul64(a, c); + auto const bc = umul64(b, c); + auto const ad = umul64(a, d); + auto const bd = umul64(b, d); + + auto const intermediate = + (bd >> 32) + stdr::uint_least32_t(ad) + stdr::uint_least32_t(bc); + + return ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32); + }; + // To silence warning. + static_cast(generic_impl); + +#if defined(__SIZEOF_INT128__) + auto const result = builtin_uint128_t(x) * builtin_uint128_t(y); + return stdr::uint_least64_t(result >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + JKJ_IF_CONSTEVAL { + // This redundant variable is to workaround MSVC's codegen bug caused by the + // interaction of NRVO and intrinsics. + auto const result = generic_impl(); + return result; + } + stdr::uint_least64_t result; + #if defined(__AVX2__) + _mulx_u64(x, y, &result); + #else + result = __umulh(x, y); + #endif + return result; +#else + return generic_impl(); +#endif + } + + // Get upper 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit + // unsigned integer. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 umul192_upper128(stdr::uint_least64_t x, + uint128 y) noexcept { + auto r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; + } + + // Get upper 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit + // unsigned integer. + inline JKJ_CONSTEXPR20 stdr::uint_least64_t + umul96_upper64(stdr::uint_least32_t x, stdr::uint_least64_t y) noexcept { +#if defined(__SIZEOF_INT128__) || (defined(_MSC_VER) && defined(_M_X64)) + return umul128_upper64(stdr::uint_least64_t(x) << 32, y); +#else + auto const yh = stdr::uint_least32_t(y >> 32); + auto const yl = stdr::uint_least32_t(y); + + auto const xyh = umul64(x, yh); + auto const xyl = umul64(x, yl); + + return xyh + (xyl >> 32); +#endif + } + + // Get lower 128-bits of multiplication of a 64-bit unsigned integer and a 128-bit + // unsigned integer. + JKJ_SAFEBUFFERS inline JKJ_CONSTEXPR20 uint128 umul192_lower128(stdr::uint_least64_t x, + uint128 y) noexcept { + auto const high = x * y.high(); + auto const high_low = umul128(x, y.low()); + return {(high + high_low.high()) & UINT64_C(0xffffffffffffffff), high_low.low()}; + } + + // Get lower 64-bits of multiplication of a 32-bit unsigned integer and a 64-bit + // unsigned integer. + constexpr stdr::uint_least64_t umul96_lower64(stdr::uint_least32_t x, + stdr::uint_least64_t y) noexcept { + return (x * y) & UINT64_C(0xffffffffffffffff); + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Some simple utilities for constexpr computation. + //////////////////////////////////////////////////////////////////////////////////////// + + template + constexpr Int compute_power(Int a) noexcept { + static_assert(k >= 0, ""); +#if JKJ_HAS_CONSTEXPR14 + Int p = 1; + for (int i = 0; i < k; ++i) { + p *= a; + } + return p; +#else + return k == 0 ? 1 + : k % 2 == 0 ? compute_power(a * a) + : a * compute_power(a * a); +#endif + } + + template + constexpr int count_factors(UInt n) noexcept { + static_assert(a > 1, ""); +#if JKJ_HAS_CONSTEXPR14 + int c = 0; + while (n % a == 0) { + n /= a; + ++c; + } + return c; +#else + return n % a == 0 ? count_factors(n / a) + 1 : 0; +#endif + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Utilities for fast/constexpr log computation. + //////////////////////////////////////////////////////////////////////////////////////// + + namespace log { + static_assert((stdr::int_fast32_t(-1) >> 1) == stdr::int_fast32_t(-1) && + (stdr::int_fast16_t(-1) >> 1) == stdr::int_fast16_t(-1), + "jkj::dragonbox: right-shift for signed integers must be arithmetic"); + + // For constexpr computation. + // Returns -1 when n = 0. + template + constexpr int floor_log2(UInt n) noexcept { +#if JKJ_HAS_CONSTEXPR14 + int count = -1; + while (n != 0) { + ++count; + n >>= 1; + } + return count; +#else + return n == 0 ? -1 : floor_log2(n / 2) + 1; +#endif + } + + template