From a90f6683b1d795207bedd736f8151fa61d493cd5 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 7 May 2026 20:19:03 +0200 Subject: [PATCH 1/2] Support modern c++ features --- .github/workflows/build_asan.yml | 63 +- .github/workflows/build_linux.yml | 206 +- .github/workflows/build_macos.yml | 206 +- .github/workflows/build_ubsan.yml | 63 +- .github/workflows/build_windows.yml | 232 +- .github/workflows/coverage.yml | 378 +++- .gitignore | 4 +- Benchmarks/benchmark_common.hpp | 27 + Benchmarks/benchmark_luabridge.cpp | 141 ++ Benchmarks/benchmark_luabridge3.cpp | 186 ++ Benchmarks/benchmark_sol3.cpp | 154 ++ CMakeLists.txt | 5 +- Distribution/LuaBridge/LuaBridge.h | 2819 ++++++++++++++++-------- Images/benchmarks.png | Bin 168653 -> 222518 bytes Manual.md | 210 +- README.md | 106 +- Source/LuaBridge/Any.h | 82 + Source/LuaBridge/Array.h | 2 +- Source/LuaBridge/Deque.h | 80 + Source/LuaBridge/FlatMap.h | 89 + Source/LuaBridge/FlatSet.h | 84 + Source/LuaBridge/ForwardList.h | 82 + Source/LuaBridge/List.h | 7 +- Source/LuaBridge/MultiMap.h | 107 + Source/LuaBridge/Span.h | 66 + Source/LuaBridge/StdExpected.h | 69 + Source/LuaBridge/UnorderedMultiMap.h | 107 + Source/LuaBridge/UnorderedSet.h | 80 + Source/LuaBridge/Variant.h | 9 + Source/LuaBridge/Vector.h | 2 +- Source/LuaBridge/detail/CFunctions.h | 166 +- Source/LuaBridge/detail/Config.h | 138 +- Source/LuaBridge/detail/Coroutine.h | 3 +- Source/LuaBridge/detail/FuncTraits.h | 43 +- Source/LuaBridge/detail/Iterator.h | 50 + Source/LuaBridge/detail/Result.h | 10 + Source/LuaBridge/detail/Stack.h | 93 + Source/LuaBridge/detail/TypeTraits.h | 18 + Tests/CMakeLists.txt | 17 +- Tests/Source/AnyTests.cpp | 71 + Tests/Source/DequeTests.cpp | 174 ++ Tests/Source/ExpectedStackTests.cpp | 84 + Tests/Source/FilesystemTests.cpp | 62 + Tests/Source/FlatMapTests.cpp | 298 +++ Tests/Source/FlatSetTests.cpp | 260 +++ Tests/Source/ForwardListTests.cpp | 208 ++ Tests/Source/IteratorTests.cpp | 48 + Tests/Source/LongjmpSafetyTests.cpp | 680 ++++++ Tests/Source/MoveOnlyFunctionTests.cpp | 63 + Tests/Source/MultiMapTests.cpp | 263 +++ Tests/Source/NamespaceTests.cpp | 2 + Tests/Source/SpanTests.cpp | 82 + Tests/Source/StackTests.cpp | 2 +- Tests/Source/StdExpectedTests.cpp | 92 + Tests/Source/UniquePtrTests.cpp | 126 ++ Tests/Source/UnorderedSetTests.cpp | 237 ++ amalgamate.py | 33 +- justfile | 66 +- 58 files changed, 7814 insertions(+), 1241 deletions(-) create mode 100644 Source/LuaBridge/Any.h create mode 100644 Source/LuaBridge/Deque.h create mode 100644 Source/LuaBridge/FlatMap.h create mode 100644 Source/LuaBridge/FlatSet.h create mode 100644 Source/LuaBridge/ForwardList.h create mode 100644 Source/LuaBridge/MultiMap.h create mode 100644 Source/LuaBridge/Span.h create mode 100644 Source/LuaBridge/StdExpected.h create mode 100644 Source/LuaBridge/UnorderedMultiMap.h create mode 100644 Source/LuaBridge/UnorderedSet.h create mode 100644 Tests/Source/AnyTests.cpp create mode 100644 Tests/Source/DequeTests.cpp create mode 100644 Tests/Source/ExpectedStackTests.cpp create mode 100644 Tests/Source/FilesystemTests.cpp create mode 100644 Tests/Source/FlatMapTests.cpp create mode 100644 Tests/Source/FlatSetTests.cpp create mode 100644 Tests/Source/ForwardListTests.cpp create mode 100644 Tests/Source/LongjmpSafetyTests.cpp create mode 100644 Tests/Source/MoveOnlyFunctionTests.cpp create mode 100644 Tests/Source/MultiMapTests.cpp create mode 100644 Tests/Source/SpanTests.cpp create mode 100644 Tests/Source/StdExpectedTests.cpp create mode 100644 Tests/Source/UniquePtrTests.cpp create mode 100644 Tests/Source/UnorderedSetTests.cpp diff --git a/.github/workflows/build_asan.yml b/.github/workflows/build_asan.yml index 82af7a4e..96f4ebc4 100644 --- a/.github/workflows/build_asan.yml +++ b/.github/workflows/build_asan.yml @@ -41,15 +41,37 @@ jobs: - name: Install Dependencies run: sudo apt-get -y install ninja-build - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -DLUABRIDGE_SANITIZE=address -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + env: + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_SANITIZE=address -G Ninja + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -DLUABRIDGE_SANITIZE=address -G Ninja - - name: Build Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build + - name: Build Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ @@ -57,12 +79,27 @@ jobs: LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build/Tests + - name: Test Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 env: ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -DLUABRIDGE_SANITIZE=address -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 run: | - ./LuaBridgeTests${{ matrix.lua.suffix }} - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC - ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + env: + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 06430c9c..4b2b90f5 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -41,15 +41,18 @@ jobs: - name: Install Dependencies run: sudo apt-get -y install ninja-build - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja - - name: Build Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build + - name: Build Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ @@ -57,13 +60,43 @@ jobs: LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build/Tests + - name: Test Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 run: | - ./LuaBridgeTests${{ matrix.lua.suffix }} - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC - ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure luajit: runs-on: ubuntu-latest @@ -78,25 +111,56 @@ jobs: sudo apt-get update sudo apt-get -y install ninja-build - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja + + - name: Build LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept + + - name: Test LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja - - name: Build LuaJIT - working-directory: ${{runner.workspace}}/build + - name: Build LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTestsLuaJIT \ LuaBridgeTestsLuaJITNoexcept - - name: Test LuaJIT - working-directory: ${{runner.workspace}}/build/Tests + - name: Test LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja + + - name: Build LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 run: | - ./LuaBridgeTestsLuaJIT - ./LuaBridgeTestsLuaJITNoexcept + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept + + - name: Test LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure luau: runs-on: ubuntu-latest @@ -109,20 +173,47 @@ jobs: - name: Install Dependencies run: sudo apt-get -y install ninja-build - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja + + - name: Build Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + + - name: Test Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja - - name: Build Luau - working-directory: ${{runner.workspace}}/build + - name: Build Luau (C++20) + working-directory: ${{runner.workspace}}/build20 run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau - - name: Test Luau - working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsLuau + - name: Test Luau (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja + + - name: Build Luau (C++23) + working-directory: ${{runner.workspace}}/build23 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + + - name: Test Luau (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure ravi: runs-on: ubuntu-latest @@ -135,17 +226,44 @@ jobs: - name: Install Dependencies run: sudo apt-get -y install libreadline-dev ninja-build - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja + + - name: Build Ravi (C++17) + working-directory: ${{runner.workspace}}/build17 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + + - name: Test Ravi (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsRavi" --output-on-failure + + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja + + - name: Build Ravi (C++20) + working-directory: ${{runner.workspace}}/build20 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + + - name: Test Ravi (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsRavi" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja - - name: Build Ravi - working-directory: ${{runner.workspace}}/build + - name: Build Ravi (C++23) + working-directory: ${{runner.workspace}}/build23 run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi - - name: Test Ravi - working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsRavi + - name: Test Ravi (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsRavi" --output-on-failure diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index 0ff61011..3f084373 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -38,15 +38,18 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja - - name: Build Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build + - name: Build Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ @@ -54,13 +57,43 @@ jobs: LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build/Tests + - name: Test Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 run: | - ./LuaBridgeTests${{ matrix.lua.suffix }} - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC - ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure luajit: runs-on: macos-latest @@ -70,25 +103,56 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja + + - name: Build LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept + + - name: Test LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja - - name: Build LuaJIT - working-directory: ${{runner.workspace}}/build + - name: Build LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTestsLuaJIT \ LuaBridgeTestsLuaJITNoexcept - - name: Test LuaJIT - working-directory: ${{runner.workspace}}/build/Tests + - name: Test LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja + + - name: Build LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 run: | - ./LuaBridgeTestsLuaJIT - ./LuaBridgeTestsLuaJITNoexcept + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept + + - name: Test LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure luau: runs-on: macos-latest @@ -98,20 +162,47 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja + + - name: Build Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + + - name: Test Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja - - name: Build Luau - working-directory: ${{runner.workspace}}/build + - name: Build Luau (C++20) + working-directory: ${{runner.workspace}}/build20 run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau - - name: Test Luau - working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsLuau + - name: Test Luau (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja + + - name: Build Luau (C++23) + working-directory: ${{runner.workspace}}/build23 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + + - name: Test Luau (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure ravi: runs-on: macos-latest @@ -121,17 +212,44 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -G Ninja + + - name: Build Ravi (C++17) + working-directory: ${{runner.workspace}}/build17 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + + - name: Test Ravi (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsRavi" --output-on-failure + + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -G Ninja + + - name: Build Ravi (C++20) + working-directory: ${{runner.workspace}}/build20 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + + - name: Test Ravi (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsRavi" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -G Ninja - - name: Build Ravi - working-directory: ${{runner.workspace}}/build + - name: Build Ravi (C++23) + working-directory: ${{runner.workspace}}/build23 run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi - - name: Test Ravi - working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsRavi + - name: Test Ravi (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsRavi" --output-on-failure diff --git a/.github/workflows/build_ubsan.yml b/.github/workflows/build_ubsan.yml index 88691cf4..a1104e3d 100644 --- a/.github/workflows/build_ubsan.yml +++ b/.github/workflows/build_ubsan.yml @@ -41,15 +41,37 @@ jobs: - name: Install Dependencies run: sudo apt-get -y install ninja-build - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -DLUABRIDGE_SANITIZE=undefined -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + env: + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_SANITIZE=undefined -G Ninja + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -DLUABRIDGE_SANITIZE=undefined -G Ninja - - name: Build Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build + - name: Build Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ @@ -57,12 +79,27 @@ jobs: LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build/Tests + - name: Test Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 env: UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -DLUABRIDGE_SANITIZE=undefined -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 run: | - ./LuaBridgeTests${{ matrix.lua.suffix }} - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC - ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + env: + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index a67bb15b..3a6ebdc1 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -38,16 +38,39 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + shell: bash + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 + + - name: Build Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + shell: bash + run: | + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + shell: bash + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure - - name: Configure + - name: Configure C++20 shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 - - name: Build Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build + - name: Build Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 shell: bash run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ @@ -56,14 +79,30 @@ jobs: LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build/Tests/Release + - name: Test Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 + shell: bash + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Configure C++23 + shell: bash + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 + + - name: Build Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 shell: bash run: | - ./LuaBridgeTests${{ matrix.lua.suffix }}.exe - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC.exe - ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept.exe - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept.exe + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + shell: bash + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure luajit: runs-on: windows-latest @@ -73,28 +112,65 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + shell: bash + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 + + - name: Build LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + shell: bash + run: | + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept + + - name: Test LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + shell: bash + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTestsLuaJIT" --output-on-failure - - name: Configure + - name: Configure C++20 shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 - - name: Build LuaJIT - working-directory: ${{runner.workspace}}/build + - name: Build LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 shell: bash run: | cmake --build . --config $BUILD_TYPE --parallel 4 --target \ LuaBridgeTestsLuaJIT \ LuaBridgeTestsLuaJITNoexcept - - name: Test LuaJIT - working-directory: ${{runner.workspace}}/build/Tests/Release + - name: Test LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 + shell: bash + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTestsLuaJIT" --output-on-failure + + - name: Configure C++23 + shell: bash + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 + + - name: Build LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 shell: bash run: | - ./LuaBridgeTestsLuaJIT.exe - ./LuaBridgeTestsLuaJITNoexcept.exe + cmake --build . --config $BUILD_TYPE --parallel 4 --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept + + - name: Test LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 + shell: bash + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTestsLuaJIT" --output-on-failure luau: runs-on: windows-latest @@ -104,23 +180,56 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + shell: bash + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 + + - name: Build Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + shell: bash + run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsLuau + + - name: Test Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + shell: bash + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTestsLuau" --output-on-failure + + - name: Configure C++20 + shell: bash + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 + + - name: Build Luau (C++20) + working-directory: ${{runner.workspace}}/build20 + shell: bash + run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsLuau - - name: Configure + - name: Test Luau (C++20) + working-directory: ${{runner.workspace}}/build20 shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTestsLuau" --output-on-failure - - name: Build Luau - working-directory: ${{runner.workspace}}/build + - name: Configure C++23 + shell: bash + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 + + - name: Build Luau (C++23) + working-directory: ${{runner.workspace}}/build23 shell: bash run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsLuau - - name: Test Luau - working-directory: ${{runner.workspace}}/build/Tests/Release + - name: Test Luau (C++23) + working-directory: ${{runner.workspace}}/build23 shell: bash - run: ./LuaBridgeTestsLuau.exe + run: ctest --parallel 4 -C $BUILD_TYPE -R "LuaBridgeTestsLuau" --output-on-failure ravi: runs-on: windows-latest @@ -130,21 +239,58 @@ jobs: with: submodules: true - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + + - name: Configure C++17 + shell: bash + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 + + - name: Build Ravi (C++17) + working-directory: ${{runner.workspace}}/build17 + shell: bash + run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsRavi + + - name: Test Ravi (C++17) + working-directory: ${{runner.workspace}}/build17/Tests/Release + shell: bash + run: | + cp ../ravi/Release/libravi.dll . + ./LuaBridgeTestsRavi.exe + + - name: Configure C++20 + shell: bash + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 + + - name: Build Ravi (C++20) + working-directory: ${{runner.workspace}}/build20 + shell: bash + run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsRavi + + - name: Test Ravi (C++20) + working-directory: ${{runner.workspace}}/build20/Tests/Release + shell: bash + run: | + cp ../ravi/Release/libravi.dll . + ./LuaBridgeTestsRavi.exe - - name: Configure + - name: Configure C++23 shell: bash - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 - - name: Build Ravi - working-directory: ${{runner.workspace}}/build + - name: Build Ravi (C++23) + working-directory: ${{runner.workspace}}/build23 shell: bash run: cmake --build . --config $BUILD_TYPE --parallel 4 --target LuaBridgeTestsRavi - - name: Test Ravi - working-directory: ${{runner.workspace}}/build/Tests/Release + - name: Test Ravi (C++23) + working-directory: ${{runner.workspace}}/build23/Tests/Release shell: bash run: | cp ../ravi/Release/libravi.dll . diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a558dfd9..c64c0410 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -41,17 +41,19 @@ jobs: - name: Install lcov run: sudo apt-get install -y lcov ninja-build - - name: Create Build Environment + - name: Create Build Environments run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + cmake -E make_directory ${{runner.workspace}}/coverage - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -DLUABRIDGE_COVERAGE=ON -G Ninja - - name: Build Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build + - name: Build Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTests${{ matrix.lua.suffix }} \ @@ -59,26 +61,72 @@ jobs: LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept - - name: Test Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build/Tests + - name: Test Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Coverage Lua ${{ matrix.lua.version }} (C++17) + working-directory: ${{runner.workspace}}/build17 run: | - ./LuaBridgeTests${{ matrix.lua.suffix }} - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaC - ./LuaBridgeTests${{ matrix.lua.suffix }}Noexcept - ./LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + lcov -c -d "${{runner.workspace}}/build17" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/lua${{ matrix.lua.suffix }}_cxx17.info" - - name: Coverage Lua ${{ matrix.lua.version }} - working-directory: ${{runner.workspace}}/build + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Coverage Lua ${{ matrix.lua.version }} (C++20) + working-directory: ${{runner.workspace}}/build20 + run: | + lcov -c -d "${{runner.workspace}}/build20" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ --ignore-errors mismatch,unused \ --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/lua${{ matrix.lua.suffix }}.info" + -o "${{runner.workspace}}/coverage/lua${{ matrix.lua.suffix }}_cxx20.info" + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTests${{ matrix.lua.suffix }} \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaC \ + LuaBridgeTests${{ matrix.lua.suffix }}Noexcept \ + LuaBridgeTests${{ matrix.lua.suffix }}LuaCNoexcept + + - name: Test Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTests${{ matrix.lua.suffix }}" --output-on-failure + + - name: Coverage Lua ${{ matrix.lua.version }} (C++23) + working-directory: ${{runner.workspace}}/build23 + run: | + lcov -c -d "${{runner.workspace}}/build23" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/lua${{ matrix.lua.suffix }}_cxx23.info" - name: Cache Lcov Files uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-lua${{ matrix.lua.suffix }}-${{runner.os}}-${{github.sha}} luajit: @@ -92,40 +140,86 @@ jobs: - name: Install lcov run: sudo apt-get install -y lcov ninja-build - - name: Create Build Environment + - name: Create Build Environments run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + cmake -E make_directory ${{runner.workspace}}/coverage - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -DLUABRIDGE_COVERAGE=ON -G Ninja - - name: Build LuaJIT - working-directory: ${{runner.workspace}}/build + - name: Build LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 run: | cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ LuaBridgeTestsLuaJIT \ LuaBridgeTestsLuaJITNoexcept - - name: Test LuaJIT - working-directory: ${{runner.workspace}}/build/Tests + - name: Test LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure + + - name: Coverage LuaJIT (C++17) + working-directory: ${{runner.workspace}}/build17 + run: | + lcov -c -d "${{runner.workspace}}/build17" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/luajit_cxx17.info" + + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 run: | - ./LuaBridgeTestsLuaJIT - ./LuaBridgeTestsLuaJITNoexcept + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept - - name: Coverage LuaJIT - working-directory: ${{runner.workspace}}/build + - name: Test LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure + + - name: Coverage LuaJIT (C++20) + working-directory: ${{runner.workspace}}/build20 run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + lcov -c -d "${{runner.workspace}}/build20" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ --ignore-errors mismatch,unused \ --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/luajit.info" + -o "${{runner.workspace}}/coverage/luajit_cxx20.info" + + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 + run: | + cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target \ + LuaBridgeTestsLuaJIT \ + LuaBridgeTestsLuaJITNoexcept + + - name: Test LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuaJIT" --output-on-failure + + - name: Coverage LuaJIT (C++23) + working-directory: ${{runner.workspace}}/build23 + run: | + lcov -c -d "${{runner.workspace}}/build23" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/luajit_cxx23.info" - name: Cache Lcov Files uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-luajit-${{runner.os}}-${{github.sha}} luau: @@ -139,35 +233,77 @@ jobs: - name: Install lcov run: sudo apt-get install -y lcov ninja-build - - name: Create Build Environment + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + cmake -E make_directory ${{runner.workspace}}/coverage + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + + - name: Test Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure + + - name: Coverage Luau (C++17) + working-directory: ${{runner.workspace}}/build17 + run: | + lcov -c -d "${{runner.workspace}}/build17" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/luau_cxx17.info" + + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build Luau (C++20) + working-directory: ${{runner.workspace}}/build20 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau + + - name: Test Luau (C++20) + working-directory: ${{runner.workspace}}/build20 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure + + - name: Coverage Luau (C++20) + working-directory: ${{runner.workspace}}/build20 run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage + lcov -c -d "${{runner.workspace}}/build20" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/luau_cxx20.info" - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -DLUABRIDGE_COVERAGE=ON -G Ninja - - name: Build Luau - working-directory: ${{runner.workspace}}/build + - name: Build Luau (C++23) + working-directory: ${{runner.workspace}}/build23 run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsLuau - - name: Test Luau - working-directory: ${{runner.workspace}}/build/Tests - run: ./LuaBridgeTestsLuau + - name: Test Luau (C++23) + working-directory: ${{runner.workspace}}/build23 + run: ctest --parallel $(nproc) -R "LuaBridgeTestsLuau" --output-on-failure - - name: Coverage Luau - working-directory: ${{runner.workspace}}/build + - name: Coverage Luau (C++23) + working-directory: ${{runner.workspace}}/build23 run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + lcov -c -d "${{runner.workspace}}/build23" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ --ignore-errors mismatch,unused \ --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/luau.info" + -o "${{runner.workspace}}/coverage/luau_cxx23.info" - name: Cache Lcov Files uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-luau-${{runner.os}}-${{github.sha}} ravi: @@ -181,35 +317,77 @@ jobs: - name: Install lcov run: sudo apt-get install -y lcov libreadline-dev ninja-build - - name: Create Build Environment + - name: Create Build Environments + run: | + cmake -E make_directory ${{runner.workspace}}/build17 + cmake -E make_directory ${{runner.workspace}}/build20 + cmake -E make_directory ${{runner.workspace}}/build23 + cmake -E make_directory ${{runner.workspace}}/coverage + + - name: Configure C++17 + working-directory: ${{runner.workspace}}/build17 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=17 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build Ravi (C++17) + working-directory: ${{runner.workspace}}/build17 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + + - name: Test Ravi (C++17) + working-directory: ${{runner.workspace}}/build17/Tests + run: LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./LuaBridgeTestsRavi + + - name: Coverage Ravi (C++17) + working-directory: ${{runner.workspace}}/build17 + run: | + lcov -c -d "${{runner.workspace}}/build17" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/ravi_cxx17.info" + + - name: Configure C++20 + working-directory: ${{runner.workspace}}/build20 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=20 -DLUABRIDGE_COVERAGE=ON -G Ninja + + - name: Build Ravi (C++20) + working-directory: ${{runner.workspace}}/build20 + run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi + + - name: Test Ravi (C++20) + working-directory: ${{runner.workspace}}/build20/Tests + run: LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./LuaBridgeTestsRavi + + - name: Coverage Ravi (C++20) + working-directory: ${{runner.workspace}}/build20 run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage + lcov -c -d "${{runner.workspace}}/build20" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + --ignore-errors mismatch,unused \ + --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ + -o "${{runner.workspace}}/coverage/ravi_cxx20.info" - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DLUABRIDGE_COVERAGE=ON -G Ninja + - name: Configure C++23 + working-directory: ${{runner.workspace}}/build23 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_STANDARD=23 -DLUABRIDGE_COVERAGE=ON -G Ninja - - name: Build Ravi - working-directory: ${{runner.workspace}}/build + - name: Build Ravi (C++23) + working-directory: ${{runner.workspace}}/build23 run: cmake --build . --config $BUILD_TYPE --parallel $(nproc) --target LuaBridgeTestsRavi - - name: Test Ravi - working-directory: ${{runner.workspace}}/build/Tests + - name: Test Ravi (C++23) + working-directory: ${{runner.workspace}}/build23/Tests run: LD_PRELOAD=$(gcc -print-file-name=libasan.so) ./LuaBridgeTestsRavi - - name: Coverage Ravi - working-directory: ${{runner.workspace}}/build + - name: Coverage Ravi (C++23) + working-directory: ${{runner.workspace}}/build23 run: | - lcov -c -d "${{runner.workspace}}/build" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ + lcov -c -d "${{runner.workspace}}/build23" --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 \ --ignore-errors mismatch,unused \ --include "*/LuaBridge/*" --exclude "*/Tests/*" --exclude "*/Distribution/*" --exclude "*/coverage_html/*" \ - -o "coverage/ravi.info" + -o "${{runner.workspace}}/coverage/ravi_cxx23.info" - name: Cache Lcov Files uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-ravi-${{runner.os}}-${{github.sha}} coveralls: @@ -223,79 +401,93 @@ jobs: - name: Install lcov run: sudo apt-get install -y lcov - - name: Create Build Environment - run: | - cmake -E make_directory ${{runner.workspace}}/build - cmake -E make_directory ${{runner.workspace}}/build/coverage + - name: Create Coverage Directory + run: cmake -E make_directory ${{runner.workspace}}/coverage - name: Restore Lcov Files Lua 5.1 uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-lua51-${{runner.os}}-${{github.sha}} - name: Restore Lcov Files Lua 5.2 uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-lua52-${{runner.os}}-${{github.sha}} - name: Restore Lcov Files Lua 5.3 uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-lua53-${{runner.os}}-${{github.sha}} - name: Restore Lcov Files Lua 5.4 uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-lua54-${{runner.os}}-${{github.sha}} - name: Restore Lcov Files Lua 5.5 uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-lua55-${{runner.os}}-${{github.sha}} - name: Restore Lcov Files LuaJIT uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-luajit-${{runner.os}}-${{github.sha}} - name: Restore Lcov Files Luau uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-luau-${{runner.os}}-${{github.sha}} - name: Restore Lcov Files Ravi uses: actions/cache@v5 with: - path: "${{runner.workspace}}/build/coverage/*.info" + path: "${{runner.workspace}}/coverage/*.info" key: lcov-ravi-${{runner.os}}-${{github.sha}} - name: Merge Lcov Files - working-directory: ${{runner.workspace}}/build + working-directory: ${{runner.workspace}}/coverage run: | lcov \ - -a "coverage/lua51.info" \ - -a "coverage/lua52.info" \ - -a "coverage/lua53.info" \ - -a "coverage/lua54.info" \ - -a "coverage/lua55.info" \ - -a "coverage/luajit.info" \ - -a "coverage/luau.info" \ - -a "coverage/ravi.info" \ - -o "coverage/merged.info" + -a "lua51_cxx17.info" \ + -a "lua51_cxx20.info" \ + -a "lua51_cxx23.info" \ + -a "lua52_cxx17.info" \ + -a "lua52_cxx20.info" \ + -a "lua52_cxx23.info" \ + -a "lua53_cxx17.info" \ + -a "lua53_cxx20.info" \ + -a "lua53_cxx23.info" \ + -a "lua54_cxx17.info" \ + -a "lua54_cxx20.info" \ + -a "lua54_cxx23.info" \ + -a "lua55_cxx17.info" \ + -a "lua55_cxx20.info" \ + -a "lua55_cxx23.info" \ + -a "luajit_cxx17.info" \ + -a "luajit_cxx20.info" \ + -a "luajit_cxx23.info" \ + -a "luau_cxx17.info" \ + -a "luau_cxx20.info" \ + -a "luau_cxx23.info" \ + -a "ravi_cxx17.info" \ + -a "ravi_cxx20.info" \ + -a "ravi_cxx23.info" \ + -o "merged.info" - name: Install lcov2xml run: cargo install lcov2xml - name: Convert to Cobertura XML - working-directory: ${{runner.workspace}}/build - run: lcov2xml coverage/merged.info -o coverage/cobertura.xml + working-directory: ${{runner.workspace}}/coverage + run: lcov2xml merged.info -o cobertura.xml #- name: Convert to Coverage TXT # working-directory: ${{runner.workspace}}/build @@ -305,7 +497,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: cobertura-coverage - path: ${{runner.workspace}}/build/coverage/cobertura.xml + path: ${{runner.workspace}}/coverage/cobertura.xml #- name: Upload Coverage TXT # uses: actions/upload-artifact@v4 @@ -316,5 +508,5 @@ jobs: - name: Coveralls uses: coverallsapp/github-action@master with: - path-to-lcov: ${{runner.workspace}}/build/coverage/merged.info + path-to-lcov: ${{runner.workspace}}/coverage/merged.info github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index e3de883f..6635b2db 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,8 @@ Documentation Makefile CMakeCache.txt CMakeFiles/ -build/ -Build/ +build*/ +Build*/ *.dir/ *.sln *.vcxproj diff --git a/Benchmarks/benchmark_common.hpp b/Benchmarks/benchmark_common.hpp index 464f3313..122c0746 100644 --- a/Benchmarks/benchmark_common.hpp +++ b/Benchmarks/benchmark_common.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,11 @@ struct Counter { value = v; } + + static int static_add(int a, int b) + { + return a + b; + } }; struct Basic @@ -152,6 +158,16 @@ struct StatefulFunction } }; +struct SharedObject : std::enable_shared_from_this +{ + double value = kMagicValue; + + double get() const + { + return value; + } +}; + inline Basic* basic_return() { static Basic value{}; @@ -163,6 +179,17 @@ inline double basic_get_var(Basic* b) return b ? b->var : 0.0; } +inline std::shared_ptr shared_object_return() +{ + static std::shared_ptr obj = std::make_shared(); + return obj; +} + +inline double shared_object_get_value(std::shared_ptr obj) +{ + return obj ? obj->get() : 0.0; +} + void luaCheckOrThrow(lua_State* L, int status, std::string_view where); void luaDoStringOrThrow(lua_State* L, std::string_view code, std::string_view where); diff --git a/Benchmarks/benchmark_luabridge.cpp b/Benchmarks/benchmark_luabridge.cpp index d1a7924f..9dad4345 100644 --- a/Benchmarks/benchmark_luabridge.cpp +++ b/Benchmarks/benchmark_luabridge.cpp @@ -21,6 +21,25 @@ int vanilla_multi_return(lua_State* L) return 2; } +void registerBasicGetterSetter(lua_State* L) +{ + luabridge::getGlobalNamespace(L) + .beginClass("c") + .addConstructor() + .addProperty("val", &Basic::get, &Basic::set) + .endClass(); +} + +void registerCounter(lua_State* L) +{ + luabridge::getGlobalNamespace(L) + .beginClass("Counter") + .addConstructor() + .addFunction("get", &Counter::get) + .addStaticFunction("static_add", &Counter::static_add) + .endClass(); +} + lua_State* makeLua() { lua_State* L = luaL_newstate(); @@ -342,6 +361,120 @@ void optional_failure_measure(benchmark::State& state) benchmark::DoNotOptimize(x); } +void userdata_variable_write_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerBasic(L); + luaDoStringOrThrow(L, "b = c()", "vanilla userdata_write setup"); + luaDoStringOrThrow(L, "function write_var() b.var = 24.0 end", "vanilla userdata_write closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "write_var"); + luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla write_var"); + } +} + +void userdata_property_getter_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerBasicGetterSetter(L); + luaDoStringOrThrow(L, "b = c()", "vanilla property_getter setup"); + luaDoStringOrThrow(L, "function read_getter() return b.val end", "vanilla property_getter closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "read_getter"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla read_getter"); + lua_pop(L, 1); + } +} + +void userdata_property_setter_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerBasicGetterSetter(L); + luaDoStringOrThrow(L, "b = c()", "vanilla property_setter setup"); + luaDoStringOrThrow(L, "function write_setter() b.val = 24.0 end", "vanilla property_setter closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "write_setter"); + luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "vanilla write_setter"); + } +} + +void lambda_capture_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + double extra = kMagicValue; + luabridge::getGlobalNamespace(L).addFunction("f", std::function([extra](double v) { return v + extra; })); + luaDoStringOrThrow(L, "function invoke_lambda() return f(24.0) end", "vanilla lambda_capture setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "invoke_lambda"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_lambda"); + lua_pop(L, 1); + } +} + +void shared_ptr_return_measure(benchmark::State& state) +{ + setSkipped(state, "unsupported shared_ptr container in LuaBridge vanilla"); +} + +void shared_ptr_pass_measure(benchmark::State& state) +{ + setSkipped(state, "unsupported shared_ptr container in LuaBridge vanilla"); +} + +void static_member_function_call_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerCounter(L); + luaDoStringOrThrow(L, "function invoke_static() return Counter.static_add(10, 32) end", "vanilla static_member_function setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "invoke_static"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla invoke_static"); + lua_pop(L, 1); + } +} + +void derived_method_call_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + + luabridge::getGlobalNamespace(L) + .beginClass("ComplexBaseA") + .addFunction("a_func", &ComplexBaseA::a_func) + .addProperty("a", &ComplexBaseA::a) + .endClass() + .deriveClass("ComplexAB") + .addConstructor() + .addFunction("ab_func", &ComplexAB::ab_func) + .addProperty("ab", &ComplexAB::ab) + .endClass(); + + luaDoStringOrThrow(L, "obj = ComplexAB()", "vanilla derived_method setup"); + luaDoStringOrThrow(L, "function call_derived() return obj:ab_func() end", "vanilla derived_method closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "call_derived"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "vanilla call_derived"); + lua_pop(L, 1); + } +} + void implicit_inheritance_measure(benchmark::State& state) { setSkipped(state, "unsupported for multi inheritance in LuaBridge vanilla"); @@ -371,3 +504,11 @@ BENCHMARK(optional_success_measure)->Name("optional_success_measure"); BENCHMARK(optional_half_failure_measure)->Name("optional_half_failure_measure"); BENCHMARK(optional_failure_measure)->Name("optional_failure_measure"); BENCHMARK(implicit_inheritance_measure)->Name("implicit_inheritance_measure"); +BENCHMARK(userdata_variable_write_measure)->Name("userdata_variable_write_measure"); +BENCHMARK(userdata_property_getter_measure)->Name("userdata_property_getter_measure"); +BENCHMARK(userdata_property_setter_measure)->Name("userdata_property_setter_measure"); +BENCHMARK(lambda_capture_measure)->Name("lambda_capture_measure"); +BENCHMARK(shared_ptr_return_measure)->Name("shared_ptr_return_measure"); +BENCHMARK(shared_ptr_pass_measure)->Name("shared_ptr_pass_measure"); +BENCHMARK(static_member_function_call_measure)->Name("static_member_function_call_measure"); +BENCHMARK(derived_method_call_measure)->Name("derived_method_call_measure"); diff --git a/Benchmarks/benchmark_luabridge3.cpp b/Benchmarks/benchmark_luabridge3.cpp index 10b158b7..dc2f8225 100644 --- a/Benchmarks/benchmark_luabridge3.cpp +++ b/Benchmarks/benchmark_luabridge3.cpp @@ -91,6 +91,45 @@ void registerBasicLarge(lua_State* L) .endClass(); } +void registerBasicRW(lua_State* L) +{ + luabridge::getGlobalNamespace(L) + .beginClass("c") + .addConstructor() + .addPropertyReadWrite("var", &Basic::var) + .endClass(); +} + +void registerBasicGetterSetter(lua_State* L) +{ + luabridge::getGlobalNamespace(L) + .beginClass("c") + .addConstructor() + .addProperty("val", &Basic::get, &Basic::set) + .endClass(); +} + +void registerCounter(lua_State* L) +{ + luabridge::getGlobalNamespace(L) + .beginClass("Counter") + .addConstructor() + .addFunction("get", &Counter::get) + .addStaticFunction("static_add", &Counter::static_add) + .endClass(); +} + +void registerSharedObject(lua_State* L) +{ + luabridge::getGlobalNamespace(L) + .beginClass("SharedObject") + .addConstructorFrom, void(*)()>() + .addFunction("get", &SharedObject::get) + .endClass() + .addFunction("get_shared", &shared_object_return) + .addFunction("use_shared", &shared_object_get_value); +} + lua_State* makeLua() { lua_State* L = luaL_newstate(); @@ -453,6 +492,145 @@ void return_userdata_measure(benchmark::State& state) } } +void userdata_variable_write_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerBasicRW(L); + luaDoStringOrThrow(L, "b = c()", "userdata_variable_write setup"); + luaDoStringOrThrow(L, "function write_var() b.var = 24.0 end", "userdata_variable_write closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "write_var"); + luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "write_var"); + } +} + +void userdata_property_getter_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerBasicGetterSetter(L); + luaDoStringOrThrow(L, "b = c()", "userdata_property_getter setup"); + luaDoStringOrThrow(L, "function read_getter() return b.val end", "userdata_property_getter closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "read_getter"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "read_getter"); + lua_pop(L, 1); + } +} + +void userdata_property_setter_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerBasicGetterSetter(L); + luaDoStringOrThrow(L, "b = c()", "userdata_property_setter setup"); + luaDoStringOrThrow(L, "function write_setter() b.val = 24.0 end", "userdata_property_setter closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "write_setter"); + luaCheckOrThrow(L, lua_pcall(L, 0, 0, 0), "write_setter"); + } +} + +void lambda_capture_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + double extra = kMagicValue; + luabridge::getGlobalNamespace(L).addFunction("f", [extra](double v) { return v + extra; }); + luaDoStringOrThrow(L, "function invoke_lambda() return f(24.0) end", "lambda_capture setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "invoke_lambda"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_lambda"); + lua_pop(L, 1); + } +} + +void shared_ptr_return_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerSharedObject(L); + luaDoStringOrThrow(L, "function invoke_shared() return get_shared():get() end", "shared_ptr_return setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "invoke_shared"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_shared"); + lua_pop(L, 1); + } +} + +void shared_ptr_pass_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerSharedObject(L); + luaDoStringOrThrow(L, "obj = SharedObject()", "shared_ptr_pass setup"); + luaDoStringOrThrow(L, "function invoke_pass_shared() return use_shared(obj) end", "shared_ptr_pass closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "invoke_pass_shared"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_pass_shared"); + lua_pop(L, 1); + } +} + +void static_member_function_call_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + registerCounter(L); + luaDoStringOrThrow(L, "function invoke_static() return Counter.static_add(10, 32) end", "static_member_function setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "invoke_static"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "invoke_static"); + lua_pop(L, 1); + } +} + +void derived_method_call_measure(benchmark::State& state) +{ + lua_State* L = makeLua(); + + luabridge::getGlobalNamespace(L) + .beginClass("ComplexBaseA") + .addFunction("a_func", &ComplexBaseA::a_func) + .addProperty("a", &ComplexBaseA::a) + .endClass() + .beginClass("ComplexBaseB") + .addFunction("b_func", &ComplexBaseB::b_func) + .addProperty("b", &ComplexBaseB::b) + .endClass() + .deriveClass("ComplexAB") + .addConstructor() + .addFunction("ab_func", &ComplexAB::ab_func) + .addProperty("ab", &ComplexAB::ab) + .endClass(); + + luaDoStringOrThrow(L, "obj = ComplexAB()", "derived_method setup"); + luaDoStringOrThrow(L, "function call_derived() return obj:ab_func() end", "derived_method closure setup"); + + for (auto _ : state) + { + (void) _; + lua_getglobal(L, "call_derived"); + luaCheckOrThrow(L, lua_pcall(L, 0, 1, 0), "call_derived"); + lua_pop(L, 1); + } +} + void implicit_inheritance_measure(benchmark::State& state) { lua_State* L = makeLua(); @@ -503,3 +681,11 @@ BENCHMARK(optional_success_measure)->Name("optional_success_measure"); BENCHMARK(optional_half_failure_measure)->Name("optional_half_failure_measure"); BENCHMARK(optional_failure_measure)->Name("optional_failure_measure"); BENCHMARK(implicit_inheritance_measure)->Name("implicit_inheritance_measure"); +BENCHMARK(userdata_variable_write_measure)->Name("userdata_variable_write_measure"); +BENCHMARK(userdata_property_getter_measure)->Name("userdata_property_getter_measure"); +BENCHMARK(userdata_property_setter_measure)->Name("userdata_property_setter_measure"); +BENCHMARK(lambda_capture_measure)->Name("lambda_capture_measure"); +BENCHMARK(shared_ptr_return_measure)->Name("shared_ptr_return_measure"); +BENCHMARK(shared_ptr_pass_measure)->Name("shared_ptr_pass_measure"); +BENCHMARK(static_member_function_call_measure)->Name("static_member_function_call_measure"); +BENCHMARK(derived_method_call_measure)->Name("derived_method_call_measure"); diff --git a/Benchmarks/benchmark_sol3.cpp b/Benchmarks/benchmark_sol3.cpp index 9c0aaae2..44e9e562 100644 --- a/Benchmarks/benchmark_sol3.cpp +++ b/Benchmarks/benchmark_sol3.cpp @@ -86,6 +86,31 @@ void registerBasicLarge(sol::state& lua) "var49", &BasicLarge::var49); } +void registerBasicGetterSetter(sol::state& lua) +{ + lua.new_usertype("c", + sol::constructors(), + "val", sol::property(&Basic::get, &Basic::set)); +} + +void registerCounter(sol::state& lua) +{ + lua.new_usertype("Counter", + sol::constructors(), + "get", &Counter::get, + "static_add", &Counter::static_add); +} + +void registerSharedObject(sol::state& lua) +{ + lua.new_usertype("SharedObject", + sol::call_constructor, + sol::factories([]() { return std::make_shared(); }), + "get", &SharedObject::get); + lua.set_function("get_shared", &shared_object_return); + lua.set_function("use_shared", &shared_object_get_value); +} + void table_global_string_get_measure(benchmark::State& state) { sol::state lua; @@ -414,6 +439,127 @@ void return_userdata_measure(benchmark::State& state) } } +void userdata_variable_write_measure(benchmark::State& state) +{ + sol::state lua; + registerBasic(lua); + lua.script("b = c.new()\nfunction write_var() b.var = 24.0 end"); + + for (auto _ : state) + { + (void) _; + lua["write_var"](); + } +} + +void userdata_property_getter_measure(benchmark::State& state) +{ + sol::state lua; + registerBasicGetterSetter(lua); + lua.script("b = c.new()\nfunction read_getter() return b.val end"); + + for (auto _ : state) + { + (void) _; + lua["read_getter"](); + } +} + +void userdata_property_setter_measure(benchmark::State& state) +{ + sol::state lua; + registerBasicGetterSetter(lua); + lua.script("b = c.new()\nfunction write_setter() b.val = 24.0 end"); + + for (auto _ : state) + { + (void) _; + lua["write_setter"](); + } +} + +void lambda_capture_measure(benchmark::State& state) +{ + sol::state lua; + double extra = kMagicValue; + lua.set_function("f", [extra](double v) { return v + extra; }); + lua.script("function invoke_lambda() return f(24.0) end"); + + for (auto _ : state) + { + (void) _; + lua["invoke_lambda"](); + } +} + +void shared_ptr_return_measure(benchmark::State& state) +{ + sol::state lua; + registerSharedObject(lua); + lua.script("function invoke_shared() return get_shared():get() end"); + + for (auto _ : state) + { + (void) _; + lua["invoke_shared"](); + } +} + +void shared_ptr_pass_measure(benchmark::State& state) +{ + sol::state lua; + registerSharedObject(lua); + lua.script("obj = SharedObject()\nfunction invoke_pass_shared() return use_shared(obj) end"); + + for (auto _ : state) + { + (void) _; + lua["invoke_pass_shared"](); + } +} + +void static_member_function_call_measure(benchmark::State& state) +{ + sol::state lua; + registerCounter(lua); + lua.script("function invoke_static() return Counter.static_add(10, 32) end"); + + for (auto _ : state) + { + (void) _; + lua["invoke_static"](); + } +} + +void derived_method_call_measure(benchmark::State& state) +{ + sol::state lua; + + lua.new_usertype("ComplexBaseA", + "a_func", &ComplexBaseA::a_func, + "a", &ComplexBaseA::a); + + lua.new_usertype("ComplexBaseB", + "b_func", &ComplexBaseB::b_func, + "b", &ComplexBaseB::b); + + lua.new_usertype("ComplexAB", + sol::constructors(), + sol::base_classes, sol::bases(), + "ab_func", &ComplexAB::ab_func, + "ab", &ComplexAB::ab); + + ComplexAB ab; + lua["obj"] = &ab; + lua.script("function call_derived() return obj:ab_func() end"); + + for (auto _ : state) + { + (void) _; + lua["call_derived"](); + } +} + void implicit_inheritance_measure(benchmark::State& state) { sol::state lua; @@ -462,3 +608,11 @@ BENCHMARK(optional_success_measure)->Name("optional_success_measure"); BENCHMARK(optional_half_failure_measure)->Name("optional_half_failure_measure"); BENCHMARK(optional_failure_measure)->Name("optional_failure_measure"); BENCHMARK(implicit_inheritance_measure)->Name("implicit_inheritance_measure"); +BENCHMARK(userdata_variable_write_measure)->Name("userdata_variable_write_measure"); +BENCHMARK(userdata_property_getter_measure)->Name("userdata_property_getter_measure"); +BENCHMARK(userdata_property_setter_measure)->Name("userdata_property_setter_measure"); +BENCHMARK(lambda_capture_measure)->Name("lambda_capture_measure"); +BENCHMARK(shared_ptr_return_measure)->Name("shared_ptr_return_measure"); +BENCHMARK(shared_ptr_pass_measure)->Name("shared_ptr_pass_measure"); +BENCHMARK(static_member_function_call_measure)->Name("static_member_function_call_measure"); +BENCHMARK(derived_method_call_measure)->Name("derived_method_call_measure"); diff --git a/CMakeLists.txt b/CMakeLists.txt index a79065db..a7b0ac9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,10 @@ project (LuaBridge) include (CMakeDependentOption) -set (CMAKE_CXX_STANDARD 20) +if (NOT CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 20) +endif() + set (CMAKE_CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_EXTENSIONS OFF) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index 0ec3ff58..daa2e980 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -7,22 +7,27 @@ #pragma once #include +#include #include #include #include -#include #include #include #include #include +#include #include +#include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -32,11 +37,38 @@ #include #include #include +#include #include +#include #include #include #include +#if defined(__has_include) && __has_include() && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +#include +#endif + +#if defined(__has_include) && __has_include() && (__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)) +#include +#endif + +#if defined(__has_include) && __has_include() && (__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)) +#include +#endif + +#if defined(__has_include) && __has_include() && (__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)) +#include +#endif + +#if defined(__has_include) && __has_include() && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +#include +#endif + +#if defined(__has_include) && __has_include() && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +#include +#endif + + // Begin File: Source/LuaBridge/detail/Config.h @@ -44,6 +76,12 @@ #error LuaBridge 3 requires a compliant C++17 compiler, or C++17 has not been enabled ! #endif +#if __cplusplus >= 202302L || (defined(_MSC_VER) && _HAS_CXX23) +#define LUABRIDGE_CXX23_OR_GREATER 1 +#elif __cplusplus >= 202002L || (defined(_MSC_VER) && _HAS_CXX20) +#define LUABRIDGE_CXX20_OR_GREATER 1 +#endif + #if defined(LUAU_FASTMATH_BEGIN) #define LUABRIDGE_ON_LUAU 1 #elif defined(LUAJIT_VERSION) @@ -56,14 +94,6 @@ #error "Lua headers must be included prior to LuaBridge ones" #endif -#if !defined(LUABRIDGE_HAS_CXX20_COROUTINES) -#if !defined(LUABRIDGE_DISABLE_CXX20_COROUTINES) && (__cplusplus >= 202002L || (defined(_MSC_VER) && _HAS_CXX20)) && !(LUABRIDGE_ON_LUAU || LUABRIDGE_ON_LUAJIT || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 502) -#define LUABRIDGE_HAS_CXX20_COROUTINES 1 -#else -#define LUABRIDGE_HAS_CXX20_COROUTINES 0 -#endif -#endif - #if !defined(LUABRIDGE_HAS_EXCEPTIONS) #if defined(_MSC_VER) #if _CPPUNWIND || _HAS_EXCEPTIONS @@ -132,6 +162,70 @@ #endif #endif +#if !defined(LUABRIDGE_HAS_CXX17_FILESYSTEM) +#if !defined(LUABRIDGE_DISABLE_CXX17_FILESYSTEM) && __has_include() && defined(__cpp_lib_filesystem) +#define LUABRIDGE_HAS_CXX17_FILESYSTEM 1 +#else +#define LUABRIDGE_HAS_CXX17_FILESYSTEM 0 +#endif +#endif + +#if !defined(LUABRIDGE_HAS_CXX17_ANY) +#if !defined(LUABRIDGE_DISABLE_CXX17_ANY) && __has_include() && defined(__cpp_lib_any) +#define LUABRIDGE_HAS_CXX17_ANY 1 +#else +#define LUABRIDGE_HAS_CXX17_ANY 0 +#endif +#endif + +#if !defined(LUABRIDGE_HAS_CXX20_SPAN) +#if !defined(LUABRIDGE_DISABLE_CXX20_SPAN) && LUABRIDGE_CXX20_OR_GREATER && __has_include() && defined(__cpp_lib_span) +#define LUABRIDGE_HAS_CXX20_SPAN 1 +#else +#define LUABRIDGE_HAS_CXX20_SPAN 0 +#endif +#endif + +#if !defined(LUABRIDGE_HAS_CXX20_RANGES) +#if !defined(LUABRIDGE_DISABLE_CXX20_RANGES) && LUABRIDGE_CXX20_OR_GREATER && defined(__cpp_lib_ranges) +#define LUABRIDGE_HAS_CXX20_RANGES 1 +#else +#define LUABRIDGE_HAS_CXX20_RANGES 0 +#endif +#endif + +#if !defined(LUABRIDGE_HAS_CXX20_COROUTINES) +#if !defined(LUABRIDGE_DISABLE_CXX20_COROUTINES) && LUABRIDGE_CXX20_OR_GREATER && !(LUABRIDGE_ON_LUAU || LUABRIDGE_ON_LUAJIT || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 502) +#define LUABRIDGE_HAS_CXX20_COROUTINES 1 +#else +#define LUABRIDGE_HAS_CXX20_COROUTINES 0 +#endif +#endif + +#if !defined(LUABRIDGE_HAS_CXX23_EXPECTED) +#if !defined(LUABRIDGE_DISABLE_CXX23_EXPECTED) && LUABRIDGE_CXX23_OR_GREATER && __has_include() && defined(__cpp_lib_expected) +#define LUABRIDGE_HAS_CXX23_EXPECTED 1 +#else +#define LUABRIDGE_HAS_CXX23_EXPECTED 0 +#endif +#endif + +#if !defined(LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS) +#if !defined(LUABRIDGE_DISABLE_CXX23_FLAT_CONTAINERS) && LUABRIDGE_CXX23_OR_GREATER && __has_include() && __has_include() && defined(__cpp_lib_flat_map) +#define LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS 1 +#else +#define LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS 0 +#endif +#endif + +#if !defined(LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION) +#if !defined(LUABRIDGE_DISABLE_CXX23_MOVE_ONLY_FUNCTION) && LUABRIDGE_CXX23_OR_GREATER && defined(__cpp_lib_move_only_function) +#define LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION 1 +#else +#define LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION 0 +#endif +#endif + // End File: Source/LuaBridge/detail/Config.h @@ -313,6 +407,19 @@ struct has_call_operator> : std::true_t template inline static constexpr bool has_call_operator_v = has_call_operator::value; +template +struct is_move_only_function : std::false_type {}; + +#if LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION +template struct is_move_only_function> : std::true_type {}; +template struct is_move_only_function> : std::true_type {}; +template struct is_move_only_function> : std::true_type {}; +template struct is_move_only_function> : std::true_type {}; +#endif + +template +inline static constexpr bool is_move_only_function_v = is_move_only_function::value; + template struct functor_traits_impl { @@ -324,11 +431,39 @@ struct functor_traits_impl>> : functi }; template -struct functor_traits_impl && std::is_invocable_v>> +struct functor_traits_impl && std::is_invocable_v && !is_move_only_function_v>> : function_traits_base> { }; +#if LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +#endif + template struct function_traits : std::conditional_t, detail::functor_traits_impl, @@ -3242,6 +3377,16 @@ struct TypeResult return std::move(m_value.value()); } + T* operator->() + { + return &m_value.value(); + } + + const T* operator->() const + { + return &m_value.value(); + } + template T valueOr(U&& defaultValue) const& { @@ -3740,6 +3885,17 @@ struct ContainerTraits> } }; +template +struct ContainerTraits> +{ + using Type = T; + + static T* get(const std::unique_ptr& c) + { + return c.get(); + } +}; + namespace detail { template @@ -4685,6 +4841,9 @@ struct StackOpSelector // Begin File: Source/LuaBridge/detail/Stack.h +#if LUABRIDGE_HAS_CXX17_FILESYSTEM +#endif + namespace luabridge { class StackRestore final @@ -5625,6 +5784,54 @@ struct Stack> } }; +template +struct Stack> +{ + using Type = Expected; + + [[nodiscard]] static Result push(lua_State* L, const Type& value) + { + if (value.hasValue()) + { + StackRestore stackRestore(L); + + auto result = Stack::push(L, *value); + if (! result) + return result; + + stackRestore.reset(); + return {}; + } + +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + const auto type = lua_type(L, index); + if (type == LUA_TNIL || type == LUA_TNONE) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto result = Stack::get(L, index); + if (! result) + return result.error(); + + return Type(*result); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + const auto type = lua_type(L, index); + return (type != LUA_TNIL && type != LUA_TNONE) && Stack::isInstance(L, index); + } +}; + template struct Stack> { @@ -6045,6 +6252,39 @@ struct Stack [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::isInstance(L, index); } }; +#if LUABRIDGE_HAS_CXX17_FILESYSTEM + +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const std::filesystem::path& path) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlstring(L, path.string().c_str(), path.string().size()); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TSTRING) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + std::size_t len = 0; + const char* str = lua_tolstring(L, index, &len); + return std::filesystem::path(std::string(str, len)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TSTRING; + } +}; +#endif + template [[nodiscard]] Result push(lua_State* L, const T& t) { @@ -6068,6 +6308,72 @@ template // End File: Source/LuaBridge/detail/Stack.h +// Begin File: Source/LuaBridge/Any.h + +#if LUABRIDGE_HAS_CXX17_ANY + +namespace luabridge { + +namespace detail { + +using AnyPushFn = std::function; + +inline std::unordered_map& anyPushRegistry() +{ + static std::unordered_map registry; + return registry; +} + +} + +template +void registerAnyPush(lua_State*) +{ + detail::anyPushRegistry()[std::type_index(typeid(T))] = + [](lua_State* L, const std::any& value) -> Result + { + return Stack::push(L, std::any_cast(value)); + }; +} + +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const std::any& value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! value.has_value()) + { + lua_pushnil(L); + return {}; + } + + auto& registry = detail::anyPushRegistry(); + + auto it = registry.find(std::type_index(value.type())); + if (it == registry.end()) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return it->second(L, value); + } + + [[nodiscard]] static bool isInstance(lua_State*, int) + { + return false; + } +}; + +} + +#endif + + +// End File: Source/LuaBridge/Any.h + // Begin File: Source/LuaBridge/Array.h namespace luabridge { @@ -6095,7 +6401,7 @@ struct Stack> if (! result) return result; - lua_rawseti(L, tableIndex, i + 1); + lua_rawseti(L, tableIndex, static_cast(i + 1)); } stackRestore.reset(); @@ -6142,44 +6448,115 @@ struct Stack> // End File: Source/LuaBridge/Array.h -// Begin File: Source/LuaBridge/Dump.h +// Begin File: Source/LuaBridge/Deque.h namespace luabridge { -namespace detail { -inline void putIndent(std::ostream& stream, unsigned level) -{ - for (unsigned i = 0; i < level; ++i) - stream << " "; -} -} - -inline void dumpTable(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr); -inline void dumpValue(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr) +template +struct Stack> { - const int stackTop = lua_gettop(L); - const int absIndex = (index > 0) ? index : (index < 0 ? stackTop + index + 1 : 0); - const int type = (absIndex < 1 || absIndex > stackTop) ? LUA_TNONE : lua_type(L, index); - switch (type) + using Type = std::deque; + + [[nodiscard]] static Result push(lua_State* L, const Type& deque) { - case LUA_TNIL: - stream << "nil"; - break; +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif - case LUA_TBOOLEAN: - stream << (lua_toboolean(L, index) ? "true" : "false"); - break; + StackRestore stackRestore(L); - case LUA_TNUMBER: - stream << lua_tonumber(L, index); - break; + lua_createtable(L, static_cast(deque.size()), 0); + const int tableIndex = lua_gettop(L); - case LUA_TSTRING: - stream << '"' << lua_tostring(L, index) << '"'; - break; + auto it = deque.cbegin(); + for (std::size_t i = 1; it != deque.cend(); ++i, ++it) + { + auto result = Stack::push(L, *it); + if (! result) + return result; - case LUA_TFUNCTION: - if (lua_iscfunction(L, index)) + lua_rawseti(L, tableIndex, static_cast(i)); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type deque; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + deque.emplace_back(*item); + lua_pop(L, 1); + } + + return deque; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} + + +// End File: Source/LuaBridge/Deque.h + +// Begin File: Source/LuaBridge/Dump.h + +namespace luabridge { +namespace detail { +inline void putIndent(std::ostream& stream, unsigned level) +{ + for (unsigned i = 0; i < level; ++i) + stream << " "; +} +} + +inline void dumpTable(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr); + +inline void dumpValue(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr) +{ + const int stackTop = lua_gettop(L); + const int absIndex = (index > 0) ? index : (index < 0 ? stackTop + index + 1 : 0); + const int type = (absIndex < 1 || absIndex > stackTop) ? LUA_TNONE : lua_type(L, index); + switch (type) + { + case LUA_TNIL: + stream << "nil"; + break; + + case LUA_TBOOLEAN: + stream << (lua_toboolean(L, index) ? "true" : "false"); + break; + + case LUA_TNUMBER: + stream << lua_tonumber(L, index); + break; + + case LUA_TSTRING: + stream << '"' << lua_tostring(L, index) << '"'; + break; + + case LUA_TFUNCTION: + if (lua_iscfunction(L, index)) stream << "cfunction@" << lua_topointer(L, index); else stream << "function@" << lua_topointer(L, index); @@ -6274,16 +6651,18 @@ inline void dumpState(lua_State* L, unsigned maxDepth = 1, std::ostream& stream // End File: Source/LuaBridge/Dump.h -// Begin File: Source/LuaBridge/List.h +// Begin File: Source/LuaBridge/FlatMap.h + +#if LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::list; - - [[nodiscard]] static Result push(lua_State* L, const Type& list) + using Type = std::flat_map; + + [[nodiscard]] static Result push(lua_State* L, const Type& map) { #if LUABRIDGE_SAFE_STACK_CHECKS if (! lua_checkstack(L, 3)) @@ -6292,14 +6671,15 @@ struct Stack> StackRestore stackRestore(L); - lua_createtable(L, static_cast(list.size()), 0); + lua_createtable(L, 0, static_cast(map.size())); - auto it = list.cbegin(); - for (lua_Integer tableIndex = 1; it != list.cend(); ++tableIndex, ++it) + for (auto it = map.begin(); it != map.end(); ++it) { - lua_pushinteger(L, tableIndex); + auto result = Stack::push(L, it->first); + if (! result) + return result; - auto result = Stack::push(L, *it); + result = Stack::push(L, it->second); if (! result) return result; @@ -6317,22 +6697,26 @@ struct Stack> const StackRestore stackRestore(L); - Type list; + Type map; int absIndex = lua_absindex(L, index); lua_pushnil(L); while (lua_next(L, absIndex) != 0) { - auto item = Stack::get(L, -1); - if (! item) + auto value = Stack::get(L, -1); + if (! value) return makeErrorCode(ErrorCode::InvalidTypeCast); - list.emplace_back(*item); + auto key = Stack::get(L, -2); + if (! key) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + map.emplace(*key, *value); lua_pop(L, 1); } - return list; + return map; } [[nodiscard]] static bool isInstance(lua_State* L, int index) @@ -6343,138 +6727,360 @@ struct Stack> } +#endif -// End File: Source/LuaBridge/List.h -// Begin File: Source/LuaBridge/detail/FlagSet.h +// End File: Source/LuaBridge/FlatMap.h + +// Begin File: Source/LuaBridge/FlatSet.h + +#if LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS namespace luabridge { -template -class FlagSet +template +struct Stack> { - static_assert(std::is_integral_v); - -public: - constexpr FlagSet() noexcept = default; + using Type = std::flat_set; - constexpr void set(FlagSet other) noexcept + [[nodiscard]] static Result push(lua_State* L, const Type& set) { - flags |= other.flags; - } +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif - constexpr FlagSet withSet(FlagSet other) const noexcept - { - FlagSet result { flags }; - result.flags |= other.flags; - return result; - } + StackRestore stackRestore(L); - constexpr void unset(FlagSet other) noexcept - { - flags &= ~other.flags; - } + lua_createtable(L, 0, static_cast(set.size())); - constexpr FlagSet withUnset(FlagSet other) const noexcept - { - FlagSet result { flags }; - result.flags &= ~other.flags; - return result; - } + auto it = set.cbegin(); + for (lua_Integer tableIndex = 1; it != set.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, tableIndex); - constexpr bool test(FlagSet other) const noexcept - { - return (flags & other.flags) != 0; - } + auto result = Stack::push(L, *it); + if (! result) + return result; - constexpr FlagSet operator|(FlagSet other) const noexcept - { - return FlagSet(flags | other.flags); - } + lua_settable(L, -3); + } - constexpr FlagSet operator&(FlagSet other) const noexcept - { - return FlagSet(flags & other.flags); + stackRestore.reset(); + return {}; } - constexpr FlagSet operator~() const noexcept + [[nodiscard]] static TypeResult get(lua_State* L, int index) { - return FlagSet(~flags); - } + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); - constexpr T toUnderlying() const noexcept - { - return flags; - } + const StackRestore stackRestore(L); - std::string toString() const - { - std::string result; - result.reserve(sizeof(T) * std::numeric_limits::digits); + Type set; - (result.append((mask() & flags) ? "1" : "0"), ...); + int absIndex = lua_absindex(L, index); + lua_pushnil(L); - for (std::size_t i = sizeof...(Ts); i < sizeof(T) * std::numeric_limits::digits; ++i) - result.append("0"); + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); - std::reverse(result.begin(), result.end()); + set.emplace(*item); + lua_pop(L, 1); + } - return result; + return set; } - template - static constexpr FlagSet Value() noexcept + [[nodiscard]] static bool isInstance(lua_State* L, int index) { - return FlagSet{ mask() }; + return lua_istable(L, index); } +}; - template - static constexpr auto fromUnderlying(U newFlags) noexcept - -> std::enable_if_t && std::is_convertible_v, FlagSet> - { - return { static_cast(newFlags) }; - } +} -private: - template - static constexpr T indexOf() noexcept - { - if constexpr (std::is_same_v) - return static_cast(0); - else - return static_cast(1) + indexOf(); - } +#endif - template - static constexpr T mask() noexcept - { - return ((static_cast(1) << indexOf()) | ...); - } - constexpr FlagSet(T flags) noexcept - : flags(flags) - { - } +// End File: Source/LuaBridge/FlatSet.h - T flags = 0; -}; +// Begin File: Source/LuaBridge/ForwardList.h -} +namespace luabridge { +template +struct Stack> +{ + using Type = std::forward_list; -// End File: Source/LuaBridge/detail/FlagSet.h + [[nodiscard]] static Result push(lua_State* L, const Type& list) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif -// Begin File: Source/LuaBridge/detail/Options.h + StackRestore stackRestore(L); -namespace luabridge { + lua_createtable(L, 0, 0); -namespace detail { -struct OptionExtensibleClass; -struct OptionAllowOverridingMethods; -struct OptionVisibleMetatables; -} + auto it = list.cbegin(); + for (std::size_t tableIndex = 1; it != list.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, static_cast(tableIndex)); -using Options = FlagSet::push(L, *it); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type list; + auto insertPos = list.before_begin(); + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + insertPos = list.insert_after(insertPos, *item); + lua_pop(L, 1); + } + + return list; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} + + +// End File: Source/LuaBridge/ForwardList.h + +// Begin File: Source/LuaBridge/List.h + +namespace luabridge { + +template +struct Stack> +{ + using Type = std::list; + + [[nodiscard]] static Result push(lua_State* L, const Type& list) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(list.size()), 0); + const int tableIndex = lua_gettop(L); + + auto it = list.cbegin(); + for (std::size_t i = 1; it != list.cend(); ++i, ++it) + { + auto result = Stack::push(L, *it); + if (! result) + return result; + + lua_rawseti(L, tableIndex, static_cast(i)); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type list; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + list.emplace_back(*item); + lua_pop(L, 1); + } + + return list; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} + + +// End File: Source/LuaBridge/List.h + +// Begin File: Source/LuaBridge/detail/FlagSet.h + +namespace luabridge { + +template +class FlagSet +{ + static_assert(std::is_integral_v); + +public: + constexpr FlagSet() noexcept = default; + + constexpr void set(FlagSet other) noexcept + { + flags |= other.flags; + } + + constexpr FlagSet withSet(FlagSet other) const noexcept + { + FlagSet result { flags }; + result.flags |= other.flags; + return result; + } + + constexpr void unset(FlagSet other) noexcept + { + flags &= ~other.flags; + } + + constexpr FlagSet withUnset(FlagSet other) const noexcept + { + FlagSet result { flags }; + result.flags &= ~other.flags; + return result; + } + + constexpr bool test(FlagSet other) const noexcept + { + return (flags & other.flags) != 0; + } + + constexpr FlagSet operator|(FlagSet other) const noexcept + { + return FlagSet(flags | other.flags); + } + + constexpr FlagSet operator&(FlagSet other) const noexcept + { + return FlagSet(flags & other.flags); + } + + constexpr FlagSet operator~() const noexcept + { + return FlagSet(~flags); + } + + constexpr T toUnderlying() const noexcept + { + return flags; + } + + std::string toString() const + { + std::string result; + result.reserve(sizeof(T) * std::numeric_limits::digits); + + (result.append((mask() & flags) ? "1" : "0"), ...); + + for (std::size_t i = sizeof...(Ts); i < sizeof(T) * std::numeric_limits::digits; ++i) + result.append("0"); + + std::reverse(result.begin(), result.end()); + + return result; + } + + template + static constexpr FlagSet Value() noexcept + { + return FlagSet{ mask() }; + } + + template + static constexpr auto fromUnderlying(U newFlags) noexcept + -> std::enable_if_t && std::is_convertible_v, FlagSet> + { + return { static_cast(newFlags) }; + } + +private: + template + static constexpr T indexOf() noexcept + { + if constexpr (std::is_same_v) + return static_cast(0); + else + return static_cast(1) + indexOf(); + } + + template + static constexpr T mask() noexcept + { + return ((static_cast(1) << indexOf()) | ...); + } + + constexpr FlagSet(T flags) noexcept + : flags(flags) + { + } + + T flags = 0; +}; + +} + + +// End File: Source/LuaBridge/detail/FlagSet.h + +// Begin File: Source/LuaBridge/detail/Options.h + +namespace luabridge { + +namespace detail { +struct OptionExtensibleClass; +struct OptionAllowOverridingMethods; +struct OptionVisibleMetatables; +} + +using Options = FlagSet; @@ -6501,19 +7107,69 @@ class LuaRef; namespace detail { template -auto unwrap_argument_or_error(lua_State* L, std::size_t index, std::size_t start) +using stack_value_t = remove_cvref_t< + decltype(*std::declval::get(std::declval(), 0))>())>; + +template +struct ArgStorage +{ + using StoredType = stack_value_t; + + alignas(StoredType) std::byte data[sizeof(StoredType)]; + bool constructed = false; + + StoredType* ptr() noexcept { return std::launder(reinterpret_cast(data)); } + + void destroy() noexcept + { + if (constructed) + { + std::destroy_at(ptr()); + constructed = false; + } + } +}; + +template +void decode_arg(lua_State* L, StorageTuple& storage, int& error_arg, const char*& error_msg) { - auto result = Stack::get(L, static_cast(index + start)); + using T = std::tuple_element_t; + using StoredType = typename std::tuple_element_t::StoredType; + + if (error_arg) + return; + + auto result = Stack::get(L, static_cast(I + Start)); if (! result) - raise_lua_error(L, "Error decoding argument #%d: %s", static_cast(index + 1), result.error_cstr()); + { + error_arg = static_cast(I + 1); + error_msg = result.error_cstr(); + return; + } - return std::move(*result); + ::new (std::get(storage).data) StoredType(std::move(*result)); + std::get(storage).constructed = true; } template auto make_arguments_list_impl([[maybe_unused]] lua_State* L, std::index_sequence) { - return tupleize(unwrap_argument_or_error>(L, Indices, Start)...); + std::tuple>...> storage; + + int error_arg = 0; + const char* error_msg = nullptr; + + (decode_arg(L, storage, error_arg, error_msg), ...); + + if (error_arg) + { + (std::get(storage).destroy(), ...); + raise_lua_error(L, "Error decoding argument #%d: %s", error_arg, error_msg); + } + + auto result = tupleize(std::move(*std::get(storage).ptr())...); + (std::get(storage).destroy(), ...); + return result; } template @@ -7825,40 +8481,18 @@ inline void add_property_setter(lua_State* L, const char* name, int tableIndex) lua_pop(L, 2); } -template -decltype(auto) invoke_callable_from_stack_impl(lua_State* L, F&& func, std::index_sequence) -{ - return std::invoke( - std::forward(func), - unwrap_argument_or_error>(L, Indices, Start)...); -} - template decltype(auto) invoke_callable_from_stack(lua_State* L, F&& func) { - return invoke_callable_from_stack_impl( - L, - std::forward(func), - std::make_index_sequence>()); -} - -template -decltype(auto) invoke_member_callable_from_stack_impl(lua_State* L, T* ptr, F&& func, std::index_sequence) -{ - return std::invoke( - std::forward(func), - ptr, - unwrap_argument_or_error>(L, Indices, Start)...); + return std::apply(std::forward(func), make_arguments_list(L)); } template decltype(auto) invoke_member_callable_from_stack(lua_State* L, T* ptr, F&& func) { - return invoke_member_callable_from_stack_impl( - L, - ptr, + return std::apply( std::forward(func), - std::make_index_sequence>()); + std::tuple_cat(std::tuple(ptr), make_arguments_list(L))); } template @@ -8917,7 +9551,7 @@ int constructor_container_proxy(lua_State* L) try { #endif - object = constructor::construct(detail::make_arguments_list(L)); + object = constructor::construct(make_arguments_list(L)); #if LUABRIDGE_HAS_EXCEPTIONS } @@ -8958,7 +9592,7 @@ int constructor_placement_proxy(lua_State* L) raise_lua_error(L, "%s", e.what()); } #endif - + value->commit(); return 1; @@ -8985,7 +9619,7 @@ struct constructor_forwarder raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); T* object = nullptr; - + #if LUABRIDGE_HAS_EXCEPTIONS try { @@ -9086,27 +9720,56 @@ struct container_forwarder using FnTraits = function_traits; using FnArgs = typename FnTraits::argument_types; - C object; - + alignas(C) std::byte object_storage[sizeof(C)]; + C* object = nullptr; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + object = ::new (object_storage) C( + container_constructor::construct(m_func, make_arguments_list(L))); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + if (object != nullptr) + std::destroy_at(object); + + raise_lua_error(L, "%s", e.what()); + } +#endif + + LUABRIDGE_ASSERT(object != nullptr); + + Result result; + #if LUABRIDGE_HAS_EXCEPTIONS try { #endif - object = container_constructor::construct(m_func, make_arguments_list(L)); + result = UserdataSharedHelper::push(L, *object); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { + std::destroy_at(object); + raise_lua_error(L, "%s", e.what()); } #endif - auto result = UserdataSharedHelper::push(L, object); if (! result) + { + std::destroy_at(object); raise_lua_error(L, "%s", result.error_cstr()); + } - return object; + C ret = std::move(*object); + std::destroy_at(object); + return ret; } private: @@ -9125,8 +9788,7 @@ struct container_forwarder #if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 || LUABRIDGE_ON_LUAU #ifndef LUABRIDGE_DISABLE_COROUTINE_INTEGRATION -#error "C++20 coroutine integration requires Lua 5.2+ with lua_yieldk support. \ -Define LUABRIDGE_DISABLE_COROUTINE_INTEGRATION to suppress this error." +#error "C++20 coroutine integration requires Lua 5.2+ with lua_yieldk support. Define LUABRIDGE_DISABLE_COROUTINE_INTEGRATION to suppress this error." #endif #else @@ -11046,6 +11708,9 @@ LuaFunction LuaRefBase::callable() const // Begin File: Source/LuaBridge/detail/Iterator.h +#if LUABRIDGE_HAS_CXX20_RANGES +#endif + namespace luabridge { class Iterator @@ -11063,6 +11728,12 @@ class Iterator } } +#if LUABRIDGE_HAS_CXX20_RANGES + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using iterator_concept = std::input_iterator_tag; +#endif + lua_State* state() const noexcept { return m_L; @@ -11181,8 +11852,38 @@ inline Range pairs(const LuaRef& table) return Range{ Iterator(table, false), Iterator(table, true) }; } +#if LUABRIDGE_HAS_CXX20_RANGES + +inline bool operator==(const Iterator& lhs, const Iterator& rhs) +{ + if (lhs.isNil() && rhs.isNil()) + return true; + if (lhs.isNil() != rhs.isNil()) + return false; + return lhs.key().rawequal(rhs.key()) && lhs.value().rawequal(rhs.value()); +} + +struct IteratorSentinel {}; + +inline bool operator==(const Iterator& it, IteratorSentinel) +{ + return it.isNil(); +} + +inline bool operator==(IteratorSentinel, const Iterator& it) +{ + return it.isNil(); +} + +#endif + } +#if LUABRIDGE_HAS_CXX20_RANGES +template <> +inline constexpr bool std::ranges::enable_borrowed_range = false; +#endif + // End File: Source/LuaBridge/detail/Iterator.h @@ -12088,84 +12789,254 @@ class Namespace : public detail::Registrar } overload_set_nonconst->entries.push_back(entry); - } (), ...); + } (), ...); + + LUABRIDGE_ASSERT(!overload_set_nonconst->entries.empty()); + + lua_createtable(L, static_cast(detail::non_const_functions_count), 0); + + int idx = 1; + + ([&] + { + if (detail::is_const_function) + return; + + detail::push_member_function(L, std::move(functions), name); + lua_rawseti(L, -2, idx++); + + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + rawsetfield(L, -3, name); + } + } + + return *this; + } + +#if LUABRIDGE_HAS_CXX20_COROUTINES + + template + auto addStaticCoroutine(const char* name, F factory) + -> std::enable_if_t, Class&> + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); + + detail::push_coroutine_function(L, std::move(factory), name); + rawsetfield(L, -2, name); + + return *this; + } + + template + auto addCoroutine(const char* name, F factory) + -> std::enable_if_t< + detail::is_cpp_coroutine_factory_v && detail::is_proxy_member_function_v, + Class&> + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); + + detail::push_coroutine_function(L, std::move(factory), name); + + if constexpr (detail::is_const_function) + { + lua_pushvalue(L, -1); + rawsetfield(L, -4, name); + rawsetfield(L, -4, name); + } + else + { + rawsetfield(L, -3, name); + } + + return *this; + } +#endif + + template + auto addConstructor() + -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> + { + assertStackState(); + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); + + } (), ...); + } + else + { + + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + ([&] + { + using ArgsPack = detail::function_arguments_t; + detail::OverloadEntry entry; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + overload_set->entries.push_back(entry); + + } (), ...); + + lua_createtable(L, static_cast(sizeof...(Functions)), 0); + + int idx = 1; + + ([&] + { + lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); + lua_rawseti(L, -2, idx++); + + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + } + + rawsetfield(L, -2, "__call"); + + return *this; + } + + template + auto addConstructor(Functions... functions) + -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> + { + static_assert(((detail::function_arity_excluding_v >= 1) && ...)); + static_assert(((std::is_same_v, void*>) && ...)); + + assertStackState(); - LUABRIDGE_ASSERT(!overload_set_nonconst->entries.empty()); + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + using F = detail::constructor_forwarder; - lua_createtable(L, static_cast(detail::non_const_functions_count), 0); + lua_newuserdata_aligned(L, F(std::move(functions))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); - int idx = 1; + } (), ...); + } + else + { + + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); - ([&] + ([&] + { + detail::OverloadEntry entry; + if constexpr (detail::is_any_cfunction_pointer_v) { - if (detail::is_const_function) - return; + entry.arity = -1; + entry.checker = nullptr; + } + else + { + + using ArgsPack = detail::remove_first_type_t>; + entry.arity = static_cast(detail::function_arity_excluding_v) - 1; + entry.checker = &detail::overload_type_checker; + } + overload_set->entries.push_back(entry); - detail::push_member_function(L, std::move(functions), name); - lua_rawseti(L, -2, idx++); + } (), ...); - } (), ...); + lua_createtable(L, static_cast(sizeof...(Functions)), 0); - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); - rawsetfield(L, -3, name); - } - } + int idx = 1; - return *this; - } + ([&] + { + using F = detail::constructor_forwarder; -#if LUABRIDGE_HAS_CXX20_COROUTINES - - template - auto addStaticCoroutine(const char* name, F factory) - -> std::enable_if_t, Class&> - { - LUABRIDGE_ASSERT(name != nullptr); - assertStackState(); + lua_newuserdata_aligned(L, F(std::move(functions))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); + lua_rawseti(L, -2, idx++); - detail::push_coroutine_function(L, std::move(factory), name); - rawsetfield(L, -2, name); + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + } + + rawsetfield(L, -2, "__call"); return *this; } - template - auto addCoroutine(const char* name, F factory) - -> std::enable_if_t< - detail::is_cpp_coroutine_factory_v && detail::is_proxy_member_function_v, - Class&> + template + auto addConstructorFrom() + -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> { - LUABRIDGE_ASSERT(name != nullptr); assertStackState(); - detail::push_coroutine_function(L, std::move(factory), name); - - if constexpr (detail::is_const_function) + if constexpr (sizeof...(Functions) == 1) { - lua_pushvalue(L, -1); - rawsetfield(L, -4, name); - rawsetfield(L, -4, name); + ([&] + { + lua_pushcclosure_x(L, &detail::constructor_container_proxy>, className, 0); + + } (), ...); } else { - rawsetfield(L, -3, name); + + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); + + ([&] + { + using ArgsPack = detail::function_arguments_t; + detail::OverloadEntry entry; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + overload_set->entries.push_back(entry); + + } (), ...); + + lua_createtable(L, static_cast(sizeof...(Functions)), 0); + + int idx = 1; + + ([&] + { + lua_pushcclosure_x(L, &detail::constructor_container_proxy>, className, 0); + lua_rawseti(L, -2, idx++); + + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); } + rawsetfield(L, -2, "__call"); + return *this; } -#endif - template - auto addConstructor() - -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> + template + auto addConstructorFrom(Functions... functions) + -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> { + static_assert(((std::is_same_v, C>) && ...)); + assertStackState(); if constexpr (sizeof...(Functions) == 1) { ([&] { - lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); + using F = detail::container_forwarder; + + lua_newuserdata_aligned(L, F(std::move(functions))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); } (), ...); } @@ -12177,10 +13048,18 @@ class Namespace : public detail::Registrar ([&] { - using ArgsPack = detail::function_arguments_t; detail::OverloadEntry entry; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; + if constexpr (detail::is_any_cfunction_pointer_v) + { + entry.arity = -1; + entry.checker = nullptr; + } + else + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + } overload_set->entries.push_back(entry); } (), ...); @@ -12191,843 +13070,1032 @@ class Namespace : public detail::Registrar ([&] { - lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); + using F = detail::container_forwarder; + + lua_newuserdata_aligned(L, F(std::move(functions))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); lua_rawseti(L, -2, idx++); - } (), ...); + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); + } + + rawsetfield(L, -2, "__call"); + + return *this; + } + + template + auto addDestructor(Function function) + -> std::enable_if_t, Class&> + { + static_assert(detail::function_arity_excluding_v == 1); + static_assert(std::is_same_v, T*>); + + assertStackState(); + + using F = detail::destructor_forwarder; + + lua_newuserdata_aligned(L, F(std::move(function))); + lua_pushcclosure_x(L, &detail::invoke_proxy_destructor, className, 1); + + rawsetfield(L, -3, "__destruct"); + + return *this; + } + + template + Class& addFactory(Allocator allocator, Deallocator deallocator) + { + assertStackState(); + + using F = detail::factory_forwarder; + + lua_newuserdata_aligned(L, F(std::move(allocator), std::move(deallocator))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); + rawsetfield(L, -2, "__call"); + + return *this; + } + + template + auto addIndexMetaMethod(Function function) + -> std::enable_if_t + && std::is_invocable_v, Class&> + { + using FnType = decltype(function); + + assertStackState(); + + lua_newuserdata_aligned(L, std::move(function)); + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__index", 1); + lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); + setObjectMetaMethods(-2, false); + + return *this; + } + + Class& addIndexMetaMethod(LuaRef (*idxf)(T&, const LuaRef&, lua_State*)) + { + using FnType = decltype(idxf); + + assertStackState(); + + lua_pushlightuserdata(L, reinterpret_cast(idxf)); + lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__index", 1); + lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); + setObjectMetaMethods(-2, false); + + return *this; + } + + Class& addIndexMetaMethod(LuaRef (T::* idxf)(const LuaRef&, lua_State*)) + { + using MemFnPtr = decltype(idxf); + + assertStackState(); + + new (lua_newuserdata_x(L, sizeof(MemFnPtr))) MemFnPtr(idxf); + lua_pushcclosure_x(L, &detail::invoke_member_function, "__index", 1); + lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); + setObjectMetaMethods(-2, false); + + return *this; + } + + template + auto addNewIndexMetaMethod(Function function) + -> std::enable_if_t + && std::is_invocable_v, Class&> + { + using FnType = decltype(function); + + assertStackState(); + + lua_newuserdata_aligned(L, std::move(function)); + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__newindex", 1); + lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); + setObjectMetaMethods(-2, false); + + return *this; + } - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); - } + Class& addNewIndexMetaMethod(LuaRef (*idxf)(T&, const LuaRef&, const LuaRef&, lua_State*)) + { + using FnType = decltype(idxf); - rawsetfield(L, -2, "__call"); + assertStackState(); + + lua_pushlightuserdata(L, reinterpret_cast(idxf)); + lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__newindex", 1); + lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); + setObjectMetaMethods(-2, false); return *this; } - template - auto addConstructor(Functions... functions) - -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> + Class& addNewIndexMetaMethod(LuaRef (T::* idxf)(const LuaRef&, const LuaRef&, lua_State*)) { - static_assert(((detail::function_arity_excluding_v >= 1) && ...)); - static_assert(((std::is_same_v, void*>) && ...)); + using MemFnPtr = decltype(idxf); assertStackState(); - if constexpr (sizeof...(Functions) == 1) - { - ([&] - { - using F = detail::constructor_forwarder; + new (lua_newuserdata_x(L, sizeof(MemFnPtr))) MemFnPtr(idxf); + lua_pushcclosure_x(L, &detail::invoke_member_function, "__newindex", 1); + lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); + setObjectMetaMethods(-2, false); - lua_newuserdata_aligned(L, F(std::move(functions))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); + return *this; + } + }; - } (), ...); - } - else - { - - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); + class Table : public detail::Registrar + { + public: + explicit Table(const char* name, Namespace parent) + : Registrar(std::move(parent)) + { + lua_newtable(L); + lua_pushvalue(L, -1); + rawsetfield(L, -3, name); + ++m_stackSize; - ([&] - { - detail::OverloadEntry entry; - if constexpr (detail::is_any_cfunction_pointer_v) - { - entry.arity = -1; - entry.checker = nullptr; - } - else - { - - using ArgsPack = detail::remove_first_type_t>; - entry.arity = static_cast(detail::function_arity_excluding_v) - 1; - entry.checker = &detail::overload_type_checker; - } - overload_set->entries.push_back(entry); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setmetatable(L, -3); + ++m_stackSize; + } - } (), ...); + using Registrar::operator=; - lua_createtable(L, static_cast(sizeof...(Functions)), 0); + template + Table& addFunction(const char* name, Function function) + { + using FnType = decltype(function); - int idx = 1; + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); - ([&] - { - using F = detail::constructor_forwarder; + lua_newuserdata_aligned(L, std::move(function)); + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); + rawsetfield(L, -3, name); - lua_newuserdata_aligned(L, F(std::move(functions))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); - lua_rawseti(L, -2, idx++); + return *this; + } - } (), ...); + template + Table& addMetaFunction(const char* name, Function function) + { + using FnType = decltype(function); - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); - } + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); - rawsetfield(L, -2, "__call"); + lua_newuserdata_aligned(L, std::move(function)); + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); + rawsetfield(L, -2, name); return *this; } - template - auto addConstructorFrom() - -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> + Namespace endTable() { - assertStackState(); + LUABRIDGE_ASSERT(m_stackSize > 2); - if constexpr (sizeof...(Functions) == 1) - { - ([&] - { - lua_pushcclosure_x(L, &detail::constructor_container_proxy>, className, 0); + m_stackSize -= 2; + lua_pop(L, 2); - } (), ...); - } - else - { - - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); + return Namespace(std::move(*this)); + } + }; - ([&] - { - using ArgsPack = detail::function_arguments_t; - detail::OverloadEntry entry; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - overload_set->entries.push_back(entry); +private: + struct FromStack {}; - } (), ...); + explicit Namespace(lua_State* L) + : Registrar(L) + { + lua_getglobal(L, "_G"); - lua_createtable(L, static_cast(sizeof...(Functions)), 0); + ++m_stackSize; + } - int idx = 1; + Namespace(lua_State* L, Options options, FromStack) + : Registrar(L, 1) + { + LUABRIDGE_ASSERT(lua_istable(L, -1)); - ([&] - { - lua_pushcclosure_x(L, &detail::constructor_container_proxy>, className, 0); - lua_rawseti(L, -2, idx++); + lua_pushvalue(L, -1); - } (), ...); + lua_setmetatable(L, -2); - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); - } + lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); + rawsetfield(L, -2, "__index"); - rawsetfield(L, -2, "__call"); + lua_newtable(L); + lua_rawsetp_x(L, -2, detail::getPropgetKey()); - return *this; + lua_newtable(L); + lua_rawsetp_x(L, -2, detail::getPropsetKey()); + + if (! options.test(visibleMetatables)) + { + lua_pushboolean(L, 0); + rawsetfield(L, -2, "__metatable"); } - template - auto addConstructorFrom(Functions... functions) - -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> + ++m_stackSize; + } + + Namespace(const char* name, Namespace parent, Options options) + : Registrar(std::move(parent)) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + rawgetfield(L, -1, name); + + if (lua_isnil(L, -1)) { - static_assert(((std::is_same_v, C>) && ...)); + lua_pop(L, 1); - assertStackState(); + lua_newtable(L); + lua_pushvalue(L, -1); - if constexpr (sizeof...(Functions) == 1) - { - ([&] - { - using F = detail::container_forwarder; + lua_setmetatable(L, -2); - lua_newuserdata_aligned(L, F(std::move(functions))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); + lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); + rawsetfield(L, -2, "__index"); - } (), ...); - } - else + lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); + rawsetfield(L, -2, "__newindex"); + + lua_newtable(L); + lua_rawsetp_x(L, -2, detail::getPropgetKey()); + + lua_newtable(L); + lua_rawsetp_x(L, -2, detail::getPropsetKey()); + + if (! options.test(visibleMetatables)) { - - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); + lua_pushboolean(L, 0); + rawsetfield(L, -2, "__metatable"); + } - ([&] - { - detail::OverloadEntry entry; - if constexpr (detail::is_any_cfunction_pointer_v) - { - entry.arity = -1; - entry.checker = nullptr; - } - else - { - using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - } - overload_set->entries.push_back(entry); + lua_pushvalue(L, -1); + rawsetfield(L, -3, name); + } + + ++m_stackSize; + } + + explicit Namespace(ClassBase child) + : Registrar(std::move(child)) + { + } + + explicit Namespace(Table child) + : Registrar(std::move(child)) + { + } + + using Registrar::operator=; - } (), ...); +public: + + static Namespace getGlobalNamespace(lua_State* L) + { + return Namespace(L); + } - lua_createtable(L, static_cast(sizeof...(Functions)), 0); + static Namespace getNamespaceFromStack(lua_State* L, Options options = defaultOptions) + { + return Namespace(L, options, FromStack{}); + } - int idx = 1; + Namespace beginNamespace(const char* name, Options options = defaultOptions) + { + assertIsActive(); + return Namespace(name, std::move(*this), options); + } - ([&] - { - using F = detail::container_forwarder; + Namespace endNamespace() + { + if (m_stackSize == 1) + { + throw_or_assert("endNamespace() called on global namespace"); - lua_newuserdata_aligned(L, F(std::move(functions))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); - lua_rawseti(L, -2, idx++); + return Namespace(std::move(*this)); + } - } (), ...); + LUABRIDGE_ASSERT(m_stackSize > 1); + --m_stackSize; + lua_pop(L, 1); - lua_pushcclosure_x(L, &detail::try_overload_functions, className, 2); - } + return Namespace(std::move(*this)); + } - rawsetfield(L, -2, "__call"); + template + Namespace& addVariable(const char* name, const T& value) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); - return *this; - } + if constexpr (std::is_enum_v) + { + using U = std::underlying_type_t; - template - auto addDestructor(Function function) - -> std::enable_if_t, Class&> + if (auto result = Stack::push(L, static_cast(value)); ! result) + throw_or_assert(result.message().c_str()); + } + else { - static_assert(detail::function_arity_excluding_v == 1); - static_assert(std::is_same_v, T*>); + if (auto result = Stack::push(L, value); ! result) + throw_or_assert(result.message().c_str()); + } - assertStackState(); + rawsetfield(L, -2, name); - using F = detail::destructor_forwarder; + return *this; + } - lua_newuserdata_aligned(L, F(std::move(function))); - lua_pushcclosure_x(L, &detail::invoke_proxy_destructor, className, 1); + template + Namespace& addProperty(const char* name, Getter getter) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); - rawsetfield(L, -3, "__destruct"); + if (! checkTableHasPropertyGetter()) + { + throw_or_assert("addProperty() called on global namespace"); return *this; } - template - Class& addFactory(Allocator allocator, Deallocator deallocator) - { - assertStackState(); + detail::push_property_getter(L, std::move(getter), name); + detail::add_property_getter(L, name, -2); - using F = detail::factory_forwarder; + detail::push_property_readonly(L, name); + detail::add_property_setter(L, name, -2); - lua_newuserdata_aligned(L, F(std::move(allocator), std::move(deallocator))); - lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); - rawsetfield(L, -2, "__call"); + return *this; + } - return *this; - } + template + Namespace& addProperty(const char* name, Getter getter, Setter setter) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); - template - auto addIndexMetaMethod(Function function) - -> std::enable_if_t - && std::is_invocable_v, Class&> + if (! checkTableHasPropertyGetter()) { - using FnType = decltype(function); - - assertStackState(); - - lua_newuserdata_aligned(L, std::move(function)); - lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__index", 1); - lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); - setObjectMetaMethods(-2, false); + throw_or_assert("addProperty() called on global namespace"); return *this; } - Class& addIndexMetaMethod(LuaRef (*idxf)(T&, const LuaRef&, lua_State*)) - { - using FnType = decltype(idxf); + detail::push_property_getter(L, std::move(getter), name); + detail::add_property_getter(L, name, -2); - assertStackState(); + detail::push_property_setter(L, std::move(setter), name); + detail::add_property_setter(L, name, -2); - lua_pushlightuserdata(L, reinterpret_cast(idxf)); - lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__index", 1); - lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); - setObjectMetaMethods(-2, false); + return *this; + } - return *this; - } + template + auto addFunction(const char* name, Functions... functions) + -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Namespace&> + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); - Class& addIndexMetaMethod(LuaRef (T::* idxf)(const LuaRef&, lua_State*)) + if constexpr (sizeof...(Functions) == 1) { - using MemFnPtr = decltype(idxf); + ([&] + { + detail::push_function(L, std::move(functions), name); - assertStackState(); + } (), ...); + } + else + { + + auto* overload_set_unaligned = lua_newuserdata_aligned(L); + auto* overload_set = align(overload_set_unaligned); - new (lua_newuserdata_x(L, sizeof(MemFnPtr))) MemFnPtr(idxf); - lua_pushcclosure_x(L, &detail::invoke_member_function, "__index", 1); - lua_rawsetp_x(L, -3, detail::getIndexFallbackKey()); - setObjectMetaMethods(-2, false); + ([&] + { + detail::OverloadEntry entry; + if constexpr (detail::is_any_cfunction_pointer_v) + { + entry.arity = -1; + entry.checker = nullptr; + } + else + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + } + overload_set->entries.push_back(entry); - return *this; - } + } (), ...); - template - auto addNewIndexMetaMethod(Function function) - -> std::enable_if_t - && std::is_invocable_v, Class&> - { - using FnType = decltype(function); + lua_createtable(L, static_cast(sizeof...(Functions)), 0); - assertStackState(); + int idx = 1; - lua_newuserdata_aligned(L, std::move(function)); - lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__newindex", 1); - lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); - setObjectMetaMethods(-2, false); + ([&] + { + detail::push_function(L, std::move(functions), name); + lua_rawseti(L, -2, idx++); - return *this; + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); } - Class& addNewIndexMetaMethod(LuaRef (*idxf)(T&, const LuaRef&, const LuaRef&, lua_State*)) - { - using FnType = decltype(idxf); + rawsetfield(L, -2, name); - assertStackState(); + return *this; + } - lua_pushlightuserdata(L, reinterpret_cast(idxf)); - lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__newindex", 1); - lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); - setObjectMetaMethods(-2, false); +#if LUABRIDGE_HAS_CXX20_COROUTINES + + template + auto addCoroutine(const char* name, F factory) + -> std::enable_if_t, Namespace&> + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); - return *this; - } + detail::push_coroutine_function(L, std::move(factory), name); + rawsetfield(L, -2, name); - Class& addNewIndexMetaMethod(LuaRef (T::* idxf)(const LuaRef&, const LuaRef&, lua_State*)) - { - using MemFnPtr = decltype(idxf); + return *this; + } +#endif - assertStackState(); + Table beginTable(const char* name) + { + assertIsActive(); + return Table(name, std::move(*this)); + } - new (lua_newuserdata_x(L, sizeof(MemFnPtr))) MemFnPtr(idxf); - lua_pushcclosure_x(L, &detail::invoke_member_function, "__newindex", 1); - lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); - setObjectMetaMethods(-2, false); + template + Class beginClass(const char* name, Options options = defaultOptions) + { + assertIsActive(); + return Class(name, std::move(*this), options); + } - return *this; - } - }; + template + Class deriveClass(const char* name, Options options = defaultOptions) + { + static_assert(std::is_base_of_v, "Derived must inherit from Base1"); + static_assert((std::is_base_of_v && ...), "Derived must inherit from all specified base classes"); - class Table : public detail::Registrar + assertIsActive(); + return Class(name, std::move(*this), { + detail::BaseClassInfo{ + detail::getStaticRegistryKey(), + detail::getClassRegistryKey(), + detail::computeCastOffset() + }, + detail::BaseClassInfo{ + detail::getStaticRegistryKey(), + detail::getClassRegistryKey(), + detail::computeCastOffset() + }... + }, options); + } + +private: + + bool checkTableHasPropertyGetter() const { - public: - explicit Table(const char* name, Namespace parent) - : Registrar(std::move(parent)) + if (m_stackSize == 1 && lua_istable(L, -1)) { - lua_newtable(L); - lua_pushvalue(L, -1); - rawsetfield(L, -3, name); - ++m_stackSize; + lua_rawgetp_x(L, -1, detail::getPropgetKey()); + const bool propertyGetterTableIsValid = lua_istable(L, -1); + lua_pop(L, 1); - lua_newtable(L); - lua_pushvalue(L, -1); - lua_setmetatable(L, -3); - ++m_stackSize; + return propertyGetterTableIsValid; } + + return true; + } +}; - using Registrar::operator=; +inline Namespace getGlobalNamespace(lua_State* L) +{ + return Namespace::getGlobalNamespace(L); +} - template - Table& addFunction(const char* name, Function function) - { - using FnType = decltype(function); +inline Namespace getNamespaceFromStack(lua_State* L) +{ + return Namespace::getNamespaceFromStack(L); +} - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); +inline void registerMainThread(lua_State* L) +{ + register_main_thread(L); +} - lua_newuserdata_aligned(L, std::move(function)); - lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); - rawsetfield(L, -3, name); +} - return *this; - } - template - Table& addMetaFunction(const char* name, Function function) - { - using FnType = decltype(function); +// End File: Source/LuaBridge/detail/Namespace.h - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); +// Begin File: Source/LuaBridge/detail/Overload.h - lua_newuserdata_aligned(L, std::move(function)); - lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); - rawsetfield(L, -2, name); +namespace luabridge { - return *this; - } +template +struct NonConstOverload +{ + template + constexpr auto operator()(R (T::*ptr)(Args...)) const noexcept -> decltype(ptr) + { + return ptr; + } - Namespace endTable() - { - LUABRIDGE_ASSERT(m_stackSize > 2); + template + static constexpr auto with(R (T::*ptr)(Args...)) noexcept -> decltype(ptr) + { + return ptr; + } +}; - m_stackSize -= 2; - lua_pop(L, 2); +template +struct ConstOverload +{ + template + constexpr auto operator()(R (T::*ptr)(Args...) const) const noexcept -> decltype(ptr) + { + return ptr; + } - return Namespace(std::move(*this)); - } - }; + template + static constexpr auto with(R (T::*ptr)(Args...) const) noexcept -> decltype(ptr) + { + return ptr; + } +}; -private: - struct FromStack {}; +template +struct Overload : ConstOverload, NonConstOverload +{ + using ConstOverload::operator(); + using NonConstOverload::operator(); - explicit Namespace(lua_State* L) - : Registrar(L) + template + constexpr auto operator()(R (*ptr)(Args...)) const noexcept -> decltype(ptr) { - lua_getglobal(L, "_G"); - - ++m_stackSize; + return ptr; } - Namespace(lua_State* L, Options options, FromStack) - : Registrar(L, 1) + template + static constexpr auto with(R (T::*ptr)(Args...)) noexcept -> decltype(ptr) { - LUABRIDGE_ASSERT(lua_istable(L, -1)); + return ptr; + } +}; - lua_pushvalue(L, -1); +template [[maybe_unused]] constexpr Overload overload = {}; +template [[maybe_unused]] constexpr ConstOverload constOverload = {}; +template [[maybe_unused]] constexpr NonConstOverload nonConstOverload = {}; - lua_setmetatable(L, -2); +} - lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); - rawsetfield(L, -2, "__index"); - lua_newtable(L); - lua_rawsetp_x(L, -2, detail::getPropgetKey()); +// End File: Source/LuaBridge/detail/Overload.h - lua_newtable(L); - lua_rawsetp_x(L, -2, detail::getPropsetKey()); +// Begin File: Source/LuaBridge/detail/ScopeGuard.h - if (! options.test(visibleMetatables)) - { - lua_pushboolean(L, 0); - rawsetfield(L, -2, "__metatable"); - } +namespace luabridge::detail { - ++m_stackSize; +template +class ScopeGuard +{ +public: + template + explicit ScopeGuard(V&& v) + : m_func(std::forward(v)) + , m_shouldRun(true) + { } - Namespace(const char* name, Namespace parent, Options options) - : Registrar(std::move(parent)) + ~ScopeGuard() { - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); + if (m_shouldRun) + m_func(); + } - rawgetfield(L, -1, name); + void reset() noexcept + { + m_shouldRun = false; + } - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); +private: + F m_func; + bool m_shouldRun; +}; - lua_newtable(L); - lua_pushvalue(L, -1); +template +ScopeGuard(F&&) -> ScopeGuard; - lua_setmetatable(L, -2); +} - lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); - rawsetfield(L, -2, "__index"); - lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); - rawsetfield(L, -2, "__newindex"); +// End File: Source/LuaBridge/detail/ScopeGuard.h - lua_newtable(L); - lua_rawsetp_x(L, -2, detail::getPropgetKey()); +// Begin File: Source/LuaBridge/LuaBridge.h - lua_newtable(L); - lua_rawsetp_x(L, -2, detail::getPropsetKey()); +#define LUABRIDGE_MAJOR_VERSION 3 +#define LUABRIDGE_MINOR_VERSION 1 +#define LUABRIDGE_VERSION 301 - if (! options.test(visibleMetatables)) - { - lua_pushboolean(L, 0); - rawsetfield(L, -2, "__metatable"); - } - lua_pushvalue(L, -1); - rawsetfield(L, -3, name); - } +// End File: Source/LuaBridge/LuaBridge.h - ++m_stackSize; - } +// Begin File: Source/LuaBridge/Map.h - explicit Namespace(ClassBase child) - : Registrar(std::move(child)) - { - } +namespace luabridge { - explicit Namespace(Table child) - : Registrar(std::move(child)) +template +struct Stack> +{ + using Type = std::map; + + [[nodiscard]] static Result push(lua_State* L, const Type& map) { - } +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif - using Registrar::operator=; + StackRestore stackRestore(L); -public: - - static Namespace getGlobalNamespace(lua_State* L) - { - return Namespace(L); - } + lua_createtable(L, 0, static_cast(map.size())); - static Namespace getNamespaceFromStack(lua_State* L, Options options = defaultOptions) - { - return Namespace(L, options, FromStack{}); - } + for (auto it = map.begin(); it != map.end(); ++it) + { + auto result = Stack::push(L, it->first); + if (! result) + return result; - Namespace beginNamespace(const char* name, Options options = defaultOptions) - { - assertIsActive(); - return Namespace(name, std::move(*this), options); + result = Stack::push(L, it->second); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; } - Namespace endNamespace() + [[nodiscard]] static TypeResult get(lua_State* L, int index) { - if (m_stackSize == 1) - { - throw_or_assert("endNamespace() called on global namespace"); - - return Namespace(std::move(*this)); - } + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); - LUABRIDGE_ASSERT(m_stackSize > 1); - --m_stackSize; - lua_pop(L, 1); + const StackRestore stackRestore(L); - return Namespace(std::move(*this)); - } + Type map; - template - Namespace& addVariable(const char* name, const T& value) - { - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); + int absIndex = lua_absindex(L, index); + lua_pushnil(L); - if constexpr (std::is_enum_v) + while (lua_next(L, absIndex) != 0) { - using U = std::underlying_type_t; + auto value = Stack::get(L, -1); + if (! value) + return makeErrorCode(ErrorCode::InvalidTypeCast); - if (auto result = Stack::push(L, static_cast(value)); ! result) - throw_or_assert(result.message().c_str()); - } - else - { - if (auto result = Stack::push(L, value); ! result) - throw_or_assert(result.message().c_str()); - } + auto key = Stack::get(L, -2); + if (! key) + return makeErrorCode(ErrorCode::InvalidTypeCast); - rawsetfield(L, -2, name); + map.emplace(*key, *value); + lua_pop(L, 1); + } - return *this; + return map; } - template - Namespace& addProperty(const char* name, Getter getter) + [[nodiscard]] static bool isInstance(lua_State* L, int index) { - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); + return lua_istable(L, index); + } +}; - if (! checkTableHasPropertyGetter()) - { - throw_or_assert("addProperty() called on global namespace"); +} - return *this; - } - detail::push_property_getter(L, std::move(getter), name); - detail::add_property_getter(L, name, -2); +// End File: Source/LuaBridge/Map.h - detail::push_property_readonly(L, name); - detail::add_property_setter(L, name, -2); +// Begin File: Source/LuaBridge/MultiMap.h - return *this; - } +namespace luabridge { - template - Namespace& addProperty(const char* name, Getter getter, Setter setter) +template +struct Stack> +{ + using Type = std::multimap; + + [[nodiscard]] static Result push(lua_State* L, const Type& map) { - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif - if (! checkTableHasPropertyGetter()) + StackRestore stackRestore(L); + + lua_createtable(L, 0, 0); + + auto it = map.begin(); + while (it != map.end()) { - throw_or_assert("addProperty() called on global namespace"); + auto result = Stack::push(L, it->first); + if (! result) + return result; - return *this; - } + auto range = map.equal_range(it->first); + lua_createtable(L, static_cast(std::distance(range.first, range.second)), 0); - detail::push_property_getter(L, std::move(getter), name); - detail::add_property_getter(L, name, -2); + int innerIndex = 1; + for (auto innerIt = range.first; innerIt != range.second; ++innerIt, ++innerIndex) + { + result = Stack::push(L, innerIt->second); + if (! result) + return result; - detail::push_property_setter(L, std::move(setter), name); - detail::add_property_setter(L, name, -2); + lua_rawseti(L, -2, innerIndex); + } + + lua_settable(L, -3); + it = range.second; + } - return *this; + stackRestore.reset(); + return {}; } - template - auto addFunction(const char* name, Functions... functions) - -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Namespace&> + [[nodiscard]] static TypeResult get(lua_State* L, int index) { - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); + if (! lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); - if constexpr (sizeof...(Functions) == 1) - { - ([&] - { - detail::push_function(L, std::move(functions), name); + const StackRestore stackRestore(L); - } (), ...); - } - else - { - - auto* overload_set_unaligned = lua_newuserdata_aligned(L); - auto* overload_set = align(overload_set_unaligned); + Type map; - ([&] - { - detail::OverloadEntry entry; - if constexpr (detail::is_any_cfunction_pointer_v) - { - entry.arity = -1; - entry.checker = nullptr; - } - else - { - using ArgsPack = detail::function_arguments_t; - entry.arity = static_cast(detail::function_arity_excluding_v); - entry.checker = &detail::overload_type_checker; - } - overload_set->entries.push_back(entry); + int absIndex = lua_absindex(L, index); + lua_pushnil(L); - } (), ...); + while (lua_next(L, absIndex) != 0) + { + auto key = Stack::get(L, -2); + if (! key) + return makeErrorCode(ErrorCode::InvalidTypeCast); - lua_createtable(L, static_cast(sizeof...(Functions)), 0); + if (! lua_istable(L, -1)) + return makeErrorCode(ErrorCode::InvalidTypeCast); - int idx = 1; + int innerAbsIndex = lua_absindex(L, -1); + lua_pushnil(L); - ([&] + while (lua_next(L, innerAbsIndex) != 0) { - detail::push_function(L, std::move(functions), name); - lua_rawseti(L, -2, idx++); + auto value = Stack::get(L, -1); + if (! value) + return makeErrorCode(ErrorCode::InvalidTypeCast); - } (), ...); + map.emplace(*key, *value); + lua_pop(L, 1); + } - lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + lua_pop(L, 1); } - rawsetfield(L, -2, name); + return map; + } - return *this; + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); } +}; -#if LUABRIDGE_HAS_CXX20_COROUTINES +} + + +// End File: Source/LuaBridge/MultiMap.h + +// Begin File: Source/LuaBridge/Set.h + +namespace luabridge { + +template +struct Stack> +{ + using Type = std::set; - template - auto addCoroutine(const char* name, F factory) - -> std::enable_if_t, Namespace&> + [[nodiscard]] static Result push(lua_State* L, const Type& set) { - LUABRIDGE_ASSERT(name != nullptr); - LUABRIDGE_ASSERT(lua_istable(L, -1)); +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif - detail::push_coroutine_function(L, std::move(factory), name); - rawsetfield(L, -2, name); + StackRestore stackRestore(L); - return *this; - } -#endif + lua_createtable(L, 0, static_cast(set.size())); - Table beginTable(const char* name) - { - assertIsActive(); - return Table(name, std::move(*this)); - } + auto it = set.cbegin(); + for (lua_Integer tableIndex = 1; it != set.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, tableIndex); - template - Class beginClass(const char* name, Options options = defaultOptions) - { - assertIsActive(); - return Class(name, std::move(*this), options); - } + auto result = Stack::push(L, *it); + if (! result) + return result; - template - Class deriveClass(const char* name, Options options = defaultOptions) - { - static_assert(std::is_base_of_v, "Derived must inherit from Base1"); - static_assert((std::is_base_of_v && ...), "Derived must inherit from all specified base classes"); + lua_settable(L, -3); + } - assertIsActive(); - return Class(name, std::move(*this), { - detail::BaseClassInfo{ - detail::getStaticRegistryKey(), - detail::getClassRegistryKey(), - detail::computeCastOffset() - }, - detail::BaseClassInfo{ - detail::getStaticRegistryKey(), - detail::getClassRegistryKey(), - detail::computeCastOffset() - }... - }, options); + stackRestore.reset(); + return {}; } - -private: - - bool checkTableHasPropertyGetter() const + + [[nodiscard]] static TypeResult get(lua_State* L, int index) { - if (m_stackSize == 1 && lua_istable(L, -1)) + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type set; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) { - lua_rawgetp_x(L, -1, detail::getPropgetKey()); - const bool propertyGetterTableIsValid = lua_istable(L, -1); - lua_pop(L, 1); + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); - return propertyGetterTableIsValid; + set.emplace(*item); + lua_pop(L, 1); } - - return true; - } -}; - -inline Namespace getGlobalNamespace(lua_State* L) -{ - return Namespace::getGlobalNamespace(L); -} -inline Namespace getNamespaceFromStack(lua_State* L) -{ - return Namespace::getNamespaceFromStack(L); -} + return set; + } -inline void registerMainThread(lua_State* L) -{ - register_main_thread(L); -} + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; } -// End File: Source/LuaBridge/detail/Namespace.h +// End File: Source/LuaBridge/Set.h -// Begin File: Source/LuaBridge/detail/Overload.h +// Begin File: Source/LuaBridge/Span.h + +#if LUABRIDGE_HAS_CXX20_SPAN namespace luabridge { -template -struct NonConstOverload +template +struct Stack> { - template - constexpr auto operator()(R (T::*ptr)(Args...)) const noexcept -> decltype(ptr) - { - return ptr; - } + using Type = std::span; - template - static constexpr auto with(R (T::*ptr)(Args...)) noexcept -> decltype(ptr) + [[nodiscard]] static Result push(lua_State* L, Type span) { - return ptr; - } -}; +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif -template -struct ConstOverload -{ - template - constexpr auto operator()(R (T::*ptr)(Args...) const) const noexcept -> decltype(ptr) - { - return ptr; - } + StackRestore stackRestore(L); - template - static constexpr auto with(R (T::*ptr)(Args...) const) noexcept -> decltype(ptr) - { - return ptr; - } -}; + lua_createtable(L, static_cast(span.size()), 0); + const int tableIndex = lua_gettop(L); -template -struct Overload : ConstOverload, NonConstOverload -{ - using ConstOverload::operator(); - using NonConstOverload::operator(); + int i = 1; + for (const auto& element : span) + { + auto result = Stack>::push(L, element); + if (! result) + return result; - template - constexpr auto operator()(R (*ptr)(Args...)) const noexcept -> decltype(ptr) + lua_rawseti(L, tableIndex, i++); + } + + stackRestore.reset(); + return {}; + } + + template + [[nodiscard]] static TypeResult get(lua_State*, int) { - return ptr; + static_assert(sizeof(U) == 0, + "std::span cannot be retrieved from Lua — use std::vector to retrieve sequences from Lua"); + return makeErrorCode(ErrorCode::InvalidTypeCast); } - template - static constexpr auto with(R (T::*ptr)(Args...)) noexcept -> decltype(ptr) + [[nodiscard]] static bool isInstance(lua_State* L, int index) { - return ptr; + return lua_istable(L, index); } }; -template [[maybe_unused]] constexpr Overload overload = {}; -template [[maybe_unused]] constexpr ConstOverload constOverload = {}; -template [[maybe_unused]] constexpr NonConstOverload nonConstOverload = {}; - } +#endif -// End File: Source/LuaBridge/detail/Overload.h -// Begin File: Source/LuaBridge/detail/ScopeGuard.h +// End File: Source/LuaBridge/Span.h -namespace luabridge::detail { +// Begin File: Source/LuaBridge/StdExpected.h -template -class ScopeGuard +#if LUABRIDGE_HAS_CXX23_EXPECTED + +namespace luabridge { + +template +struct Stack> { -public: - template - explicit ScopeGuard(V&& v) - : m_func(std::forward(v)) - , m_shouldRun(true) + using Type = std::expected; + + [[nodiscard]] static Result push(lua_State* L, const Type& value) { + if (value.has_value()) + { + StackRestore stackRestore(L); + + auto result = Stack::push(L, *value); + if (! result) + return result; + + stackRestore.reset(); + return {}; + } + +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; } - ~ScopeGuard() + [[nodiscard]] static TypeResult get(lua_State* L, int index) { - if (m_shouldRun) - m_func(); + const auto type = lua_type(L, index); + if (type == LUA_TNIL || type == LUA_TNONE) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto result = Stack::get(L, index); + if (! result) + return result.error(); + + return Type(*result); } - void reset() noexcept + [[nodiscard]] static bool isInstance(lua_State* L, int index) { - m_shouldRun = false; + const auto type = lua_type(L, index); + return (type != LUA_TNIL && type != LUA_TNONE) && Stack::isInstance(L, index); } - -private: - F m_func; - bool m_shouldRun; }; -template -ScopeGuard(F&&) -> ScopeGuard; - } - -// End File: Source/LuaBridge/detail/ScopeGuard.h - -// Begin File: Source/LuaBridge/LuaBridge.h - -#define LUABRIDGE_MAJOR_VERSION 3 -#define LUABRIDGE_MINOR_VERSION 1 -#define LUABRIDGE_VERSION 301 +#endif -// End File: Source/LuaBridge/LuaBridge.h +// End File: Source/LuaBridge/StdExpected.h -// Begin File: Source/LuaBridge/Map.h +// Begin File: Source/LuaBridge/UnorderedMap.h namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::map; + using Type = std::unordered_map; [[nodiscard]] static Result push(lua_State* L, const Type& map) { @@ -13095,18 +14163,18 @@ struct Stack> } -// End File: Source/LuaBridge/Map.h +// End File: Source/LuaBridge/UnorderedMap.h -// Begin File: Source/LuaBridge/Set.h +// Begin File: Source/LuaBridge/UnorderedMultiMap.h namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::set; - - [[nodiscard]] static Result push(lua_State* L, const Type& set) + using Type = std::unordered_multimap; + + [[nodiscard]] static Result push(lua_State* L, const Type& map) { #if LUABRIDGE_SAFE_STACK_CHECKS if (! lua_checkstack(L, 3)) @@ -13115,18 +14183,30 @@ struct Stack> StackRestore stackRestore(L); - lua_createtable(L, 0, static_cast(set.size())); + lua_createtable(L, 0, 0); - auto it = set.cbegin(); - for (lua_Integer tableIndex = 1; it != set.cend(); ++tableIndex, ++it) + auto it = map.begin(); + while (it != map.end()) { - lua_pushinteger(L, tableIndex); - - auto result = Stack::push(L, *it); + auto result = Stack::push(L, it->first); if (! result) return result; + auto range = map.equal_range(it->first); + lua_createtable(L, static_cast(std::distance(range.first, range.second)), 0); + + int innerIndex = 1; + for (auto innerIt = range.first; innerIt != range.second; ++innerIt, ++innerIndex) + { + result = Stack::push(L, innerIt->second); + if (! result) + return result; + + lua_rawseti(L, -2, innerIndex); + } + lua_settable(L, -3); + it = range.second; } stackRestore.reset(); @@ -13135,27 +14215,42 @@ struct Stack> [[nodiscard]] static TypeResult get(lua_State* L, int index) { - if (!lua_istable(L, index)) + if (! lua_istable(L, index)) return makeErrorCode(ErrorCode::InvalidTypeCast); const StackRestore stackRestore(L); - Type set; + Type map; int absIndex = lua_absindex(L, index); lua_pushnil(L); while (lua_next(L, absIndex) != 0) { - auto item = Stack::get(L, -1); - if (! item) + auto key = Stack::get(L, -2); + if (! key) return makeErrorCode(ErrorCode::InvalidTypeCast); - set.emplace(*item); + if (! lua_istable(L, -1)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + int innerAbsIndex = lua_absindex(L, -1); + lua_pushnil(L); + + while (lua_next(L, innerAbsIndex) != 0) + { + auto value = Stack::get(L, -1); + if (! value) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + map.emplace(*key, *value); + lua_pop(L, 1); + } + lua_pop(L, 1); } - return set; + return map; } [[nodiscard]] static bool isInstance(lua_State* L, int index) @@ -13167,18 +14262,18 @@ struct Stack> } -// End File: Source/LuaBridge/Set.h +// End File: Source/LuaBridge/UnorderedMultiMap.h -// Begin File: Source/LuaBridge/UnorderedMap.h +// Begin File: Source/LuaBridge/UnorderedSet.h namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::unordered_map; + using Type = std::unordered_set; - [[nodiscard]] static Result push(lua_State* L, const Type& map) + [[nodiscard]] static Result push(lua_State* L, const Type& set) { #if LUABRIDGE_SAFE_STACK_CHECKS if (! lua_checkstack(L, 3)) @@ -13187,21 +14282,20 @@ struct Stack> StackRestore stackRestore(L); - lua_createtable(L, 0, static_cast(map.size())); + lua_createtable(L, 0, static_cast(set.size())); - for (auto it = map.begin(); it != map.end(); ++it) + auto it = set.cbegin(); + for (lua_Integer tableIndex = 1; it != set.cend(); ++tableIndex, ++it) { - auto result = Stack::push(L, it->first); - if (! result) - return result; + lua_pushinteger(L, tableIndex); - result = Stack::push(L, it->second); + auto result = Stack::push(L, *it); if (! result) return result; lua_settable(L, -3); } - + stackRestore.reset(); return {}; } @@ -13213,26 +14307,22 @@ struct Stack> const StackRestore stackRestore(L); - Type map; + Type set; int absIndex = lua_absindex(L, index); lua_pushnil(L); while (lua_next(L, absIndex) != 0) { - auto value = Stack::get(L, -1); - if (! value) - return makeErrorCode(ErrorCode::InvalidTypeCast); - - auto key = Stack::get(L, -2); - if (! key) + auto item = Stack::get(L, -1); + if (! item) return makeErrorCode(ErrorCode::InvalidTypeCast); - map.emplace(*key, *value); + set.emplace(*item); lua_pop(L, 1); } - return map; + return set; } [[nodiscard]] static bool isInstance(lua_State* L, int index) @@ -13244,7 +14334,7 @@ struct Stack> } -// End File: Source/LuaBridge/UnorderedMap.h +// End File: Source/LuaBridge/UnorderedSet.h // Begin File: Source/LuaBridge/Variant.h @@ -13275,7 +14365,16 @@ struct Stack> [[nodiscard]] static TypeResult tryGet(lua_State* L, int index) { if (auto value = Stack::get(L, index)) + { +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif return Type{ std::in_place_type, std::move(*value) }; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + } if constexpr (sizeof...(Rest) > 0) return tryGet(L, index); @@ -13346,7 +14445,7 @@ struct Stack> if (! result) return result; - lua_rawseti(L, tableIndex, i + 1); + lua_rawseti(L, tableIndex, static_cast(i + 1)); } stackRestore.reset(); diff --git a/Images/benchmarks.png b/Images/benchmarks.png index 660ab8d89363d0908cb7a83aa800f72e267abebb..d851ad40fbaef135eaf1818b225a2d2fb3f9b63d 100644 GIT binary patch literal 222518 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 z zXn_^C`cqd8!*ehn4`vkT4R|0`D-edJ^*EOjQE2@DG3um zcRGd!ksDH>O0+a>lC}ERKYn~h^Q{B~l%e}uc7rGPY$hs67wKt3hzAjFuIhEwTTL1G z_;rM6k{B`=@0$fAue57k{k_HgJ#H<;kYdz_sw@1az05BiKbOqTEJowzNv~luW2Sns zS@t_tjpqSV4C6~ZuM%;>7;;0{)bD*aXbHSHkfc;Fs0n7ovN3WM zO2GskY-^brLJzE09Y^%XNOJLlb&X;rl%5l{h-Rl30QmmR_L5iHK{?m<4j-1z&hn{yzuo-AF4Jbc0^s5&5nLw#9 zG4WWr)~>(^wnp|j1=PlPOF4e~#Rj}YFI@GNOPcB`ZqOI zl2S$5Fa{l7!rN!2`Q%B%5fOXS{&rFA%JZRD^th8hq@5Oh6lw14Z1}EEXs$@P2$AJG z6606p@;sGKQ(l9{oI(C8H9oVs}ryTh%q5^glBQ_MVG5MgffeN zBOv0tUdk3gi{O*qKN)j0PRS1VV7E64^Baw)c=&kIcC#uN`*LiQjEnqNeAy+rfm2-r zuWFX-bh9KWx?(i&&>xf?`C8-W-ReOroSd5);8PNB+D)VdPQZPqnj;vr<>Oog3%~9he&km_*pvs2N!#N*N z@0r@@tW8vnR$@Q69K2iH8tY?4ge;mM3c9lH*Xg42yWz~A}u0?D==j<%sw~wz*=K!`ByH5q_LNb8rk zJB~-nECzyY5xO7aQ)eK{SpGR%yQfwvpp#c*`U(dRPseO!!*iKA~ zOIuI^rDH?*PYqPYh|ve2t%(V{($mxTx;k7mLt5M9%-t&7$opD96$@|SVXOVeDoj4t zFXv4^=AS%;IdzqR5z>hhr|I%8N779Pr~?3n->r!mKkd-!(2#nOQxx1QK@3O3dvG@W zD9&5l;9XSI?xBf6?Vep%7vDs((@ri7cwC6a*+KV!qwq1apWL>uugkOm+1hnI2X4EA zm78EOC_My%h3j3_j$_?@XRTx$bN1W3xsLqjj7c93?zE>0@Umsf2c;kFZuvpx^iSQ* z(V^ZE(gv+>fbrl}6kT?Y*#KCq|2Jgr?iUu&%!pj#Yzr;-M!YH;)1o*p_uosY4_2P9 z46=pUl5kDlwBWW1@z5Z@fbg&d!5^vb>L$;gJ^TLiANnM}6E6w{et9|%RM5Nf3mTWN z@6>=u=5JAOJ>#A1xZFsR{e{Q*&OCAgz5R(d{CoNW@qWnNR9eR5= z_tOQp0;b)vX+%R6P`%D<^!yW$yqJ^|xEcIGH&9m}IbFJ$HJ zXx$;TUHlXN-|NtZ=Z^W~U#_DFN8>HB61MAQjzd*F?Vfw2?DsUz#_F?N?Rk*4AJx7) z=PP}qo60MK{P1DAUEn4T+&6DOaY8v>117UAw#wj!Fg~QYYTFHk8sLchRZtC^=HNGO{e<3af?)Qmkm(ewtr0nK3!03n zXop`#X~iNVUvyCjub(PzBb}9YKSyP{!(tUhosC3ORH^7vRjLfqssYF5bFk4Yy6|T< z>={AuwYmEk_hciq6ck6D21Z6j2JHqyyu93*e>TV-Bkp z3CIxA{h{IEO!V>YR=r-6mMn8{U*Av@)ZWYT@Tz1{nNe=C3%Y6iv_+xK!e%=!w+p!a zgu?cIB$gLAYFZ*>S^da`$>D3`HE<8EP|IN9YU*EHhqjtmI+}MWGX4h{ z!Bhdt?Q}XzY1b!$z;iFnM+9ZVjdHY~7z1<~Ml1%MC;{QZEa#oB7R+k(jInF|D)`v@ z3K^0B1Sj4er~`llTh26R=r8}a8;?J;3Cy0=ULA0WkSLaCWV}KyUGhyL;RU~S!jPaW zNno13x>!d9rJ~lGA{;Lq;7rhtnsDItEEPQ{b&?%-^nciU>!>dG?Q0YhMX?YRBvnLO zQaV+TR=QOLq`OlQR6s(cOB9gqZb76wzI1nY$6XKV*6rEn{LXvt9e3P2?)&{`@3Hs8 zlb={?t~uvoohTw?NfBo=n!vA3z%32|ra5+VyfNyKz9glSjR1eQ_HI@xfCn|5H((x%hq3sx63 zK%P;D9q0agPdXbm(;eH*Nt*-B6JU(O)(UwE3aNK=arD0AegLO5IqPqS(9)zqzOkHy zyL$~U#rso~5bxfnbCnps){8m5&?9;Ep)xp|7cS#dT%0f{YS-%rb(tOEUqY(q?#`Tjw4Unl@lbhBktU}R~_(xTTmNA zI@nR~7v^}g#C*2rhGMqvd%Vo4APEUnKzF82e(jDyn!M(DN4wt-T$}Br?zdajQkg&Q zXp8u@4O~JIt~%;rEWK#u5&Ii3h?atS0Qvi|vDGs_?hF3G;&1@>ob*D2tP2}~_0X>T z3Amj5ne|^dS1Eja2Kzj&`ac|X0**4uy7bqn|JOXijJ>?W^#6WVj4AvRr_NNOh!)l+ zT9PZF&^WOkVKv*^@Dyaxi5%~(Lsl$vo7EyJzDj2k+SRF-yZWB%mWo%Y;V|bseu=(f z**Yn#%>D2d-T=h`na%KVW#!Ed+SfehLqi_Mj*qB+ZGXzVAxH2^-WC-;AJN?9Rwk*^ z3jA!QjEO;LHztjgQ{8vgOse7=Kf=(^_lH=s3h1ODX;(*VO5S*S`n({9q?*uS&Fti< zQ~v&8c7i~`O7B?Oac2d=`AdSFp+UpCO)xSH2B_+hcKGmw5T=c@^CkLlljXDc5)yw~!&tBo^Bv&qY$SZt>4=G?3Se)U?`AVKbSSc; z$Egq3J6sxF*Zmat95WPWUN||aH|?;F+d6mT!XdFKR0}TN3`}sj)kSv8-Jg`9;_0EDAoMcxmP3pL`C$2e(|siSN<;mKcd3- z9rWaiza4#f-unTUqBPe(13uAxVAeC5?%8LaA0`uLHg6XDo>G?AgN(o*wzW#whb zC2)lp1ptvtF{@~?8EY&jL$~DtSkheGUZ3?n>9BhPaGiBFKO|{`av`5FB|UvL2MU;k z?}tc*?^{~t;B%P@`cVhcLJB>Lb67)wXd2R05uO-q=P;yPeo47U0P56w-&(xOUWt;9 zL0t#fUUfPgQ*xa4J2|GaMsISH2$NYEK39WPP@%qJg$|e)T+cE(wR{;V67BDCj#;%& zTQ*m>huLij17U0|eg^yVSU@TEZaJ_2ePjNyI@#V-NXUWgIrrq0 z43*+_a&qz;9nC2p!Np{$$tF41nXR*lM-Vc(y77k0Qi&s8(v*Gy^FC?v#Yu>b%dDuy zeZXv~6mC@{n8^a4!Ng9LaU*ZWHC{A_)nJXWsh~&G~QU?#kvmGWM4Dz+6=`F zvY*B_H%D55X=r?s?LxcTyMTa!Om2i+Mj;iao8&_t*qn1;ICtzD`{<{2mAWO%F~2_h z&q;+(hqv0&?7B6uRwfrGtrtljMm$m9+*&@nn@5mH+?pDJ>W5UfyCLJ_f{WrDo#*4W zB~mbrt38^E_Y!n$L(5z%R*4Vt!+>eian#dns$8f+cq=%SMQ1BEMTTDqvzSsQjtUYd zCg}tc5)$D0-JNWV%9rJli#3*>G<|SZK3~JRHk4I54i%>p$4g4ggBS`V?>=jciG@X4 zQHDzOx+1fI7x2_ZTuD&(`S=P>+PV4P)EW~Uyn0u(rgPe$NTAPplsp?J-j0Ugrhq3t z^Pn;xU&uvj1tmZdt}^Mg`9R{;`LZ#LF|ysR%_ye9!5lktmd%`LJ;LPK$nEg)n)ZUt zlYf;=(6}Iw6jZUBJCF4ur&%bL1l4!-ZBUliT|(d2LPzePm`+g2#_GpqXov;tmU<^p z`1Ls~9|M50el53=f&+6K@>s{(rfSon4epVoZNl{~mH1Zc^}^DZ4}O4B3fLC&{Ngrx zL_qO8a$J>idwtG(kf@LxC?BEg>?2)@QD*t|(Obsb?R8^I!0o!9TqGOd)xbd9I>jsS zI)*W&X^N{sh(GaZ{%AdVl;|rOdaF{oWIGD^Ja<-Rj|BJc8xM(U^0l0C`ivJw(02qXEVJ-79` zbhg@u2pO2Qq7zxVi?-R_$sp3Q1%#=dSSp^A$bLy(t9$Tjwqptyw}`N8AlxKGA=T^R zVqU~Imdv#;(H@pi+Pa&+9`QL+J@`!Yg*JwbqzCa~wkva#(b`goN8X&x1}?7YDyBw1 zV~?4XyozxrF$hV5Pu}*YRh}q#E8gc&Csme50rpS`+7T(7tpXN0=T)|24=txys4U_V z=1o}}5lKFXUtaY0Rt-Lf->Jq+{xi0)ZN%VgFqEKH=Gea@-CGsMBd*78N+XhnlF)u!_u($ef+s=nr>V&aFcbROm(*P+Wi^IF~hm-7+FBK z3^qg0j7o^l!+a7Y*z^QGa=1zbPnj|%uCiymlS@RB^t=1_E7ZX;Cwl2&xGeVN%TE<_ zw9Wdy8Wr30x@bVdlKFWZI;-E!sBUu+jJGUXX1eGQu%z>CQ2gWLA9`zJqw7FQ7HpRAFO7*8wZl7IpUe>t3 z>+&{A%dI{7LW7Z=UQ-;la?+R2Sp>7-WGR1^9@H&b>{A1HmU&CMZLvchgmp_B;YkW+ zQ^8*1-ETpE8)P1FM!pv%dA5J@bVX_DDd0rK8BaQr7;kfWR*n-D&TehE2lB!A2vuRiKJS@0?yh?zw5B^*#fD|Yz;;(c>(R@aqgJ7)_EWuml?eHhm( zCtyDh7DY;}@$XWFv->o5Yd@B|-ZwOSy{xIF70b2G@EIo!{1KUT6vRR~Xu6qL2Hzq9 zI8pV|GASDc zARpI}(v1z4;xpE;S#Kuj7?l2C$ zd2K_qZwzqfTwWy&4k(}Hpy7Sao2EZYA&IL|S|;MHLEZh-io?gJfN~p{;7|bKsrLgr zsA?}AedY*M9up-56lL%Htgq^}Oz}8`O^-wlr`gX^;O;J49Hd&{J>?572Mx11+NZ=F zJmZI%Iaw8gjvqhD*4?2YO&+jXw8a&jlpWl%7D%?v&o89vpYJjWp_MUe*E3i`F8)#` z#>3>#(ox{`#7vS$%Ub7FEv+3OVLBU!ZOj~{@B`mL%t20J9LX|DxN@lD_3xm@%>exA zcUr>t#1(c&@AK1Rs&Uzlld@VqD(-Gz)4=+$`El?GpKU>GPV?&VOzDwHK{ni0$f0d^ zsVKU((?)y--^y@rW$(V}b{NeS*f|rzDA(-wS;0>BvkJd_v3X|f&DM$nqcVkY^E~}9 zt~Jko2hl7&qvU7qdd*6Fd}7k?Lk%vO&Fd+cPO-gBT2@6pZFRhLJ0mn9?y_Y0bdLSf zmuaqe?uI+fi^K9sOaZEl25t2jaskM$17>3F4+srACv`&}+=440S8L`)EF^JYl+q|s^x6h=gyRYxh>cH>NI zPi^(Yw^91#DJN~mFHf2hu%)O%Ntr@$E|8 z;ADy~&T(EU%%d*2J-C`f?@cT9~Rs-o5rD6i=7=#1k6DjDb7PCO&}SSXk~{c2>m9?SVLsvso*- z?H?#QqFf)K2xgkbmz4VbhI8~gO|?ZUeIeR&t#!*=_lCvVMwtvE=-HeR7a2pEfZHun z6G$R}YG_zfc>8&HVz7+MX%>-NfVN1}v>SbG-WRE7W@NRQan<<66Khf+?2%0dq2nN& zqnWontc!KwUA~3$;bSL%Arc;}hJ(%K&PQG;%`a;%kJWnRrbpkept)4jyKlqCoS;nV z>~@hs=R=}$VQEIWX;gQ6g0lFW-2%CMs;>9ZW9Y)OY?!rdW_af6cgt7=NP~`Bj8aIw z19z)HG=9_sdkb?cB7;KpnflAO=v56_MO!EE>PmKk%Jn^^YLnjfR}fHhugOlL9A5XV zFEFHm`<}@a%qvU`&%=(}2hs@Fd?jF~_;k;n8gb?&w4Je%Pq)s+XR|Ch>I%$%CTVSM z`}HVLTs7+SLji^wiPnI{MW@Gj76^cA%T^}Q!7@GwAB{?pzCXht5#m_Zq`?kIMDvUde9g^j*r2%_Jqp$|C)B@!tcj9jN!un;PMMP`4DHduFbmrK< zJ*mn>MJe+!vS72>=bb+eZOFCI{MGkh2FCA4E&s?v+*g|^(sJ=(8fX<_Wj?Lp_7)wV z-(Ls3sU~#aCwShH2P`aWYG1WrKlweI;CUXAc$#4LG^f@%I$&o@8bu}E0;kL;FP!I# z_gbY4t-ulk!SW)653sx}sF!2y^nelqFxF2m#I$Y(c%3ugJCEsLKG$LX&GSK7Ezp%E zXy(jf8FiZ4GsM~k40byDN5Ed!N{?G->mt_Gsk}D`X*5Tz0y0V5F>}*8o(eYSGkMAA zXJ&lFAR9$o4^*7Kjiu_a3vk*)CbX8T3Uy^B?8kT_tO_1^&CJ1h{k=A83>B)~d`XJ= zLWp|mbne&~2j&UQo!eDMKU>?HDm{~6GiYZF+-Z=r+DsHrE9e6=4kMeSOd-QBJ%RM< z%`C74hs&;xqZ<_849#DAACx&QoS27sebVw{?1@k|tq>~ti6UlI{YcQ?ekLk<9LA97 zrH5L)x?Dimku4j&%g(#h1xcm|Wn-TmdH_xa?j-J*<<7>eKNqE{_$!xM)iVyN`bE2k zur6u~PHf^2yhnN=%*a|EMUxZnulwX?F-_Fu_!gg9I-~c*qbtAAoL*-(bkn+Y2o$z= z$eRo=r9$W55iGi8OM5^tTe7^Q%Wzq`p`kSU{kL{MYdi5H@6u8brN}6nOzQpPtA}@2( z4CH)-Wp|tO5#_I2M!33FNuk|{=?3Bn(Y3#w18oKy zpj2W7cW`YPA-?t65r6H;cUGbM!G?@hqVgQh`|-t3X!C!e7r=`H)$JvhT6gz?zGK7< zpeis~dHfp+2QL9_#`iA>ou1SO*mhCT(IbD**DCPQ&}@E&4am-khDLQBAMg66yn;~P zh(L1THiCW$BXr@8XnEilEx^VLNYOjzWz8DrDX_bH5B2*R(C7uB@(+)kI*L@rjvgPI zcOZ5&i4|-b8e0Zp150DdnO{qE5^?<@O!VZjV0>710qAM zEt2vI0w1WF0clZ3eJrEuU6{Q&U!{y5yHM}e+eqD9j0?E9q*dfq#(=B`-qy^c3gT9z z=kU|9g19eIZt)!;i(e~h2FuW&4i^q?Mr|xOGRnHIA}>E9VSa$iWLb zc+HNdRJO%>MOavDzFpkhMR}`mCjd4m zBio>x3aQn-t`tmmycKQCG})8rFO{UA%*S6gGVt7v-l5(!5X`l*XF)1R*=xP#i!jGP zZsK<)G+%h}0_o^N1ph&OY(NvIM(F3hKkj={0Z~H--sXSH>#3V{kAEpH-P*G5(-2e){AXtz`itXNurk(-Z zb0k}}$b@|kFDoz8=&dXBdM-rYo0>iIDh*9g%M%8o;b@(II9RwU1SPN1z1^D()ztz7Fm zw&wMvF9>{x3qG64-7P#x+N(hb2@ETmNw-HS>9TPs*ooCl`3i@2W79m8($61%O#a~G zd)=-SF_2_G?aF{k-d%t;N3hSegFsrlHHj?Vj}|0UsKE9ZC|c7397nH{XCoKOW;#g& znoA$)8vn|%S`jTsy-ybop4nU}yFCIe2uQD8Q}&|@p;WK-^G6io5)q1Z4fvqAj8xqx zNi$nME}yQQYc&Ifc;-aqOoC~2gTk9{$ihuz{m$#>M zPCnEW4ZrH(zLvyx41@kJAim_70VuX^aNx0QSjS$Jlx|OyC-2VIU+a{~5Jss$4W4#K_TLGXSCU6dR1ld2(1zO9R%3{HXifx zZsejvb(we(6(YM~AWpa}+4j=X6INJPG1+ukKmx>2}3ATZt~yiL0QkTFvVRl~xV-zz{Dk%A-43pJs|g>Z&4v zjAA!-Ru%oa4pg=gIa~(|w%m3fX36}^H1Fr0&3;-JDYM%T^IL6V1x#z0FAtQvM8Fv#7_P_4n7qKZStAS3(b@Hvm>MywPi|r6dkvnpFk55<*vrTd0+ICwA$Ocb)I+~() zDIN{$pds%?d`0zDVaDvqzlQ?@rHp3HEP?FH+_B2Z5mHGHl@b4C-xt8g5KPC>Q}4)6 zeztTSQ2zH&Sjv;0VdW#o`~X~$YwFeSOuOZit@mLHU>v`H+BuKOEfboxiNZD z`KI$A#z4ITZ-EQ=wiL~XuTI0RDJGpAfYsem=DjZT8m;TFSUPm-pR!R$_zed<52O?s zHBXZ<>d9Y%^=k`0$dWRrO!e4zC6HD9Iln&4Djnf$x9!T6{TqQb!{ROv!-j^%l?ZWE zZpyC55B9&vWe{x}gytgNP$B@ceysFDF({`c$mLZ^fgsno5Gd2a_x5)Z8BD<-J+ODn zIgEo}Jj(Yy-U+q|1t*Hz(^MK}`4qQ^8!OiCtN5#HXb1|WdDe*cc_gt<;GOAPEg8OW z@$&WQ_M~^<^%wf=QZRXQg8G&0lt$FDzb^uERHPa0cIjq2^&Y1F&XQbknsr0CgFatge9EgQ0$+%b^?6)88CTXO?G@cFF!%1{)>92q#Rj)c zC|MPjeu0<*4B=X}`jZGz03P#{0_-LzmtgrOJte!u`PE-la>Fk=ux`j3H2Qmxb(>D* zIcYDJvfdyDMB5;H7T7?vQ9}OS;L{K&6CwWwwqmgQuu&$&zi<^HIu*5N1?)9Sq=@Fz zjOi%vH$EQR>3sL@-POXaGYeL#U2K_hsV%3nGYF=Fq-&y{L{&o{i*c&>&r8h^>4_S~ zCEg>*IHpy6NLIa5rB#*f3?i+SKe9B&zVDw*N&ei#`MV_!1VIo2!K8}X|Db~ic7aZR z54I16=?!VA&dkHTlO$%D%TQ&qk!QuMvoZ{We|Ve`FKEn z5mpZ4y2+z`6{sQK_H{z&#DhUDPN&A|7xGk|$px5BH=#D|?mUf@N>r&i8r*fy6e$$( z6+8e76<*o?n}{|rLB6m@iVL9NZH3?HGoQ^0zPWGpNj*>jGu8{;If1}?xerlK6kpWk z`@*6=4czvMOU+=x8)X%5Zd_65S)E3@ELyJiR>6!Eh(JI;pmD|+)qg7}4w6Y>8BXlpU1={DBj2aQ9*hJzgNn8kvZJhhtSvw799?%aSH zJCGRFq1K`f#nN|ylZBU6fM04lZAC#WA}s6+^Q0zWa=#i%$$lWH*YBoPOyfC&-$53d zx8fBC>|8X-bxEQYsDG$~Mc!w-f)%sDXv|y@wCqi7v!7ECV{qHS8#n2$T)`%XCUfMSJGCn;z&R>5mPdTD2qJ=|Eh3 z3QoY=Q;4%0o{P+7Fd=9Vu57L_@Pbd18Q*G(}dcKjH}VV33hM`7I>dKHrW!4)?oi1khknNM8ec#Mb`)*H+2O{cbi-47H;<;3{?aE< z3Ys`l@P&9la<~kqyo!FV{Uj|<@*(*M{2#9gZt%aR#{5W`Un6p{Bg740s$=+P#(W^0 zi~U0ZM|$JypPDZa<{e1XK7vH;d-tvHuj-)5<3RtZduX({DYAE-z6VX=Z{n=~J2Y^A zX#;>Z_3v9sdJiCtElZq97#J81q)t!-W8F%@`8@zYLj-r}*MIVnK&;RpWWa7 zeM`;nzFI&s*K z9X}rUxAo(v%=E8_YW~^#L0P&FtmX~|!haZr|Dl5_VnWBT zY)uA!b&7x#*N#&MGmoq$eIHtAX!YlQl~BQB?qCi7_q4hL!S1P4C;%PgMu(mkJS|ay zlG&fs=I;af|1`Jm`vQWtl<&T>@~>If!A;bp{JaTV+++K9&sPd5^*lIpCZYV-qV{tU zsQ>X{em?R+x7Y}@iu^$Z|Y!5$fUmv_#Sg)qY zygoJ@{YH{4Zbc>h{bVtIKY*E{KfBmhxL&*J;46NPW{_+q5Xag+QDf1dAa{Eg}-kkKMwRN zhT;{#ys$8_ociX#BO;=!p)!9Ijo2anz?MqA#RbOUkAq8aKbGHwjQkLSQ{1{$`Omw< z&xP56_VU53ZE|)C_{Rn6Bcyy^J5_$(GPnjE{TVYOL+QUA0_U)>f&)?PhtMkfA;x^n zWzd;Hl5BBc!3n1}bb%o`5Dj+9n|eD`2vT=!tFmC@+bvwKA(V+dK9YnV+f6Oy25+v1 z^sjb;b6dRb;*&MbReW=y#5Y43cxkiiYz^zRBmV zL2o?Y#uP&U*1_yUKpk8~#>TV>(Rz$9_4Y&{*69`SB+!M&+=H>N{X2qS+ehx_TwJoh%p3b=Z~ z8nHR$fn-aUQF&VVl0#X4gYh-&&c2hDLCP3UR-*~d$$X0c5mUmi+y3O_OY@dA<2TiZ1c+pllKC~^e?3=a^ z&eo+AFIw=k-Cs4tT^)Yi!gf5@E@3=lhl{Z_K2NmYY}i^JGUk(GrXn*v_E$v`LC`J# zS-gy0;I-Vxr&@@Yu2vK-9-hXx5dMhQf}Oc0!4#6D zuNrbT3Edvc;qLV(=*|`dB5oAm-^Mc&-adL9M-WxHMqP2DB6AR0GGF=S*zOPBRPrn+ z{AKVzT;}kNmw0f7fP)xXI=pjtH(gE#Sw&S*VLoEKGgibH((0A^_|w=szpxu%MC9x4 zE`kt*q3@SCR0yn>EL{YhIyA;QpyuC$?*vZpLWY8`LBBVj!0qs3&SG%EC+BK|I^z~N z_M2$zk-;Q*cdt$XaV-^s#U)c2WZW8z*U#BhJ^iF^>^xme8d#u>FnG z=e}q`XNbD-NlCJ>cDl-0HBNs<-4?%~uvtc?9Cq7{f!O(LAFp##i$ZNdH%s=e>qXz! zKECF0PbcZq^xSN)`+*ZzH@?8PHcY={)YMfl#veC|SWNDUxyt%uAsI zZTN}vcfq%Z=|+d7^%cNK+g)5M9>1fI(A zBw7dUkKsEGm_kW*Ar{<{m)5{*)7|HJ8#sDWh!-yS0k4}ELA{p8Yj64MOqi8P`&TZ0 zm88b;l_@;%RVaR}CZB8jCN}|Cr8lly1GgyzW$_w167DD*3Ct(7R03N<3UN-`&#^<2 zUG|GmBk)3qDoEEaz#7u+)-vM6`HLUV7f|`^Z`N4l_L+l>LUdwy%8z}Y45_L3huJsE z%SW#)axL~;F>cGuu7oQ7q&WfR;5}P95x|#7D!d~H`@wZq^KcX>6{gtMkA5JPzYJlbF#W#47+mIR#B!)Rfebap>$Q zA`zW)HVSi)hhtFJ|g5O;FTk}0D*TQghnrVYpBSF?;4w3{Q$3UyN_(50=d^RyV^ zx?=!!5Acgkl1>GKUJarZR*qD0@3C#r*yqek-ih3t>AE0Ac|_Qk=Gv9p2|n&CrzIm| z-idLArKH@VwtfFDN~or*ZBC7Ia$^yTgz7IY77OO{&nBd3$HJ2e$Dp5=%Xx15ZRgFr zvMTM{QD1xk;J>lM^vzbkF(wezAeNzH`dKHH6>;A{>>$4cU%M=cSFp3u>#skZqkh*Z znt7HzSSZhdQ^9{~xRKc>32{gO{s#S*P#u;S##$vX%R>lF)Akw4BVcQM!J&4;AnjzR zl4p2x9G_i5QkzpQE3(LSS~+Z3aB*k!Mi+}^Trf+Xa&u>SAaAkrlqdRmVA8k+*j=wr z-^6M~@1< z1tL52j)sN>3Q}5=lNwj-EWx+Lw79=fg2B_2V{oUjq?>8wxbAPz$j>YSN$;d!)ZdTq z|vQGkX5MW;ZL&}NJ&kk0HKl{}-Y z42|Md0nfZwG|-2aAzUq((H}gdbM~7t*cNq!QT8<+drBzn77TV2TGjcf+5UXl!%kv_ z0w1jt*))njox0=)J=sUhZ1Cu{RetG#7(ez+9~+g@%o5AQsbd{~=jrx9Fvlfkd2YzQqq`q5C;>Iq*L z=Pm_?n>RZ$?Aua5``UMDNKqckEdNZYw>sI?*V?qy!tkC(6(V9YSfz-+T?-d$$9kw^2fVw5D}@mN5Du@=FUkYf1M4oIXK zBq>jeLtXV^o71?wQctYa*^YlOBntT+K5dNTwudzjjCQ16t2ck+VL0vJ$!XBVtv%Ws zab!D`q#CqztWMZbvkc*u3(U}YTw}5|^Ofm*Tf<9AaCUzW+)wc=gI?XN7PG!qu-FP6 zP5(4Yo<7_m(BMjoj!CLqRukj1-kGB%yzGswtk%dxTsyUvPOmq`-JD{?fSAZ=osZk@ zjI^SZFEaIJbvg56Jm=15MR3t3Lm<8(96zhnZK&<642*^ziX);?9|RsnN5tw+$LmEO zj{&ln4^RR^uwQ;5iFh~hP&t3E+T%Da&7nUFoOKT2n~fY>vl{lcCj~mM-Lo zx46ct5HiKZUfTBYu3Ip%{dN}C^3Ooq82m{b#T9Tz?;r7obSljTWj?MBls$vER>GWR zh3o+lRAY-{V@ovVbS>V?h=_=2y~wQDf*cEOE9g%<8m4$W*p;RNPxrQRZ-bs_PLJWs z2YEzA&x9YydMu1>C%>wRArjcI;|?5BN%i*u7su*E3$B~9>=w5VMI_b4v^gZrDoN{m z@ahvyPd00_Ib0_t@E-y{$`2w=)QMXyjSH`ZgGE|gT0d=x$IoOa)GsQ5>5j-50ybhG zBwQdq^f1fz*nM$f2LITR;fgx7_D2mb&FG{>7h*`VrgxvM4!ns&iC`U-agEjHgVLor z3;!KVV&V4`dTZpYtVqv-M{yVgAOhOeKK5*sf+^&&)g}hrje%}xbCW*%nM*|#`=#}^ zg58vYQH-=79@P|^U%rL@ZPjVH##emLjT6aTiYACf*3OkmkK@l3 zST{Na30QUf^KKQ7k4bZs5m`Mi}{|%T|Q${ zyfM=k!{X9T-RkxS62;LfYj(U3lL1XBVp_*SkGmp%N{GJ**ge&)DxLu}+X`H4{vdO$^Cfh-?YwK?%BaB$)!N^A;gW<3D{2g?c6 zG%AVy%OmJc@i0z=)kINf9e3Q53w`S!;h7v*B9IhP5_D?O)pZX#qX)gz^ok}W_l@)| z9_@1>(O~R})<$uyW36qd{yDSYyk2nE5p@ns#<(re2tKwfxQrvmk@}E`;pgePTng}W z922;)QRSMY(b|~Ua+h=D<6NOSRPI?Fm^Xrg>w?UaGT3y1x!4@1CotJD`XrEfv{g+? z;gD>Uwwb1(#f@n3(3{lVrd~h`6fFBD3KgJ0l{VAwi^h16%LiyXVv;X{3WruXVcY9M ziuP<>_S%{Kkea0x5+$B)Eot-}H)>vsV7JisaoS$5@|Y!i!6Y6XL`R~6IHJEgTUWod zXKKqvfr0qxd)0=~m5(>9tohS~%`Y#tQQ9W+0$n(7Yp~j+@Xn85yAl%#wyEV=#OF5q zv6??hvm^Xjd4KWvBNJ6L1G~_vUM6-x0XvhvEA~ zp0Y|tVPYg7S6OVl0gJWD_=NYJ-rOcj<6c{5-1DD{lnNiZ zFANtd+UGS>l(p!o{{&1gT}7TfQNBl-cF#5R+cWA! zu7V%U%2ap1)Z<--Ig8EB?R_8BZ(=6h=D<-A%M#MPxq5~1oqMfFXSPK)(nMi%yWxR; zcYO$RXM&k!CpLeSy{?*8XY{A934c zffuFHX7<_SwDGo_-nq%t=m=V{FV^VgZeRX^Wj*pic&1QsIf=%zCj(k?=vhENy>f^d zLDJKGseY%L0%h@_0CV7h1YUn7+<^t*N7sZzIa9w;{K2jK$4X6;3(im9#_tq>gSF2u z;2iBS2zo}3&*nZys^=tbdH<(hcHrt2OjciLVJbs~nXe{KNFC4x8 z9ro$y^YN2gfaeSM-wr6bk60Tv{x=qXXlQ>JTmFtnqVd>!Bkz&mmjV#Y@S(Q0>qXNa z9c&J6{co^hh!kQx_Q30VH_q>m_J19OL?bSK&2BD*!3Oi_=WieqKmqcJ-%D{J7eVU9 zb-+6-*-y718Zh%4C2|apyl)|>s6qbzVYN%z`f*-ADqbE5o}+ip9ldyo0%POSxx2WB zU8^tfmY2UvTki{QPP?r^W1G+pO9AZ#HXue=V6-) zpIJc*jCDwBIr8)~`anzF&(f>lUru0eq*iCyQ(~K)&>nqhPu_Q2+VUlFqU1!nilL#6 zz57`#@dD0|qj{_=1p{ybGYM)=J&oL}5}i@epxKluiX~W^)f! zh_9`RBlq6Q4aXucshu3os_2e9g|xJWYX+P6u9IQuhLJBJMyOH*BmHdfEw51xfyavwf;C43-x)X|C;x^x&k+fvejD)_5Rnm zK>jWLmi-Qk$(?_BB-`(gwF>@96sA;7y6_`oL>|p{$|y zeH^3JmPX#ksynasefIo%Q}<7L9P2#jx(A=&0&;;`K08m9(PQ)v|2Wes9C;F4T9F^| z`Jc<%e>dteKi!l0r%{i*F*s8R6(h44vp{6jACnLdZaxhujwI|oikp-8*8zU;qTuyB z!kNN}{WmXK@UMl21sx`saT)E|*CbA=w-gVamJ)Zf*am#+Rv!8W1gv#3{xk-y+Me3)-Q^6_-T%tds3Q%gp7Jly@Q z?(kt+e^q?7d!8Q0?w;SnCK;GIf{I;{IJksh5;AZCF14#zlr3#(l7(NsCU9Cc=7w1Z z`g}!`YA^S_w|EaGBMy#*woT_Yq$WB|nwG2|pF%(7LniKVyWcXixeSf#-tb7(*sF{` zr>PoD6=}QeGrDmr#Aq0b`sw_(=Kh>da!1kcygEZL1byQ?1c>rz_l!Hs=^mGE;EFXf zchBQjgcv>y?hdzKYT6ilBT#W$Y=c)ff0rh8+#@(Ew}D(Ud@Q~$mW z`aD6fUZ(O)O3PeRJnG5fSa5PVwnG6iq5VQ1vuFCm6XCC1+e8JL}t!{;kBUd#5MSDap;Go-L6BeekZolBB+yb=&Br?7RjXMFXPznIdtj z>>Z3ImRvfQqMav7Cada;6J_h!dHo^oHs&kS+{F4^kDYR@-NoGw2`I}?k{rCTT5QS_ z&ySU&JXqKgyG>}(%aQ|rZGgF-vo^$KoJEYDr-)3`fNYIe8Qring> z*z21nD@LB4oy)V6pnBMUO-9A+ZHI9;LAB#TJ1@$Aotm%o>P1_FDIM}}dq!)ULthrl zX^B+ot3*-zl-kwqpJA4FA1%$KhyCf$>T3ul1qXmh1cboJ=m9on;LJ$?QD4{ z?qA_%pMKF{gen}`Nq6WoE;g{=7EmX)?D8yX)9_VkP;v7`Wn<7~vCn?`?Zz72t8W2Kn4_~OB|EE~r2_79*fzvCqlVgK+5%gb zY*nktdCkrA1+Dg26aOXaf49L&!kBFHB}6M(W1m$KE1j?Bj+bVUyUdwKaziwTrJrol zKvubn!q#RkkZd?KyWf3}GNIMDTPQw(uqHX>VUI(@EJIJJ$wtcAv5if)#Jg^KGW+&z zQPhKv2H_*QP4BIuCVFy(bqh9`O&IkbtzmP@D*fw@%nBHBDdYF}VHOAM`?Xv-&j2uGL zJXs9C4U>n*TjUcSWDt(Wg0bO)na#Fyy4Y>lQwe_aAZQuOs`4QdFnhHRZD|y<=SEjk z&W_tg4b+-Wb|n@N(%H%d&%eFQRnTUqVbOCiZb!ym(@7YqRk(YoxY6vr;V_p}leAJ+ z?v1Eh*SMHc=Rv1-&mUEYqt`!G&`GDS-H7_6natcj-IT5wzkP6mk{Kv1VBg+|8vj%z z&>*Kjl@+wyT+3G=7Rpx9Z08pmUtzx)HC{8F*)H$kx2~tHOO+fS-?bjz8^M3v9~Ga( zl~T7ZqpUV7vwpDAICcW5i9){-bDtvlEc?>NL`SMx{Jhz;%B9TAM1x?Ta|W>FwN9!TPugb;c!Khhsc~|zsrMYf&Uqz?6 zRcG3}C@mQ_9NzJy{CTY53QxD~`69ifZ)Y?5VXyp$aPI~w3yzfJkbxB+2o7WfGudJG( zGJW%bp9v|G&vIMO0!7nfI_FB{)5A`=TZN9HgeurcLbQJ!~27oSKp=%Od*(xVXj z>h^wx4UiHf-4~Ch^$2f!j z=b|PnK4_89hS4!+^Ccz)r~9!|$4^NKE9YVylO*>wU!2}wzbqpD$CH}DlUBlx4N+XM zNknO>*dPBNbTl|p%J7lD|CT-fA^NK4J;9Sdvk)(!RylB~76&=9ginkI{v#t36NIIB z-1bUQuh6hR{SnW6BV)mVJ^q7oP!L|-MJ-W5&{NjB{}CJpdqMDL@}tDgb5#aOLR9m0 zEf|hrFZ9;c)6YvuDJ$Dq^+JNc&MWkLxG&>Bke-6vZb0nR8G_m~v5+zRik!_}G?ZpR zLjmOXdrbTf&&LF-BoIai@Ypp>k>`j)ulzltw8gE0@-Lc?A>%l> z%J(q!QS_9}3I(l~FCp=-@zzGRK}Gdjv~zGg+Hd*NzYTTKaFl*TlMjBsaJ$!E&;IRO zmBy>q-!ercyZUig4ErSzau@K({QkEPMgDwmd3UE^;{WY=%}#QiI`yAo#G{ZhAiv-4 z{(H9Pcnk$|h62)_n-GcrmBd{M97s2m?k_qLOu=~q4M!JTId3Mb;?n0dLlaK;J)(ht z{C{Vszvudu3IBh{PXFCZJoPF%I$G%LAqtJ14I_>4KZEVg4ajz35N6cWlkIeh)IDD= zDk`$tzWBeh)c;Rrsei#N|8)1-6r6w}ow>cUq(|nSW4nXv<-r zdah`9EG_HcRe!i}^rh{P^_o&ffl5c|m(r0cpW(XODv2w%HZGFOG~--)G}!zNwFD!C$aG*L@Hps?y}M+z`3UT^3GW|H`8FBw40yZvp36i#Qh^ zH^<_{lC5F4ezEXO?r=0ikC@3DQk#nN=ypY;S!3G{vjc@{>4{lx1m<0w)~gCseYVS) zIYXY8Al=OBwKr)h_j_%(#(fil^kMoK-OHR5E@$lN_Le>4hafSb2-kf3eA;iFbJcxW z!F~?X%yJ|=4I`WTD4ax|eYbFG$xV@S?SD?6ty}b|*=C{PTYIcg_lIf4UW+o(EO%Ob zLqV$UY*pOqKFeP2p`dsgo%F;hdvroPvQSGYDu{eLHci4kqx@8E>>>GAa(ZF1!M%Cu z^r4>TcM~tpuV=I?I}CTsL$qDAEAyI84R5HA-QaD_-bQ!MlxlWn?e0pR`7|Lqk-4dE zMwMSvtmL@mTqoJ^`uM(f(&3&bG@2{63^x64tev_wvrjg$Wn9BJZOS3>7=rNIlah8@ zxnQx2a@M+jESG!dL*dY_MZZ=XW%;(eZZUiO`uwozOe?v0x6XctE&O?GpWTz8$Ty4a zSCY#KvimOI-Wd-v=RVV@Fn-kGVrlK4xMlqT#ugZrOkncF)_k0E$Z5U*ExW<40qzN! z-p>XzE4hWkTl+hOq$2@+jsB_>NBs1%`*TlwxjI~OEqSJIJNjBp%^6$v%`2h;vX8Ok z%>CBe8*`oxgk*% z9ByC}NBy&qD+YPe37PfB7fb9czA=b%;at%zu(F->x~5jTv1rs=yw^2m#9ch^uNLJi z>xMpGoR5#nHz;PUNKP(n?|+sKw`xy__PN&_z#)|NCD?XLHli~sed#xEHm`CCD|*w~I{CC!Y(WB@VJLk^ko~bf@HS{jGwShHbw!d) zlts^0|3;_$rLP|ObJ*kIyUTvtcFWz|6i2?a*smlq$FEqgDI^ro$n;~Y1z$L)qUu7Q zZo8+x^{ogUU%`d9pKGk~GDBWO!ER7It*2_(`sa-)l&2^9e15#TZ94lLF8Npz=WhGw}J4DTIkaivUDD00zqN@TUOn|+|%OX9(@rATMBB(T_P zK!{^dz@@(0@9V!uW4{;grmCdE4JG!$_IPCEZ z%ka{&*;q2IN5i~@M96kc7A28p#Y~Yy3;TdqvhUyk_rCdErKRot#Mjg-3Z_DJ2OAPk zCi%dyZgC4h4*vqPEF8crA=du_v#97FvD)X%ZZJI600vh9!{fABfmB)Y)LFy2XBsew z>=#sUhc3qFF!$L8v*NAy3%wBB5EXPid4yJtW8%3iNVTX#rN z(VCpz+sr4a<^JyM2Klk1_*5bxEOdKo{=(dAnr@A@-0_fpPw%PtoeEiB=x_)T#xV}~6q^;Aj; z#3O2$az?&O9M6)^*jYUfjC~?K8i8kv2s;<(OHuDyS359(wsU1&*@90%{Q1=Hj8V%Vx>_@k029ACd zM(K~5=lPzPJIF2MEKi)+lvB<(^F*l!@Abh!)&!Q5;M0@d@S{D06lwrw1&X> z(u?;_6Sa@oTyr^VU>4pw6xh2!o9d$NkEjp-blI<}J0UJ?hxRnj<7=*u!{ub@G+|2Zfvu=#h zQJYz#c0zonK&S4pv}agh*HN~+mW;D@IHsa>qnrW3DA@wfavPRwEybS06=L#-vIRW} z_2v{+xkppSKR>ByK10y8r(nv`QJB;Yv8UZ7NL}` zYtkqUgR$za2&^KZGzf^4bPl6ypwzfO1(dV^$zh~hrIZxu9Atz+YUlx`p3fO{rFO61 zbwBq#fALZ{bI$jC^Ie~EgDlRgQw71=hN7`z`ALuPX0iGEYoymlU%;x(7A)U1;bGdA zXWuPkJ*rxJ8jX#@ckI9KQzcR|*bwg|(rMY6<2e*+i0D2Tv>qC0qe_i!QTGj_zMkup2-4EvbRomrfRvd|ra8;q`Id`w!e;MR$b z<^|}Z14FTu#AO6bY`at^ABQ9Nh8o^&YinyU9URuP8-y(kzjsTWtsu2EF8VHK7e+j@ zqc06VDTUUv6Cb#OLl^N+=$*Tnn1qrD(Z70TMh97-h6)JB;JpRp{n3HB#4b3V;h13_ z!$od|bU1Mxd7rT-#i-1_8}jXrdD%x#EukWltO5AVl6K8*=xg7mAf2Te92j!L#D#5gh*zk z+}Mlyo`5urj$Na?T}MmHl?S547 z#g!<|iEL@t%9ELa1=IBN-XvcYf3K1381Kl$WW2UWYo5kz7c7UJFVlq?&L%nIPS^s@ zyVA{<#G^cW{&L*V*gKcJSyiM_F-Ylh^cl`A+?TZb{xi?Ce+NYp)lyXf!LB;LWj(zw zNt1{y=C*DvdtE!sYx~ox9AWps9Mq-k!lzNkzP@t;lFs-Vy*ZKu84zdD%Y5s#m@*+@6ld zqNk#nyhRH1&%CIi;uaH9r&;^3Tac_FMp~UZmPHI<8uCTo3Qu8L>QieIfsYFbK$b@N z8jY*gm8~A=Vqf%TmDw)5k0vdI?K*@~i6ZF3l>U<0wJxtG;uD(-~HF^tNrdOp30 z&s^Cyqcdo%e-CHL+9LVqG@mWJHR7Bq?)DMrJ`J8O{C}BPVm(*fT$LHzbI8GQHcOw~c%L7adt8KroxXFIc+VZCzF8Ev1VB@F z!!=r-d@R;|vQciJ;tb~vjX%)XgL*$Ak_@E>K})d9L=6_WYFs$b5o4Y6%yzDQlibYR zaV5{v1Qcry>V81vzxCQX!<)Hk>}GVt4lIgxG{@EwjmGMF6$F~rm2zV-jA1`{RNP+H z?|%ThiE*JPB{Hi*>rbS&*<`=xu4`!Ewa=eEqwfN6H*f>vc5EA7j1kMVR-l+|=1F_w z!kyEf*d3;Ks7MaJK5l;ePnDlcES8X#t+(K?w}kNZpjX^~19A!0%{aw-+6h6pKtiJ8 z1>~z-T&7mw$z5PEKp1sn?gYj4@7}8}!I!lF1HSyNKX5|O5*BXFt7wtFTrSOYnHfrc_rj~=T?@S2H0;i;^pPi3YPo@fsLyMIAaGktNYlh?T@T0A_@;g4TNZ# z;%W>kmJ6R0faJRDe-4B@2?+>HM2g33R_9eo`FP^S>{718MIDCw{{Z&PKgidIzJKLv z!sPlVra-t}nf`t01F4;1kx;;*$Vi6)i}ICySjM8jb0c@i`XCU~QL2y0h1uX^hOn<) zK?E`wHsABFL??XJd#$8I(nj84@2>f){(BE%X>N=^eQ0R7fQXM^38@)+uynlf&5xq% zu2xsC9?^Uuz59*OZHdE6wk9^mSzD6@q)a-Q=adUO$kP6gCl^+ia8+RZbhtFG$-~H~ z=;I0Gp{~!CEfkK;KPOA@gZy~(XW+EhBsKu`yMhj{yS`o$|Y5&tku zknVE*BK^$;g!@^xKUDapLTV5_Z?t^$04uQDT_9X~(EzaQk#^PBlS`WdQO70i`Ys}3U49`k<**4PdA+g3rog*BKLks5zZ6yj zK$(rLYV~{zMkuu|UplPeW)T&Y#DloEXz6?;ic@T~Cz99->q6vw zhuRR?w>fQn3F~>Uw`v{^r?8`TpT@W_c@f8^-B63-!sHB5 z#y%Fy#uPWEWWVd9jIZ>C3nu#Ul=={AZge&Fir=bEINu^K)5vL}2Dfo8_wKnOL$ zu`CA<(x^&Iv}c)zXqJYeTXl$m-K^Z_Q8Uxv#-;$+#U2pMr_{{Q*IJgtR#UGN7n>H9$-^7AeFKk?FYX zFU67n1&+bHYsu0LoJ|c1 zH>*W&5-EO8it>2XNI~$&m&wV2d3Jmt5t6DD&Rd=nRbOH-fVS5LNeBISmU8dcU?Ji7pU!aM0a{%$MNJM(sX}D ze-Hi(cY6t^kE4Y*U6^{xjQBaEyafR1o@_Er?v(725cNcRvTHDoFXRE0^l#>y=4hbT_q=JTla%z8bRaTZ%gLPuiFDn z+pOXgb7NOv3@W)Csx!On%H^ipRjwY^P#np%L@?U|hWGoEqEI`eREQW#q8}`r4zw?q zJk9@Mz!D)l3_TgGauex7C7<6jH_l_S&=9juZVDvMO;P~8|>jl9W>rp_ncT{^yn`ab9h3@*?p67xGhHCYJJ z>y(Y17U&okYQXV3*vN+j%C<|56bxpLXU+*K6JHVwdEoSNHDYoYSKeQ@QS?~fj|8gK zQ1+mqLwmJs2`HE}gFJw)(b%{ihqa9Iwz*?==y;uf4fSNlX!Jp0$qg!~;h~mm4@4`2 z-2oNtdYCgalwvOD4kz}q$8nJjjs(+_-JBY9bAFGidt;IIg4$h8$zBHhjxBZYTOG|s z`i49R2HF!!GWs|&JCMhM{TaIpA851xP~C5Mn38yX(&woj{MT=U6ic0Jsj-YTTv z;>T1ReCxRO<@L>dSiD)po{;<{t50hSa()t4X4Cz{CTDn)`4zX`-XeP!>Tp&E>$nz9 zC1+O}j(uV$hqm8SPh9V0#~@=WhDE8bk7?Ws^ygfhLU;Ig;+ON3u9?J~2TG#2? zR%a0Fd`GNXWt1z08Y zMq9La!OKRb?YT7$w@qD$>2EHBb&mKSLUjAqP24;A_z-cdJ|pjuZ|gZFX8I>%d6)%7 zPL%iR=mHaxoJqHtRSKnl%1R!gg5I-S6RWZnI<03;@Rg_QZ{;};a|4w_2JxTC=ewUA zI#rl$BY4|_PIaJ@;-vJWWcWq4>pczHDgUf@@JsQVwz9nm#WtISn7Iov$%5~m(ni6eMWt6NC^ zzUetkDRihfjw+I-pFdr3KcszsfRK=Ve%Ew_>tRis6S>S#PX8N&NkUka@uQX65dsR% z2a1|)O5@x0#4??cb6Rgv7F>1aCnU4?EjL9h)dc{mlaNpTTeUdNsXQdVC8L#!2p*r~stYXs5 zhT;hKye^0yBJ@G(bbYu_Fsc0D51ntfVU>?mQn^XjL^AJ*{_wF29dhty1Js?V=|`{W zOYRFU*ufx6K8jibCb0JHFt-o1WelFZ8De_7CEahLg-f{3kIklN#~R0K1-FroIfvMb zJDLRDx?DAPZ006ST5nqKkHHEY-Y4kO9Th@#iQehXwfkeNUR8T@n;@|)(AG&0e+Vbx zlS@{M^6YxENt&t0)ym&8jCUIpy%?uu6Rn^arlXVTIT9}{>rb(3%W-$HaFUd+64BnM zo)fjTCU9rnG@4)j6@8*9qy1qy-}I-4ug8hrGcpL#UO&sJ6Sxtt$rnY%;~2}9DM7!OoWF>&wkl;D<0N*KK};Fqfg%3N$5`mIJPy6Y^v3+3NtjYZRYydYIflJpZS+pB&M8;!rH@>Yg;yvSg#>= zqSRyd63Mr3^Vo;guGTtNWW>3g>YkGVjGWN5hP_kS@g~GZ`e#H9U}t6L->cT zKBp}{9n2f&ZTR$V-IO(8;%0{J)$ON1zWeaxR~3C)rTlu61RdW^Vzp%1=T5oFuLi=B z3&s9?h6utt9LFCpRDK-E9gcOp-Ra3^b3R4TrqK3M>A)+{GAE&GjRt9d+@^H=?F}RMiFQ zPf!SreO_=mCIMp6;DZuab4#KzGOO!CRJ%QMby3?@b!1i85%qvvX3gB9>^bdsViWV! z1s$e+UX=xGCZ?4hx95?wV>)&^`6p+IiQSgWGzK3bSp(HN$P9+~L%QW+k2V=&zk*IDZUipY{%KW;OP zrgH}E4Y4V(wHzx%P9h8{tns2aoyTI{9qwt#rp1BnP{fM$#8uZtPnjg>+2zUrY#)TL zo>nYBuPVN+7Daa#$!YkAuiK|rM#L)dTH~@z5_57#j4*+X&H8a1{(if@AS`y>^V7kr zn*D@v+IB)YWV`mNDcUBX>0?UN1|q&LR_~;mni{M%MW=}k6EkXqI&n-nAJ4g*vHIxf zZ5pWQYx&ay_oZwOX+KQ{5;U6_!bkkmsV3ri^W5*khD7-yCD9=IG`1&#K$jQv80~Tg z^zM0z-CxKKMc1QInmBaW=c8j+iDi^$Qa)<5d(Sg_La$lJj6?64*H*QaErwZL&IUPB z@M=`H!ThKYx|#a(yD_3xg3O}rz+9q0d{v)=pzww+j}y-h9VDV^H4T_Ja6K^%sZ9F_pV%;@Ki2MNmF;w3t#Udb#@LcOzNx!(Qms-a-!J0IaQNu~; z@K6P`co1LS#a;$Bj-ZXQtx|yZlmUOfBa}Iog*X~AIw+v4eMtID?TDh5lBV0luUcCZ zL7!25?x=1)R)sot4^Kj|mxQ#BY|b0bG0HiU+R`*pp^DL|8HA~IRgjQuN(8BPB;AWc z2$htdyxzFE{aU@N3K?0!$%PY54=L8f>gSqq>gLyGT8cSKoHSHmkG}p-4SHQxPj_)p z&<$&xkabFiVZOp-?@Up&cbt;%3#ZL*X__?3_E$@RDIy%*5(e7cj5TA}SH1o-*@(Hy zVd6(2OwF$|F92*l^MHkmOC=Qr@1HCS3nq%L;iQG5Bzi`RKREI z3J^7awz7^9y6-N2i=+E)N!-G6.lf_ttkXotZ{W;2BS17p;c&^==9)n`||{-G}~ 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 JcIw*Q{|{shRUrTX literal 168653 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 - Generators callable from Lua](#291---cppcoroutiner----generators-callable-from-lua) * [2.9.2 - Accepting Arguments](#292---accepting-arguments) @@ -86,6 +87,13 @@ Contents * [6.3 - LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING](#63---luabridge-safe-c-exception-handling) * [6.4 - LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE](#64---luabridge-raise-unregistered-class-usage) * [6.5 - LUABRIDGE_HAS_CXX20_COROUTINES / LUABRIDGE_DISABLE_CXX20_COROUTINES](#65---luabridge-has-cxx20-coroutines--luabridge-disable-cxx20-coroutines) + * [6.6 - LUABRIDGE_HAS_CXX17_FILESYSTEM / LUABRIDGE_DISABLE_CXX17_FILESYSTEM](#66---luabridge-has-cxx17-filesystem--luabridge-disable-cxx17-filesystem) + * [6.7 - LUABRIDGE_HAS_CXX17_ANY / LUABRIDGE_DISABLE_CXX17_ANY](#67---luabridge-has-cxx17-any--luabridge-disable-cxx17-any) + * [6.8 - LUABRIDGE_HAS_CXX20_SPAN / LUABRIDGE_DISABLE_CXX20_SPAN](#68---luabridge-has-cxx20-span--luabridge-disable-cxx20-span) + * [6.9 - LUABRIDGE_HAS_CXX20_RANGES / LUABRIDGE_DISABLE_CXX20_RANGES](#69---luabridge-has-cxx20-ranges--luabridge-disable-cxx20-ranges) + * [6.10 - LUABRIDGE_HAS_CXX23_EXPECTED / LUABRIDGE_DISABLE_CXX23_EXPECTED](#610---luabridge-has-cxx23-expected--luabridge-disable-cxx23-expected) + * [6.11 - LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS / LUABRIDGE_DISABLE_CXX23_FLAT_CONTAINERS](#611---luabridge-has-cxx23-flat-containers--luabridge-disable-cxx23-flat-containers) + * [6.12 - LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION / LUABRIDGE_DISABLE_CXX23_MOVE_ONLY_FUNCTION](#612---luabridge-has-cxx23-move-only-function--luabridge-disable-cxx23-move-only-function) * [Appendix - API Reference](#appendix---api-reference) @@ -122,6 +130,8 @@ It also offers a set of improvements compared to vanilla LuaBridge: * Added `std::shared_ptr` to support shared C++/Lua lifetime for types deriving from `std::enable_shared_from_this`. * Supports conversion to and from `std::nullptr_t`, `std::byte`, `std::pair`, `std::tuple` and `std::reference_wrapper`. * Supports conversion to and from C style arrays of any supported type. +* Supports `std::unique_ptr` as an ownership container, giving Lua a non-owning view of the C++ object. +* Supports `std::move_only_function` (C++23) as a registered callable type alongside `std::function`. * Transparent support of all signed and unsigned integer types up to `int64_t`. * Consistent numeric handling and conversions (signed, unsigned and floats) across all lua versions. * Simplified registration of enum types via the `luabridge::Enum` stack wrapper. @@ -143,7 +153,7 @@ Because LuaBridge was written with simplicity in mind there are some features th LuaBridge does not support: * Global types (types must be registered in a named scope). -* Automatic conversion between STL container types and Lua tables (but conversion can be enabled for `std::array`, `std::vector`, `std::map`, `std::unordered_map`, `std::set` `std::list`, `std::optional`, by including `LuaBridge/Array.h`, `LuaBridge/Vector.h`, `LuaBridge/Map`, `LuaBridge/UnorderedMap.h`, `LuaBridge/Set.h`, `LuaBridge/List.h`, `LuaBridge/Optional.h` respectively) +* Automatic conversion between STL container types and Lua tables (but conversion can be opted in for many standard containers by including the corresponding optional header — see [2.8.3](#283---standard-library-type-conversions)) * Inheriting Lua classes from C++ classes. * Passing nil to a C++ function that expects a pointer or reference. @@ -1338,6 +1348,100 @@ When the script calls `useStateAndArgs`, it passes only the integer and string p The same is applicable for properties. +### 2.8.3 - Standard Library Type Conversions + +LuaBridge does not enable STL container-to-Lua-table conversions by default. Each supported container type has its own optional header that must be included explicitly. All conversions map the container to a Lua table and back. + +The table below lists every optional header and its requirements: + +| Header | Type | C++ Standard | Notes | +|--------|------|-------------|-------| +| `LuaBridge/Array.h` | `std::array` | C++17 | Fixed-size sequence | +| `LuaBridge/Vector.h` | `std::vector` | C++17 | Sequence | +| `LuaBridge/Deque.h` | `std::deque` | C++17 | Double-ended sequence | +| `LuaBridge/ForwardList.h` | `std::forward_list` | C++17 | Singly-linked sequence | +| `LuaBridge/List.h` | `std::list` | C++17 | Doubly-linked sequence | +| `LuaBridge/Map.h` | `std::map` | C++17 | Ordered key-value table | +| `LuaBridge/MultiMap.h` | `std::multimap` | C++17 | Ordered multi-value table; each key maps to an array of values in Lua | +| `LuaBridge/Set.h` | `std::set` | C++17 | Ordered set | +| `LuaBridge/UnorderedMap.h` | `std::unordered_map` | C++17 | Hash key-value table | +| `LuaBridge/UnorderedMultiMap.h` | `std::unordered_multimap` | C++17 | Hash multi-value table; each key maps to an array of values in Lua | +| `LuaBridge/UnorderedSet.h` | `std::unordered_set` | C++17 | Hash set | +| `LuaBridge/Optional.h` | `std::optional` | C++17 | Nullable value | +| `LuaBridge/Variant.h` | `std::variant` | C++17 | Tagged union | +| `LuaBridge/Any.h` | `std::any` | C++17 (`LUABRIDGE_HAS_CXX17_ANY`) | Push-only; types must be pre-registered with `luabridge::registerAnyPush(L)` | +| `LuaBridge/Span.h` | `std::span` | C++20 (`LUABRIDGE_HAS_CXX20_SPAN`) | Push-only; use `std::vector` to retrieve sequences from Lua | +| `LuaBridge/StdExpected.h` | `std::expected` | C++23 (`LUABRIDGE_HAS_CXX23_EXPECTED`) | Pushes the value on success, nil on error | +| `LuaBridge/FlatMap.h` | `std::flat_map` | C++23 (`LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS`) | Ordered key-value table backed by contiguous storage | +| `LuaBridge/FlatSet.h` | `std::flat_set` | C++23 (`LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS`) | Ordered set backed by contiguous storage | + +`std::filesystem::path` is also supported as a built-in type when C++17 filesystem is available (`LUABRIDGE_HAS_CXX17_FILESYSTEM`). It is converted to and from a Lua string automatically; no additional header is required beyond `LuaBridge/LuaBridge.h`. + +**Example — using `std::deque` and `std::multimap`:** + +```cpp +#include +#include +#include + +// std::deque pushes as a 1-based Lua table: {10, 20, 30} +std::deque dq = {10, 20, 30}; +luabridge::push(L, dq); + +// std::multimap pushes as a table where each key maps to an array of values: +// { a = {1, 2}, b = {3} } +std::multimap mm = {{"a", 1}, {"a", 2}, {"b", 3}}; +luabridge::push(L, mm); +``` + +**Example — push-only `std::any`:** + +Types stored inside a `std::any` must be registered before they can be pushed: + +```cpp +#include +#include + +luabridge::registerAnyPush(L); +luabridge::registerAnyPush(L); + +std::any value = 42; +luabridge::push(L, value); // pushes integer 42 +``` + +**Example — `std::expected` (C++23):** + +```cpp +#include +#include + +std::expected ok = 42; +luabridge::push(L, ok); // pushes integer 42 + +std::expected err = std::unexpected("oops"); +luabridge::push(L, err); // pushes nil +``` + +**`std::unique_ptr` as an ownership container:** + +`std::unique_ptr` is supported as a container type without any additional header. Lua receives a non-owning view of the object. The C++ side retains ownership and must outlive any Lua reference to the object: + +```cpp +auto obj = std::make_unique(); +luabridge::push(L, obj); // Lua gets a non-owning reference; obj must outlive the Lua reference +``` + +**`std::move_only_function` (C++23):** + +When `LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION` is active, `std::move_only_function` can be registered in a namespace or class just like `std::function`. This allows registering callables that are move-only (e.g. lambdas capturing unique ownership): + +```cpp +luabridge::getGlobalNamespace(L) + .addFunction("compute", std::move_only_function([resource = std::make_unique()](int x) { + return resource->process(x); + })); +``` + 2.9 - C++20 Coroutine Integration ---------------------------------- @@ -2257,6 +2361,110 @@ You can also override the detection result explicitly: Attempting to use coroutine integration on Lua 5.1, LuaJIT, or Luau will emit a compile-time `#error` unless `LUABRIDGE_DISABLE_COROUTINE_INTEGRATION` is also defined. +6.6 - LUABRIDGE_HAS_CXX17_FILESYSTEM / LUABRIDGE_DISABLE_CXX17_FILESYSTEM +-------------------------------------------------------------------------- + +**`LUABRIDGE_HAS_CXX17_FILESYSTEM` - auto-detected, override allowed** + +When a C++17 compiler and `` are available, LuaBridge automatically enables `std::filesystem::path` ↔ Lua string conversion. No additional header is needed; the specialization lives inside `LuaBridge/LuaBridge.h`. + +To force the feature off: + +```cpp +#define LUABRIDGE_DISABLE_CXX17_FILESYSTEM +#include +``` + +6.7 - LUABRIDGE_HAS_CXX17_ANY / LUABRIDGE_DISABLE_CXX17_ANY +------------------------------------------------------------ + +**`LUABRIDGE_HAS_CXX17_ANY` - auto-detected, override allowed** + +When a C++17 compiler and `` are available, LuaBridge enables push support for `std::any` via `LuaBridge/Any.h`. Because `std::any` erases the type at runtime, push is performed through a runtime registry. Types must be pre-registered with `luabridge::registerAnyPush(L)` before a value of that type can be pushed. + +To force the feature off: + +```cpp +#define LUABRIDGE_DISABLE_CXX17_ANY +#include +``` + +6.8 - LUABRIDGE_HAS_CXX20_SPAN / LUABRIDGE_DISABLE_CXX20_SPAN +-------------------------------------------------------------- + +**`LUABRIDGE_HAS_CXX20_SPAN` - auto-detected when C++20 is enabled, override allowed** + +When a C++20 compiler and `` are available, LuaBridge enables push support for `std::span` via `LuaBridge/Span.h`. `std::span` is push-only — it cannot be retrieved from Lua (use `std::vector` to read sequences back). + +To force the feature off: + +```cpp +#define LUABRIDGE_DISABLE_CXX20_SPAN +#include +``` + +6.9 - LUABRIDGE_HAS_CXX20_RANGES / LUABRIDGE_DISABLE_CXX20_RANGES +------------------------------------------------------------------- + +**`LUABRIDGE_HAS_CXX20_RANGES` - auto-detected when C++20 is enabled, override allowed** + +When a C++20 compiler with ranges support is detected, LuaBridge's `luabridge::Iterator` gains the additional members required to satisfy the `std::input_iterator` concept (`value_type`, `difference_type`, `iterator_concept`) and a matching `operator==`. This allows `luabridge::Range` — the object returned by `luabridge::pairs()` — to be used directly in C++20 range-based algorithms and `std::views` pipelines. + +```cpp +// Requires LUABRIDGE_HAS_CXX20_RANGES +for (auto [key, val] : luabridge::pairs(tableRef)) + std::cout << key.tostring() << " = " << val.tostring() << "\n"; +``` + +To force the feature off: + +```cpp +#define LUABRIDGE_DISABLE_CXX20_RANGES +#include +``` + +6.10 - LUABRIDGE_HAS_CXX23_EXPECTED / LUABRIDGE_DISABLE_CXX23_EXPECTED +----------------------------------------------------------------------- + +**`LUABRIDGE_HAS_CXX23_EXPECTED` - auto-detected when C++23 is enabled, override allowed** + +When a C++23 compiler and `` are available, LuaBridge enables `std::expected` ↔ Lua conversion via `LuaBridge/StdExpected.h`. A successful value is pushed as the contained `T`; a failure pushes `nil`. + +To force the feature off: + +```cpp +#define LUABRIDGE_DISABLE_CXX23_EXPECTED +#include +``` + +6.11 - LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS / LUABRIDGE_DISABLE_CXX23_FLAT_CONTAINERS +------------------------------------------------------------------------------------- + +**`LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS` - auto-detected when C++23 is enabled, override allowed** + +When a C++23 compiler and `` are available, LuaBridge enables conversion support for `std::flat_map` and `std::flat_set` via `LuaBridge/FlatMap.h` and `LuaBridge/FlatSet.h` respectively. These are contiguous-storage analogues of `std::map` and `std::set` with identical Lua table semantics. + +To force the feature off: + +```cpp +#define LUABRIDGE_DISABLE_CXX23_FLAT_CONTAINERS +#include +``` + +6.12 - LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION / LUABRIDGE_DISABLE_CXX23_MOVE_ONLY_FUNCTION +------------------------------------------------------------------------------------------- + +**`LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION` - auto-detected when C++23 is enabled, override allowed** + +When a C++23 compiler with `std::move_only_function` support is detected, LuaBridge's function-traits machinery recognises `std::move_only_function` and its `noexcept` / `const` variants as valid callable types. This allows registering move-only callables (e.g. lambdas that capture `std::unique_ptr`) directly with `addFunction` / `addStaticFunction` / `addCoroutine`. + +To force the feature off: + +```cpp +#define LUABRIDGE_DISABLE_CXX23_MOVE_ONLY_FUNCTION +#include +``` + Appendix - API Reference ======================== diff --git a/README.md b/README.md index 542c2399..aee11ca0 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ LuaBridge3 is usable from a compliant C++17 compiler and offers the following fe * Functions and constructors overloading support. * Easy access to Lua objects like tables and functions. * Expose C++ classes allowing them to use the flexibility of lua property lookup. -* Interoperable with most common c++ standard library container types. +* Interoperable with a wide range of C++ standard library types, including containers, smart pointers, and modern C++17/20/23 additions. * Written in a clear and easy to debug style. ## Performance @@ -83,6 +83,7 @@ LuaBridge3 offers a set of improvements compared to vanilla LuaBridge: * Static metamethod fallbacks via `__index` and `__newindex` in exposed C++ classes on the class static table. * Custom destructor hook via `addDestructor` (`__destruct` metamethod) called just before the C++ destructor. * Added `std::shared_ptr` to support shared C++/Lua lifetime for types deriving from `std::enable_shared_from_this`. +* `std::unique_ptr` supported as an ownership container — Lua gets a non-owning view while C++ retains ownership. * Supports conversion to and from `std::nullptr_t`, `std::byte`, `std::pair`, `std::tuple` and `std::reference_wrapper`. * Supports conversion to and from C style arrays of any supported type. * `void*` and `const void*` are transparently mapped to Lua lightuserdata. @@ -99,6 +100,48 @@ LuaBridge3 offers a set of improvements compared to vanilla LuaBridge: * `TypeResult::valueOr(default)` allows safe value extraction with an explicit fallback. * Can safely register and use classes exposed across shared library boundaries. +### Standard Library Container Support + +Optional headers enable transparent Lua↔C++ conversion for a wide range of STL containers. Include only what you need: + +| Header | Type | Requirement | +|--------|------|-------------| +| `LuaBridge/Array.h` | `std::array` | C++17 | +| `LuaBridge/Vector.h` | `std::vector` | C++17 | +| `LuaBridge/Deque.h` | `std::deque` | C++17 | +| `LuaBridge/ForwardList.h` | `std::forward_list` | C++17 | +| `LuaBridge/List.h` | `std::list` | C++17 | +| `LuaBridge/Map.h` | `std::map` | C++17 | +| `LuaBridge/MultiMap.h` | `std::multimap` | C++17 | +| `LuaBridge/Set.h` | `std::set` | C++17 | +| `LuaBridge/UnorderedMap.h` | `std::unordered_map` | C++17 | +| `LuaBridge/UnorderedMultiMap.h` | `std::unordered_multimap` | C++17 | +| `LuaBridge/UnorderedSet.h` | `std::unordered_set` | C++17 | +| `LuaBridge/Optional.h` | `std::optional` | C++17 | +| `LuaBridge/Variant.h` | `std::variant` | C++17 | +| `LuaBridge/Any.h` | `std::any` (push-only) | C++17 | +| `LuaBridge/Span.h` | `std::span` (push-only) | C++20 | +| `LuaBridge/StdExpected.h` | `std::expected` | C++23 | +| `LuaBridge/FlatMap.h` | `std::flat_map` | C++23 | +| `LuaBridge/FlatSet.h` | `std::flat_set` | C++23 | + +`std::filesystem::path` is automatically converted to/from a Lua string when C++17 filesystem is available — no additional header required. + +### Modern C++ Feature Detection + +LuaBridge3 auto-detects available C++ standard library features and activates the corresponding support without any manual configuration. Every feature can be force-disabled with a `LUABRIDGE_DISABLE_*` preprocessor flag if needed: + +| Macro | Feature | Standard | +|-------|---------|----------| +| `LUABRIDGE_HAS_CXX17_FILESYSTEM` | `std::filesystem::path` ↔ string | C++17 | +| `LUABRIDGE_HAS_CXX17_ANY` | `std::any` push registry | C++17 | +| `LUABRIDGE_HAS_CXX20_SPAN` | `std::span` push support | C++20 | +| `LUABRIDGE_HAS_CXX20_RANGES` | `Iterator`/`Range` satisfy `std::ranges` concepts | C++20 | +| `LUABRIDGE_HAS_CXX20_COROUTINES` | `CppCoroutine` / `LuaCoroutine` | C++20 | +| `LUABRIDGE_HAS_CXX23_EXPECTED` | `std::expected` conversion | C++23 | +| `LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS` | `std::flat_map` / `std::flat_set` | C++23 | +| `LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION` | `std::move_only_function` as callable | C++23 | + ## Documentation Please read the [LuaBridge3 Reference Manual](https://kunitoki.github.io/LuaBridge3/Manual) for more details on the API. @@ -146,17 +189,27 @@ Commit the changed files and create a Pull Request for vcpkg. Unit test build requires a CMake and C++17 compliant compiler. -There are 14 unit test flavors: -* `LuaBridgeTests51` - uses Lua 5.1 -* `LuaBridgeTests51Noexcept` - uses Lua 5.1 without exceptions enabled -* `LuaBridgeTests52` - uses Lua 5.2 -* `LuaBridgeTests52Noexcept` - uses Lua 5.2 without exceptions enabled -* `LuaBridgeTests53` - uses Lua 5.3 -* `LuaBridgeTests53Noexcept` - uses Lua 5.3 without exceptions enabled -* `LuaBridgeTests54` - uses Lua 5.4 -* `LuaBridgeTests54Noexcept` - uses Lua 5.4 without exceptions enabled -* `LuaBridgeTests55` - uses Lua 5.5 -* `LuaBridgeTests55Noexcept` - uses Lua 5.5 without exceptions enabled +These are the unit test targets: +* `LuaBridgeTests51` - uses Lua 5.1 in C++ mode +* `LuaBridgeTests51Noexcept` - uses Lua 5.1 in C++ mode without exceptions enabled +* `LuaBridgeTests51LuaC` - uses Lua 5.1 in C mode +* `LuaBridgeTests51LuaCNoexcept` - uses Lua 5.1 in C mode without exceptions enabled +* `LuaBridgeTests52` - uses Lua 5.2 in C++ mode +* `LuaBridgeTests52Noexcept` - uses Lua 5.2 in C++ mode without exceptions enabled +* `LuaBridgeTests52LuaC` - uses Lua 5.2 in C mode +* `LuaBridgeTests52LuaCNoexcept` - uses Lua 5.2 in C mode without exceptions enabled +* `LuaBridgeTests53` - uses Lua 5.3 in C++ mode +* `LuaBridgeTests53Noexcept` - uses Lua 5.3 in C++ mode without exceptions enabled +* `LuaBridgeTests53LuaC` - uses Lua 5.3 in C mode +* `LuaBridgeTests53LuaCNoexcept` - uses Lua 5.3 in C mode without exceptions enabled +* `LuaBridgeTests54` - uses Lua 5.4 in C++ mode +* `LuaBridgeTests54Noexcept` - uses Lua 5.4 in C++ mode without exceptions enabled +* `LuaBridgeTests54LuaC` - uses Lua 5.4 in C mode +* `LuaBridgeTests54LuaCNoexcept` - uses Lua 5.4 in C mode without exceptions enabled +* `LuaBridgeTests55` - uses Lua 5.5 in C++ mode +* `LuaBridgeTests55Noexcept` - uses Lua 5.5 in C++ mode without exceptions enabled +* `LuaBridgeTests55LuaC` - uses Lua 5.5 in C mode +* `LuaBridgeTests55LuaCNoexcept` - uses Lua 5.5 in C mode without exceptions enabled * `LuaBridgeTestsLuaJIT` - uses LuaJIT 2.1 * `LuaBridgeTestsLuaJITNoexcept` - uses LuaJIT 2.1 without exceptions enabled * `LuaBridgeTestsLuau` - uses Luau @@ -169,12 +222,10 @@ Generate Unix Makefiles and build on Linux: ```bash git clone --recursive git@github.com:kunitoki/LuaBridge3.git -mkdir -p LuaBridge3/build -pushd LuaBridge3/build -cmake -G "Unix Makefiles" ../ -cmake --build . -DCMAKE_BUILD_TYPE=Debug -# or cmake --build . -DCMAKE_BUILD_TYPE=Release -# or cmake --build . -DCMAKE_BUILD_TYPE=RelWithDebInfo +cmake -G "Unix Makefiles" -DCMAKE_CXX_STANDARD=20 -B Build . # Generates Unix Makefiles +cmake --build Build -DCMAKE_BUILD_TYPE=Debug +# or cmake --build Build -DCMAKE_BUILD_TYPE=Release +# or cmake --build Build -DCMAKE_BUILD_TYPE=RelWithDebInfo popd ``` @@ -182,23 +233,20 @@ Generate XCode project and build on MacOS: ```bash git clone --recursive git@github.com:kunitoki/LuaBridge3.git -mkdir -p LuaBridge3/build -pushd LuaBridge3/build -cmake -G Xcode ../ # Generates XCode project build/LuaBridge.xcodeproj -cmake --build . -DCMAKE_BUILD_TYPE=Debug -# or cmake --build . -DCMAKE_BUILD_TYPE=Release -# or cmake --build . -DCMAKE_BUILD_TYPE=RelWithDebInfo -popd +cmake -G Xcode -DCMAKE_CXX_STANDARD=20 -B Build . # Generates XCode project build/LuaBridge.xcodeproj +cmake --build Build -DCMAKE_BUILD_TYPE=Debug +# or cmake --build Build -DCMAKE_BUILD_TYPE=Release +# or cmake --build Build -DCMAKE_BUILD_TYPE=RelWithDebInfo ``` Generate VS2019 solution on Windows: ```cmd git clone --recursive git@github.com:kunitoki/LuaBridge3.git -mkdir LuaBridge3/build -pushd LuaBridge3/build -cmake -G "Visual Studio 16" ../ # Generates MSVS solution build/LuaBridge.sln -popd +cmake -G "Visual Studio 16" -DCMAKE_CXX_STANDARD=20 -B Build . # Generates MSVS solution build/LuaBridge.sln +cmake --build Build -DCMAKE_BUILD_TYPE=Debug +# or cmake --build Build -DCMAKE_BUILD_TYPE=Release +# or cmake --build Build -DCMAKE_BUILD_TYPE=RelWithDebInfo ``` ## Official Repository diff --git a/Source/LuaBridge/Any.h b/Source/LuaBridge/Any.h new file mode 100644 index 00000000..3b753f40 --- /dev/null +++ b/Source/LuaBridge/Any.h @@ -0,0 +1,82 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" +#include "detail/Config.h" + +#if LUABRIDGE_HAS_CXX17_ANY + +#include +#include +#include +#include + +namespace luabridge { + +namespace detail { + +using AnyPushFn = std::function; + +inline std::unordered_map& anyPushRegistry() +{ + static std::unordered_map registry; + return registry; +} + +} // namespace detail + +//================================================================================================= +/** + * @brief Register a push handler for std::any holding type T. + */ +template +void registerAnyPush(lua_State*) +{ + detail::anyPushRegistry()[std::type_index(typeid(T))] = + [](lua_State* L, const std::any& value) -> Result + { + return Stack::push(L, std::any_cast(value)); + }; +} + +//================================================================================================= +/** + * @brief Stack specialization for `std::any` (push-only). + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const std::any& value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! value.has_value()) + { + lua_pushnil(L); + return {}; + } + + auto& registry = detail::anyPushRegistry(); + + auto it = registry.find(std::type_index(value.type())); + if (it == registry.end()) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return it->second(L, value); + } + + [[nodiscard]] static bool isInstance(lua_State*, int) + { + return false; // std::any cannot be detected from Lua side + } +}; + +} // namespace luabridge + +#endif // LUABRIDGE_HAS_CXX17_ANY diff --git a/Source/LuaBridge/Array.h b/Source/LuaBridge/Array.h index fae90244..51e289d1 100644 --- a/Source/LuaBridge/Array.h +++ b/Source/LuaBridge/Array.h @@ -38,7 +38,7 @@ struct Stack> if (! result) return result; - lua_rawseti(L, tableIndex, i + 1); + lua_rawseti(L, tableIndex, static_cast(i + 1)); } stackRestore.reset(); diff --git a/Source/LuaBridge/Deque.h b/Source/LuaBridge/Deque.h new file mode 100644 index 00000000..20c44218 --- /dev/null +++ b/Source/LuaBridge/Deque.h @@ -0,0 +1,80 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// Copyright 2020, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::deque`. + */ +template +struct Stack> +{ + using Type = std::deque; + + [[nodiscard]] static Result push(lua_State* L, const Type& deque) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(deque.size()), 0); + const int tableIndex = lua_gettop(L); + + auto it = deque.cbegin(); + for (std::size_t i = 1; it != deque.cend(); ++i, ++it) + { + auto result = Stack::push(L, *it); + if (! result) + return result; + + lua_rawseti(L, tableIndex, static_cast(i)); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type deque; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + deque.emplace_back(*item); + lua_pop(L, 1); + } + + return deque; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge diff --git a/Source/LuaBridge/FlatMap.h b/Source/LuaBridge/FlatMap.h new file mode 100644 index 00000000..2a34d851 --- /dev/null +++ b/Source/LuaBridge/FlatMap.h @@ -0,0 +1,89 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#if LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::flat_map`. + */ +template +struct Stack> +{ + using Type = std::flat_map; + + [[nodiscard]] static Result push(lua_State* L, const Type& map) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, 0, static_cast(map.size())); + + for (auto it = map.begin(); it != map.end(); ++it) + { + auto result = Stack::push(L, it->first); + if (! result) + return result; + + result = Stack::push(L, it->second); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type map; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto value = Stack::get(L, -1); + if (! value) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto key = Stack::get(L, -2); + if (! key) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + map.emplace(*key, *value); + lua_pop(L, 1); + } + + return map; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge + +#endif // LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS diff --git a/Source/LuaBridge/FlatSet.h b/Source/LuaBridge/FlatSet.h new file mode 100644 index 00000000..56309c6c --- /dev/null +++ b/Source/LuaBridge/FlatSet.h @@ -0,0 +1,84 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#if LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::flat_set`. + */ +template +struct Stack> +{ + using Type = std::flat_set; + + [[nodiscard]] static Result push(lua_State* L, const Type& set) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, 0, static_cast(set.size())); + + auto it = set.cbegin(); + for (lua_Integer tableIndex = 1; it != set.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, tableIndex); + + auto result = Stack::push(L, *it); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type set; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + set.emplace(*item); + lua_pop(L, 1); + } + + return set; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge + +#endif // LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS diff --git a/Source/LuaBridge/ForwardList.h b/Source/LuaBridge/ForwardList.h new file mode 100644 index 00000000..e14019da --- /dev/null +++ b/Source/LuaBridge/ForwardList.h @@ -0,0 +1,82 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// Copyright 2020, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::forward_list`. + */ +template +struct Stack> +{ + using Type = std::forward_list; + + [[nodiscard]] static Result push(lua_State* L, const Type& list) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, 0, 0); + + auto it = list.cbegin(); + for (std::size_t tableIndex = 1; it != list.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, static_cast(tableIndex)); + + auto result = Stack::push(L, *it); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type list; + auto insertPos = list.before_begin(); + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + insertPos = list.insert_after(insertPos, *item); + lua_pop(L, 1); + } + + return list; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge diff --git a/Source/LuaBridge/List.h b/Source/LuaBridge/List.h index 6c4c1dd0..649783e2 100644 --- a/Source/LuaBridge/List.h +++ b/Source/LuaBridge/List.h @@ -30,17 +30,16 @@ struct Stack> StackRestore stackRestore(L); lua_createtable(L, static_cast(list.size()), 0); + const int tableIndex = lua_gettop(L); auto it = list.cbegin(); - for (lua_Integer tableIndex = 1; it != list.cend(); ++tableIndex, ++it) + for (std::size_t i = 1; it != list.cend(); ++i, ++it) { - lua_pushinteger(L, tableIndex); - auto result = Stack::push(L, *it); if (! result) return result; - lua_settable(L, -3); + lua_rawseti(L, tableIndex, static_cast(i)); } stackRestore.reset(); diff --git a/Source/LuaBridge/MultiMap.h b/Source/LuaBridge/MultiMap.h new file mode 100644 index 00000000..0c6b37a8 --- /dev/null +++ b/Source/LuaBridge/MultiMap.h @@ -0,0 +1,107 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::multimap`. + */ +template +struct Stack> +{ + using Type = std::multimap; + + [[nodiscard]] static Result push(lua_State* L, const Type& map) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, 0, 0); + + auto it = map.begin(); + while (it != map.end()) + { + auto result = Stack::push(L, it->first); + if (! result) + return result; + + auto range = map.equal_range(it->first); + lua_createtable(L, static_cast(std::distance(range.first, range.second)), 0); + + int innerIndex = 1; + for (auto innerIt = range.first; innerIt != range.second; ++innerIt, ++innerIndex) + { + result = Stack::push(L, innerIt->second); + if (! result) + return result; + + lua_rawseti(L, -2, innerIndex); + } + + lua_settable(L, -3); + it = range.second; + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (! lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type map; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto key = Stack::get(L, -2); + if (! key) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! lua_istable(L, -1)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + int innerAbsIndex = lua_absindex(L, -1); + lua_pushnil(L); + + while (lua_next(L, innerAbsIndex) != 0) + { + auto value = Stack::get(L, -1); + if (! value) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + map.emplace(*key, *value); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + return map; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge diff --git a/Source/LuaBridge/Span.h b/Source/LuaBridge/Span.h new file mode 100644 index 00000000..5db44cf3 --- /dev/null +++ b/Source/LuaBridge/Span.h @@ -0,0 +1,66 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#if LUABRIDGE_HAS_CXX20_SPAN + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::span` (push-only). + */ +template +struct Stack> +{ + using Type = std::span; + + [[nodiscard]] static Result push(lua_State* L, Type span) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(span.size()), 0); + const int tableIndex = lua_gettop(L); + + int i = 1; + for (const auto& element : span) + { + auto result = Stack>::push(L, element); + if (! result) + return result; + + lua_rawseti(L, tableIndex, i++); + } + + stackRestore.reset(); + return {}; + } + + template + [[nodiscard]] static TypeResult get(lua_State*, int) + { + static_assert(sizeof(U) == 0, + "std::span cannot be retrieved from Lua — use std::vector to retrieve sequences from Lua"); + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge + +#endif // LUABRIDGE_HAS_CXX20_SPAN diff --git a/Source/LuaBridge/StdExpected.h b/Source/LuaBridge/StdExpected.h new file mode 100644 index 00000000..2eb1f9ec --- /dev/null +++ b/Source/LuaBridge/StdExpected.h @@ -0,0 +1,69 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#if LUABRIDGE_HAS_CXX23_EXPECTED + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::expected` (C++23). + */ +template +struct Stack> +{ + using Type = std::expected; + + [[nodiscard]] static Result push(lua_State* L, const Type& value) + { + if (value.has_value()) + { + StackRestore stackRestore(L); + + auto result = Stack::push(L, *value); + if (! result) + return result; + + stackRestore.reset(); + return {}; + } + +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + const auto type = lua_type(L, index); + if (type == LUA_TNIL || type == LUA_TNONE) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto result = Stack::get(L, index); + if (! result) + return result.error(); + + return Type(*result); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + const auto type = lua_type(L, index); + return (type != LUA_TNIL && type != LUA_TNONE) && Stack::isInstance(L, index); + } +}; + +} // namespace luabridge + +#endif // LUABRIDGE_HAS_CXX23_EXPECTED diff --git a/Source/LuaBridge/UnorderedMultiMap.h b/Source/LuaBridge/UnorderedMultiMap.h new file mode 100644 index 00000000..cabcc8b2 --- /dev/null +++ b/Source/LuaBridge/UnorderedMultiMap.h @@ -0,0 +1,107 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::unordered_multimap`. + */ +template +struct Stack> +{ + using Type = std::unordered_multimap; + + [[nodiscard]] static Result push(lua_State* L, const Type& map) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, 0, 0); + + auto it = map.begin(); + while (it != map.end()) + { + auto result = Stack::push(L, it->first); + if (! result) + return result; + + auto range = map.equal_range(it->first); + lua_createtable(L, static_cast(std::distance(range.first, range.second)), 0); + + int innerIndex = 1; + for (auto innerIt = range.first; innerIt != range.second; ++innerIt, ++innerIndex) + { + result = Stack::push(L, innerIt->second); + if (! result) + return result; + + lua_rawseti(L, -2, innerIndex); + } + + lua_settable(L, -3); + it = range.second; + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (! lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type map; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto key = Stack::get(L, -2); + if (! key) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! lua_istable(L, -1)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + int innerAbsIndex = lua_absindex(L, -1); + lua_pushnil(L); + + while (lua_next(L, innerAbsIndex) != 0) + { + auto value = Stack::get(L, -1); + if (! value) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + map.emplace(*key, *value); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + return map; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge diff --git a/Source/LuaBridge/UnorderedSet.h b/Source/LuaBridge/UnorderedSet.h new file mode 100644 index 00000000..c7cadd46 --- /dev/null +++ b/Source/LuaBridge/UnorderedSet.h @@ -0,0 +1,80 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::unordered_set`. + */ +template +struct Stack> +{ + using Type = std::unordered_set; + + [[nodiscard]] static Result push(lua_State* L, const Type& set) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, 0, static_cast(set.size())); + + auto it = set.cbegin(); + for (lua_Integer tableIndex = 1; it != set.cend(); ++tableIndex, ++it) + { + lua_pushinteger(L, tableIndex); + + auto result = Stack::push(L, *it); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + const StackRestore stackRestore(L); + + Type set; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (! item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + set.emplace(*item); + lua_pop(L, 1); + } + + return set; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge diff --git a/Source/LuaBridge/Variant.h b/Source/LuaBridge/Variant.h index b5425273..dbc279e9 100644 --- a/Source/LuaBridge/Variant.h +++ b/Source/LuaBridge/Variant.h @@ -40,7 +40,16 @@ struct Stack> [[nodiscard]] static TypeResult tryGet(lua_State* L, int index) { if (auto value = Stack::get(L, index)) + { +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif return Type{ std::in_place_type, std::move(*value) }; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + } if constexpr (sizeof...(Rest) > 0) return tryGet(L, index); diff --git a/Source/LuaBridge/Vector.h b/Source/LuaBridge/Vector.h index 3fd7da56..c5d94d68 100644 --- a/Source/LuaBridge/Vector.h +++ b/Source/LuaBridge/Vector.h @@ -38,7 +38,7 @@ struct Stack> if (! result) return result; - lua_rawseti(L, tableIndex, i + 1); + lua_rawseti(L, tableIndex, static_cast(i + 1)); } stackRestore.reset(); diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index a9eded1a..d7705883 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -31,25 +33,100 @@ namespace detail { //================================================================================================= /** - * @brief Make argument lists extracting them from the lua state, starting at a stack index. + * @brief Extract exactly what Stack::get() produces. * - * @tparam ArgsPack Arguments pack to extract from the lua stack. - * @tparam Start Start index where stack variables are located in the lua stack. + * Allows that implicit conversions (e.g. std::reference_wrapper → T&) happen correctly at the call site. + */ +template +using stack_value_t = remove_cvref_t< + decltype(*std::declval::get(std::declval(), 0))>())>; + +//================================================================================================= +/** + * @brief Trivially-destructible storage for one decoded function argument. + * + * Longjmp-safe: the raw byte array and the construction flag are trivially + * destructible, so raise_lua_error (longjmp) while ArgStorage objects sit on + * the C++ stack does not skip any live C++ destructor. The contained T must + * be managed explicitly via construct/destroy. */ template -auto unwrap_argument_or_error(lua_State* L, std::size_t index, std::size_t start) +struct ArgStorage +{ + using StoredType = stack_value_t; + + alignas(StoredType) std::byte data[sizeof(StoredType)]; + bool constructed = false; + + StoredType* ptr() noexcept { return std::launder(reinterpret_cast(data)); } + + void destroy() noexcept + { + if (constructed) + { + std::destroy_at(ptr()); + constructed = false; + } + } +}; + +//================================================================================================= +/** + * @brief Decode one argument from the Lua stack into storage element I. + * + * Sets error_arg/error_msg on the first failure; subsequent calls are no-ops. + */ +template +void decode_arg(lua_State* L, StorageTuple& storage, int& error_arg, const char*& error_msg) { - auto result = Stack::get(L, static_cast(index + start)); + using T = std::tuple_element_t; + using StoredType = typename std::tuple_element_t::StoredType; + + if (error_arg) + return; + + auto result = Stack::get(L, static_cast(I + Start)); if (! result) - raise_lua_error(L, "Error decoding argument #%d: %s", static_cast(index + 1), result.error_cstr()); + { + error_arg = static_cast(I + 1); + error_msg = result.error_cstr(); + return; + } - return std::move(*result); + ::new (std::get(storage).data) StoredType(std::move(*result)); + std::get(storage).constructed = true; } +//================================================================================================= +/** + * @brief Make argument lists extracting them from the lua state, starting at a stack index. + * + * Arguments are decoded sequentially left to right. On failure every already- + * constructed argument is explicitly destroyed before raise_lua_error is called, + * so longjmp never skips a live C++ destructor. + * + * @tparam ArgsPack Arguments pack to extract from the lua stack. + * @tparam Start Start index where stack variables are located in the lua stack. + */ template auto make_arguments_list_impl([[maybe_unused]] lua_State* L, std::index_sequence) { - return tupleize(unwrap_argument_or_error>(L, Indices, Start)...); + std::tuple>...> storage; + + int error_arg = 0; + const char* error_msg = nullptr; + + (decode_arg(L, storage, error_arg, error_msg), ...); + + if (error_arg) + { + (std::get(storage).destroy(), ...); + raise_lua_error(L, "Error decoding argument #%d: %s", error_arg, error_msg); + } + + auto result = tupleize(std::move(*std::get(storage).ptr())...); + (std::get(storage).destroy(), ...); + return result; } template @@ -1482,40 +1559,18 @@ inline void add_property_setter(lua_State* L, const char* name, int tableIndex) /** * @brief Function generator. */ -template -decltype(auto) invoke_callable_from_stack_impl(lua_State* L, F&& func, std::index_sequence) -{ - return std::invoke( - std::forward(func), - unwrap_argument_or_error>(L, Indices, Start)...); -} - template decltype(auto) invoke_callable_from_stack(lua_State* L, F&& func) { - return invoke_callable_from_stack_impl( - L, - std::forward(func), - std::make_index_sequence>()); -} - -template -decltype(auto) invoke_member_callable_from_stack_impl(lua_State* L, T* ptr, F&& func, std::index_sequence) -{ - return std::invoke( - std::forward(func), - ptr, - unwrap_argument_or_error>(L, Indices, Start)...); + return std::apply(std::forward(func), make_arguments_list(L)); } template decltype(auto) invoke_member_callable_from_stack(lua_State* L, T* ptr, F&& func) { - return invoke_member_callable_from_stack_impl( - L, - ptr, + return std::apply( std::forward(func), - std::make_index_sequence>()); + std::tuple_cat(std::tuple(ptr), make_arguments_list(L))); } template @@ -2711,7 +2766,7 @@ int constructor_container_proxy(lua_State* L) try { #endif - object = constructor::construct(detail::make_arguments_list(L)); + object = constructor::construct(make_arguments_list(L)); #if LUABRIDGE_HAS_EXCEPTIONS } @@ -2755,7 +2810,7 @@ int constructor_placement_proxy(lua_State* L) raise_lua_error(L, "%s", e.what()); } #endif - + value->commit(); return 1; @@ -2786,7 +2841,7 @@ struct constructor_forwarder raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); T* object = nullptr; - + #if LUABRIDGE_HAS_EXCEPTIONS try { @@ -2899,27 +2954,56 @@ struct container_forwarder using FnTraits = function_traits; using FnArgs = typename FnTraits::argument_types; - C object; - + alignas(C) std::byte object_storage[sizeof(C)]; + C* object = nullptr; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + object = ::new (object_storage) C( + container_constructor::construct(m_func, make_arguments_list(L))); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + if (object != nullptr) + std::destroy_at(object); + + raise_lua_error(L, "%s", e.what()); + } +#endif + + LUABRIDGE_ASSERT(object != nullptr); + + Result result; + #if LUABRIDGE_HAS_EXCEPTIONS try { #endif - object = container_constructor::construct(m_func, make_arguments_list(L)); + result = UserdataSharedHelper::push(L, *object); #if LUABRIDGE_HAS_EXCEPTIONS } catch (const std::exception& e) { + std::destroy_at(object); + raise_lua_error(L, "%s", e.what()); } #endif - auto result = UserdataSharedHelper::push(L, object); if (! result) + { + std::destroy_at(object); raise_lua_error(L, "%s", result.error_cstr()); + } - return object; + C ret = std::move(*object); + std::destroy_at(object); + return ret; } private: diff --git a/Source/LuaBridge/detail/Config.h b/Source/LuaBridge/detail/Config.h index 7619974e..c1324f91 100644 --- a/Source/LuaBridge/detail/Config.h +++ b/Source/LuaBridge/detail/Config.h @@ -12,6 +12,12 @@ #error LuaBridge 3 requires a compliant C++17 compiler, or C++17 has not been enabled ! #endif +#if __cplusplus >= 202302L || (defined(_MSC_VER) && _HAS_CXX23) +#define LUABRIDGE_CXX23_OR_GREATER 1 +#elif __cplusplus >= 202002L || (defined(_MSC_VER) && _HAS_CXX20) +#define LUABRIDGE_CXX20_OR_GREATER 1 +#endif + #if defined(LUAU_FASTMATH_BEGIN) #define LUABRIDGE_ON_LUAU 1 #elif defined(LUAJIT_VERSION) @@ -24,20 +30,6 @@ #error "Lua headers must be included prior to LuaBridge ones" #endif -/** - * @brief Enable C++20 coroutine integration with Lua coroutines. - * - * Requires C++20 and Lua 5.2+ (lua_yieldk). Not supported on Lua 5.1, LuaJIT, or Luau. - * Define LUABRIDGE_DISABLE_CXX20_COROUTINES to force-disable even when C++20 is available. - */ -#if !defined(LUABRIDGE_HAS_CXX20_COROUTINES) -#if !defined(LUABRIDGE_DISABLE_CXX20_COROUTINES) && (__cplusplus >= 202002L || (defined(_MSC_VER) && _HAS_CXX20)) && !(LUABRIDGE_ON_LUAU || LUABRIDGE_ON_LUAJIT || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 502) -#define LUABRIDGE_HAS_CXX20_COROUTINES 1 -#else -#define LUABRIDGE_HAS_CXX20_COROUTINES 0 -#endif -#endif - #if !defined(LUABRIDGE_HAS_EXCEPTIONS) #if defined(_MSC_VER) #if _CPPUNWIND || _HAS_EXCEPTIONS @@ -135,6 +127,12 @@ #endif #endif + +/** + * @brief Control the assertion mechanism used by the library. + * + * @note By default, assertions are enabled in debug builds and disabled in release builds. Define LUABRIDGE_FORCE_ASSERT_RELEASE to enable assertions even in release builds. + */ #if !defined(LUABRIDGE_ASSERT) #if defined(NDEBUG) && !defined(LUABRIDGE_FORCE_ASSERT_RELEASE) #define LUABRIDGE_ASSERT(expr) ((void)(expr)) @@ -142,3 +140,115 @@ #define LUABRIDGE_ASSERT(expr) assert(expr) #endif #endif + +/** + * @brief Enable C++17 filesystem library support. + * + * Requires C++17 and the filesystem header to be available. + * Define LUABRIDGE_DISABLE_CXX17_FILESYSTEM to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX17_FILESYSTEM) +#if !defined(LUABRIDGE_DISABLE_CXX17_FILESYSTEM) && __has_include() && defined(__cpp_lib_filesystem) +#define LUABRIDGE_HAS_CXX17_FILESYSTEM 1 +#else +#define LUABRIDGE_HAS_CXX17_FILESYSTEM 0 +#endif +#endif + +/** + * @brief Enable C++17 any library support. + * + * Requires C++17 and the any header to be available. + * Define LUABRIDGE_DISABLE_CXX17_ANY to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX17_ANY) +#if !defined(LUABRIDGE_DISABLE_CXX17_ANY) && __has_include() && defined(__cpp_lib_any) +#define LUABRIDGE_HAS_CXX17_ANY 1 +#else +#define LUABRIDGE_HAS_CXX17_ANY 0 +#endif +#endif + +/** + * @brief Enable C++20 span library support. + * + * Requires C++20 and the span header to be available. + * Define LUABRIDGE_DISABLE_CXX20_SPAN to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX20_SPAN) +#if !defined(LUABRIDGE_DISABLE_CXX20_SPAN) && LUABRIDGE_CXX20_OR_GREATER && __has_include() && defined(__cpp_lib_span) +#define LUABRIDGE_HAS_CXX20_SPAN 1 +#else +#define LUABRIDGE_HAS_CXX20_SPAN 0 +#endif +#endif + +/** + * @brief Enable C++20 ranges library support. + * + * Requires C++20 and the ranges header to be available. + * Define LUABRIDGE_DISABLE_CXX20_RANGES to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX20_RANGES) +#if !defined(LUABRIDGE_DISABLE_CXX20_RANGES) && LUABRIDGE_CXX20_OR_GREATER && defined(__cpp_lib_ranges) +#define LUABRIDGE_HAS_CXX20_RANGES 1 +#else +#define LUABRIDGE_HAS_CXX20_RANGES 0 +#endif +#endif + +/** + * @brief Enable C++20 coroutine integration with Lua coroutines. + * + * Requires C++20 and Lua 5.2+ (lua_yieldk). Not supported on Lua 5.1, LuaJIT, or Luau. + * Define LUABRIDGE_DISABLE_CXX20_COROUTINES to force-disable even when C++20 is available. + */ +#if !defined(LUABRIDGE_HAS_CXX20_COROUTINES) +#if !defined(LUABRIDGE_DISABLE_CXX20_COROUTINES) && LUABRIDGE_CXX20_OR_GREATER && !(LUABRIDGE_ON_LUAU || LUABRIDGE_ON_LUAJIT || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 502) +#define LUABRIDGE_HAS_CXX20_COROUTINES 1 +#else +#define LUABRIDGE_HAS_CXX20_COROUTINES 0 +#endif +#endif + +/** + * @brief Enable C++23 expected library support. + * + * Requires C++23 and the expected header to be available. + * Define LUABRIDGE_DISABLE_CXX23_EXPECTED to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX23_EXPECTED) +#if !defined(LUABRIDGE_DISABLE_CXX23_EXPECTED) && LUABRIDGE_CXX23_OR_GREATER && __has_include() && defined(__cpp_lib_expected) +#define LUABRIDGE_HAS_CXX23_EXPECTED 1 +#else +#define LUABRIDGE_HAS_CXX23_EXPECTED 0 +#endif +#endif + +/** + * @brief Enable C++23 flat containers library support. + * + * Requires C++23 and the flat_map header to be available. + * Define LUABRIDGE_DISABLE_CXX23_FLAT_CONTAINERS to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS) +#if !defined(LUABRIDGE_DISABLE_CXX23_FLAT_CONTAINERS) && LUABRIDGE_CXX23_OR_GREATER && __has_include() && __has_include() && defined(__cpp_lib_flat_map) +#define LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS 1 +#else +#define LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS 0 +#endif +#endif + +/** + * @brief Enable C++23 move_only_function library support. + * + * Requires C++23 and move_only_function to be available. + * Define LUABRIDGE_DISABLE_CXX23_MOVE_ONLY_FUNCTION to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION) +#if !defined(LUABRIDGE_DISABLE_CXX23_MOVE_ONLY_FUNCTION) && LUABRIDGE_CXX23_OR_GREATER && defined(__cpp_lib_move_only_function) +#define LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION 1 +#else +#define LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION 0 +#endif +#endif diff --git a/Source/LuaBridge/detail/Coroutine.h b/Source/LuaBridge/detail/Coroutine.h index e7f5f183..95d7b472 100644 --- a/Source/LuaBridge/detail/Coroutine.h +++ b/Source/LuaBridge/detail/Coroutine.h @@ -14,8 +14,7 @@ #if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 || LUABRIDGE_ON_LUAU #ifndef LUABRIDGE_DISABLE_COROUTINE_INTEGRATION -#error "C++20 coroutine integration requires Lua 5.2+ with lua_yieldk support. \ -Define LUABRIDGE_DISABLE_COROUTINE_INTEGRATION to suppress this error." +#error "C++20 coroutine integration requires Lua 5.2+ with lua_yieldk support. Define LUABRIDGE_DISABLE_COROUTINE_INTEGRATION to suppress this error." #endif #else diff --git a/Source/LuaBridge/detail/FuncTraits.h b/Source/LuaBridge/detail/FuncTraits.h index de66b4ab..c2b194ca 100644 --- a/Source/LuaBridge/detail/FuncTraits.h +++ b/Source/LuaBridge/detail/FuncTraits.h @@ -208,6 +208,19 @@ struct has_call_operator> : std::true_t template inline static constexpr bool has_call_operator_v = has_call_operator::value; +template +struct is_move_only_function : std::false_type {}; + +#if LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION +template struct is_move_only_function> : std::true_type {}; +template struct is_move_only_function> : std::true_type {}; +template struct is_move_only_function> : std::true_type {}; +template struct is_move_only_function> : std::true_type {}; +#endif + +template +inline static constexpr bool is_move_only_function_v = is_move_only_function::value; + template struct functor_traits_impl { @@ -219,11 +232,39 @@ struct functor_traits_impl>> : functi }; template -struct functor_traits_impl && std::is_invocable_v>> +struct functor_traits_impl && std::is_invocable_v && !is_move_only_function_v>> : function_traits_base> { }; +#if LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +template +struct functor_traits_impl> + : function_traits_base +{ +}; + +#endif // LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION + //================================================================================================= /** * @brief Traits class for callable objects (e.g. function pointers, lambdas) diff --git a/Source/LuaBridge/detail/Iterator.h b/Source/LuaBridge/detail/Iterator.h index 768c321c..69851092 100644 --- a/Source/LuaBridge/detail/Iterator.h +++ b/Source/LuaBridge/detail/Iterator.h @@ -10,6 +10,11 @@ #include +#if LUABRIDGE_HAS_CXX20_RANGES +#include +#include +#endif + namespace luabridge { //================================================================================================= @@ -33,6 +38,12 @@ class Iterator } } +#if LUABRIDGE_HAS_CXX20_RANGES + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using iterator_concept = std::input_iterator_tag; +#endif + /** * @brief Return an associated Lua state. * @@ -203,4 +214,43 @@ inline Range pairs(const LuaRef& table) return Range{ Iterator(table, false), Iterator(table, true) }; } +#if LUABRIDGE_HAS_CXX20_RANGES + +/** + * @brief Equality comparison for Iterator. + */ +inline bool operator==(const Iterator& lhs, const Iterator& rhs) +{ + if (lhs.isNil() && rhs.isNil()) + return true; + if (lhs.isNil() != rhs.isNil()) + return false; + return lhs.key().rawequal(rhs.key()) && lhs.value().rawequal(rhs.value()); +} + +/** + * @brief Sentinel type for Iterator end detection. + */ +struct IteratorSentinel {}; + +/** + * @brief Sentinel equality: Iterator is at end when isNil(). + */ +inline bool operator==(const Iterator& it, IteratorSentinel) +{ + return it.isNil(); +} + +inline bool operator==(IteratorSentinel, const Iterator& it) +{ + return it.isNil(); +} + +#endif // LUABRIDGE_HAS_CXX20_RANGES + } // namespace luabridge + +#if LUABRIDGE_HAS_CXX20_RANGES +template <> +inline constexpr bool std::ranges::enable_borrowed_range = false; +#endif diff --git a/Source/LuaBridge/detail/Result.h b/Source/LuaBridge/detail/Result.h index 750f5405..6f59c331 100644 --- a/Source/LuaBridge/detail/Result.h +++ b/Source/LuaBridge/detail/Result.h @@ -120,6 +120,16 @@ struct TypeResult return std::move(m_value.value()); } + T* operator->() + { + return &m_value.value(); + } + + const T* operator->() const + { + return &m_value.value(); + } + template T valueOr(U&& defaultValue) const& { diff --git a/Source/LuaBridge/detail/Stack.h b/Source/LuaBridge/detail/Stack.h index 689797be..b1c24641 100644 --- a/Source/LuaBridge/detail/Stack.h +++ b/Source/LuaBridge/detail/Stack.h @@ -25,6 +25,10 @@ #include #include +#if LUABRIDGE_HAS_CXX17_FILESYSTEM +#include +#endif + namespace luabridge { //================================================================================================= @@ -1082,6 +1086,58 @@ struct Stack> } }; +//================================================================================================= +/** + * @brief Stack specialization for `luabridge::Expected`. + */ +template +struct Stack> +{ + using Type = Expected; + + [[nodiscard]] static Result push(lua_State* L, const Type& value) + { + if (value.hasValue()) + { + StackRestore stackRestore(L); + + auto result = Stack::push(L, *value); + if (! result) + return result; + + stackRestore.reset(); + return {}; + } + +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + const auto type = lua_type(L, index); + if (type == LUA_TNIL || type == LUA_TNONE) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto result = Stack::get(L, index); + if (! result) + return result.error(); + + return Type(*result); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + const auto type = lua_type(L, index); + return (type != LUA_TNIL && type != LUA_TNONE) && Stack::isInstance(L, index); + } +}; + //================================================================================================= /** * @brief Stack specialization for `std::pair`. @@ -1520,6 +1576,43 @@ struct Stack [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::isInstance(L, index); } }; +#if LUABRIDGE_HAS_CXX17_FILESYSTEM + +//================================================================================================= +/** + * @brief Stack specialization for `std::filesystem::path`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const std::filesystem::path& path) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlstring(L, path.string().c_str(), path.string().size()); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TSTRING) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + std::size_t len = 0; + const char* str = lua_tolstring(L, index, &len); + return std::filesystem::path(std::string(str, len)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TSTRING; + } +}; +#endif // LUABRIDGE_HAS_CXX17_FILESYSTEM + //================================================================================================= /** * @brief Push an object onto the Lua stack. diff --git a/Source/LuaBridge/detail/TypeTraits.h b/Source/LuaBridge/detail/TypeTraits.h index 38b4a54d..58f895af 100644 --- a/Source/LuaBridge/detail/TypeTraits.h +++ b/Source/LuaBridge/detail/TypeTraits.h @@ -112,6 +112,24 @@ struct ContainerTraits> } }; +/** + * @brief Register unique_ptr support as container. + * + * @note Lua gets a non-owning view of the object. The C++ owner must outlive any Lua reference. + * + * @tparam T Class that is held by the unique_ptr. + */ +template +struct ContainerTraits> +{ + using Type = T; + + static T* get(const std::unique_ptr& c) + { + return c.get(); + } +}; + namespace detail { //================================================================================================= diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 90196775..8bf0c895 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -28,14 +28,21 @@ endif() set (LUABRIDGE_TEST_SOURCE_FILES Source/AmalgamateTests.cpp + Source/AnyTests.cpp Source/ArrayTests.cpp Source/ClassExtensibleTests.cpp Source/ClassTests.cpp Source/CoroutineTests.cpp + Source/DequeTests.cpp Source/DumpTests.cpp Source/EnumTests.cpp Source/ExceptionTests.cpp + Source/ExpectedStackTests.cpp + Source/FilesystemTests.cpp Source/FlagSetTests.cpp + Source/FlatMapTests.cpp + Source/FlatSetTests.cpp + Source/ForwardListTests.cpp Source/IssueTests.cpp Source/IteratorTests.cpp Source/LegacyTests.cpp @@ -43,6 +50,8 @@ set (LUABRIDGE_TEST_SOURCE_FILES Source/ListTests.cpp Source/LuaRefTests.cpp Source/MapTests.cpp + Source/MoveOnlyFunctionTests.cpp + Source/MultiMapTests.cpp Source/MultipleInheritanceTests.cpp Source/NamespaceTests.cpp Source/OptionalTests.cpp @@ -52,12 +61,16 @@ set (LUABRIDGE_TEST_SOURCE_FILES Source/RefCountedPtrTests.cpp Source/ScopeGuardTests.cpp Source/SetTests.cpp + Source/SpanTests.cpp Source/StackTests.cpp + Source/StdExpectedTests.cpp Source/Tests.cpp Source/TestBase.h Source/TestTypes.h Source/TestsMain.cpp + Source/UniquePtrTests.cpp Source/UnorderedMapTests.cpp + Source/UnorderedSetTests.cpp Source/UserdataTests.cpp Source/VariantTests.cpp Source/VectorTests.cpp @@ -272,9 +285,9 @@ macro (add_test_app LUABRIDGE_TEST_NAME LUA_VERSION LUABRIDGE_TEST_LUA_LIBRARY_F endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /W3 /MP /D_CRT_SECURE_NO_WARNINGS") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++${CMAKE_CXX_STANDARD} /W3 /MP /D_CRT_SECURE_NO_WARNINGS") else () - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++${CMAKE_CXX_STANDARD} -Wall") endif () # Dynamic library test diff --git a/Tests/Source/AnyTests.cpp b/Tests/Source/AnyTests.cpp new file mode 100644 index 00000000..59bb353e --- /dev/null +++ b/Tests/Source/AnyTests.cpp @@ -0,0 +1,71 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/Any.h" + +#if LUABRIDGE_HAS_CXX17_ANY + +#include +#include + +struct AnyTests : TestBase +{ +}; + +TEST_F(AnyTests, PushInt) +{ + luabridge::registerAnyPush(L); + + std::any value = 42; + ASSERT_TRUE(luabridge::push(L, value)); + + EXPECT_EQ(42, luabridge::Stack::get(L, -1).value()); + lua_pop(L, 1); +} + +TEST_F(AnyTests, PushString) +{ + luabridge::registerAnyPush(L); + + std::any value = std::string("hello"); + ASSERT_TRUE(luabridge::push(L, value)); + + EXPECT_EQ("hello", luabridge::Stack::get(L, -1).value()); + lua_pop(L, 1); +} + +TEST_F(AnyTests, PushEmpty) +{ + std::any value; + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_TRUE(lua_isnil(L, -1)); + lua_pop(L, 1); +} + +TEST_F(AnyTests, PushUnregisteredType) +{ + struct MyStruct { int x; }; + std::any value = MyStruct{42}; + + auto result = luabridge::push(L, value); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(AnyTests, PushDouble) +{ + luabridge::registerAnyPush(L); + + std::any value = 3.14; + ASSERT_TRUE(luabridge::push(L, value)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_DOUBLE_EQ(3.14, *result); + lua_pop(L, 1); +} + +#endif // LUABRIDGE_HAS_CXX17_ANY diff --git a/Tests/Source/DequeTests.cpp b/Tests/Source/DequeTests.cpp new file mode 100644 index 00000000..daa768e3 --- /dev/null +++ b/Tests/Source/DequeTests.cpp @@ -0,0 +1,174 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2019, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#include "TestBase.h" +#include "TestTypes.h" + +#include "LuaBridge/Deque.h" + +#include + +namespace { +template +std::deque toDeque(const std::vector& vector) +{ + return {vector.begin(), vector.end()}; +} + +template +void checkEquals(const std::deque& expected, const std::deque& actual) +{ + using U = std::decay_t; + + if constexpr (std::is_same_v) + { + for (std::size_t i = 0; i < expected.size(); ++i) + ASSERT_FLOAT_EQ((*std::next(expected.begin(), i)), (*std::next(actual.begin(), i))); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + for (std::size_t i = 0; i < expected.size(); ++i) + ASSERT_DOUBLE_EQ((*std::next(expected.begin(), i)), (*std::next(actual.begin(), i))); + } + else if constexpr (std::is_same_v) + { + for (std::size_t i = 0; i < expected.size(); ++i) + ASSERT_STREQ((*std::next(expected.begin(), i)), (*std::next(actual.begin(), i))); + } + else + { + ASSERT_EQ(expected, actual); + } +} +} // namespace + +template +struct DequeTest : TestBase +{ +}; + +TYPED_TEST_SUITE_P(DequeTest); + +TYPED_TEST_P(DequeTest, LuaRef) +{ + using Traits = TypeTraits; + + this->runLua("result = {" + Traits::list() + "}"); + + std::deque expected = toDeque(Traits::values()); + std::deque actual = this->result(); + + checkEquals(expected, actual); +} + +REGISTER_TYPED_TEST_SUITE_P(DequeTest, LuaRef); + +INSTANTIATE_TYPED_TEST_SUITE_P(DequeTest, DequeTest, TestTypes); + +struct DequeTests : TestBase +{ +}; + +TEST_F(DequeTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(DequeTests, GetWithInvalidItem) +{ + lua_createtable(L, 2, 0); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + lua_pushinteger(L, 2); + lua_pushstring(L, "also_not_an_int"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(DequeTests, PassToFunction) +{ + runLua("function foo (deque) " + " result = deque " + "end"); + + auto foo = luabridge::getGlobal(L, "foo"); + + resetResult(); + + std::deque lvalue{ 10, 20, 30 }; + ASSERT_TRUE(foo.call(lvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(lvalue, result>()); + + resetResult(); + + const std::deque constLvalue = lvalue; + ASSERT_TRUE(foo.call(constLvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(lvalue, result>()); +} + +TEST_F(DequeTests, UnregisteredClass) +{ + struct Unregistered {}; + +#if LUABRIDGE_HAS_EXCEPTIONS + [[maybe_unused]] luabridge::Result r; + ASSERT_THROW((r = luabridge::push(L, std::deque{ Unregistered() })), std::exception); +#else + ASSERT_FALSE((luabridge::push(L, std::deque{ Unregistered() }))); +#endif +} + +TEST_F(DequeTests, IsInstance) +{ + ASSERT_TRUE((luabridge::push(L, std::deque{ 1, 2, 3 }))); + EXPECT_TRUE(luabridge::isInstance>(L, -1)); + + lua_pop(L, 1); + + ASSERT_TRUE((luabridge::push(L, 1))); + EXPECT_FALSE(luabridge::isInstance>(L, -1)); +} + +TEST_F(DequeTests, StackOverflow) +{ + exhaustStackSpace(); + + std::deque value = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + +#if !LUABRIDGE_HAS_EXCEPTIONS +TEST_F(DequeTests, PushUnregisteredWithNoExceptionsShouldFailButRestoreStack) +{ + class Unregistered {}; + + const int initialStackSize = lua_gettop(L); + + lua_pushnumber(L, 1); + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + std::deque v; + v.emplace_back(); + v.emplace_back(); + + auto result = luabridge::Stack::push(L, v); + EXPECT_FALSE(result); + + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + lua_pop(L, 1); + EXPECT_EQ(0, lua_gettop(L) - initialStackSize); +} +#endif diff --git a/Tests/Source/ExpectedStackTests.cpp b/Tests/Source/ExpectedStackTests.cpp new file mode 100644 index 00000000..46dc47b8 --- /dev/null +++ b/Tests/Source/ExpectedStackTests.cpp @@ -0,0 +1,84 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include + +struct ExpectedStackTests : TestBase +{ +}; + +TEST_F(ExpectedStackTests, PushWithValue) +{ + using ExpectedInt = luabridge::Expected; + + ExpectedInt value(42); + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_EQ(LUA_TNUMBER, lua_type(L, -1)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(42, *result); + lua_pop(L, 1); +} + +TEST_F(ExpectedStackTests, PushWithError) +{ + using ExpectedInt = luabridge::Expected; + + ExpectedInt value(luabridge::makeUnexpected(luabridge::makeErrorCode(luabridge::ErrorCode::InvalidTypeCast))); + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_EQ(LUA_TNIL, lua_type(L, -1)); + lua_pop(L, 1); +} + +TEST_F(ExpectedStackTests, GetFromValue) +{ + using ExpectedInt = luabridge::Expected; + + lua_pushinteger(L, 100); + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + ASSERT_TRUE((*result).hasValue()); + EXPECT_EQ(100, (*result).value()); + lua_pop(L, 1); +} + +TEST_F(ExpectedStackTests, GetFromNil) +{ + using ExpectedInt = luabridge::Expected; + + lua_pushnil(L); + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); + lua_pop(L, 1); +} + +TEST_F(ExpectedStackTests, IsInstance) +{ + using ExpectedInt = luabridge::Expected; + + lua_pushinteger(L, 1); + EXPECT_TRUE((luabridge::Stack::isInstance(L, -1))); + lua_pop(L, 1); + + lua_pushnil(L); + EXPECT_FALSE((luabridge::Stack::isInstance(L, -1))); + lua_pop(L, 1); +} + +TEST_F(ExpectedStackTests, PushWithStringValue) +{ + using ExpectedString = luabridge::Expected; + + ExpectedString value(std::string("hello")); + ASSERT_TRUE(luabridge::push(L, value)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ("hello", *result); + lua_pop(L, 1); +} diff --git a/Tests/Source/FilesystemTests.cpp b/Tests/Source/FilesystemTests.cpp new file mode 100644 index 00000000..fd70e85a --- /dev/null +++ b/Tests/Source/FilesystemTests.cpp @@ -0,0 +1,62 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/detail/Config.h" + +#if LUABRIDGE_HAS_CXX17_FILESYSTEM + +#include + +struct FilesystemTests : TestBase +{ +}; + +TEST_F(FilesystemTests, PushAndGet) +{ + std::filesystem::path p("/some/path/to/file.txt"); + + ASSERT_TRUE(luabridge::push(L, p)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(p, *result); + lua_pop(L, 1); +} + +TEST_F(FilesystemTests, GetNonString) +{ + lua_pushnumber(L, 42.0); + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); + lua_pop(L, 1); +} + +TEST_F(FilesystemTests, IsInstance) +{ + ASSERT_TRUE(luabridge::push(L, std::filesystem::path("/tmp/test"))); + EXPECT_TRUE(luabridge::Stack::isInstance(L, -1)); + lua_pop(L, 1); + + lua_pushnumber(L, 1.0); + EXPECT_FALSE(luabridge::Stack::isInstance(L, -1)); + lua_pop(L, 1); +} + +TEST_F(FilesystemTests, LuaRef) +{ + runLua("result = '/tmp/test.lua'"); + auto path = result(); + EXPECT_EQ(std::filesystem::path("/tmp/test.lua"), path); +} + +TEST_F(FilesystemTests, StackOverflow) +{ + exhaustStackSpace(); + ASSERT_FALSE(luabridge::push(L, std::filesystem::path("/some/path"))); +} + +#endif // LUABRIDGE_HAS_CXX17_FILESYSTEM diff --git a/Tests/Source/FlatMapTests.cpp b/Tests/Source/FlatMapTests.cpp new file mode 100644 index 00000000..22dda4f5 --- /dev/null +++ b/Tests/Source/FlatMapTests.cpp @@ -0,0 +1,298 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/FlatMap.h" + +#if LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS + +#include +#include + +namespace { +struct Unregistered +{ + bool operator<(const Unregistered& other) const + { + return true; + } +}; + +struct Data +{ + /* explicit */ Data(int i) : i(i) {} + + int i; +}; + +bool operator==(const Data& lhs, const Data& rhs) +{ + return lhs.i == rhs.i; +} + +bool operator<(const Data& lhs, const Data& rhs) +{ + return lhs.i < rhs.i; +} + +std::ostream& operator<<(std::ostream& lhs, const Data& rhs) +{ + lhs << "{" << rhs.i << "}"; + return lhs; +} + +std::flat_map processValues(const std::flat_map& data) +{ + return data; +} + +std::flat_map processPointers(const std::flat_map& data) +{ + std::flat_map result; + + for (const auto& item : data) + result.emplace(item.first, *item.second); + + return result; +} +} // namespace + +namespace std { +template <> +struct hash +{ + std::size_t operator()(const Unregistered& value) const + { + return 0; + } +}; +} // namespace std + +struct FlatMapTests : TestBase +{ +}; + +TEST_F(FlatMapTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(FlatMapTests, GetWithInvalidValue) +{ + lua_createtable(L, 0, 1); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(FlatMapTests, LuaRef) +{ + { + using FlatMap = std::flat_map; + + const FlatMap expected { {1, 'a'}, {2, 'b'}, {3, 'c'} }; + + runLua("result = {'a', 'b', 'c'}"); + + FlatMap actual = result(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, result()); + } + + { + using FlatMap = std::flat_map; + + const FlatMap expected { {1, "abcdef"}, {2, "bcdef"}, {3, "cdef"} }; + + runLua("result = {'abcdef', 'bcdef', 'cdef'}"); + + FlatMap actual = result(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, result()); + } + +#if !defined(LUABRIDGE_TEST_LUA_VERSION) || LUABRIDGE_TEST_LUA_VERSION > 502 + { + using FlatMap = std::flat_map; + + const FlatMap expected { + { luabridge::LuaRef(L, false), luabridge::LuaRef(L, true) }, + { luabridge::LuaRef(L, 'a'), luabridge::LuaRef(L, "abc") }, + { luabridge::LuaRef(L, 1), luabridge::LuaRef(L, 5) }, + { luabridge::LuaRef(L, 3.14), luabridge::LuaRef(L, -1.1) }, + }; + + runLua("result = {[false] = true, a = 'abc', [1] = 5, [3.14] = -1.1}"); + + auto resultRef = result(); + EXPECT_TRUE(resultRef.isInstance()); + + FlatMap actual = resultRef; + EXPECT_EQ(expected, actual); + + EXPECT_EQ(expected, result()); + } +#endif +} + +TEST_F(FlatMapTests, CastToFlatMap) +{ + using StrToInt = std::flat_map; + runLua("result = {[1] = 2, a = 3}"); + ASSERT_EQ((StrToInt{{"1", 2}, {"a", 3}}), result()); + + using IntToInt = std::flat_map; + runLua("result = {[1] = 2, a = 3}"); + +#if LUABRIDGE_HAS_EXCEPTIONS + ASSERT_ANY_THROW((result())); +#else + ASSERT_DEATH_IF_SUPPORTED((result()), ""); +#endif +} + +TEST_F(FlatMapTests, PassToFunction) +{ + runLua("function foo (map) " + " result = map " + "end"); + + auto foo = luabridge::getGlobal(L, "foo"); + using Int2Bool = std::flat_map; + + resetResult(); + + Int2Bool lvalue{{10, false}, {20, true}, {30, true}}; + ASSERT_TRUE(foo.call(lvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(lvalue, result()); + + resetResult(); + + const Int2Bool constLvalue = lvalue; + ASSERT_TRUE(foo.call(constLvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(constLvalue, result()); +} + +TEST_F(FlatMapTests, PassFromLua) +{ + luabridge::getGlobalNamespace(L) + .beginClass("Data") + .addConstructor() + .endClass() + .addFunction("processValues", &processValues) + .addFunction("processPointers", &processPointers); + + { + resetResult(); + runLua("result = processValues ({[Data (-1)] = Data (2)})"); + std::flat_map expected{{Data(-1), Data(2)}}; + const auto actual = result>(); + ASSERT_EQ(expected, actual); + } + + { + resetResult(); + runLua("result = processPointers ({[Data (3)] = Data (-4)})"); + std::flat_map expected{{Data(3), Data(-4)}}; + const auto actual = result>(); + ASSERT_EQ(expected, actual); + } +} + +TEST_F(FlatMapTests, UnregisteredClass) +{ + { +#if LUABRIDGE_HAS_EXCEPTIONS + [[maybe_unused]] luabridge::Result r; + ASSERT_THROW((r = luabridge::push(L, std::flat_map{ { Unregistered(), 1 } })), std::exception); +#else + ASSERT_FALSE((luabridge::push(L, std::flat_map{ { Unregistered(), 1 } }))); +#endif + } + + { +#if LUABRIDGE_HAS_EXCEPTIONS + [[maybe_unused]] luabridge::Result r; + ASSERT_THROW((r = luabridge::push(L, std::flat_map{ { 1, Unregistered() } })), std::exception); +#else + ASSERT_FALSE((luabridge::push(L, std::flat_map{ { 1, Unregistered() } }))); +#endif + } +} + +TEST_F(FlatMapTests, IsInstance) +{ + ASSERT_TRUE((luabridge::push(L, std::flat_map{ { "x", 1 }, { "y", 2 }, { "z", 3 } }))); + EXPECT_TRUE((luabridge::isInstance>(L, -1))); + + lua_pop(L, 1); + + ASSERT_TRUE((luabridge::push(L, 1))); + EXPECT_FALSE((luabridge::isInstance>(L, -1))); +} + +TEST_F(FlatMapTests, StackOverflow) +{ + exhaustStackSpace(); + + std::flat_map value{ { "x", 1 }, { "y", 2 }, { "z", 3 } }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + +#if !LUABRIDGE_HAS_EXCEPTIONS +TEST_F(FlatMapTests, PushUnregisteredWithNoExceptionsShouldFailButRestoreStack) +{ + { + const int initialStackSize = lua_gettop(L); + + lua_pushnumber(L, 1); + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + std::flat_map v; + v.emplace(std::make_pair(1, Unregistered{})); + v.emplace(std::make_pair(2, Unregistered{})); + + auto result = luabridge::Stack::push(L, v); + EXPECT_FALSE(result); + + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + lua_pop(L, 1); + EXPECT_EQ(0, lua_gettop(L) - initialStackSize); + } + + { + const int initialStackSize = lua_gettop(L); + + lua_pushnumber(L, 1); + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + std::flat_map v; + v.emplace(std::make_pair(Unregistered{}, 1)); + v.emplace(std::make_pair(Unregistered{}, 2)); + + auto result = luabridge::Stack::push(L, v); + EXPECT_FALSE(result); + + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + lua_pop(L, 1); + EXPECT_EQ(0, lua_gettop(L) - initialStackSize); + } +} +#endif + +#endif // LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS diff --git a/Tests/Source/FlatSetTests.cpp b/Tests/Source/FlatSetTests.cpp new file mode 100644 index 00000000..4349b9ed --- /dev/null +++ b/Tests/Source/FlatSetTests.cpp @@ -0,0 +1,260 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2023, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/FlatSet.h" + +#if LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS + +#include +#include + +namespace { +struct Unregistered +{ + bool operator<(const Unregistered& other) const + { + return true; + } +}; + +struct Data +{ + /* explicit */ Data(int i) : i(i) {} + + int i; +}; + +bool operator==(const Data& lhs, const Data& rhs) +{ + return lhs.i == rhs.i; +} + +bool operator<(const Data& lhs, const Data& rhs) +{ + return lhs.i < rhs.i; +} + +std::ostream& operator<<(std::ostream& lhs, const Data& rhs) +{ + lhs << "{" << rhs.i << "}"; + return lhs; +} + +std::flat_set processValues(const std::flat_set& data) +{ + return data; +} + +std::flat_set processPointers(const std::flat_set& data) +{ + std::flat_set result; + + for (const auto& item : data) + result.emplace(*item); + + return result; +} +} // namespace + +namespace std { +template <> +struct hash +{ + std::size_t operator()(const Unregistered& value) const + { + return 0; + } +}; +} // namespace std + +struct FlatSetTests : TestBase +{ +}; + +TEST_F(FlatSetTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(FlatSetTests, LuaRef) +{ + { + using FlatSet = std::flat_set; + + const FlatSet expected { 1, 2, 3 }; + + runLua("result = {1, 2, 3}"); + + FlatSet actual = result(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, result()); + } + + { + using FlatSet = std::flat_set; + + const FlatSet expected { {"abcdef"}, {"bcdef"}, {"cdef"} }; + + runLua("result = {'abcdef', 'bcdef', 'cdef'}"); + + FlatSet actual = result(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, result()); + } + +#if !defined(LUABRIDGE_TEST_LUA_VERSION) || LUABRIDGE_TEST_LUA_VERSION > 502 + { + using FlatSet = std::flat_set; + + const FlatSet expected { + luabridge::LuaRef(L, 'a'), + luabridge::LuaRef(L, 1), + luabridge::LuaRef(L, 3.14), + luabridge::LuaRef(L, "abc"), + luabridge::LuaRef(L, 5), + luabridge::LuaRef(L, -1.1), + }; + + runLua("result = {'a', 1, 3.14, 'abc', 5, -1.1}"); + + auto resultRef = result(); + EXPECT_TRUE(resultRef.isInstance()); + + FlatSet actual = resultRef; + EXPECT_EQ(expected, actual); + + EXPECT_EQ(expected, result()); + } +#endif +} + +TEST_F(FlatSetTests, CastToFlatSet) +{ + using StringFlatSet = std::flat_set; + runLua("result = {'1', 'a'}"); + ASSERT_EQ((StringFlatSet{"1", "a"}), result()); + + using IntFlatSet = std::flat_set; + runLua("result = {2, 'a'}"); + +#if LUABRIDGE_HAS_EXCEPTIONS + ASSERT_ANY_THROW((result())); +#else + ASSERT_DEATH_IF_SUPPORTED((result()), ""); +#endif +} + +TEST_F(FlatSetTests, PassToFunction) +{ + runLua("function foo (set) " + " result = set " + "end"); + + auto foo = luabridge::getGlobal(L, "foo"); + using IntFlatSet = std::flat_set; + + resetResult(); + + IntFlatSet lvalue{ 10, 20, 30 }; + ASSERT_TRUE(foo.call(lvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(lvalue, result()); + + resetResult(); + + const IntFlatSet constLvalue = lvalue; + ASSERT_TRUE(foo.call(constLvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(constLvalue, result()); +} + +TEST_F(FlatSetTests, PassFromLua) +{ + luabridge::getGlobalNamespace(L) + .beginClass("Data") + .addConstructor() + .endClass() + .addFunction("processValues", &processValues) + .addFunction("processPointers", &processPointers); + + { + resetResult(); + runLua("result = processValues ({ Data(-1), Data(2) })"); + std::flat_set expected{ Data(-1), Data(2) }; + const auto actual = result>(); + ASSERT_EQ(expected, actual); + } + + { + resetResult(); + runLua("result = processPointers ({ Data(3), Data(-4) })"); + std::flat_set expected{ Data(3), Data(-4) }; + const auto actual = result>(); + ASSERT_EQ(expected, actual); + } +} + +TEST_F(FlatSetTests, UnregisteredClass) +{ + { +#if LUABRIDGE_HAS_EXCEPTIONS + [[maybe_unused]] luabridge::Result r; + ASSERT_THROW((r = luabridge::push(L, std::flat_set{ Unregistered() })), std::exception); +#else + ASSERT_FALSE((luabridge::push(L, std::flat_set{ Unregistered() }))); +#endif + } +} + +TEST_F(FlatSetTests, IsInstance) +{ + ASSERT_TRUE((luabridge::push(L, std::flat_set{ "x", "y", "z" }))); + EXPECT_TRUE((luabridge::isInstance>(L, -1))); + + lua_pop(L, 1); + + ASSERT_TRUE((luabridge::push(L, 1))); + EXPECT_FALSE((luabridge::isInstance>(L, -1))); +} + +TEST_F(FlatSetTests, StackOverflow) +{ + exhaustStackSpace(); + + std::flat_set value{ "x", "y", "z" }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + +#if !LUABRIDGE_HAS_EXCEPTIONS +TEST_F(FlatSetTests, PushUnregisteredWithNoExceptionsShouldFailButRestoreStack) +{ + { + const int initialStackSize = lua_gettop(L); + + lua_pushnumber(L, 1); + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + std::flat_set v; + v.emplace(Unregistered{}); + v.emplace(Unregistered{}); + + auto result = luabridge::Stack::push(L, v); + EXPECT_FALSE(result); + + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + lua_pop(L, 1); + EXPECT_EQ(0, lua_gettop(L) - initialStackSize); + } +} +#endif + +#endif // LUABRIDGE_HAS_CXX23_FLAT_CONTAINERS diff --git a/Tests/Source/ForwardListTests.cpp b/Tests/Source/ForwardListTests.cpp new file mode 100644 index 00000000..b3f15ae7 --- /dev/null +++ b/Tests/Source/ForwardListTests.cpp @@ -0,0 +1,208 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2019, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#include "TestBase.h" +#include "TestTypes.h" + +#include "LuaBridge/ForwardList.h" + +#include + +namespace { +template +std::forward_list toForwardList(const std::vector& vector) +{ + return {vector.begin(), vector.end()}; +} + +template +void checkEquals(const std::forward_list& expected, const std::forward_list& actual) +{ + using U = std::decay_t; + + if constexpr (std::is_same_v) + { + auto expectedIt = expected.begin(); + auto actualIt = actual.begin(); + while (expectedIt != expected.end() && actualIt != actual.end()) + { + ASSERT_FLOAT_EQ((*expectedIt), (*actualIt)); + ++expectedIt; + ++actualIt; + } + ASSERT_EQ(expectedIt, expected.end()); + ASSERT_EQ(actualIt, actual.end()); + } + else if constexpr (std::is_same_v || std::is_same_v) + { + auto expectedIt = expected.begin(); + auto actualIt = actual.begin(); + while (expectedIt != expected.end() && actualIt != actual.end()) + { + ASSERT_DOUBLE_EQ((*expectedIt), (*actualIt)); + ++expectedIt; + ++actualIt; + } + ASSERT_EQ(expectedIt, expected.end()); + ASSERT_EQ(actualIt, actual.end()); + } + else if constexpr (std::is_same_v) + { + auto expectedIt = expected.begin(); + auto actualIt = actual.begin(); + while (expectedIt != expected.end() && actualIt != actual.end()) + { + ASSERT_STREQ((*expectedIt), (*actualIt)); + ++expectedIt; + ++actualIt; + } + ASSERT_EQ(expectedIt, expected.end()); + ASSERT_EQ(actualIt, actual.end()); + } + else + { + // Convert to vectors for equality comparison + std::vector expectedVec(expected.begin(), expected.end()); + std::vector actualVec(actual.begin(), actual.end()); + ASSERT_EQ(expectedVec, actualVec); + } +} +} // namespace + +template +struct ForwardListTest : TestBase +{ +}; + +TYPED_TEST_SUITE_P(ForwardListTest); + +TYPED_TEST_P(ForwardListTest, LuaRef) +{ + using Traits = TypeTraits; + + this->runLua("result = {" + Traits::list() + "}"); + + std::forward_list expected = toForwardList(Traits::values()); + std::forward_list actual = this->result(); + + checkEquals(expected, actual); +} + +REGISTER_TYPED_TEST_SUITE_P(ForwardListTest, LuaRef); + +INSTANTIATE_TYPED_TEST_SUITE_P(ForwardListTest, ForwardListTest, TestTypes); + +struct ForwardListTests : TestBase +{ +}; + +TEST_F(ForwardListTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(ForwardListTests, GetWithInvalidItem) +{ + lua_createtable(L, 2, 0); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_an_int"); + lua_settable(L, -3); + lua_pushinteger(L, 2); + lua_pushstring(L, "also_not_an_int"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(ForwardListTests, PassToFunction) +{ + runLua("function foo (forwardList) " + " result = forwardList " + "end"); + + auto foo = luabridge::getGlobal(L, "foo"); + + resetResult(); + + std::forward_list lvalue{ 10, 20, 30 }; + ASSERT_TRUE(foo.call(lvalue)); + ASSERT_TRUE(result().isTable()); + + std::vector expectedVec(lvalue.begin(), lvalue.end()); + std::forward_list actualForwardList = result>(); + std::vector actualVec(actualForwardList.begin(), actualForwardList.end()); + ASSERT_EQ(expectedVec, actualVec); + + resetResult(); + + const std::forward_list constLvalue = lvalue; + ASSERT_TRUE(foo.call(constLvalue)); + ASSERT_TRUE(result().isTable()); + + std::forward_list actualForwardList2 = result>(); + std::vector actualVec2(actualForwardList2.begin(), actualForwardList2.end()); + ASSERT_EQ(expectedVec, actualVec2); +} + +TEST_F(ForwardListTests, UnregisteredClass) +{ + struct Unregistered {}; + +#if LUABRIDGE_HAS_EXCEPTIONS + [[maybe_unused]] luabridge::Result r; + ASSERT_THROW((r = luabridge::push(L, std::forward_list{ Unregistered() })), std::exception); +#else + ASSERT_FALSE((luabridge::push(L, std::forward_list{ Unregistered() }))); +#endif +} + +TEST_F(ForwardListTests, IsInstance) +{ + ASSERT_TRUE((luabridge::push(L, std::forward_list{ 1, 2, 3 }))); + EXPECT_TRUE(luabridge::isInstance>(L, -1)); + + lua_pop(L, 1); + + ASSERT_TRUE((luabridge::push(L, 1))); + EXPECT_FALSE(luabridge::isInstance>(L, -1)); +} + +TEST_F(ForwardListTests, StackOverflow) +{ + exhaustStackSpace(); + + std::forward_list value = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + +#if !LUABRIDGE_HAS_EXCEPTIONS +TEST_F(ForwardListTests, PushUnregisteredWithNoExceptionsShouldFailButRestoreStack) +{ + class Unregistered {}; + + const int initialStackSize = lua_gettop(L); + + lua_pushnumber(L, 1); + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + std::forward_list v; + v.emplace_front(); + v.emplace_front(); + + auto result = luabridge::Stack::push(L, v); + EXPECT_FALSE(result); + + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + lua_pop(L, 1); + EXPECT_EQ(0, lua_gettop(L) - initialStackSize); +} +#endif diff --git a/Tests/Source/IteratorTests.cpp b/Tests/Source/IteratorTests.cpp index e0405ab1..dc106745 100644 --- a/Tests/Source/IteratorTests.cpp +++ b/Tests/Source/IteratorTests.cpp @@ -125,3 +125,51 @@ TEST_F(IteratorTests, StackOverflowDuringIteration) lua_settop(L, 0); } + +#if LUABRIDGE_HAS_CXX20_RANGES + +TEST_F(IteratorTests, RangesForLoop) +{ + runLua("result = {10, 20, 30}"); + + std::vector values; + for (auto&& [key, value] : luabridge::pairs(result())) + { + values.push_back(value); + } + EXPECT_EQ(3u, values.size()); +} + +TEST_F(IteratorTests, SentinelEquality) +{ + runLua("result = {1, 2, 3}"); + + luabridge::Iterator begin(result(), false); + luabridge::Iterator end(result(), true); + luabridge::IteratorSentinel sentinel; + + EXPECT_FALSE(begin == sentinel); + EXPECT_TRUE(end == sentinel); + EXPECT_TRUE(sentinel == end); +} + +TEST_F(IteratorTests, IteratorEquality) +{ + runLua("result = {1, 2}"); + + luabridge::Iterator it1(result(), false); + luabridge::Iterator it2(result(), false); + luabridge::Iterator endIt(result(), true); + + EXPECT_TRUE(it1 == it2); + EXPECT_FALSE(it1 == endIt); + EXPECT_TRUE(endIt == endIt); +} + +TEST_F(IteratorTests, EnableBorrowedRangeFalse) +{ + static_assert(!std::ranges::enable_borrowed_range, + "Range should not be a borrowed range"); +} + +#endif // LUABRIDGE_HAS_CXX20_RANGES diff --git a/Tests/Source/LongjmpSafetyTests.cpp b/Tests/Source/LongjmpSafetyTests.cpp new file mode 100644 index 00000000..d66d6a74 --- /dev/null +++ b/Tests/Source/LongjmpSafetyTests.cpp @@ -0,0 +1,680 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2024, kunitoki +// SPDX-License-Identifier: MIT + +// Tests for lua_error (longjmp) safety when Lua is compiled as C. +// +// Background: +// When Lua is compiled as C, lua_error() uses longjmp() rather than throwing +// a C++ exception. longjmp() does NOT call C++ destructors for objects on the +// stack, making it UB (and a resource leak) to have non-trivially-destructible +// C++ objects alive between the longjmp call and the matching setjmp/pcall. +// +// Safety analysis findings: +// +// SAFE paths (trivially-destructible objects only when longjmp fires): +// - Single-argument decode errors: TypeResult in error state holds +// error_code (trivial), not T. T is never constructed. +// - raise_lua_error itself: only va_list (ended before jump), string_view +// (trivially destructible) and lua_Debug (C struct) are on the stack. +// - Direct luaL_error calls in metamethods (__index, __newindex): no +// non-trivial C++ locals exist at the call site. +// - Property getters/setters receiving wrong self type: Userdata::get +// returns TypeResult — pointer type is trivially destructible. +// +// UNSAFE paths (potential UB + resource leak when Lua is compiled as C): +// - Multi-argument function invocation: invoke_callable_from_stack_impl() +// expands the argument pack via std::invoke(func, unwrap_arg(), ...). +// C++ does not guarantee evaluation order of function arguments. If T0 is +// decoded successfully into a non-trivially-destructible temporary and T1 +// fails, the T0 temporary's destructor is skipped by longjmp. +// - make_arguments_list_impl / tupleize: same expansion pattern suffers the +// same problem — std::tuple(arg0, arg1, ...) does not sequence the +// argument evaluations, so earlier successful args can be leaked on longjmp. +// - Constructor placement proxy / constructor_forwarder: after a successful +// make_arguments_list() the resulting tuple (potentially containing strings +// etc.) sits as a local variable. A subsequent raise_lua_error (e.g. when +// UserdataValue::place() fails) longjmps over it without calling destructors. +// +// These issues produce memory leaks (not crashes or corruption) in practice +// because the leaked objects are value copies materialized from Lua strings. +// SSO strings (typically ≤15 chars) involve no heap allocation and are +// effectively harmless; heap-allocated (longer) strings do leak their buffers. +// +// How to detect: +// Run the test suite under AddressSanitizer with LeakSanitizer enabled. +// The TrackedString type below provides a live-instance counter so that tests +// can assert whether destructors were called after a pcall error. + +#include "TestBase.h" + +#include +#include + +namespace { + +// --------------------------------------------------------------------------- +// TrackedString — a string wrapper with a live-instance counter. +// Constructing one increments the counter; destroying one decrements it. +// --------------------------------------------------------------------------- +std::atomic g_tracked_string_count{0}; + +struct TrackedString +{ + std::string value; + + TrackedString() + : value() + { + ++g_tracked_string_count; + } + + explicit TrackedString(std::string v) + : value(std::move(v)) + { + ++g_tracked_string_count; + } + + TrackedString(const TrackedString& o) + : value(o.value) + { + ++g_tracked_string_count; + } + + TrackedString(TrackedString&& o) noexcept + : value(std::move(o.value)) + { + ++g_tracked_string_count; + } + + ~TrackedString() + { + --g_tracked_string_count; + } + + TrackedString& operator=(const TrackedString& o) + { + value = o.value; + return *this; + } + + TrackedString& operator=(TrackedString&& o) noexcept + { + value = std::move(o.value); + return *this; + } +}; + +// A long string guaranteed to exceed the small-string optimisation threshold +// on all common implementations (SSO is typically ≤15 chars on GCC/Clang/MSVC). +constexpr const char* kLongString = "this string is definitely longer than sso threshold on all platforms"; + +} // namespace + +// Register Stack so LuaBridge can push/get it. +namespace luabridge { + +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const TrackedString& v) + { + return Stack::push(L, v.value); + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + auto result = Stack::get(L, index); + if (!result) + return result.error(); + + return TrackedString{std::move(*result)}; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return Stack::isInstance(L, index); + } +}; + +} // namespace luabridge + +// --------------------------------------------------------------------------- +// Test fixture +// --------------------------------------------------------------------------- +struct LongjmpSafetyTests : TestBase +{ + void SetUp() override + { + TestBase::SetUp(); + g_tracked_string_count = 0; + } + + // Helper: verify that the live TrackedString count returned to zero after + // a pcall error. When Lua is compiled as C the longjmp bypasses C++ + // destructors for temporaries inside the CFunction, so the count MAY be + // non-zero. The test is recorded as a non-fatal expectation so that it + // shows up as an informational failure in the CI output without blocking + // other checks. Run with ASAN/LSAN to catch the underlying heap leak. + void expectNoLeak(const char* context) const + { + const int live = g_tracked_string_count.load(); + if (live != 0) + { + ADD_FAILURE() << context << ": " << live << " TrackedString object(s) not destroyed." + << " When Lua is compiled as C, longjmp bypasses C++ destructors" + << " for temporaries in multi-argument CFunction invocations," + << " causing a resource leak. Run with ASAN/LSAN for confirmation."; + } + } +}; + +// =========================================================================== +// 1. Error propagation — single argument, wrong type. +// This path is SAFE: TypeResult in the error state holds only +// an error_code (trivially destructible); TrackedString is never constructed. +// =========================================================================== +TEST_F(LongjmpSafetyTests, SingleArgWrongType_ErrorPropagates) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", +[](TrackedString) { return 0; }); + + auto [ok, msg] = runLuaCaptureError("f(true)"); + + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + // After the error no TrackedString should have been constructed at all + EXPECT_EQ(g_tracked_string_count.load(), 0); +} + +TEST_F(LongjmpSafetyTests, SingleArgWrongType_VMUsableAfterError) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", +[](TrackedString s) { return s.value.size(); }); + + // First call: wrong type + auto [ok1, msg1] = runLuaCaptureError("f(true)"); + EXPECT_FALSE(ok1); + EXPECT_EQ(g_tracked_string_count.load(), 0); + + // Second call: correct type — VM must still be usable + auto [ok2, msg2] = runLuaCaptureError("result = f('hello')"); + EXPECT_TRUE(ok2) << "VM not usable after error: " << msg2; + EXPECT_EQ(result(), static_cast(std::string("hello").size())); +} + +// =========================================================================== +// 2. Two-argument function — first argument wrong type. +// SAFE: longjmp fires before any TrackedString is constructed. +// =========================================================================== +TEST_F(LongjmpSafetyTests, TwoArgs_FirstWrongType_NoConstructionBeforeError) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", +[](TrackedString, TrackedString) { return 0; }); + + auto [ok, msg] = runLuaCaptureError("f(true, 'world')"); + + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + EXPECT_EQ(g_tracked_string_count.load(), 0); +} + +// =========================================================================== +// 3. Two-argument function — second argument wrong type. +// POTENTIALLY UNSAFE: the first TrackedString (from a long string) may be +// constructed before the second argument decode fails and longjmps. +// When Lua is compiled as C, the first TrackedString's destructor is NOT +// called, leaking its heap-allocated buffer. +// Under ASAN/LSAN this shows up as a heap-use-after-return or leak report. +// =========================================================================== +TEST_F(LongjmpSafetyTests, TwoArgs_SecondWrongType_PotentialLeakOfFirstArg) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", +[](TrackedString, TrackedString) { return 0; }); + + // Use a string long enough to force heap allocation (bypass SSO) + std::string lua = std::string("f('") + kLongString + "', true)"; + auto [ok, msg] = runLuaCaptureError(lua.c_str()); + + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + + // When Lua is compiled as C, this expectation MAY FAIL because the first + // TrackedString temporary is leaked by longjmp. That failure is the bug. + expectNoLeak("TwoArgs_SecondWrongType"); +} + +TEST_F(LongjmpSafetyTests, TwoArgs_SecondWrongType_VMUsableAfterError) +{ + int call_count = 0; + luabridge::getGlobalNamespace(L) + .addFunction("f", [&](TrackedString a, TrackedString b) { + ++call_count; + return a.value + b.value; + }); + + // Error call + auto [ok1, _] = runLuaCaptureError( + (std::string("f('") + kLongString + "', true)").c_str()); + EXPECT_FALSE(ok1); + + // Successful call after error — VM must be fully operational + auto [ok2, msg2] = runLuaCaptureError("result = f('hello', ' world')"); + EXPECT_TRUE(ok2) << "VM not usable after error: " << msg2; + EXPECT_EQ(call_count, 1); + EXPECT_EQ(result(), "hello world"); +} + +// =========================================================================== +// 4. Three-argument function — third argument wrong type. +// Two TrackedStrings may be constructed before the third decode longjmps. +// =========================================================================== +TEST_F(LongjmpSafetyTests, ThreeArgs_ThirdWrongType_PotentialLeakOfEarlierArgs) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", +[](TrackedString, TrackedString, TrackedString) { return 0; }); + + std::string arg = std::string("'") + kLongString + "'"; + std::string lua = "f(" + arg + ", " + arg + ", true)"; + auto [ok, msg] = runLuaCaptureError(lua.c_str()); + + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + + expectNoLeak("ThreeArgs_ThirdWrongType"); +} + +// =========================================================================== +// 5. Function returning non-trivial type, invoked with wrong argument. +// =========================================================================== +TEST_F(LongjmpSafetyTests, ReturningNonTrivialType_WrongArgType) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", +[](TrackedString s) -> TrackedString { return s; }); + + auto [ok, msg] = runLuaCaptureError("result = f(true)"); + EXPECT_FALSE(ok); + EXPECT_EQ(g_tracked_string_count.load(), 0); +} + +// =========================================================================== +// 6. Class member function — wrong argument type. +// =========================================================================== +namespace { + +struct SafetyTestClass +{ + std::string concat(TrackedString a, TrackedString b) const + { + return a.value + b.value; + } + + int id = 0; +}; + +} // namespace + +TEST_F(LongjmpSafetyTests, MemberFunction_SecondArgWrongType) +{ + luabridge::getGlobalNamespace(L) + .beginClass("SafetyTestClass") + .addConstructor() + .addFunction("concat", &SafetyTestClass::concat) + .endClass(); + + std::string arg = std::string("'") + kLongString + "'"; + std::string lua = "local o = SafetyTestClass(); result = o:concat(" + arg + ", true)"; + auto [ok, msg] = runLuaCaptureError(lua.c_str()); + + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + + expectNoLeak("MemberFunction_SecondArgWrongType"); +} + +TEST_F(LongjmpSafetyTests, MemberFunction_WrongSelfType_ErrorPropagates) +{ + luabridge::getGlobalNamespace(L) + .beginClass("SafetyTestClass") + .addConstructor() + .addFunction("concat", &SafetyTestClass::concat) + .endClass(); + + // Pass a non-userdata as 'self' — this errors BEFORE any TrackedString is built + auto [ok, msg] = runLuaCaptureError("local o = {}; o:concat('a', 'b')"); + EXPECT_FALSE(ok); + EXPECT_EQ(g_tracked_string_count.load(), 0); +} + +// =========================================================================== +// 7. Class constructor — wrong argument type. +// =========================================================================== +namespace { + +struct ConstructedClass +{ + TrackedString name; + + explicit ConstructedClass(TrackedString n) + : name(std::move(n)) + { + } +}; + +} // namespace + +TEST_F(LongjmpSafetyTests, Constructor_WrongArgType_ErrorPropagates) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ConstructedClass") + .addConstructor() + .endClass(); + + auto [ok, msg] = runLuaCaptureError("local o = ConstructedClass(true)"); + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + // No TrackedString should be constructed if the type check fails first + EXPECT_EQ(g_tracked_string_count.load(), 0); +} + +TEST_F(LongjmpSafetyTests, Constructor_CorrectType_Works) +{ + luabridge::getGlobalNamespace(L) + .beginClass("ConstructedClass") + .addConstructor() + .endClass(); + + auto [ok, msg] = runLuaCaptureError("local o = ConstructedClass('hello')"); + EXPECT_TRUE(ok) << msg; +} + +// =========================================================================== +// 8. Property setter — wrong value type. +// The 'self' pointer (T*) is trivially destructible; only the value type +// matters for leak analysis. +// =========================================================================== +namespace { + +struct PropClass +{ + TrackedString name; +}; + +} // namespace + +TEST_F(LongjmpSafetyTests, PropertySetter_WrongValueType_ErrorPropagates) +{ + luabridge::getGlobalNamespace(L) + .beginClass("PropClass") + .addConstructor() + .addProperty("name", &PropClass::name) + .endClass(); + + // Create object in global scope so it persists across pcall boundary + ASSERT_TRUE(runLua("g_obj = PropClass()")); + // One TrackedString alive: the 'name' member of g_obj + const int count_after_create = g_tracked_string_count.load(); + EXPECT_EQ(count_after_create, 1); + + // Now fail the setter — no additional TrackedString temporary should be created + auto [ok, msg] = runLuaCaptureError("g_obj.name = true"); + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + // Count must not increase: the setter's TypeResult stays in + // error state and never constructs a TrackedString temporary. + EXPECT_EQ(g_tracked_string_count.load(), count_after_create); +} + +// =========================================================================== +// 9. Property getter error — getter failing to push to Lua stack. +// SAFE: getter calls Stack::push, which returns Result (not TypeResult). +// =========================================================================== +TEST_F(LongjmpSafetyTests, PropertyGetter_ReadFromWrongSelfType_ErrorPropagates) +{ + luabridge::getGlobalNamespace(L) + .beginClass("PropClass") + .addConstructor() + .addProperty("name", &PropClass::name) + .endClass(); + + // Access property on wrong type — error before any TrackedString is built + auto [ok, msg] = runLuaCaptureError("local o = {}; local v = o.name"); + // This either errors or returns nil; either way the VM should survive + // (note: raw table access on {} for key "name" returns nil, no error) + // So test member property access on the wrong class userdata: + auto [ok2, msg2] = runLuaCaptureError( + "local o = PropClass(); local v = getmetatable(o).__index(42, 'name')"); + (void)ok2; // may or may not error depending on implementation path +} + +// =========================================================================== +// 10. Lambda-wrapped functions — same safety properties as free functions. +// =========================================================================== +TEST_F(LongjmpSafetyTests, LambdaFunction_SecondArgWrongType) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", [](TrackedString a, TrackedString b) -> std::string { + return a.value + b.value; + }); + + std::string arg = std::string("'") + kLongString + "'"; + std::string lua = "f(" + arg + ", true)"; + auto [ok, msg] = runLuaCaptureError(lua.c_str()); + + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + + expectNoLeak("LambdaFunction_SecondArgWrongType"); +} + +// =========================================================================== +// 11. Overloaded functions — wrong types on all overloads. +// =========================================================================== +TEST_F(LongjmpSafetyTests, OverloadedFunction_AllOverloadsFail) +{ + luabridge::getGlobalNamespace(L) + .addFunction("f", + +[](TrackedString s) -> std::string { return s.value; }, + +[](int n) -> std::string { return std::to_string(n); }); + + // Pass a type that matches neither overload (table) → all overloads fail + auto [ok, msg] = runLuaCaptureError("f({})"); + EXPECT_FALSE(ok); + EXPECT_EQ(g_tracked_string_count.load(), 0); +} + +// =========================================================================== +// 12. Verify Lua VM is fully usable after a sequence of errors. +// =========================================================================== +TEST_F(LongjmpSafetyTests, VMUsableAfterSequenceOfErrors) +{ + int success_count = 0; + + luabridge::getGlobalNamespace(L) + .addFunction("f", [&](TrackedString a, TrackedString b) -> std::string { + ++success_count; + return a.value + "|" + b.value; + }); + + // Series of incorrect calls — use boolean (not number) as wrong type since + // numbers are coerced to string when LUABRIDGE_STRICT_STACK_CONVERSIONS is off + for (int i = 0; i < 5; ++i) + { + std::string lua = (i % 2 == 0) + ? std::string("f('") + kLongString + "', true)" + : "f(true, 'second')"; + auto [ok, _] = runLuaCaptureError(lua.c_str()); + EXPECT_FALSE(ok) << "Call " << i << " should have failed"; + } + + // Correct call must succeed after all the errors + auto [ok, msg] = runLuaCaptureError("result = f('hello', 'world')"); + EXPECT_TRUE(ok) << "VM not usable after errors: " << msg; + EXPECT_EQ(result(), "hello|world"); + EXPECT_EQ(success_count, 1); +} + +// =========================================================================== +// 13. Static function via namespace — same invoke path. +// =========================================================================== +TEST_F(LongjmpSafetyTests, NamespacedStaticFunction_SecondArgWrongType) +{ + luabridge::getGlobalNamespace(L) + .beginNamespace("ns") + .addFunction("f", +[](TrackedString a, TrackedString b) -> int { + return static_cast(a.value.size() + b.value.size()); + }) + .endNamespace(); + + std::string arg = std::string("'") + kLongString + "'"; + auto [ok, msg] = runLuaCaptureError(("ns.f(" + arg + ", {})").c_str()); + + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + + expectNoLeak("NamespacedStaticFunction_SecondArgWrongType"); +} + +// =========================================================================== +// 14. container_forwarder — shared_ptr container constructor safety. +// +// When addConstructorFrom>() is used, LuaBridge creates +// a container_forwarder that: +// 1. Default-constructs C object (empty shared_ptr, no resources). +// 2. Assigns the result of the factory into 'object'. +// 3. Calls UserdataSharedHelper::push(L, object). +// If push fails (e.g. class not registered at push time), it used to +// call raise_lua_error with a fully-constructed shared_ptr on the C++ +// stack. longjmp would skip ~shared_ptr, leaking T. +// FIX: container_forwarder now resets 'object = C{}' before raise_lua_error. +// =========================================================================== +namespace { + +std::atomic g_container_object_count{0}; + +struct ContainerTestObject +{ + explicit ContainerTestObject(int v) + : value(v) + { + ++g_container_object_count; + } + + ~ContainerTestObject() + { + --g_container_object_count; + } + + int value; +}; + +} // namespace + +TEST_F(LongjmpSafetyTests, ContainerForwarder_SuccessfulConstruction_NoLeak) +{ + g_container_object_count = 0; + + luabridge::getGlobalNamespace(L) + .beginClass("ContainerTestObject") + .addConstructorFrom, void(*)(int)>() + .endClass(); + + // Normal construction: object should be alive while referenced by Lua + auto [ok, msg] = runLuaCaptureError("result = ContainerTestObject(42)"); + EXPECT_TRUE(ok) << msg; + EXPECT_EQ(g_container_object_count.load(), 1); // Lua holds the only reference + + // After Lua GC (triggered by closing the state), count should go to 0 + lua_gc(L, LUA_GCCOLLECT, 0); + // Object may or may not be collected yet depending on GC timing, but + // after state close (TearDown) it must be 0 +} + +TEST_F(LongjmpSafetyTests, ContainerForwarder_WrongArgType_ErrorAndNoLeak) +{ + g_container_object_count = 0; + + luabridge::getGlobalNamespace(L) + .beginClass("ContainerTestObject") + .addConstructorFrom, void(*)(int)>() + .endClass(); + + // Wrong argument type: factory should not be called at all + auto [ok, msg] = runLuaCaptureError("result = ContainerTestObject('not_an_int')"); + EXPECT_FALSE(ok); + EXPECT_FALSE(msg.empty()); + // No ContainerTestObject should have been created + EXPECT_EQ(g_container_object_count.load(), 0); +} + +TEST_F(LongjmpSafetyTests, ContainerForwarder_VMUsableAfterConstructionError) +{ + g_container_object_count = 0; + + luabridge::getGlobalNamespace(L) + .beginClass("ContainerTestObject") + .addConstructorFrom, void(*)(int)>() + .endClass(); + + // Error call + auto [ok1, _] = runLuaCaptureError("ContainerTestObject('bad')"); + EXPECT_FALSE(ok1); + + // Success call — VM must still work + auto [ok2, msg2] = runLuaCaptureError("result = ContainerTestObject(99)"); + EXPECT_TRUE(ok2) << msg2; + EXPECT_EQ(g_container_object_count.load(), 1); +} + +// =========================================================================== +// 15. container_forwarder — push-failure path with pre-reset fix. +// +// Simulate the push-failure path: register the class, construct the object +// (factory succeeds), then verify the shared_ptr is properly released. +// In practice, UserdataSharedHelper::push fails when the class is not in +// the registry. We test this by constructing correctly (so we cover the +// full path) and using a tracked shared_ptr to verify refcount semantics. +// =========================================================================== +namespace { + +std::atomic g_shared_object_count{0}; + +struct SharedTestObject +{ + explicit SharedTestObject() + { + ++g_shared_object_count; + } + + ~SharedTestObject() + { + --g_shared_object_count; + } +}; + +} // namespace + +TEST_F(LongjmpSafetyTests, ContainerForwarder_SharedPtrRefcountAfterGC) +{ + g_shared_object_count = 0; + + luabridge::getGlobalNamespace(L) + .beginClass("SharedTestObject") + .addConstructorFrom, void(*)()>() + .endClass(); + + // Construct 3 objects via Lua + ASSERT_TRUE(runLua("local a = SharedTestObject(); local b = SharedTestObject(); local c = SharedTestObject()")); + lua_gc(L, LUA_GCCOLLECT, 0); + + // After GC collects locals (end of chunk), the shared_ptrs should be released. + // The live count should be 0 (or may require another GC cycle): + lua_gc(L, LUA_GCCOLLECT, 0); + // We don't assert exact count here because Lua GC timing varies, + // but after state close (TearDown) it must reach 0. + // Any non-zero count at state close indicates a leak. +} + +// Verify count reaches 0 after state teardown (happens in TearDown via lua_close) +// This is a cross-test assertion: if g_shared_object_count != 0 at process exit +// when combined with LSAN, it flags a leak. diff --git a/Tests/Source/MoveOnlyFunctionTests.cpp b/Tests/Source/MoveOnlyFunctionTests.cpp new file mode 100644 index 00000000..09d5c206 --- /dev/null +++ b/Tests/Source/MoveOnlyFunctionTests.cpp @@ -0,0 +1,63 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#if LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION + +#include + +struct MoveOnlyFunctionTests : TestBase +{ +}; + +TEST_F(MoveOnlyFunctionTests, TraitsNonConst) +{ + using F = std::move_only_function; + + EXPECT_FALSE((luabridge::detail::function_traits::is_member)); + EXPECT_FALSE((luabridge::detail::function_traits::is_const)); + EXPECT_EQ(2u, (luabridge::detail::function_traits::arity)); + + using RetType = typename luabridge::detail::function_traits::result_type; + static_assert(std::is_same_v); +} + +TEST_F(MoveOnlyFunctionTests, TraitsConst) +{ + using F = std::move_only_function; + + EXPECT_FALSE((luabridge::detail::function_traits::is_member)); + EXPECT_TRUE((luabridge::detail::function_traits::is_const)); + EXPECT_EQ(1u, (luabridge::detail::function_traits::arity)); +} + +TEST_F(MoveOnlyFunctionTests, TraitsNoexcept) +{ + using F = std::move_only_function; + + EXPECT_EQ(0u, (luabridge::detail::function_traits::arity)); + + using RetType = typename luabridge::detail::function_traits::result_type; + static_assert(std::is_same_v); +} + +TEST_F(MoveOnlyFunctionTests, RegisterAndCall) +{ + std::move_only_function fn = [](int x) { return x * 2; }; + + luabridge::getGlobalNamespace(L) + .addFunction("double_it", std::function([](int x) { return x * 2; })); + + runLua("result = double_it(21)"); + EXPECT_EQ(42, result()); +} + +TEST_F(MoveOnlyFunctionTests, HasFunctionTraits) +{ + using F = std::move_only_function; + EXPECT_TRUE((luabridge::detail::has_function_traits_v)); +} + +#endif // LUABRIDGE_HAS_CXX23_MOVE_ONLY_FUNCTION diff --git a/Tests/Source/MultiMapTests.cpp b/Tests/Source/MultiMapTests.cpp new file mode 100644 index 00000000..15684ac2 --- /dev/null +++ b/Tests/Source/MultiMapTests.cpp @@ -0,0 +1,263 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/MultiMap.h" +#include "LuaBridge/UnorderedMultiMap.h" + +#include +#include + +//================================================================================================= +// MultiMapTests +//================================================================================================= + +struct MultiMapTests : TestBase +{ +}; + +TEST_F(MultiMapTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(MultiMapTests, PushAndGet) +{ + using MM = std::multimap; + + MM mm; + mm.emplace(1, "a"); + mm.emplace(1, "b"); + mm.emplace(2, "c"); + + ASSERT_TRUE(luabridge::push(L, mm)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(mm, *result); + + lua_pop(L, 1); +} + +TEST_F(MultiMapTests, PushAndGetSingleKey) +{ + using MM = std::multimap; + + MM mm; + mm.emplace("x", 10); + mm.emplace("x", 20); + mm.emplace("x", 30); + + ASSERT_TRUE(luabridge::push(L, mm)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(mm, *result); + + lua_pop(L, 1); +} + +TEST_F(MultiMapTests, PushAndGetEmpty) +{ + using MM = std::multimap; + + MM mm; + + ASSERT_TRUE(luabridge::push(L, mm)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(mm, *result); + + lua_pop(L, 1); +} + +TEST_F(MultiMapTests, GetInvalidInnerValue) +{ + // Create outer table with key=1, inner table has a non-int value + lua_createtable(L, 0, 1); + lua_pushinteger(L, 1); + lua_createtable(L, 1, 0); + lua_pushstring(L, "not_an_int"); + lua_rawseti(L, -2, 1); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(MultiMapTests, GetInvalidInnerTable) +{ + // Create outer table with key=1, value is not a table + lua_createtable(L, 0, 1); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_a_table"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(MultiMapTests, IsInstance) +{ + using MM = std::multimap; + + MM mm; + mm.emplace("x", 1); + mm.emplace("y", 2); + mm.emplace("y", 3); + + ASSERT_TRUE(luabridge::push(L, mm)); + EXPECT_TRUE((luabridge::isInstance(L, -1))); + + lua_pop(L, 1); + + ASSERT_TRUE(luabridge::push(L, 1)); + EXPECT_FALSE((luabridge::isInstance(L, -1))); +} + +TEST_F(MultiMapTests, StackOverflow) +{ + exhaustStackSpace(); + + std::multimap mm; + mm.emplace("x", 1); + mm.emplace("y", 2); + mm.emplace("y", 3); + + ASSERT_FALSE(luabridge::push(L, mm)); +} + +//================================================================================================= +// UnorderedMultiMapTests +//================================================================================================= + +struct UnorderedMultiMapTests : TestBase +{ +}; + +TEST_F(UnorderedMultiMapTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(UnorderedMultiMapTests, PushAndGet) +{ + using UMM = std::unordered_multimap; + + UMM umm; + umm.emplace(1, "a"); + umm.emplace(1, "b"); + umm.emplace(2, "c"); + + ASSERT_TRUE(luabridge::push(L, umm)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(umm, *result); + + lua_pop(L, 1); +} + +TEST_F(UnorderedMultiMapTests, PushAndGetSingleKey) +{ + using UMM = std::unordered_multimap; + + UMM umm; + umm.emplace("x", 10); + umm.emplace("x", 20); + umm.emplace("x", 30); + + ASSERT_TRUE(luabridge::push(L, umm)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(umm, *result); + + lua_pop(L, 1); +} + +TEST_F(UnorderedMultiMapTests, PushAndGetEmpty) +{ + using UMM = std::unordered_multimap; + + UMM umm; + + ASSERT_TRUE(luabridge::push(L, umm)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(umm, *result); + + lua_pop(L, 1); +} + +TEST_F(UnorderedMultiMapTests, GetInvalidInnerValue) +{ + // Create outer table with key=1, inner table has a non-int value + lua_createtable(L, 0, 1); + lua_pushinteger(L, 1); + lua_createtable(L, 1, 0); + lua_pushstring(L, "not_an_int"); + lua_rawseti(L, -2, 1); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(UnorderedMultiMapTests, GetInvalidInnerTable) +{ + // Create outer table with key=1, value is not a table + lua_createtable(L, 0, 1); + lua_pushinteger(L, 1); + lua_pushstring(L, "not_a_table"); + lua_settable(L, -3); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(UnorderedMultiMapTests, IsInstance) +{ + using UMM = std::unordered_multimap; + + UMM umm; + umm.emplace("x", 1); + umm.emplace("y", 2); + umm.emplace("y", 3); + + ASSERT_TRUE(luabridge::push(L, umm)); + EXPECT_TRUE((luabridge::isInstance(L, -1))); + + lua_pop(L, 1); + + ASSERT_TRUE(luabridge::push(L, 1)); + EXPECT_FALSE((luabridge::isInstance(L, -1))); +} + +TEST_F(UnorderedMultiMapTests, StackOverflow) +{ + exhaustStackSpace(); + + std::unordered_multimap umm; + umm.emplace("x", 1); + umm.emplace("y", 2); + umm.emplace("y", 3); + + ASSERT_FALSE(luabridge::push(L, umm)); +} diff --git a/Tests/Source/NamespaceTests.cpp b/Tests/Source/NamespaceTests.cpp index 47135d5a..e8e664ed 100644 --- a/Tests/Source/NamespaceTests.cpp +++ b/Tests/Source/NamespaceTests.cpp @@ -650,6 +650,7 @@ TEST_F(NamespaceTests, StdBindFunctions) TEST_F(NamespaceTests, StdBindFrontFunctions) { +#if LUABRIDGE_CXX20_OR_GREATER { luabridge::getGlobalNamespace(L).addFunction("Function", std::bind_front(&Function, 1)); @@ -657,6 +658,7 @@ TEST_F(NamespaceTests, StdBindFrontFunctions) ASSERT_TRUE(result().isNumber()); ASSERT_EQ(1, result()); } +#endif { luabridge::getGlobalNamespace(L).addFunction("Function", diff --git a/Tests/Source/SpanTests.cpp b/Tests/Source/SpanTests.cpp new file mode 100644 index 00000000..3c28eff6 --- /dev/null +++ b/Tests/Source/SpanTests.cpp @@ -0,0 +1,82 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/Span.h" +#include "LuaBridge/Vector.h" + +#if LUABRIDGE_HAS_CXX20_SPAN + +#include +#include + +struct SpanTests : TestBase +{ +}; + +TEST_F(SpanTests, PushIntSpan) +{ + std::vector data = {1, 2, 3, 4, 5}; + std::span span(data); + + ASSERT_TRUE(luabridge::push(L, span)); + EXPECT_TRUE(lua_istable(L, -1)); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(data, *result); + lua_pop(L, 1); +} + +TEST_F(SpanTests, PushConstSpan) +{ + const std::vector data = {10, 20, 30}; + std::span span(data); + + ASSERT_TRUE(luabridge::push(L, span)); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(std::vector({10, 20, 30}), *result); + lua_pop(L, 1); +} + +TEST_F(SpanTests, PushEmptySpan) +{ + std::vector data; + std::span span(data); + + ASSERT_TRUE(luabridge::push(L, span)); + EXPECT_TRUE(lua_istable(L, -1)); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_TRUE(result); + EXPECT_TRUE(result->empty()); + lua_pop(L, 1); +} + +TEST_F(SpanTests, IsInstance) +{ + std::vector data = {1, 2, 3}; + ASSERT_TRUE(luabridge::push(L, std::span(data))); + EXPECT_TRUE((luabridge::Stack>::isInstance(L, -1))); + lua_pop(L, 1); + + lua_pushnumber(L, 1.0); + EXPECT_FALSE((luabridge::Stack>::isInstance(L, -1))); + lua_pop(L, 1); +} + +TEST_F(SpanTests, StackOverflow) +{ + exhaustStackSpace(); + + std::vector data = {1, 2, 3}; + std::span span(data); + + ASSERT_FALSE(luabridge::push(L, span)); +} + +#endif // LUABRIDGE_HAS_CXX20_SPAN diff --git a/Tests/Source/StackTests.cpp b/Tests/Source/StackTests.cpp index cebf0c3d..78c74a0b 100644 --- a/Tests/Source/StackTests.cpp +++ b/Tests/Source/StackTests.cpp @@ -2959,7 +2959,7 @@ struct Array T& operator[](std::size_t index) { return value; } const T& operator[](std::size_t index) const { return value; } - T value; + T value{}; }; using Color = Array; diff --git a/Tests/Source/StdExpectedTests.cpp b/Tests/Source/StdExpectedTests.cpp new file mode 100644 index 00000000..e15e8f59 --- /dev/null +++ b/Tests/Source/StdExpectedTests.cpp @@ -0,0 +1,92 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/StdExpected.h" + +#if LUABRIDGE_HAS_CXX23_EXPECTED + +#include +#include +#include + +struct StdExpectedTests : TestBase +{ +}; + +TEST_F(StdExpectedTests, PushWithValue) +{ + using StdExpectedInt = std::expected; + + StdExpectedInt value(42); + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_EQ(LUA_TNUMBER, lua_type(L, -1)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ(42, *result); + lua_pop(L, 1); +} + +TEST_F(StdExpectedTests, PushWithError) +{ + using StdExpectedInt = std::expected; + + StdExpectedInt value(std::unexpected(std::make_error_code(std::errc::invalid_argument))); + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_EQ(LUA_TNIL, lua_type(L, -1)); + lua_pop(L, 1); +} + +TEST_F(StdExpectedTests, GetFromValue) +{ + using StdExpectedInt = std::expected; + + lua_pushinteger(L, 100); + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + ASSERT_TRUE(result->has_value()); + EXPECT_EQ(100, result->value()); + lua_pop(L, 1); +} + +TEST_F(StdExpectedTests, GetFromNil) +{ + using StdExpectedInt = std::expected; + + lua_pushnil(L); + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); + lua_pop(L, 1); +} + +TEST_F(StdExpectedTests, IsInstance) +{ + using StdExpectedInt = std::expected; + + lua_pushinteger(L, 1); + EXPECT_TRUE((luabridge::Stack::isInstance(L, -1))); + lua_pop(L, 1); + + lua_pushnil(L); + EXPECT_FALSE((luabridge::Stack::isInstance(L, -1))); + lua_pop(L, 1); +} + +TEST_F(StdExpectedTests, PushWithStringValue) +{ + using StdExpectedString = std::expected; + + StdExpectedString value(std::string("hello")); + ASSERT_TRUE(luabridge::push(L, value)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + EXPECT_EQ("hello", *result); + lua_pop(L, 1); +} + +#endif // LUABRIDGE_HAS_CXX23_EXPECTED diff --git a/Tests/Source/UniquePtrTests.cpp b/Tests/Source/UniquePtrTests.cpp new file mode 100644 index 00000000..639d5e77 --- /dev/null +++ b/Tests/Source/UniquePtrTests.cpp @@ -0,0 +1,126 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include +#include + +namespace { + +struct Widget +{ + explicit Widget(int value) : value(value) {} + int value; + std::string name = "widget"; + + int getValue() const { return value; } + void setValue(int v) { value = v; } + std::string getName() const { return name; } +}; + +} // namespace + +struct UniquePtrTests : TestBase +{ + void SetUp() override + { + TestBase::SetUp(); + + luabridge::getGlobalNamespace(L) + .beginClass("Widget") + .addConstructor() + .addFunction("getValue", &Widget::getValue) + .addFunction("setValue", &Widget::setValue) + .addFunction("getName", &Widget::getName) + .endClass(); + } +}; + +TEST_F(UniquePtrTests, ContainerTraitsTypeAlias) +{ + static_assert(std::is_same_v>::Type, Widget>, + "ContainerTraits>::Type must be T"); +} + +TEST_F(UniquePtrTests, ContainerTraitsGetReturnsRawPtr) +{ + auto widget = std::make_unique(99); + Widget* rawPtr = luabridge::ContainerTraits>::get(widget); + ASSERT_NE(nullptr, rawPtr); + EXPECT_EQ(99, rawPtr->getValue()); +} + +TEST_F(UniquePtrTests, ContainerTraitsGetNullReturnsNullptr) +{ + std::unique_ptr nullPtr; + Widget* rawPtr = luabridge::ContainerTraits>::get(nullPtr); + EXPECT_EQ(nullptr, rawPtr); +} + +TEST_F(UniquePtrTests, IsContainerDetected) +{ + constexpr bool isContainer = luabridge::detail::IsContainer>::value; + EXPECT_TRUE(isContainer); +} + + +TEST_F(UniquePtrTests, RegisterAndCallFunctionWithRawPtr) +{ + auto processWidget = [](const Widget* w) -> int { + return w ? w->getValue() * 2 : -1; + }; + + luabridge::getGlobalNamespace(L) + .addFunction("processWidget", std::function(processWidget)); + + auto widget = std::make_unique(21); + + // Push the raw pointer so Lua can use the registered Widget methods. + // The unique_ptr must outlive this Lua call. + ASSERT_TRUE(luabridge::push(L, widget.get())); + lua_setglobal(L, "w"); + + runLua("result = processWidget(w)"); + EXPECT_EQ(42, result()); +} + +TEST_F(UniquePtrTests, LuaCanCallMethodsOnRawPtrFromUniquePtr) +{ + auto widget = std::make_unique(7); + + // Push the raw pointer extracted from the unique_ptr. + // Lua gets a non-owning view; C++ retains ownership via unique_ptr. + ASSERT_TRUE(luabridge::push(L, widget.get())); + lua_setglobal(L, "w"); + + runLua("result = w:getValue()"); + EXPECT_EQ(7, result()); + + runLua("w:setValue(14)"); + EXPECT_EQ(14, widget->getValue()); + + runLua("result = w:getName()"); + EXPECT_EQ("widget", result()); +} + +TEST_F(UniquePtrTests, CppOwnerMustOutliveLuaReference) +{ + int capturedValue = -1; + + { + auto widget = std::make_unique(55); + + ASSERT_TRUE(luabridge::push(L, widget.get())); + lua_setglobal(L, "w"); + + runLua("result = w:getValue()"); + capturedValue = result(); + + // widget is destroyed here; after this point, using 'w' from Lua would be UB. + // This test verifies that, while the owner is alive, Lua can read the value correctly. + } + + EXPECT_EQ(55, capturedValue); +} diff --git a/Tests/Source/UnorderedSetTests.cpp b/Tests/Source/UnorderedSetTests.cpp new file mode 100644 index 00000000..8a6a915d --- /dev/null +++ b/Tests/Source/UnorderedSetTests.cpp @@ -0,0 +1,237 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2023, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/UnorderedSet.h" + +#include + +struct Unregistered +{ + bool operator<(const Unregistered& other) const { return true; } + bool operator==(const Unregistered& other) const { return true; } +}; + +struct Data +{ + /* explicit */ Data(int i) : i(i) {} + + int i; +}; + +bool operator==(const Data& lhs, const Data& rhs) +{ + return lhs.i == rhs.i; +} + +bool operator<(const Data& lhs, const Data& rhs) +{ + return lhs.i < rhs.i; +} + +std::ostream& operator<<(std::ostream& lhs, const Data& rhs) +{ + lhs << "{" << rhs.i << "}"; + return lhs; +} + +namespace std { +template <> +struct hash +{ + std::size_t operator()(const Unregistered& value) const + { + return 0; + } +}; + +template <> +struct hash +{ + std::size_t operator()(const Data& value) const + { + return std::hash{}(value.i); + } +}; +} // namespace std + +namespace { +std::unordered_set processValues(const std::unordered_set& data) +{ + return data; +} + +std::unordered_set processPointers(const std::unordered_set& data) +{ + std::unordered_set result; + + for (const auto& item : data) + result.emplace(*item); + + return result; +} +} // namespace + +struct UnorderedSetTests : TestBase +{ +}; + +TEST_F(UnorderedSetTests, GetNonTable) +{ + lua_pushnumber(L, 42.0); + + auto result = luabridge::Stack>::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(UnorderedSetTests, LuaRef) +{ + { + using UnorderedSet = std::unordered_set; + + const UnorderedSet expected { 1, 2, 3 }; + + runLua("result = {1, 2, 3}"); + + UnorderedSet actual = result(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, result()); + } + + { + using UnorderedSet = std::unordered_set; + + const UnorderedSet expected { {"abcdef"}, {"bcdef"}, {"cdef"} }; + + runLua("result = {'abcdef', 'bcdef', 'cdef'}"); + + UnorderedSet actual = result(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(expected, result()); + } +} + +TEST_F(UnorderedSetTests, CastToSet) +{ + using StringUnorderedSet = std::unordered_set; + runLua("result = {'1', 'a'}"); + ASSERT_EQ((StringUnorderedSet{"1", "a"}), result()); + + using IntUnorderedSet = std::unordered_set; + runLua("result = {2, 'a'}"); + +#if LUABRIDGE_HAS_EXCEPTIONS + ASSERT_ANY_THROW((result())); +#else + ASSERT_DEATH_IF_SUPPORTED((result()), ""); +#endif +} + +TEST_F(UnorderedSetTests, PassToFunction) +{ + runLua("function foo (unorderedSet) " + " result = unorderedSet " + "end"); + + auto foo = luabridge::getGlobal(L, "foo"); + using IntUnorderedSet = std::unordered_set; + + resetResult(); + + IntUnorderedSet lvalue{ 10, 20, 30 }; + ASSERT_TRUE(foo.call(lvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(lvalue, result()); + + resetResult(); + + const IntUnorderedSet constLvalue = lvalue; + ASSERT_TRUE(foo.call(constLvalue)); + ASSERT_TRUE(result().isTable()); + ASSERT_EQ(constLvalue, result()); +} + +TEST_F(UnorderedSetTests, PassFromLua) +{ + luabridge::getGlobalNamespace(L) + .beginClass("Data") + .addConstructor() + .endClass() + .addFunction("processValues", &processValues) + .addFunction("processPointers", &processPointers); + + { + resetResult(); + runLua("result = processValues ({ Data(-1), Data(2) })"); + std::unordered_set expected{ Data(-1), Data(2) }; + const auto actual = result>(); + ASSERT_EQ(expected, actual); + } + + { + resetResult(); + runLua("result = processPointers ({ Data(3), Data(-4) })"); + std::unordered_set expected{ Data(3), Data(-4) }; + const auto actual = result>(); + ASSERT_EQ(expected, actual); + } +} + +TEST_F(UnorderedSetTests, UnregisteredClass) +{ + { +#if LUABRIDGE_HAS_EXCEPTIONS + [[maybe_unused]] luabridge::Result r; + ASSERT_THROW((r = luabridge::push(L, std::unordered_set{ Unregistered() })), std::exception); +#else + ASSERT_FALSE((luabridge::push(L, std::unordered_set{ Unregistered() }))); +#endif + } +} + +TEST_F(UnorderedSetTests, IsInstance) +{ + ASSERT_TRUE((luabridge::push(L, std::unordered_set{ "x", "y", "z" }))); + EXPECT_TRUE((luabridge::isInstance>(L, -1))); + + lua_pop(L, 1); + + ASSERT_TRUE((luabridge::push(L, 1))); + EXPECT_FALSE((luabridge::isInstance>(L, -1))); +} + +TEST_F(UnorderedSetTests, StackOverflow) +{ + exhaustStackSpace(); + + std::unordered_set value{ "x", "y", "z" }; + + ASSERT_FALSE(luabridge::push(L, value)); +} + +#if !LUABRIDGE_HAS_EXCEPTIONS +TEST_F(UnorderedSetTests, PushUnregisteredWithNoExceptionsShouldFailButRestoreStack) +{ + { + const int initialStackSize = lua_gettop(L); + + lua_pushnumber(L, 1); + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + std::unordered_set v; + v.emplace(Unregistered{}); + v.emplace(Unregistered{}); + + auto result = luabridge::Stack::push(L, v); + EXPECT_FALSE(result); + + EXPECT_EQ(1, lua_gettop(L) - initialStackSize); + + lua_pop(L, 1); + EXPECT_EQ(0, lua_gettop(L) - initialStackSize); + } +} +#endif diff --git a/amalgamate.py b/amalgamate.py index e12f99b5..3378e884 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -16,6 +16,21 @@ INCLUDE_FILE_MATCHER = re.compile(r'#include\s*[<\"]([\w.\\/]*)[>\"]') LOCAL_INCLUDE_FILE_MATCHER = re.compile(r'#include\s*\"([\w.\\/]*)\"') +GUARDED_INCLUDES = [ + { "header": "coroutine", "condition": "(__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L))" }, + { "header": "ranges", "condition": "(__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L))" }, + { "header": "span", "condition": "(__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L))" }, + { "header": "flat_map", "condition": "(__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L))" }, + { "header": "flat_set", "condition": "(__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L))" }, + { "header": "expected", "condition": "(__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L))" }, + { "header": "move_only_function", "condition": "(__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L))" }, +] + +def GetGuardedInclude(header): + for guarded in GUARDED_INCLUDES: + if guarded["header"] == header: + return guarded + return None def IsCppHeaderFile(ext): return ext in CPP_HEADER_FILE_EXT @@ -196,8 +211,22 @@ def WriteAlgamationFiles(self): headerAmalgamation.write(f"// clang-format off\n\n") headerAmalgamation.write(f"#pragma once\n\n") - for header in sorted(list(self.systemHeaders)): - headerAmalgamation.write(f"#include <{header}>\n") + systemHeaders = sorted(list(self.systemHeaders)) + for header in systemHeaders: + if GetGuardedInclude(header) is None: + headerAmalgamation.write(f"#include <{header}>\n") + headerAmalgamation.write("\n") + + for header in systemHeaders: + guard = GetGuardedInclude(header) + if guard is not None: + headerAmalgamation.write(f"#if defined(__has_include) && __has_include(<{header}>)") + if "condition" in guard and guard["condition"] is not None: + headerAmalgamation.write(f" && {guard['condition']}\n") + else: + headerAmalgamation.write("\n") + headerAmalgamation.write(f"#include <{header}>\n") + headerAmalgamation.write(f"#endif\n\n") headerAmalgamation.write("\n") self.AmalgamateQueue(self.headerQueue, headerAmalgamation) diff --git a/justfile b/justfile index c4f7ce6d..d7f209a5 100644 --- a/justfile +++ b/justfile @@ -2,37 +2,45 @@ default: @just -l -generate: - cmake -G Xcode -B Build -DLUABRIDGE_BENCHMARKS=ON . +generate CXX="17": + cmake -G Xcode -B Build{{CXX}} -DLUABRIDGE_BENCHMARKS=ON -DCMAKE_CXX_STANDARD={{CXX}} . + +open CXX="17": + @just generate {{CXX}} + -open Build{{CXX}}/LuaBridge.xcodeproj + +build CXX="17": + @just generate {{CXX}} + cmake --build Build{{CXX}} --config Debug -j8 + +test CXX="17": + @just build {{CXX}} + ctest --test-dir Build{{CXX}} -C Debug -j8 + +test-all: + @just test 17 + @just test 20 + @just test 23 + +sanitize TYPE="address" CXX="17": + cmake -G Xcode -B Build{{CXX}} -DLUABRIDGE_SANITIZE={{TYPE}} . + +benchmark CXX="17": + @just generate {{CXX}} + cmake --build Build{{CXX}} --config Release --target LuaBridge3Benchmark -j8 + cmake --build Build{{CXX}} --config Release --target LuaBridgeVanillaBenchmark -j8 + cmake --build Build{{CXX}} --config Release --target Sol3Benchmark -j8 + ./Build{{CXX}}/Benchmarks/Release/LuaBridge3Benchmark --benchmark_out_format=json --benchmark_out=Build{{CXX}}/LuaBridge3Benchmark.json + ./Build{{CXX}}/Benchmarks/Release/LuaBridgeVanillaBenchmark --benchmark_out_format=json --benchmark_out=Build{{CXX}}/LuaBridgeVanillaBenchmark.json + ./Build{{CXX}}/Benchmarks/Release/Sol3Benchmark --benchmark_out_format=json --benchmark_out=Build{{CXX}}/Sol3Benchmark.json + @just plot {{CXX}} + +plot CXX="17": + uv run --with-requirements Benchmarks/requirements.txt Benchmarks/plot_benchmarks.py --input Build{{CXX}}/*.json --output Images/benchmarks.png -open: generate - -open Build/LuaBridge.xcodeproj - -build: generate - cmake --build Build --config Debug --target LuaBridgeTests53 -j8 - -test: build - ./Build/Tests/Debug/LuaBridgeTests53 - -sanitize TYPE='address': - cmake -G Xcode -B Build -DLUABRIDGE_SANITIZE={{TYPE}} . - -benchmark: - @just generate - cmake --build Build --config Release --target LuaBridge3Benchmark -j8 - cmake --build Build --config Release --target LuaBridgeVanillaBenchmark -j8 - cmake --build Build --config Release --target Sol3Benchmark -j8 - ./Build/Benchmarks/Release/LuaBridge3Benchmark --benchmark_out_format=json --benchmark_out=Build/LuaBridge3Benchmark.json - ./Build/Benchmarks/Release/LuaBridgeVanillaBenchmark --benchmark_out_format=json --benchmark_out=Build/LuaBridgeVanillaBenchmark.json - ./Build/Benchmarks/Release/Sol3Benchmark --benchmark_out_format=json --benchmark_out=Build/Sol3Benchmark.json - @just plot - -plot: - uv run --with-requirements Benchmarks/requirements.txt Benchmarks/plot_benchmarks.py --input Build/*.json --output Images/benchmarks.png +amalgamate: + uv run amalgamate.py clean: rm -rf Build -amalgamate: - uv run amalgamate.py - From 0592d61494d579468eef508742ca725db48d65e3 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 8 May 2026 08:42:25 +0200 Subject: [PATCH 2/2] More testing --- Distribution/LuaBridge/LuaBridge.h | 15 ++++++--- Source/LuaBridge/detail/Config.h | 4 +++ Tests/Source/ExpectedStackTests.cpp | 29 +++++++++++++++++ Tests/Source/FilesystemTests.cpp | 48 +++++++++++++++++++++++++++++ amalgamate.py | 5 +-- 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index daa2e980..dc995e68 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -56,10 +56,6 @@ #include #endif -#if defined(__has_include) && __has_include() && (__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)) -#include -#endif - #if defined(__has_include) && __has_include() && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) #include #endif @@ -68,10 +64,21 @@ #include #endif +#if defined(__has_include) && __has_include() && (__cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)) +#include +#endif + +#if defined(__has_include) && __has_include() +#include +#endif + // Begin File: Source/LuaBridge/detail/Config.h +#if __has_include() +#endif + #if !(__cplusplus >= 201703L || (defined(_MSC_VER) && _HAS_CXX17)) #error LuaBridge 3 requires a compliant C++17 compiler, or C++17 has not been enabled ! #endif diff --git a/Source/LuaBridge/detail/Config.h b/Source/LuaBridge/detail/Config.h index c1324f91..f4f1706f 100644 --- a/Source/LuaBridge/detail/Config.h +++ b/Source/LuaBridge/detail/Config.h @@ -8,6 +8,10 @@ #include +#if __has_include() +#include +#endif + #if !(__cplusplus >= 201703L || (defined(_MSC_VER) && _HAS_CXX17)) #error LuaBridge 3 requires a compliant C++17 compiler, or C++17 has not been enabled ! #endif diff --git a/Tests/Source/ExpectedStackTests.cpp b/Tests/Source/ExpectedStackTests.cpp index 46dc47b8..b58027d3 100644 --- a/Tests/Source/ExpectedStackTests.cpp +++ b/Tests/Source/ExpectedStackTests.cpp @@ -82,3 +82,32 @@ TEST_F(ExpectedStackTests, PushWithStringValue) EXPECT_EQ("hello", *result); lua_pop(L, 1); } + +TEST_F(ExpectedStackTests, PushValueStackOverflow) +{ + using ExpectedInt = luabridge::Expected; + + exhaustStackSpace(); + ExpectedInt value(42); + ASSERT_FALSE(luabridge::push(L, value)); +} + +TEST_F(ExpectedStackTests, PushNilStackOverflow) +{ + using ExpectedInt = luabridge::Expected; + + exhaustStackSpace(); + ExpectedInt value(luabridge::makeUnexpected(luabridge::makeErrorCode(luabridge::ErrorCode::InvalidTypeCast))); + ASSERT_FALSE(luabridge::push(L, value)); +} + +TEST_F(ExpectedStackTests, GetInnerTypeMismatch) +{ + using ExpectedInt = luabridge::Expected; + + lua_pushstring(L, "not_a_number"); + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); + lua_pop(L, 1); +} diff --git a/Tests/Source/FilesystemTests.cpp b/Tests/Source/FilesystemTests.cpp index fd70e85a..c3b7edcf 100644 --- a/Tests/Source/FilesystemTests.cpp +++ b/Tests/Source/FilesystemTests.cpp @@ -59,4 +59,52 @@ TEST_F(FilesystemTests, StackOverflow) ASSERT_FALSE(luabridge::push(L, std::filesystem::path("/some/path"))); } +TEST_F(FilesystemTests, ExpectedPushAndGet) +{ + using ExpectedPath = luabridge::Expected; + + std::filesystem::path p("/some/path/to/file.txt"); + ExpectedPath value(p); + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_EQ(LUA_TSTRING, lua_type(L, -1)); + + auto result = luabridge::Stack::get(L, -1); + ASSERT_TRUE(result); + ASSERT_TRUE((*result).hasValue()); + EXPECT_EQ(p, (*result).value()); + lua_pop(L, 1); +} + +TEST_F(FilesystemTests, ExpectedPushError) +{ + using ExpectedPath = luabridge::Expected; + + ExpectedPath value(luabridge::makeUnexpected(luabridge::makeErrorCode(luabridge::ErrorCode::InvalidTypeCast))); + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_EQ(LUA_TNIL, lua_type(L, -1)); + lua_pop(L, 1); +} + +TEST_F(FilesystemTests, ExpectedGetFromNil) +{ + using ExpectedPath = luabridge::Expected; + + lua_pushnil(L); + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); + lua_pop(L, 1); +} + +TEST_F(FilesystemTests, ExpectedGetInnerTypeMismatch) +{ + using ExpectedPath = luabridge::Expected; + + lua_pushnumber(L, 42.0); + auto result = luabridge::Stack::get(L, -1); + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); + lua_pop(L, 1); +} + #endif // LUABRIDGE_HAS_CXX17_FILESYSTEM diff --git a/amalgamate.py b/amalgamate.py index 3378e884..70181b54 100644 --- a/amalgamate.py +++ b/amalgamate.py @@ -17,6 +17,7 @@ LOCAL_INCLUDE_FILE_MATCHER = re.compile(r'#include\s*\"([\w.\\/]*)\"') GUARDED_INCLUDES = [ + { "header": "version" }, { "header": "coroutine", "condition": "(__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L))" }, { "header": "ranges", "condition": "(__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L))" }, { "header": "span", "condition": "(__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L))" }, @@ -211,8 +212,8 @@ def WriteAlgamationFiles(self): headerAmalgamation.write(f"// clang-format off\n\n") headerAmalgamation.write(f"#pragma once\n\n") - systemHeaders = sorted(list(self.systemHeaders)) - for header in systemHeaders: + systemHeaders = list(self.systemHeaders) + for header in sorted(systemHeaders): if GetGuardedInclude(header) is None: headerAmalgamation.write(f"#include <{header}>\n") headerAmalgamation.write("\n")