diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 0aff9456..00000000 --- a/.cirrus.yml +++ /dev/null @@ -1,30 +0,0 @@ -build_and_store_wheels: &BUILD_AND_STORE_WHEELS - install_cibuildwheel_script: - - python -m pip install cibuildwheel==2.16.2 - run_cibuildwheel_script: - - cibuildwheel - wheels_artifacts: - path: "wheelhouse/*" - - # Upload only for tagged commit - only_if: $CIRRUS_TAG != '' - publish_script: - - python -m pip install twine - - python -m twine upload --repository-url https://upload.pypi.org/legacy/ --username __token__ wheelhouse/*.whl - - -linux_aarch64_task: - name: Build Linux aarch64 wheels. - compute_engine_instance: - image_project: cirrus-images - image: family/docker-builder-arm64 - architecture: arm64 - platform: linux - cpu: 4 - memory: 4G - environment: - TWINE_PASSWORD: ENCRYPTED[ade2037764e68fea251152f7585f3f77cdd748af06dc0f06942c45a8a8770fff19032c985f8dc193229c8adb2c0fecb9] - - install_pre_requirements_script: - - apt install -y python3-venv python-is-python3 - <<: *BUILD_AND_STORE_WHEELS diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0fd988d9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: syoyo # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml new file mode 100644 index 00000000..77d39d3c --- /dev/null +++ b/.github/workflows/cron.yml @@ -0,0 +1,22 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + days-before-issue-stale: 14 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 14 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 00000000..b40cf7a0 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,196 @@ +name: Python + +on: [push, pull_request] + +jobs: + check_format: + name: Check Python code format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Set up Python and install dependencies + working-directory: tests/python + run: uv sync --project . --python 3.13 + + - name: Check code format + working-directory: tests/python + run: uv run --project . black --check ../.. + + build_wheels_quick: + name: Build wheels for quick testing + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true # Optional, use if you use setuptools_scm + + - name: Build wheels + uses: pypa/cibuildwheel@v3.3.1 + env: + # These are the only wheels we need for the `test_wheels` job. For + # faster iteration times, we limit the job to only build these wheels. + # Restrict to CPython to avoid building unused PyPy wheels. + CIBW_BUILD: "cp*-manylinux_x86_64" + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-quick + path: ./wheelhouse/*.whl + + test_wheels: + name: Test wheels with Python ${{ matrix.python-version }} and NumPy ${{ matrix.numpy-version }} + needs: [build_wheels_quick] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.9" + numpy-version: "1.25.2" + - python-version: "3.10" + numpy-version: "1.26.4" + - python-version: "3.11" + numpy-version: "1.26.4" + - python-version: "3.12" + numpy-version: "1.26.4" + - python-version: "3.11" + numpy-version: "2.4.2" + - python-version: "3.12" + numpy-version: "2.4.2" + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Download wheel artifacts + uses: actions/download-artifact@v4 + with: + pattern: cibw-wheels-quick + path: dist + merge-multiple: true + + - name: Set up Python ${{ matrix.python-version }} and install dependencies + working-directory: tests/python + run: uv sync --project . --python ${{ matrix.python-version }} + + - name: Install NumPy ${{ matrix.numpy-version }} + working-directory: tests/python + run: | + uv pip install --project . --only-binary :all: numpy==${{ matrix.numpy-version }} + + - name: Install manylinux wheel built for Python ${{ matrix.python-version }} + working-directory: tests/python + run: uv pip install --project . ../../dist/*cp$(echo ${{ matrix.python-version }} | tr -d .)*.whl + + - name: Run tests + working-directory: tests/python + run: uv run --project . pytest + + build_wheels_main: + name: Build remaining wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true # Optional, use if you use setuptools_scm + + - name: Build wheels + uses: pypa/cibuildwheel@v3.3.1 + env: + CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" + CIBW_ARCHS_WINDOWS: "AMD64" + # The quick build has already taken care of manylinux. + CIBW_SKIP: "*-manylinux_x86_64" + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-main-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + # It looks cibuildwheels did not clean build folder(CMake), and it results to Windows arm64 build failure(trying to reuse x86 build of .obj) + # So supply separated build job for Windows ARM64 build + # TODO: clean build folder using CIBW_BEFORE_ALL? + build_wheels_win_arm64: + name: Build ARM64 wheels on Windows + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true # Optional, use if you use setuptools_scm + + - name: Build wheels + uses: pypa/cibuildwheel@v3.3.1 + env: + CIBW_ARCHS_WINDOWS: "ARM64" + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-win-arm64 + path: ./wheelhouse/*.whl + + make_sdist: + name: Make SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Optional, use if you use setuptools_scm + fetch-tags: true # Optional, use if you use setuptools_scm + + - name: Build SDist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_all: + needs: [build_wheels_quick, build_wheels_main, build_wheels_win_arm64, make_sdist] + runs-on: ubuntu-latest + environment: release + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + # upload to PyPI on every tag starting with 'v' + # NOTE: Without github.event_name & githug.ref check, `upload_all` task is still triggered on 'main' branch push. + # (then get 'Branch "main" is not allowed to deploy to release due to environment protection rules.' error) + # So still do event_name and github.ref check. + # TODO: Make it work only using Github `environment` feature. + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + # alternatively, to publish when a GitHub Release is created, use the following rule: + # if: github.event_name == 'push' && github.event.action == 'published' + steps: + - uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + # Use Trusted Publisher feature: + # https://docs.pypi.org/trusted-publishers/ + # so no use of PYPI_API_TOKEN + #password: ${{ secrets.PYPI_API_TOKEN }} + # + # Avoid race condition when using multiple CIs + skip-existing: true + verbose: true diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 00000000..3a7ffc82 --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,22 @@ +name: Unit Tests + +on: + push: + branches: + - '**' + tags: + - 'v*' + pull_request: + branches: + - '**' + +jobs: + unit_linux: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Run unit tests + run: | + cd tests + make check diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml deleted file mode 100644 index b37104a2..00000000 --- a/.github/workflows/wheels.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Build and upload to PyPI - -# Build on every branch push, tag push, and pull request change: -on: [push, pull_request] - -jobs: - - build_wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true # Optional, use if you use setuptools_scm - - - name: Build wheels - uses: pypa/cibuildwheel@v2.16.5 - # to supply options, put them in 'env', like: - # env: - # CIBW_SOME_OPTION: value - # Disable building PyPy wheels on all platforms - env: - CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" - CIBW_ARCHS_WINDOWS: "AMD64 x86" - # disable aarm64 build since its too slow to build(docker + qemu) - CIBW_ARCHS_LINUX: "x86_64 i686" - # it looks cibuildwheel fails to add version string to wheel file for python 3.6, so skip it - CIBW_SKIP: pp* - - - uses: actions/upload-artifact@v4 - with: - name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl - - # It looks cibuildwheels did not clean build folder(CMake), and it results to Windows arm64 build failure(trying to reuse x86 build of .obj) - # So supply separated build job for Windows ARM64 build - # TODO: clean build folder using CIBW_BEFORE_ALL? - build_win_arm64_wheels: - name: Build ARM64 wheels on Windows. - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true # Optional, use if you use setuptools_scm - - - name: Build wheels - uses: pypa/cibuildwheel@v2.16.5 - # to supply options, put them in 'env', like: - # env: - # CIBW_SOME_OPTION: value - # Disable building PyPy wheels on all platforms - env: - CIBW_ARCHS_WINDOWS: "ARM64" - CIBW_SKIP: pp* - - - uses: actions/upload-artifact@v4 - with: - name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl - - make_sdist: - name: Make SDist - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Optional, use if you use setuptools_scm - fetch-tags: true # Optional, use if you use setuptools_scm - - - name: Build SDist - run: pipx run build --sdist - - - uses: actions/upload-artifact@v4 - with: - name: cibw-sdist - path: dist/*.tar.gz - - upload_all: - needs: [build_wheels, build_wheels, make_sdist] - runs-on: ubuntu-latest - environment: release - permissions: - # IMPORTANT: this permission is mandatory for trusted publishing - id-token: write - # upload to PyPI on every tag starting with 'v' - # NOTE: Without github.event_name & githug.ref check, `upload_all` task is still triggered on 'main' branch push. - # (then get 'Branch "main" is not allowed to deploy to release due to environment protection rules.' error) - # So still do event_name and github.ref check. - # TODO: Make it work only using Github `environment` feature. - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - # alternatively, to publish when a GitHub Release is created, use the following rule: - # if: github.event_name == 'push' && github.event.action == 'published' - steps: - - uses: actions/download-artifact@v4 - with: - pattern: cibw-* - path: dist - merge-multiple: true - - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - # Use Trusted Publisher feature: - # https://docs.pypi.org/trusted-publishers/ - # so no use of PYPI_API_TOKEN - #password: ${{ secrets.PYPI_API_TOKEN }} - # - # Avoid race condition when using multiple CIs - skip-existing: true - verbose: true diff --git a/.gitignore b/.gitignore index f9b4d691..4bd2eb9d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,10 @@ build/ /python/tiny_obj_loader.h /tests/tester /tests/tester.dSYM +/_codeql_build_dir/ +/_codeql_detected_source_root +/python/_version.py +/tinyobjloader.egg-info/ +**/__pycache__/ + +/Testing/Temporary/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 12b67f28..00000000 --- a/.travis.yml +++ /dev/null @@ -1,56 +0,0 @@ -language: cpp -sudo: required -matrix: - include: - - addons: &1 - apt: - sources: - - george-edison55-precise-backports - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.7 - packages: - - cmake - - cmake-data - - ninja-build - - g++-4.9 - - clang-3.7 - compiler: clang - env: DEPLOY_BUILD=1 COMPILER_VERSION=3.7 BUILD_TYPE=Debug - - addons: *1 - compiler: clang - env: COMPILER_VERSION=3.7 BUILD_TYPE=Release - - addons: &2 - apt: - sources: - - george-edison55-precise-backports - - ubuntu-toolchain-r-test - packages: - - cmake - - cmake-data - - ninja-build - - g++-4.9 - compiler: gcc - env: COMPILER_VERSION=4.9 BUILD_TYPE=Debug - - addons: *2 - compiler: gcc - env: COMPILER_VERSION=4.9 BUILD_TYPE=Release - - addons: *1 - compiler: clang - env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug -before_install: -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi -- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get update python; fi -- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get install python-dev libffi-dev libssl-dev; fi -- if [ -n "$REPORT_COVERAGE" ]; then sudo pip install --upgrade pip; fi -- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user requests[security]; fi -- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user cpp-coveralls; fi -script: -- cd tests -- make check -- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e - jni -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi -- cd .. -- rm -rf dist -- mkdir dist -- cp tiny_obj_loader.h dist/ - diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..87715260 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +# Use this for strict compilation check(will work on clang 3.8+) +#EXTRA_CXXFLAGS := -fsanitize=address,undefined -Wall -Werror -Weverything -DTINYOBJLOADER_ENABLE_THREADED=1 -Wno-c++98-compat +#EXTRA_CXXFLAGS := -Weverything -Wall -DTINYOBJLOADER_ENABLE_THREADED=1 -Wno-c++98-compat +# Note: fast_float is now bundled by default (no separate include path needed). +# Define TINYOBJLOADER_DISABLE_FAST_FLOAT to opt out of the bundled parser. +EXTRA_CXXFLAGS := + +all: + g++ $(EXTRA_CXXFLAGS) -DTINYOBJLOADER_USE_DOUBLE=1 -std=c++11 -g -O0 -o loader_example loader_example.cc + #clang++ $(EXTRA_CXXFLAGS) -DTINYOBJLOADER_USE_DOUBLE=1 -std=c++11 -g -O2 -o loader_example loader_example.cc + +lint: + ./cpplint.py tiny_gltf_loader.h diff --git a/README.md b/README.md index 2bf40db1..0150156a 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,16 @@ [![PyPI version](https://badge.fury.io/py/tinyobjloader.svg)](https://badge.fury.io/py/tinyobjloader) -[![AZ Build Status](https://dev.azure.com/tinyobjloader/tinyobjloader/_apis/build/status/tinyobjloader.tinyobjloader?branchName=master)](https://dev.azure.com/tinyobjloader/tinyobjloader/_build/latest?definitionId=1&branchName=master) - -[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/m6wfkvket7gth8wn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader-6e4qf/branch/master) - -[![Coverage Status](https://coveralls.io/repos/github/syoyo/tinyobjloader/badge.svg?branch=master)](https://coveralls.io/github/syoyo/tinyobjloader?branch=master) - -[![AUR version](https://img.shields.io/aur/version/tinyobjloader?logo=arch-linux)](https://aur.archlinux.org/packages/tinyobjloader) - -Tiny but powerful single file wavefront obj loader written in C++03. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time. +Tiny but powerful single file wavefront obj loader written in C++11. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time. `tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) -If you are looking for C89 version, please see https://github.com/syoyo/tinyobjloader-c . +If you are looking for C99 version, please see https://github.com/syoyo/tinyobjloader-c . Version notice -------------- -We recommend to use `master`(`main`) branch. Its v2.0 release candidate. Most features are now nearly robust and stable(Remaining task for release v2.0 is polishing C++ and Python API, and fix built-in triangulation code). +We recommend using the `release` (main) branch. It contains the v2.0 release candidate. Most features are now nearly robust and stable. (The remaining task for release v2.0 is polishing C++ and Python API, and fix built-in triangulation code). We have released new version v1.0.0 on 20 Aug, 2016. Old version is available as `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x @@ -34,7 +26,7 @@ Old version is available as `v0.9.x` branch https://github.com/syoyo/tinyobjload ## Requirements -* C++03 compiler +* C++11 compiler ### Old version @@ -74,8 +66,10 @@ TinyObjLoader is successfully used in ... * metal-ray-tracer - Writing ray-tracer using Metal Performance Shaders https://github.com/sergeyreznik/metal-ray-tracer https://sergeyreznik.github.io/metal-ray-tracer/index.html * Supernova Engine - 2D and 3D projects with Lua or C++ in data oriented design: https://github.com/supernovaengine/supernova * AGE (Arc Game Engine) - An open-source engine for building 2D & 3D real-time rendering and interactive contents: https://github.com/MohitSethi99/ArcGameEngine -* [Wicked Engine](https://github.com/turanszkij/WickedEngine) - 3D engine with modern graphics -* Your project here! (Letting us know via github issue is welcome!) +* [Wicked Engine](https://github.com/turanszkij/WickedEngine) - 3D engine with modern graphics +* [Lumina Game Engine](https://github.com/MrDrElliot/LuminaEngine) - A modern, high-performance game engine built with Vulkan +* lacecore: Python polygonal mesh library optimized for cloud computation https://github.com/lace/lacecore +* Your project here! (Plese send PR) ### Old version(v0.9.x) @@ -245,12 +239,28 @@ TinyObjLoader now use `real_t` for floating point data type. Default is `float(32bit)`. You can enable `double(64bit)` precision by using `TINYOBJLOADER_USE_DOUBLE` define. +### High-performance float parsing (fast_float) + +By default, TinyObjLoader embeds [fast_float v8.0.2](https://github.com/fastfloat/fast_float) +for ~3× faster, bit-exact ASCII-to-float conversion (equivalent to `strtod` but without locale overhead). + +To opt out and use the built-in hand-written parser instead, define: + +```c++ +#define TINYOBJLOADER_DISABLE_FAST_FLOAT +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" +``` + +**Note:** If your project already includes `fast_float` under the `fast_float` namespace, +defining `TINYOBJLOADER_DISABLE_FAST_FLOAT` avoids a redefinition conflict. + ### Robust triangulation When you enable `triangulation`(default is enabled), TinyObjLoader triangulate polygons(faces with 4 or more vertices). -Built-in trinagulation code may not work well in some polygon shape. +Built-in triangulation code may not work well in some polygon shape. You can define `TINYOBJLOADER_USE_MAPBOX_EARCUT` for robust triangulation using `mapbox/earcut.hpp`. This requires C++11 compiler though. And you need to copy `mapbox/earcut.hpp` to your project. @@ -260,7 +270,7 @@ If you have your own `mapbox/earcut.hpp` file incuded in your project, you can d ```c++ #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc -// Optional. define TINYOBJLOADER_USE_MAPBOX_EARCUT gives robust trinagulation. Requires C++11 +// Optional. define TINYOBJLOADER_USE_MAPBOX_EARCUT gives robust triangulation. Requires C++11 //#define TINYOBJLOADER_USE_MAPBOX_EARCUT #include "tiny_obj_loader.h" @@ -332,7 +342,7 @@ for (size_t s = 0; s < shapes.size(); s++) { ```c++ #define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc -// Optional. define TINYOBJLOADER_USE_MAPBOX_EARCUT gives robust trinagulation. Requires C++11 +// Optional. define TINYOBJLOADER_USE_MAPBOX_EARCUT gives robust triangulation. Requires C++11 //#define TINYOBJLOADER_USE_MAPBOX_EARCUT #include "tiny_obj_loader.h" @@ -425,17 +435,16 @@ See [python/sample.py](python/sample.py) for example use of Python binding of ti ### CI + PyPI upload -cibuildwheels + twine upload for each git tagging event is handled in Github Actions and Cirrus CI(arm builds). +cibuildwheels + twine upload for each git tagging event is handled in Github Actions. #### How to bump version(For developer) -* Apply `black` to python files(`python/sample.py`) * Bump version in CMakeLists.txt * Commit and push `release`. Confirm C.I. build is OK. * Create tag starting with `v`(e.g. `v2.1.0`) * `git push --tags` * version settings is automatically handled in python binding through setuptools_scm. - * cibuildwheels + pypi upload(through twine) will be automatically triggered in Github Actions + Cirrus CI. + * cibuildwheels + pypi upload (through twine) will be automatically triggered in Github Actions. ## Tests diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 93f691ce..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: 1.0.{build} - -platform: x64 - -install: - ####################################################################################### - # All external dependencies are installed in C:\projects\deps - ####################################################################################### - - mkdir C:\projects\deps - - ####################################################################################### - # Install Ninja - ####################################################################################### - - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-win.zip" - - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip - - 7z x ninja.zip -oC:\projects\deps\ninja > nul - - set PATH=C:\projects\deps\ninja;%PATH% - - ninja --version - -build_script: - - cd tests - - vcbuild.bat diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 2580f172..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,170 +0,0 @@ -# -# Python wheels build is now done in Github Actions + Cirrus CI(for arm build) -# so python build is disabled in Azure pipelines. -# - -variables: - # https://cibuildwheel.readthedocs.io/en/stable/cpp_standards/ - # cibuildwheel now supports python 3.6+(as of 2022 Oct) - #CIBW_SKIP: "pp*" - CIBW_BEFORE_BUILD: "pip install pybind11" - CIBW_ARCHS_LINUXBEFORE_BUILD: "pip install pybind11" - # disable aarch64 build for a while since it(pulling docker aarch64 image) exceeds Azure's 60 min limit - # NOTE: aarch64 linux support in Azure pipeline is not yet officially supported(as of 2022 Oct) https://github.com/microsoft/azure-pipelines-agent/issues/3935 - #CIBW_ARCHS_LINUX: auto aarch64 - CIBW_ARCHS_MACOS: x86_64 universal2 arm64 - #CIBW_BEFORE_BUILD_MACOS: "pip install -U pip setuptools" - #CIBW_BEFORE_BUILD_LINUX: "pip install -U pip setuptools" - #CIBW_TEST_COMMAND: TODO "python -c \"import tinyobjloader; tinyobjloader.test()\"" - CIBW_BUILD_VERBOSITY: "2" - #CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 - #CIBW_MANYLINUX_I686_IMAGE: manylinux2014 - -jobs: - - job: unit_linux - pool: { vmImage: "ubuntu-latest" } - steps: - - script: | - cd tests - make && ./tester - displayName: Run unit tests - - - job: python_format - pool: { vmImage: "ubuntu-latest" } - steps: - - task: UsePythonVersion@0 - - script: | - # 19.10b0 triggers 'cannot import name '_unicodefun' from 'click'' error. - # https://stackoverflow.com/questions/71673404/importerror-cannot-import-name-unicodefun-from-click - #pip install black==19.10b0 - #pip install black==22.3.0 - pip install black==22.10.0 - - black --check python/ - displayName: Check Python code format - - # Disabled: python build - ## - ## Ubuntu16.04 seems now deprecated(as of 2021/12/01), - ## so use `ubuntu-latest` - #- job: linux - # pool: {vmImage: "ubuntu-latest"} - # steps: - # - task: UsePythonVersion@0 - # - bash: | - # python3 -m pip install --upgrade pip - # pip3 install cibuildwheel twine - - # # Use pipx to build source dist - # pip3 install pipx - - # # Source dist - # pipx run build --sdist - # ls -la dist/* - - # # build binary wheels - # cibuildwheel --platform linux --output-dir wheelhouse . - - # - task: CopyFiles@2 - # inputs: - # contents: 'wheelhouse/**' - # targetFolder: $(Build.ArtifactStagingDirectory) - - # - task: CopyFiles@2 - # inputs: - # contents: 'dist/**' - # targetFolder: $(Build.ArtifactStagingDirectory) - - # - task: PublishBuildArtifacts@1 - # inputs: - # path: $(Build.ArtifactStagingDirectory) - # artifactName: tinyobjDeployLinux - - #- job: macos - # pool: {vmImage: 'macOS-latest'} - # variables: - # # Support C++11: https://github.com/joerick/cibuildwheel/pull/156 - # MACOSX_DEPLOYMENT_TARGET: 10.9 - # steps: - # - task: UsePythonVersion@0 - # - bash: | - # python3 -m pip install --upgrade pip - # pip3 install cibuildwheel - # cibuildwheel --platform macos --output-dir wheelhouse . - # - task: CopyFiles@2 - # inputs: - # contents: 'wheelhouse/*.whl' - # targetFolder: $(Build.ArtifactStagingDirectory) - # - task: PublishBuildArtifacts@1 - # inputs: - # path: $(Build.ArtifactStagingDirectory) - # artifactName: tinyobjDeployMacOS - - #- job: windows - # pool: {vmImage: 'windows-latest'} - # steps: - # - task: UsePythonVersion@0 - # - bash: | - # python -m pip install --upgrade pip - # pip install cibuildwheel - # cibuildwheel --platform windows --output-dir wheelhouse . - # - task: CopyFiles@2 - # inputs: - # contents: 'wheelhouse/*.whl' - # targetFolder: $(Build.ArtifactStagingDirectory) - # - task: PublishBuildArtifacts@1 - # inputs: - # path: $(Build.ArtifactStagingDirectory) - # artifactName: tinyobjDeployWindows - - #- job: deployPyPI - # # Based on vispy: https://github.com/vispy/vispy/blob/master/azure-pipelines.yml - # pool: {vmImage: 'ubuntu-latest'} - # condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) - # dependsOn: - # - linux - # - macos - # - windows - # steps: - # - task: UsePythonVersion@0 - - # # TODO(syoyo): Use buildType: specific to download multiple artifacts at once? - # - task: DownloadBuildArtifacts@0 - # inputs: - # artifactName: 'tinyobjDeployLinux' - # downloadPath: $(Pipeline.Workspace) - - # - task: DownloadBuildArtifacts@0 - # inputs: - # artifactName: 'tinyobjDeployMacOS' - # downloadPath: $(Pipeline.Workspace) - - # - task: DownloadBuildArtifacts@0 - # inputs: - # artifactName: 'tinyobjDeployWindows' - # downloadPath: $(Pipeline.Workspace) - - # # Publish to PyPI through twine - # - bash: | - # cd $(Pipeline.Workspace) - # find . - # python -m pip install --upgrade pip - # pip install twine - # echo tinyobjDeployLinux/dist/* - # echo tinyobjDeployLinux/wheelhouse/* tinyobjDeployMacOS/wheelhouse/* tinyobjDeployWindows/wheelhouse/* - # twine upload -u "__token__" --skip-existing tinyobjDeployLinux/dist/* tinyobjDeployLinux/wheelhouse/* tinyobjDeployMacOS/wheelhouse/* tinyobjDeployWindows/wheelhouse/* - # env: - # TWINE_PASSWORD: $(pypiToken2) - -trigger: - branches: - include: - - '*' - tags: - include: - - 'v*' - -pr: - branches: - include: - - "*" diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc index 059e57fe..2e6bd952 100644 --- a/examples/viewer/viewer.cc +++ b/examples/viewer/viewer.cc @@ -152,6 +152,8 @@ bool mouseRightPressed; float curr_quat[4]; float prev_quat[4]; float eye[3], lookat[3], up[3]; +float g_angleX = 0.0f; // in degree +float g_angleY = 0.0f; // in degree bool g_show_wire = true; bool g_cull_face = false; @@ -235,6 +237,29 @@ struct mat3 { } }; +struct mat4 { + float m[4][4]; + mat4() { + m[0][0] = 1.0f; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + m[1][0] = 0.0f; + m[1][1] = 1.0f; + m[1][2] = 0.0f; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; + m[2][3] = 0.0f; + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + m[3][3] = 1.0f; + } +}; + + void matmul3x3(const mat3 &a, const mat3 &b, mat3 &dst) { for (size_t i = 0; i < 3; i++) { for (size_t j = 0; j < 3; j++) { @@ -247,6 +272,18 @@ void matmul3x3(const mat3 &a, const mat3 &b, mat3 &dst) { } } +void matmul4x4(const mat4 &a, const mat4 &b, mat4 &dst) { + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + float v = 0.0f; + for (size_t k = 0; k < 4; k++) { + v += a.m[i][k] * b.m[k][j]; + } + dst.m[i][j] = v; + } + } +} + void normalizeVector(vec3 &v) { float len2 = v.v[0] * v.v[0] + v.v[1] * v.v[1] + v.v[2] * v.v[2]; if (len2 > 0.0f) { @@ -258,12 +295,13 @@ void normalizeVector(vec3 &v) { } } -// Maya-like turntable +// Maya-like turntable // Reference: // https://gamedev.stackexchange.com/questions/204367/implementing-a-maya-like-orbit-camera-in-vulkan-opengl // // angleX, angleY = angle in degree. -static void turntable(float angleX, float angleY, float center[3]) { +// TODO: scale +static void turntable(float angleX, float angleY, float center[3], float dst[4][4]) { float pivot[3]; pivot[0] = center[0]; pivot[1] = center[1]; @@ -299,7 +337,7 @@ static void turntable(float angleX, float angleY, float center[3]) { rotX.m[2][1] = -sinX; rotX.m[2][2] = cosX; - + } @@ -1138,15 +1176,25 @@ int main(int argc, char** argv) { GLfloat mat[4][4]; gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]); + + float center[3]; + center[0] = 0.5 * (bmax[0] + bmin[0]); + center[1] = 0.5 * (bmax[1] + bmin[1]); + center[2] = 0.5 * (bmax[2] + bmin[2]); + float rotm[4][4]; + turntable(g_angleX, g_angleY, center, rotm); + build_rotmatrix(mat, curr_quat); glMultMatrixf(&mat[0][0]); // Fit to -1, 1 glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent); +#if 0 // Centerize object. glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]), -0.5 * (bmax[2] + bmin[2])); +#endif Draw(gDrawObjects, materials, textures); diff --git a/fuzzer/runner.py b/fuzzer/runner.py index 0c06d4ba..a647d3ce 100644 --- a/fuzzer/runner.py +++ b/fuzzer/runner.py @@ -2,10 +2,11 @@ import glob import subprocess + def main(): for g in glob.glob("../tests/afl/id*"): print(g) - + cmd = ["../a.out", g] proc = subprocess.Popen(cmd) diff --git a/models/cube_w_BOM.mtl b/models/cube_w_BOM.mtl new file mode 100644 index 00000000..96255b54 --- /dev/null +++ b/models/cube_w_BOM.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/cube_w_BOM.obj b/models/cube_w_BOM.obj new file mode 100644 index 00000000..3c395f04 --- /dev/null +++ b/models/cube_w_BOM.obj @@ -0,0 +1,32 @@ +mtllib cube_w_BOM.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +# two white spaces between 'back' and 'cube' +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/issue-389-comment.obj b/models/issue-389-comment.obj new file mode 100644 index 00000000..cf16d926 --- /dev/null +++ b/models/issue-389-comment.obj @@ -0,0 +1,44 @@ +g Part 1 +v 0.0576127 0.0488792 0.0423 +v 0.0576127 0.0488792 0 +v -0.0483158 0.0488792 0 +v -0.0483158 0.0488792 0.0423 +v -0.0483158 -0.0139454 0 +v -0.0483158 -0.0139454 0.0423 +v 0.0576127 -0.0139454 0 +v 0.0576127 -0.0139454 0.0423 +vn 0 1 0 +vn -1 0 0 +vn 0 -1 0 +vn 1 0 0 +vn 0 0 1 +vn 0 0 -1 +o mesh0 +f 1//1 2//1 3//1 +f 3//1 4//1 1//1 +o mesh1 +f 4//2 3//2 5//2 +f 5//2 6//2 4//2 +o mesh2 +f 6//3 5//3 7//3 +f 7//3 8//3 6//3 +o mesh3 +f 8//4 7//4 2//4 +f 2//4 1//4 8//4 +o mesh4 +f 8//5 1//5 4//5 +f 4//5 6//5 8//5 +o mesh5 +f 5//6 3//6 2//6 +f 2//6 7//6 5//6 + +# Zusätzliche Linien (aus der Oberseite) +o lines +v 0.0576127 0.0488792 0.0423 # Startpunkt Linie 1 (Ecke 1 Oberseite) +v 0.0576127 0.0488792 0.2423 # Endpunkt Linie 1 (2m Höhe) +v -0.0483158 -0.0139454 0.0423 # Startpunkt Linie 2 (Ecke 6 Oberseite) +v -0.0483158 -0.0139454 0.2423 # Endpunkt Linie 2 (2m Höhe) + +# Linien +l 1 9 # Linie 1 +l 6 10 # Linie 2 diff --git a/models/issue-391.mtl b/models/issue-391.mtl new file mode 100644 index 00000000..c23ced4b --- /dev/null +++ b/models/issue-391.mtl @@ -0,0 +1,4 @@ +newmtl has_kd +Kd 1 0 0 +newmtl has_map +map_Kd test.png \ No newline at end of file diff --git a/models/issue-391.obj b/models/issue-391.obj new file mode 100644 index 00000000..06d8774b --- /dev/null +++ b/models/issue-391.obj @@ -0,0 +1,9 @@ +mtllib issue-391.mtl +v 0 0 0 +v 1 0 0 +v 0 1 0 +vn 0 0 1 +usemtl has_map +f 1//1 2//1 3//1 +usemtl has_kd +f 1//1 2//1 3//1 \ No newline at end of file diff --git a/models/issue-400-num-face-vertices.obj b/models/issue-400-num-face-vertices.obj new file mode 100644 index 00000000..77e25b48 --- /dev/null +++ b/models/issue-400-num-face-vertices.obj @@ -0,0 +1,15 @@ +# Regression test model for issue #400 - numpy_num_face_vertices() +# Mixed quad and triangle faces to verify correct uint type handling. +# With the bug (unsigned char instead of unsigned int in the numpy binding), +# numpy_num_face_vertices() returned all zeros for quad (4-vertex) faces. +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 1.0 1.0 0.0 +v 0.0 1.0 0.0 +v 0.5 0.5 1.0 +# quad face (num_face_vertices = 4) +f 1 2 3 4 +# triangle face (num_face_vertices = 3) +f 1 2 5 +# triangle face (num_face_vertices = 3) +f 2 3 5 diff --git a/models/numeric-edge-cases.obj b/models/numeric-edge-cases.obj new file mode 100644 index 00000000..71d8c156 --- /dev/null +++ b/models/numeric-edge-cases.obj @@ -0,0 +1,64 @@ +# Numeric edge cases for fast_float migration testing +# Each vertex exercises a different parsing path. + +# v0: basic integers and zero +v 0 0 0 + +# v1: simple decimals +v 1.5 -2.25 3.125 + +# v2: leading decimal dot (no integer part) +v .5 -.75 .001 + +# v3: trailing dot (no fractional part) +v 1. -2. 100. + +# v4: scientific notation (lowercase e) +v 1.5e2 -3.0e-4 7e10 + +# v5: scientific notation (uppercase E) +v 2.5E3 -1.0E-2 4E+5 + +# v6: leading + sign +v +1.0 +0.5 +100 + +# v7: leading zeros +v 007.5 -003.14 000.001 + +# v8: very small subnormal-range value +v 1e-300 -1e-300 5e-310 + +# v9: very large value near overflow +v 1.7976931348623157e+308 -1e+308 1e+307 + +# v10: negative zero +v -0 -0.0 -0.0e0 + +# v11: exponent with leading zeros +v 1.5e002 -3.0e+007 7e-003 + +# v12: single digit values +v 0 1 9 + +# v13: mixed sign exponents +v 1e+0 1e-0 -1e+0 + +# v14: max precision decimal (many digits) +v 3.141592653589793 2.718281828459045 1.4142135623730951 + +# v15: one as exponent boundary +v 1e1 1e-1 -1e1 + +# Normals to test normal parsing path too +vn 0.0 1.0 0.0 +vn -0.707107 0.0 0.707107 +vn 1e-5 -1e-5 0.99999 + +# Texture coords with edge values +vt 0.0 0.0 +vt 1.0 1.0 +vt 0.5 .5 +vt +0.25 +0.75 + +f 1//1 2//1 3//1 +f 4//2 5//2 6//2 diff --git a/models/texcoord-w-mixed.obj b/models/texcoord-w-mixed.obj new file mode 100644 index 00000000..16421ae5 --- /dev/null +++ b/models/texcoord-w-mixed.obj @@ -0,0 +1,15 @@ +# OBJ file with mixed 3-component and 2-component texture coordinates +# Tests that texcoord_ws correctly stores 0.0 for omitted w values +v 0 0 0 +v 1 0 0 +v 1 1 0 +v 0 1 0 + +# vt lines alternating: w present, w omitted, w present, w omitted +vt 0.0 0.0 0.5 +vt 1.0 0.0 +vt 1.0 1.0 0.75 +vt 0.0 1.0 + +f 1/1 2/2 3/3 +f 1/1 3/3 4/4 diff --git a/models/texcoord-w.obj b/models/texcoord-w.obj new file mode 100644 index 00000000..019ea7ae --- /dev/null +++ b/models/texcoord-w.obj @@ -0,0 +1,14 @@ +# OBJ file with 3-component texture coordinates to test texcoord_ws parsing +v 0 0 0 +v 1 0 0 +v 1 1 0 +v 0 1 0 + +# texture coords with optional w component +vt 0.0 0.0 0.5 +vt 1.0 0.0 0.25 +vt 1.0 1.0 0.75 +vt 0.0 1.0 0.0 + +f 1/1 2/2 3/3 +f 1/1 3/3 4/4 diff --git a/models/utf8-path-test.mtl b/models/utf8-path-test.mtl new file mode 100644 index 00000000..b89434a9 --- /dev/null +++ b/models/utf8-path-test.mtl @@ -0,0 +1,4 @@ +newmtl Material +Ka 0 0 0 +Kd 0.8 0.8 0.8 +Ks 0 0 0 diff --git a/models/utf8-path-test.obj b/models/utf8-path-test.obj new file mode 100644 index 00000000..a70ea094 --- /dev/null +++ b/models/utf8-path-test.obj @@ -0,0 +1,6 @@ +mtllib utf8-path-test.mtl +v 0.0 0.0 0.0 +v 1.0 0.0 0.0 +v 0.0 1.0 0.0 +usemtl Material +f 1 2 3 diff --git a/pyproject.toml b/pyproject.toml index d3ba7cf3..bcf10b1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,21 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 140 +force-exclude = ''' +( + /deps/.*$ + | /kuroga.py$ + | /config-msvc.py$ + | /config-posix.py$ + | ^python/build/.*$ + | ^python/dist/.*$ + | ^python/tinyobjloader.egg-info/.*$ +) +''' [project] name = "tinyobjloader" +license = { text = "MIT AND ISC" } # version: Use setuptools_scm dynamic = ["version", "classifiers", "authors", "description"] @@ -37,3 +49,7 @@ readme = {file = "README.md", content-type = "text/markdown"} # setuptools_scm<8 write_to = "python/_version.py" + +[tool.cibuildwheel] +# Disable aarch64 build since it's too slow to build(docker + qemu). +skip = ["cp38-*", "cp314t-*", "*_aarch64*"] diff --git a/python/bindings.cc b/python/bindings.cc index e7e6c951..a303e01f 100644 --- a/python/bindings.cc +++ b/python/bindings.cc @@ -149,9 +149,10 @@ PYBIND11_MODULE(tinyobjloader, tobj_module) .def(py::init<>()) .def_readonly("num_face_vertices", &mesh_t::num_face_vertices) .def("numpy_num_face_vertices", [] (mesh_t &instance) { - auto ret = py::array_t(instance.num_face_vertices.size()); + using T = typename std::remove_reference::type::value_type; + auto ret = py::array_t(instance.num_face_vertices.size()); py::buffer_info buf = ret.request(); - memcpy(buf.ptr, instance.num_face_vertices.data(), instance.num_face_vertices.size() * sizeof(unsigned char)); + memcpy(buf.ptr, instance.num_face_vertices.data(), instance.num_face_vertices.size() * sizeof(T)); return ret; }) .def("vertex_indices", [](mesh_t &self) { diff --git a/sandbox/parse_fp/CMakeLists.txt b/sandbox/parse_fp/CMakeLists.txt new file mode 100644 index 00000000..c0c9df9e --- /dev/null +++ b/sandbox/parse_fp/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.0) +project(parse_fp_test CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_executable(test_parse_fp test_parse_fp.cc) + +# Optional: enable optimizations for benchmark mode +if(CMAKE_BUILD_TYPE STREQUAL "Release") + if(MSVC) + target_compile_options(test_parse_fp PRIVATE /O2) + else() + target_compile_options(test_parse_fp PRIVATE -O2) + endif() +endif() diff --git a/sandbox/parse_fp/LICENSE-APACHE b/sandbox/parse_fp/LICENSE-APACHE new file mode 100644 index 00000000..26f4398f --- /dev/null +++ b/sandbox/parse_fp/LICENSE-APACHE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021 The fast_float authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sandbox/parse_fp/LICENSE-BOOST b/sandbox/parse_fp/LICENSE-BOOST new file mode 100644 index 00000000..127a5bc3 --- /dev/null +++ b/sandbox/parse_fp/LICENSE-BOOST @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/sandbox/parse_fp/LICENSE-MIT b/sandbox/parse_fp/LICENSE-MIT new file mode 100644 index 00000000..2fb2a37a --- /dev/null +++ b/sandbox/parse_fp/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2021 The fast_float authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/sandbox/parse_fp/fast_float.h b/sandbox/parse_fp/fast_float.h new file mode 100644 index 00000000..cb044c28 --- /dev/null +++ b/sandbox/parse_fp/fast_float.h @@ -0,0 +1,4443 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// with contributions from Lénárd Szolnoki +// with contributions from Jan Pharago +// with contributions from Maya Warrier +// with contributions from Taha Khokhar +// with contributions from Anders Dalvander +// +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License or the Boost License. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// +// BOOST License Notice +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +// Testing for https://wg21.link/N3652, adopted in C++14 +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 +#define FASTFLOAT_CONSTEXPR14 constexpr +#else +#define FASTFLOAT_CONSTEXPR14 +#endif + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +#define FASTFLOAT_HAS_BIT_CAST 1 +#else +#define FASTFLOAT_HAS_BIT_CAST 0 +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && \ + __cpp_lib_is_constant_evaluated >= 201811L +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#else +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#endif + +#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L +#define FASTFLOAT_IF_CONSTEXPR17(x) if constexpr (x) +#else +#define FASTFLOAT_IF_CONSTEXPR17(x) if (x) +#endif + +// Testing for relevant C++20 constexpr library features +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ + defined(__cpp_lib_constexpr_algorithms) && \ + __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ +#define FASTFLOAT_CONSTEXPR20 constexpr +#define FASTFLOAT_IS_CONSTEXPR 1 +#else +#define FASTFLOAT_CONSTEXPR20 +#define FASTFLOAT_IS_CONSTEXPR 0 +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 +#else +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 +#endif + +#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#ifdef __has_include +#if __has_include() && (__cplusplus > 202002L || (defined(_MSVC_LANG) && (_MSVC_LANG > 202002L))) +#include +#endif +#endif + +#define FASTFLOAT_VERSION_MAJOR 8 +#define FASTFLOAT_VERSION_MINOR 0 +#define FASTFLOAT_VERSION_PATCH 2 + +#define FASTFLOAT_STRINGIZE_IMPL(x) #x +#define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) + +#define FASTFLOAT_VERSION_STR \ + FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MAJOR) \ + "." FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MINOR) "." FASTFLOAT_STRINGIZE( \ + FASTFLOAT_VERSION_PATCH) + +#define FASTFLOAT_VERSION \ + (FASTFLOAT_VERSION_MAJOR * 10000 + FASTFLOAT_VERSION_MINOR * 100 + \ + FASTFLOAT_VERSION_PATCH) + +namespace fast_float { + +enum class chars_format : uint64_t; + +namespace detail { +constexpr chars_format basic_json_fmt = chars_format(1 << 5); +constexpr chars_format basic_fortran_fmt = chars_format(1 << 6); +} // namespace detail + +enum class chars_format : uint64_t { + scientific = 1 << 0, + fixed = 1 << 2, + hex = 1 << 3, + no_infnan = 1 << 4, + // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 + json = uint64_t(detail::basic_json_fmt) | fixed | scientific | no_infnan, + // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. + json_or_infnan = uint64_t(detail::basic_json_fmt) | fixed | scientific, + fortran = uint64_t(detail::basic_fortran_fmt) | fixed | scientific, + general = fixed | scientific, + allow_leading_plus = 1 << 7, + skip_white_space = 1 << 8, +}; + +template struct from_chars_result_t { + UC const *ptr; + std::errc ec; +}; + +using from_chars_result = from_chars_result_t; + +template struct parse_options_t { + constexpr explicit parse_options_t(chars_format fmt = chars_format::general, + UC dot = UC('.'), int b = 10) + : format(fmt), decimal_point(dot), base(b) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + UC decimal_point; + /** The base used for integers */ + int base; +}; + +using parse_options = parse_options_t; + +} // namespace fast_float + +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ + defined(__MINGW64__) || defined(__s390x__) || \ + (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ + defined(__PPC64LE__)) || \ + defined(__loongarch64)) +#define FASTFLOAT_64BIT 1 +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ + defined(__MINGW32__) || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_32BIT 1 +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. +// We can never tell the register width, but the SIZE_MAX is a good +// approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max +// portability. +#if SIZE_MAX == 0xffff +#error Unknown platform (16-bit, unsupported) +#elif SIZE_MAX == 0xffffffff +#define FASTFLOAT_32BIT 1 +#elif SIZE_MAX == 0xffffffffffffffff +#define FASTFLOAT_64BIT 1 +#else +#error Unknown platform (not 32-bit, not 64-bit?) +#endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ + (defined(_M_ARM64) && !defined(__MINGW32__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ + (defined(_M_AMD64) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP == 2))) +#define FASTFLOAT_SSE2 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +#define FASTFLOAT_NEON 1 +#endif + +#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) +#define FASTFLOAT_HAS_SIMD 1 +#endif + +#if defined(__GNUC__) +// disable -Wcast-align=strict (GCC only) +#define FASTFLOAT_SIMD_DISABLE_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-align\"") +#else +#define FASTFLOAT_SIMD_DISABLE_WARNINGS +#endif + +#if defined(__GNUC__) +#define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +#else +#define FASTFLOAT_SIMD_RESTORE_WARNINGS +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) \ + { ((void)(x)); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#define FASTFLOAT_DEBUG_ASSERT(x) \ + { ((void)(x)); } +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) \ + { \ + if (!(x)) \ + return false; \ + } + +#define FASTFLOAT_ENABLE_IF(...) \ + typename std::enable_if<(__VA_ARGS__), int>::type + +namespace fast_float { + +fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +template +struct is_supported_float_type + : std::integral_constant< + bool, std::is_same::value || std::is_same::value +#ifdef __STDCPP_FLOAT64_T__ + || std::is_same::value +#endif +#ifdef __STDCPP_FLOAT32_T__ + || std::is_same::value +#endif +#ifdef __STDCPP_FLOAT16_T__ + || std::is_same::value +#endif +#ifdef __STDCPP_BFLOAT16_T__ + || std::is_same::value +#endif + > { +}; + +template +using equiv_uint_t = typename std::conditional< + sizeof(T) == 1, uint8_t, + typename std::conditional< + sizeof(T) == 2, uint16_t, + typename std::conditional::type>::type>::type; + +template struct is_supported_integer_type : std::is_integral {}; + +template +struct is_supported_char_type + : std::integral_constant::value || + std::is_same::value || + std::is_same::value || + std::is_same::value +#ifdef __cpp_char8_t + || std::is_same::value +#endif + > { +}; + +// Compares two ASCII strings in a case insensitive manner. +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, + size_t length) { + for (size_t i = 0; i < length; ++i) { + UC const actual = actual_mixedcase[i]; + if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) { + return false; + } + } + return true; +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template struct span { + T const *ptr; + size_t length; + + constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {} + + constexpr span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { return length; } + + FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + + constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + + constexpr value128() : low(0), high(0) {} +}; + +/* Helper C++14 constexpr generic implementation of leading_zeroes */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int +leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { + if (input_num & uint64_t(0xffffffff00000000)) { + input_num >>= 32; + last_bit |= 32; + } + if (input_num & uint64_t(0xffff0000)) { + input_num >>= 16; + last_bit |= 16; + } + if (input_num & uint64_t(0xff00)) { + input_num >>= 8; + last_bit |= 8; + } + if (input_num & uint64_t(0xf0)) { + input_num >>= 4; + last_bit |= 4; + } + if (input_num & uint64_t(0xc)) { + input_num >>= 2; + last_bit |= 2; + } + if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ + last_bit |= 1; + } + return 63 - last_bit; +} + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int +leading_zeroes(uint64_t input_num) { + assert(input_num > 0); + if (cpp20_and_in_constexpr()) { + return leading_zeroes_generic(input_num); + } +#ifdef FASTFLOAT_VISUAL_STUDIO +#if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); +#else + return leading_zeroes_generic(input_num); +#endif +#else + return __builtin_clzll(input_num); +#endif +} + +// slow emulation routine for 32-bit +fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = (uint64_t)(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + (uint64_t)(lo < bd); + return lo; +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, + uint64_t cd, + uint64_t *hi) { + return umul128_generic(ab, cd, hi); +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + +// compute 64-bit a*b +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +full_multiplication(uint64_t a, uint64_t b) { + if (cpp20_and_in_constexpr()) { + value128 answer; + answer.low = umul128_generic(a, b, &answer.high); + return answer; + } + value128 answer; +#if defined(_M_ARM64) && !defined(__MINGW32__) + // ARM64 has native support for 64-bit multiplications, no need to emulate + // But MinGW on ARM64 doesn't have native support for 64-bit multiplications + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || \ + (defined(_WIN64) && !defined(__clang__) && !defined(_M_ARM64)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + answer.low = umul128_generic(a, b, &answer.high); +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + + constexpr bool operator==(adjusted_mantissa const &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + + constexpr bool operator!=(adjusted_mantissa const &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +// used for binary_format_lookup_tables::max_mantissa +constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; + +template struct binary_format_lookup_tables; + +template struct binary_format : binary_format_lookup_tables { + using equiv_uint = equiv_uint_t; + + static constexpr int mantissa_explicit_bits(); + static constexpr int minimum_exponent(); + static constexpr int infinite_power(); + static constexpr int sign_index(); + static constexpr int + min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST + static constexpr int max_exponent_fast_path(); + static constexpr int max_exponent_round_to_even(); + static constexpr int min_exponent_round_to_even(); + static constexpr uint64_t max_mantissa_fast_path(int64_t power); + static constexpr uint64_t + max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST + static constexpr int largest_power_of_ten(); + static constexpr int smallest_power_of_ten(); + static constexpr T exact_power_of_ten(int64_t power); + static constexpr size_t max_digits(); + static constexpr equiv_uint exponent_mask(); + static constexpr equiv_uint mantissa_mask(); + static constexpr equiv_uint hidden_bit_mask(); +}; + +template struct binary_format_lookup_tables { + static constexpr double powers_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Largest integer value v so that (5**index * v) <= 1<<53. + // 0x20000000000000 == 1 << 53 + static constexpr uint64_t max_mantissa[] = { + 0x20000000000000, + 0x20000000000000 / 5, + 0x20000000000000 / (5 * 5), + 0x20000000000000 / (5 * 5 * 5), + 0x20000000000000 / (5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555), + 0x20000000000000 / (constant_55555 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr double binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template struct binary_format_lookup_tables { + static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, + 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; + + // Largest integer value v so that (5**index * v) <= 1<<24. + // 0x1000000 == 1<<24 + static constexpr uint64_t max_mantissa[] = { + 0x1000000, + 0x1000000 / 5, + 0x1000000 / (5 * 5), + 0x1000000 / (5 * 5 * 5), + 0x1000000 / (5 * 5 * 5 * 5), + 0x1000000 / (constant_55555), + 0x1000000 / (constant_55555 * 5), + 0x1000000 / (constant_55555 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * constant_55555), + 0x1000000 / (constant_55555 * constant_55555 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr float binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 63; +} + +template <> inline constexpr int binary_format::sign_index() { + return 31; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +// credit: Jakub Jelínek +#ifdef __STDCPP_FLOAT16_T__ +template struct binary_format_lookup_tables { + static constexpr std::float16_t powers_of_ten[] = {1e0f16, 1e1f16, 1e2f16, + 1e3f16, 1e4f16}; + + // Largest integer value v so that (5**index * v) <= 1<<11. + // 0x800 == 1<<11 + static constexpr uint64_t max_mantissa[] = {0x800, + 0x800 / 5, + 0x800 / (5 * 5), + 0x800 / (5 * 5 * 5), + 0x800 / (5 * 5 * 5 * 5), + 0x800 / (constant_55555)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr std::float16_t + binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t + binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr std::float16_t +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7C00; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x03FF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0400; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 4; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 10; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 4 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { + return 0; +} + +template <> +inline constexpr int +binary_format::max_exponent_round_to_even() { + return 5; +} + +template <> +inline constexpr int +binary_format::min_exponent_round_to_even() { + return -22; +} + +template <> +inline constexpr int binary_format::minimum_exponent() { + return -15; +} + +template <> +inline constexpr int binary_format::infinite_power() { + return 0x1F; +} + +template <> inline constexpr int binary_format::sign_index() { + return 15; +} + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 4; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -27; +} + +template <> +inline constexpr size_t binary_format::max_digits() { + return 22; +} +#endif // __STDCPP_FLOAT16_T__ + +// credit: Jakub Jelínek +#ifdef __STDCPP_BFLOAT16_T__ +template struct binary_format_lookup_tables { + static constexpr std::bfloat16_t powers_of_ten[] = {1e0bf16, 1e1bf16, 1e2bf16, + 1e3bf16}; + + // Largest integer value v so that (5**index * v) <= 1<<8. + // 0x100 == 1<<8 + static constexpr uint64_t max_mantissa[] = {0x100, 0x100 / 5, 0x100 / (5 * 5), + 0x100 / (5 * 5 * 5), + 0x100 / (5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr std::bfloat16_t + binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t + binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr std::bfloat16_t +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 3; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F80; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007F; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0080; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 7; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 3 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { + return 0; +} + +template <> +inline constexpr int +binary_format::max_exponent_round_to_even() { + return 3; +} + +template <> +inline constexpr int +binary_format::min_exponent_round_to_even() { + return -24; +} + +template <> +inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> +inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 15; +} + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -60; +} + +template <> +inline constexpr size_t binary_format::max_digits() { + return 98; +} +#endif // __STDCPP_BFLOAT16_T__ + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 22 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 10 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr double +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} + +template <> inline constexpr int binary_format::smallest_power_of_ten() { + return -64; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F800000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007FFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x00800000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +to_float(bool negative, adjusted_mantissa am, T &value) { + using equiv_uint = equiv_uint_t; + equiv_uint word = equiv_uint(am.mantissa); + word = equiv_uint(word | equiv_uint(am.power2) + << binary_format::mantissa_explicit_bits()); + word = + equiv_uint(word | equiv_uint(negative) << binary_format::sign_index()); +#if FASTFLOAT_HAS_BIT_CAST + value = std::bit_cast(word); +#else + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +template struct space_lut { + static constexpr bool value[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr bool space_lut::value[]; + +#endif + +template constexpr bool is_space(UC c) { + return c < 256 && space_lut<>::value[uint8_t(c)]; +} + +template static constexpr uint64_t int_cmp_zeros() { + static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), + "Unsupported character size"); + return (sizeof(UC) == 1) ? 0x3030303030303030 + : (sizeof(UC) == 2) + ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | + uint64_t(UC('0')) << 16 | UC('0')) + : (uint64_t(UC('0')) << 32 | UC('0')); +} + +template static constexpr int int_cmp_len() { + return sizeof(uint64_t) / sizeof(UC); +} + +template constexpr UC const *str_const_nan(); + +template <> constexpr char const *str_const_nan() { return "nan"; } + +template <> constexpr wchar_t const *str_const_nan() { return L"nan"; } + +template <> constexpr char16_t const *str_const_nan() { + return u"nan"; +} + +template <> constexpr char32_t const *str_const_nan() { + return U"nan"; +} + +#ifdef __cpp_char8_t +template <> constexpr char8_t const *str_const_nan() { + return u8"nan"; +} +#endif + +template constexpr UC const *str_const_inf(); + +template <> constexpr char const *str_const_inf() { return "infinity"; } + +template <> constexpr wchar_t const *str_const_inf() { + return L"infinity"; +} + +template <> constexpr char16_t const *str_const_inf() { + return u"infinity"; +} + +template <> constexpr char32_t const *str_const_inf() { + return U"infinity"; +} + +#ifdef __cpp_char8_t +template <> constexpr char8_t const *str_const_inf() { + return u8"infinity"; +} +#endif + +template struct int_luts { + static constexpr uint8_t chdigit[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + + static constexpr size_t maxdigits_u64[] = { + 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, + 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; + + static constexpr uint64_t min_safe_u64[] = { + 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, + 7450580596923828125, 4738381338321616896, 3909821048582988049, + 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, + 5559917313492231481, 2218611106740436992, 8650415919381337933, + 2177953337809371136, 6568408355712890625, 1152921504606846976, + 2862423051509815793, 6746640616477458432, 15181127029874798299ull, + 1638400000000000000, 3243919932521508681, 6221821273427820544, + 11592836324538749809ull, 876488338465357824, 1490116119384765625, + 2481152873203736576, 4052555153018976267, 6502111422497947648, + 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, + 1152921504606846976, 1667889514952984961, 2386420683693101056, + 3379220508056640625, 4738381338321616896}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint8_t int_luts::chdigit[]; + +template constexpr size_t int_luts::maxdigits_u64[]; + +template constexpr uint64_t int_luts::min_safe_u64[]; + +#endif + +template +fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { + return int_luts<>::chdigit[static_cast(c)]; +} + +fastfloat_really_inline constexpr size_t max_digits_u64(int base) { + return int_luts<>::maxdigits_u64[base - 2]; +} + +// If a u64 is exactly max_digits_u64() in length, this is +// the value below which it has definitely overflowed. +fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { + return int_luts<>::min_safe_u64[base - 2]; +} + +static_assert(std::is_same, uint64_t>::value, + "equiv_uint should be uint64_t for double"); +static_assert(std::numeric_limits::is_iec559, + "double must fulfill the requirements of IEC 559 (IEEE 754)"); + +static_assert(std::is_same, uint32_t>::value, + "equiv_uint should be uint32_t for float"); +static_assert(std::numeric_limits::is_iec559, + "float must fulfill the requirements of IEC 559 (IEEE 754)"); + +#ifdef __STDCPP_FLOAT64_T__ +static_assert(std::is_same, uint64_t>::value, + "equiv_uint should be uint64_t for std::float64_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float64_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_FLOAT64_T__ + +#ifdef __STDCPP_FLOAT32_T__ +static_assert(std::is_same, uint32_t>::value, + "equiv_uint should be uint32_t for std::float32_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float32_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_FLOAT32_T__ + +#ifdef __STDCPP_FLOAT16_T__ +static_assert( + std::is_same::equiv_uint, uint16_t>::value, + "equiv_uint should be uint16_t for std::float16_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float16_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_FLOAT16_T__ + +#ifdef __STDCPP_BFLOAT16_T__ +static_assert( + std::is_same::equiv_uint, uint16_t>::value, + "equiv_uint should be uint16_t for std::bfloat16_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::bfloat16_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_BFLOAT16_T__ + +constexpr chars_format operator~(chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(~static_cast(rhs)); +} + +constexpr chars_format operator&(chars_format lhs, chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(static_cast(lhs) & + static_cast(rhs)); +} + +constexpr chars_format operator|(chars_format lhs, chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +constexpr chars_format operator^(chars_format lhs, chars_format rhs) noexcept { + using int_type = std::underlying_type::type; + return static_cast(static_cast(lhs) ^ + static_cast(rhs)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator&=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs & rhs); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator|=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs | rhs); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator^=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +namespace detail { +// adjust for deprecated feature macros +constexpr chars_format adjust_for_feature_macros(chars_format fmt) { + return fmt +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS + | chars_format::allow_leading_plus +#endif +#ifdef FASTFLOAT_SKIP_WHITE_SPACE + | chars_format::skip_white_space +#endif + ; +} +} // namespace detail + +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + + +namespace fast_float { +/** + * This function parses the character sequence [first,last) for a number. It + * parses floating-point numbers expecting a locale-indepent format equivalent + * to what is used by std::strtod in the default ("C") locale. The resulting + * floating-point value is the closest floating-point values (using either float + * or double), using the "round to even" convention for values that would + * otherwise fall right in-between two values. That is, we provide exact parsing + * according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to + * point right after the parsed number, and the `value` referenced is set to the + * parsed value. In case of error, the returned `ec` contains a representative + * error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with + * `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an + * optional last argument of the type `fast_float::chars_format`. It is a bitset + * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & + * fast_float::chars_format::scientific` are set to determine whether we allow + * the fixed point and scientific notation respectively. The default is + * `fast_float::chars_format::general` which allows both `fixed` and + * `scientific`. + */ +template ::value)> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + * Both for floating-point types and integer types. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept; + +/** + * from_chars for integer types. + */ +template ::value)> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; + +} // namespace fast_float + +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include +#include +#include + + +#ifdef FASTFLOAT_SSE2 +#include +#endif + +#ifdef FASTFLOAT_NEON +#include +#endif + +namespace fast_float { + +template fastfloat_really_inline constexpr bool has_simd_opt() { +#ifdef FASTFLOAT_HAS_SIMD + return std::is_same::value; +#else + return false; +#endif +} + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +template +fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { + return !(c > UC('9') || c < UC('0')); +} + +fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | + (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | + (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | + (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; +} + +// Read 8 UC into a u64. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +read8_to_u64(UC const *chars) { + if (cpp20_and_in_constexpr() || !std::is_same::value) { + uint64_t val = 0; + for (int i = 0; i < 8; ++i) { + val |= uint64_t(uint8_t(*chars)) << (i * 8); + ++chars; + } + return val; + } + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +#ifdef FASTFLOAT_SSE2 + +fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + __m128i const packed = _mm_packus_epi16(data, data); +#ifdef FASTFLOAT_64BIT + return uint64_t(_mm_cvtsi128_si64(packed)); +#else + uint64_t value; + // Visual Studio + older versions of GCC don't support _mm_storeu_si64 + _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); + return value; +#endif + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#elif defined(FASTFLOAT_NEON) + +fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint8x8_t utf8_packed = vmovn_u16(data); + return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + vld1q_u16(reinterpret_cast(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#endif // FASTFLOAT_SSE2 + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +uint64_t simd_read8_to_u64(UC const *) { + return 0; +} + +// credit @aqrit +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t +parse_eight_digits_unrolled(uint64_t val) { + uint64_t const mask = 0x000000FF000000FF; + uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +// Call this if chars are definitely 8 digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t +parse_eight_digits_unrolled(UC const *chars) noexcept { + if (cpp20_and_in_constexpr() || !has_simd_opt()) { + return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay + } + return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline constexpr bool +is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +#ifdef FASTFLOAT_HAS_SIMD + +// Call this if chars might not be 8 digits. +// Using this style (instead of is_made_of_eight_digits_fast() then +// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +simd_parse_if_eight_digits_unrolled(char16_t const *chars, + uint64_t &i) noexcept { + if (cpp20_and_in_constexpr()) { + return false; + } +#ifdef FASTFLOAT_SSE2 + FASTFLOAT_SIMD_DISABLE_WARNINGS + __m128i const data = + _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); + __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); + + if (_mm_movemask_epi8(t1) == 0) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#elif defined(FASTFLOAT_NEON) + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint16x8_t const data = vld1q_u16(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0')); + uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); + + if (vminvq_u16(mask) == 0xFFFF) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#else + (void)chars; + (void)i; + return false; +#endif // FASTFLOAT_SSE2 +} + +#endif // FASTFLOAT_HAS_SIMD + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { + return 0; +} + +template ::value) = 0> +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) { + if (!has_simd_opt()) { + return; + } + while ((std::distance(p, pend) >= 8) && + simd_parse_if_eight_digits_unrolled( + p, i)) { // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(char const *&p, char const *const pend, + uint64_t &i) { + // optimizes better than parse_if_eight_digits_unrolled() for UC = char. + while ((std::distance(p, pend) >= 8) && + is_made_of_eight_digits_fast(read8_to_u64(p))) { + i = i * 100000000 + + parse_eight_digits_unrolled(read8_to_u64( + p)); // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +enum class parse_error { + no_error, + // [JSON-only] The minus sign must be followed by an integer. + missing_integer_after_sign, + // A sign must be followed by an integer or dot. + missing_integer_or_dot_after_sign, + // [JSON-only] The integer part must not have leading zeros. + leading_zeros_in_integer_part, + // [JSON-only] The integer part must have at least one digit. + no_digits_in_integer_part, + // [JSON-only] If there is a decimal point, there must be digits in the + // fractional part. + no_digits_in_fractional_part, + // The mantissa must have at least one digit. + no_digits_in_mantissa, + // Scientific notation requires an exponential part. + missing_exponential_part, +}; + +template struct parsed_number_string_t { + int64_t exponent{0}; + uint64_t mantissa{0}; + UC const *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + span integer{}; // non-nullable + span fraction{}; // nullable + parse_error error{parse_error::no_error}; +}; + +using byte_span = span; +using parsed_number_string = parsed_number_string_t; + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +report_parse_error(UC const *p, parse_error error) { + parsed_number_string_t answer; + answer.valid = false; + answer.lastmatch = p; + answer.error = error; + return answer; +} + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string(UC const *p, UC const *pend, + parse_options_t options) noexcept { + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + UC const decimal_point = options.decimal_point; + + parsed_number_string_t answer; + answer.valid = false; + answer.too_many_digits = false; + // assume p < pend, so dereference without checks; + answer.negative = (*p == UC('-')); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && + !basic_json_fmt && *p == UC('+'))) { + ++p; + if (p == pend) { + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + if (!is_integer(*p)) { // a sign must be followed by an integer + return report_parse_error(p, + parse_error::missing_integer_after_sign); + } + } + else { + if (!is_integer(*p) && + (*p != + decimal_point)) { // a sign must be followed by an integer or the dot + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + } + } + UC const *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - + UC('0')); // might overflow, we will handle the overflow later + ++p; + } + UC const *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = span(start_digits, size_t(digit_count)); + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + // at least 1 digit in integer part, without leading zeros + if (digit_count == 0) { + return report_parse_error(p, parse_error::no_digits_in_integer_part); + } + if ((start_digits[0] == UC('0') && digit_count > 1)) { + return report_parse_error(start_digits, + parse_error::leading_zeros_in_integer_part); + } + } + + int64_t exponent = 0; + bool const has_decimal_point = (p != pend) && (*p == decimal_point); + if (has_decimal_point) { + ++p; + UC const *before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + loop_parse_if_eight_digits(p, pend, i); + + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = span(before, size_t(p - before)); + digit_count -= exponent; + } + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + // at least 1 digit in fractional part + if (has_decimal_point && exponent == 0) { + return report_parse_error(p, + parse_error::no_digits_in_fractional_part); + } + } + else if (digit_count == 0) { // we must have encountered at least one integer! + return report_parse_error(p, parse_error::no_digits_in_mantissa); + } + int64_t exp_number = 0; // explicit exponential part + if ((uint64_t(fmt & chars_format::scientific) && (p != pend) && + ((UC('e') == *p) || (UC('E') == *p))) || + (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) && + ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || + (UC('D') == *p)))) { + UC const *location_of_e = p; + if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || + (UC('D') == *p)) { + ++p; + } + bool neg_exp = false; + if ((p != pend) && (UC('-') == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && + (UC('+') == + *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if (!uint64_t(fmt & chars_format::fixed)) { + // The exponential part is invalid for scientific notation, so it must + // be a trailing token for fixed notation. However, fixed notation is + // disabled, so report a scientific notation error. + return report_parse_error(p, parse_error::missing_exponential_part); + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if (neg_exp) { + exp_number = -exp_number; + } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if (uint64_t(fmt & chars_format::scientific) && + !uint64_t(fmt & chars_format::fixed)) { + return report_parse_error(p, parse_error::missing_exponential_part); + } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + UC const *start = start_digits; + while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { + if (*start == UC('0')) { + digit_count--; + } + start++; + } + + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + UC const *int_end = p + answer.integer.len(); + uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + UC const *frac_end = p + answer.fraction.len(); + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +parse_int_string(UC const *p, UC const *pend, T &value, + parse_options_t options) { + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + int const base = options.base; + + from_chars_result_t answer; + + UC const *const first = p; + + bool const negative = (*p == UC('-')); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4127) +#endif + if (!std::is_signed::value && negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + if ((*p == UC('-')) || + (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) { + ++p; + } + + UC const *const start_num = p; + + while (p != pend && *p == UC('0')) { + ++p; + } + + bool const has_leading_zeros = p > start_num; + + UC const *const start_digits = p; + + uint64_t i = 0; + if (base == 10) { + loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible + } + while (p != pend) { + uint8_t digit = ch_to_digit(*p); + if (digit >= base) { + break; + } + i = uint64_t(base) * i + digit; // might overflow, check this later + p++; + } + + size_t digit_count = size_t(p - start_digits); + + if (digit_count == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = std::errc(); + answer.ptr = p; + } else { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + answer.ptr = p; + + // check u64 overflow + size_t max_digits = max_digits_u64(base); + if (digit_count > max_digits) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + // this check can be eliminated for all other types, but they will all require + // a max_digits(base) equivalent + if (digit_count == max_digits && i < min_safe_u64(base)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + + // check other types overflow + if (!std::is_same::value) { + if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { + answer.ec = std::errc::result_out_of_range; + return answer; + } + } + + if (negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4146) +#endif + // this weird workaround is required because: + // - converting unsigned to signed when its value is greater than signed max + // is UB pre-C++23. + // - reinterpret_casting (~i + 1) would work, but it is not constexpr + // this is always optimized into a neg instruction (note: T is an integer + // type) + value = T(-std::numeric_limits::max() - + T(i - uint64_t(std::numeric_limits::max()))); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + } else { + value = T(i); + } + + answer.ec = std::errc(); + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^-1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template struct powers_template { + + constexpr static int smallest_power_of_five = + binary_format::smallest_power_of_ten(); + constexpr static int largest_power_of_five = + binary_format::largest_power_of_ten(); + constexpr static int number_of_entries = + 2 * (largest_power_of_five - smallest_power_of_five + 1); + // Powers of five from 5^-342 all the way to 5^308 rounded toward one. + constexpr static uint64_t power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a, 0x113faa2906a13b3f, + 0x9558b4661b6565f8, 0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, + 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, + 0x91d8a02bb6c10594, 0x79071b9b8a4be869, + 0xb64ec836a47146f9, 0x9748e2826cdee284, + 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f, 0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723, 0xad2c788035e61382, + 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, + 0xa9c98d8ccb009506, 0x680efdaf511f18c2, + 0xd43bf0effdc0ba48, 0x212bd1b2566def2, + 0x84a57695fe98746d, 0x14bb630f7604b57, + 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, + 0xcf42894a5dce35ea, 0x52064cac828675b9, + 0x818995ce7aa0e1b2, 0x7343efebd1940993, + 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6, 0xd41a26e077774ef6, + 0xfd00b897478238d0, 0x8920b098955522b4, + 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, + 0xc5a890362fddbc62, 0xeb2189f734aa831d, + 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d, 0x47b233c92125366e, + 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, + 0xf148440a256e2c76, 0xc00670ea43ca250d, + 0x96cd2a865764dbca, 0x380406926a5e5728, + 0xbc807527ed3e12bc, 0xc605083704f5ecf2, + 0xeba09271e88d976b, 0xf7864a44c633682e, + 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c, 0x5960ea05bad82964, + 0xe61acf033d1a45df, 0x6fb92487298e33bd, + 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, + 0xe0b62e2929aba83c, 0x331acdabfe94de87, + 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, + 0x892731ac9faf056e, 0xbe311c083a225cd2, + 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, + 0xd64d3d9db981787d, 0x92cbbccdad5b108, + 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, + 0xa76c582338ed2621, 0xaf2af2b80af6f24e, + 0xd1476e2c07286faa, 0x1af5af660db4aee1, + 0x82cca4db847945ca, 0x50d98d9fc890ed4d, + 0xa37fce126597973c, 0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1, 0x77b191618c54e9ac, + 0xc795830d75038c1d, 0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, + 0x9becce62836ac577, 0x4ee367f9430aec32, + 0xc2e801fb244576d5, 0x229c41f793cda73f, + 0xf3a20279ed56d48a, 0x6b43527578c1110f, + 0x9845418c345644d6, 0x830a13896b78aaa9, + 0xbe5691ef416bd60c, 0x23cc986bc656d553, + 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, + 0x91376c36d99995be, 0x23100809b9c21fa1, + 0xb58547448ffffb2d, 0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, + 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, + 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d, 0x40405643d711d583, + 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, + 0xad1c8eab5ee43b66, 0xda3243650005eecf, + 0xd863b256369d4a40, 0x90bed43e40076a82, + 0x873e4f75e2224e68, 0x5a7744a6e804a291, + 0xa90de3535aaae202, 0x711515d0a205cb36, + 0xd3515c2831559a83, 0xd5a5b44ca873e03, + 0x8412d9991ed58091, 0xe858790afe9486c2, + 0xa5178fff668ae0b6, 0x626e974dbe39a872, + 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, + 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, + 0xc987434744ac874e, 0xa327ffb266b56220, + 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, + 0xc4ce17b399107c22, 0xcb550fb4384d21d3, + 0xf6019da07f549b2b, 0x7e2a53a146606a48, + 0x99c102844f94e0fb, 0x2eda7444cbfc426d, + 0xc0314325637a1939, 0xfa911155fefb5308, + 0xf03d93eebc589f88, 0x793555ab7eba27ca, + 0x96267c7535b763b5, 0x4bc1558b2f3458de, + 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb, 0x465e15a979c1cadc, + 0x92a1958a7675175f, 0xbfacd89ec191ec9, + 0xb749faed14125d36, 0xcef980ec671f667b, + 0xe51c79a85916f484, 0x82b7e12780e7401a, + 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9, 0x67a791e093e1d49a, + 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d, 0x58fae9f773886e18, + 0xda7f5bf590966848, 0xaf39a475506a899e, + 0x888f99797a5e012d, 0x6d8406c952429603, + 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, + 0xd0601d8efc57b08b, 0xf13b94daf124da26, + 0x823c12795db6ce57, 0x76c53d08d6b70858, + 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, + 0xf867241c8cc6d4c0, 0xc30163d203c94b62, + 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, + 0xc21094364dfb5636, 0x985915fc12f542e4, + 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, + 0xbd8430bd08277231, 0x50c6ff782a838353, + 0xece53cec4a314ebd, 0xa4f8bf5635246428, + 0x940f4613ae5ed136, 0x871b7795e136be99, + 0xb913179899f68584, 0x28e2557b59846e3f, + 0xe757dd7ec07426e5, 0x331aeada2fe589cf, + 0x9096ea6f3848984f, 0x3ff0d2c85def7621, + 0xb4bca50b065abe63, 0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, + 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, + 0xb080392cc4349dec, 0xbd8d794d96aacfb3, + 0xdca04777f541c567, 0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60, 0xf41686c49db57244, + 0xac5d37d5b79b6239, 0x311c2875c522ced5, + 0xd77485cb25823ac7, 0x7d633293366b828b, + 0x86a8d39ef77164bc, 0xae5dff9c02033197, + 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, + 0xd267caa862a12d66, 0xd072df63c324fd7b, + 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, + 0xa46116538d0deb78, 0x52d9be85f074e608, + 0xcd795be870516656, 0x67902e276c921f8b, + 0x806bd9714632dff6, 0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c, 0x796b805720085f81, + 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, + 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b, 0xee92fb5515482d44, + 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, + 0xef340a98172aace4, 0x86fb897116c87c34, + 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, + 0xbae0a846d2195712, 0x8974836059cca109, + 0xe998d258869facd7, 0x2bd1a438703fc94b, + 0x91ff83775423cc06, 0x7b6306a34627ddcf, + 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, + 0x8e938662882af53e, 0x547eb47b7282ee9c, + 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, + 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, + 0xae0b158b4738705e, 0x9624ab50b148d445, + 0xd98ddaee19068c76, 0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b, 0x7647c3200069671f, + 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, + 0xa5fb0a17c777cf09, 0xf468107100525890, + 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, + 0x81ac1fe293d599bf, 0xc6f14cd848405530, + 0xa21727db38cb002f, 0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, + 0xfd442e4688bd304a, 0x908f4a166d1da663, + 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, + 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, + 0xf7549530e188c128, 0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, + 0xc13a148e3032d6e7, 0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, + 0xebdf661791d60f56, 0x111b495b3464ad21, + 0x936b9fcebb25c995, 0xcab10dd900beec34, + 0xb84687c269ef3bfb, 0x3d5d514f40eea742, + 0xe65829b3046b0afa, 0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, + 0xb3f4e093db73a093, 0x59ed216765690f56, + 0xe0f218b8d25088b8, 0x306869c13ec3532c, + 0x8c974f7383725573, 0x1e414218c73a13fb, + 0xafbd2350644eeacf, 0xe5d1929ef90898fa, + 0xdbac6c247d62a583, 0xdf45f746b74abf39, + 0x894bc396ce5da772, 0x6b8bba8c328eb783, + 0xab9eb47c81f5114f, 0x66ea92f3f326564, + 0xd686619ba27255a2, 0xc80a537b0efefebd, + 0x8613fd0145877585, 0xbd06742ce95f5f36, + 0xa798fc4196e952e7, 0x2c48113823b73704, + 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, + 0x82ef85133de648c4, 0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3, 0x318df905079926a8, + 0xffbbcfe994e5c61f, 0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d, 0x6bea10ca65c084e, + 0xc31bfa0fe5698db8, 0x486e494fcff30a62, + 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, + 0xbe89523386091465, 0xf6bbb397f1135823, + 0xee2ba6c0678b597f, 0x746aa07ded582e2c, + 0x94db483840b717ef, 0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb, 0x92f34d62616ce413, + 0xe896a0d7e51e1566, 0x77b020baf9c81d17, + 0x915e2486ef32cd60, 0xace1474dc1d122e, + 0xb5b5ada8aaff80b8, 0xd819992132456ba, + 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, + 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, + 0xd89d64d57a607744, 0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b, 0x11471cd764ad4972, + 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, + 0xd389b47879823479, 0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb, 0xcedf722a585139ba, + 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, + 0xce947a3da6a9273e, 0x733d226229feea32, + 0x811ccc668829b887, 0x806357d5a3f525f, + 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, + 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, + 0xc5029163f384a931, 0xa9e795e65d4df11, + 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e, 0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, + 0xf07da27a82c37088, 0x5d767327bb4e5a4c, + 0x964e858c91ba2655, 0x3a6a07f8d510f86f, + 0xbbe226efb628afea, 0x890489f70a55368b, + 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, + 0xe55990879ddcaabd, 0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, + 0xb32df8e9f3546564, 0x47939822dc96abf9, + 0xdff9772470297ebd, 0x59787e2b93bc56f7, + 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, + 0xaefae51477a06b03, 0xede622920b6b23f1, + 0xdab99e59958885c4, 0xe95fab368e45eced, + 0x88b402f7fd75539b, 0x11dbcb0218ebb414, + 0xaae103b5fcd2a881, 0xd652bdc29f26a119, + 0xd59944a37c0752a2, 0x4be76d3346f0495f, + 0x857fcae62d8493a5, 0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, + 0x825ecc24c873782f, 0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b, 0x728900802f0f32fa, + 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc, 0xe2f610c84987bfa8, + 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143, 0x91503d1c79720dbb, + 0xf8a95fcf88747d94, 0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, + 0xc24452da229b021b, 0xfbe85badce996168, + 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, + 0x97c560ba6b0919a5, 0xdccd879fc967d41a, + 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, + 0xed246723473e3813, 0x290123e9aab23b68, + 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, + 0xb94470938fa89bce, 0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, + 0x8d590723948a535f, 0x579c487e5a38ad0e, + 0xb0af48ec79ace837, 0x2d835a9df0c6d851, + 0xdcdb1b2798182244, 0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, + 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, + 0xa87fea27a539e9a5, 0x3f2398d747b36224, + 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89, 0x1953cf68300424ac, + 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, + 0xcdb02555653131b6, 0x3792f412cb06794d, + 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b, 0xf245825a5a445275, + 0xfb158592be068d2e, 0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d, 0x55464dd69685606b, + 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, + 0xf53304714d9265df, 0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab, 0xe546a8038efe4029, + 0xbf8fdb78849a5f96, 0xde98520472bdd033, + 0xef73d256a5c0f77c, 0x963e66858f6d4440, + 0x95a8637627989aad, 0xdde7001379a44aa8, + 0xbb127c53b17ec159, 0x5560c018580d5d52, + 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, + 0x9226712162ab070d, 0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, + 0xb267ed1940f1c61c, 0x55f038b237591ed3, + 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, + 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, + 0xae397d8aa96c1b77, 0xabec975e0a0d081a, + 0xd9c7dced53c72255, 0x96e7bd358c904a21, + 0x881cea14545c7575, 0x7e50d64177da2e54, + 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, + 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba, 0x67de18eda5814af2, + 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, + 0xa2425ff75e14fc31, 0xa1258379a94d028d, + 0xcad2f7f5359a3b3e, 0x96ee45813a04330, + 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, + 0x9e74d1b791e07e48, 0x775ea264cf55347e, + 0xc612062576589dda, 0x95364afe032a819e, + 0xf79687aed3eec551, 0x3a83ddbd83f52205, + 0x9abe14cd44753b52, 0xc4926a9672793543, + 0xc16d9a0095928a27, 0x75b7053c0f178294, + 0xf1c90080baf72cb1, 0x5324c68b12dd6339, + 0x971da05074da7bee, 0xd3f6fc16ebca5e04, + 0xbce5086492111aea, 0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07, 0x3aff322e62439fd0, + 0xb877aa3236a4b449, 0x9befeb9fad487c3, + 0xe69594bec44de15b, 0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, + 0xb424dc35095cd80f, 0x538484c19ef38c95, + 0xe12e13424bb40e13, 0x2865a5f206b06fba, + 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, + 0xafebff0bcb24aafe, 0xf78f69a51539d749, + 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, + 0x89705f4136b4a597, 0x31680a88f8953031, + 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, + 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, + 0xa7c5ac471b478423, 0xfcf80dc33721d54, + 0xd1b71758e219652b, 0xd3c36113404ea4a9, + 0x83126e978d4fdf3b, 0x645a1cac083126ea, + 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, + 0xcccccccccccccccc, 0xcccccccccccccccd, + 0x8000000000000000, 0x0, + 0xa000000000000000, 0x0, + 0xc800000000000000, 0x0, + 0xfa00000000000000, 0x0, + 0x9c40000000000000, 0x0, + 0xc350000000000000, 0x0, + 0xf424000000000000, 0x0, + 0x9896800000000000, 0x0, + 0xbebc200000000000, 0x0, + 0xee6b280000000000, 0x0, + 0x9502f90000000000, 0x0, + 0xba43b74000000000, 0x0, + 0xe8d4a51000000000, 0x0, + 0x9184e72a00000000, 0x0, + 0xb5e620f480000000, 0x0, + 0xe35fa931a0000000, 0x0, + 0x8e1bc9bf04000000, 0x0, + 0xb1a2bc2ec5000000, 0x0, + 0xde0b6b3a76400000, 0x0, + 0x8ac7230489e80000, 0x0, + 0xad78ebc5ac620000, 0x0, + 0xd8d726b7177a8000, 0x0, + 0x878678326eac9000, 0x0, + 0xa968163f0a57b400, 0x0, + 0xd3c21bcecceda100, 0x0, + 0x84595161401484a0, 0x0, + 0xa56fa5b99019a5c8, 0x0, + 0xcecb8f27f4200f3a, 0x0, + 0x813f3978f8940984, 0x4000000000000000, + 0xa18f07d736b90be5, 0x5000000000000000, + 0xc9f2c9cd04674ede, 0xa400000000000000, + 0xfc6f7c4045812296, 0x4d00000000000000, + 0x9dc5ada82b70b59d, 0xf020000000000000, + 0xc5371912364ce305, 0x6c28000000000000, + 0xf684df56c3e01bc6, 0xc732000000000000, + 0x9a130b963a6c115c, 0x3c7f400000000000, + 0xc097ce7bc90715b3, 0x4b9f100000000000, + 0xf0bdc21abb48db20, 0x1e86d40000000000, + 0x96769950b50d88f4, 0x1314448000000000, + 0xbc143fa4e250eb31, 0x17d955a000000000, + 0xeb194f8e1ae525fd, 0x5dcfab0800000000, + 0x92efd1b8d0cf37be, 0x5aa1cae500000000, + 0xb7abc627050305ad, 0xf14a3d9e40000000, + 0xe596b7b0c643c719, 0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, + 0xb35dbf821ae4f38b, 0xdda2802c8a800000, + 0xe0352f62a19e306e, 0xd50b2037ad200000, + 0x8c213d9da502de45, 0x4526f422cc340000, + 0xaf298d050e4395d6, 0x9670b12b7f410000, + 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, + 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, + 0xab0e93b6efee0053, 0x8eea0d047a457a00, + 0xd5d238a4abe98068, 0x72a4904598d6d880, + 0x85a36366eb71f041, 0x47a6da2b7f864750, + 0xa70c3c40a64e6c51, 0x999090b65f67d924, + 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, + 0x82818f1281ed449f, 0xbff8f10e7a8921a4, + 0xa321f2d7226895c7, 0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, + 0xfee50b7025c36a08, 0x2f236d04753d5b4, + 0x9f4f2726179a2245, 0x1d762422c946590, + 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, + 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a, 0x8bef464e3945ef7a, + 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, + 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44, 0x60dbbca87196b616, + 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, + 0xb51d13aea4a488dd, 0x6babab6398bdbe41, + 0xe264589a4dcdab14, 0xc696963c7eed2dd1, + 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, + 0xdd15fe86affad912, 0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, + 0xacb92ed9397bf996, 0x49c2c37f07965404, + 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, + 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, + 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b, 0xf50a3fa490c30190, + 0x83c7088e1aab65db, 0x792667c6da79e0fa, + 0xa4b8cab1a1563f52, 0x577001b891185938, + 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, + 0x80b05e5ac60b6178, 0x544f8158315b05b4, + 0xa0dc75f1778e39d6, 0x696361ae3db1c721, + 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f, 0x4ab48a04065c723, + 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, + 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, + 0xf5746577930d6500, 0xca8f44ec7ee36479, + 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5, 0xbba1f1d158724a12, + 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, + 0xea1575143cf97226, 0xf52d09d71a3293bd, + 0x924d692ca61be758, 0x593c2626705f9c56, + 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, + 0xe498f455c38b997a, 0xb6dfb9c0f956447, + 0x8edf98b59a373fec, 0x4724bd4189bd5eac, + 0xb2977ee300c50fe7, 0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, + 0x8b865b215899f46c, 0xbd79e0d20082ee74, + 0xae67f1e9aec07187, 0xecd8590680a3aa11, + 0xda01ee641a708de9, 0xe80e6f4820cc9495, + 0x884134fe908658b2, 0x3109058d147fdcdd, + 0xaa51823e34a7eede, 0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, + 0x850fadc09923329e, 0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45, 0x84db8346b786151c, + 0xcfe87f7cef46ff16, 0xe612641865679a63, + 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749, 0xe3be5e330f38f09d, + 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, + 0xc646d63501a1511d, 0xb281e1fd541501b8, + 0xf7d88bc24209a565, 0x1f225a7ca91a4226, + 0x9ae757596946075f, 0x3375788de9b06958, + 0xc1a12d2fc3978937, 0x52d6b1641c83ae, + 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332, 0xf840b7ba963646e0, + 0xbd176620a501fbff, 0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf, 0xc66f336c36b10137, + 0xb8a8d9bbe123f017, 0xb80b0047445d4184, + 0xe6d3102ad96cec1d, 0xa60dc059157491e5, + 0x9043ea1ac7e41392, 0x87c89837ad68db2f, + 0xb454e4a179dd1877, 0x29babe4598c311fb, + 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, + 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d, 0x76707543f4fa1f73, + 0x899504ae72497eba, 0x6a06494a791c53a8, + 0xabfa45da0edbde69, 0x487db9d17636892, + 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, + 0xa7f26836f282b732, 0x8e6cac7768d7141e, + 0xd1ef0244af2364ff, 0x3207d795430cd926, + 0x8335616aed761f1f, 0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, + 0xcd036837130890a1, 0x36dba887c37a8c0f, + 0x802221226be55a64, 0xc2494954da2c9789, + 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d, 0x6f92829494e5acc7, + 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, + 0x9c69a97284b578d7, 0xff2a760414536efb, + 0xc38413cf25e2d70d, 0xfef5138519684aba, + 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, + 0x98bf2f79d5993802, 0xef2f773ffbd97a61, + 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2, 0xdd945a747bf26183, + 0xba756174393d88df, 0x94f971119aeef9e4, + 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, + 0x91abb422ccb812ee, 0xac62e055c10ab33a, + 0xb616a12b7fe617aa, 0x577b986b314d6009, + 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d, 0x14588f13be847307, + 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee, 0x25de7bb9480d5854, + 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, + 0xd910f7ff28069da4, 0x1b2ba1518094da04, + 0x87aa9aff79042286, 0x90fb44d2f05d0842, + 0xa99541bf57452b28, 0x353a1607ac744a53, + 0xd3fa922f2d1675f2, 0x42889b8997915ce8, + 0x847c9b5d7c2e09b7, 0x69956135febada11, + 0xa59bc234db398c25, 0x43fab9837e699095, + 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, + 0x8161afb94b44f57d, 0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc, 0x6462d92a69731732, + 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78, 0x5cda735244c3d43e, + 0x9defbf01b061adab, 0x3a0888136afa64a7, + 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b, 0x8aad549e57273d45, + 0x9a3c2087a63f6399, 0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, + 0x969eb7c47859e743, 0x9f644ae5a4b1b325, + 0xbc4665b596706114, 0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, + 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, + 0x8fa475791a569d10, 0xf96e017d694487bc, + 0xb38d92d760ec4455, 0x37c981dcc395a9ac, + 0xe070f78d3927556a, 0x85bbe253f47b1417, + 0x8c469ab843b89562, 0x93956d7478ccec8e, + 0xaf58416654a6babb, 0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, + 0x88fcf317f22241e2, 0x441fece3bdf81f03, + 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, + 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, + 0x85c7056562757456, 0xf6872d5667844e49, + 0xa738c6bebb12d16c, 0xb428f8ac016561db, + 0xd106f86e69d785c7, 0xe13336d701beba52, + 0x82a45b450226b39c, 0xecc0024661173473, + 0xa34d721642b06084, 0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, + 0xff290242c83396ce, 0x7e67047175a15271, + 0x9f79a169bd203e41, 0xf0062c6e984d386, + 0xc75809c42c684dd1, 0x52c07b78a3e60868, + 0xf92e0c3537826145, 0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb, 0x88a66076400bb691, + 0xc2abf989935ddbfe, 0x6acff893d00ea435, + 0xf356f7ebf83552fe, 0x583f6b8c4124d43, + 0x98165af37b2153de, 0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, + 0xeda2ee1c7064130c, 0x1162def06f79df73, + 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, + 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, + 0x8da471a9de737e24, 0x5ceaecfed289e5d2, + 0xb10d8e1456105dad, 0x7425a83e872c5f47, + 0xdd50f1996b947518, 0xd12f124e28f77719, + 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, + 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, + 0x8714a775e3e95c78, 0x65acfaec34810a71, + 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, + 0xd31045a8341ca07c, 0x1ede48111209a050, + 0x83ea2b892091e44d, 0x934aed0aab460432, + 0xa4e4b66b68b65d60, 0xf81da84d5617853f, + 0xce1de40642e3f4b9, 0x36251260ab9d668e, + 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, + 0xa1075a24e4421730, 0xb24cf65b8612f81f, + 0xc94930ae1d529cfc, 0xdee033f26797b627, + 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, + 0x9d412e0806e88aa5, 0x8e1f289560ee864e, + 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, + 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, + 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, + 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513, 0x84c86189216dc5ed, + 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515, 0xfabaf3feaa5334a, + 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8, 0x743e20e9ef511012, + 0xdf78e4b2bd342cf6, 0x914da9246b255416, + 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, + 0xae9672aba3d0c320, 0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, + 0x8865899617fb1871, 0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, + 0xd51ea6fa85785631, 0x552a74227f3ea565, + 0x8533285c936b35de, 0xd53a88958f87275f, + 0xa67ff273b8460356, 0x8a892abaf368f137, + 0xd01fef10a657842c, 0x2d2b7569b0432d85, + 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, + 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, + 0xcb3f2f7642717713, 0x241c70a936219a73, + 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, + 0x9ec95d1463e8a506, 0xf4363804324a40aa, + 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, + 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, + 0x976e41088617ca01, 0xd5be0503e085d813, + 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, + 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45, 0xcabb90e5c942b503, + 0xb8da1662e7b00a17, 0x3d6a751f3b936243, + 0xe7109bfba19c0c9d, 0xcc512670a783ad4, + 0x906a617d450187e2, 0x27fb2b80668b24c5, + 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, + 0xe1a63853bbd26451, 0x5e7873f8a0396973, + 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, + 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, + 0xac2820d9623bf429, 0x546345fa9fbdcd44, + 0xd732290fbacaf133, 0xa97c177947ad4095, + 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, + 0xa81f301449ee8c70, 0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c, 0x73832eec6fff3111, + 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, + 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, + 0xa0555e361951c366, 0xd7e105bcc332621f, + 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, + 0xfa856334878fc150, 0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07, 0xa862f80ec4700c8, + 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, + 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, + 0xeeea5d5004981478, 0x1858ccfce06cac74, + 0x95527a5202df0ccb, 0xf37801e0c43ebc8, + 0xbaa718e68396cffd, 0xd30560258f54e6ba, + 0xe950df20247c83fd, 0x47c6b82ef32a2069, + 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, + 0xb6472e511c81471d, 0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5, 0x58180fddd97723a6, + 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, + }; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr uint64_t + powers_template::power_of_five_128[number_of_entries]; + +#endif + +using powers = powers_template<>; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit +// words approximating the result, with the "high" part corresponding to the +// most significant bits and the low part corresponding to the least significant +// bits. +// +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +compute_product_approximation(int64_t q, uint64_t w) { + int const index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact + // because The line value128 firstproduct = full_multiplication(w, + // power_of_five_128[index]); gives the exact answer. + value128 firstproduct = + full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), + " precision should be in (0,64]"); + constexpr uint64_t precision_mask = + (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if ((firstproduct.high & precision_mask) == + precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our + // expectation is that the compiler will optimize this extra work away if + // needed. + value128 secondproduct = + full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if (secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ +constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; +} +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa +compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = + compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// Computers w * 10 ** q. +// The returned value should be a valid number that simply needs to be +// packed. However, in some very rare cases, the computation will fail. In such +// cases, we return an adjusted_mantissa with a negative power of 2: the caller +// should recompute in such cases. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, + // powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, + // requiring a shift) + + value128 product = + compute_product_approximation(q, w); + // The computed 'product' is always sufficient. + // Mathematical proof: + // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to + // appear) See script/mushtak_lemire.py + + // The "compute_product_approximation" function can be slightly slower than a + // branchless approach: value128 product = compute_product(q, w); but in + // practice, we can win big with the compute_product_approximation if its + // additional branch is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; + + answer.mantissa = product.high >> shift; + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - + binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if (-answer.power2 + 1 >= + 64) { // if we have more than 64 bits below the minimum exponent, you + // have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0 in the 32-bit and + // and 64-bit case (with no more than 19 digits). + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = + (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) + ? 0 + : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && + (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1)) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - + // binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go + // back!!! + if ((answer.mantissa << shift) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include +#include +#include +#include + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB 1 +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(stackvec const &) = delete; + stackvec &operator=(stackvec const &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + + FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + + // index from the end of the container + FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { + length = uint16_t(len); + } + + constexpr size_t len() const noexcept { return length; } + + constexpr bool is_empty() const noexcept { return length == 0; } + + constexpr size_t capacity() const noexcept { return size; } + + // append item to vector, without bounds checking + FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + + // append item to vector, returning if item was added + FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + + // add items to the vector, from a span, without bounds checking + FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { + limb *ptr = data + length; + std::copy_n(s.ptr, s.len(), ptr); + set_len(len() + s.len()); + } + + // try to add items to the vector, returning if items were added + FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + FASTFLOAT_CONSTEXPR20 + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb *first = data + len(); + limb *last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + + // try to resize the vector, returning if the vector was resized. + FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + + // normalize the big integer, so most-significant zero limbs are removed. + FASTFLOAT_CONSTEXPR14 void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +empty_hi64(bool &truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, bool &truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, bool &truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_add(limb x, limb y, bool &overflow) noexcept { + limb z; +// gcc and clang +#if defined(__has_builtin) +#if __has_builtin(__builtin_add_overflow) + if (!cpp20_and_in_constexpr()) { + overflow = __builtin_add_overflow(x, y, &z); + return z; + } +#endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_mul(limb x, limb y, limb &carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB +#if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; +#endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, + size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +small_add(stackvec &vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, + limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, + size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +large_add_from(stackvec &x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +template struct pow5_tables { + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, + 5UL, + 25UL, + 125UL, + 625UL, + 3125UL, + 15625UL, + 78125UL, + 390625UL, + 1953125UL, + 9765625UL, + 48828125UL, + 244140625UL, + 1220703125UL, + 6103515625UL, + 30517578125UL, + 152587890625UL, + 762939453125UL, + 3814697265625UL, + 19073486328125UL, + 95367431640625UL, + 476837158203125UL, + 2384185791015625UL, + 11920928955078125UL, + 59604644775390625UL, + 298023223876953125UL, + 1490116119384765625UL, + 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint32_t pow5_tables::large_step; + +template constexpr uint64_t pow5_tables::small_power_of_5[]; + +template constexpr limb pow5_tables::large_power_of_5[]; + +#endif + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint : pow5_tables<> { + // storage of the limbs, in little-endian order. + stackvec vec; + + FASTFLOAT_CONSTEXPR20 bigint() : vec() {} + + bigint(bigint const &) = delete; + bigint &operator=(bigint const &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = + uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb *dst = vec.data + n; + limb const *src = vec.data; + std::copy_backward(src, src + vec.len(), dst + vec.len()); + // fill in empty limbs + limb *first = vec.data; + limb *last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } + + FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } + + // multiply as if by 2 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } + + // multiply as if by 5 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + // This is similar to https://github.com/llvm/llvm-project/issues/47746, + // except the workaround described there don't work here + FASTFLOAT_TRY(small_mul( + vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include +#include +#include +#include + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = {1UL, + 10UL, + 100UL, + 1000UL, + 10000UL, + 100000UL, + 1000000UL, + 10000000UL, + 100000000UL, + 1000000000UL, + 10000000000UL, + 100000000000UL, + 1000000000000UL, + 10000000000000UL, + 100000000000000UL, + 1000000000000000UL, + 10000000000000000UL, + 100000000000000000UL, + 1000000000000000000UL, + 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t +scientific_exponent(parsed_number_string_t &num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended(T value) noexcept { + using equiv_uint = equiv_uint_t; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + equiv_uint bits; +#if FASTFLOAT_HAS_BIT_CAST + bits = std::bit_cast(value); +#else + ::memcpy(&bits, &value, sizeof(T)); +#endif + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> + binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, + callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < + (uint64_t(1) << binary_format::mantissa_explicit_bits())) + ? 0 + : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= + (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, + callback cb) noexcept { + uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; + uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); + uint64_t truncated_bits = am.mantissa & mask; + bool is_above = truncated_bits > halfway; + bool is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_down(adjusted_mantissa &am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +skip_zeros(UC const *&first, UC const *last) noexcept { + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + break; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(UC const *first, UC const *last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (!cpp20_and_in_constexpr() && + std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + return true; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + return true; + } + ++first; + } + return false; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +parse_eight_digits(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +parse_one_digit(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 10 + limb(*p - UC('0')); + p++; + counter++; + count++; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +add_native(bigint &big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +round_up_bigint(bigint &big, size_t &count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +template +inline FASTFLOAT_CONSTEXPR20 void +parse_mantissa(bigint &result, parsed_number_string_t &num, + size_t max_digits, size_t &digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + UC const *p = num.integer.ptr; + UC const *pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, + [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || + (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( + bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint &real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with + // -Wnoexcept-type. + round(am_b, + [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +#include +#include +#include +#include + +namespace fast_float { + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result_t + FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, + T &value, chars_format fmt) noexcept { + from_chars_result_t answer{}; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + // assume first < last, so dereference without checks; + bool const minusSign = (*first == UC('-')); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + if ((*first == UC('-')) || + (uint64_t(fmt & chars_format::allow_leading_plus) && + (*first == UC('+')))) { + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() + : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, + // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if (first != last && *first == UC('(')) { + for (UC const *ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == UC(')')) { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || + (UC('A') <= *ptr && *ptr <= UC('Z')) || + (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { + if ((last - first >= 8) && + fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +/** + * Returns true if the floating-pointing rounding mode is to 'nearest'. + * It is the default on most system. This function is meant to be inexpensive. + * Credit : @mwalcott3 + */ +fastfloat_really_inline bool rounds_to_nearest() noexcept { + // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return false; +#endif + // See + // A fast function to check your floating-point rounding mode + // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ + // + // This function is meant to be equivalent to : + // prior: #include + // return fegetround() == FE_TONEAREST; + // However, it is expected to be much faster than the fegetround() + // function call. + // + // The volatile keyword prevents the compiler from computing the function + // at compile-time. + // There might be other ways to prevent compile-time optimizations (e.g., + // asm). The value does not need to be std::numeric_limits::min(), any + // small value so that 1 + x should round to 1 would do (after accounting for + // excess precision, as in 387 instructions). + static float volatile fmin = std::numeric_limits::min(); + float fmini = fmin; // we copy it so that it gets loaded at most once. +// +// Explanation: +// Only when fegetround() == FE_TONEAREST do we have that +// fmin + 1.0f == 1.0f - fmin. +// +// FE_UPWARD: +// fmin + 1.0f > 1 +// 1.0f - fmin == 1 +// +// FE_DOWNWARD or FE_TOWARDZERO: +// fmin + 1.0f == 1 +// 1.0f - fmin < 1 +// +// Note: This may fail to be accurate if fast-math has been +// enabled, as rounding conventions may not apply. +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +// todo: is there a VS warning? +// see +// https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + return (fmini + 1.0f == 1.0f - fmini); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#elif defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +} // namespace detail + +template struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced(first, last, value, options); + } +}; + +#ifdef __STDCPP_FLOAT32_T__ +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float32_t &value, + parse_options_t options) noexcept { + // if std::float32_t is defined, and we are in C++23 mode; macro set for + // float32; set value to float due to equivalence between float and + // float32_t + float val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +#ifdef __STDCPP_FLOAT64_T__ +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float64_t &value, + parse_options_t options) noexcept { + // if std::float64_t is defined, and we are in C++23 mode; macro set for + // float64; set value as double due to equivalence between double and + // float64_t + double val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_caller::call(first, last, value, + parse_options_t(fmt)); +} + +/** + * This function overload takes parsed_number_string_t structure that is created + * and populated either by from_chars_advanced function taking chars range and + * parsing options or other parsing custom function implemented by user. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { + + static_assert(is_supported_float_type::value, + "only some floating-point types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; + + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // The implementation of the Clinger's fast path is convoluted because + // we want round-to-nearest in all cases, irrespective of the rounding mode + // selected on the thread. + // We proceed optimistically, assuming that detail::rounds_to_nearest() + // returns true. + if (binary_format::min_exponent_fast_path() <= pns.exponent && + pns.exponent <= binary_format::max_exponent_fast_path() && + !pns.too_many_digits) { + // Unfortunately, the conventional Clinger's fast path is only possible + // when the system rounds to the nearest float. + // + // We expect the next branch to almost always be selected. + // We could check it first (before the previous branch), but + // there might be performance advantages at having the check + // be last. + if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { + // We have that fegetround() == FE_TONEAREST. + // Next is Clinger's fast path. + if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { + value = T(pns.mantissa); + if (pns.exponent < 0) { + value = value / binary_format::exact_power_of_ten(-pns.exponent); + } else { + value = value * binary_format::exact_power_of_ten(pns.exponent); + } + if (pns.negative) { + value = -value; + } + return answer; + } + } else { + // We do not have that fegetround() == FE_TONEAREST. + // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's + // proposal + if (pns.exponent >= 0 && + pns.mantissa <= + binary_format::max_mantissa_fast_path(pns.exponent)) { +#if defined(__clang__) || defined(FASTFLOAT_32BIT) + // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if (pns.mantissa == 0) { + value = pns.negative ? T(-0.) : T(0.); + return answer; + } +#endif + value = T(pns.mantissa) * + binary_format::exact_power_of_ten(pns.exponent); + if (pns.negative) { + value = -value; + } + return answer; + } + } + } + adjusted_mantissa am = + compute_float>(pns.exponent, pns.mantissa); + if (pns.too_many_digits && am.power2 >= 0) { + if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) + // and we have an invalid power (am.power2 < 0), then we need to go the long + // way around again. This is very uncommon. + if (am.power2 < 0) { + am = digit_comp(pns, am); + } + to_float(pns.negative, am, value); + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || + am.power2 == binary_format::infinite_power()) { + answer.ec = std::errc::result_out_of_range; + } + return answer; +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_float_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_float_type::value, + "only some floating-point types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + + from_chars_result_t answer; + if (uint64_t(fmt & chars_format::skip_white_space)) { + while ((first != last) && fast_float::is_space(*first)) { + first++; + } + } + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string_t pns = + uint64_t(fmt & detail::basic_json_fmt) + ? parse_number_string(first, last, options) + : parse_number_string(first, last, options); + if (!pns.valid) { + if (uint64_t(fmt & chars_format::no_infnan)) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } else { + return detail::parse_infnan(first, last, value, fmt); + } + } + + // call overload that takes parsed_number_string_t directly. + return from_chars_advanced(pns, value); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base) noexcept { + + static_assert(is_supported_integer_type::value, + "only integer types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + parse_options_t options; + options.base = base; + return from_chars_advanced(first, last, value, options); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_int_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_integer_type::value, + "only integer types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + int const base = options.base; + + from_chars_result_t answer; + if (uint64_t(fmt & chars_format::skip_white_space)) { + while ((first != last) && fast_float::is_space(*first)) { + first++; + } + } + if (first == last || base < 2 || base > 36) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + + return parse_int_string(first, last, value, options); +} + +template struct from_chars_advanced_caller { + static_assert(TypeIx > 0, "unsupported type"); +}; + +template <> struct from_chars_advanced_caller<1> { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_float_advanced(first, last, value, options); + } +}; + +template <> struct from_chars_advanced_caller<2> { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_int_advanced(first, last, value, options); + } +}; + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced_caller< + size_t(is_supported_float_type::value) + + 2 * size_t(is_supported_integer_type::value)>::call(first, last, value, + options); +} + +} // namespace fast_float + +#endif + diff --git a/sandbox/parse_fp/obj_parse_float.h b/sandbox/parse_fp/obj_parse_float.h new file mode 100644 index 00000000..454faa78 --- /dev/null +++ b/sandbox/parse_fp/obj_parse_float.h @@ -0,0 +1,227 @@ +// obj_parse_float.h - OBJ-specific float parser built on fast_float. +// +// Handles OBJ quirks: +// - Leading '+' sign (e.g. "+1.0", "+0.5e+2") +// - nan / NaN / NAN (case-insensitive) +// - inf / Inf / INF / infinity (case-insensitive, with optional sign) +// +// Non-finite replacement (NumPy-style defaults): +// nan -> 0.0 +// inf -> std::numeric_limits::max() +// -inf -> std::numeric_limits::lowest() +// +// Usage: +// double val; +// const char *end; +// bool ok = obj::parseFloat(" +1.5e3 rest", &val, &end); +// // val == 1500.0, end points to ' ' before "rest" +// +// obj::ParseOptions opts; +// opts.nan_value = -1.0; // replace nan with -1 +// ok = obj::parseFloat("nan", &val, &end, opts); +// // val == -1.0 +// +// SPDX-License-Identifier: MIT OR Apache-2.0 OR BSL-1.0 + +#ifndef OBJ_PARSE_FLOAT_H_ +#define OBJ_PARSE_FLOAT_H_ + +#include "fast_float.h" + +#include +#include +#include + +namespace obj { + +template +struct ParseOptions { + T nan_value; + T inf_value; + T neg_inf_value; + + ParseOptions() + : nan_value(static_cast(0.0)), + inf_value((std::numeric_limits::max)()), + neg_inf_value(std::numeric_limits::lowest()) {} +}; + +namespace detail { + +// Case-insensitive prefix match. Returns pointer past matched prefix, or NULL. +inline const char *match_iprefix(const char *p, const char *end, + const char *prefix) { + while (*prefix) { + if (p == end) return NULL; + char c = *p; + char e = *prefix; + // ASCII tolower without locale + if (c >= 'A' && c <= 'Z') c += 32; + if (e >= 'A' && e <= 'Z') e += 32; + if (c != e) return NULL; + ++p; + ++prefix; + } + return p; +} + +// Check if character is a whitespace or end-of-token in OBJ context. +inline bool is_obj_delim(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0'; +} + +// Try to parse nan/inf. Returns true if matched, sets *result and *end_ptr. +template +inline bool tryParseNanInf(const char *first, const char *last, T *result, + const char **end_ptr, + const ParseOptions &opts) { + if (first >= last) return false; + + const char *p = first; + bool negative = false; + + // Optional sign + if (*p == '-') { + negative = true; + ++p; + } else if (*p == '+') { + ++p; + } + + if (p >= last) return false; + + // Try "nan" + const char *after = match_iprefix(p, last, "nan"); + if (after) { + *result = opts.nan_value; + *end_ptr = after; + return true; + } + + // Try "infinity" first (longer match), then "inf" + after = match_iprefix(p, last, "infinity"); + if (after) { + *result = negative ? opts.neg_inf_value : opts.inf_value; + *end_ptr = after; + return true; + } + + after = match_iprefix(p, last, "inf"); + if (after) { + *result = negative ? opts.neg_inf_value : opts.inf_value; + *end_ptr = after; + return true; + } + + return false; +} + +} // namespace detail + +// Parse a float/double from an OBJ token string. +// +// - Skips leading whitespace (space/tab). +// - Handles leading '+' (via allow_leading_plus format flag). +// - Handles nan/inf with replacement values. +// - Sets *end_ptr to the character after the parsed number. +// - Returns true on success. +template +inline bool parseFloat(const char *s, T *result, const char **end_ptr, + const ParseOptions &opts = ParseOptions()) { + // Skip leading whitespace to find the token start (needed for nan/inf + // detection and token_end computation below). + const char *p = s; + while (*p == ' ' || *p == '\t') ++p; + + if (*p == '\0') { + *end_ptr = p; + return false; + } + + // Check first significant char to decide path. + // nan/inf starts with [nNiI] or [+-] followed by [nNiI]. + const char *q = p; + if (*q == '+' || *q == '-') ++q; + char fc = *q; + // ASCII tolower + if (fc >= 'A' && fc <= 'Z') fc += 32; + + if (fc == 'n' || fc == 'i') { + // Potential nan/inf — find token end and try match. + const char *token_end = p; + while (*token_end && !detail::is_obj_delim(*token_end)) ++token_end; + if (p != token_end && + detail::tryParseNanInf(p, token_end, result, end_ptr, opts)) { + return true; + } + } + + // Fast path: numeric parse (most common case). + // Scan to the end of the numeric token (null or OBJ delimiter) so that + // fast_float never reads past the bounds of the current token/buffer. + // allow_leading_plus is a built-in fast_float flag that handles the '+' + // prefix without manual code. + const char *token_end = p; + while (*token_end && !detail::is_obj_delim(*token_end)) ++token_end; + + auto r = fast_float::from_chars( + p, token_end, *result, + fast_float::chars_format::general | + fast_float::chars_format::allow_leading_plus); + if (r.ec == std::errc()) { + *end_ptr = r.ptr; + return true; + } + + *end_ptr = s; + return false; +} + +// Convenience: parse from null-terminated string, no end_ptr needed. +template +inline bool parseFloat(const char *s, T *result, + const ParseOptions &opts = ParseOptions()) { + const char *end; + return parseFloat(s, result, &end, opts); +} + +// Parse with explicit [first, last) range (no whitespace skip, no null-term). +template +inline bool parseFloatRange(const char *first, const char *last, T *result, + const char **end_ptr, + const ParseOptions &opts = ParseOptions()) { + if (first >= last) { + *end_ptr = first; + return false; + } + + // Check first significant char for nan/inf. + const char *p = first; + if (p < last && (*p == '+' || *p == '-')) ++p; + if (p < last) { + char fc = *p; + if (fc >= 'A' && fc <= 'Z') fc += 32; + if (fc == 'n' || fc == 'i') { + if (detail::tryParseNanInf(first, last, result, end_ptr, opts)) { + return true; + } + } + } + + // Numeric parse: allow_leading_plus handles the '+' prefix natively so + // no manual advancement is needed. + auto r = fast_float::from_chars(first, last, *result, + fast_float::chars_format::general | + fast_float::chars_format::allow_leading_plus); + if (r.ec == std::errc()) { + *end_ptr = r.ptr; + return true; + } + + *end_ptr = first; + return false; +} + +} // namespace obj + +#endif // OBJ_PARSE_FLOAT_H_ diff --git a/sandbox/parse_fp/test_parse_fp.cc b/sandbox/parse_fp/test_parse_fp.cc new file mode 100644 index 00000000..76288dc8 --- /dev/null +++ b/sandbox/parse_fp/test_parse_fp.cc @@ -0,0 +1,679 @@ +// Standalone test and benchmark for fast_float + OBJ-specific float parser. +// Build: cmake -B build -DCMAKE_BUILD_TYPE=Release . && cmake --build build +// Run: ./build/test_parse_fp + +#include "obj_parse_float.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Reference: tinyobjloader's existing tryParseDouble (for comparison) +// --------------------------------------------------------------------------- +#define IS_DIGIT(x) (static_cast((x) - '0') < 10) + +static bool tryParseDouble_legacy(const char *s, const char *s_end, + double *result) { + if (s >= s_end) return false; + + double mantissa = 0.0; + int exponent = 0; + char sign = '+'; + char exp_sign = '+'; + const char *curr = s; + int read = 0; + bool end_not_reached = false; + bool leading_decimal_dots = false; + + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + if ((curr != s_end) && (*curr == '.')) { + leading_decimal_dots = true; + } + } else if (IS_DIGIT(*curr)) { + } else if (*curr == '.') { + leading_decimal_dots = true; + } else { + goto fail; + } + + end_not_reached = (curr != s_end); + if (!leading_decimal_dots) { + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + if (read == 0) goto fail; + } + + if (!end_not_reached) goto assemble; + + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + static const double pow_lut[] = { + 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, + }; + const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; + mantissa += static_cast(*curr - 0x30) * + (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + if (*curr == 'e' || *curr == 'E') { + curr++; + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { + } else { + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + if (exponent > (2147483647 / 10)) goto fail; + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble: + *result = + (sign == '+' ? 1 : -1) * + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) + : mantissa); + return true; +fail: + return false; +} + +#undef IS_DIGIT + +// --------------------------------------------------------------------------- +// Test helpers +// --------------------------------------------------------------------------- +static int g_failures = 0; + +#define CHECK(cond, ...) \ + do { \ + if (!(cond)) { \ + printf("FAIL %s:%d: ", __FILE__, __LINE__); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + g_failures++; \ + } \ + } while (0) + +// --------------------------------------------------------------------------- +// 1. Basic fast_float correctness +// --------------------------------------------------------------------------- +static int test_fast_float_basic() { + struct TestCase { + const char *input; + double expected; + bool should_succeed; + }; + + const TestCase cases[] = { + {"0", 0.0, true}, + {"1", 1.0, true}, + {"-1", -1.0, true}, + {"123", 123.0, true}, + {"-456", -456.0, true}, + {"3.14", 3.14, true}, + {"-2.718", -2.718, true}, + {"0.5", 0.5, true}, + {".5", 0.5, true}, + {"-.5", -0.5, true}, + {"1e10", 1e10, true}, + {"1E10", 1E10, true}, + {"1.5e3", 1.5e3, true}, + {"-2.5e-4", -2.5e-4, true}, + {"1e0", 1.0, true}, + {"1e+0", 1.0, true}, + {"1e-0", 1.0, true}, + {"1e308", 1e308, true}, + {"5e-324", 5e-324, true}, + {"1.7976931348623157e308", 1.7976931348623157e308, true}, + {"0.123456", 0.123456, true}, + {"-0.987654", -0.987654, true}, + {"1.000000", 1.0, true}, + {"0.000000", 0.0, true}, + }; + + int before = g_failures; + const int n = sizeof(cases) / sizeof(cases[0]); + + for (int i = 0; i < n; i++) { + const TestCase &tc = cases[i]; + double val = 0.0; + const char *first = tc.input; + const char *last = first + std::strlen(first); + auto result = fast_float::from_chars(first, last, val); + + bool ok = (result.ec == std::errc()); + CHECK(ok == tc.should_succeed, "[%d] \"%s\": expected %s, got %s", i, + tc.input, tc.should_succeed ? "success" : "failure", + ok ? "success" : "failure"); + + if (ok && tc.expected != 0.0) { + double rel_err = std::fabs((val - tc.expected) / tc.expected); + CHECK(rel_err <= 1e-15, "[%d] \"%s\": expected %.17g, got %.17g", i, + tc.input, tc.expected, val); + } else if (ok && tc.expected == 0.0) { + CHECK(val == 0.0, "[%d] \"%s\": expected 0.0, got %.17g", i, tc.input, + val); + } + } + + return g_failures - before; +} + +// --------------------------------------------------------------------------- +// 2. OBJ wrapper: leading '+' sign +// --------------------------------------------------------------------------- +static int test_leading_plus() { + int before = g_failures; + + struct TestCase { + const char *input; + double expected; + }; + + const TestCase cases[] = { + {"+1.0", 1.0}, {"+0", 0.0}, {"+0.5", 0.5}, + {"+123", 123.0}, {"+1e5", 1e5}, {"+.5", 0.5}, + {"+1.5e-3", 1.5e-3}, + }; + + const int n = sizeof(cases) / sizeof(cases[0]); + for (int i = 0; i < n; i++) { + double val = -999.0; + const char *end = NULL; + bool ok = obj::parseFloat(cases[i].input, &val, &end); + CHECK(ok, "leading+ [%d] \"%s\": parse failed", i, cases[i].input); + if (ok && cases[i].expected != 0.0) { + double rel_err = std::fabs((val - cases[i].expected) / cases[i].expected); + CHECK(rel_err <= 1e-15, "leading+ [%d] \"%s\": expected %.17g, got %.17g", + i, cases[i].input, cases[i].expected, val); + } else if (ok) { + CHECK(val == 0.0, "leading+ [%d] \"%s\": expected 0.0, got %.17g", i, + cases[i].input, val); + } + } + + return g_failures - before; +} + +// --------------------------------------------------------------------------- +// 3. nan/inf parsing with default replacements +// --------------------------------------------------------------------------- +static int test_nan_inf_defaults() { + int before = g_failures; + + double dmax = (std::numeric_limits::max)(); + double dmin = std::numeric_limits::lowest(); + + struct TestCase { + const char *input; + double expected; + }; + + const TestCase cases[] = { + // nan variants + {"nan", 0.0}, + {"NaN", 0.0}, + {"NAN", 0.0}, + {"+nan", 0.0}, + {"-nan", 0.0}, + + // inf variants + {"inf", dmax}, + {"Inf", dmax}, + {"INF", dmax}, + {"infinity", dmax}, + {"Infinity", dmax}, + {"INFINITY", dmax}, + {"+inf", dmax}, + {"+Inf", dmax}, + {"+infinity", dmax}, + + // -inf variants + {"-inf", dmin}, + {"-Inf", dmin}, + {"-INF", dmin}, + {"-infinity", dmin}, + {"-Infinity", dmin}, + {"-INFINITY", dmin}, + }; + + const int n = sizeof(cases) / sizeof(cases[0]); + for (int i = 0; i < n; i++) { + double val = -12345.0; + const char *end = NULL; + bool ok = obj::parseFloat(cases[i].input, &val, &end); + CHECK(ok, "nan_inf [%d] \"%s\": parse failed", i, cases[i].input); + CHECK(val == cases[i].expected, + "nan_inf [%d] \"%s\": expected %.17g, got %.17g", i, cases[i].input, + cases[i].expected, val); + } + + return g_failures - before; +} + +// --------------------------------------------------------------------------- +// 4. nan/inf with custom replacement values +// --------------------------------------------------------------------------- +static int test_nan_inf_custom() { + int before = g_failures; + + obj::ParseOptions opts; + opts.nan_value = -1.0; + opts.inf_value = 9999.0; + opts.neg_inf_value = -9999.0; + + { + double val = 0.0; + bool ok = obj::parseFloat("nan", &val, opts); + CHECK(ok && val == -1.0, "custom nan: expected -1.0, got %.17g", val); + } + { + double val = 0.0; + bool ok = obj::parseFloat("NaN", &val, opts); + CHECK(ok && val == -1.0, "custom NaN: expected -1.0, got %.17g", val); + } + { + double val = 0.0; + bool ok = obj::parseFloat("inf", &val, opts); + CHECK(ok && val == 9999.0, "custom inf: expected 9999.0, got %.17g", val); + } + { + double val = 0.0; + bool ok = obj::parseFloat("-inf", &val, opts); + CHECK(ok && val == -9999.0, "custom -inf: expected -9999.0, got %.17g", + val); + } + { + double val = 0.0; + bool ok = obj::parseFloat("infinity", &val, opts); + CHECK(ok && val == 9999.0, "custom infinity: expected 9999.0, got %.17g", + val); + } + { + double val = 0.0; + bool ok = obj::parseFloat("-infinity", &val, opts); + CHECK(ok && val == -9999.0, + "custom -infinity: expected -9999.0, got %.17g", val); + } + + // float version + obj::ParseOptions fopts; + fopts.nan_value = -2.0f; + fopts.inf_value = 1e30f; + fopts.neg_inf_value = -1e30f; + { + float fval = 0.0f; + bool ok = obj::parseFloat("nan", &fval, fopts); + CHECK(ok && fval == -2.0f, "custom float nan: expected -2.0, got %f", fval); + } + { + float fval = 0.0f; + bool ok = obj::parseFloat("inf", &fval, fopts); + CHECK(ok && fval == 1e30f, "custom float inf: expected 1e30, got %g", fval); + } + + return g_failures - before; +} + +// --------------------------------------------------------------------------- +// 5. Pointer advancement / whitespace handling +// --------------------------------------------------------------------------- +static int test_pointer_advance() { + int before = g_failures; + + // parseFloat skips leading whitespace + { + double val = 0.0; + const char *end = NULL; + bool ok = obj::parseFloat(" 1.5 rest", &val, &end); + CHECK(ok, "ws: parse failed"); + CHECK(val == 1.5, "ws: expected 1.5, got %.17g", val); + CHECK(end && *end == ' ', "ws: expected end at space before 'rest'"); + } + + // Tab whitespace + { + double val = 0.0; + const char *end = NULL; + bool ok = obj::parseFloat("\t3.14\tnext", &val, &end); + CHECK(ok, "tab ws: parse failed"); + CHECK(std::fabs(val - 3.14) < 1e-15, "tab ws: expected 3.14, got %.17g", + val); + CHECK(end && *end == '\t', "tab ws: expected end at tab"); + } + + // nan pointer advancement + { + double val = -1.0; + const char *end = NULL; + bool ok = obj::parseFloat("nan rest", &val, &end); + CHECK(ok, "nan ptr: parse failed"); + CHECK(val == 0.0, "nan ptr: expected 0.0, got %.17g", val); + CHECK(end && *end == ' ', "nan ptr: expected end at space"); + } + + // inf pointer advancement + { + double val = 0.0; + const char *end = NULL; + bool ok = obj::parseFloat("infinity rest", &val, &end); + CHECK(ok, "inf ptr: parse failed"); + CHECK(end && *end == ' ', "inf ptr: expected end at space"); + } + + // parseFloatRange + { + double val = 0.0; + const char *end = NULL; + const char *s = "+1.5e2xxx"; + bool ok = obj::parseFloatRange(s, s + 6, &val, &end); + CHECK(ok, "range: parse failed"); + CHECK(val == 150.0, "range: expected 150.0, got %.17g", val); + } + + // parseFloatRange with nan + { + double val = -1.0; + const char *end = NULL; + const char *s = "nan"; + bool ok = obj::parseFloatRange(s, s + 3, &val, &end); + CHECK(ok, "range nan: parse failed"); + CHECK(val == 0.0, "range nan: expected 0.0, got %.17g", val); + } + + return g_failures - before; +} + +// --------------------------------------------------------------------------- +// 6. Edge cases / error handling +// --------------------------------------------------------------------------- +static int test_edge_cases() { + int before = g_failures; + + // Empty string + { + double val = 999.0; + const char *end = NULL; + bool ok = obj::parseFloat("", &val, &end); + CHECK(!ok, "empty: should fail"); + } + + // Whitespace only + { + double val = 999.0; + const char *end = NULL; + bool ok = obj::parseFloat(" ", &val, &end); + CHECK(!ok, "ws-only: should fail"); + } + + // Just '+' + { + double val = 999.0; + const char *end = NULL; + bool ok = obj::parseFloat("+", &val, &end); + CHECK(!ok, "bare-plus: should fail"); + } + + // Normal negative still works + { + double val = 0.0; + bool ok = obj::parseFloat("-3.14", &val); + CHECK(ok && std::fabs(val - (-3.14)) < 1e-15, + "negative: expected -3.14, got %.17g", val); + } + + // "nana" should parse "nan" and stop + { + double val = -1.0; + const char *end = NULL; + // Use range-based to see where it stops + const char *s = "nana"; + bool ok = obj::parseFloatRange(s, s + 4, &val, &end); + CHECK(ok, "nana: parse should succeed (match 'nan')"); + CHECK(val == 0.0, "nana: expected 0.0, got %.17g", val); + CHECK(end == s + 3, "nana: expected end at s+3, got s+%d", (int)(end - s)); + } + + // "info" should parse "inf" and stop at 'o' + { + double val = 0.0; + const char *end = NULL; + const char *s = "info"; + bool ok = obj::parseFloatRange(s, s + 4, &val, &end); + CHECK(ok, "info: parse should succeed (match 'inf')"); + double dmax = (std::numeric_limits::max)(); + CHECK(val == dmax, "info: expected max, got %.17g", val); + CHECK(end == s + 3, "info: expected end at s+3, got s+%d", (int)(end - s)); + } + + return g_failures - before; +} + +// --------------------------------------------------------------------------- +// 7. Accuracy comparison: fast_float vs legacy tryParseDouble vs strtod +// --------------------------------------------------------------------------- +static int test_accuracy() { + const char *hard_cases[] = { + "2.2250738585072014e-308", + "2.2250738585072011e-308", + "1.00000000000000011102230246251565404236316680908203125", + "0.3", + "0.1", + "0.2", + "7205759403792794e-1", + "922337203685477.5807", + "1e23", + }; + + int before = g_failures; + const int n = sizeof(hard_cases) / sizeof(hard_cases[0]); + + printf("--- Accuracy comparison ---\n"); + printf("%-55s %22s %22s %22s\n", "Input", "strtod", "fast_float", "legacy"); + + for (int i = 0; i < n; i++) { + const char *s = hard_cases[i]; + size_t len = std::strlen(s); + + double ref = std::strtod(s, NULL); + + double ff_val = 0.0; + fast_float::from_chars(s, s + len, ff_val); + + double lg_val = 0.0; + tryParseDouble_legacy(s, s + len, &lg_val); + + printf("%-55s %22.17g %22.17g %22.17g\n", s, ref, ff_val, lg_val); + + CHECK(ref == ff_val, "accuracy [%d] \"%s\": fast_float mismatch vs strtod", + i, s); + if (ref != lg_val) { + printf(" ^^ legacy MISMATCH vs strtod (expected)\n"); + } + } + + return g_failures - before; +} + +// --------------------------------------------------------------------------- +// Benchmark +// --------------------------------------------------------------------------- +static void benchmark() { + const int N = 1000000; + std::vector data; + data.reserve(N); + + const char *patterns[] = { + "0.123456", "-0.987654", "1.000000", "0.000000", "123.456", + "-789.012", "0.5", "-0.5", "1e-5", "3.14159265", + "2.71828182", "-0.0001234", "999.999999", "0.333333", "-1.414213", + "+0.5", "+123.456", "+1e-5", // OBJ '+' prefix + }; + const int np = sizeof(patterns) / sizeof(patterns[0]); + for (int i = 0; i < N; i++) { + data.push_back(patterns[i % np]); + } + + double sum; + typedef std::chrono::high_resolution_clock Clock; + + // Benchmark obj::parseFloat (fast_float + OBJ wrapper) + sum = 0.0; + auto t0 = Clock::now(); + for (int i = 0; i < N; i++) { + double val = 0.0; + obj::parseFloat(data[i].c_str(), &val); + sum += val; + } + auto t1 = Clock::now(); + double obj_ms = + std::chrono::duration_cast(t1 - t0).count() / + 1000.0; + printf("obj::parseFloat: %8.2f ms (sum=%.6f, %d values)\n", obj_ms, sum, N); + + // Benchmark legacy tryParseDouble + sum = 0.0; + t0 = Clock::now(); + for (int i = 0; i < N; i++) { + double val = 0.0; + const char *s = data[i].c_str(); + size_t len = data[i].size(); + // Skip whitespace for fair comparison + while (*s == ' ' || *s == '\t') { ++s; --len; } + const char *e = s + len; + while (e > s && (*(e-1) == ' ' || *(e-1) == '\t')) --e; + tryParseDouble_legacy(s, e, &val); + sum += val; + } + t1 = Clock::now(); + double lg_ms = + std::chrono::duration_cast(t1 - t0).count() / + 1000.0; + printf("legacy: %8.2f ms (sum=%.6f, %d values)\n", lg_ms, sum, N); + + // Benchmark strtod + sum = 0.0; + t0 = Clock::now(); + for (int i = 0; i < N; i++) { + sum += std::strtod(data[i].c_str(), NULL); + } + t1 = Clock::now(); + double sd_ms = + std::chrono::duration_cast(t1 - t0).count() / + 1000.0; + printf("strtod: %8.2f ms (sum=%.6f, %d values)\n", sd_ms, sum, N); + + printf("\nSpeedup obj::parseFloat vs legacy: %.2fx\n", lg_ms / obj_ms); + printf("Speedup obj::parseFloat vs strtod: %.2fx\n", sd_ms / obj_ms); + + // Benchmark nan/inf mix + printf("\n--- nan/inf mixed benchmark (100K) ---\n"); + const int M = 100000; + std::vector mixed; + mixed.reserve(M); + const char *mixed_patterns[] = { + "1.5", "-2.3", "nan", "inf", "-inf", + "0.5", "NaN", "Inf", "+1.0", "infinity", + }; + const int mp = sizeof(mixed_patterns) / sizeof(mixed_patterns[0]); + for (int i = 0; i < M; i++) { + mixed.push_back(mixed_patterns[i % mp]); + } + + sum = 0.0; + t0 = Clock::now(); + for (int i = 0; i < M; i++) { + double val = 0.0; + obj::parseFloat(mixed[i].c_str(), &val); + sum += val; + } + t1 = Clock::now(); + double mix_ms = + std::chrono::duration_cast(t1 - t0).count() / + 1000.0; + printf("obj::parseFloat (mixed): %8.2f ms (sum=%.6f, %d values)\n", mix_ms, + sum, M); +} + +// --------------------------------------------------------------------------- +int main() { + int section_fails; + + printf("=== 1. fast_float basic correctness ===\n"); + section_fails = test_fast_float_basic(); + printf(" %s\n\n", section_fails == 0 ? "PASSED" : "FAILED"); + + printf("=== 2. Leading '+' sign ===\n"); + section_fails = test_leading_plus(); + printf(" %s\n\n", section_fails == 0 ? "PASSED" : "FAILED"); + + printf("=== 3. nan/inf with default replacements ===\n"); + section_fails = test_nan_inf_defaults(); + printf(" %s\n\n", section_fails == 0 ? "PASSED" : "FAILED"); + + printf("=== 4. nan/inf with custom replacements ===\n"); + section_fails = test_nan_inf_custom(); + printf(" %s\n\n", section_fails == 0 ? "PASSED" : "FAILED"); + + printf("=== 5. Pointer advancement / whitespace ===\n"); + section_fails = test_pointer_advance(); + printf(" %s\n\n", section_fails == 0 ? "PASSED" : "FAILED"); + + printf("=== 6. Edge cases ===\n"); + section_fails = test_edge_cases(); + printf(" %s\n\n", section_fails == 0 ? "PASSED" : "FAILED"); + + printf("=== 7. Accuracy comparison ===\n"); + section_fails = test_accuracy(); + printf(" %s\n\n", section_fails == 0 ? "PASSED" : "FAILED"); + + printf("=== Benchmark (1M parses) ===\n"); + benchmark(); + + printf("\n"); + if (g_failures == 0) { + printf("ALL TESTS PASSED.\n"); + return 0; + } else { + printf("%d TEST(S) FAILED.\n", g_failures); + return 1; + } +} diff --git a/setup.py b/setup.py index cb950871..53ab3312 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ # Adapted from https://github.com/pybind/python_example/blob/master/setup.py import sys -#from pybind11 import get_cmake_dir +# from pybind11 import get_cmake_dir # Available at setup time due to pyproject.toml -from pybind11.setup_helpers import Pybind11Extension#, build_ext +from pybind11.setup_helpers import Pybind11Extension # , build_ext from setuptools import setup try: @@ -12,9 +12,6 @@ except: __version__ = "2.0.0rc10" -with open("README.md", "r", encoding="utf8") as fh: - long_description = fh.read() - # The main interface is through Pybind11Extension. # * You can add cxx_std=11/14/17, and then build_ext can be removed. # * You can set include_pybind11=false to add the include directory yourself, @@ -25,27 +22,27 @@ # reproducible builds (https://github.com/pybind/python_example/pull/53) ext_modules = [ - Pybind11Extension("tinyobjloader", + Pybind11Extension( + "tinyobjloader", sorted(["python/bindings.cc", "python/tiny_obj_loader.cc"]), # Example: passing in the version to the compiled code - define_macros = [('VERSION_INFO', __version__)], + define_macros=[("VERSION_INFO", __version__)], cxx_std=11, - ), + ), ] setup( name="tinyobjloader", - packages=['python'], - #version=__version__, + packages=["python"], + # version=__version__, author="Syoyo Fujita", author_email="syoyo@lighttransport.com", url="https://github.com/tinyobjloader/tinyobjloader", - #project_urls={ + # project_urls={ # "Issue Tracker": "https://github.com/tinyobjloader/tinyobjloader/issues", - #}, + # }, description="Tiny but powerful Wavefront OBJ loader", - long_description=long_description, - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -54,16 +51,14 @@ "Topic :: Artistic Software", "Topic :: Multimedia :: Graphics :: 3D Modeling", "Topic :: Scientific/Engineering :: Visualization", - "License :: OSI Approved :: MIT License", - "License :: OSI Approved :: ISC License (ISCL)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", ], ext_modules=ext_modules, - #extras_require={"test": "pytest"}, + # extras_require={"test": "pytest"}, # Currently, build_ext only provides an optional "highest supported C++ # level" feature, but in the future it may provide more features. # cmdclass={"build_ext": build_ext}, - #zip_safe=False, - #python_requires=">=3.6", + # zip_safe=False, + # python_requires=">=3.6", ) diff --git a/tests/Makefile b/tests/Makefile index 83d297d2..4a557a4e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -2,7 +2,7 @@ CXX ?= clang++ CXXFLAGS ?= -g -O1 -EXTRA_CXXFLAGS ?= -std=c++03 -fsanitize=address +EXTRA_CXXFLAGS ?= -std=c++11 -fsanitize=address tester: tester.cc ../tiny_obj_loader.h $(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) -o tester tester.cc diff --git a/tests/python/README.md b/tests/python/README.md new file mode 100644 index 00000000..543a238d --- /dev/null +++ b/tests/python/README.md @@ -0,0 +1,12 @@ +# tinyobjloader Python tests + +This folder hosts a project for running the Python binding tests. + +## Development + +The tests require NumPy. To optimize CI install times, the uv.lock excludes +NumPy, as for some Python versions, pinning a version would result in builds +from source which are then discarded. To run the tests locally, after running +`uv sync`, install into the venv a version of NumPy from the build matrix in +`.github/workflows/python.yml`. + diff --git a/tests/python/pyproject.toml b/tests/python/pyproject.toml new file mode 100644 index 00000000..8157856b --- /dev/null +++ b/tests/python/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "tinyobjloader-tests" +version = "0.0.1" +description = "Tests for tinyobjloader Python bindings" +readme = "README.md" +requires-python = ">=3.9" + +dependencies = ["pytest>=8.0", "black==22.10.0"] + +[build-system] +requires = ["hatchling>=1.24"] +build-backend = "hatchling.build" diff --git a/tests/python/tinyobjloader_tests/__init__.py b/tests/python/tinyobjloader_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/python/tinyobjloader_tests/loader.py b/tests/python/tinyobjloader_tests/loader.py new file mode 100644 index 00000000..9d7b3dad --- /dev/null +++ b/tests/python/tinyobjloader_tests/loader.py @@ -0,0 +1,33 @@ +from tinyobjloader import ObjReader, ObjReaderConfig + + +class LoadException(Exception): + pass + + +class Loader: + """ + A light wrapper around ObjReader to provide a convenient interface for testing. + """ + + def __init__(self, triangulate): + self.reader = ObjReader() + config = ObjReaderConfig() + config.triangulate = triangulate + self.config = config + + def load(self, mesh_path): + if not self.reader.ParseFromFile(mesh_path, self.config): + raise LoadException(self.reader.Error() or self.reader.Warning()) + + def loads(self, mesh_string): + if not self.reader.ParseFromString(mesh_string, "", self.config): + raise LoadException(self.reader.Error() or self.reader.Warning()) + + @property + def shapes(self): + return self.reader.GetShapes() + + @property + def attrib(self): + return self.reader.GetAttrib() diff --git a/tests/python/tinyobjloader_tests/test_loader.py b/tests/python/tinyobjloader_tests/test_loader.py new file mode 100644 index 00000000..c9159ea1 --- /dev/null +++ b/tests/python/tinyobjloader_tests/test_loader.py @@ -0,0 +1,235 @@ +from pathlib import Path + +import numpy as np + +from .loader import Loader, LoadException + +MODELS_DIR = Path(__file__).parent.parent.parent.parent / "models" + +TWO_QUADS = """ +v 0 0 0 +v 0 0 0 +v 0 0 0 +v 0 0 0 +f 1 2 3 4 +v 46.367584 82.676086 8.867414 +v 46.524185 82.81955 8.825487 +v 46.59864 83.086678 8.88121 +v 46.461926 82.834091 8.953863 +f 5 6 7 8 +""" + +MIXED_ARITY = """ +v 0 1 1 +v 0 2 2 +v 0 3 3 +v 0 4 4 +v 0 5 5 +f 1 2 3 4 +f 1 4 5 +""" + + +def test_numpy_face_vertices_two_quads(): + """ + Test for https://github.com/tinyobjloader/tinyobjloader/issues/400 + """ + + # Set up. + loader = Loader(triangulate=False) + loader.loads(TWO_QUADS) + + shapes = loader.shapes + assert len(shapes) == 1 + + # Confidence check. + (shape,) = shapes + expected_num_face_vertices = [4, 4] + assert shape.mesh.num_face_vertices == expected_num_face_vertices + + # Test. + np.testing.assert_array_equal(shape.mesh.numpy_num_face_vertices(), expected_num_face_vertices) + + +def test_numpy_face_vertices_two_quads_with_triangulate(): + """ + Test for https://github.com/tinyobjloader/tinyobjloader/issues/400 + """ + + # Set up. + loader = Loader(triangulate=True) + loader.loads(TWO_QUADS) + + shapes = loader.shapes + assert len(shapes) == 1 + + # Confidence check. + (shape,) = shapes + expected_num_face_vertices = [3, 3, 3, 3] + assert shape.mesh.num_face_vertices == expected_num_face_vertices + + # Test. + np.testing.assert_array_equal(shape.mesh.numpy_num_face_vertices(), expected_num_face_vertices) + + +def test_numpy_face_vertices_mixed_arity(): + """ + Test for: + - https://github.com/tinyobjloader/tinyobjloader/issues/400 + - https://github.com/tinyobjloader/tinyobjloader/issues/402 + """ + + # Set up. + loader = Loader(triangulate=False) + loader.loads(MIXED_ARITY) + + shapes = loader.shapes + assert len(shapes) == 1 + + # Confidence check. + (shape,) = shapes + expected_num_face_vertices = [4, 3] + assert shape.mesh.num_face_vertices == expected_num_face_vertices + + # Test. + np.testing.assert_array_equal(shape.mesh.numpy_num_face_vertices(), expected_num_face_vertices) + + +def test_numpy_face_vertices_mixed_arity_with_triangulate(): + """ + Test for https://github.com/tinyobjloader/tinyobjloader/issues/400 + """ + + # Set up. + loader = Loader(triangulate=True) + loader.loads(MIXED_ARITY) + + shapes = loader.shapes + assert len(shapes) == 1 + + # Confidence check. + (shape,) = shapes + expected_num_face_vertices = [3, 3, 3] + assert shape.mesh.num_face_vertices == expected_num_face_vertices + + # Test. + np.testing.assert_array_equal(shape.mesh.numpy_num_face_vertices(), expected_num_face_vertices) + + +def test_numpy_index_array_two_quads(): + """ + Test for https://github.com/tinyobjloader/tinyobjloader/issues/401 + """ + + # Set up. + loader = Loader(triangulate=False) + loader.loads(TWO_QUADS) + + shapes = loader.shapes + assert len(shapes) == 1 + + # Confidence check. + (shape,) = shapes + expected_vertex_index = [0, 1, 2, 3, 4, 5, 6, 7] + assert [x.vertex_index for x in shape.mesh.indices] == expected_vertex_index + + # Test. + expected_numpy_indices = [ + 0, + -1, + -1, + 1, + -1, + -1, + 2, + -1, + -1, + 3, + -1, + -1, + 4, + -1, + -1, + 5, + -1, + -1, + 6, + -1, + -1, + 7, + -1, + -1, + ] + np.testing.assert_array_equal(shape.mesh.numpy_indices(), expected_numpy_indices) + + +def test_numpy_vertex_array_two_quads(): + """ + Test for https://github.com/tinyobjloader/tinyobjloader/issues/401 + """ + + # Set up. + loader = Loader(triangulate=False) + loader.loads(TWO_QUADS) + + # Confidence check. + expected_vertices = [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 46.367584, + 82.676086, + 8.867414, + 46.524185, + 82.81955, + 8.825487, + 46.59864, + 83.086678, + 8.88121, + 46.461926, + 82.834091, + 8.953863, + ] + np.testing.assert_array_almost_equal(loader.attrib.vertices, expected_vertices, decimal=6) + + # Test. + np.testing.assert_array_almost_equal(loader.attrib.numpy_vertices(), expected_vertices, decimal=6) + + +def test_numpy_num_face_vertices_from_file(): + """ + Regression test for https://github.com/tinyobjloader/tinyobjloader/issues/400 + + Loads a mixed quad/triangle mesh from a .obj file and checks that + numpy_num_face_vertices() returns correct unsigned int values. + With the bug (unsigned char element type), values were read as zeros. + """ + + # Set up. + obj_path = str(MODELS_DIR / "issue-400-num-face-vertices.obj") + loader = Loader(triangulate=False) + loader.load(obj_path) + + shapes = loader.shapes + assert len(shapes) == 1 + + (shape,) = shapes + # The file has one quad face (4 vertices) and two triangle faces (3 vertices each). + expected_num_face_vertices = [4, 3, 3] + + # Confidence check using the non-numpy accessor. + assert shape.mesh.num_face_vertices == expected_num_face_vertices + + # Test: numpy_num_face_vertices() must return the same values with the correct dtype. + result = shape.mesh.numpy_num_face_vertices() + np.testing.assert_array_equal(result, expected_num_face_vertices) + assert result.dtype == np.dtype("uint32") diff --git a/tests/python/uv.lock b/tests/python/uv.lock new file mode 100644 index 00000000..98022816 --- /dev/null +++ b/tests/python/uv.lock @@ -0,0 +1,303 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[[package]] +name = "black" +version = "22.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/89/629fca2eea0899c06befaa58dc0f49d56807d454202bb2e54bd0d98c77f3/black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1", size = 547735, upload-time = "2022-10-06T22:44:48.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/49/ea03c318a25be359b8e5178a359d47e2da8f7524e1522c74b8f74c66b6f8/black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa", size = 1413786, upload-time = "2022-10-07T18:06:56.738Z" }, + { url = "https://files.pythonhosted.org/packages/a6/84/5c3f3ffc4143fa7e208d745d2239d915e74d3709fdbc64c3e98d3fd27e56/black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef", size = 1395367, upload-time = "2022-10-07T18:07:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/f2/23/f4278377cabf882298b4766e977fd04377f288d1ccef706953076a1e0598/black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4", size = 1412948, upload-time = "2022-10-07T18:06:45.929Z" }, + { url = "https://files.pythonhosted.org/packages/2c/11/f2737cd3b458d91401801e83a014e87c63e8904dc063200f77826c352f54/black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb", size = 1248864, upload-time = "2022-10-07T18:34:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/a5/5f/9cfc6dd95965f8df30194472543e6f0515a10d78ea5378426ef1546735c7/black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7", size = 1542985, upload-time = "2022-10-06T22:54:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ce/22281871536b3d79474fd44d48dad48f7cbc5c3982bddf6a7495e7079d00/black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66", size = 1198188, upload-time = "2022-10-06T22:58:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2f/a8406a9e337a213802aa90a3e9fbf90c86f3edce92f527255fd381309b77/black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae", size = 1233231, upload-time = "2022-10-07T18:35:05.895Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9e/fa912c5ae4b8eb6d36982fc8ac2d779cf944dbd7c3c1fe7a28acf462c1ed/black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b", size = 1527386, upload-time = "2022-10-06T22:54:25.636Z" }, + { url = "https://files.pythonhosted.org/packages/56/df/913d71817c7034edba25d596c54f782c2f809b6af30367d2f00309e8890a/black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d", size = 1201344, upload-time = "2022-10-06T22:58:58.134Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/903cdf41514088d5a716538cb189c471ab34e56ae9a1c2da6b8bfe8e4dbf/black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0", size = 1248291, upload-time = "2022-10-07T18:34:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/b9/51/403b0b0eb9fb412ca02b79dc38472469f2f88c9aacc6bb5262143e4ff0bc/black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383", size = 1542631, upload-time = "2022-10-06T22:54:31.965Z" }, + { url = "https://files.pythonhosted.org/packages/ab/15/61119d166a44699827c112d7c4726421f14323c2cb7aa9f4c26628f237f9/black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de", size = 1197402, upload-time = "2022-10-06T22:59:03.766Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6f/74492b8852ee4f2ad2178178f6b65bc8fc80ad539abe56c1c23eab6732e2/black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458", size = 165761, upload-time = "2022-10-06T22:44:46.108Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "tinyobjloader-tests" +version = "0.0.1" +source = { editable = "." } +dependencies = [ + { name = "black" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", specifier = "==22.10.0" }, + { name = "pytest", specifier = ">=8.0" }, +] + +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] diff --git a/tests/tester.cc b/tests/tester.cc index 2f336c76..9709d1c5 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -1,4 +1,5 @@ #define TINYOBJLOADER_IMPLEMENTATION +#define TINYOBJLOADER_STREAM_READER_MAX_BYTES (size_t(8) * size_t(1024) * size_t(1024)) #include "../tiny_obj_loader.h" #if defined(__clang__) @@ -29,6 +30,29 @@ #include #include #include +#include + +#ifdef _WIN32 +#include // _mkdir +#include // GetTempPathW, CreateDirectoryW, RegOpenKeyExA +#include // registry constants +#pragma comment(lib, "Advapi32.lib") // RegOpenKeyExA, RegQueryValueExA, RegCloseKey + +// Converts a UTF-16 wide string to a UTF-8 std::string. +static std::string WcharToUTF8(const std::wstring &wstr) { + if (wstr.empty()) return std::string(); + int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, + NULL, 0, NULL, NULL); + if (len <= 0) return std::string(); + std::string str(static_cast(len), '\0'); + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], len, NULL, NULL); + str.resize(static_cast(len - 1)); // trim terminating '\0' + return str; +} +#else +#include +#include // mkdir +#endif template static bool FloatEquals(const T& a, const T& b) { @@ -399,6 +423,190 @@ static bool TestStreamLoadObj() { const char* gMtlBasePath = "../models/"; +// --------------------------------------------------------------------------- +// Helpers for path-related tests +// --------------------------------------------------------------------------- + +// Creates a single directory level. Returns true on success or if it already exists. +static bool MakeDir(const std::string& path) { +#ifdef _WIN32 + // Use the wide-character API so that paths with non-ASCII characters work. + std::wstring wpath = UTF8ToWchar(path); + if (wpath.empty()) return false; + if (CreateDirectoryW(wpath.c_str(), NULL) != 0) return true; + return GetLastError() == ERROR_ALREADY_EXISTS; +#else + return mkdir(path.c_str(), 0755) == 0 || errno == EEXIST; +#endif +} + +// Removes a directory and all its contents. +// NOTE: All callers pass paths that are fully constructed within this test +// file from hardcoded string literals, so there is no user-controlled input +// that could be used for command injection. +static void RemoveTestDir(const std::string& path) { +#ifdef _WIN32 + std::string cmd = "rd /s /q \"" + path + "\""; +#else + std::string cmd = "rm -rf '" + path + "'"; +#endif + if (system(cmd.c_str()) != 0) { /* cleanup failure is non-fatal */ } +} + +// Copies a file in binary mode. The destination path is taken as UTF-8. +// On Windows, LongPathW(UTF8ToWchar()) is used so that long paths (> MAX_PATH) +// are handled, exercising the same conversion that tinyobjloader itself uses. +static bool CopyTestFile(const std::string& src, const std::string& dst) { + std::ifstream in(src.c_str(), std::ios::binary); + if (!in) return false; +#ifdef _WIN32 + // Apply long-path prefix so that the copy works even for paths > MAX_PATH. + std::ofstream out(LongPathW(UTF8ToWchar(dst)).c_str(), std::ios::binary); +#else + std::ofstream out(dst.c_str(), std::ios::binary); +#endif + if (!out) return false; + out << in.rdbuf(); + return !out.fail(); +} + +#ifdef _WIN32 +// Returns true if Windows has the system-wide long path support enabled +// (HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1). +static bool IsWindowsLongPathEnabled() { + HKEY hKey; + DWORD value = 0; + DWORD size = sizeof(DWORD); + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Control\\FileSystem", 0, + KEY_READ, &hKey) == ERROR_SUCCESS) { + RegQueryValueExA(hKey, "LongPathsEnabled", NULL, NULL, + reinterpret_cast(&value), &size); + RegCloseKey(hKey); + } + return value != 0; +} +#endif // _WIN32 + +// --------------------------------------------------------------------------- +// Path-related tests +// --------------------------------------------------------------------------- + +// Test: load .obj/.mtl from a directory path containing UTF-8 non-ASCII +// characters. On Windows our code converts the UTF-8 path to UTF-16 before +// calling the file API. On Linux, UTF-8 paths are handled natively. +void test_load_obj_from_utf8_path() { + // Build a temp directory name that contains the UTF-8 encoded character é + // (U+00E9, encoded as \xC3\xA9 in UTF-8). +#ifdef _WIN32 + wchar_t wtmpbuf[MAX_PATH]; + GetTempPathW(MAX_PATH, wtmpbuf); + std::string test_dir = + WcharToUTF8(wtmpbuf) + "tinyobj_utf8_\xc3\xa9_test\\"; +#else + std::string test_dir = "/tmp/tinyobj_utf8_\xc3\xa9_test/"; +#endif + + if (!MakeDir(test_dir)) { + std::cout << "SKIPPED: Cannot create Unicode temp directory: " << test_dir + << "\n"; + return; + } + + const std::string obj_dst = test_dir + "utf8-path-test.obj"; + const std::string mtl_dst = test_dir + "utf8-path-test.mtl"; + + if (!CopyTestFile("../models/utf8-path-test.obj", obj_dst) || + !CopyTestFile("../models/utf8-path-test.mtl", mtl_dst)) { + RemoveTestDir(test_dir); + TEST_CHECK_(false, "Failed to copy test files to Unicode temp directory"); + return; + } + + tinyobj::ObjReader reader; + bool ret = reader.ParseFromFile(obj_dst); + + RemoveTestDir(test_dir); + + if (!reader.Warning().empty()) + std::cout << "WARN: " << reader.Warning() << "\n"; + if (!reader.Error().empty()) + std::cerr << "ERR: " << reader.Error() << "\n"; + + TEST_CHECK(ret == true); + TEST_CHECK(reader.GetShapes().size() == 1); + TEST_CHECK(reader.GetMaterials().size() == 1); +} + +// Test: load .obj/.mtl from a path whose total length exceeds MAX_PATH (260). +// On Windows, tinyobjloader prepends the \\?\ extended-length path prefix so +// that the file can be opened even on systems that have the OS-wide long path +// support enabled. The test is skipped when that support is not active. +// On Linux, long paths work natively; this test verifies no regression. +void test_load_obj_from_long_path() { +#ifdef _WIN32 + if (!IsWindowsLongPathEnabled()) { + std::cout + << "SKIPPED: Windows long path support (LongPathsEnabled) is not " + "enabled\n"; + return; + } + wchar_t wtmpbuf[MAX_PATH]; + GetTempPathW(MAX_PATH, wtmpbuf); + std::string base = WcharToUTF8(wtmpbuf); // e.g. "C:\Users\...\Temp\" + const char path_sep = '\\'; +#else + std::string base = "/tmp/"; + const char path_sep = '/'; +#endif + + // Create a two-level directory where the deepest directory name is 250 + // characters long. Combined with the base path and the filename + // "utf8-path-test.obj" (18 chars) the total file path comfortably exceeds + // MAX_PATH (260) on all supported platforms. + std::string test_root = base + "tinyobj_lp_test" + path_sep; + std::string long_subdir = test_root + std::string(250, 'a') + path_sep; + std::string obj_path = long_subdir + "utf8-path-test.obj"; + + // obj_path must exceed MAX_PATH for the test to be meaningful. + // (On a typical Windows installation it is ~320 chars; on Linux ~287 chars.) + if (obj_path.size() <= 260) { + std::cout << "SKIPPED: generated path (" << obj_path.size() + << " chars) does not exceed MAX_PATH=260\n"; + return; + } + + if (!MakeDir(test_root) || !MakeDir(long_subdir)) { + RemoveTestDir(test_root); + std::cout << "SKIPPED: Cannot create long-path temp directory: " + << long_subdir << "\n"; + return; + } + + if (!CopyTestFile("../models/utf8-path-test.obj", + long_subdir + "utf8-path-test.obj") || + !CopyTestFile("../models/utf8-path-test.mtl", + long_subdir + "utf8-path-test.mtl")) { + RemoveTestDir(test_root); + TEST_CHECK_(false, "Failed to copy test files to long-path directory"); + return; + } + + tinyobj::ObjReader reader; + bool ret = reader.ParseFromFile(obj_path); + + RemoveTestDir(test_root); + + if (!reader.Warning().empty()) + std::cout << "WARN: " << reader.Warning() << "\n"; + if (!reader.Error().empty()) + std::cerr << "ERR: " << reader.Error() << "\n"; + + TEST_CHECK(ret == true); + TEST_CHECK(reader.GetShapes().size() == 1); + TEST_CHECK(reader.GetMaterials().size() == 1); +} + void test_cornell_box() { TEST_CHECK(true == TestLoadObj("../models/cornell_box.obj", gMtlBasePath)); } @@ -426,6 +634,13 @@ void test_catmark_torus_creases0() { TEST_CHECK(1 == shapes.size()); TEST_CHECK(8 == shapes[0].mesh.tags.size()); + TEST_CHECK(std::string("crease") == shapes[0].mesh.tags[0].name); + TEST_CHECK(2 == shapes[0].mesh.tags[0].intValues.size()); + TEST_CHECK(1 == shapes[0].mesh.tags[0].floatValues.size()); + TEST_CHECK(0 == shapes[0].mesh.tags[0].stringValues.size()); + TEST_CHECK(1 == shapes[0].mesh.tags[0].intValues[0]); + TEST_CHECK(5 == shapes[0].mesh.tags[0].intValues[1]); + TEST_CHECK(FloatEquals(4.7f, shapes[0].mesh.tags[0].floatValues[0])); } void test_pbr() { @@ -889,7 +1104,7 @@ void test_invalid_texture_vertex_index() { std::string err; bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, - "../models/invalid-relative-texture-vertex-index.obj", gMtlBasePath); + "../models/invalid-relative-texture-index.obj", gMtlBasePath); if (!warn.empty()) { std::cout << "WARN: " << warn << std::endl; @@ -1408,6 +1623,1680 @@ void test_face_missing_issue295() { TEST_CHECK((3 * 28) == shapes[0].mesh.indices.size()); // 28 triangle faces x 3 } +void test_comment_issue389() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/issue-389-comment.obj", + gMtlBasePath, /* triangualte */false); + + TEST_CHECK(warn.empty()); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); +} + +void test_default_kd_for_multiple_materials_issue391() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-391.obj", gMtlBasePath); + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + const tinyobj::real_t kGrey[] = {0.6, 0.6, 0.6}; + const tinyobj::real_t kRed[] = {1.0, 0.0, 0.0}; + + TEST_CHECK(true == ret); + TEST_CHECK(2 == materials.size()); + for (size_t i = 0; i < materials.size(); ++i) { + const tinyobj::material_t& material = materials[i]; + if (material.name == "has_map") { + for (int i = 0; i < 3; ++i) TEST_CHECK(material.diffuse[i] == kGrey[i]); + } else if (material.name == "has_kd") { + for (int i = 0; i < 3; ++i) TEST_CHECK(material.diffuse[i] == kRed[i]); + } else { + std::cerr << "Unexpected material found!" << std::endl; + TEST_CHECK(false); + } + } +} + +void test_removeUtf8Bom() { + // Basic input with BOM + std::string withBOM = "\xEF\xBB\xBFhello world"; + TEST_CHECK(tinyobj::removeUtf8Bom(withBOM) == "hello world"); + + // Input without BOM + std::string noBOM = "hello world"; + TEST_CHECK(tinyobj::removeUtf8Bom(noBOM) == "hello world"); + + // Leaves short string unchanged + std::string shortStr = "\xEF"; + TEST_CHECK(tinyobj::removeUtf8Bom(shortStr) == shortStr); + + std::string shortStr2 = "\xEF\xBB"; + TEST_CHECK(tinyobj::removeUtf8Bom(shortStr2) == shortStr2); + + // BOM only returns empty string + std::string justBom = "\xEF\xBB\xBF"; + TEST_CHECK(tinyobj::removeUtf8Bom(justBom) == ""); + + // Empty string + std::string emptyStr = ""; + TEST_CHECK(tinyobj::removeUtf8Bom(emptyStr) == ""); +} + +void test_loadObj_with_BOM() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/cube_w_BOM.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(6 == shapes.size()); + TEST_CHECK(0 == shapes[0].name.compare("front cube")); + TEST_CHECK(0 == shapes[1].name.compare("back cube")); // multiple whitespaces + // are aggregated as + // single white space. +} + + +void test_texcoord_w_component() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/texcoord-w.obj", gMtlBasePath, + /*triangulate*/ false); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(4 == attrib.texcoords.size() / 2); // 4 uv pairs + TEST_CHECK(4 == attrib.texcoord_ws.size()); // 4 w values + TEST_CHECK(FloatEquals(0.50f, attrib.texcoord_ws[0])); + TEST_CHECK(FloatEquals(0.25f, attrib.texcoord_ws[1])); + TEST_CHECK(FloatEquals(0.75f, attrib.texcoord_ws[2])); + TEST_CHECK(FloatEquals(0.00f, attrib.texcoord_ws[3])); +} + + + +void test_texcoord_w_mixed_component() { + // Test a mix of vt lines with the optional w present and omitted. + // Lines without w should produce 0.0 in texcoord_ws. + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/texcoord-w-mixed.obj", gMtlBasePath, + /*triangulate*/ false); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(4 == attrib.texcoords.size() / 2); // 4 uv pairs + TEST_CHECK(4 == attrib.texcoord_ws.size()); // 4 w values (present or defaulted) + TEST_CHECK(FloatEquals(0.50f, attrib.texcoord_ws[0])); // w present + TEST_CHECK(FloatEquals(0.00f, attrib.texcoord_ws[1])); // w omitted -> 0.0 + TEST_CHECK(FloatEquals(0.75f, attrib.texcoord_ws[2])); // w present + TEST_CHECK(FloatEquals(0.00f, attrib.texcoord_ws[3])); // w omitted -> 0.0 +} + +void test_loadObjWithCallback_with_BOM() { + // Verify that LoadObjWithCallback correctly strips a UTF-8 BOM from the + // first line, just as LoadObj and LoadMtl do. + // We reuse cube_w_BOM.obj which starts with 0xEF 0xBB 0xBF followed by + // "mtllib cube_w_BOM.mtl". Without BOM stripping the mtllib line would + // not be recognised and no materials would be loaded; with BOM stripping + // all 8 vertices and 6 groups are parsed. + + struct CallbackData { + int vertex_count; + int group_count; + int material_count; + CallbackData() : vertex_count(0), group_count(0), material_count(0) {} + }; + + CallbackData data; + + tinyobj::callback_t cb; + cb.vertex_cb = [](void *user_data, tinyobj::real_t x, tinyobj::real_t y, + tinyobj::real_t z, tinyobj::real_t w) { + reinterpret_cast(user_data)->vertex_count++; + }; + cb.group_cb = [](void *user_data, const char **names, int num_names) { + if (num_names > 0) + reinterpret_cast(user_data)->group_count++; + }; + cb.mtllib_cb = [](void *user_data, const tinyobj::material_t *materials, + int num_materials) { + reinterpret_cast(user_data)->material_count += + num_materials; + }; + + std::ifstream ifs("../models/cube_w_BOM.obj"); + TEST_CHECK(ifs.is_open()); + + tinyobj::MaterialFileReader matReader(gMtlBasePath); + std::string warn, err; + bool ret = tinyobj::LoadObjWithCallback(ifs, cb, &data, &matReader, &warn, &err); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(8 == data.vertex_count); // 8 vertices in cube_w_BOM.obj + TEST_CHECK(6 == data.group_count); // 6 groups: front/back/right/top/left/bottom + TEST_CHECK(data.material_count > 0); // materials loaded => mtllib line was parsed +} + +void test_loadObjWithCallback_mtllib_failure_does_not_crash() { + // mtllib load failure should not crash callback path, and should report an + // error/warning while continuing OBJ parsing. + std::string obj_text = "mtllib test.mtl\nv 1 2 3\n"; + std::istringstream obj_stream(obj_text); + + std::string oversized_mtl(TINYOBJLOADER_STREAM_READER_MAX_BYTES + size_t(1), ' '); + std::istringstream mtl_stream(oversized_mtl); + tinyobj::MaterialStreamReader mtl_reader(mtl_stream); + + struct CallbackData { + int vertex_count; + int mtllib_count; + CallbackData() : vertex_count(0), mtllib_count(0) {} + } data; + + tinyobj::callback_t cb; + cb.vertex_cb = [](void *user_data, tinyobj::real_t, tinyobj::real_t, + tinyobj::real_t, tinyobj::real_t) { + reinterpret_cast(user_data)->vertex_count++; + }; + cb.mtllib_cb = [](void *user_data, const tinyobj::material_t *, int) { + reinterpret_cast(user_data)->mtllib_count++; + }; + + std::string warn, err; + bool ret = tinyobj::LoadObjWithCallback(obj_stream, cb, &data, &mtl_reader, + &warn, &err); + + TEST_CHECK(ret == true); + TEST_CHECK(data.vertex_count == 1); + TEST_CHECK(data.mtllib_count == 0); + TEST_CHECK(warn.find("Failed to load material file(s)") != std::string::npos); + TEST_CHECK(err.find("input stream too large") != std::string::npos); +} + +void test_mtllib_empty_filename_is_ignored_loadobj() { + std::string obj_text = "mtllib \nv 1 2 3\n"; + std::istringstream obj_stream(obj_text); + + std::string mtl_text = "newmtl should_not_load\nKd 1 1 1\n"; + std::istringstream mtl_stream(mtl_text); + tinyobj::MaterialStreamReader mtl_reader(mtl_stream); + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, &mtl_reader); + + TEST_CHECK(ret == true); + TEST_CHECK(materials.empty()); + TEST_CHECK(warn.find("Looks like empty filename for mtllib") != std::string::npos); + TEST_CHECK(err.empty()); +} + +void test_mtllib_empty_filename_is_ignored_callback() { + std::string obj_text = "mtllib \nv 1 2 3\n"; + std::istringstream obj_stream(obj_text); + + std::string mtl_text = "newmtl should_not_load\nKd 1 1 1\n"; + std::istringstream mtl_stream(mtl_text); + tinyobj::MaterialStreamReader mtl_reader(mtl_stream); + + struct CallbackData { + int vertex_count; + int mtllib_count; + CallbackData() : vertex_count(0), mtllib_count(0) {} + } data; + + tinyobj::callback_t cb; + cb.vertex_cb = [](void *user_data, tinyobj::real_t, tinyobj::real_t, + tinyobj::real_t, tinyobj::real_t) { + reinterpret_cast(user_data)->vertex_count++; + }; + cb.mtllib_cb = [](void *user_data, const tinyobj::material_t *, int) { + reinterpret_cast(user_data)->mtllib_count++; + }; + + std::string warn, err; + bool ret = tinyobj::LoadObjWithCallback(obj_stream, cb, &data, &mtl_reader, + &warn, &err); + + TEST_CHECK(ret == true); + TEST_CHECK(data.vertex_count == 1); + TEST_CHECK(data.mtllib_count == 0); + TEST_CHECK(warn.find("Looks like empty filename for mtllib") != std::string::npos); + TEST_CHECK(err.empty()); +} + +void test_usemtl_callback_trims_trailing_comment() { + std::string obj_text = + "mtllib test.mtl\n" + "usemtl mat # trailing comment\n" + "v 0 0 0\n"; + std::istringstream obj_stream(obj_text); + + std::string mtl_text = "newmtl mat\nKd 1 1 1\n"; + std::istringstream mtl_stream(mtl_text); + tinyobj::MaterialStreamReader mtl_reader(mtl_stream); + + struct CallbackData { + int usemtl_count; + int last_material_id; + std::string last_name; + CallbackData() : usemtl_count(0), last_material_id(-1), last_name() {} + } data; + + tinyobj::callback_t cb; + cb.usemtl_cb = [](void *user_data, const char *name, int material_id) { + CallbackData *d = reinterpret_cast(user_data); + d->usemtl_count++; + d->last_name = name ? name : ""; + d->last_material_id = material_id; + }; + + std::string warn, err; + bool ret = tinyobj::LoadObjWithCallback(obj_stream, cb, &data, &mtl_reader, + &warn, &err); + + TEST_CHECK(ret == true); + TEST_CHECK(data.usemtl_count == 1); + TEST_CHECK(data.last_name == "mat"); + TEST_CHECK(data.last_material_id == 0); + TEST_CHECK(err.empty()); +} + +void test_tag_triple_huge_count_is_safely_rejected() { + std::string obj_text = + "v 0 0 0\n" + "v 1 0 0\n" + "v 0 1 0\n" + "f 1 2 3\n" + "t crease 999999999999999999999999999999999999999999999999999999999999999999/0/0\n"; + std::istringstream obj_stream(obj_text); + std::string mtl_text; + std::istringstream mtl_stream(mtl_text); + tinyobj::MaterialStreamReader mtl_reader(mtl_stream); + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, &mtl_reader); + + TEST_CHECK(ret == true); + TEST_CHECK(shapes.size() == size_t(1)); + TEST_CHECK(shapes[0].mesh.tags.size() == size_t(1)); + TEST_CHECK(shapes[0].mesh.tags[0].intValues.size() == size_t(0)); + TEST_CHECK(shapes[0].mesh.tags[0].floatValues.size() == size_t(0)); + TEST_CHECK(shapes[0].mesh.tags[0].stringValues.size() == size_t(0)); +} + + + +// Verify that mmap-based loading (TINYOBJLOADER_USE_MMAP) produces the same +// vertex/shape/material data as the standard ifstream-based path. +void test_file_and_stream_load_agree() { + const char *obj_file = "../models/cornell_box.obj"; + + // Load using the file path API (uses mmap when TINYOBJLOADER_USE_MMAP is defined). + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + obj_file, gMtlBasePath); + if (!warn.empty()) std::cout << "WARN: " << warn << "\n"; + if (!err.empty()) std::cerr << "ERR: " << err << "\n"; + TEST_CHECK(ret == true); + + // Also load via the stream API (always uses ifstream-equivalent path). + tinyobj::attrib_t attrib2; + std::vector shapes2; + std::vector materials2; + std::string warn2, err2; + std::ifstream ifs(obj_file); + TEST_CHECK(ifs.good()); + tinyobj::MaterialFileReader matReader(gMtlBasePath); + bool ret2 = tinyobj::LoadObj(&attrib2, &shapes2, &materials2, &warn2, &err2, + &ifs, &matReader); + TEST_CHECK(ret2 == true); + + // Compare results. + TEST_CHECK(attrib.vertices.size() == attrib2.vertices.size()); + TEST_CHECK(attrib.normals.size() == attrib2.normals.size()); + TEST_CHECK(shapes.size() == shapes2.size()); + TEST_CHECK(materials.size() == materials2.size()); + for (size_t i = 0; i < shapes.size(); i++) { + TEST_CHECK(shapes[i].mesh.indices.size() == shapes2[i].mesh.indices.size()); + } +} + +// Verify robustness: loading from a memory buffer (imemstream) is consistent +// with standard file loading. +void test_load_from_memory_buffer() { + const char *obj_file = "../models/cube.obj"; + + // Read file into memory manually. + std::ifstream file(obj_file, std::ios::binary | std::ios::ate); + TEST_CHECK(file.good()); + std::streamsize sz = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buf(static_cast(sz)); + TEST_CHECK(file.read(buf.data(), sz).good()); + file.close(); + + // Parse from the memory buffer via the stream API. + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + // Copy the memory buffer into a std::string and parse via std::istringstream. + std::string obj_text(buf.begin(), buf.end()); + std::istringstream obj_ss(obj_text); + tinyobj::MaterialFileReader matReader(gMtlBasePath); + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_ss, &matReader); + if (!warn.empty()) std::cout << "WARN: " << warn << "\n"; + if (!err.empty()) std::cerr << "ERR: " << err << "\n"; + TEST_CHECK(ret == true); + + // Compare with direct file load to check consistency. + tinyobj::attrib_t attrib2; + std::vector shapes2; + std::vector materials2; + std::string warn2, err2; + bool ret2 = tinyobj::LoadObj(&attrib2, &shapes2, &materials2, &warn2, &err2, + obj_file, gMtlBasePath); + TEST_CHECK(ret2 == true); + TEST_CHECK(attrib.vertices.size() == attrib2.vertices.size()); + TEST_CHECK(shapes.size() == shapes2.size()); +} + + +// --- Error reporting tests --- + +void test_streamreader_column_tracking() { + const char *input = "hello world\nfoo\n"; + tinyobj::StreamReader sr(input, strlen(input)); + + TEST_CHECK(sr.col_num() == 1); + TEST_CHECK(sr.line_num() == 1); + + // Advance 5 chars: "hello" + sr.advance(5); + TEST_CHECK(sr.col_num() == 6); // col is 1-based, after 5 chars -> col 6 + TEST_CHECK(sr.line_num() == 1); + + // skip_space: " " + sr.skip_space(); + TEST_CHECK(sr.col_num() == 7); + + // read_token: "world" + std::string tok = sr.read_token(); + TEST_CHECK(tok == "world"); + TEST_CHECK(sr.col_num() == 12); + + // skip_line: "\n" + sr.skip_line(); + TEST_CHECK(sr.line_num() == 2); + TEST_CHECK(sr.col_num() == 1); + + // get each char of "foo" + sr.get(); // 'f' + TEST_CHECK(sr.col_num() == 2); + sr.get(); // 'o' + sr.get(); // 'o' + TEST_CHECK(sr.col_num() == 4); +} + +void test_stream_load_from_current_offset() { + std::string prefix = "v 0 0 0\n"; + std::string payload = "v 1 2 3\n"; + std::string text = prefix + payload; + std::istringstream obj_ss(text); + obj_ss.seekg(static_cast(prefix.size()), std::ios::beg); + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_ss, NULL); + if (!warn.empty()) std::cout << "WARN: " << warn << "\n"; + if (!err.empty()) std::cerr << "ERR: " << err << "\n"; + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 3); + TEST_CHECK(attrib.vertices[0] == 1.0f); + TEST_CHECK(attrib.vertices[1] == 2.0f); + TEST_CHECK(attrib.vertices[2] == 3.0f); +} + +void test_stream_load_rejects_oversized_input() { + std::string oversized(TINYOBJLOADER_STREAM_READER_MAX_BYTES + size_t(1), ' '); + std::istringstream obj_ss(oversized); + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_ss, NULL); + TEST_CHECK(ret == false); + TEST_CHECK(err.find("input stream too large") != std::string::npos); +} + +void test_error_format_clang_style() { + const char *input = "v 1.0 abc 3.0\n"; + tinyobj::StreamReader sr(input, strlen(input)); + + // Position to the 'a' in 'abc' (column 7) + sr.advance(6); // past "v 1.0 " + TEST_CHECK(sr.col_num() == 7); + + std::string err = sr.format_error("test.obj", "expected number"); + // Should contain file:line:col + TEST_CHECK(err.find("test.obj:1:7: error: expected number") != std::string::npos); + // Should contain the source line + TEST_CHECK(err.find("v 1.0 abc 3.0") != std::string::npos); + // Should contain a caret + TEST_CHECK(err.find("^") != std::string::npos); +} + +void test_error_stack() { + const char *input = "test\n"; + tinyobj::StreamReader sr(input, strlen(input)); + + TEST_CHECK(!sr.has_errors()); + TEST_CHECK(sr.error_stack().empty()); + + sr.push_error("error 1\n"); + sr.push_error("error 2\n"); + TEST_CHECK(sr.has_errors()); + TEST_CHECK(sr.error_stack().size() == 2); + + std::string all = sr.get_errors(); + TEST_CHECK(all.find("error 1") != std::string::npos); + TEST_CHECK(all.find("error 2") != std::string::npos); + + sr.clear_errors(); + TEST_CHECK(!sr.has_errors()); + TEST_CHECK(sr.error_stack().empty()); +} + +void test_malformed_vertex_error() { + const char *obj_text = "v 1.0 abc 3.0\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + // Early return: malformed vertex coordinate is unrecoverable + TEST_CHECK(ret == false); + TEST_CHECK(err.find("expected number") != std::string::npos); + TEST_CHECK(err.find("abc") != std::string::npos); +} + +void test_malformed_mtl_error() { + const char *mtl_text = "newmtl test\nNs abc\n"; + std::istringstream mtl_iss(mtl_text); + std::map matMap; + std::vector materials; + std::string warn, err; + tinyobj::LoadMtl(&matMap, &materials, &mtl_iss, &warn, &err); + // LoadMtl is void (public API), but error should still be reported + TEST_CHECK(err.find("expected number") != std::string::npos); + TEST_CHECK(err.find("abc") != std::string::npos); +} + +void test_parse_error_backward_compat() { + // Verify that valid OBJ input parses without errors (the old non-error + // sr_parseReal path is still exercised by the callback API). + const char *obj_text = "v 1.0 2.0 3.0\nv 4.0 5.0 6.0\nf 1 2 1\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(err.empty()); + TEST_CHECK(attrib.vertices.size() == 6); +} + +void test_split_string_preserves_non_escape_backslash() { + std::vector tokens; + tinyobj::SplitString("subdir\\file.mtl", ' ', '\\', tokens); + + TEST_CHECK(tokens.size() == 1); + TEST_CHECK(tokens[0] == "subdir\\file.mtl"); + + tokens.clear(); + tinyobj::SplitString("a\\ b.mtl", ' ', '\\', tokens); + TEST_CHECK(tokens.size() == 1); + TEST_CHECK(tokens[0] == "a b.mtl"); +} + +void test_numeric_edge_cases() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/numeric-edge-cases.obj"); + + if (!warn.empty()) std::cout << "WARN: " << warn << std::endl; + if (!err.empty()) std::cerr << "ERR: " << err << std::endl; + + TEST_CHECK(true == ret); + + // 16 vertices * 3 components = 48 + TEST_CHECK(attrib.vertices.size() == 48); + + // v0: 0 0 0 + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[0])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[1])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[2])); + + // v1: 1.5 -2.25 3.125 + TEST_CHECK(FloatEquals(1.5f, attrib.vertices[3])); + TEST_CHECK(FloatEquals(-2.25f, attrib.vertices[4])); + TEST_CHECK(FloatEquals(3.125f, attrib.vertices[5])); + + // v2: .5 -.75 .001 (leading decimal dot) + TEST_CHECK(FloatEquals(0.5f, attrib.vertices[6])); + TEST_CHECK(FloatEquals(-0.75f, attrib.vertices[7])); + TEST_CHECK(FloatEquals(0.001f, attrib.vertices[8])); + + // v3: 1. -2. 100. (trailing dot) + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[9])); + TEST_CHECK(FloatEquals(-2.0f, attrib.vertices[10])); + TEST_CHECK(FloatEquals(100.0f, attrib.vertices[11])); + + // v4: 1.5e2 -3.0e-4 7e10 (scientific notation lowercase) + TEST_CHECK(FloatEquals(150.0f, attrib.vertices[12])); + TEST_CHECK(FloatEquals(-3.0e-4f, attrib.vertices[13])); + TEST_CHECK(FloatEquals(7e10f, attrib.vertices[14])); + + // v5: 2.5E3 -1.0E-2 4E+5 (scientific notation uppercase) + TEST_CHECK(FloatEquals(2500.0f, attrib.vertices[15])); + TEST_CHECK(FloatEquals(-0.01f, attrib.vertices[16])); + TEST_CHECK(FloatEquals(400000.0f, attrib.vertices[17])); + + // v6: +1.0 +0.5 +100 (leading plus) + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[18])); + TEST_CHECK(FloatEquals(0.5f, attrib.vertices[19])); + TEST_CHECK(FloatEquals(100.0f, attrib.vertices[20])); + + // v7: 007.5 -003.14 000.001 (leading zeros) + TEST_CHECK(FloatEquals(7.5f, attrib.vertices[21])); + TEST_CHECK(FloatEquals(-3.14f, attrib.vertices[22])); + TEST_CHECK(FloatEquals(0.001f, attrib.vertices[23])); + + // v8: 1e-300 -1e-300 5e-310 (tiny values -- flush to zero in float) + // These are below float min, so they become 0 in float mode. + // Just check they parsed without error (ret == true above). + + // v9: 1.7976931348623157e+308 -1e+308 1e+307 + // These overflow float, but should not crash. Check parse succeeded. + + // v10: -0 -0.0 -0.0e0 (negative zero) + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[30])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[31])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[32])); + + // v11: 1.5e002 -3.0e+007 7e-003 (exponent with leading zeros) + TEST_CHECK(FloatEquals(150.0f, attrib.vertices[33])); + TEST_CHECK(FloatEquals(-3.0e7f, attrib.vertices[34])); + TEST_CHECK(FloatEquals(7e-3f, attrib.vertices[35])); + + // v12: 0 1 9 (single digit values) + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[36])); + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[37])); + TEST_CHECK(FloatEquals(9.0f, attrib.vertices[38])); + + // v13: 1e+0 1e-0 -1e+0 (exponent zero) + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[39])); + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[40])); + TEST_CHECK(FloatEquals(-1.0f, attrib.vertices[41])); + + // v14: pi, e, sqrt(2) (high precision) + TEST_CHECK(FloatEquals(3.141592653589793f, attrib.vertices[42])); + TEST_CHECK(FloatEquals(2.718281828459045f, attrib.vertices[43])); + TEST_CHECK(FloatEquals(1.4142135623730951f, attrib.vertices[44])); + + // v15: 1e1 1e-1 -1e1 (simple exponent) + TEST_CHECK(FloatEquals(10.0f, attrib.vertices[45])); + TEST_CHECK(FloatEquals(0.1f, attrib.vertices[46])); + TEST_CHECK(FloatEquals(-10.0f, attrib.vertices[47])); + + // Normals: 3 normals * 3 = 9 + TEST_CHECK(attrib.normals.size() == 9); + TEST_CHECK(FloatEquals(0.0f, attrib.normals[0])); + TEST_CHECK(FloatEquals(1.0f, attrib.normals[1])); + TEST_CHECK(FloatEquals(0.0f, attrib.normals[2])); + TEST_CHECK(FloatEquals(-0.707107f, attrib.normals[3])); + TEST_CHECK(FloatEquals(0.0f, attrib.normals[4])); + TEST_CHECK(FloatEquals(0.707107f, attrib.normals[5])); + TEST_CHECK(FloatEquals(1e-5f, attrib.normals[6])); + TEST_CHECK(FloatEquals(-1e-5f, attrib.normals[7])); + TEST_CHECK(FloatEquals(0.99999f, attrib.normals[8])); + + // Texcoords: 4 texcoords * 2 = 8 + TEST_CHECK(attrib.texcoords.size() == 8); + TEST_CHECK(FloatEquals(0.0f, attrib.texcoords[0])); + TEST_CHECK(FloatEquals(0.0f, attrib.texcoords[1])); + TEST_CHECK(FloatEquals(1.0f, attrib.texcoords[2])); + TEST_CHECK(FloatEquals(1.0f, attrib.texcoords[3])); + TEST_CHECK(FloatEquals(0.5f, attrib.texcoords[4])); + TEST_CHECK(FloatEquals(0.5f, attrib.texcoords[5])); + TEST_CHECK(FloatEquals(0.25f, attrib.texcoords[6])); + TEST_CHECK(FloatEquals(0.75f, attrib.texcoords[7])); +} + +void test_numeric_nan_inf() { + // Test nan/inf parsing via an in-memory OBJ string + std::string obj_str = + "v nan 0 0\n" + "v NaN 1 1\n" + "v NAN 2 2\n" + "v inf 0 0\n" + "v -inf 1 1\n" + "v Inf 2 2\n" + "v -Inf 3 3\n" + "v INF 4 4\n" + "v infinity 0 0\n" + "v -infinity 1 1\n" + "v +nan 0 0\n" + "v +inf 0 0\n" + "f 1 2 3\n"; + + std::istringstream obj_stream(obj_str); + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, NULL); + + if (!warn.empty()) std::cout << "WARN: " << warn << std::endl; + if (!err.empty()) std::cerr << "ERR: " << err << std::endl; + + TEST_CHECK(true == ret); + // 12 vertices * 3 components = 36 + TEST_CHECK(attrib.vertices.size() == 36); + + // All nan/inf should parse without crashing. + // The exact values depend on the implementation (nan -> 0.0, inf -> max, -inf -> lowest), + // but the parser must not fail or produce garbage for the non-nan/inf coords. + + // v0: nan 0 0 -> second and third should be 0 + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[1])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[2])); + + // v3: inf 0 0 -> second and third should be 0 + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[10])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[11])); + + // v4: -inf 1 1 + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[13])); + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[14])); +} + +void test_numeric_from_stream() { + // Test that stream-based loading also gets the same numeric results + std::string obj_str = + "v 1.5e2 -3.0e-4 +7.5\n" + "v .001 -.999 1.\n" + "v 0 0 0\n" + "f 1 2 3\n"; + + std::istringstream obj_stream(obj_str); + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, NULL); + + TEST_CHECK(true == ret); + TEST_CHECK(attrib.vertices.size() == 9); + + TEST_CHECK(FloatEquals(150.0f, attrib.vertices[0])); + TEST_CHECK(FloatEquals(-3.0e-4f, attrib.vertices[1])); + TEST_CHECK(FloatEquals(7.5f, attrib.vertices[2])); + + TEST_CHECK(FloatEquals(0.001f, attrib.vertices[3])); + TEST_CHECK(FloatEquals(-0.999f, attrib.vertices[4])); + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[5])); + + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[6])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[7])); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[8])); +} + +void test_numeric_overflow_preserves_default() { + // Regression: values that overflow double must not crash or corrupt memory. + // tryParseDouble now parses into a temp; *result is only written on success. + // With the StreamReader-based parser, overflow is detected as a parse error. + std::string obj_str = + "v 1e9999 2.0 3.0\n" // first coord overflows + "f 1\n"; + + std::istringstream obj_stream(obj_str); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, NULL); + + // Must not crash. Parser detects overflow and returns false. + TEST_CHECK(false == ret); + TEST_CHECK(!err.empty()); +} + +void test_numeric_empty_and_whitespace() { + // Regression: empty tokens, whitespace-only lines, and trailing whitespace + // must not crash the parser. + std::string obj_str = + "v 1.0 2.0 3.0 \n" // extra whitespace around values + "v 4.0 5.0 6.0\r\n" // Windows line endings + "v\t7.0\t8.0\t9.0\n" // tab-separated + "\n" // blank line + " \n" // whitespace-only line + "f 1 2 3\n"; + + std::istringstream obj_stream(obj_str); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, NULL); + + TEST_CHECK(true == ret); + TEST_CHECK(attrib.vertices.size() == 9); + + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[0])); + TEST_CHECK(FloatEquals(2.0f, attrib.vertices[1])); + TEST_CHECK(FloatEquals(3.0f, attrib.vertices[2])); + TEST_CHECK(FloatEquals(4.0f, attrib.vertices[3])); + TEST_CHECK(FloatEquals(5.0f, attrib.vertices[4])); + TEST_CHECK(FloatEquals(6.0f, attrib.vertices[5])); + TEST_CHECK(FloatEquals(7.0f, attrib.vertices[6])); + TEST_CHECK(FloatEquals(8.0f, attrib.vertices[7])); + TEST_CHECK(FloatEquals(9.0f, attrib.vertices[8])); +} + +void test_numeric_garbage_input() { + // Regression: totally invalid numeric input must not crash. + // With the StreamReader-based parser, garbage input is detected and + // LoadObj returns false with an error message. + std::string obj_str = + "v abc def ghi\n" // alphabetic garbage + "f 1\n"; + + std::istringstream obj_stream(obj_str); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, NULL); + + // Must not crash. Parser detects invalid input and returns false. + TEST_CHECK(false == ret); + TEST_CHECK(!err.empty()); + TEST_CHECK(err.find("expected number") != std::string::npos); +} + +void test_numeric_extreme_precision() { + // Regression: values with many digits must not crash or corrupt. + // fast_float handles arbitrary digit counts gracefully. + std::string obj_str = + "v 1.00000000000000000000000000000000000001 " + "2.99999999999999999999999999999999999999 " + "0.00000000000000000000000000000000000001\n" + "v 123456789012345678.0 -123456789012345678.0 0.0\n" + "f 1 2\n"; + + std::istringstream obj_stream(obj_str); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &obj_stream, NULL); + + TEST_CHECK(true == ret); + TEST_CHECK(attrib.vertices.size() == 6); + + // Values should round to nearest representable float + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[0])); + TEST_CHECK(FloatEquals(3.0f, attrib.vertices[1])); +} + +// --------------------------------------------------------------------------- +// Additional coverage tests +// --------------------------------------------------------------------------- + +// StreamReader: direct unit tests for public API methods +void test_streamreader_eof_and_remaining() { + // Empty input + { + tinyobj::StreamReader sr("", 0); + TEST_CHECK(sr.eof() == true); + TEST_CHECK(sr.remaining() == 0); + TEST_CHECK(sr.peek() == '\0'); + TEST_CHECK(sr.peek_at(0) == '\0'); + TEST_CHECK(sr.peek_at(100) == '\0'); + TEST_CHECK(sr.get() == '\0'); + TEST_CHECK(sr.char_at(0, 'a') == false); + TEST_CHECK(sr.match("abc", 3) == false); + TEST_CHECK(sr.line_num() == 1); + TEST_CHECK(sr.col_num() == 1); + } + // Single char + { + tinyobj::StreamReader sr("x", 1); + TEST_CHECK(sr.eof() == false); + TEST_CHECK(sr.remaining() == 1); + TEST_CHECK(sr.peek() == 'x'); + TEST_CHECK(sr.char_at(0, 'x') == true); + TEST_CHECK(sr.char_at(0, 'y') == false); + TEST_CHECK(sr.char_at(1, 'x') == false); // out of bounds + TEST_CHECK(sr.match("x", 1) == true); + TEST_CHECK(sr.match("xy", 2) == false); + char c = sr.get(); + TEST_CHECK(c == 'x'); + TEST_CHECK(sr.eof() == true); + TEST_CHECK(sr.remaining() == 0); + // After EOF, these should be safe + TEST_CHECK(sr.peek() == '\0'); + TEST_CHECK(sr.peek_at(0) == '\0'); + TEST_CHECK(sr.match("a", 1) == false); + TEST_CHECK(sr.char_at(0, 'a') == false); + } +} + +void test_streamreader_skip_and_read() { + const char *input = " hello \t world\r\nline2\n"; + tinyobj::StreamReader sr(input, strlen(input)); + + // skip_space should skip spaces and tabs + sr.skip_space(); + TEST_CHECK(sr.peek() == 'h'); + TEST_CHECK(sr.col_num() == 3); + + // read_token should return "hello" + std::string tok = sr.read_token(); + TEST_CHECK(tok == "hello"); + + // skip_space should skip " \t " + sr.skip_space(); + TEST_CHECK(sr.peek() == 'w'); + + // read_token should return "world" + tok = sr.read_token(); + TEST_CHECK(tok == "world"); + + // at_line_end should be true (next is \r\n) + TEST_CHECK(sr.at_line_end() == true); + + // skip_line should advance past \r\n + sr.skip_line(); + TEST_CHECK(sr.line_num() == 2); + TEST_CHECK(sr.col_num() == 1); + + // read_line should return "line2" + std::string line = sr.read_line(); + TEST_CHECK(line == "line2"); + // read_line reads the content but line_num updates on skip_line/get past \n + TEST_CHECK(sr.line_num() == 2); +} + +void test_streamreader_match_and_advance() { + const char *input = "mtllib foo.mtl\n"; + tinyobj::StreamReader sr(input, strlen(input)); + + TEST_CHECK(sr.match("mtllib", 6) == true); + TEST_CHECK(sr.match("mtlliX", 6) == false); + TEST_CHECK(sr.match("mtllib foo.mtl\n", 15) == true); + // match longer than remaining + TEST_CHECK(sr.match("mtllib foo.mtl\nX", 16) == false); + + sr.advance(7); // past "mtllib " + TEST_CHECK(sr.peek() == 'f'); + TEST_CHECK(sr.col_num() == 8); + + // advance past end should clamp to EOF + sr.advance(1000); + TEST_CHECK(sr.eof() == true); +} + +void test_streamreader_current_line_text() { + const char *input = "first line\nsecond line\nthird\n"; + tinyobj::StreamReader sr(input, strlen(input)); + + // On first line + std::string lt = sr.current_line_text(); + TEST_CHECK(lt == "first line"); + + sr.skip_line(); // move to second line + lt = sr.current_line_text(); + TEST_CHECK(lt == "second line"); + + sr.advance(3); // in the middle of "second line" -> "con" in "second" + lt = sr.current_line_text(); + TEST_CHECK(lt == "second line"); +} + +void test_streamreader_error_stack() { + const char *input = "hello\n"; + tinyobj::StreamReader sr(input, strlen(input)); + + TEST_CHECK(sr.has_errors() == false); + TEST_CHECK(sr.get_errors().empty()); + + sr.push_error("error 1"); + TEST_CHECK(sr.has_errors() == true); + TEST_CHECK(sr.error_stack().size() == 1); + + sr.push_error("error 2"); + TEST_CHECK(sr.error_stack().size() == 2); + // get_errors() concatenates all errors into a single string + TEST_CHECK(sr.get_errors().find("error 1") != std::string::npos); + TEST_CHECK(sr.get_errors().find("error 2") != std::string::npos); + + sr.clear_errors(); + TEST_CHECK(sr.has_errors() == false); + TEST_CHECK(sr.get_errors().empty()); +} + +// Empty OBJ file (0 bytes) +void test_empty_obj_file() { + std::istringstream iss(""); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.empty()); + TEST_CHECK(shapes.empty()); +} + +// OBJ with only BOM (3 bytes, no content) +void test_bom_only_obj() { + std::string bom("\xEF\xBB\xBF"); + std::istringstream iss(bom); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.empty()); +} + +// File with no trailing newline +void test_no_trailing_newline() { + const char *obj_text = "v 1.0 2.0 3.0\nv 4.0 5.0 6.0"; // no \n at end + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 6); + TEST_CHECK(FloatEquals(4.0f, attrib.vertices[3])); + TEST_CHECK(FloatEquals(5.0f, attrib.vertices[4])); + TEST_CHECK(FloatEquals(6.0f, attrib.vertices[5])); +} + +// Mixed CRLF, LF, and CR-only line endings +void test_mixed_line_endings() { + // LF, CRLF, CR-only, and no trailing newline + std::string obj_text = "v 1.0 2.0 3.0\n" + "v 4.0 5.0 6.0\r\n" + "v 7.0 8.0 9.0\r" + "f 1 2 3"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 9); + TEST_CHECK(FloatEquals(7.0f, attrib.vertices[6])); + TEST_CHECK(FloatEquals(8.0f, attrib.vertices[7])); + TEST_CHECK(FloatEquals(9.0f, attrib.vertices[8])); + TEST_CHECK(shapes.size() == 1); +} + +// Vertex colors from in-memory stream (6-component vertices) +void test_vertex_colors_from_stream() { + const char *obj_text = + "v 1.0 2.0 3.0 0.5 0.6 0.7\n" + "v 4.0 5.0 6.0 0.1 0.2 0.3\n" + "f 1 2 1\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 6); + TEST_CHECK(attrib.colors.size() == 6); + TEST_CHECK(FloatEquals(0.5f, attrib.colors[0])); + TEST_CHECK(FloatEquals(0.6f, attrib.colors[1])); + TEST_CHECK(FloatEquals(0.7f, attrib.colors[2])); + TEST_CHECK(FloatEquals(0.1f, attrib.colors[3])); + TEST_CHECK(FloatEquals(0.2f, attrib.colors[4])); + TEST_CHECK(FloatEquals(0.3f, attrib.colors[5])); +} + +// Mixed: some vertices with colors, some without +void test_vertex_colors_mixed() { + const char *obj_text = + "v 1.0 2.0 3.0 0.5 0.6 0.7\n" + "v 4.0 5.0 6.0\n" + "f 1 2 1\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 6); + // Colors array should have entries for both vertices (default 1.0 for no-color vertex) + TEST_CHECK(attrib.colors.size() == 6); + TEST_CHECK(FloatEquals(0.5f, attrib.colors[0])); + TEST_CHECK(FloatEquals(0.6f, attrib.colors[1])); + TEST_CHECK(FloatEquals(0.7f, attrib.colors[2])); + TEST_CHECK(FloatEquals(1.0f, attrib.colors[3])); + TEST_CHECK(FloatEquals(1.0f, attrib.colors[4])); + TEST_CHECK(FloatEquals(1.0f, attrib.colors[5])); +} + +// OBJ with all element types: v, vn, vt, f, l, p +void test_all_element_types() { + const char *obj_text = + "v 1.0 0.0 0.0\n" + "v 0.0 1.0 0.0\n" + "v 0.0 0.0 1.0\n" + "vn 0.0 0.0 1.0\n" + "vt 0.5 0.5\n" + "f 1/1/1 2/1/1 3/1/1\n" + "l 1 2\n" + "p 3\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 9); + TEST_CHECK(attrib.normals.size() == 3); + TEST_CHECK(attrib.texcoords.size() == 2); + TEST_CHECK(shapes.size() >= 1); + // Face indices + TEST_CHECK(shapes[0].mesh.indices.size() == 3); + // Line indices + TEST_CHECK(shapes[0].lines.indices.size() == 2); + // Point indices + TEST_CHECK(shapes[0].points.indices.size() == 1); +} + +// Multiple groups and objects +void test_multiple_objects() { + const char *obj_text = + "v 0.0 0.0 0.0\n" + "v 1.0 0.0 0.0\n" + "v 0.0 1.0 0.0\n" + "v 0.0 0.0 1.0\n" + "o obj1\n" + "f 1 2 3\n" + "o obj2\n" + "f 2 3 4\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(shapes.size() == 2); + TEST_CHECK(shapes[0].name == "obj1"); + TEST_CHECK(shapes[1].name == "obj2"); + TEST_CHECK(shapes[0].mesh.indices.size() == 3); + TEST_CHECK(shapes[1].mesh.indices.size() == 3); +} + +// MTL warning accumulation (d and Tr conflict) +void test_mtl_d_and_tr_warning() { + // Both d and Tr in same material should produce a warning + const char *mtl_text = "newmtl test\nd 0.5\nTr 0.8\n"; + std::istringstream mtl_iss(mtl_text); + std::map matMap; + std::vector materials; + std::string warn, err; + tinyobj::LoadMtl(&matMap, &materials, &mtl_iss, &warn, &err); + TEST_CHECK(materials.size() == 1); + // d=0.5 should win over Tr=0.8 + TEST_CHECK(FloatEquals(0.5f, materials[0].dissolve)); +} + +// Multiple malformed lines: errors should accumulate +void test_multiple_malformed_vertices() { + const char *obj_text = + "v 1.0 bad1 3.0\n" + "v 4.0 bad2 6.0\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == false); + // First error causes early return, so at least one error must be present + TEST_CHECK(err.find("bad1") != std::string::npos); +} + +// Malformed normal +void test_malformed_normal_error() { + const char *obj_text = "vn 1.0 xyz 0.0\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == false); + TEST_CHECK(err.find("expected number") != std::string::npos); +} + +// Malformed texcoord +void test_malformed_texcoord_error() { + const char *obj_text = "vt abc 0.5\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == false); + TEST_CHECK(err.find("expected number") != std::string::npos); +} + +// Negative vertex indices (relative indexing) +void test_negative_vertex_indices() { + const char *obj_text = + "v 1.0 0.0 0.0\n" + "v 0.0 1.0 0.0\n" + "v 0.0 0.0 1.0\n" + "f -3 -2 -1\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(shapes.size() == 1); + TEST_CHECK(shapes[0].mesh.indices.size() == 3); + // -3 should resolve to index 0, -2 to 1, -1 to 2 + TEST_CHECK(shapes[0].mesh.indices[0].vertex_index == 0); + TEST_CHECK(shapes[0].mesh.indices[1].vertex_index == 1); + TEST_CHECK(shapes[0].mesh.indices[2].vertex_index == 2); +} + +// Comments everywhere (inline and full-line) +void test_comments_everywhere() { + const char *obj_text = + "# full line comment\n" + "v 1.0 2.0 3.0 # inline comment\n" + " # indented comment\n" + "v 4.0 5.0 6.0\n" + "# another comment\n" + "f 1 2 1 # face comment\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 6); + TEST_CHECK(shapes.size() == 1); +} + +// Multiple spaces/tabs between tokens +void test_excessive_whitespace() { + const char *obj_text = + "v 1.0 \t 2.0 \t\t 3.0\n" + "v\t4.0\t5.0\t6.0\n" + "f 1 2 1\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 6); + TEST_CHECK(FloatEquals(1.0f, attrib.vertices[0])); + TEST_CHECK(FloatEquals(2.0f, attrib.vertices[1])); + TEST_CHECK(FloatEquals(3.0f, attrib.vertices[2])); + TEST_CHECK(FloatEquals(4.0f, attrib.vertices[3])); +} + +// v2 ObjReader API +void test_objreader_api_stream() { + const char *obj_text = + "v 1.0 2.0 3.0\n" + "v 4.0 5.0 6.0\n" + "v 7.0 8.0 9.0\n" + "f 1 2 3\n"; + + tinyobj::ObjReader reader; + bool ret = reader.ParseFromString(obj_text, ""); + TEST_CHECK(ret == true); + TEST_CHECK(reader.Valid()); + TEST_CHECK(reader.GetAttrib().vertices.size() == 9); + TEST_CHECK(reader.GetShapes().size() == 1); + // Warning output is optional and is not asserted here. +} + +// ObjReader with invalid input +void test_objreader_api_error() { + tinyobj::ObjReader reader; + bool ret = reader.ParseFromString("v 1.0 badval 3.0\n", ""); + TEST_CHECK(ret == false); + TEST_CHECK(!reader.Error().empty()); +} + +// SplitString edge cases +void test_split_string_edge_cases() { + std::vector tokens; + + // Empty input — SplitString always pushes at least one token (possibly empty) + tinyobj::SplitString("", ' ', '\\', tokens); + TEST_CHECK(tokens.size() == 1); + TEST_CHECK(tokens[0].empty()); + + // Only spaces — trailing token is empty + tokens.clear(); + tinyobj::SplitString(" ", ' ', '\\', tokens); + TEST_CHECK(tokens.size() == 1); + TEST_CHECK(tokens[0].empty()); + + // Multiple tokens with multiple delimiters + tokens.clear(); + tinyobj::SplitString("a b c", ' ', '\\', tokens); + TEST_CHECK(tokens.size() == 3); + TEST_CHECK(tokens[0] == "a"); + TEST_CHECK(tokens[1] == "b"); + TEST_CHECK(tokens[2] == "c"); + + // Escaped space in middle + tokens.clear(); + tinyobj::SplitString("path\\ name.mtl other.mtl", ' ', '\\', tokens); + TEST_CHECK(tokens.size() == 2); + TEST_CHECK(tokens[0] == "path name.mtl"); + TEST_CHECK(tokens[1] == "other.mtl"); + + // Trailing backslash (not an escape — preserved as-is) + tokens.clear(); + tinyobj::SplitString("dir\\", ' ', '\\', tokens); + TEST_CHECK(tokens.size() == 1); + TEST_CHECK(tokens[0] == "dir\\"); +} + +// Quad face (non-triangle) +void test_quad_face() { + const char *obj_text = + "v 0.0 0.0 0.0\n" + "v 1.0 0.0 0.0\n" + "v 1.0 1.0 0.0\n" + "v 0.0 1.0 0.0\n" + "f 1 2 3 4\n"; + std::istringstream iss(obj_text); + tinyobj::ObjReaderConfig config; + config.triangulate = false; + tinyobj::ObjReader reader; + bool ret = reader.ParseFromString(obj_text, "", config); + TEST_CHECK(ret == true); + TEST_CHECK(reader.GetShapes().size() == 1); + // Without triangulation: 4 indices, 1 face + TEST_CHECK(reader.GetShapes()[0].mesh.indices.size() == 4); + TEST_CHECK(reader.GetShapes()[0].mesh.num_face_vertices.size() == 1); + TEST_CHECK(reader.GetShapes()[0].mesh.num_face_vertices[0] == 4); +} + +// Quad face with triangulation +void test_quad_face_triangulated() { + const char *obj_text = + "v 0.0 0.0 0.0\n" + "v 1.0 0.0 0.0\n" + "v 1.0 1.0 0.0\n" + "v 0.0 1.0 0.0\n" + "f 1 2 3 4\n"; + std::istringstream iss(obj_text); + tinyobj::ObjReaderConfig config; + config.triangulate = true; + tinyobj::ObjReader reader; + bool ret = reader.ParseFromString(obj_text, "", config); + TEST_CHECK(ret == true); + TEST_CHECK(reader.GetShapes().size() == 1); + // With triangulation: quad -> 2 triangles = 6 indices + TEST_CHECK(reader.GetShapes()[0].mesh.indices.size() == 6); + TEST_CHECK(reader.GetShapes()[0].mesh.num_face_vertices.size() == 2); +} + +// Face with v/vt/vn format +void test_face_full_index_format() { + const char *obj_text = + "v 0.0 0.0 0.0\n" + "v 1.0 0.0 0.0\n" + "v 0.0 1.0 0.0\n" + "vt 0.0 0.0\n" + "vt 1.0 0.0\n" + "vt 0.0 1.0\n" + "vn 0.0 0.0 1.0\n" + "f 1/1/1 2/2/1 3/3/1\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(shapes[0].mesh.indices[0].vertex_index == 0); + TEST_CHECK(shapes[0].mesh.indices[0].texcoord_index == 0); + TEST_CHECK(shapes[0].mesh.indices[0].normal_index == 0); + TEST_CHECK(shapes[0].mesh.indices[1].vertex_index == 1); + TEST_CHECK(shapes[0].mesh.indices[1].texcoord_index == 1); + TEST_CHECK(shapes[0].mesh.indices[2].vertex_index == 2); + TEST_CHECK(shapes[0].mesh.indices[2].texcoord_index == 2); +} + +// Face with v//vn format (no texcoord) +void test_face_vertex_normal_only() { + const char *obj_text = + "v 0.0 0.0 0.0\n" + "v 1.0 0.0 0.0\n" + "v 0.0 1.0 0.0\n" + "vn 0.0 0.0 1.0\n" + "f 1//1 2//1 3//1\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(shapes[0].mesh.indices[0].vertex_index == 0); + TEST_CHECK(shapes[0].mesh.indices[0].texcoord_index == -1); + TEST_CHECK(shapes[0].mesh.indices[0].normal_index == 0); +} + +// MTL with multiple materials and various properties +void test_mtl_multiple_properties() { + const char *mtl_text = + "newmtl mat1\n" + "Ka 0.1 0.2 0.3\n" + "Kd 0.4 0.5 0.6\n" + "Ks 0.7 0.8 0.9\n" + "Ns 100.0\n" + "d 0.5\n" + "illum 2\n" + "\n" + "newmtl mat2\n" + "Ka 0.0 0.0 0.0\n" + "Kd 1.0 1.0 1.0\n" + "Ns 50.0\n"; + std::istringstream mtl_iss(mtl_text); + std::map matMap; + std::vector materials; + std::string warn, err; + tinyobj::LoadMtl(&matMap, &materials, &mtl_iss, &warn, &err); + TEST_CHECK(materials.size() == 2); + TEST_CHECK(materials[0].name == "mat1"); + TEST_CHECK(FloatEquals(0.1f, materials[0].ambient[0])); + TEST_CHECK(FloatEquals(0.4f, materials[0].diffuse[0])); + TEST_CHECK(FloatEquals(0.7f, materials[0].specular[0])); + TEST_CHECK(FloatEquals(100.0f, materials[0].shininess)); + TEST_CHECK(FloatEquals(0.5f, materials[0].dissolve)); + TEST_CHECK(materials[0].illum == 2); + TEST_CHECK(materials[1].name == "mat2"); + TEST_CHECK(FloatEquals(1.0f, materials[1].diffuse[0])); + TEST_CHECK(FloatEquals(50.0f, materials[1].shininess)); +} + +// Callback API: vertices, normals, texcoords, and faces +void test_callback_all_elements() { + const char *obj_text = + "v 1.0 2.0 3.0\n" + "v 4.0 5.0 6.0\n" + "v 7.0 8.0 9.0\n" + "vn 0.0 0.0 1.0\n" + "vt 0.5 0.5\n" + "f 1 2 3\n"; + std::istringstream iss(obj_text); + + struct Counts { + int vertices; + int normals; + int texcoords; + int faces; + }; + Counts counts = {0, 0, 0, 0}; + + tinyobj::callback_t cb; + cb.vertex_cb = [](void *user, float, float, float, float) { + static_cast(user)->vertices++; + }; + cb.normal_cb = [](void *user, float, float, float) { + static_cast(user)->normals++; + }; + cb.texcoord_cb = [](void *user, float, float, float) { + static_cast(user)->texcoords++; + }; + cb.index_cb = [](void *user, tinyobj::index_t *, int) { + static_cast(user)->faces++; + }; + + std::string warn, err; + bool ret = tinyobj::LoadObjWithCallback(iss, cb, &counts, NULL, &warn, &err); + TEST_CHECK(ret == true); + TEST_CHECK(counts.vertices == 3); + TEST_CHECK(counts.normals == 1); + TEST_CHECK(counts.texcoords == 1); + TEST_CHECK(counts.faces == 1); +} + +// Callback API with NULL callbacks (should not crash) +void test_callback_null_callbacks() { + const char *obj_text = + "v 1.0 2.0 3.0\n" + "vn 0.0 0.0 1.0\n" + "vt 0.5 0.5\n" + "f 1/1/1 1/1/1 1/1/1\n"; + std::istringstream iss(obj_text); + + tinyobj::callback_t cb; + // All callbacks are NULL by default + std::string warn, err; + bool ret = tinyobj::LoadObjWithCallback(iss, cb, NULL, NULL, &warn, &err); + TEST_CHECK(ret == true); +} + +// Subnormal float values should parse without error +void test_numeric_subnormal_values() { + // 5e-310 is subnormal for double, 1e-45 is subnormal for float + const char *obj_text = "v 5e-310 1e-45 0.0\n"; + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 3); + // Values should be >= 0 (either the subnormal or flushed to zero) + TEST_CHECK(attrib.vertices[0] >= 0.0f); + TEST_CHECK(attrib.vertices[1] >= 0.0f); + TEST_CHECK(FloatEquals(0.0f, attrib.vertices[2])); +} + +// Empty MTL should not produce a phantom material +void test_empty_mtl_no_phantom_material() { + const char *mtl_text = "# just a comment\n"; + std::istringstream mtl_iss(mtl_text); + std::map matMap; + std::vector materials; + std::string warn, err; + tinyobj::LoadMtl(&matMap, &materials, &mtl_iss, &warn, &err); + TEST_CHECK(materials.empty()); + TEST_CHECK(matMap.empty()); +} + +// StreamReader should not be copyable (deleted copy constructor) +void test_streamreader_not_copyable() { + // This is a compile-time check. If StreamReader were copyable, + // copying one built from istream would create a dangling buf_ pointer. + // We just verify construction and basic use work correctly. + const char *input = "hello"; + tinyobj::StreamReader sr(input, 5); + TEST_CHECK(sr.remaining() == 5); + TEST_CHECK(sr.peek() == 'h'); +} + +// Out-of-range face indices should not crash +void test_out_of_range_face_index() { + const char *obj_text = + "v 1.0 0.0 0.0\n" + "v 0.0 1.0 0.0\n" + "v 0.0 0.0 1.0\n" + "f 1 2 999\n"; // index 999 doesn't exist + std::istringstream iss(obj_text); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &iss, NULL); + // Should parse without crashing. The face will have an out-of-range index + // which may produce a warning during triangulation. + TEST_CHECK(ret == true); + TEST_CHECK(attrib.vertices.size() == 9); +} + // Fuzzer test. // Just check if it does not crash. // Disable by default since Windows filesystem can't create filename of afl @@ -1503,7 +3392,7 @@ TEST_LIST = { test_usemtl_then_o_issue235}, {"mtl_searchpaths_issue244", test_mtl_searchpaths_issue244}, - {"usemtl_whitespece_issue246", + {"usemtl_whitespace_issue246", test_usemtl_whitespace_issue246}, {"texres_texopt_issue248", test_texres_texopt_issue248}, @@ -1511,8 +3400,86 @@ TEST_LIST = { test_mtl_filename_with_whitespace_issue46}, {"test_face_missing_issue295", test_face_missing_issue295}, + {"test_comment_issue389", + test_comment_issue389}, {"test_invalid_relative_vertex_index", test_invalid_relative_vertex_index}, {"test_invalid_texture_vertex_index", test_invalid_texture_vertex_index}, + {"default_kd_for_multiple_materials_issue391", + test_default_kd_for_multiple_materials_issue391}, + {"test_removeUtf8Bom", test_removeUtf8Bom}, + {"test_loadObj_with_BOM", test_loadObj_with_BOM}, + {"test_load_obj_from_utf8_path", test_load_obj_from_utf8_path}, + {"test_load_obj_from_long_path", test_load_obj_from_long_path}, + {"test_loadObjWithCallback_with_BOM", test_loadObjWithCallback_with_BOM}, + {"test_loadObjWithCallback_mtllib_failure_does_not_crash", + test_loadObjWithCallback_mtllib_failure_does_not_crash}, + {"test_mtllib_empty_filename_is_ignored_loadobj", + test_mtllib_empty_filename_is_ignored_loadobj}, + {"test_mtllib_empty_filename_is_ignored_callback", + test_mtllib_empty_filename_is_ignored_callback}, + {"test_usemtl_callback_trims_trailing_comment", + test_usemtl_callback_trims_trailing_comment}, + {"test_tag_triple_huge_count_is_safely_rejected", + test_tag_triple_huge_count_is_safely_rejected}, + {"test_texcoord_w_component", test_texcoord_w_component}, + {"test_texcoord_w_mixed_component", test_texcoord_w_mixed_component}, + {"test_numeric_edge_cases", test_numeric_edge_cases}, + {"test_numeric_nan_inf", test_numeric_nan_inf}, + {"test_numeric_from_stream", test_numeric_from_stream}, + {"test_numeric_overflow_preserves_default", test_numeric_overflow_preserves_default}, + {"test_numeric_empty_and_whitespace", test_numeric_empty_and_whitespace}, + {"test_numeric_garbage_input", test_numeric_garbage_input}, + {"test_numeric_extreme_precision", test_numeric_extreme_precision}, + {"test_file_and_stream_load_agree", test_file_and_stream_load_agree}, + {"test_load_from_memory_buffer", test_load_from_memory_buffer}, + {"test_streamreader_column_tracking", test_streamreader_column_tracking}, + {"test_stream_load_from_current_offset", test_stream_load_from_current_offset}, + {"test_stream_load_rejects_oversized_input", test_stream_load_rejects_oversized_input}, + {"test_error_format_clang_style", test_error_format_clang_style}, + {"test_error_stack", test_error_stack}, + {"test_malformed_vertex_error", test_malformed_vertex_error}, + {"test_malformed_mtl_error", test_malformed_mtl_error}, + {"test_parse_error_backward_compat", test_parse_error_backward_compat}, + {"test_split_string_preserves_non_escape_backslash", + test_split_string_preserves_non_escape_backslash}, + {"test_streamreader_eof_and_remaining", + test_streamreader_eof_and_remaining}, + {"test_streamreader_skip_and_read", test_streamreader_skip_and_read}, + {"test_streamreader_match_and_advance", + test_streamreader_match_and_advance}, + {"test_streamreader_current_line_text", + test_streamreader_current_line_text}, + {"test_streamreader_error_stack", test_streamreader_error_stack}, + {"test_empty_obj_file", test_empty_obj_file}, + {"test_bom_only_obj", test_bom_only_obj}, + {"test_no_trailing_newline", test_no_trailing_newline}, + {"test_mixed_line_endings", test_mixed_line_endings}, + {"test_vertex_colors_from_stream", test_vertex_colors_from_stream}, + {"test_vertex_colors_mixed", test_vertex_colors_mixed}, + {"test_all_element_types", test_all_element_types}, + {"test_multiple_objects", test_multiple_objects}, + {"test_mtl_d_and_tr_warning", test_mtl_d_and_tr_warning}, + {"test_multiple_malformed_vertices", test_multiple_malformed_vertices}, + {"test_malformed_normal_error", test_malformed_normal_error}, + {"test_malformed_texcoord_error", test_malformed_texcoord_error}, + {"test_negative_vertex_indices", test_negative_vertex_indices}, + {"test_comments_everywhere", test_comments_everywhere}, + {"test_excessive_whitespace", test_excessive_whitespace}, + {"test_objreader_api_stream", test_objreader_api_stream}, + {"test_objreader_api_error", test_objreader_api_error}, + {"test_split_string_edge_cases", test_split_string_edge_cases}, + {"test_quad_face", test_quad_face}, + {"test_quad_face_triangulated", test_quad_face_triangulated}, + {"test_face_full_index_format", test_face_full_index_format}, + {"test_face_vertex_normal_only", test_face_vertex_normal_only}, + {"test_mtl_multiple_properties", test_mtl_multiple_properties}, + {"test_callback_all_elements", test_callback_all_elements}, + {"test_callback_null_callbacks", test_callback_null_callbacks}, + {"test_numeric_subnormal_values", test_numeric_subnormal_values}, + {"test_empty_mtl_no_phantom_material", + test_empty_mtl_no_phantom_material}, + {"test_streamreader_not_copyable", test_streamreader_not_copyable}, + {"test_out_of_range_face_index", test_out_of_range_face_index}, {NULL, NULL}}; diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index c23acc0d..af98ac2d 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -70,12 +70,11 @@ THE SOFTWARE. namespace tinyobj { -// TODO(syoyo): Better C++11 detection for older compiler -#if __cplusplus > 199711L -#define TINYOBJ_OVERRIDE override -#else -#define TINYOBJ_OVERRIDE +// C++11 is now the minimum required standard. +#if __cplusplus < 201103L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201103L) +#error "tinyobjloader requires C++11 or later. Compile with -std=c++11 or higher." #endif +#define TINYOBJ_OVERRIDE override #ifdef __clang__ #pragma clang diagnostic push @@ -657,12 +656,35 @@ bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, #ifdef TINYOBJLOADER_IMPLEMENTATION #include #include +#include #include #include +#include +#include #include #include #include #include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +#ifdef TINYOBJLOADER_USE_MMAP +#if !defined(_WIN32) +// POSIX headers for mmap +#include +#include +#include +#include +#endif +#endif // TINYOBJLOADER_USE_MMAP #include #include #include @@ -690,131 +712,5124 @@ bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, #endif // TINYOBJLOADER_USE_MAPBOX_EARCUT -namespace tinyobj { +#ifdef _WIN32 +// Converts a UTF-8 encoded string to a UTF-16 wide string for use with +// Windows file APIs that support Unicode paths (including paths longer than +// MAX_PATH when combined with the extended-length path prefix). +static std::wstring UTF8ToWchar(const std::string &str) { + if (str.empty()) return std::wstring(); + int size_needed = + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), + static_cast(str.size()), NULL, 0); + if (size_needed == 0) return std::wstring(); + std::wstring wstr(static_cast(size_needed), L'\0'); + int result = + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), + static_cast(str.size()), &wstr[0], size_needed); + if (result == 0) return std::wstring(); + return wstr; +} -MaterialReader::~MaterialReader() {} +// Prepends the Windows extended-length path prefix ("\\?\") to an absolute +// path when the path length meets or exceeds MAX_PATH (260 characters). +// This allows Windows APIs to handle paths up to 32767 characters long. +// UNC paths (starting with "\\") are converted to "\\?\UNC\" form. +static std::wstring LongPathW(const std::wstring &wpath) { + const std::wstring kLongPathPrefix = L"\\\\?\\"; + const std::wstring kUNCPrefix = L"\\\\"; + const std::wstring kLongUNCPathPrefix = L"\\\\?\\UNC\\"; + + // Already has the extended-length prefix; return as-is. + if (wpath.size() >= kLongPathPrefix.size() && + wpath.substr(0, kLongPathPrefix.size()) == kLongPathPrefix) { + return wpath; + } -struct vertex_index_t { - int v_idx, vt_idx, vn_idx; - vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} - explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} - vertex_index_t(int vidx, int vtidx, int vnidx) - : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} -}; + // Only add the prefix when the path is long enough to require it. + if (wpath.size() < MAX_PATH) { + return wpath; + } -// Internal data structure for face representation -// index + smoothing group. -struct face_t { - unsigned int - smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. - int pad_; - std::vector vertex_indices; // face vertex indices. + // Normalize forward slashes to backslashes: the extended-length "\\?\" + // prefix requires backslash separators only. + std::wstring normalized = wpath; + for (std::wstring::size_type i = 0; i < normalized.size(); ++i) { + if (normalized[i] == L'/') normalized[i] = L'\\'; + } - face_t() : smoothing_group_id(0), pad_(0) {} -}; + // UNC path: "\\server\share\..." -> "\\?\UNC\server\share\..." + if (normalized.size() >= kUNCPrefix.size() && + normalized.substr(0, kUNCPrefix.size()) == kUNCPrefix) { + return kLongUNCPathPrefix + normalized.substr(kUNCPrefix.size()); + } -// Internal data structure for line representation -struct __line_t { - // l v1/vt1 v2/vt2 ... - // In the specification, line primitrive does not have normal index, but - // TinyObjLoader allow it - std::vector vertex_indices; + // Absolute path with drive letter: "C:\..." -> "\\?\C:\..." + if (normalized.size() >= 2 && normalized[1] == L':') { + return kLongPathPrefix + normalized; + } + + return normalized; +} +#endif // _WIN32 + +// -------------------------------------------------------------------------- +// Embedded fast_float v8.0.2 for high-performance, bit-exact float parsing. +// Disable by defining TINYOBJLOADER_DISABLE_FAST_FLOAT before including +// this file with TINYOBJLOADER_IMPLEMENTATION. +// -------------------------------------------------------------------------- +#ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT + +// Standard headers needed by the embedded fast_float. +#include +#include + +namespace tinyobj_ff { + +// --- integral_constant, true_type, false_type --- +template +struct integral_constant { + static const T value = V; + typedef T value_type; + typedef integral_constant type; + operator value_type() const { return value; } +}; +typedef integral_constant true_type; +typedef integral_constant false_type; + +// --- is_same --- +template struct is_same : false_type {}; +template struct is_same : true_type {}; + +// --- enable_if --- +template struct enable_if {}; +template struct enable_if { typedef T type; }; + +// --- conditional --- +template struct conditional { typedef T type; }; +template struct conditional { typedef F type; }; + +// --- is_integral --- +template struct is_integral : false_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; +template <> struct is_integral : true_type {}; + +// --- is_signed --- +template struct is_signed : integral_constant {}; + +// --- underlying_type (uses compiler builtin) --- +template struct underlying_type { + typedef __underlying_type(T) type; }; -// Internal data structure for points representation -struct __points_t { - // p v1 v2 ... - // In the specification, point primitrive does not have normal index and - // texture coord index, but TinyObjLoader allow it. - std::vector vertex_indices; +// --- ff_errc (replaces std::errc, our own enum - no system_error needed) --- +enum class ff_errc { ok = 0, invalid_argument = 22, result_out_of_range = 34 }; + +// --- min_val (replaces std::min, avoids Windows min/max macro conflicts) --- +template +inline T min_val(T a, T b) { return (b < a) ? b : a; } + +// --- copy_n --- +template +inline OutputIt copy_n(InputIt first, Size count, OutputIt result) { + for (Size i = 0; i < count; ++i) *result++ = *first++; + return result; +} + +// --- copy_backward --- +template +inline BidirIt2 copy_backward(BidirIt1 first, BidirIt1 last, BidirIt2 d_last) { + while (first != last) *(--d_last) = *(--last); + return d_last; +} + +// --- fill --- +template +inline void fill(ForwardIt first, ForwardIt last, const T &value) { + for (; first != last; ++first) *first = value; +} + +// --- distance --- +template +inline typename conditional::type +distance(It first, It last) { + return last - first; +} + +} // namespace tinyobj_ff + +// --- Begin embedded fast_float v8.0.2 (MIT / Apache-2.0 / BSL-1.0) --- +// https://github.com/fastfloat/fast_float +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// with contributions from Lénárd Szolnoki +// with contributions from Jan Pharago +// with contributions from Maya Warrier +// with contributions from Taha Khokhar +// with contributions from Anders Dalvander +// +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License or the Boost License. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// +// BOOST License Notice +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +// Testing for https://wg21.link/N3652, adopted in C++14 +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 +#define FASTFLOAT_CONSTEXPR14 constexpr +#else +#define FASTFLOAT_CONSTEXPR14 +#endif + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +#define FASTFLOAT_HAS_BIT_CAST 1 +#else +#define FASTFLOAT_HAS_BIT_CAST 0 +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && \ + __cpp_lib_is_constant_evaluated >= 201811L +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#else +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#endif + +#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L +#define FASTFLOAT_IF_CONSTEXPR17(x) if constexpr (x) +#else +#define FASTFLOAT_IF_CONSTEXPR17(x) if (x) +#endif + +// Testing for relevant C++20 constexpr library features +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ + defined(__cpp_lib_constexpr_algorithms) && \ + __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ +#define FASTFLOAT_CONSTEXPR20 constexpr +#define FASTFLOAT_IS_CONSTEXPR 1 +#else +#define FASTFLOAT_CONSTEXPR20 +#define FASTFLOAT_IS_CONSTEXPR 0 +#endif + +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 +#else +#define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 +#endif + +#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include +#ifdef __has_include +#if __has_include() && (__cplusplus > 202002L || (defined(_MSVC_LANG) && (_MSVC_LANG > 202002L))) +#include +#endif +#endif + +#define FASTFLOAT_VERSION_MAJOR 8 +#define FASTFLOAT_VERSION_MINOR 0 +#define FASTFLOAT_VERSION_PATCH 2 + +#define FASTFLOAT_STRINGIZE_IMPL(x) #x +#define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) + +#define FASTFLOAT_VERSION_STR \ + FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MAJOR) \ + "." FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MINOR) "." FASTFLOAT_STRINGIZE( \ + FASTFLOAT_VERSION_PATCH) + +#define FASTFLOAT_VERSION \ + (FASTFLOAT_VERSION_MAJOR * 10000 + FASTFLOAT_VERSION_MINOR * 100 + \ + FASTFLOAT_VERSION_PATCH) + +namespace fast_float { + +enum class chars_format : uint64_t; + +namespace detail { +constexpr chars_format basic_json_fmt = chars_format(1 << 5); +constexpr chars_format basic_fortran_fmt = chars_format(1 << 6); +} // namespace detail + +enum class chars_format : uint64_t { + scientific = 1 << 0, + fixed = 1 << 2, + hex = 1 << 3, + no_infnan = 1 << 4, + // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 + json = uint64_t(detail::basic_json_fmt) | fixed | scientific | no_infnan, + // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. + json_or_infnan = uint64_t(detail::basic_json_fmt) | fixed | scientific, + fortran = uint64_t(detail::basic_fortran_fmt) | fixed | scientific, + general = fixed | scientific, + allow_leading_plus = 1 << 7, + skip_white_space = 1 << 8, }; -struct tag_sizes { - tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} - int num_ints; - int num_reals; - int num_strings; +template struct from_chars_result_t { + UC const *ptr; + tinyobj_ff::ff_errc ec; }; -struct obj_shape { - std::vector v; - std::vector vn; - std::vector vt; +using from_chars_result = from_chars_result_t; + +template struct parse_options_t { + constexpr explicit parse_options_t(chars_format fmt = chars_format::general, + UC dot = UC('.'), int b = 10) + : format(fmt), decimal_point(dot), base(b) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + UC decimal_point; + /** The base used for integers */ + int base; }; -// -// Manages group of primitives(face, line, points, ...) -struct PrimGroup { - std::vector faceGroup; - std::vector<__line_t> lineGroup; - std::vector<__points_t> pointsGroup; +using parse_options = parse_options_t; - void clear() { - faceGroup.clear(); - lineGroup.clear(); - pointsGroup.clear(); +} // namespace fast_float + +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ + defined(__MINGW64__) || defined(__s390x__) || \ + (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ + defined(__PPC64LE__)) || \ + defined(__loongarch64)) +#define FASTFLOAT_64BIT 1 +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ + defined(__MINGW32__) || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_32BIT 1 +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. +// We can never tell the register width, but the SIZE_MAX is a good +// approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max +// portability. +#if SIZE_MAX == 0xffff +#error Unknown platform (16-bit, unsupported) +#elif SIZE_MAX == 0xffffffff +#define FASTFLOAT_32BIT 1 +#elif SIZE_MAX == 0xffffffffffffffff +#define FASTFLOAT_64BIT 1 +#else +#error Unknown platform (not 32-bit, not 64-bit?) +#endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ + (defined(_M_ARM64) && !defined(__MINGW32__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ + (defined(_M_AMD64) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP == 2))) +#define FASTFLOAT_SSE2 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +#define FASTFLOAT_NEON 1 +#endif + +#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) +#define FASTFLOAT_HAS_SIMD 1 +#endif + +#if defined(__GNUC__) +// disable -Wcast-align=strict (GCC only) +#define FASTFLOAT_SIMD_DISABLE_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-align\"") +#else +#define FASTFLOAT_SIMD_DISABLE_WARNINGS +#endif + +#if defined(__GNUC__) +#define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +#else +#define FASTFLOAT_SIMD_RESTORE_WARNINGS +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) \ + { ((void)(x)); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#define FASTFLOAT_DEBUG_ASSERT(x) \ + { ((void)(x)); } +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) \ + { \ + if (!(x)) \ + return false; \ } - bool IsEmpty() const { - return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); +#define FASTFLOAT_ENABLE_IF(...) \ + typename tinyobj_ff::enable_if<(__VA_ARGS__), int>::type + +namespace fast_float { + +fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +template +struct is_supported_float_type + : tinyobj_ff::integral_constant< + bool, tinyobj_ff::is_same::value || tinyobj_ff::is_same::value +#ifdef __STDCPP_FLOAT64_T__ + || tinyobj_ff::is_same::value +#endif +#ifdef __STDCPP_FLOAT32_T__ + || tinyobj_ff::is_same::value +#endif +#ifdef __STDCPP_FLOAT16_T__ + || tinyobj_ff::is_same::value +#endif +#ifdef __STDCPP_BFLOAT16_T__ + || tinyobj_ff::is_same::value +#endif + > { +}; + +template +using equiv_uint_t = typename tinyobj_ff::conditional< + sizeof(T) == 1, uint8_t, + typename tinyobj_ff::conditional< + sizeof(T) == 2, uint16_t, + typename tinyobj_ff::conditional::type>::type>::type; + +template struct is_supported_integer_type : tinyobj_ff::is_integral {}; + +template +struct is_supported_char_type + : tinyobj_ff::integral_constant::value || + tinyobj_ff::is_same::value || + tinyobj_ff::is_same::value || + tinyobj_ff::is_same::value +#ifdef __cpp_char8_t + || tinyobj_ff::is_same::value +#endif + > { +}; + +// Compares two ASCII strings in a case insensitive manner. +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, + size_t length) { + for (size_t i = 0; i < length; ++i) { + UC const actual = actual_mixedcase[i]; + if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) { + return false; + } } + return true; +} - // TODO(syoyo): bspline, surface, ... +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template struct span { + T const *ptr; + size_t length; + + constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {} + + constexpr span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { return length; } + + FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } }; -// See -// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf -static std::istream &safeGetline(std::istream &is, std::string &t) { - t.clear(); +struct value128 { + uint64_t low; + uint64_t high; - // The characters in the stream are read one-by-one using a std::streambuf. - // That is faster than reading them one-by-one using the std::istream. - // Code that uses streambuf this way must be guarded by a sentry object. - // The sentry object performs various tasks, - // such as thread synchronization and updating the stream state. + constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} - std::istream::sentry se(is, true); - std::streambuf *sb = is.rdbuf(); + constexpr value128() : low(0), high(0) {} +}; - if (se) { - for (;;) { - int c = sb->sbumpc(); - switch (c) { - case '\n': - return is; - case '\r': - if (sb->sgetc() == '\n') sb->sbumpc(); - return is; - case EOF: - // Also handle the case when the last line has no line ending - if (t.empty()) is.setstate(std::ios::eofbit); - return is; - default: - t += static_cast(c); - } - } +/* Helper C++14 constexpr generic implementation of leading_zeroes */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int +leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { + if (input_num & uint64_t(0xffffffff00000000)) { + input_num >>= 32; + last_bit |= 32; + } + if (input_num & uint64_t(0xffff0000)) { + input_num >>= 16; + last_bit |= 16; + } + if (input_num & uint64_t(0xff00)) { + input_num >>= 8; + last_bit |= 8; + } + if (input_num & uint64_t(0xf0)) { + input_num >>= 4; + last_bit |= 4; + } + if (input_num & uint64_t(0xc)) { + input_num >>= 2; + last_bit |= 2; + } + if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ + last_bit |= 1; } + return 63 - last_bit; +} - return is; +/* result might be undefined when input_num is zero */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int +leading_zeroes(uint64_t input_num) { + assert(input_num > 0); + if (cpp20_and_in_constexpr()) { + return leading_zeroes_generic(input_num); + } +#ifdef FASTFLOAT_VISUAL_STUDIO +#if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); +#else + return leading_zeroes_generic(input_num); +#endif +#else + return __builtin_clzll(input_num); +#endif } -#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) -#define IS_DIGIT(x) \ - (static_cast((x) - '0') < static_cast(10)) -#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) +// slow emulation routine for 32-bit +fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} -template -static inline std::string toString(const T &t) { - std::stringstream ss; - ss << t; - return ss.str(); +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = (uint64_t)(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + (uint64_t)(lo < bd); + return lo; +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, + uint64_t cd, + uint64_t *hi) { + return umul128_generic(ab, cd, hi); +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + +// compute 64-bit a*b +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +full_multiplication(uint64_t a, uint64_t b) { + if (cpp20_and_in_constexpr()) { + value128 answer; + answer.low = umul128_generic(a, b, &answer.high); + return answer; + } + value128 answer; +#if defined(_M_ARM64) && !defined(__MINGW32__) + // ARM64 has native support for 64-bit multiplications, no need to emulate + // But MinGW on ARM64 doesn't have native support for 64-bit multiplications + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || \ + (defined(_WIN64) && !defined(__clang__) && !defined(_M_ARM64)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + answer.low = umul128_generic(a, b, &answer.high); +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + + constexpr bool operator==(adjusted_mantissa const &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + + constexpr bool operator!=(adjusted_mantissa const &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +// used for binary_format_lookup_tables::max_mantissa +constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; + +template struct binary_format_lookup_tables; + +template struct binary_format : binary_format_lookup_tables { + using equiv_uint = equiv_uint_t; + + static constexpr int mantissa_explicit_bits(); + static constexpr int minimum_exponent(); + static constexpr int infinite_power(); + static constexpr int sign_index(); + static constexpr int + min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST + static constexpr int max_exponent_fast_path(); + static constexpr int max_exponent_round_to_even(); + static constexpr int min_exponent_round_to_even(); + static constexpr uint64_t max_mantissa_fast_path(int64_t power); + static constexpr uint64_t + max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST + static constexpr int largest_power_of_ten(); + static constexpr int smallest_power_of_ten(); + static constexpr T exact_power_of_ten(int64_t power); + static constexpr size_t max_digits(); + static constexpr equiv_uint exponent_mask(); + static constexpr equiv_uint mantissa_mask(); + static constexpr equiv_uint hidden_bit_mask(); +}; + +template struct binary_format_lookup_tables { + static constexpr double powers_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Largest integer value v so that (5**index * v) <= 1<<53. + // 0x20000000000000 == 1 << 53 + static constexpr uint64_t max_mantissa[] = { + 0x20000000000000, + 0x20000000000000 / 5, + 0x20000000000000 / (5 * 5), + 0x20000000000000 / (5 * 5 * 5), + 0x20000000000000 / (5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555), + 0x20000000000000 / (constant_55555 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), + 0x20000000000000 / + (constant_55555 * constant_55555 * constant_55555 * constant_55555), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5), + 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * + constant_55555 * 5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr double binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template struct binary_format_lookup_tables { + static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, + 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; + + // Largest integer value v so that (5**index * v) <= 1<<24. + // 0x1000000 == 1<<24 + static constexpr uint64_t max_mantissa[] = { + 0x1000000, + 0x1000000 / 5, + 0x1000000 / (5 * 5), + 0x1000000 / (5 * 5 * 5), + 0x1000000 / (5 * 5 * 5 * 5), + 0x1000000 / (constant_55555), + 0x1000000 / (constant_55555 * 5), + 0x1000000 / (constant_55555 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * constant_55555), + 0x1000000 / (constant_55555 * constant_55555 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr float binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> +inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> +inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 63; +} + +template <> inline constexpr int binary_format::sign_index() { + return 31; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +// credit: Jakub Jelínek +#ifdef __STDCPP_FLOAT16_T__ +template struct binary_format_lookup_tables { + static constexpr std::float16_t powers_of_ten[] = {1e0f16, 1e1f16, 1e2f16, + 1e3f16, 1e4f16}; + + // Largest integer value v so that (5**index * v) <= 1<<11. + // 0x800 == 1<<11 + static constexpr uint64_t max_mantissa[] = {0x800, + 0x800 / 5, + 0x800 / (5 * 5), + 0x800 / (5 * 5 * 5), + 0x800 / (5 * 5 * 5 * 5), + 0x800 / (constant_55555)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr std::float16_t + binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t + binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr std::float16_t +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7C00; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x03FF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0400; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 4; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 10; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 4 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { + return 0; +} + +template <> +inline constexpr int +binary_format::max_exponent_round_to_even() { + return 5; +} + +template <> +inline constexpr int +binary_format::min_exponent_round_to_even() { + return -22; +} + +template <> +inline constexpr int binary_format::minimum_exponent() { + return -15; +} + +template <> +inline constexpr int binary_format::infinite_power() { + return 0x1F; +} + +template <> inline constexpr int binary_format::sign_index() { + return 15; +} + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 4; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -27; +} + +template <> +inline constexpr size_t binary_format::max_digits() { + return 22; +} +#endif // __STDCPP_FLOAT16_T__ + +// credit: Jakub Jelínek +#ifdef __STDCPP_BFLOAT16_T__ +template struct binary_format_lookup_tables { + static constexpr std::bfloat16_t powers_of_ten[] = {1e0bf16, 1e1bf16, 1e2bf16, + 1e3bf16}; + + // Largest integer value v so that (5**index * v) <= 1<<8. + // 0x100 == 1<<8 + static constexpr uint64_t max_mantissa[] = {0x100, 0x100 / 5, 0x100 / (5 * 5), + 0x100 / (5 * 5 * 5), + 0x100 / (5 * 5 * 5 * 5)}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr std::bfloat16_t + binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t + binary_format_lookup_tables::max_mantissa[]; + +#endif + +template <> +inline constexpr std::bfloat16_t +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr int binary_format::max_exponent_fast_path() { + return 3; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F80; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007F; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0080; +} + +template <> +inline constexpr int binary_format::mantissa_explicit_bits() { + return 7; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 3 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr int binary_format::min_exponent_fast_path() { + return 0; +} + +template <> +inline constexpr int +binary_format::max_exponent_round_to_even() { + return 3; +} + +template <> +inline constexpr int +binary_format::min_exponent_round_to_even() { + return -24; +} + +template <> +inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> +inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { + return 15; +} + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -60; +} + +template <> +inline constexpr size_t binary_format::max_digits() { + return 98; +} +#endif // __STDCPP_BFLOAT16_T__ + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 22 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr uint64_t +binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 10 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr double +binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} + +template <> inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} + +template <> inline constexpr int binary_format::smallest_power_of_ten() { + return -64; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7F800000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x007FFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x00800000; +} + +template <> +inline constexpr binary_format::equiv_uint +binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +to_float(bool negative, adjusted_mantissa am, T &value) { + using equiv_uint = equiv_uint_t; + equiv_uint word = equiv_uint(am.mantissa); + word = equiv_uint(word | equiv_uint(am.power2) + << binary_format::mantissa_explicit_bits()); + word = + equiv_uint(word | equiv_uint(negative) << binary_format::sign_index()); +#if FASTFLOAT_HAS_BIT_CAST + value = std::bit_cast(word); +#else + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +template struct space_lut { + static constexpr bool value[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr bool space_lut::value[]; + +#endif + +template constexpr bool is_space(UC c) { + return c < 256 && space_lut<>::value[uint8_t(c)]; +} + +template static constexpr uint64_t int_cmp_zeros() { + static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), + "Unsupported character size"); + return (sizeof(UC) == 1) ? 0x3030303030303030 + : (sizeof(UC) == 2) + ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | + uint64_t(UC('0')) << 16 | UC('0')) + : (uint64_t(UC('0')) << 32 | UC('0')); +} + +template static constexpr int int_cmp_len() { + return sizeof(uint64_t) / sizeof(UC); +} + +template constexpr UC const *str_const_nan(); + +template <> constexpr char const *str_const_nan() { return "nan"; } + +template <> constexpr wchar_t const *str_const_nan() { return L"nan"; } + +template <> constexpr char16_t const *str_const_nan() { + return u"nan"; +} + +template <> constexpr char32_t const *str_const_nan() { + return U"nan"; +} + +#ifdef __cpp_char8_t +template <> constexpr char8_t const *str_const_nan() { + return u8"nan"; +} +#endif + +template constexpr UC const *str_const_inf(); + +template <> constexpr char const *str_const_inf() { return "infinity"; } + +template <> constexpr wchar_t const *str_const_inf() { + return L"infinity"; +} + +template <> constexpr char16_t const *str_const_inf() { + return u"infinity"; +} + +template <> constexpr char32_t const *str_const_inf() { + return U"infinity"; +} + +#ifdef __cpp_char8_t +template <> constexpr char8_t const *str_const_inf() { + return u8"infinity"; +} +#endif + +template struct int_luts { + static constexpr uint8_t chdigit[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255}; + + static constexpr size_t maxdigits_u64[] = { + 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, + 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; + + static constexpr uint64_t min_safe_u64[] = { + 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, + 7450580596923828125, 4738381338321616896, 3909821048582988049, + 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, + 5559917313492231481, 2218611106740436992, 8650415919381337933, + 2177953337809371136, 6568408355712890625, 1152921504606846976, + 2862423051509815793, 6746640616477458432, 15181127029874798299ull, + 1638400000000000000, 3243919932521508681, 6221821273427820544, + 11592836324538749809ull, 876488338465357824, 1490116119384765625, + 2481152873203736576, 4052555153018976267, 6502111422497947648, + 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, + 1152921504606846976, 1667889514952984961, 2386420683693101056, + 3379220508056640625, 4738381338321616896}; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint8_t int_luts::chdigit[]; + +template constexpr size_t int_luts::maxdigits_u64[]; + +template constexpr uint64_t int_luts::min_safe_u64[]; + +#endif + +template +fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { + return int_luts<>::chdigit[static_cast(c)]; +} + +fastfloat_really_inline constexpr size_t max_digits_u64(int base) { + return int_luts<>::maxdigits_u64[base - 2]; +} + +// If a u64 is exactly max_digits_u64() in length, this is +// the value below which it has definitely overflowed. +fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { + return int_luts<>::min_safe_u64[base - 2]; +} + +static_assert(tinyobj_ff::is_same, uint64_t>::value, + "equiv_uint should be uint64_t for double"); +static_assert(std::numeric_limits::is_iec559, + "double must fulfill the requirements of IEC 559 (IEEE 754)"); + +static_assert(tinyobj_ff::is_same, uint32_t>::value, + "equiv_uint should be uint32_t for float"); +static_assert(std::numeric_limits::is_iec559, + "float must fulfill the requirements of IEC 559 (IEEE 754)"); + +#ifdef __STDCPP_FLOAT64_T__ +static_assert(tinyobj_ff::is_same, uint64_t>::value, + "equiv_uint should be uint64_t for std::float64_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float64_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_FLOAT64_T__ + +#ifdef __STDCPP_FLOAT32_T__ +static_assert(tinyobj_ff::is_same, uint32_t>::value, + "equiv_uint should be uint32_t for std::float32_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float32_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_FLOAT32_T__ + +#ifdef __STDCPP_FLOAT16_T__ +static_assert( + tinyobj_ff::is_same::equiv_uint, uint16_t>::value, + "equiv_uint should be uint16_t for std::float16_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::float16_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_FLOAT16_T__ + +#ifdef __STDCPP_BFLOAT16_T__ +static_assert( + tinyobj_ff::is_same::equiv_uint, uint16_t>::value, + "equiv_uint should be uint16_t for std::bfloat16_t"); +static_assert( + std::numeric_limits::is_iec559, + "std::bfloat16_t must fulfill the requirements of IEC 559 (IEEE 754)"); +#endif // __STDCPP_BFLOAT16_T__ + +constexpr chars_format operator~(chars_format rhs) noexcept { + using int_type = tinyobj_ff::underlying_type::type; + return static_cast(~static_cast(rhs)); +} + +constexpr chars_format operator&(chars_format lhs, chars_format rhs) noexcept { + using int_type = tinyobj_ff::underlying_type::type; + return static_cast(static_cast(lhs) & + static_cast(rhs)); +} + +constexpr chars_format operator|(chars_format lhs, chars_format rhs) noexcept { + using int_type = tinyobj_ff::underlying_type::type; + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +constexpr chars_format operator^(chars_format lhs, chars_format rhs) noexcept { + using int_type = tinyobj_ff::underlying_type::type; + return static_cast(static_cast(lhs) ^ + static_cast(rhs)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator&=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs & rhs); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator|=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs | rhs); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & +operator^=(chars_format &lhs, chars_format rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +namespace detail { +// adjust for deprecated feature macros +constexpr chars_format adjust_for_feature_macros(chars_format fmt) { + return fmt +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS + | chars_format::allow_leading_plus +#endif +#ifdef FASTFLOAT_SKIP_WHITE_SPACE + | chars_format::skip_white_space +#endif + ; +} +} // namespace detail + +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + + +namespace fast_float { +/** + * This function parses the character sequence [first,last) for a number. It + * parses floating-point numbers expecting a locale-indepent format equivalent + * to what is used by std::strtod in the default ("C") locale. The resulting + * floating-point value is the closest floating-point values (using either float + * or double), using the "round to even" convention for values that would + * otherwise fall right in-between two values. That is, we provide exact parsing + * according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to + * point right after the parsed number, and the `value` referenced is set to the + * parsed value. In case of error, the returned `ec` contains a representative + * error, otherwise the default (`tinyobj_ff::ff_errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with + * `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an + * optional last argument of the type `fast_float::chars_format`. It is a bitset + * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & + * fast_float::chars_format::scientific` are set to determine whether we allow + * the fixed point and scientific notation respectively. The default is + * `fast_float::chars_format::general` which allows both `fixed` and + * `scientific`. + */ +template ::value)> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + * Both for floating-point types and integer types. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept; + +/** + * from_chars for integer types. + */ +template ::value)> +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; + +} // namespace fast_float + +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include + + +#ifdef FASTFLOAT_SSE2 +#include +#endif + +#ifdef FASTFLOAT_NEON +#include +#endif + +namespace fast_float { + +template fastfloat_really_inline constexpr bool has_simd_opt() { +#ifdef FASTFLOAT_HAS_SIMD + return tinyobj_ff::is_same::value; +#else + return false; +#endif +} + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +template +fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { + return !(c > UC('9') || c < UC('0')); +} + +fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | + (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | + (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | + (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; +} + +// Read 8 UC into a u64. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +read8_to_u64(UC const *chars) { + if (cpp20_and_in_constexpr() || !tinyobj_ff::is_same::value) { + uint64_t val = 0; + for (int i = 0; i < 8; ++i) { + val |= uint64_t(uint8_t(*chars)) << (i * 8); + ++chars; + } + return val; + } + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +#ifdef FASTFLOAT_SSE2 + +fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + __m128i const packed = _mm_packus_epi16(data, data); +#ifdef FASTFLOAT_64BIT + return uint64_t(_mm_cvtsi128_si64(packed)); +#else + uint64_t value; + // Visual Studio + older versions of GCC don't support _mm_storeu_si64 + _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); + return value; +#endif + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#elif defined(FASTFLOAT_NEON) + +fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint8x8_t utf8_packed = vmovn_u16(data); + return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { + FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64( + vld1q_u16(reinterpret_cast(chars))); + FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#endif // FASTFLOAT_SSE2 + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +uint64_t simd_read8_to_u64(UC const *) { + return 0; +} + +// credit @aqrit +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t +parse_eight_digits_unrolled(uint64_t val) { + uint64_t const mask = 0x000000FF000000FF; + uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +// Call this if chars are definitely 8 digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t +parse_eight_digits_unrolled(UC const *chars) noexcept { + if (cpp20_and_in_constexpr() || !has_simd_opt()) { + return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay + } + return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline constexpr bool +is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +#ifdef FASTFLOAT_HAS_SIMD + +// Call this if chars might not be 8 digits. +// Using this style (instead of is_made_of_eight_digits_fast() then +// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +simd_parse_if_eight_digits_unrolled(char16_t const *chars, + uint64_t &i) noexcept { + if (cpp20_and_in_constexpr()) { + return false; + } +#ifdef FASTFLOAT_SSE2 + FASTFLOAT_SIMD_DISABLE_WARNINGS + __m128i const data = + _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); + __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); + + if (_mm_movemask_epi8(t1) == 0) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#elif defined(FASTFLOAT_NEON) + FASTFLOAT_SIMD_DISABLE_WARNINGS + uint16x8_t const data = vld1q_u16(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0')); + uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); + + if (vminvq_u16(mask) == 0xFFFF) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } else + return false; + FASTFLOAT_SIMD_RESTORE_WARNINGS +#else + (void)chars; + (void)i; + return false; +#endif // FASTFLOAT_SSE2 +} + +#endif // FASTFLOAT_HAS_SIMD + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ()) = 0> +#endif +// dummy for compile +bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { + return 0; +} + +template ::value) = 0> +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) { + if (!has_simd_opt()) { + return; + } + while ((tinyobj_ff::distance(p, pend) >= 8) && + simd_parse_if_eight_digits_unrolled( + p, i)) { // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +loop_parse_if_eight_digits(char const *&p, char const *const pend, + uint64_t &i) { + // optimizes better than parse_if_eight_digits_unrolled() for UC = char. + while ((tinyobj_ff::distance(p, pend) >= 8) && + is_made_of_eight_digits_fast(read8_to_u64(p))) { + i = i * 100000000 + + parse_eight_digits_unrolled(read8_to_u64( + p)); // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +enum class parse_error { + no_error, + // [JSON-only] The minus sign must be followed by an integer. + missing_integer_after_sign, + // A sign must be followed by an integer or dot. + missing_integer_or_dot_after_sign, + // [JSON-only] The integer part must not have leading zeros. + leading_zeros_in_integer_part, + // [JSON-only] The integer part must have at least one digit. + no_digits_in_integer_part, + // [JSON-only] If there is a decimal point, there must be digits in the + // fractional part. + no_digits_in_fractional_part, + // The mantissa must have at least one digit. + no_digits_in_mantissa, + // Scientific notation requires an exponential part. + missing_exponential_part, +}; + +template struct parsed_number_string_t { + int64_t exponent{0}; + uint64_t mantissa{0}; + UC const *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + span integer{}; // non-nullable + span fraction{}; // nullable + parse_error error{parse_error::no_error}; +}; + +using byte_span = span; +using parsed_number_string = parsed_number_string_t; + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +report_parse_error(UC const *p, parse_error error) { + parsed_number_string_t answer; + answer.valid = false; + answer.lastmatch = p; + answer.error = error; + return answer; +} + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string(UC const *p, UC const *pend, + parse_options_t options) noexcept { + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + UC const decimal_point = options.decimal_point; + + parsed_number_string_t answer; + answer.valid = false; + answer.too_many_digits = false; + // assume p < pend, so dereference without checks; + answer.negative = (*p == UC('-')); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && + !basic_json_fmt && *p == UC('+'))) { + ++p; + if (p == pend) { + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + if (!is_integer(*p)) { // a sign must be followed by an integer + return report_parse_error(p, + parse_error::missing_integer_after_sign); + } + } + else { + if (!is_integer(*p) && + (*p != + decimal_point)) { // a sign must be followed by an integer or the dot + return report_parse_error( + p, parse_error::missing_integer_or_dot_after_sign); + } + } + } + UC const *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - + UC('0')); // might overflow, we will handle the overflow later + ++p; + } + UC const *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = span(start_digits, size_t(digit_count)); + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + // at least 1 digit in integer part, without leading zeros + if (digit_count == 0) { + return report_parse_error(p, parse_error::no_digits_in_integer_part); + } + if ((start_digits[0] == UC('0') && digit_count > 1)) { + return report_parse_error(start_digits, + parse_error::leading_zeros_in_integer_part); + } + } + + int64_t exponent = 0; + bool const has_decimal_point = (p != pend) && (*p == decimal_point); + if (has_decimal_point) { + ++p; + UC const *before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + loop_parse_if_eight_digits(p, pend, i); + + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = span(before, size_t(p - before)); + digit_count -= exponent; + } + FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { + // at least 1 digit in fractional part + if (has_decimal_point && exponent == 0) { + return report_parse_error(p, + parse_error::no_digits_in_fractional_part); + } + } + else if (digit_count == 0) { // we must have encountered at least one integer! + return report_parse_error(p, parse_error::no_digits_in_mantissa); + } + int64_t exp_number = 0; // explicit exponential part + if ((uint64_t(fmt & chars_format::scientific) && (p != pend) && + ((UC('e') == *p) || (UC('E') == *p))) || + (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) && + ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || + (UC('D') == *p)))) { + UC const *location_of_e = p; + if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || + (UC('D') == *p)) { + ++p; + } + bool neg_exp = false; + if ((p != pend) && (UC('-') == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && + (UC('+') == + *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if (!uint64_t(fmt & chars_format::fixed)) { + // The exponential part is invalid for scientific notation, so it must + // be a trailing token for fixed notation. However, fixed notation is + // disabled, so report a scientific notation error. + return report_parse_error(p, parse_error::missing_exponential_part); + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if (neg_exp) { + exp_number = -exp_number; + } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if (uint64_t(fmt & chars_format::scientific) && + !uint64_t(fmt & chars_format::fixed)) { + return report_parse_error(p, parse_error::missing_exponential_part); + } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + UC const *start = start_digits; + while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { + if (*start == UC('0')) { + digit_count--; + } + start++; + } + + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + UC const *int_end = p + answer.integer.len(); + uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + UC const *frac_end = p + answer.fraction.len(); + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t +parse_int_string(UC const *p, UC const *pend, T &value, + parse_options_t options) { + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + int const base = options.base; + + from_chars_result_t answer; + + UC const *const first = p; + + bool const negative = (*p == UC('-')); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4127) +#endif + if (!tinyobj_ff::is_signed::value && negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + answer.ec = tinyobj_ff::ff_errc::invalid_argument; + answer.ptr = first; + return answer; + } + if ((*p == UC('-')) || + (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) { + ++p; + } + + UC const *const start_num = p; + + while (p != pend && *p == UC('0')) { + ++p; + } + + bool const has_leading_zeros = p > start_num; + + UC const *const start_digits = p; + + uint64_t i = 0; + if (base == 10) { + loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible + } + while (p != pend) { + uint8_t digit = ch_to_digit(*p); + if (digit >= base) { + break; + } + i = uint64_t(base) * i + digit; // might overflow, check this later + p++; + } + + size_t digit_count = size_t(p - start_digits); + + if (digit_count == 0) { + if (has_leading_zeros) { + value = 0; + answer.ec = tinyobj_ff::ff_errc(); + answer.ptr = p; + } else { + answer.ec = tinyobj_ff::ff_errc::invalid_argument; + answer.ptr = first; + } + return answer; + } + + answer.ptr = p; + + // check u64 overflow + size_t max_digits = max_digits_u64(base); + if (digit_count > max_digits) { + answer.ec = tinyobj_ff::ff_errc::result_out_of_range; + return answer; + } + // this check can be eliminated for all other types, but they will all require + // a max_digits(base) equivalent + if (digit_count == max_digits && i < min_safe_u64(base)) { + answer.ec = tinyobj_ff::ff_errc::result_out_of_range; + return answer; + } + + // check other types overflow + if (!tinyobj_ff::is_same::value) { + if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { + answer.ec = tinyobj_ff::ff_errc::result_out_of_range; + return answer; + } + } + + if (negative) { +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +#pragma warning(disable : 4146) +#endif + // this weird workaround is required because: + // - converting unsigned to signed when its value is greater than signed max + // is UB pre-C++23. + // - reinterpret_casting (~i + 1) would work, but it is not constexpr + // this is always optimized into a neg instruction (note: T is an integer + // type) + value = T(-std::numeric_limits::max() - + T(i - uint64_t(std::numeric_limits::max()))); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#endif + } else { + value = T(i); + } + + answer.ec = tinyobj_ff::ff_errc(); + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^-1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template struct powers_template { + + constexpr static int smallest_power_of_five = + binary_format::smallest_power_of_ten(); + constexpr static int largest_power_of_five = + binary_format::largest_power_of_ten(); + constexpr static int number_of_entries = + 2 * (largest_power_of_five - smallest_power_of_five + 1); + // Powers of five from 5^-342 all the way to 5^308 rounded toward one. + constexpr static uint64_t power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a, 0x113faa2906a13b3f, + 0x9558b4661b6565f8, 0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, + 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, + 0x91d8a02bb6c10594, 0x79071b9b8a4be869, + 0xb64ec836a47146f9, 0x9748e2826cdee284, + 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f, 0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723, 0xad2c788035e61382, + 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, + 0xa9c98d8ccb009506, 0x680efdaf511f18c2, + 0xd43bf0effdc0ba48, 0x212bd1b2566def2, + 0x84a57695fe98746d, 0x14bb630f7604b57, + 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, + 0xcf42894a5dce35ea, 0x52064cac828675b9, + 0x818995ce7aa0e1b2, 0x7343efebd1940993, + 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6, 0xd41a26e077774ef6, + 0xfd00b897478238d0, 0x8920b098955522b4, + 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, + 0xc5a890362fddbc62, 0xeb2189f734aa831d, + 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d, 0x47b233c92125366e, + 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, + 0xf148440a256e2c76, 0xc00670ea43ca250d, + 0x96cd2a865764dbca, 0x380406926a5e5728, + 0xbc807527ed3e12bc, 0xc605083704f5ecf2, + 0xeba09271e88d976b, 0xf7864a44c633682e, + 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c, 0x5960ea05bad82964, + 0xe61acf033d1a45df, 0x6fb92487298e33bd, + 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, + 0xe0b62e2929aba83c, 0x331acdabfe94de87, + 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, + 0x892731ac9faf056e, 0xbe311c083a225cd2, + 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, + 0xd64d3d9db981787d, 0x92cbbccdad5b108, + 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, + 0xa76c582338ed2621, 0xaf2af2b80af6f24e, + 0xd1476e2c07286faa, 0x1af5af660db4aee1, + 0x82cca4db847945ca, 0x50d98d9fc890ed4d, + 0xa37fce126597973c, 0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1, 0x77b191618c54e9ac, + 0xc795830d75038c1d, 0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, + 0x9becce62836ac577, 0x4ee367f9430aec32, + 0xc2e801fb244576d5, 0x229c41f793cda73f, + 0xf3a20279ed56d48a, 0x6b43527578c1110f, + 0x9845418c345644d6, 0x830a13896b78aaa9, + 0xbe5691ef416bd60c, 0x23cc986bc656d553, + 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, + 0x91376c36d99995be, 0x23100809b9c21fa1, + 0xb58547448ffffb2d, 0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, + 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, + 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d, 0x40405643d711d583, + 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, + 0xad1c8eab5ee43b66, 0xda3243650005eecf, + 0xd863b256369d4a40, 0x90bed43e40076a82, + 0x873e4f75e2224e68, 0x5a7744a6e804a291, + 0xa90de3535aaae202, 0x711515d0a205cb36, + 0xd3515c2831559a83, 0xd5a5b44ca873e03, + 0x8412d9991ed58091, 0xe858790afe9486c2, + 0xa5178fff668ae0b6, 0x626e974dbe39a872, + 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, + 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, + 0xc987434744ac874e, 0xa327ffb266b56220, + 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, + 0xc4ce17b399107c22, 0xcb550fb4384d21d3, + 0xf6019da07f549b2b, 0x7e2a53a146606a48, + 0x99c102844f94e0fb, 0x2eda7444cbfc426d, + 0xc0314325637a1939, 0xfa911155fefb5308, + 0xf03d93eebc589f88, 0x793555ab7eba27ca, + 0x96267c7535b763b5, 0x4bc1558b2f3458de, + 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb, 0x465e15a979c1cadc, + 0x92a1958a7675175f, 0xbfacd89ec191ec9, + 0xb749faed14125d36, 0xcef980ec671f667b, + 0xe51c79a85916f484, 0x82b7e12780e7401a, + 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9, 0x67a791e093e1d49a, + 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d, 0x58fae9f773886e18, + 0xda7f5bf590966848, 0xaf39a475506a899e, + 0x888f99797a5e012d, 0x6d8406c952429603, + 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, + 0xd0601d8efc57b08b, 0xf13b94daf124da26, + 0x823c12795db6ce57, 0x76c53d08d6b70858, + 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, + 0xf867241c8cc6d4c0, 0xc30163d203c94b62, + 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, + 0xc21094364dfb5636, 0x985915fc12f542e4, + 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, + 0xbd8430bd08277231, 0x50c6ff782a838353, + 0xece53cec4a314ebd, 0xa4f8bf5635246428, + 0x940f4613ae5ed136, 0x871b7795e136be99, + 0xb913179899f68584, 0x28e2557b59846e3f, + 0xe757dd7ec07426e5, 0x331aeada2fe589cf, + 0x9096ea6f3848984f, 0x3ff0d2c85def7621, + 0xb4bca50b065abe63, 0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, + 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, + 0xb080392cc4349dec, 0xbd8d794d96aacfb3, + 0xdca04777f541c567, 0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60, 0xf41686c49db57244, + 0xac5d37d5b79b6239, 0x311c2875c522ced5, + 0xd77485cb25823ac7, 0x7d633293366b828b, + 0x86a8d39ef77164bc, 0xae5dff9c02033197, + 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, + 0xd267caa862a12d66, 0xd072df63c324fd7b, + 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, + 0xa46116538d0deb78, 0x52d9be85f074e608, + 0xcd795be870516656, 0x67902e276c921f8b, + 0x806bd9714632dff6, 0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c, 0x796b805720085f81, + 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, + 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b, 0xee92fb5515482d44, + 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, + 0xef340a98172aace4, 0x86fb897116c87c34, + 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, + 0xbae0a846d2195712, 0x8974836059cca109, + 0xe998d258869facd7, 0x2bd1a438703fc94b, + 0x91ff83775423cc06, 0x7b6306a34627ddcf, + 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, + 0x8e938662882af53e, 0x547eb47b7282ee9c, + 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, + 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, + 0xae0b158b4738705e, 0x9624ab50b148d445, + 0xd98ddaee19068c76, 0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b, 0x7647c3200069671f, + 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, + 0xa5fb0a17c777cf09, 0xf468107100525890, + 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, + 0x81ac1fe293d599bf, 0xc6f14cd848405530, + 0xa21727db38cb002f, 0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, + 0xfd442e4688bd304a, 0x908f4a166d1da663, + 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, + 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, + 0xf7549530e188c128, 0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, + 0xc13a148e3032d6e7, 0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, + 0xebdf661791d60f56, 0x111b495b3464ad21, + 0x936b9fcebb25c995, 0xcab10dd900beec34, + 0xb84687c269ef3bfb, 0x3d5d514f40eea742, + 0xe65829b3046b0afa, 0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, + 0xb3f4e093db73a093, 0x59ed216765690f56, + 0xe0f218b8d25088b8, 0x306869c13ec3532c, + 0x8c974f7383725573, 0x1e414218c73a13fb, + 0xafbd2350644eeacf, 0xe5d1929ef90898fa, + 0xdbac6c247d62a583, 0xdf45f746b74abf39, + 0x894bc396ce5da772, 0x6b8bba8c328eb783, + 0xab9eb47c81f5114f, 0x66ea92f3f326564, + 0xd686619ba27255a2, 0xc80a537b0efefebd, + 0x8613fd0145877585, 0xbd06742ce95f5f36, + 0xa798fc4196e952e7, 0x2c48113823b73704, + 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, + 0x82ef85133de648c4, 0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3, 0x318df905079926a8, + 0xffbbcfe994e5c61f, 0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d, 0x6bea10ca65c084e, + 0xc31bfa0fe5698db8, 0x486e494fcff30a62, + 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, + 0xbe89523386091465, 0xf6bbb397f1135823, + 0xee2ba6c0678b597f, 0x746aa07ded582e2c, + 0x94db483840b717ef, 0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb, 0x92f34d62616ce413, + 0xe896a0d7e51e1566, 0x77b020baf9c81d17, + 0x915e2486ef32cd60, 0xace1474dc1d122e, + 0xb5b5ada8aaff80b8, 0xd819992132456ba, + 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, + 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, + 0xd89d64d57a607744, 0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b, 0x11471cd764ad4972, + 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, + 0xd389b47879823479, 0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb, 0xcedf722a585139ba, + 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, + 0xce947a3da6a9273e, 0x733d226229feea32, + 0x811ccc668829b887, 0x806357d5a3f525f, + 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, + 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, + 0xc5029163f384a931, 0xa9e795e65d4df11, + 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e, 0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, + 0xf07da27a82c37088, 0x5d767327bb4e5a4c, + 0x964e858c91ba2655, 0x3a6a07f8d510f86f, + 0xbbe226efb628afea, 0x890489f70a55368b, + 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, + 0xe55990879ddcaabd, 0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, + 0xb32df8e9f3546564, 0x47939822dc96abf9, + 0xdff9772470297ebd, 0x59787e2b93bc56f7, + 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, + 0xaefae51477a06b03, 0xede622920b6b23f1, + 0xdab99e59958885c4, 0xe95fab368e45eced, + 0x88b402f7fd75539b, 0x11dbcb0218ebb414, + 0xaae103b5fcd2a881, 0xd652bdc29f26a119, + 0xd59944a37c0752a2, 0x4be76d3346f0495f, + 0x857fcae62d8493a5, 0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, + 0x825ecc24c873782f, 0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b, 0x728900802f0f32fa, + 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc, 0xe2f610c84987bfa8, + 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143, 0x91503d1c79720dbb, + 0xf8a95fcf88747d94, 0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, + 0xc24452da229b021b, 0xfbe85badce996168, + 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, + 0x97c560ba6b0919a5, 0xdccd879fc967d41a, + 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, + 0xed246723473e3813, 0x290123e9aab23b68, + 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, + 0xb94470938fa89bce, 0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, + 0x8d590723948a535f, 0x579c487e5a38ad0e, + 0xb0af48ec79ace837, 0x2d835a9df0c6d851, + 0xdcdb1b2798182244, 0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, + 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, + 0xa87fea27a539e9a5, 0x3f2398d747b36224, + 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89, 0x1953cf68300424ac, + 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, + 0xcdb02555653131b6, 0x3792f412cb06794d, + 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b, 0xf245825a5a445275, + 0xfb158592be068d2e, 0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d, 0x55464dd69685606b, + 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, + 0xf53304714d9265df, 0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab, 0xe546a8038efe4029, + 0xbf8fdb78849a5f96, 0xde98520472bdd033, + 0xef73d256a5c0f77c, 0x963e66858f6d4440, + 0x95a8637627989aad, 0xdde7001379a44aa8, + 0xbb127c53b17ec159, 0x5560c018580d5d52, + 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, + 0x9226712162ab070d, 0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, + 0xb267ed1940f1c61c, 0x55f038b237591ed3, + 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, + 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, + 0xae397d8aa96c1b77, 0xabec975e0a0d081a, + 0xd9c7dced53c72255, 0x96e7bd358c904a21, + 0x881cea14545c7575, 0x7e50d64177da2e54, + 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, + 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba, 0x67de18eda5814af2, + 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, + 0xa2425ff75e14fc31, 0xa1258379a94d028d, + 0xcad2f7f5359a3b3e, 0x96ee45813a04330, + 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, + 0x9e74d1b791e07e48, 0x775ea264cf55347e, + 0xc612062576589dda, 0x95364afe032a819e, + 0xf79687aed3eec551, 0x3a83ddbd83f52205, + 0x9abe14cd44753b52, 0xc4926a9672793543, + 0xc16d9a0095928a27, 0x75b7053c0f178294, + 0xf1c90080baf72cb1, 0x5324c68b12dd6339, + 0x971da05074da7bee, 0xd3f6fc16ebca5e04, + 0xbce5086492111aea, 0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07, 0x3aff322e62439fd0, + 0xb877aa3236a4b449, 0x9befeb9fad487c3, + 0xe69594bec44de15b, 0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, + 0xb424dc35095cd80f, 0x538484c19ef38c95, + 0xe12e13424bb40e13, 0x2865a5f206b06fba, + 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, + 0xafebff0bcb24aafe, 0xf78f69a51539d749, + 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, + 0x89705f4136b4a597, 0x31680a88f8953031, + 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, + 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, + 0xa7c5ac471b478423, 0xfcf80dc33721d54, + 0xd1b71758e219652b, 0xd3c36113404ea4a9, + 0x83126e978d4fdf3b, 0x645a1cac083126ea, + 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, + 0xcccccccccccccccc, 0xcccccccccccccccd, + 0x8000000000000000, 0x0, + 0xa000000000000000, 0x0, + 0xc800000000000000, 0x0, + 0xfa00000000000000, 0x0, + 0x9c40000000000000, 0x0, + 0xc350000000000000, 0x0, + 0xf424000000000000, 0x0, + 0x9896800000000000, 0x0, + 0xbebc200000000000, 0x0, + 0xee6b280000000000, 0x0, + 0x9502f90000000000, 0x0, + 0xba43b74000000000, 0x0, + 0xe8d4a51000000000, 0x0, + 0x9184e72a00000000, 0x0, + 0xb5e620f480000000, 0x0, + 0xe35fa931a0000000, 0x0, + 0x8e1bc9bf04000000, 0x0, + 0xb1a2bc2ec5000000, 0x0, + 0xde0b6b3a76400000, 0x0, + 0x8ac7230489e80000, 0x0, + 0xad78ebc5ac620000, 0x0, + 0xd8d726b7177a8000, 0x0, + 0x878678326eac9000, 0x0, + 0xa968163f0a57b400, 0x0, + 0xd3c21bcecceda100, 0x0, + 0x84595161401484a0, 0x0, + 0xa56fa5b99019a5c8, 0x0, + 0xcecb8f27f4200f3a, 0x0, + 0x813f3978f8940984, 0x4000000000000000, + 0xa18f07d736b90be5, 0x5000000000000000, + 0xc9f2c9cd04674ede, 0xa400000000000000, + 0xfc6f7c4045812296, 0x4d00000000000000, + 0x9dc5ada82b70b59d, 0xf020000000000000, + 0xc5371912364ce305, 0x6c28000000000000, + 0xf684df56c3e01bc6, 0xc732000000000000, + 0x9a130b963a6c115c, 0x3c7f400000000000, + 0xc097ce7bc90715b3, 0x4b9f100000000000, + 0xf0bdc21abb48db20, 0x1e86d40000000000, + 0x96769950b50d88f4, 0x1314448000000000, + 0xbc143fa4e250eb31, 0x17d955a000000000, + 0xeb194f8e1ae525fd, 0x5dcfab0800000000, + 0x92efd1b8d0cf37be, 0x5aa1cae500000000, + 0xb7abc627050305ad, 0xf14a3d9e40000000, + 0xe596b7b0c643c719, 0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, + 0xb35dbf821ae4f38b, 0xdda2802c8a800000, + 0xe0352f62a19e306e, 0xd50b2037ad200000, + 0x8c213d9da502de45, 0x4526f422cc340000, + 0xaf298d050e4395d6, 0x9670b12b7f410000, + 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, + 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, + 0xab0e93b6efee0053, 0x8eea0d047a457a00, + 0xd5d238a4abe98068, 0x72a4904598d6d880, + 0x85a36366eb71f041, 0x47a6da2b7f864750, + 0xa70c3c40a64e6c51, 0x999090b65f67d924, + 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, + 0x82818f1281ed449f, 0xbff8f10e7a8921a4, + 0xa321f2d7226895c7, 0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, + 0xfee50b7025c36a08, 0x2f236d04753d5b4, + 0x9f4f2726179a2245, 0x1d762422c946590, + 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, + 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a, 0x8bef464e3945ef7a, + 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, + 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44, 0x60dbbca87196b616, + 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, + 0xb51d13aea4a488dd, 0x6babab6398bdbe41, + 0xe264589a4dcdab14, 0xc696963c7eed2dd1, + 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, + 0xdd15fe86affad912, 0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, + 0xacb92ed9397bf996, 0x49c2c37f07965404, + 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, + 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, + 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b, 0xf50a3fa490c30190, + 0x83c7088e1aab65db, 0x792667c6da79e0fa, + 0xa4b8cab1a1563f52, 0x577001b891185938, + 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, + 0x80b05e5ac60b6178, 0x544f8158315b05b4, + 0xa0dc75f1778e39d6, 0x696361ae3db1c721, + 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f, 0x4ab48a04065c723, + 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, + 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, + 0xf5746577930d6500, 0xca8f44ec7ee36479, + 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5, 0xbba1f1d158724a12, + 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, + 0xea1575143cf97226, 0xf52d09d71a3293bd, + 0x924d692ca61be758, 0x593c2626705f9c56, + 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, + 0xe498f455c38b997a, 0xb6dfb9c0f956447, + 0x8edf98b59a373fec, 0x4724bd4189bd5eac, + 0xb2977ee300c50fe7, 0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, + 0x8b865b215899f46c, 0xbd79e0d20082ee74, + 0xae67f1e9aec07187, 0xecd8590680a3aa11, + 0xda01ee641a708de9, 0xe80e6f4820cc9495, + 0x884134fe908658b2, 0x3109058d147fdcdd, + 0xaa51823e34a7eede, 0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, + 0x850fadc09923329e, 0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45, 0x84db8346b786151c, + 0xcfe87f7cef46ff16, 0xe612641865679a63, + 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749, 0xe3be5e330f38f09d, + 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, + 0xc646d63501a1511d, 0xb281e1fd541501b8, + 0xf7d88bc24209a565, 0x1f225a7ca91a4226, + 0x9ae757596946075f, 0x3375788de9b06958, + 0xc1a12d2fc3978937, 0x52d6b1641c83ae, + 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332, 0xf840b7ba963646e0, + 0xbd176620a501fbff, 0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf, 0xc66f336c36b10137, + 0xb8a8d9bbe123f017, 0xb80b0047445d4184, + 0xe6d3102ad96cec1d, 0xa60dc059157491e5, + 0x9043ea1ac7e41392, 0x87c89837ad68db2f, + 0xb454e4a179dd1877, 0x29babe4598c311fb, + 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, + 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d, 0x76707543f4fa1f73, + 0x899504ae72497eba, 0x6a06494a791c53a8, + 0xabfa45da0edbde69, 0x487db9d17636892, + 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, + 0xa7f26836f282b732, 0x8e6cac7768d7141e, + 0xd1ef0244af2364ff, 0x3207d795430cd926, + 0x8335616aed761f1f, 0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, + 0xcd036837130890a1, 0x36dba887c37a8c0f, + 0x802221226be55a64, 0xc2494954da2c9789, + 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d, 0x6f92829494e5acc7, + 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, + 0x9c69a97284b578d7, 0xff2a760414536efb, + 0xc38413cf25e2d70d, 0xfef5138519684aba, + 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, + 0x98bf2f79d5993802, 0xef2f773ffbd97a61, + 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2, 0xdd945a747bf26183, + 0xba756174393d88df, 0x94f971119aeef9e4, + 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, + 0x91abb422ccb812ee, 0xac62e055c10ab33a, + 0xb616a12b7fe617aa, 0x577b986b314d6009, + 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d, 0x14588f13be847307, + 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee, 0x25de7bb9480d5854, + 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, + 0xd910f7ff28069da4, 0x1b2ba1518094da04, + 0x87aa9aff79042286, 0x90fb44d2f05d0842, + 0xa99541bf57452b28, 0x353a1607ac744a53, + 0xd3fa922f2d1675f2, 0x42889b8997915ce8, + 0x847c9b5d7c2e09b7, 0x69956135febada11, + 0xa59bc234db398c25, 0x43fab9837e699095, + 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, + 0x8161afb94b44f57d, 0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc, 0x6462d92a69731732, + 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78, 0x5cda735244c3d43e, + 0x9defbf01b061adab, 0x3a0888136afa64a7, + 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b, 0x8aad549e57273d45, + 0x9a3c2087a63f6399, 0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, + 0x969eb7c47859e743, 0x9f644ae5a4b1b325, + 0xbc4665b596706114, 0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, + 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, + 0x8fa475791a569d10, 0xf96e017d694487bc, + 0xb38d92d760ec4455, 0x37c981dcc395a9ac, + 0xe070f78d3927556a, 0x85bbe253f47b1417, + 0x8c469ab843b89562, 0x93956d7478ccec8e, + 0xaf58416654a6babb, 0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, + 0x88fcf317f22241e2, 0x441fece3bdf81f03, + 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, + 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, + 0x85c7056562757456, 0xf6872d5667844e49, + 0xa738c6bebb12d16c, 0xb428f8ac016561db, + 0xd106f86e69d785c7, 0xe13336d701beba52, + 0x82a45b450226b39c, 0xecc0024661173473, + 0xa34d721642b06084, 0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, + 0xff290242c83396ce, 0x7e67047175a15271, + 0x9f79a169bd203e41, 0xf0062c6e984d386, + 0xc75809c42c684dd1, 0x52c07b78a3e60868, + 0xf92e0c3537826145, 0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb, 0x88a66076400bb691, + 0xc2abf989935ddbfe, 0x6acff893d00ea435, + 0xf356f7ebf83552fe, 0x583f6b8c4124d43, + 0x98165af37b2153de, 0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, + 0xeda2ee1c7064130c, 0x1162def06f79df73, + 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, + 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, + 0x8da471a9de737e24, 0x5ceaecfed289e5d2, + 0xb10d8e1456105dad, 0x7425a83e872c5f47, + 0xdd50f1996b947518, 0xd12f124e28f77719, + 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, + 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, + 0x8714a775e3e95c78, 0x65acfaec34810a71, + 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, + 0xd31045a8341ca07c, 0x1ede48111209a050, + 0x83ea2b892091e44d, 0x934aed0aab460432, + 0xa4e4b66b68b65d60, 0xf81da84d5617853f, + 0xce1de40642e3f4b9, 0x36251260ab9d668e, + 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, + 0xa1075a24e4421730, 0xb24cf65b8612f81f, + 0xc94930ae1d529cfc, 0xdee033f26797b627, + 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, + 0x9d412e0806e88aa5, 0x8e1f289560ee864e, + 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, + 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, + 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, + 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513, 0x84c86189216dc5ed, + 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515, 0xfabaf3feaa5334a, + 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8, 0x743e20e9ef511012, + 0xdf78e4b2bd342cf6, 0x914da9246b255416, + 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, + 0xae9672aba3d0c320, 0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, + 0x8865899617fb1871, 0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, + 0xd51ea6fa85785631, 0x552a74227f3ea565, + 0x8533285c936b35de, 0xd53a88958f87275f, + 0xa67ff273b8460356, 0x8a892abaf368f137, + 0xd01fef10a657842c, 0x2d2b7569b0432d85, + 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, + 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, + 0xcb3f2f7642717713, 0x241c70a936219a73, + 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, + 0x9ec95d1463e8a506, 0xf4363804324a40aa, + 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, + 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, + 0x976e41088617ca01, 0xd5be0503e085d813, + 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, + 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45, 0xcabb90e5c942b503, + 0xb8da1662e7b00a17, 0x3d6a751f3b936243, + 0xe7109bfba19c0c9d, 0xcc512670a783ad4, + 0x906a617d450187e2, 0x27fb2b80668b24c5, + 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, + 0xe1a63853bbd26451, 0x5e7873f8a0396973, + 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, + 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, + 0xac2820d9623bf429, 0x546345fa9fbdcd44, + 0xd732290fbacaf133, 0xa97c177947ad4095, + 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, + 0xa81f301449ee8c70, 0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c, 0x73832eec6fff3111, + 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, + 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, + 0xa0555e361951c366, 0xd7e105bcc332621f, + 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, + 0xfa856334878fc150, 0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07, 0xa862f80ec4700c8, + 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, + 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, + 0xeeea5d5004981478, 0x1858ccfce06cac74, + 0x95527a5202df0ccb, 0xf37801e0c43ebc8, + 0xbaa718e68396cffd, 0xd30560258f54e6ba, + 0xe950df20247c83fd, 0x47c6b82ef32a2069, + 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, + 0xb6472e511c81471d, 0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5, 0x58180fddd97723a6, + 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, + }; +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template +constexpr uint64_t + powers_template::power_of_five_128[number_of_entries]; + +#endif + +using powers = powers_template<>; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit +// words approximating the result, with the "high" part corresponding to the +// most significant bits and the low part corresponding to the least significant +// bits. +// +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 +compute_product_approximation(int64_t q, uint64_t w) { + int const index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact + // because The line value128 firstproduct = full_multiplication(w, + // power_of_five_128[index]); gives the exact answer. + value128 firstproduct = + full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), + " precision should be in (0,64]"); + constexpr uint64_t precision_mask = + (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if ((firstproduct.high & precision_mask) == + precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our + // expectation is that the compiler will optimize this extra work away if + // needed. + value128 secondproduct = + full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if (secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ +constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; +} +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa +compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = + compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// Computers w * 10 ** q. +// The returned value should be a valid number that simply needs to be +// packed. However, in some very rare cases, the computation will fail. In such +// cases, we return an adjusted_mantissa with a negative power of 2: the caller +// should recompute in such cases. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, + // powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, + // requiring a shift) + + value128 product = + compute_product_approximation(q, w); + // The computed 'product' is always sufficient. + // Mathematical proof: + // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to + // appear) See script/mushtak_lemire.py + + // The "compute_product_approximation" function can be slightly slower than a + // branchless approach: value128 product = compute_product(q, w); but in + // practice, we can win big with the compute_product_approximation if its + // additional branch is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; + + answer.mantissa = product.high >> shift; + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - + binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if (-answer.power2 + 1 >= + 64) { // if we have more than 64 bits below the minimum exponent, you + // have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0 in the 32-bit and + // and 64-bit case (with no more than 19 digits). + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = + (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) + ? 0 + : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && + (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1)) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - + // binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go + // back!!! + if ((answer.mantissa << shift) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB 1 +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(stackvec const &) = delete; + stackvec &operator=(stackvec const &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + + FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + + // index from the end of the container + FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { + length = uint16_t(len); + } + + constexpr size_t len() const noexcept { return length; } + + constexpr bool is_empty() const noexcept { return length == 0; } + + constexpr size_t capacity() const noexcept { return size; } + + // append item to vector, without bounds checking + FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + + // append item to vector, returning if item was added + FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + + // add items to the vector, from a span, without bounds checking + FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { + limb *ptr = data + length; + tinyobj_ff::copy_n(s.ptr, s.len(), ptr); + set_len(len() + s.len()); + } + + // try to add items to the vector, returning if items were added + FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + FASTFLOAT_CONSTEXPR20 + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb *first = data + len(); + limb *last = first + count; + tinyobj_ff::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + + // try to resize the vector, returning if the vector was resized. + FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + + // normalize the big integer, so most-significant zero limbs are removed. + FASTFLOAT_CONSTEXPR14 void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t +empty_hi64(bool &truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, bool &truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, bool &truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t +uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_add(limb x, limb y, bool &overflow) noexcept { + limb z; +// gcc and clang +#if defined(__has_builtin) +#if __has_builtin(__builtin_add_overflow) + if (!cpp20_and_in_constexpr()) { + overflow = __builtin_add_overflow(x, y, &z); + return z; + } +#endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb +scalar_mul(limb x, limb y, limb &carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB +#if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; +#endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, + size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +small_add(stackvec &vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, + limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, + size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +large_add_from(stackvec &x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +template struct pow5_tables { + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, + 5UL, + 25UL, + 125UL, + 625UL, + 3125UL, + 15625UL, + 78125UL, + 390625UL, + 1953125UL, + 9765625UL, + 48828125UL, + 244140625UL, + 1220703125UL, + 6103515625UL, + 30517578125UL, + 152587890625UL, + 762939453125UL, + 3814697265625UL, + 19073486328125UL, + 95367431640625UL, + 476837158203125UL, + 2384185791015625UL, + 11920928955078125UL, + 59604644775390625UL, + 298023223876953125UL, + 1490116119384765625UL, + 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif +}; + +#if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE + +template constexpr uint32_t pow5_tables::large_step; + +template constexpr uint64_t pow5_tables::small_power_of_5[]; + +template constexpr limb pow5_tables::large_power_of_5[]; + +#endif + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint : pow5_tables<> { + // storage of the limbs, in little-endian order. + stackvec vec; + + FASTFLOAT_CONSTEXPR20 bigint() : vec() {} + + bigint(bigint const &) = delete; + bigint &operator=(bigint const &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = + uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb *dst = vec.data + n; + limb const *src = vec.data; + tinyobj_ff::copy_backward(src, src + vec.len(), dst + vec.len()); + // fill in empty limbs + limb *first = vec.data; + limb *last = first + n; + tinyobj_ff::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } + + FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } + + // multiply as if by 2 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } + + // multiply as if by 5 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + // This is similar to https://github.com/llvm/llvm-project/issues/47746, + // except the workaround described there don't work here + FASTFLOAT_TRY(small_mul( + vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = {1UL, + 10UL, + 100UL, + 1000UL, + 10000UL, + 100000UL, + 1000000UL, + 10000000UL, + 100000000UL, + 1000000000UL, + 10000000000UL, + 100000000000UL, + 1000000000000UL, + 10000000000000UL, + 100000000000000UL, + 1000000000000000UL, + 10000000000000000UL, + 100000000000000000UL, + 1000000000000000000UL, + 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t +scientific_exponent(parsed_number_string_t &num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended(T value) noexcept { + using equiv_uint = equiv_uint_t; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + equiv_uint bits; +#if FASTFLOAT_HAS_BIT_CAST + bits = std::bit_cast(value); +#else + ::memcpy(&bits, &value, sizeof(T)); +#endif + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> + binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, + callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, tinyobj_ff::min_val(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < + (uint64_t(1) << binary_format::mantissa_explicit_bits())) + ? 0 + : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= + (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, + callback cb) noexcept { + uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; + uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); + uint64_t truncated_bits = am.mantissa & mask; + bool is_above = truncated_bits > halfway; + bool is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +round_down(adjusted_mantissa &am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +skip_zeros(UC const *&first, UC const *last) noexcept { + uint64_t val; + while (!cpp20_and_in_constexpr() && + tinyobj_ff::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + break; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(UC const *first, UC const *last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (!cpp20_and_in_constexpr() && + tinyobj_ff::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + return true; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + return true; + } + ++first; + } + return false; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool +is_truncated(span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +parse_eight_digits(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void +parse_one_digit(UC const *&p, limb &value, size_t &counter, + size_t &count) noexcept { + value = value * 10 + limb(*p - UC('0')); + p++; + counter++; + count++; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +add_native(bigint &big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void +round_up_bigint(bigint &big, size_t &count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +template +inline FASTFLOAT_CONSTEXPR20 void +parse_mantissa(bigint &result, parsed_number_string_t &num, + size_t max_digits, size_t &digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + UC const *p = num.integer.ptr; + UC const *pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((tinyobj_ff::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((tinyobj_ff::distance(p, pend) >= 8) && (step - counter >= 8) && + (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - + binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, + [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || + (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( + bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint &real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with + // -Wnoexcept-type. + round(am_b, + [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa &a, int32_t shift) { + round_nearest_tie_even( + a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa +digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +#include +#include +#include + +namespace fast_float { + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result_t + FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, + T &value, chars_format fmt) noexcept { + from_chars_result_t answer{}; + answer.ptr = first; + answer.ec = tinyobj_ff::ff_errc(); // be optimistic + // assume first < last, so dereference without checks; + bool const minusSign = (*first == UC('-')); + // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + if ((*first == UC('-')) || + (uint64_t(fmt & chars_format::allow_leading_plus) && + (*first == UC('+')))) { + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() + : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, + // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if (first != last && *first == UC('(')) { + for (UC const *ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == UC(')')) { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || + (UC('A') <= *ptr && *ptr <= UC('Z')) || + (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { + if ((last - first >= 8) && + fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = tinyobj_ff::ff_errc::invalid_argument; + return answer; +} + +/** + * Returns true if the floating-pointing rounding mode is to 'nearest'. + * It is the default on most system. This function is meant to be inexpensive. + * Credit : @mwalcott3 + */ +fastfloat_really_inline bool rounds_to_nearest() noexcept { + // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return false; +#endif + // See + // A fast function to check your floating-point rounding mode + // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ + // + // This function is meant to be equivalent to : + // prior: #include + // return fegetround() == FE_TONEAREST; + // However, it is expected to be much faster than the fegetround() + // function call. + // + // The volatile keyword prevents the compiler from computing the function + // at compile-time. + // There might be other ways to prevent compile-time optimizations (e.g., + // asm). The value does not need to be std::numeric_limits::min(), any + // small value so that 1 + x should round to 1 would do (after accounting for + // excess precision, as in 387 instructions). + static float volatile fmin = std::numeric_limits::min(); + float fmini = fmin; // we copy it so that it gets loaded at most once. +// +// Explanation: +// Only when fegetround() == FE_TONEAREST do we have that +// fmin + 1.0f == 1.0f - fmin. +// +// FE_UPWARD: +// fmin + 1.0f > 1 +// 1.0f - fmin == 1 +// +// FE_DOWNWARD or FE_TOWARDZERO: +// fmin + 1.0f == 1 +// 1.0f - fmin < 1 +// +// Note: This may fail to be accurate if fast-math has been +// enabled, as rounding conventions may not apply. +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(push) +// todo: is there a VS warning? +// see +// https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + return (fmini + 1.0f == 1.0f - fmini); +#ifdef FASTFLOAT_VISUAL_STUDIO +#pragma warning(pop) +#elif defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +} // namespace detail + +template struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced(first, last, value, options); + } +}; + +#ifdef __STDCPP_FLOAT32_T__ +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float32_t &value, + parse_options_t options) noexcept { + // if std::float32_t is defined, and we are in C++23 mode; macro set for + // float32; set value to float due to equivalence between float and + // float32_t + float val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +#ifdef __STDCPP_FLOAT64_T__ +template <> struct from_chars_caller { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, std::float64_t &value, + parse_options_t options) noexcept { + // if std::float64_t is defined, and we are in C++23 mode; macro set for + // float64; set value as double due to equivalence between double and + // float64_t + double val; + auto ret = from_chars_advanced(first, last, val, options); + value = val; + return ret; + } +}; +#endif + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, + chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_caller::call(first, last, value, + parse_options_t(fmt)); +} + +/** + * This function overload takes parsed_number_string_t structure that is created + * and populated either by from_chars_advanced function taking chars range and + * parsing options or other parsing custom function implemented by user. + */ +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { + + static_assert(is_supported_float_type::value, + "only some floating-point types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; + + answer.ec = tinyobj_ff::ff_errc(); // be optimistic + answer.ptr = pns.lastmatch; + // The implementation of the Clinger's fast path is convoluted because + // we want round-to-nearest in all cases, irrespective of the rounding mode + // selected on the thread. + // We proceed optimistically, assuming that detail::rounds_to_nearest() + // returns true. + if (binary_format::min_exponent_fast_path() <= pns.exponent && + pns.exponent <= binary_format::max_exponent_fast_path() && + !pns.too_many_digits) { + // Unfortunately, the conventional Clinger's fast path is only possible + // when the system rounds to the nearest float. + // + // We expect the next branch to almost always be selected. + // We could check it first (before the previous branch), but + // there might be performance advantages at having the check + // be last. + if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { + // We have that fegetround() == FE_TONEAREST. + // Next is Clinger's fast path. + if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { + value = T(pns.mantissa); + if (pns.exponent < 0) { + value = value / binary_format::exact_power_of_ten(-pns.exponent); + } else { + value = value * binary_format::exact_power_of_ten(pns.exponent); + } + if (pns.negative) { + value = -value; + } + return answer; + } + } else { + // We do not have that fegetround() == FE_TONEAREST. + // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's + // proposal + if (pns.exponent >= 0 && + pns.mantissa <= + binary_format::max_mantissa_fast_path(pns.exponent)) { +#if defined(__clang__) || defined(FASTFLOAT_32BIT) + // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if (pns.mantissa == 0) { + value = pns.negative ? T(-0.) : T(0.); + return answer; + } +#endif + value = T(pns.mantissa) * + binary_format::exact_power_of_ten(pns.exponent); + if (pns.negative) { + value = -value; + } + return answer; + } + } + } + adjusted_mantissa am = + compute_float>(pns.exponent, pns.mantissa); + if (pns.too_many_digits && am.power2 >= 0) { + if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) + // and we have an invalid power (am.power2 < 0), then we need to go the long + // way around again. This is very uncommon. + if (am.power2 < 0) { + am = digit_comp(pns, am); + } + to_float(pns.negative, am, value); + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || + am.power2 == binary_format::infinite_power()) { + answer.ec = tinyobj_ff::ff_errc::result_out_of_range; + } + return answer; +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_float_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_float_type::value, + "only some floating-point types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + + from_chars_result_t answer; + if (uint64_t(fmt & chars_format::skip_white_space)) { + while ((first != last) && fast_float::is_space(*first)) { + first++; + } + } + if (first == last) { + answer.ec = tinyobj_ff::ff_errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string_t pns = + uint64_t(fmt & detail::basic_json_fmt) + ? parse_number_string(first, last, options) + : parse_number_string(first, last, options); + if (!pns.valid) { + if (uint64_t(fmt & chars_format::no_infnan)) { + answer.ec = tinyobj_ff::ff_errc::invalid_argument; + answer.ptr = first; + return answer; + } else { + return detail::parse_infnan(first, last, value, fmt); + } + } + + // call overload that takes parsed_number_string_t directly. + return from_chars_advanced(pns, value); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars(UC const *first, UC const *last, T &value, int base) noexcept { + + static_assert(is_supported_integer_type::value, + "only integer types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + parse_options_t options; + options.base = base; + return from_chars_advanced(first, last, value, options); +} + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_int_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + + static_assert(is_supported_integer_type::value, + "only integer types are supported"); + static_assert(is_supported_char_type::value, + "only char, wchar_t, char16_t and char32_t are supported"); + + chars_format const fmt = detail::adjust_for_feature_macros(options.format); + int const base = options.base; + + from_chars_result_t answer; + if (uint64_t(fmt & chars_format::skip_white_space)) { + while ((first != last) && fast_float::is_space(*first)) { + first++; + } + } + if (first == last || base < 2 || base > 36) { + answer.ec = tinyobj_ff::ff_errc::invalid_argument; + answer.ptr = first; + return answer; + } + + return parse_int_string(first, last, value, options); +} + +template struct from_chars_advanced_caller { + static_assert(TypeIx > 0, "unsupported type"); +}; + +template <> struct from_chars_advanced_caller<1> { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_float_advanced(first, last, value, options); + } +}; + +template <> struct from_chars_advanced_caller<2> { + template + FASTFLOAT_CONSTEXPR20 static from_chars_result_t + call(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_int_advanced(first, last, value, options); + } +}; + +template +FASTFLOAT_CONSTEXPR20 from_chars_result_t +from_chars_advanced(UC const *first, UC const *last, T &value, + parse_options_t options) noexcept { + return from_chars_advanced_caller< + size_t(is_supported_float_type::value) + + 2 * size_t(is_supported_integer_type::value)>::call(first, last, value, + options); +} + +} // namespace fast_float + +#endif + + +// --- End embedded fast_float --- + +// Clean up fast_float macros to avoid polluting the user's namespace. +#undef FASTFLOAT_32BIT +#undef FASTFLOAT_32BIT_LIMB +#undef FASTFLOAT_64BIT +#undef FASTFLOAT_64BIT_LIMB +#undef FASTFLOAT_ASCII_NUMBER_H +#undef FASTFLOAT_ASSERT +#undef FASTFLOAT_BIGINT_H +#undef FASTFLOAT_CONSTEXPR14 +#undef FASTFLOAT_CONSTEXPR20 +#undef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#undef FASTFLOAT_DEBUG_ASSERT +#undef FASTFLOAT_DECIMAL_TO_BINARY_H +#undef FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE +#undef FASTFLOAT_DIGIT_COMPARISON_H +#undef FASTFLOAT_ENABLE_IF +#undef FASTFLOAT_FAST_FLOAT_H +#undef FASTFLOAT_FAST_TABLE_H +#undef FASTFLOAT_FLOAT_COMMON_H +#undef FASTFLOAT_HAS_BIT_CAST +#undef FASTFLOAT_HAS_IS_CONSTANT_EVALUATED +#undef FASTFLOAT_HAS_SIMD +#undef FASTFLOAT_IF_CONSTEXPR17 +#undef FASTFLOAT_IS_BIG_ENDIAN +#undef FASTFLOAT_IS_CONSTEXPR +#undef FASTFLOAT_NEON +#undef FASTFLOAT_PARSE_NUMBER_H +#undef fastfloat_really_inline +#undef FASTFLOAT_SIMD_DISABLE_WARNINGS +#undef FASTFLOAT_SIMD_RESTORE_WARNINGS +#undef FASTFLOAT_SSE2 +#undef FASTFLOAT_STRINGIZE +#undef FASTFLOAT_STRINGIZE_IMPL +#undef FASTFLOAT_TRY +#undef FASTFLOAT_VERSION +#undef FASTFLOAT_VERSION_MAJOR +#undef FASTFLOAT_VERSION_MINOR +#undef FASTFLOAT_VERSION_PATCH +#undef FASTFLOAT_VERSION_STR +#undef FASTFLOAT_VISUAL_STUDIO + +#endif // TINYOBJLOADER_DISABLE_FAST_FLOAT + +namespace tinyobj { + +MaterialReader::~MaterialReader() {} + +// Byte-stream reader for bounds-checked text parsing. +// Replaces raw `const char*` token pointers with `(buf, len, idx)` triple. +// Every byte access is guarded by an EOF check. +class StreamReader { + public: +// Maximum number of bytes StreamReader will buffer from std::istream. +// Define this macro to a larger value if your application needs to parse +// very large streamed OBJ/MTL content. +#ifndef TINYOBJLOADER_STREAM_READER_MAX_BYTES +#define TINYOBJLOADER_STREAM_READER_MAX_BYTES (size_t(256) * size_t(1024) * size_t(1024)) +#endif + + StreamReader(const char *buf, size_t length) + : buf_(buf), length_(length), idx_(0), line_num_(1), col_num_(1) {} + + // Non-copyable, non-movable: buf_ may point into owned_buf_. + StreamReader(const StreamReader &) /* = delete */; + StreamReader &operator=(const StreamReader &) /* = delete */; + + // Build from std::istream by reading all content into an internal buffer. + explicit StreamReader(std::istream &is) : buf_(NULL), length_(0), idx_(0), line_num_(1), col_num_(1) { + const size_t max_stream_bytes = TINYOBJLOADER_STREAM_READER_MAX_BYTES; + std::streampos start_pos = is.tellg(); + bool can_seek = (start_pos != std::streampos(-1)); + if (can_seek) { + is.seekg(0, std::ios::end); + std::streampos end_pos = is.tellg(); + if (end_pos >= start_pos) { + std::streamoff remaining_off = static_cast(end_pos - start_pos); + is.seekg(start_pos); + unsigned long long remaining_ull = static_cast(remaining_off); + if (remaining_ull > static_cast((std::numeric_limits::max)())) { + std::stringstream ss; + ss << "input stream too large for this platform (" << remaining_ull + << " bytes exceeds size_t max " << (std::numeric_limits::max)() << ")\n"; + push_error(ss.str()); + buf_ = ""; + length_ = 0; + return; + } + size_t remaining_size = static_cast(remaining_ull); + if (remaining_size > max_stream_bytes) { + std::stringstream ss; + ss << "input stream too large (" << remaining_size + << " bytes exceeds limit " << max_stream_bytes << " bytes)\n"; + push_error(ss.str()); + buf_ = ""; + length_ = 0; + return; + } + owned_buf_.resize(remaining_size); + if (remaining_size > 0) { + is.read(&owned_buf_[0], static_cast(remaining_size)); + } + size_t actually_read = static_cast(is.gcount()); + owned_buf_.resize(actually_read); + } + } + if (!can_seek || owned_buf_.empty()) { + // Stream doesn't support seeking, or seek probing failed. + if (can_seek) is.seekg(start_pos); + is.clear(); + std::vector content; + char chunk[4096]; + size_t total_read = 0; + while (is.good()) { + is.read(chunk, static_cast(sizeof(chunk))); + std::streamsize nread = is.gcount(); + if (nread <= 0) break; + size_t n = static_cast(nread); + if (n > (max_stream_bytes - total_read)) { + std::stringstream ss; + ss << "input stream too large (exceeds limit " << max_stream_bytes + << " bytes)\n"; + push_error(ss.str()); + owned_buf_.clear(); + buf_ = ""; + length_ = 0; + return; + } + content.insert(content.end(), chunk, chunk + n); + total_read += n; + } + owned_buf_.swap(content); + } + buf_ = owned_buf_.empty() ? "" : &owned_buf_[0]; + length_ = owned_buf_.size(); + } + + bool eof() const { return idx_ >= length_; } + size_t tell() const { return idx_; } + size_t size() const { return length_; } + size_t line_num() const { return line_num_; } + size_t col_num() const { return col_num_; } + + char peek() const { + if (idx_ >= length_) return '\0'; + return buf_[idx_]; + } + + char get() { + if (idx_ >= length_) return '\0'; + char c = buf_[idx_++]; + if (c == '\n') { line_num_++; col_num_ = 1; } else { col_num_++; } + return c; + } + + void advance(size_t n) { + for (size_t i = 0; i < n && idx_ < length_; i++) { + if (buf_[idx_] == '\n') { line_num_++; col_num_ = 1; } else { col_num_++; } + idx_++; + } + } + + void skip_space() { + while (idx_ < length_ && (buf_[idx_] == ' ' || buf_[idx_] == '\t')) { + col_num_++; + idx_++; + } + } + + void skip_space_and_cr() { + while (idx_ < length_ && (buf_[idx_] == ' ' || buf_[idx_] == '\t' || buf_[idx_] == '\r')) { + col_num_++; + idx_++; + } + } + + void skip_line() { + while (idx_ < length_) { + char c = buf_[idx_]; + if (c == '\n') { + idx_++; + line_num_++; + col_num_ = 1; + return; + } + if (c == '\r') { + idx_++; + if (idx_ < length_ && buf_[idx_] == '\n') { + idx_++; + } + line_num_++; + col_num_ = 1; + return; + } + col_num_++; + idx_++; + } + } + + bool at_line_end() const { + if (idx_ >= length_) return true; + char c = buf_[idx_]; + return (c == '\n' || c == '\r' || c == '\0'); + } + + std::string read_line() { + std::string result; + while (idx_ < length_) { + char c = buf_[idx_]; + if (c == '\n' || c == '\r') break; + result += c; + col_num_++; + idx_++; + } + return result; + } + + // Reads a whitespace-delimited token. Used by tests and as a general utility. + std::string read_token() { + skip_space(); + std::string result; + while (idx_ < length_) { + char c = buf_[idx_]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; + result += c; + col_num_++; + idx_++; + } + return result; + } + + bool match(const char *prefix, size_t len) const { + if (idx_ >= length_ || len > length_ - idx_) return false; + return (memcmp(buf_ + idx_, prefix, len) == 0); + } + + bool char_at(size_t offset, char c) const { + if (idx_ >= length_ || offset >= length_ - idx_) return false; + return buf_[idx_ + offset] == c; + } + + char peek_at(size_t offset) const { + if (idx_ >= length_ || offset >= length_ - idx_) return '\0'; + return buf_[idx_ + offset]; + } + + const char *current_ptr() const { + if (idx_ >= length_) return ""; + return buf_ + idx_; + } + + size_t remaining() const { + return (idx_ < length_) ? (length_ - idx_) : 0; + } + + // Returns the full text of the current line (for diagnostic display). + std::string current_line_text() const { + // Scan backward to find line start + size_t line_start = idx_; + while (line_start > 0 && buf_[line_start - 1] != '\n' && buf_[line_start - 1] != '\r') { + line_start--; + } + // Scan forward to find line end + size_t line_end = idx_; + while (line_end < length_ && buf_[line_end] != '\n' && buf_[line_end] != '\r') { + line_end++; + } + return std::string(buf_ + line_start, line_end - line_start); + } + + // Clang-style formatted error with file:line:col and caret. + std::string format_error(const std::string &filename, const std::string &msg) const { + std::stringstream line_ss, col_ss; + line_ss << line_num_; + col_ss << col_num_; + std::string result; + result += filename + ":" + line_ss.str() + ":" + col_ss.str() + ": error: " + msg + "\n"; + std::string line_text = current_line_text(); + result += line_text + "\n"; + // Build caret line preserving tab alignment + std::string caret; + size_t caret_pos = (col_num_ > 0) ? (col_num_ - 1) : 0; + for (size_t i = 0; i < caret_pos && i < line_text.size(); i++) { + caret += (line_text[i] == '\t') ? '\t' : ' '; + } + caret += "^"; + result += caret + "\n"; + return result; + } + + std::string format_error(const std::string &msg) const { + return format_error("", msg); + } + + // Error stack + void push_error(const std::string &msg) { + errors_.push_back(msg); + } + + void push_formatted_error(const std::string &filename, const std::string &msg) { + errors_.push_back(format_error(filename, msg)); + } + + bool has_errors() const { return !errors_.empty(); } + + std::string get_errors() const { + std::string result; + for (size_t i = 0; i < errors_.size(); i++) { + result += errors_[i]; + } + return result; + } + + const std::vector &error_stack() const { return errors_; } + + void clear_errors() { errors_.clear(); } + + private: + const char *buf_; + size_t length_; + size_t idx_; + size_t line_num_; + size_t col_num_; + std::vector owned_buf_; + std::vector errors_; +}; + +#ifdef TINYOBJLOADER_USE_MMAP +// RAII wrapper for memory-mapped file I/O. +// Opens a file and maps it into memory; the mapping is released on destruction. +// For empty files, data is set to "" and is_mapped remains false so close() +// will not attempt to unmap a string literal. +struct MappedFile { + const char *data; + size_t size; + bool is_mapped; // true when data points to an actual mapped region +#if defined(_WIN32) + HANDLE hFile; + HANDLE hMapping; +#else + void *mapped_ptr; +#endif + + MappedFile() : data(NULL), size(0), is_mapped(false) +#if defined(_WIN32) + , hFile(INVALID_HANDLE_VALUE), hMapping(NULL) +#else + , mapped_ptr(NULL) +#endif + {} + + // Opens and maps the file. Returns true on success. + bool open(const char *filepath) { +#if defined(_WIN32) + std::wstring wfilepath = LongPathW(UTF8ToWchar(std::string(filepath))); + hFile = CreateFileW(wfilepath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return false; + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(hFile, &fileSize)) { close(); return false; } + if (fileSize.QuadPart < 0) { close(); return false; } + unsigned long long fsize = static_cast(fileSize.QuadPart); + if (fsize > static_cast((std::numeric_limits::max)())) { + close(); + return false; + } + size = static_cast(fsize); + if (size == 0) { data = ""; return true; } // valid but empty; is_mapped stays false + hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hMapping == NULL) { close(); return false; } + data = static_cast(MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0)); + if (!data) { close(); return false; } + is_mapped = true; + return true; +#else + int fd = ::open(filepath, O_RDONLY); + if (fd == -1) return false; + struct stat sb; + if (fstat(fd, &sb) != 0) { ::close(fd); return false; } + if (sb.st_size < 0) { ::close(fd); return false; } + if (static_cast(sb.st_size) > + static_cast((std::numeric_limits::max)())) { + ::close(fd); + return false; + } + size = static_cast(sb.st_size); + if (size == 0) { ::close(fd); data = ""; return true; } // valid but empty + mapped_ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + ::close(fd); + if (mapped_ptr == MAP_FAILED) { mapped_ptr = NULL; size = 0; return false; } + data = static_cast(mapped_ptr); + is_mapped = true; + return true; +#endif + } + + void close() { +#if defined(_WIN32) + if (is_mapped && data) { UnmapViewOfFile(data); } + data = NULL; + is_mapped = false; + if (hMapping != NULL) { CloseHandle(hMapping); hMapping = NULL; } + if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; } +#else + if (is_mapped && mapped_ptr && mapped_ptr != MAP_FAILED) { munmap(mapped_ptr, size); } + mapped_ptr = NULL; + data = NULL; + is_mapped = false; +#endif + size = 0; + } + + ~MappedFile() { close(); } + + private: + MappedFile(const MappedFile &); // non-copyable + MappedFile &operator=(const MappedFile &); // non-copyable +}; +#endif // TINYOBJLOADER_USE_MMAP + + +struct vertex_index_t { + int v_idx, vt_idx, vn_idx; + vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} + vertex_index_t(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} +}; + +// Internal data structure for face representation +// index + smoothing group. +struct face_t { + unsigned int + smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. + int pad_; + std::vector vertex_indices; // face vertex indices. + + face_t() : smoothing_group_id(0), pad_(0) {} +}; + +// Internal data structure for line representation +struct __line_t { + // l v1/vt1 v2/vt2 ... + // In the specification, line primitrive does not have normal index, but + // TinyObjLoader allow it + std::vector vertex_indices; +}; + +// Internal data structure for points representation +struct __points_t { + // p v1 v2 ... + // In the specification, point primitrive does not have normal index and + // texture coord index, but TinyObjLoader allow it. + std::vector vertex_indices; +}; + +struct tag_sizes { + tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} + int num_ints; + int num_reals; + int num_strings; +}; + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +// +// Manages group of primitives(face, line, points, ...) +struct PrimGroup { + std::vector faceGroup; + std::vector<__line_t> lineGroup; + std::vector<__points_t> pointsGroup; + + void clear() { + faceGroup.clear(); + lineGroup.clear(); + pointsGroup.clear(); + } + + bool IsEmpty() const { + return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); + } + + // TODO(syoyo): bspline, surface, ... +}; + +// See +// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +template +static inline std::string toString(const T &t) { + std::stringstream ss; + ss << t; + return ss.str(); +} + +static inline std::string removeUtf8Bom(const std::string& input) { + // UTF-8 BOM = 0xEF,0xBB,0xBF + if (input.size() >= 3 && + static_cast(input[0]) == 0xEF && + static_cast(input[1]) == 0xBB && + static_cast(input[2]) == 0xBF) { + return input.substr(3); // Skip BOM + } + return input; +} + +// Trim trailing spaces and tabs from a string. +static inline std::string trimTrailingWhitespace(const std::string &s) { + size_t end = s.find_last_not_of(" \t"); + if (end == std::string::npos) return ""; + return s.substr(0, end + 1); } struct warning_context { std::string *warn; size_t line_number; + std::string filename; }; +// Safely convert size_t to int, clamping at INT_MAX to prevent overflow. +static inline int size_to_int(size_t sz) { + return sz > static_cast(INT_MAX) ? INT_MAX : static_cast(sz); +} + // Make index zero-base, and also support relative index. static inline bool fixIndex(int idx, int n, int *ret, bool allow_zero, const warning_context &context) { @@ -831,9 +5846,9 @@ static inline bool fixIndex(int idx, int n, int *ret, bool allow_zero, // zero is not allowed according to the spec. if (context.warn) { (*context.warn) += - "A zero value index found (will have a value of -1 for normal and " - "tex indices. Line " + - toString(context.line_number) + ").\n"; + context.filename + ":" + toString(context.line_number) + + ": warning: zero value index found (will have a value of -1 for " + "normal and tex indices)\n"; } (*ret) = idx - 1; @@ -867,6 +5882,145 @@ static inline int parseInt(const char **token) { return i; } +#ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT + +// ---- fast_float-based float parser (bit-exact with strtod, ~3x faster) ---- + +namespace detail_fp { + +// Case-insensitive prefix match. Returns pointer past matched prefix, or NULL. +static inline const char *match_iprefix(const char *p, const char *end, + const char *prefix) { + while (*prefix) { + if (p == end) return NULL; + char c = *p; + char e = *prefix; + if (c >= 'A' && c <= 'Z') c += 32; + if (e >= 'A' && e <= 'Z') e += 32; + if (c != e) return NULL; + ++p; + ++prefix; + } + return p; +} + +// Try to parse nan/inf. Returns true if matched, sets *result and *end_ptr. +static inline bool tryParseNanInf(const char *first, const char *last, + double *result, const char **end_ptr) { + if (first >= last) return false; + + const char *p = first; + bool negative = false; + + if (*p == '-') { + negative = true; + ++p; + } else if (*p == '+') { + ++p; + } + + if (p >= last) return false; + + // Try "nan" + const char *after = match_iprefix(p, last, "nan"); + if (after) { + *result = 0.0; // nan -> 0.0 for OBJ + *end_ptr = after; + return true; + } + + // Try "infinity" first (longer match), then "inf" + after = match_iprefix(p, last, "infinity"); + if (after) { + *result = negative ? std::numeric_limits::lowest() + : (std::numeric_limits::max)(); + *end_ptr = after; + return true; + } + + after = match_iprefix(p, last, "inf"); + if (after) { + *result = negative ? std::numeric_limits::lowest() + : (std::numeric_limits::max)(); + *end_ptr = after; + return true; + } + + return false; +} + +} // namespace detail_fp + +// Tries to parse a floating point number located at s. +// Uses fast_float::from_chars for bit-exact, high-performance parsing. +// Handles OBJ quirks: leading '+', nan/inf with replacement values. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) { + if (!s || !s_end || !result || s >= s_end) { + return false; + } + + // Check for nan/inf (starts with [nNiI] or [+-] followed by [nNiI]) + const char *p = s; + if (p < s_end && (*p == '+' || *p == '-')) ++p; + if (p < s_end) { + char fc = *p; + if (fc >= 'A' && fc <= 'Z') fc += 32; + if (fc == 'n' || fc == 'i') { + const char *end_ptr; + if (detail_fp::tryParseNanInf(s, s_end, result, &end_ptr)) { + return true; + } + } + } + + // Use allow_leading_plus so fast_float handles '+' natively. + double tmp; + auto r = fast_float::from_chars(s, s_end, tmp, + fast_float::chars_format::general | + fast_float::chars_format::allow_leading_plus); + if (r.ec == tinyobj_ff::ff_errc::ok) { + *result = tmp; + return true; + } + // On error (invalid_argument, result_out_of_range), *result is unchanged. + + return false; +} + +static inline real_t parseReal(const char **token, double default_value = 0.0) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val = default_value; + tryParseDouble((*token), end, &val); + real_t f = static_cast(val); + (*token) = end; + return f; +} + +static inline bool parseReal(const char **token, real_t *out) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val; + bool ret = tryParseDouble((*token), end, &val); + if (ret) { + real_t f = static_cast(val); + (*out) = f; + } + (*token) = end; + return ret; +} + +#else // TINYOBJLOADER_DISABLE_FAST_FLOAT + +// ---- Legacy hand-written float parser (fallback) ---- + // Tries to parse a floating point number located at s. // // s_end should be a location in the string where reading should absolutely @@ -1004,7 +6158,7 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) { // To avoid annoying MSVC's min/max macro definiton, // Use hardcoded int max value if (exponent > - (2147483647 / 10)) { // 2147483647 = std::numeric_limits::max() + ((2147483647 - 9) / 10)) { // (INT_MAX - 9) / 10, guards both multiply and add // Integer overflow goto fail; } @@ -1050,6 +6204,8 @@ static inline bool parseReal(const char **token, real_t *out) { return ret; } +#endif // TINYOBJLOADER_DISABLE_FAST_FLOAT + static inline void parseReal2(real_t *x, real_t *y, const char **token, const double default_x = 0.0, const double default_y = 0.0) { @@ -1271,6 +6427,444 @@ static vertex_index_t parseRawTriple(const char **token) { return vi; } +// --- Stream-based parse functions --- + +static inline std::string sr_parseString(StreamReader &sr) { + sr.skip_space(); + std::string s; + while (!sr.eof()) { + char c = sr.peek(); + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; + s += c; + sr.advance(1); + } + return s; +} + +static inline int sr_parseInt(StreamReader &sr) { + sr.skip_space(); + const char *start = sr.current_ptr(); + size_t rem = sr.remaining(); + size_t len = 0; + while (len < rem) { + char c = start[len]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; + len++; + } + int i = 0; + if (len > 0) { + char tmp[64]; + size_t copy_len = len < 63 ? len : 63; + if (copy_len != len) { + sr.advance(len); + return 0; + } + memcpy(tmp, start, copy_len); + tmp[copy_len] = '\0'; + errno = 0; + char *endptr = NULL; + long val = strtol(tmp, &endptr, 10); + const bool has_error = + (errno == ERANGE || endptr == tmp || + val > (std::numeric_limits::max)() || + val < (std::numeric_limits::min)()); + if (!has_error) { + i = static_cast(val); + } + } + sr.advance(len); + return i; +} + +static inline real_t sr_parseReal(StreamReader &sr, double default_value = 0.0) { + sr.skip_space(); + const char *start = sr.current_ptr(); + size_t rem = sr.remaining(); + size_t len = 0; + while (len < rem) { + char c = start[len]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; + len++; + } + double val = default_value; + if (len > 0) { + tryParseDouble(start, start + len, &val); + } + sr.advance(len); + return static_cast(val); +} + +static inline bool sr_parseReal(StreamReader &sr, real_t *out) { + sr.skip_space(); + const char *start = sr.current_ptr(); + size_t rem = sr.remaining(); + size_t len = 0; + while (len < rem) { + char c = start[len]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; + len++; + } + if (len == 0) return false; + double val; + bool ret = tryParseDouble(start, start + len, &val); + if (ret) { + (*out) = static_cast(val); + } + sr.advance(len); + return ret; +} + +static inline void sr_parseReal2(real_t *x, real_t *y, StreamReader &sr, + const double default_x = 0.0, + const double default_y = 0.0) { + (*x) = sr_parseReal(sr, default_x); + (*y) = sr_parseReal(sr, default_y); +} + +static inline void sr_parseReal3(real_t *x, real_t *y, real_t *z, + StreamReader &sr, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = sr_parseReal(sr, default_x); + (*y) = sr_parseReal(sr, default_y); + (*z) = sr_parseReal(sr, default_z); +} + +static inline int sr_parseVertexWithColor(real_t *x, real_t *y, real_t *z, + real_t *r, real_t *g, real_t *b, + StreamReader &sr, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = sr_parseReal(sr, default_x); + (*y) = sr_parseReal(sr, default_y); + (*z) = sr_parseReal(sr, default_z); + + bool has_r = sr_parseReal(sr, r); + if (!has_r) { + (*r) = (*g) = (*b) = 1.0; + return 3; + } + + bool has_g = sr_parseReal(sr, g); + if (!has_g) { + (*g) = (*b) = 1.0; + return 4; + } + + bool has_b = sr_parseReal(sr, b); + if (!has_b) { + (*r) = (*g) = (*b) = 1.0; + return 3; + } + + return 6; +} + +// --- Error-reporting overloads --- +// These overloads push clang-style diagnostics into `err` when parsing fails +// and return false so callers can early-return on unrecoverable parse errors. +// The original signatures are preserved above for backward compatibility. + +static inline bool sr_parseInt(StreamReader &sr, int *out, std::string *err, + const std::string &filename) { + sr.skip_space(); + const char *start = sr.current_ptr(); + size_t rem = sr.remaining(); + size_t len = 0; + while (len < rem) { + char c = start[len]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; + len++; + } + if (len == 0) { + if (err) { + (*err) += sr.format_error(filename, "expected integer value"); + } + *out = 0; + return false; + } + char tmp[64]; + size_t copy_len = len < 63 ? len : 63; + memcpy(tmp, start, copy_len); + tmp[copy_len] = '\0'; + if (copy_len != len) { + if (err) { + (*err) += sr.format_error(filename, "integer value too long"); + } + *out = 0; + sr.advance(len); + return false; + } + errno = 0; + char *endptr = NULL; + long val = strtol(tmp, &endptr, 10); + if (errno == ERANGE || val > (std::numeric_limits::max)() || + val < (std::numeric_limits::min)()) { + if (err) { + (*err) += sr.format_error(filename, + "integer value out of range, got '" + std::string(tmp) + "'"); + } + *out = 0; + sr.advance(len); + return false; + } + if (endptr == tmp || (*endptr != '\0' && *endptr != ' ' && *endptr != '\t')) { + if (err) { + (*err) += sr.format_error(filename, + "expected integer, got '" + std::string(tmp) + "'"); + } + *out = 0; + sr.advance(len); + return false; + } + *out = static_cast(val); + sr.advance(len); + return true; +} + +static inline bool sr_parseReal(StreamReader &sr, real_t *out, + double default_value, + std::string *err, + const std::string &filename) { + sr.skip_space(); + const char *start = sr.current_ptr(); + size_t rem = sr.remaining(); + size_t len = 0; + while (len < rem) { + char c = start[len]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; + len++; + } + if (len == 0) { + // No token to parse — not necessarily an error (e.g. optional component). + *out = static_cast(default_value); + return true; + } + double val; + if (!tryParseDouble(start, start + len, &val)) { + if (err) { + char tmp[64]; + size_t copy_len = len < 63 ? len : 63; + memcpy(tmp, start, copy_len); + tmp[copy_len] = '\0'; + (*err) += sr.format_error(filename, + "expected number, got '" + std::string(tmp) + "'"); + } + *out = static_cast(default_value); + sr.advance(len); + return false; + } + *out = static_cast(val); + sr.advance(len); + return true; +} + +static inline bool sr_parseReal2(real_t *x, real_t *y, StreamReader &sr, + std::string *err, + const std::string &filename, + const double default_x = 0.0, + const double default_y = 0.0) { + if (!sr_parseReal(sr, x, default_x, err, filename)) return false; + if (!sr_parseReal(sr, y, default_y, err, filename)) return false; + return true; +} + +static inline bool sr_parseReal3(real_t *x, real_t *y, real_t *z, + StreamReader &sr, + std::string *err, + const std::string &filename, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + if (!sr_parseReal(sr, x, default_x, err, filename)) return false; + if (!sr_parseReal(sr, y, default_y, err, filename)) return false; + if (!sr_parseReal(sr, z, default_z, err, filename)) return false; + return true; +} + +// Returns number of components parsed (3, 4, or 6) on success, -1 on error. +static inline int sr_parseVertexWithColor(real_t *x, real_t *y, real_t *z, + real_t *r, real_t *g, real_t *b, + StreamReader &sr, + std::string *err, + const std::string &filename, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + if (!sr_parseReal(sr, x, default_x, err, filename)) return -1; + if (!sr_parseReal(sr, y, default_y, err, filename)) return -1; + if (!sr_parseReal(sr, z, default_z, err, filename)) return -1; + + bool has_r = sr_parseReal(sr, r); + if (!has_r) { + (*r) = (*g) = (*b) = 1.0; + return 3; + } + + bool has_g = sr_parseReal(sr, g); + if (!has_g) { + (*g) = (*b) = 1.0; + return 4; + } + + bool has_b = sr_parseReal(sr, b); + if (!has_b) { + (*r) = (*g) = (*b) = 1.0; + return 3; + } + + return 6; +} + +static inline int sr_parseIntNoSkip(StreamReader &sr); + +// Advance past remaining characters in a tag triple field (stops at '/', whitespace, or line end). +static inline void sr_skipTagField(StreamReader &sr) { + while (!sr.eof() && !sr.at_line_end() && !IS_SPACE(sr.peek()) && + sr.peek() != '/') { + sr.advance(1); + } +} + +static tag_sizes sr_parseTagTriple(StreamReader &sr) { + tag_sizes ts; + + sr.skip_space(); + ts.num_ints = sr_parseIntNoSkip(sr); + sr_skipTagField(sr); + if (!sr.eof() && sr.peek() == '/') { + sr.advance(1); + sr.skip_space(); + ts.num_reals = sr_parseIntNoSkip(sr); + sr_skipTagField(sr); + if (!sr.eof() && sr.peek() == '/') { + sr.advance(1); + ts.num_strings = sr_parseInt(sr); + } + } + return ts; +} + +static inline int sr_parseIntNoSkip(StreamReader &sr) { + const char *start = sr.current_ptr(); + size_t rem = sr.remaining(); + size_t len = 0; + if (len < rem && (start[len] == '+' || start[len] == '-')) len++; + while (len < rem && start[len] >= '0' && start[len] <= '9') len++; + int i = 0; + if (len > 0) { + char tmp[64]; + size_t copy_len = len < 63 ? len : 63; + if (copy_len != len) { + sr.advance(len); + return 0; + } + memcpy(tmp, start, copy_len); + tmp[copy_len] = '\0'; + errno = 0; + char *endptr = NULL; + long val = strtol(tmp, &endptr, 10); + if (errno == 0 && endptr != tmp && *endptr == '\0' && + val <= (std::numeric_limits::max)() && + val >= (std::numeric_limits::min)()) { + i = static_cast(val); + } + } + sr.advance(len); + return i; +} + +static inline void sr_skipUntil(StreamReader &sr, const char *delims) { + while (!sr.eof()) { + char c = sr.peek(); + for (const char *d = delims; *d; d++) { + if (c == *d) return; + } + sr.advance(1); + } +} + +static bool sr_parseTriple(StreamReader &sr, int vsize, int vnsize, int vtsize, + vertex_index_t *ret, const warning_context &context) { + if (!ret) return false; + + vertex_index_t vi(-1); + + sr.skip_space(); + if (!fixIndex(sr_parseIntNoSkip(sr), vsize, &vi.v_idx, false, context)) { + return false; + } + + sr_skipUntil(sr, "/ \t\r\n"); + if (sr.eof() || sr.peek() != '/') { + (*ret) = vi; + return true; + } + sr.advance(1); + + // i//k + if (!sr.eof() && sr.peek() == '/') { + sr.advance(1); + if (!fixIndex(sr_parseIntNoSkip(sr), vnsize, &vi.vn_idx, true, context)) { + return false; + } + sr_skipUntil(sr, "/ \t\r\n"); + (*ret) = vi; + return true; + } + + // i/j/k or i/j + if (!fixIndex(sr_parseIntNoSkip(sr), vtsize, &vi.vt_idx, true, context)) { + return false; + } + + sr_skipUntil(sr, "/ \t\r\n"); + if (sr.eof() || sr.peek() != '/') { + (*ret) = vi; + return true; + } + + // i/j/k + sr.advance(1); + if (!fixIndex(sr_parseIntNoSkip(sr), vnsize, &vi.vn_idx, true, context)) { + return false; + } + sr_skipUntil(sr, "/ \t\r\n"); + + (*ret) = vi; + return true; +} + +static vertex_index_t sr_parseRawTriple(StreamReader &sr) { + vertex_index_t vi(static_cast(0)); + + sr.skip_space(); + vi.v_idx = sr_parseIntNoSkip(sr); + sr_skipUntil(sr, "/ \t\r\n"); + if (sr.eof() || sr.peek() != '/') return vi; + sr.advance(1); + + // i//k + if (!sr.eof() && sr.peek() == '/') { + sr.advance(1); + vi.vn_idx = sr_parseIntNoSkip(sr); + sr_skipUntil(sr, "/ \t\r\n"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = sr_parseIntNoSkip(sr); + sr_skipUntil(sr, "/ \t\r\n"); + if (sr.eof() || sr.peek() != '/') return vi; + + sr.advance(1); + vi.vn_idx = sr_parseIntNoSkip(sr); + sr_skipUntil(sr, "/ \t\r\n"); + return vi; +} + bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, const char *linebuf) { // @todo { write more robust lexer and parser. } @@ -1469,7 +7063,9 @@ inline real_t GetLength(TinyObjPoint &e) { } inline TinyObjPoint Normalize(TinyObjPoint e) { - real_t inv_length = real_t(1) / GetLength(e); + real_t len = GetLength(e); + if (len <= real_t(0)) return TinyObjPoint(real_t(0), real_t(0), real_t(0)); + real_t inv_length = real_t(1) / len; return TinyObjPoint(e.x * inv_length, e.y * inv_length, e.z * inv_length); } @@ -1513,6 +7109,13 @@ static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, vertex_index_t i2 = face.vertex_indices[2]; vertex_index_t i3 = face.vertex_indices[3]; + if (i0.v_idx < 0 || i1.v_idx < 0 || i2.v_idx < 0 || i3.v_idx < 0) { + if (warn) { + (*warn) += "Face with invalid vertex index found.\n"; + } + continue; + } + size_t vi0 = size_t(i0.v_idx); size_t vi1 = size_t(i1.v_idx); size_t vi2 = size_t(i2.v_idx); @@ -1614,8 +7217,26 @@ static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); - } else { -#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + } else { +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + // Validate all vertex indices before accessing the vertex array. + { + bool valid_poly = true; + for (size_t k = 0; k < npolys; ++k) { + size_t vi = size_t(face.vertex_indices[k].v_idx); + if ((3 * vi + 2) >= v.size()) { + valid_poly = false; + break; + } + } + if (!valid_poly) { + if (warn) { + (*warn) += "Face with invalid vertex index found.\n"; + } + continue; + } + } + vertex_index_t i0 = face.vertex_indices[0]; vertex_index_t i0_2 = i0; @@ -2036,8 +7657,13 @@ static void SplitString(const std::string &s, char delim, char escape, if (escaping) { escaping = false; } else if (ch == escape) { - escaping = true; - continue; + if ((i + 1) < s.size()) { + const char next = s[i + 1]; + if ((next == delim) || (next == escape)) { + escaping = true; + continue; + } + } } else if (ch == delim) { if (!token.empty()) { elems.push_back(token); @@ -2051,6 +7677,20 @@ static void SplitString(const std::string &s, char delim, char escape, elems.push_back(token); } +static void RemoveEmptyTokens(std::vector *tokens) { + if (!tokens) return; + + const std::vector &src = *tokens; + std::vector filtered; + filtered.reserve(src.size()); + for (size_t i = 0; i < src.size(); i++) { + if (!src[i].empty()) { + filtered.push_back(src[i]); + } + } + tokens->swap(filtered); +} + static std::string JoinPath(const std::string &dir, const std::string &filename) { if (dir.empty()) { @@ -2066,12 +7706,18 @@ static std::string JoinPath(const std::string &dir, } } -void LoadMtl(std::map *material_map, - std::vector *materials, std::istream *inStream, - std::string *warning, std::string *err) { - (void)err; +static bool LoadMtlInternal(std::map *material_map, + std::vector *materials, + StreamReader &sr, + std::string *warning, std::string *err, + const std::string &filename = "") { + if (sr.has_errors()) { + if (err) { + (*err) += sr.get_errors(); + } + return false; + } - // Create a default material anyway. material_t material; InitMaterial(&material); @@ -2085,43 +7731,23 @@ void LoadMtl(std::map *material_map, std::stringstream warn_ss; - size_t line_no = 0; - std::string linebuf; - while (inStream->peek() != -1) { - safeGetline(*inStream, linebuf); - line_no++; - - // Trim trailing whitespace. - if (linebuf.size() > 0) { - linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); - } - - // Trim newline '\r\n' or '\n' - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\n') - linebuf.erase(linebuf.size() - 1); - } - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\r') - linebuf.erase(linebuf.size() - 1); - } - - // Skip if empty line. - if (linebuf.empty()) { - continue; - } - - // Skip leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); + // Handle BOM + if (sr.remaining() >= 3 && + static_cast(sr.peek()) == 0xEF && + static_cast(sr.peek_at(1)) == 0xBB && + static_cast(sr.peek_at(2)) == 0xBF) { + sr.advance(3); + } - assert(token); - if (token[0] == '\0') continue; // empty line + while (!sr.eof()) { + sr.skip_space(); + if (sr.at_line_end()) { sr.skip_line(); continue; } + if (sr.peek() == '#') { sr.skip_line(); continue; } - if (token[0] == '#') continue; // comment line + size_t line_num = sr.line_num(); // new mtl - if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { + if (sr.match("newmtl", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { // flush previous material. if (!material.name.empty()) { material_map->insert(std::pair( @@ -2129,17 +7755,15 @@ void LoadMtl(std::map *material_map, materials->push_back(material); } - // initial temporary material InitMaterial(&material); has_d = false; has_tr = false; + has_kd = false; - // set new mtl name - token += 7; + sr.advance(7); { - std::string namebuf = parseString(&token); - // TODO: empty name check? + std::string namebuf = sr_parseString(sr); if (namebuf.empty()) { if (warning) { (*warning) += "empty material name in `newmtl`\n"; @@ -2147,325 +7771,384 @@ void LoadMtl(std::map *material_map, } material.name = namebuf; } + sr.skip_line(); continue; } // ambient - if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { - token += 2; + if (sr.peek() == 'K' && sr.peek_at(1) == 'a' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); real_t r, g, b; - parseReal3(&r, &g, &b, &token); + if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; material.ambient[0] = r; material.ambient[1] = g; material.ambient[2] = b; + sr.skip_line(); continue; } // diffuse - if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { - token += 2; + if (sr.peek() == 'K' && sr.peek_at(1) == 'd' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); real_t r, g, b; - parseReal3(&r, &g, &b, &token); + if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; material.diffuse[0] = r; material.diffuse[1] = g; material.diffuse[2] = b; has_kd = true; + sr.skip_line(); continue; } // specular - if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { - token += 2; + if (sr.peek() == 'K' && sr.peek_at(1) == 's' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); real_t r, g, b; - parseReal3(&r, &g, &b, &token); + if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; material.specular[0] = r; material.specular[1] = g; material.specular[2] = b; + sr.skip_line(); continue; } // transmittance - if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || - (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { - token += 2; + if ((sr.peek() == 'K' && sr.peek_at(1) == 't' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) || + (sr.peek() == 'T' && sr.peek_at(1) == 'f' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t'))) { + sr.advance(2); real_t r, g, b; - parseReal3(&r, &g, &b, &token); + if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; material.transmittance[0] = r; material.transmittance[1] = g; material.transmittance[2] = b; + sr.skip_line(); continue; } // ior(index of refraction) - if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { - token += 2; - material.ior = parseReal(&token); + if (sr.peek() == 'N' && sr.peek_at(1) == 'i' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); + if (!sr_parseReal(sr, &material.ior, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // emission - if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { - token += 2; + if (sr.peek() == 'K' && sr.peek_at(1) == 'e' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); real_t r, g, b; - parseReal3(&r, &g, &b, &token); + if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; material.emission[0] = r; material.emission[1] = g; material.emission[2] = b; + sr.skip_line(); continue; } // shininess - if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { - token += 2; - material.shininess = parseReal(&token); + if (sr.peek() == 'N' && sr.peek_at(1) == 's' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); + if (!sr_parseReal(sr, &material.shininess, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // illum model - if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { - token += 6; - material.illum = parseInt(&token); + if (sr.match("illum", 5) && (sr.peek_at(5) == ' ' || sr.peek_at(5) == '\t')) { + sr.advance(6); + if (!sr_parseInt(sr, &material.illum, err, filename)) return false; + sr.skip_line(); continue; } // dissolve - if ((token[0] == 'd' && IS_SPACE(token[1]))) { - token += 1; - material.dissolve = parseReal(&token); + if (sr.peek() == 'd' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(1); + if (!sr_parseReal(sr, &material.dissolve, 0.0, err, filename)) return false; if (has_tr) { warn_ss << "Both `d` and `Tr` parameters defined for \"" << material.name - << "\". Use the value of `d` for dissolve (line " << line_no + << "\". Use the value of `d` for dissolve (line " << line_num << " in .mtl.)\n"; } has_d = true; + sr.skip_line(); continue; } - if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { - token += 2; + if (sr.peek() == 'T' && sr.peek_at(1) == 'r' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); if (has_d) { - // `d` wins. Ignore `Tr` value. warn_ss << "Both `d` and `Tr` parameters defined for \"" << material.name - << "\". Use the value of `d` for dissolve (line " << line_no + << "\". Use the value of `d` for dissolve (line " << line_num << " in .mtl.)\n"; } else { - // We invert value of Tr(assume Tr is in range [0, 1]) - // NOTE: Interpretation of Tr is application(exporter) dependent. For - // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) - material.dissolve = static_cast(1.0) - parseReal(&token); + real_t tr_val; + if (!sr_parseReal(sr, &tr_val, 0.0, err, filename)) return false; + material.dissolve = static_cast(1.0) - tr_val; } has_tr = true; + sr.skip_line(); continue; } // PBR: roughness - if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { - token += 2; - material.roughness = parseReal(&token); + if (sr.peek() == 'P' && sr.peek_at(1) == 'r' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); + if (!sr_parseReal(sr, &material.roughness, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // PBR: metallic - if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { - token += 2; - material.metallic = parseReal(&token); + if (sr.peek() == 'P' && sr.peek_at(1) == 'm' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); + if (!sr_parseReal(sr, &material.metallic, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // PBR: sheen - if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { - token += 2; - material.sheen = parseReal(&token); + if (sr.peek() == 'P' && sr.peek_at(1) == 's' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); + if (!sr_parseReal(sr, &material.sheen, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // PBR: clearcoat thickness - if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { - token += 2; - material.clearcoat_thickness = parseReal(&token); + if (sr.peek() == 'P' && sr.peek_at(1) == 'c' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(2); + if (!sr_parseReal(sr, &material.clearcoat_thickness, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // PBR: clearcoat roughness - if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { - token += 4; - material.clearcoat_roughness = parseReal(&token); + if (sr.match("Pcr", 3) && (sr.peek_at(3) == ' ' || sr.peek_at(3) == '\t')) { + sr.advance(4); + if (!sr_parseReal(sr, &material.clearcoat_roughness, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // PBR: anisotropy - if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { - token += 6; - material.anisotropy = parseReal(&token); + if (sr.match("aniso", 5) && (sr.peek_at(5) == ' ' || sr.peek_at(5) == '\t')) { + sr.advance(6); + if (!sr_parseReal(sr, &material.anisotropy, 0.0, err, filename)) return false; + sr.skip_line(); continue; } // PBR: anisotropy rotation - if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { - token += 7; - material.anisotropy_rotation = parseReal(&token); + if (sr.match("anisor", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + if (!sr_parseReal(sr, &material.anisotropy_rotation, 0.0, err, filename)) return false; + sr.skip_line(); continue; } + // For texture directives, read rest of line and delegate to + // ParseTextureNameAndOption (which uses the old const char* parse functions). + // ambient or ambient occlusion texture - if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Ka", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.ambient_texname), - &(material.ambient_texopt), token); + &(material.ambient_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // diffuse texture - if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Kd", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.diffuse_texname), - &(material.diffuse_texopt), token); - - // Set a decent diffuse default value if a diffuse texture is specified - // without a matching Kd value. + &(material.diffuse_texopt), line_rest.c_str()); if (!has_kd) { material.diffuse[0] = static_cast(0.6); material.diffuse[1] = static_cast(0.6); material.diffuse[2] = static_cast(0.6); } - + sr.skip_line(); continue; } // specular texture - if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Ks", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.specular_texname), - &(material.specular_texopt), token); + &(material.specular_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // specular highlight texture - if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Ns", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.specular_highlight_texname), - &(material.specular_highlight_texopt), token); + &(material.specular_highlight_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // bump texture - if (((0 == strncmp(token, "map_bump", 8)) || - (0 == strncmp(token, "map_Bump", 8))) && - IS_SPACE(token[8])) { - token += 9; + if ((sr.match("map_bump", 8) || sr.match("map_Bump", 8)) && + (sr.peek_at(8) == ' ' || sr.peek_at(8) == '\t')) { + sr.advance(9); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.bump_texname), - &(material.bump_texopt), token); + &(material.bump_texopt), line_rest.c_str()); + sr.skip_line(); continue; } - // bump texture - if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { - token += 5; + // bump texture (short form) + if (sr.match("bump", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { + sr.advance(5); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.bump_texname), - &(material.bump_texopt), token); + &(material.bump_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // alpha texture - if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { - token += 6; - material.alpha_texname = token; + if (sr.match("map_d", 5) && (sr.peek_at(5) == ' ' || sr.peek_at(5) == '\t')) { + sr.advance(6); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.alpha_texname), - &(material.alpha_texopt), token); + &(material.alpha_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // displacement texture - if (((0 == strncmp(token, "map_disp", 8)) || - (0 == strncmp(token, "map_Disp", 8))) && - IS_SPACE(token[8])) { - token += 9; + if ((sr.match("map_disp", 8) || sr.match("map_Disp", 8)) && + (sr.peek_at(8) == ' ' || sr.peek_at(8) == '\t')) { + sr.advance(9); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.displacement_texname), - &(material.displacement_texopt), token); + &(material.displacement_texopt), line_rest.c_str()); + sr.skip_line(); continue; } - // displacement texture - if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { - token += 5; + // displacement texture (short form) + if (sr.match("disp", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { + sr.advance(5); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.displacement_texname), - &(material.displacement_texopt), token); + &(material.displacement_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // reflection map - if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) { - token += 5; + if (sr.match("refl", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { + sr.advance(5); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.reflection_texname), - &(material.reflection_texopt), token); + &(material.reflection_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // PBR: roughness texture - if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Pr", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.roughness_texname), - &(material.roughness_texopt), token); + &(material.roughness_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // PBR: metallic texture - if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Pm", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.metallic_texname), - &(material.metallic_texopt), token); + &(material.metallic_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // PBR: sheen texture - if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Ps", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.sheen_texname), - &(material.sheen_texopt), token); + &(material.sheen_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // PBR: emissive texture - if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { - token += 7; + if (sr.match("map_Ke", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.emissive_texname), - &(material.emissive_texopt), token); + &(material.emissive_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // PBR: normal map texture - if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { - token += 5; + if (sr.match("norm", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { + sr.advance(5); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); ParseTextureNameAndOption(&(material.normal_texname), - &(material.normal_texopt), token); + &(material.normal_texopt), line_rest.c_str()); + sr.skip_line(); continue; } // unknown parameter - const char *_space = strchr(token, ' '); - if (!_space) { - _space = strchr(token, '\t'); - } - if (_space) { - std::ptrdiff_t len = _space - token; - std::string key(token, static_cast(len)); - std::string value = _space + 1; - material.unknown_parameter.insert( - std::pair(key, value)); + { + std::string line_rest = trimTrailingWhitespace(sr.read_line()); + const char *_lp = line_rest.c_str(); + const char *_space = strchr(_lp, ' '); + if (!_space) { + _space = strchr(_lp, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - _lp; + std::string key(_lp, static_cast(len)); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } } + sr.skip_line(); + } + // flush last material (only if it was actually defined). + if (!material.name.empty()) { + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); } - // flush last material. - material_map->insert(std::pair( - material.name, static_cast(materials->size()))); - materials->push_back(material); if (warning) { - (*warning) = warn_ss.str(); + (*warning) += warn_ss.str(); } + + return true; +} + +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning, std::string *err) { + StreamReader sr(*inStream); + LoadMtlInternal(material_map, materials, sr, warning, err); } + bool MaterialFileReader::operator()(const std::string &matId, std::vector *materials, std::map *matMap, @@ -2489,12 +8172,34 @@ bool MaterialFileReader::operator()(const std::string &matId, for (size_t i = 0; i < paths.size(); i++) { std::string filepath = JoinPath(paths[i], matId); +#ifdef TINYOBJLOADER_USE_MMAP + { + MappedFile mf; + if (!mf.open(filepath.c_str())) continue; + if (mf.size > TINYOBJLOADER_STREAM_READER_MAX_BYTES) { + if (err) { + std::stringstream ss; + ss << "input stream too large (" << mf.size + << " bytes exceeds limit " + << TINYOBJLOADER_STREAM_READER_MAX_BYTES << " bytes)\n"; + (*err) += ss.str(); + } + return false; + } + StreamReader sr(mf.data, mf.size); + return LoadMtlInternal(matMap, materials, sr, warn, err, filepath); + } +#else // !TINYOBJLOADER_USE_MMAP +#ifdef _WIN32 + std::ifstream matIStream(LongPathW(UTF8ToWchar(filepath)).c_str()); +#else std::ifstream matIStream(filepath.c_str()); +#endif if (matIStream) { - LoadMtl(matMap, materials, &matIStream, warn, err); - - return true; + StreamReader mtl_sr(matIStream); + return LoadMtlInternal(matMap, materials, mtl_sr, warn, err, filepath); } +#endif // TINYOBJLOADER_USE_MMAP } std::stringstream ss; @@ -2507,12 +8212,36 @@ bool MaterialFileReader::operator()(const std::string &matId, } else { std::string filepath = matId; + +#ifdef TINYOBJLOADER_USE_MMAP + { + MappedFile mf; + if (mf.open(filepath.c_str())) { + if (mf.size > TINYOBJLOADER_STREAM_READER_MAX_BYTES) { + if (err) { + std::stringstream ss; + ss << "input stream too large (" << mf.size + << " bytes exceeds limit " + << TINYOBJLOADER_STREAM_READER_MAX_BYTES << " bytes)\n"; + (*err) += ss.str(); + } + return false; + } + StreamReader sr(mf.data, mf.size); + return LoadMtlInternal(matMap, materials, sr, warn, err, filepath); + } + } +#else // !TINYOBJLOADER_USE_MMAP +#ifdef _WIN32 + std::ifstream matIStream(LongPathW(UTF8ToWchar(filepath)).c_str()); +#else std::ifstream matIStream(filepath.c_str()); +#endif if (matIStream) { - LoadMtl(matMap, materials, &matIStream, warn, err); - - return true; + StreamReader mtl_sr(matIStream); + return LoadMtlInternal(matMap, materials, mtl_sr, warn, err, filepath); } +#endif // TINYOBJLOADER_USE_MMAP std::stringstream ss; ss << "Material file [ " << filepath @@ -2529,7 +8258,6 @@ bool MaterialStreamReader::operator()(const std::string &matId, std::vector *materials, std::map *matMap, std::string *warn, std::string *err) { - (void)err; (void)matId; if (!m_inStream) { std::stringstream ss; @@ -2540,60 +8268,31 @@ bool MaterialStreamReader::operator()(const std::string &matId, return false; } - LoadMtl(matMap, materials, &m_inStream, warn, err); - - return true; + StreamReader mtl_sr(m_inStream); + return LoadMtlInternal(matMap, materials, mtl_sr, warn, err, ""); } -bool LoadObj(attrib_t *attrib, std::vector *shapes, - std::vector *materials, std::string *warn, - std::string *err, const char *filename, const char *mtl_basedir, - bool triangulate, bool default_vcols_fallback) { - attrib->vertices.clear(); - attrib->normals.clear(); - attrib->texcoords.clear(); - attrib->colors.clear(); - shapes->clear(); - - std::stringstream errss; - - std::ifstream ifs(filename); - if (!ifs) { - errss << "Cannot open file [" << filename << "]\n"; +static bool LoadObjInternal(attrib_t *attrib, std::vector *shapes, + std::vector *materials, + std::string *warn, std::string *err, + StreamReader &sr, + MaterialReader *readMatFn, bool triangulate, + bool default_vcols_fallback, + const std::string &filename = "") { + if (sr.has_errors()) { if (err) { - (*err) = errss.str(); + (*err) += sr.get_errors(); } return false; } - std::string baseDir = mtl_basedir ? mtl_basedir : ""; - if (!baseDir.empty()) { -#ifndef _WIN32 - const char dirsep = '/'; -#else - const char dirsep = '\\'; -#endif - if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; - } - MaterialFileReader matFileReader(baseDir); - - return LoadObj(attrib, shapes, materials, warn, err, &ifs, &matFileReader, - triangulate, default_vcols_fallback); -} - -bool LoadObj(attrib_t *attrib, std::vector *shapes, - std::vector *materials, std::string *warn, - std::string *err, std::istream *inStream, - MaterialReader *readMatFn /*= NULL*/, bool triangulate, - bool default_vcols_fallback) { - std::stringstream errss; - std::vector v; - std::vector vertex_weights; // optional [w] component in `v` + std::vector vertex_weights; std::vector vn; std::vector vt; + std::vector vt_w; // optional [w] component in `vt` std::vector vc; - std::vector vw; // tinyobj extension: vertex skin weights + std::vector vw; std::vector tags; PrimGroup prim_group; std::string name; @@ -2603,9 +8302,7 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, std::map material_map; int material = -1; - // smoothing group id - unsigned int current_smoothing_id = - 0; // Initial value. 0 means no smoothing. + unsigned int current_smoothing_id = 0; int greatest_v_idx = -1; int greatest_vn_idx = -1; @@ -2613,54 +8310,42 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, shape_t shape; - bool found_all_colors = true; // check if all 'v' line has color info - - size_t line_num = 0; - std::string linebuf; - while (inStream->peek() != -1) { - safeGetline(*inStream, linebuf); - - line_num++; - - // Trim newline '\r\n' or '\n' - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\n') - linebuf.erase(linebuf.size() - 1); - } - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\r') - linebuf.erase(linebuf.size() - 1); - } + bool found_all_colors = true; - // Skip if empty line. - if (linebuf.empty()) { - continue; - } + // Handle BOM + if (sr.remaining() >= 3 && + static_cast(sr.peek()) == 0xEF && + static_cast(sr.peek_at(1)) == 0xBB && + static_cast(sr.peek_at(2)) == 0xBF) { + sr.advance(3); + } - // Skip leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); + warning_context context; + context.warn = warn; + context.filename = filename; - assert(token); - if (token[0] == '\0') continue; // empty line + while (!sr.eof()) { + sr.skip_space(); + if (sr.at_line_end()) { sr.skip_line(); continue; } + if (sr.peek() == '#') { sr.skip_line(); continue; } - if (token[0] == '#') continue; // comment line + size_t line_num = sr.line_num(); // vertex - if (token[0] == 'v' && IS_SPACE((token[1]))) { - token += 2; + if (sr.peek() == 'v' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(2); real_t x, y, z; real_t r, g, b; - int num_components = parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); + int num_components = sr_parseVertexWithColor(&x, &y, &z, &r, &g, &b, sr, err, filename); + if (num_components < 0) return false; found_all_colors &= (num_components == 6); v.push_back(x); v.push_back(y); v.push_back(z); - vertex_weights.push_back( - r); // r = w, and initialized to 1.0 when `w` component is not found. + vertex_weights.push_back(r); if ((num_components == 6) || default_vcols_fallback) { vc.push_back(r); @@ -2668,163 +8353,163 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, vc.push_back(b); } + sr.skip_line(); continue; } // normal - if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { - token += 3; + if (sr.peek() == 'v' && sr.peek_at(1) == 'n' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(3); real_t x, y, z; - parseReal3(&x, &y, &z, &token); + if (!sr_parseReal3(&x, &y, &z, sr, err, filename)) return false; vn.push_back(x); vn.push_back(y); vn.push_back(z); + sr.skip_line(); continue; } // texcoord - if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { - token += 3; + if (sr.peek() == 'v' && sr.peek_at(1) == 't' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(3); real_t x, y; - parseReal2(&x, &y, &token); + if (!sr_parseReal2(&x, &y, sr, err, filename)) return false; vt.push_back(x); vt.push_back(y); + + // Parse optional w component + real_t w = static_cast(0.0); + sr_parseReal(sr, &w); + vt_w.push_back(w); + + sr.skip_line(); continue; } // skin weight. tinyobj extension - if (token[0] == 'v' && token[1] == 'w' && IS_SPACE((token[2]))) { - token += 3; + if (sr.peek() == 'v' && sr.peek_at(1) == 'w' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(3); - // vw ... - // example: - // vw 0 0 0.25 1 0.25 2 0.5 - - // TODO(syoyo): Add syntax check - int vid = 0; - vid = parseInt(&token); + int vid; + if (!sr_parseInt(sr, &vid, err, filename)) return false; skin_weight_t sw; - sw.vertex_id = vid; - while (!IS_NEW_LINE(token[0])) { + size_t vw_loop_max = sr.remaining() + 1; + size_t vw_loop_iter = 0; + while (!sr.at_line_end() && sr.peek() != '#' && + vw_loop_iter < vw_loop_max) { real_t j, w; - // joint_id should not be negative, weight may be negative - // TODO(syoyo): # of elements check - parseReal2(&j, &w, &token, -1.0); + sr_parseReal2(&j, &w, sr, -1.0); if (j < static_cast(0)) { if (err) { - std::stringstream ss; - ss << "Failed parse `vw' line. joint_id is negative. " - "line " - << line_num << ".)\n"; - (*err) += ss.str(); + (*err) += sr.format_error(filename, + "failed to parse `vw' line: joint_id is negative"); } return false; } joint_and_weight_t jw; - jw.joint_id = int(j); jw.weight = w; sw.weightValues.push_back(jw); - - size_t n = strspn(token, " \t\r"); - token += n; + sr.skip_space_and_cr(); + vw_loop_iter++; } vw.push_back(sw); + sr.skip_line(); + continue; } - warning_context context; - context.warn = warn; context.line_number = line_num; // line - if (token[0] == 'l' && IS_SPACE((token[1]))) { - token += 2; + if (sr.peek() == 'l' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(2); __line_t line; - while (!IS_NEW_LINE(token[0])) { + size_t l_loop_max = sr.remaining() + 1; + size_t l_loop_iter = 0; + while (!sr.at_line_end() && sr.peek() != '#' && + l_loop_iter < l_loop_max) { vertex_index_t vi; - if (!parseTriple(&token, static_cast(v.size() / 3), - static_cast(vn.size() / 3), - static_cast(vt.size() / 2), &vi, context)) { + if (!sr_parseTriple(sr, size_to_int(v.size() / 3), + size_to_int(vn.size() / 3), + size_to_int(vt.size() / 2), &vi, context)) { if (err) { - (*err) += - "Failed to parse `l' line (e.g. a zero value for vertex index. " - "Line " + - toString(line_num) + ").\n"; + (*err) += sr.format_error(filename, + "failed to parse `l' line (invalid vertex index)"); } return false; } line.vertex_indices.push_back(vi); - - size_t n = strspn(token, " \t\r"); - token += n; + sr.skip_space_and_cr(); + l_loop_iter++; } prim_group.lineGroup.push_back(line); - + sr.skip_line(); continue; } // points - if (token[0] == 'p' && IS_SPACE((token[1]))) { - token += 2; + if (sr.peek() == 'p' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(2); __points_t pts; - while (!IS_NEW_LINE(token[0])) { + size_t p_loop_max = sr.remaining() + 1; + size_t p_loop_iter = 0; + while (!sr.at_line_end() && sr.peek() != '#' && + p_loop_iter < p_loop_max) { vertex_index_t vi; - if (!parseTriple(&token, static_cast(v.size() / 3), - static_cast(vn.size() / 3), - static_cast(vt.size() / 2), &vi, context)) { + if (!sr_parseTriple(sr, size_to_int(v.size() / 3), + size_to_int(vn.size() / 3), + size_to_int(vt.size() / 2), &vi, context)) { if (err) { - (*err) += - "Failed to parse `p' line (e.g. a zero value for vertex index. " - "Line " + - toString(line_num) + ").\n"; + (*err) += sr.format_error(filename, + "failed to parse `p' line (invalid vertex index)"); } return false; } pts.vertex_indices.push_back(vi); - - size_t n = strspn(token, " \t\r"); - token += n; + sr.skip_space_and_cr(); + p_loop_iter++; } prim_group.pointsGroup.push_back(pts); - + sr.skip_line(); continue; } // face - if (token[0] == 'f' && IS_SPACE((token[1]))) { - token += 2; - token += strspn(token, " \t"); + if (sr.peek() == 'f' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(2); + sr.skip_space(); face_t face; face.smoothing_group_id = current_smoothing_id; face.vertex_indices.reserve(3); - while (!IS_NEW_LINE(token[0])) { + size_t f_loop_max = sr.remaining() + 1; + size_t f_loop_iter = 0; + while (!sr.at_line_end() && sr.peek() != '#' && + f_loop_iter < f_loop_max) { vertex_index_t vi; - if (!parseTriple(&token, static_cast(v.size() / 3), - static_cast(vn.size() / 3), - static_cast(vt.size() / 2), &vi, context)) { + if (!sr_parseTriple(sr, size_to_int(v.size() / 3), + size_to_int(vn.size() / 3), + size_to_int(vt.size() / 2), &vi, context)) { if (err) { - (*err) += - "Failed to parse `f' line (e.g. a zero value for vertex index " - "or invalid relative vertex index). Line " + - toString(line_num) + ").\n"; + (*err) += sr.format_error(filename, + "failed to parse `f' line (invalid vertex index)"); } return false; } @@ -2836,20 +8521,19 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx; face.vertex_indices.push_back(vi); - size_t n = strspn(token, " \t\r"); - token += n; + sr.skip_space_and_cr(); + f_loop_iter++; } - // replace with emplace_back + std::move on C++11 prim_group.faceGroup.push_back(face); - + sr.skip_line(); continue; } // use mtl - if ((0 == strncmp(token, "usemtl", 6))) { - token += 6; - std::string namebuf = parseString(&token); + if (sr.match("usemtl", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(6); + std::string namebuf = sr_parseString(sr); int newMaterialId = -1; std::map::const_iterator it = @@ -2857,32 +8541,31 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, if (it != material_map.end()) { newMaterialId = it->second; } else { - // { error!! material not found } if (warn) { (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n"; } } if (newMaterialId != material) { - // Create per-face material. Thus we don't add `shape` to `shapes` at - // this time. - // just clear `faceGroup` after `exportGroupsToShape()` call. exportGroupsToShape(&shape, prim_group, tags, material, name, triangulate, v, warn); prim_group.faceGroup.clear(); material = newMaterialId; } + sr.skip_line(); continue; } // load mtl - if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (sr.match("mtllib", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { if (readMatFn) { - token += 7; + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); std::vector filenames; - SplitString(std::string(token), ' ', '\\', filenames); + SplitString(line_rest, ' ', '\\', filenames); + RemoveEmptyTokens(&filenames); if (filenames.empty()) { if (warn) { @@ -2930,15 +8613,16 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, } } + sr.skip_line(); continue; } // group name - if (token[0] == 'g' && IS_SPACE((token[1]))) { + if (sr.peek() == 'g' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { // flush previous face group. bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, triangulate, v, warn); - (void)ret; // return value not used. + (void)ret; if (shape.mesh.indices.size() > 0) { shapes->push_back(shape); @@ -2951,10 +8635,14 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, std::vector names; - while (!IS_NEW_LINE(token[0])) { - std::string str = parseString(&token); + size_t g_loop_max = sr.remaining() + 1; + size_t g_loop_iter = 0; + while (!sr.at_line_end() && sr.peek() != '#' && + g_loop_iter < g_loop_max) { + std::string str = sr_parseString(sr); names.push_back(str); - token += strspn(token, " \t\r"); // skip tag + sr.skip_space_and_cr(); + g_loop_iter++; } // names[0] must be 'g' @@ -2971,10 +8659,6 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, std::stringstream ss; ss << names[1]; - // tinyobjloader does not support multiple groups for a primitive. - // Currently we concatinate multiple group names with a space to get - // single group name. - for (size_t i = 2; i < names.size(); i++) { ss << " " << names[i]; } @@ -2982,15 +8666,16 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, name = ss.str(); } + sr.skip_line(); continue; } // object name - if (token[0] == 'o' && IS_SPACE((token[1]))) { + if (sr.peek() == 'o' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { // flush previous face group. bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, triangulate, v, warn); - (void)ret; // return value not used. + (void)ret; if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 || shape.points.indices.size() > 0) { @@ -3001,24 +8686,23 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, prim_group.clear(); shape = shape_t(); - // @todo { multiple object name? } - token += 2; - std::stringstream ss; - ss << token; - name = ss.str(); + sr.advance(2); + std::string rest = sr.read_line(); + name = rest; + sr.skip_line(); continue; } - if (token[0] == 't' && IS_SPACE(token[1])) { - const int max_tag_nums = 8192; // FIXME(syoyo): Parameterize. + if (sr.peek() == 't' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + const int max_tag_nums = 8192; tag_t tag; - token += 2; + sr.advance(2); - tag.name = parseString(&token); + tag.name = sr_parseString(sr); - tag_sizes ts = parseTagTriple(&token); + tag_sizes ts = sr_parseTagTriple(sr); if (ts.num_ints < 0) { ts.num_ints = 0; @@ -3044,58 +8728,57 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, tag.intValues.resize(static_cast(ts.num_ints)); for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { - tag.intValues[i] = parseInt(&token); + tag.intValues[i] = sr_parseInt(sr); } tag.floatValues.resize(static_cast(ts.num_reals)); for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { - tag.floatValues[i] = parseReal(&token); + tag.floatValues[i] = sr_parseReal(sr); } tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - tag.stringValues[i] = parseString(&token); + tag.stringValues[i] = sr_parseString(sr); } tags.push_back(tag); + sr.skip_line(); continue; } - if (token[0] == 's' && IS_SPACE(token[1])) { + if (sr.peek() == 's' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { // smoothing group id - token += 2; - - // skip space. - token += strspn(token, " \t"); // skip space + sr.advance(2); + sr.skip_space(); - if (token[0] == '\0') { + if (sr.at_line_end()) { + sr.skip_line(); continue; } - if (token[0] == '\r' || token[1] == '\n') { + if (sr.peek() == '\r') { + sr.skip_line(); continue; } - if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' && - token[2] == 'f') { + if (sr.remaining() >= 3 && sr.match("off", 3)) { current_smoothing_id = 0; } else { - // assume number - int smGroupId = parseInt(&token); + int smGroupId = sr_parseInt(sr); if (smGroupId < 0) { - // parse error. force set to 0. - // FIXME(syoyo): Report warning. current_smoothing_id = 0; } else { current_smoothing_id = static_cast(smGroupId); } } + sr.skip_line(); continue; - } // smoothing group id + } // Ignore unknown command. + sr.skip_line(); } // not all vertices have colors, no default colors desired? -> clear colors @@ -3103,25 +8786,25 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, vc.clear(); } - if (greatest_v_idx >= static_cast(v.size() / 3)) { + if (greatest_v_idx >= size_to_int(v.size() / 3)) { if (warn) { std::stringstream ss; - ss << "Vertex indices out of bounds (line " << line_num << ".)\n\n"; + ss << "Vertex indices out of bounds (line " << sr.line_num() << ".)\n\n"; (*warn) += ss.str(); } } - if (greatest_vn_idx >= static_cast(vn.size() / 3)) { + if (greatest_vn_idx >= size_to_int(vn.size() / 3)) { if (warn) { std::stringstream ss; - ss << "Vertex normal indices out of bounds (line " << line_num + ss << "Vertex normal indices out of bounds (line " << sr.line_num() << ".)\n\n"; (*warn) += ss.str(); } } - if (greatest_vt_idx >= static_cast(vt.size() / 2)) { + if (greatest_vt_idx >= size_to_int(vt.size() / 2)) { if (warn) { std::stringstream ss; - ss << "Vertex texcoord indices out of bounds (line " << line_num + ss << "Vertex texcoord indices out of bounds (line " << sr.line_num() << ".)\n\n"; (*warn) += ss.str(); } @@ -3129,42 +8812,132 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, triangulate, v, warn); - // exportGroupsToShape return false when `usemtl` is called in the last - // line. - // we also add `shape` to `shapes` when `shape.mesh` has already some - // faces(indices) - if (ret || shape.mesh.indices - .size()) { // FIXME(syoyo): Support other prims(e.g. lines) + if (ret || shape.mesh.indices.size()) { shapes->push_back(shape); } - prim_group.clear(); // for safety - - if (err) { - (*err) += errss.str(); - } + prim_group.clear(); attrib->vertices.swap(v); attrib->vertex_weights.swap(vertex_weights); attrib->normals.swap(vn); attrib->texcoords.swap(vt); - attrib->texcoord_ws.swap(vt); + attrib->texcoord_ws.swap(vt_w); attrib->colors.swap(vc); attrib->skin_weights.swap(vw); return true; } -bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, - void *user_data /*= NULL*/, - MaterialReader *readMatFn /*= NULL*/, - std::string *warn, /* = NULL*/ - std::string *err /*= NULL*/) { - std::stringstream errss; +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, const char *filename, const char *mtl_basedir, + bool triangulate, bool default_vcols_fallback) { + attrib->vertices.clear(); + attrib->vertex_weights.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->texcoord_ws.clear(); + attrib->colors.clear(); + attrib->skin_weights.clear(); + shapes->clear(); + + std::string baseDir = mtl_basedir ? mtl_basedir : ""; + if (!baseDir.empty()) { +#ifndef _WIN32 + const char dirsep = '/'; +#else + const char dirsep = '\\'; +#endif + if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; + } + MaterialFileReader matFileReader(baseDir); + +#ifdef TINYOBJLOADER_USE_MMAP + { + MappedFile mf; + if (!mf.open(filename)) { + if (err) { + std::stringstream ss; + ss << "Cannot open file [" << filename << "]\n"; + (*err) = ss.str(); + } + return false; + } + if (mf.size > TINYOBJLOADER_STREAM_READER_MAX_BYTES) { + if (err) { + std::stringstream ss; + ss << "input stream too large (" << mf.size + << " bytes exceeds limit " + << TINYOBJLOADER_STREAM_READER_MAX_BYTES << " bytes)\n"; + (*err) += ss.str(); + } + return false; + } + StreamReader sr(mf.data, mf.size); + return LoadObjInternal(attrib, shapes, materials, warn, err, sr, + &matFileReader, triangulate, default_vcols_fallback, + filename); + } +#else // !TINYOBJLOADER_USE_MMAP +#ifdef _WIN32 + std::ifstream ifs(LongPathW(UTF8ToWchar(filename)).c_str()); +#else + std::ifstream ifs(filename); +#endif + if (!ifs) { + if (err) { + std::stringstream ss; + ss << "Cannot open file [" << filename << "]\n"; + (*err) = ss.str(); + } + return false; + } + { + StreamReader sr(ifs); + return LoadObjInternal(attrib, shapes, materials, warn, err, sr, + &matFileReader, triangulate, default_vcols_fallback, + filename); + } +#endif // TINYOBJLOADER_USE_MMAP +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn /*= NULL*/, bool triangulate, + bool default_vcols_fallback) { + attrib->vertices.clear(); + attrib->vertex_weights.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->texcoord_ws.clear(); + attrib->colors.clear(); + attrib->skin_weights.clear(); + shapes->clear(); + + StreamReader sr(*inStream); + return LoadObjInternal(attrib, shapes, materials, warn, err, sr, + readMatFn, triangulate, default_vcols_fallback); +} + + +static bool LoadObjWithCallbackInternal(StreamReader &sr, + const callback_t &callback, + void *user_data, + MaterialReader *readMatFn, + std::string *warn, + std::string *err) { + if (sr.has_errors()) { + if (err) { + (*err) += sr.get_errors(); + } + return false; + } // material std::set material_filenames; std::map material_map; - int material_id = -1; // -1 = invalid + int material_id = -1; std::vector indices; std::vector materials; @@ -3172,81 +8945,75 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, names.reserve(2); std::vector names_out; - std::string linebuf; - while (inStream.peek() != -1) { - safeGetline(inStream, linebuf); - - // Trim newline '\r\n' or '\n' - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\n') - linebuf.erase(linebuf.size() - 1); - } - if (linebuf.size() > 0) { - if (linebuf[linebuf.size() - 1] == '\r') - linebuf.erase(linebuf.size() - 1); - } - - // Skip if empty line. - if (linebuf.empty()) { - continue; - } - - // Skip leading space. - const char *token = linebuf.c_str(); - token += strspn(token, " \t"); - - assert(token); - if (token[0] == '\0') continue; // empty line + // Handle BOM + if (sr.remaining() >= 3 && + static_cast(sr.peek()) == 0xEF && + static_cast(sr.peek_at(1)) == 0xBB && + static_cast(sr.peek_at(2)) == 0xBF) { + sr.advance(3); + } - if (token[0] == '#') continue; // comment line + while (!sr.eof()) { + sr.skip_space(); + if (sr.at_line_end()) { sr.skip_line(); continue; } + if (sr.peek() == '#') { sr.skip_line(); continue; } // vertex - if (token[0] == 'v' && IS_SPACE((token[1]))) { - token += 2; + if (sr.peek() == 'v' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(2); real_t x, y, z; real_t r, g, b; - int num_components = parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); + int num_components = sr_parseVertexWithColor(&x, &y, &z, &r, &g, &b, sr, err, std::string()); + if (num_components < 0) { + return false; + } if (callback.vertex_cb) { - callback.vertex_cb(user_data, x, y, z, r); // r=w is optional + callback.vertex_cb(user_data, x, y, z, r); } if (callback.vertex_color_cb) { bool found_color = (num_components == 6); callback.vertex_color_cb(user_data, x, y, z, r, g, b, found_color); } + sr.skip_line(); continue; } // normal - if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { - token += 3; + if (sr.peek() == 'v' && sr.peek_at(1) == 'n' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(3); real_t x, y, z; - parseReal3(&x, &y, &z, &token); + sr_parseReal3(&x, &y, &z, sr); if (callback.normal_cb) { callback.normal_cb(user_data, x, y, z); } + sr.skip_line(); continue; } // texcoord - if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { - token += 3; - real_t x, y, z; // y and z are optional. default = 0.0 - parseReal3(&x, &y, &z, &token); + if (sr.peek() == 'v' && sr.peek_at(1) == 't' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { + sr.advance(3); + real_t x, y, z; + sr_parseReal3(&x, &y, &z, sr); if (callback.texcoord_cb) { callback.texcoord_cb(user_data, x, y, z); } + sr.skip_line(); continue; } // face - if (token[0] == 'f' && IS_SPACE((token[1]))) { - token += 2; - token += strspn(token, " \t"); + if (sr.peek() == 'f' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(2); + sr.skip_space(); indices.clear(); - while (!IS_NEW_LINE(token[0])) { - vertex_index_t vi = parseRawTriple(&token); + size_t cf_loop_max = sr.remaining() + 1; + size_t cf_loop_iter = 0; + while (!sr.at_line_end() && sr.peek() != '#' && + cf_loop_iter < cf_loop_max) { + vertex_index_t vi = sr_parseRawTriple(sr); index_t idx; idx.vertex_index = vi.v_idx; @@ -3254,8 +9021,8 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, idx.texcoord_index = vi.vt_idx; indices.push_back(idx); - size_t n = strspn(token, " \t\r"); - token += n; + sr.skip_space_and_cr(); + cf_loop_iter++; } if (callback.index_cb && indices.size() > 0) { @@ -3263,15 +9030,14 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, static_cast(indices.size())); } + sr.skip_line(); continue; } // use mtl - if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { - token += 7; - std::stringstream ss; - ss << token; - std::string namebuf = ss.str(); + if (sr.match("usemtl", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { + sr.advance(6); + std::string namebuf = sr_parseString(sr); int newMaterialId = -1; std::map::const_iterator it = @@ -3279,7 +9045,6 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, if (it != material_map.end()) { newMaterialId = it->second; } else { - // { warn!! material not found } if (warn && (!callback.usemtl_cb)) { (*warn) += "material [ " + namebuf + " ] not found in .mtl\n"; } @@ -3293,16 +9058,19 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, callback.usemtl_cb(user_data, namebuf.c_str(), material_id); } + sr.skip_line(); continue; } // load mtl - if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (sr.match("mtllib", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { if (readMatFn) { - token += 7; + sr.advance(7); + std::string line_rest = trimTrailingWhitespace(sr.read_line()); std::vector filenames; - SplitString(std::string(token), ' ', '\\', filenames); + SplitString(line_rest, ' ', '\\', filenames); + RemoveEmptyTokens(&filenames); if (filenames.empty()) { if (warn) { @@ -3324,7 +9092,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, &material_map, &warn_mtl, &err_mtl); if (warn && (!warn_mtl.empty())) { - (*warn) += warn_mtl; // This should be warn message. + (*warn) += warn_mtl; } if (err && (!err_mtl.empty())) { @@ -3345,7 +9113,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, "material.\n"; } } else { - if (callback.mtllib_cb) { + if (callback.mtllib_cb && !materials.empty()) { callback.mtllib_cb(user_data, &materials.at(0), static_cast(materials.size())); } @@ -3353,24 +9121,28 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, } } + sr.skip_line(); continue; } // group name - if (token[0] == 'g' && IS_SPACE((token[1]))) { + if (sr.peek() == 'g' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { names.clear(); - while (!IS_NEW_LINE(token[0])) { - std::string str = parseString(&token); + size_t cg_loop_max = sr.remaining() + 1; + size_t cg_loop_iter = 0; + while (!sr.at_line_end() && sr.peek() != '#' && + cg_loop_iter < cg_loop_max) { + std::string str = sr_parseString(sr); names.push_back(str); - token += strspn(token, " \t\r"); // skip tag + sr.skip_space_and_cr(); + cg_loop_iter++; } assert(names.size() > 0); if (callback.group_cb) { if (names.size() > 1) { - // create const char* array. names_out.resize(names.size() - 1); for (size_t j = 0; j < names_out.size(); j++) { names_out[j] = names[j + 1].c_str(); @@ -3383,57 +9155,46 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, } } + sr.skip_line(); continue; } // object name - if (token[0] == 'o' && IS_SPACE((token[1]))) { - // @todo { multiple object name? } - token += 2; - - std::stringstream ss; - ss << token; - std::string object_name = ss.str(); + if (sr.peek() == 'o' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { + sr.advance(2); + std::string object_name = sr.read_line(); if (callback.object_cb) { callback.object_cb(user_data, object_name.c_str()); } + sr.skip_line(); continue; } #if 0 // @todo - if (token[0] == 't' && IS_SPACE(token[1])) { + if (sr.peek() == 't' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { tag_t tag; - token += 2; - std::stringstream ss; - ss << token; - tag.name = ss.str(); - - token += tag.name.size() + 1; + sr.advance(2); + tag.name = sr_parseString(sr); - tag_sizes ts = parseTagTriple(&token); + tag_sizes ts = sr_parseTagTriple(sr); tag.intValues.resize(static_cast(ts.num_ints)); for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { - tag.intValues[i] = atoi(token); - token += strcspn(token, "/ \t\r") + 1; + tag.intValues[i] = sr_parseInt(sr); } tag.floatValues.resize(static_cast(ts.num_reals)); for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { - tag.floatValues[i] = parseReal(&token); - token += strcspn(token, "/ \t\r") + 1; + tag.floatValues[i] = sr_parseReal(sr); } tag.stringValues.resize(static_cast(ts.num_strings)); for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { - std::stringstream ss; - ss << token; - tag.stringValues[i] = ss.str(); - token += tag.stringValues[i].size() + 1; + tag.stringValues[i] = sr_parseString(sr); } tags.push_back(tag); @@ -3441,15 +9202,22 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, #endif // Ignore unknown command. - } - - if (err) { - (*err) += errss.str(); + sr.skip_line(); } return true; } +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data /*= NULL*/, + MaterialReader *readMatFn /*= NULL*/, + std::string *warn, /* = NULL*/ + std::string *err /*= NULL*/) { + StreamReader sr(inStream); + return LoadObjWithCallbackInternal(sr, callback, user_data, readMatFn, + warn, err); +} + bool ObjReader::ParseFromFile(const std::string &filename, const ObjReaderConfig &config) { std::string mtl_search_path;