From 771241f7211894bbb3bb9871bd905ad9f0dbd5d7 Mon Sep 17 00:00:00 2001 From: Causeless Date: Sat, 24 Oct 2026 16:46:35 +0100 Subject: [PATCH 01/13] Added LuaBridge3 LuaBridge3 is going to replace luabind. This adds its Source/LuaBridge folder as external/include/LuaBridge3/LuaBridge, unmodified, from upstream commit 64b3ec3d265578c742abd5d3939b133353952718 (master, 17 October 2026, version 3.1 in development), along with its licence. Nothing uses it yet. The changes the game needs made to it follow in commits of their own. Co-Authored-By: Claude Opus 5.5 --- Licences/LICENSE.LuaBridge3.txt | 27 + external/include/LuaBridge3/LuaBridge/Any.h | 82 + external/include/LuaBridge3/LuaBridge/Array.h | 83 + external/include/LuaBridge3/LuaBridge/Deque.h | 80 + external/include/LuaBridge3/LuaBridge/Dump.h | 156 + .../include/LuaBridge3/LuaBridge/FlatMap.h | 89 + .../include/LuaBridge3/LuaBridge/FlatSet.h | 84 + .../LuaBridge3/LuaBridge/ForwardList.h | 82 + external/include/LuaBridge3/LuaBridge/List.h | 80 + .../include/LuaBridge3/LuaBridge/LuaBridge.h | 40 + external/include/LuaBridge3/LuaBridge/Map.h | 86 + .../include/LuaBridge3/LuaBridge/MultiMap.h | 107 + external/include/LuaBridge3/LuaBridge/Set.h | 80 + external/include/LuaBridge3/LuaBridge/Span.h | 66 + .../LuaBridge3/LuaBridge/StdExpected.h | 69 + .../LuaBridge3/LuaBridge/UnorderedMap.h | 86 + .../LuaBridge3/LuaBridge/UnorderedMultiMap.h | 107 + .../LuaBridge3/LuaBridge/UnorderedSet.h | 80 + .../include/LuaBridge3/LuaBridge/Variant.h | 94 + .../include/LuaBridge3/LuaBridge/Vector.h | 80 + .../LuaBridge3/LuaBridge/detail/CFunctions.h | 3321 +++++++++++++++++ .../LuaBridge3/LuaBridge/detail/ClassInfo.h | 259 ++ .../LuaBridge3/LuaBridge/detail/Config.h | 272 ++ .../LuaBridge3/LuaBridge/detail/Converter.h | 183 + .../LuaBridge3/LuaBridge/detail/Coroutine.h | 492 +++ .../LuaBridge3/LuaBridge/detail/Enum.h | 67 + .../LuaBridge3/LuaBridge/detail/Errors.h | 123 + .../LuaBridge3/LuaBridge/detail/Expected.h | 1632 ++++++++ .../LuaBridge3/LuaBridge/detail/FlagSet.h | 124 + .../LuaBridge3/LuaBridge/detail/FuncTraits.h | 895 +++++ .../LuaBridge3/LuaBridge/detail/Globals.h | 77 + .../LuaBridge3/LuaBridge/detail/Invoke.h | 263 ++ .../LuaBridge3/LuaBridge/detail/Iterator.h | 256 ++ .../LuaBridge/detail/LuaException.h | 188 + .../LuaBridge3/LuaBridge/detail/LuaHelpers.h | 793 ++++ .../LuaBridge3/LuaBridge/detail/LuaRef.h | 2099 +++++++++++ .../LuaBridge3/LuaBridge/detail/Namespace.h | 2206 +++++++++++ .../LuaBridge3/LuaBridge/detail/Options.h | 50 + .../LuaBridge3/LuaBridge/detail/Overload.h | 84 + .../LuaBridge3/LuaBridge/detail/Result.h | 257 ++ .../LuaBridge3/LuaBridge/detail/ScopeGuard.h | 47 + .../LuaBridge3/LuaBridge/detail/Stack.h | 1726 +++++++++ .../LuaBridge3/LuaBridge/detail/TypeTraits.h | 159 + .../LuaBridge3/LuaBridge/detail/Userdata.h | 1179 ++++++ 44 files changed, 18410 insertions(+) create mode 100644 Licences/LICENSE.LuaBridge3.txt create mode 100644 external/include/LuaBridge3/LuaBridge/Any.h create mode 100644 external/include/LuaBridge3/LuaBridge/Array.h create mode 100644 external/include/LuaBridge3/LuaBridge/Deque.h create mode 100644 external/include/LuaBridge3/LuaBridge/Dump.h create mode 100644 external/include/LuaBridge3/LuaBridge/FlatMap.h create mode 100644 external/include/LuaBridge3/LuaBridge/FlatSet.h create mode 100644 external/include/LuaBridge3/LuaBridge/ForwardList.h create mode 100644 external/include/LuaBridge3/LuaBridge/List.h create mode 100644 external/include/LuaBridge3/LuaBridge/LuaBridge.h create mode 100644 external/include/LuaBridge3/LuaBridge/Map.h create mode 100644 external/include/LuaBridge3/LuaBridge/MultiMap.h create mode 100644 external/include/LuaBridge3/LuaBridge/Set.h create mode 100644 external/include/LuaBridge3/LuaBridge/Span.h create mode 100644 external/include/LuaBridge3/LuaBridge/StdExpected.h create mode 100644 external/include/LuaBridge3/LuaBridge/UnorderedMap.h create mode 100644 external/include/LuaBridge3/LuaBridge/UnorderedMultiMap.h create mode 100644 external/include/LuaBridge3/LuaBridge/UnorderedSet.h create mode 100644 external/include/LuaBridge3/LuaBridge/Variant.h create mode 100644 external/include/LuaBridge3/LuaBridge/Vector.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/CFunctions.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/ClassInfo.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Config.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Converter.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Coroutine.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Enum.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Errors.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Expected.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/FlagSet.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/FuncTraits.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Globals.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Invoke.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Iterator.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/LuaException.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/LuaHelpers.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/LuaRef.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Namespace.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Options.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Overload.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Result.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/ScopeGuard.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Stack.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/TypeTraits.h create mode 100644 external/include/LuaBridge3/LuaBridge/detail/Userdata.h diff --git a/Licences/LICENSE.LuaBridge3.txt b/Licences/LICENSE.LuaBridge3.txt new file mode 100644 index 0000000000..abe126dbca --- /dev/null +++ b/Licences/LICENSE.LuaBridge3.txt @@ -0,0 +1,27 @@ +LuaBridge3 is published under the terms of the MIT License + + http://www.opensource.org/licenses/mit-license.html + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +* Copyright 2020, kunitoki +* Copyright 2019, Dmitry Tarakanov +* Copyright 2012, Vinnie Falco +* Copyright 2008, Nigel Atkinson +* Copyright 2007, Nathan Reed diff --git a/external/include/LuaBridge3/LuaBridge/Any.h b/external/include/LuaBridge3/LuaBridge/Any.h new file mode 100644 index 0000000000..3b753f40fa --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/Array.h b/external/include/LuaBridge3/LuaBridge/Array.h new file mode 100644 index 0000000000..51e289d176 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/Array.h @@ -0,0 +1,83 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2020, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::array`. + */ +template +struct Stack> +{ + using Type = std::array; + + [[nodiscard]] static Result push(lua_State* L, const Type& array) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(Size), 0); + const int tableIndex = lua_gettop(L); + + for (std::size_t i = 0; i < Size; ++i) + { + auto result = Stack::push(L, array[i]); + if (! result) + return result; + + lua_rawseti(L, tableIndex, static_cast(i + 1)); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (get_length(L, index) != Size) + return makeErrorCode(ErrorCode::InvalidTableSizeInCast); + + const StackRestore stackRestore(L); + + Type array; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + int arrayIndex = 0; + while (lua_next(L, absIndex) != 0) + { + auto item = Stack::get(L, -1); + if (!item) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + array[arrayIndex++] = *item; + lua_pop(L, 1); + } + + return array; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index) && get_length(L, index) == Size; + } +}; + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/Deque.h b/external/include/LuaBridge3/LuaBridge/Deque.h new file mode 100644 index 0000000000..20c4421837 --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/Dump.h b/external/include/LuaBridge3/LuaBridge/Dump.h new file mode 100644 index 0000000000..a9899f7c09 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/Dump.h @@ -0,0 +1,156 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2019, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// Copyright 2007, Nathan Reed +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/ClassInfo.h" + +#include +#include + +namespace luabridge { +namespace detail { +inline void putIndent(std::ostream& stream, unsigned level) +{ + for (unsigned i = 0; i < level; ++i) + stream << " "; +} +} // namespace detail + +//================================================================================================= +/** + * @brief Forward for dumpTable. + */ +inline void dumpTable(lua_State* L, int index, unsigned maxDepth = 1, unsigned level = 0, bool newLine = true, std::ostream& stream = std::cerr); + +//================================================================================================= +/** + * @brief Dump a lua value on the stack. + */ +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); + break; + + case LUA_TTHREAD: + stream << "thread@" << lua_tothread(L, index); + break; + + case LUA_TLIGHTUSERDATA: + stream << "lightuserdata@" << lua_touserdata(L, index); + break; + + case LUA_TTABLE: + dumpTable(L, index, maxDepth, level, false, stream); + break; + + case LUA_TUSERDATA: + stream << "userdata@" << lua_touserdata(L, index); + break; + + default: + stream << lua_typename(L, type); + break; + } + + if (newLine) + stream << '\n'; +} + +//================================================================================================= +/** + * @brief Dump a lua table on the stack. + */ +inline void dumpTable(lua_State* L, int index, unsigned maxDepth, unsigned level, bool newLine, std::ostream& stream) +{ + stream << "table@" << lua_topointer(L, index); + + if (level > maxDepth) + { + if (newLine) + stream << '\n'; + + return; + } + + index = lua_absindex(L, index); + + stream << " {"; + + int valuesCount = 0; + + lua_pushnil(L); // Initial key + while (lua_next(L, index)) + { + stream << '\n'; + detail::putIndent(stream, level + 1); + + dumpValue(L, -2, maxDepth, level + 1, false, stream); // Key + stream << ": "; + dumpValue(L, -1, maxDepth, level + 1, false, stream); // Value + stream << ","; + + lua_pop(L, 1); // Value + + ++valuesCount; + } + + if (valuesCount > 0) + { + stream << '\n'; + detail::putIndent(stream, level); + } + + stream << "}"; + + if (newLine) + stream << '\n'; +} + +//================================================================================================= +/** + * @brief Dump the current stack, optionally recursively. + */ +inline void dumpState(lua_State* L, unsigned maxDepth = 1, std::ostream& stream = std::cerr) +{ + stream << "----------------------------------------------" << '\n'; + + int top = lua_gettop(L); + for (int i = 1; i <= top; ++i) + { + stream << "stack #" << i << " (" << -(top - i + 1) << "): "; + + dumpValue(L, i, maxDepth, 0, true, stream); + } +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/FlatMap.h b/external/include/LuaBridge3/LuaBridge/FlatMap.h new file mode 100644 index 0000000000..6c8d507a5c --- /dev/null +++ b/external/include/LuaBridge3/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_MAP + +#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_MAP diff --git a/external/include/LuaBridge3/LuaBridge/FlatSet.h b/external/include/LuaBridge3/LuaBridge/FlatSet.h new file mode 100644 index 0000000000..44db824e40 --- /dev/null +++ b/external/include/LuaBridge3/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_SET + +#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_SET diff --git a/external/include/LuaBridge3/LuaBridge/ForwardList.h b/external/include/LuaBridge3/LuaBridge/ForwardList.h new file mode 100644 index 0000000000..e14019da44 --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/List.h b/external/include/LuaBridge3/LuaBridge/List.h new file mode 100644 index 0000000000..649783e277 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/List.h @@ -0,0 +1,80 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2020, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::array`. + */ +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); + } +}; + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/LuaBridge.h b/external/include/LuaBridge3/LuaBridge/LuaBridge.h new file mode 100644 index 0000000000..ede78604ca --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/LuaBridge.h @@ -0,0 +1,40 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2020, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// Copyright 2007, Nathan Reed +// SPDX-License-Identifier: MIT + +#pragma once + +// All #include dependencies are listed here +// instead of in the individual header files. + +#define LUABRIDGE_MAJOR_VERSION 3 +#define LUABRIDGE_MINOR_VERSION 1 +#define LUABRIDGE_VERSION 301 + +#include "detail/Config.h" + +#include "detail/CFunctions.h" +#include "detail/ClassInfo.h" +#include "detail/Coroutine.h" +#include "detail/Enum.h" +#include "detail/Errors.h" +#include "detail/Expected.h" +#include "detail/FlagSet.h" +#include "detail/FuncTraits.h" +#include "detail/Globals.h" +#include "detail/Invoke.h" +#include "detail/Iterator.h" +#include "detail/LuaException.h" +#include "detail/LuaHelpers.h" +#include "detail/LuaRef.h" +#include "detail/Namespace.h" +#include "detail/Options.h" +#include "detail/Overload.h" +#include "detail/Result.h" +#include "detail/ScopeGuard.h" +#include "detail/Stack.h" +#include "detail/TypeTraits.h" +#include "detail/Userdata.h" diff --git a/external/include/LuaBridge3/LuaBridge/Map.h b/external/include/LuaBridge3/LuaBridge/Map.h new file mode 100644 index 0000000000..e4ac42dec5 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/Map.h @@ -0,0 +1,86 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2018, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::map`. + */ +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 + + 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 diff --git a/external/include/LuaBridge3/LuaBridge/MultiMap.h b/external/include/LuaBridge3/LuaBridge/MultiMap.h new file mode 100644 index 0000000000..0c6b37a828 --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/Set.h b/external/include/LuaBridge3/LuaBridge/Set.h new file mode 100644 index 0000000000..ccb92d7ba7 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/Set.h @@ -0,0 +1,80 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::set`. + */ +template +struct Stack> +{ + using Type = std::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/external/include/LuaBridge3/LuaBridge/Span.h b/external/include/LuaBridge3/LuaBridge/Span.h new file mode 100644 index 0000000000..5db44cf35e --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/StdExpected.h b/external/include/LuaBridge3/LuaBridge/StdExpected.h new file mode 100644 index 0000000000..2eb1f9ece6 --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/UnorderedMap.h b/external/include/LuaBridge3/LuaBridge/UnorderedMap.h new file mode 100644 index 0000000000..cef355ca58 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/UnorderedMap.h @@ -0,0 +1,86 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2019, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::unordered_map`. + */ +template +struct Stack> +{ + using Type = std::unordered_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 diff --git a/external/include/LuaBridge3/LuaBridge/UnorderedMultiMap.h b/external/include/LuaBridge3/LuaBridge/UnorderedMultiMap.h new file mode 100644 index 0000000000..cabcc8b272 --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/UnorderedSet.h b/external/include/LuaBridge3/LuaBridge/UnorderedSet.h new file mode 100644 index 0000000000..c7cadd4624 --- /dev/null +++ b/external/include/LuaBridge3/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/external/include/LuaBridge3/LuaBridge/Variant.h b/external/include/LuaBridge3/LuaBridge/Variant.h new file mode 100644 index 0000000000..dbc279e9b1 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/Variant.h @@ -0,0 +1,94 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::variant`. + */ +template +struct Stack> +{ + using Type = std::variant; + + [[nodiscard]] static Result push(lua_State* L, const Type& variant) + { + return std::visit([L](const auto& value) { return Stack>::push(L, value); }, variant); + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + return tryGet(L, index); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return (Stack::isInstance(L, index) || ...); + } + +private: + template + [[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); + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } +}; + +/** + * @brief Stack specialization for `std::monostate`. + */ +template <> +struct Stack +{ + using Type = std::monostate; + + [[nodiscard]] static Result push(lua_State* L, const Type&) + { +#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) + { + if (lua_isnoneornil(L, index)) + return Type{}; + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_isnoneornil(L, index); + } +}; + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/Vector.h b/external/include/LuaBridge3/LuaBridge/Vector.h new file mode 100644 index 0000000000..c5d94d68b7 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/Vector.h @@ -0,0 +1,80 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2018, Dmitry Tarakanov +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::vector`. + */ +template +struct Stack> +{ + using Type = std::vector; + + [[nodiscard]] static Result push(lua_State* L, const Type& vector) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(vector.size()), 0); + const int tableIndex = lua_gettop(L); + + for (std::size_t i = 0; i < vector.size(); ++i) + { + auto result = Stack::push(L, vector[i]); + if (! result) + return result; + + lua_rawseti(L, tableIndex, static_cast(i + 1)); + } + + 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 vector; + vector.reserve(static_cast(get_length(L, index))); + + 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); + + vector.emplace_back(*item); + lua_pop(L, 1); + } + + return vector; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_istable(L, index); + } +}; + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/CFunctions.h b/external/include/LuaBridge3/LuaBridge/detail/CFunctions.h new file mode 100644 index 0000000000..f0d534486d --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/CFunctions.h @@ -0,0 +1,3321 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2019, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "ClassInfo.h" +#include "Errors.h" +#include "FuncTraits.h" +#include "LuaHelpers.h" +#include "Options.h" +#include "Stack.h" +#include "TypeTraits.h" +#include "Userdata.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace luabridge { + +class LuaRef; + +namespace detail { + +//================================================================================================= +/** + * @brief Extract exactly what Stack::get() produces. + * + * 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 +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) +{ + 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) + { + error_arg = static_cast(I + 1); + error_msg = result.error_cstr(); + return; + } + + ::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) +{ + 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 +auto make_arguments_list(lua_State* L) +{ + return make_arguments_list_impl(L, std::make_index_sequence>()); +} + +//================================================================================================= +/** + * @brief Helpers for iterating through tuple arguments, pushing each argument to the lua stack. + */ +template +auto push_arguments(lua_State*, std::tuple) + -> std::enable_if_t> +{ + return std::make_tuple(Result(), Index + 1); +} + +template +auto push_arguments(lua_State* L, std::tuple t) + -> std::enable_if_t> +{ + using T = std::tuple_element_t>; + + auto result = Stack::push(L, std::get(t)); + if (! result) + return std::make_tuple(result, Index + 1); + + return push_arguments(L, std::move(t)); +} + +//================================================================================================= +/** + * @brief Helpers for iterating through tuple arguments, popping each argument from the lua stack. + */ +template +auto pop_arguments(lua_State*, std::tuple&) + -> std::enable_if_t +{ + return sizeof...(Types); +} + +template +auto pop_arguments(lua_State* L, std::tuple& t) + -> std::enable_if_t +{ + using T = std::tuple_element_t>; + + std::get(t) = Stack::get(L, Start - Index); + + return pop_arguments(L, t); +} + +//================================================================================================= +/** + * @brief Push a tuple on the stack. + */ +template +Result push_tuple_impl(lua_State* L, const Tuple& value, std::index_sequence) +{ + Result result; + (void)((result = Stack>>::push(L, std::get(value)), bool(result)) && ...); + return result; +} + +template +Result push_tuple(lua_State* L, const std::tuple& value) +{ + return push_tuple_impl(L, value, std::index_sequence_for{}); +} + +//================================================================================================= +/** + * @brief Check if a method name is a metamethod. + */ +template +constexpr auto make_array(Args&&... args) +{ + if constexpr (std::is_same_v) + return std::array...>, sizeof...(Args)>{{ std::forward(args)... }}; + else + return std::array{{ std::forward(args)... }}; +} + +inline bool is_metamethod(std::string_view method_name) +{ + static constexpr auto metamethods = make_array( + "__add", + "__band", + "__bnot", + "__bor", + "__bxor", + "__call", + "__close", + "__concat", + "__div", + "__eq", + "__gc", + "__idiv", + "__index", + "__ipairs", + "__le", + "__len", + "__lt", + "__metatable", + "__mod", + "__mode", + "__mul", + "__name", + "__newindex", + "__pairs", + "__pow", + "__shl", + "__shr", + "__sub", + "__tostring", + "__unm" + ); + + if (method_name.size() <= 2 || method_name[0] != '_' || method_name[1] != '_') + return false; + + auto result = std::lower_bound(metamethods.begin(), metamethods.end(), method_name); + return result != metamethods.end() && *result == method_name; +} + +/** + * @brief Check if a method name is a binary operator metamethod. + */ +inline bool is_binary_operator_metamethod(std::string_view method_name) +{ + static constexpr auto metamethods = make_array( + "__add", + "__band", + "__bor", + "__bxor", + "__concat", + "__div", + "__eq", + "__idiv", + "__le", + "__lt", + "__mod", + "__mul", + "__pow", + "__shl", + "__shr", + "__sub" + ); + + if (method_name.size() <= 2 || method_name[0] != '_' || method_name[1] != '_') + return false; + + auto result = std::lower_bound(metamethods.begin(), metamethods.end(), method_name); + return result != metamethods.end() && *result == method_name; +} + +/** + * @brief Get the key at the given stack index as a string, only if it is a string. + * + * Unlike lua_tostring, a number key is not converted in place, so the original key type is preserved for metamethods. + */ +inline const char* get_string_key(lua_State* L, int index) +{ + return lua_type(L, index) == LUA_TSTRING ? lua_tostring(L, index) : nullptr; +} + +/** + * @brief Get a printable name for the key at the given stack index, for error reporting only. + * + * May push a value onto the stack. + */ +inline const char* get_key_name_for_error(lua_State* L, int index) +{ + index = lua_absindex(L, index); + + switch (lua_type(L, index)) + { + case LUA_TSTRING: + return lua_tostring(L, index); + + case LUA_TNUMBER: + lua_pushvalue(L, index); + return lua_tostring(L, -1); + + default: + return lua_typename(L, lua_type(L, index)); + } +} + +inline void rawset_super_method(lua_State* L, int tableIndex, const char* key) +{ + LUABRIDGE_ASSERT(key != nullptr); + + tableIndex = lua_absindex(L, tableIndex); + + // Stack before: ..., value + if (key[0] == '_') + lua_pushliteral(L, "super"); // Stack: ..., value, "super" + else + lua_pushliteral(L, "super_"); // Stack: ..., value, "super_" + + lua_pushstring(L, key); // Stack: ..., value, prefix, key + lua_concat(L, 2); // Stack: ..., value, super_key + lua_insert(L, -2); // Stack: ..., super_key, value + lua_rawset(L, tableIndex); // Pops key/value. Stack: ... +} + +//================================================================================================= +/** + * @brief Make super method name. + */ +inline Options get_class_options(lua_State* L, int index) +{ + LUABRIDGE_ASSERT(lua_istable(L, index)); // Stack: mt + + Options options = defaultOptions; + + lua_rawgetp_x(L, index, getClassOptionsKey()); // Stack: mt, ifb (may be nil) + if (lua_isnumber(L, -1)) + options = Options::fromUnderlying(lua_tointeger(L, -1)); + + lua_pop(L, 1); + + return options; +} + +//================================================================================================= +/** + * @brief Push class or const table onto stack. + */ +inline void push_class_or_const_table(lua_State* L, int index) +{ + LUABRIDGE_ASSERT(lua_istable(L, index)); // Stack: mt + + lua_rawgetp_x(L, index, getClassKey()); // Stack: mt, class table (ct) | nil + if (! lua_istable(L, -1)) // Stack: mt, nil + { + lua_pop(L, 1); // Stack: mt + + lua_rawgetp_x(L, index, getConstKey()); // Stack: mt, const table (co) | nil + if (! lua_istable(L, -1)) // Stack: mt, nil + return; + } +} + +//================================================================================================= +/** + * @brief Look up a static function or property through an object's class metatable. + */ +inline std::optional try_call_instance_static_index(lua_State* L, int classMetatableIndex) +{ + lua_rawgetp_x(L, classMetatableIndex, getStaticKey()); // Stack: ..., static table (st) | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return std::nullopt; + } + + lua_pushvalue(L, 2); // Stack: ..., st, field name + lua_rawget(L, -2); // Stack: ..., st, value | nil + if (! lua_isnil(L, -1)) + { + lua_remove(L, -2); // Stack: ..., value + return 1; + } + + lua_pop(L, 1); // Stack: ..., st + lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: ..., st, propget table (pg) | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 2); + return std::nullopt; + } + + lua_pushvalue(L, 2); // Stack: ..., st, pg, field name + lua_rawget(L, -2); // Stack: ..., st, pg, getter | nil + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 3); + return std::nullopt; + } + + lua_remove(L, -2); // Stack: ..., st, getter + lua_remove(L, -2); // Stack: ..., getter + lua_call(L, 0, 1); // Stack: ..., value + return 1; +} + +/** + * @brief Call a static property setter through an object's class metatable. + */ +inline std::optional try_call_instance_static_newindex(lua_State* L, int classMetatableIndex) +{ + lua_rawgetp_x(L, classMetatableIndex, getStaticKey()); // Stack: ..., static table (st) | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return std::nullopt; + } + + lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: ..., st, propset table (ps) | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 2); + return std::nullopt; + } + + lua_pushvalue(L, 2); // Stack: ..., st, ps, field name + lua_rawget(L, -2); // Stack: ..., st, ps, setter | nil + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 3); + return std::nullopt; + } + + lua_remove(L, -2); // Stack: ..., st, setter + lua_remove(L, -2); // Stack: ..., setter + lua_pushvalue(L, 3); // Stack: ..., setter, new value + lua_call(L, 1, 0); + return 0; +} + +//================================================================================================= +/** + * @brief __index metamethod for a namespace or class static and non-static members. + * + * Retrieves functions from metatables and properties from propget tables. Looks through the class hierarchy if inheritance is present. + */ + +inline std::optional try_call_index_fallback(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getIndexFallbackKey()); // Stack: mt, ifb (may be nil) + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + lua_pushvalue(L, 1); // Stack: mt, ifb, arg1 + lua_pushvalue(L, 2); // Stack: mt, ifb, arg1, arg2 + lua_call(L, 2, 1); // Stack: mt, ifbresult + + if (! lua_isnoneornil(L, -1)) + { + lua_remove(L, -2); // Stack: ifbresult + return 1; + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +inline std::optional try_call_static_index_fallback(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getStaticIndexFallbackKey()); // Stack: mt, ifb (may be nil) + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + lua_pushvalue(L, 2); // Stack: mt, ifb, arg1 (key only, no self for static) + lua_call(L, 1, 1); // Stack: mt, ifbresult + + if (! lua_isnoneornil(L, -1)) + { + lua_remove(L, -2); // Stack: ifbresult + return 1; + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +template +inline std::optional try_call_index_extensible(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + if constexpr (IsObject) + push_class_or_const_table(L, -1); // Stack: mt, cl | co + else + lua_rawgetp_x(L, -1, getStaticKey()); // Stack: mt, st + + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt, cl | co | st + lua_pushvalue(L, 2); // Stack: mt, cl | co | st, field name + lua_rawget(L, -2); // Stack: mt, cl | co | st, ifbresult | nil + + if (! lua_isnoneornil(L, -1)) // Stack: mt, cl | co | st, ifbresult + { + lua_remove(L, -2); // Stack: mt, ifbresult + lua_remove(L, -2); // Stack: ifbresult + return 1; + } + + lua_pop(L, 2); // Stack: mt + return std::nullopt; +} + +/** + * @brief Scan the flattened parent list for an extensible class getter. + * + * Only extensible checks are performed; __index fallbacks are intentionally ignored so that a + * registered getter anywhere in the hierarchy always takes priority over any fallback. + */ +template +inline std::optional try_call_parent_index_extensibles(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + + const Options parentOptions = get_class_options(L, -1); // Stack: mt, parent list, parent mt + if (parentOptions.test(extensibleClass | ~allowOverridingMethods)) + { + if (auto result = try_call_index_extensible(L)) + { + lua_remove(L, -2); // Stack: mt, result + lua_remove(L, -2); // Stack: result + return *result; + } + } + + lua_pop(L, 1); // Stack: mt, parent list + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +/** + * @brief Scan the flattened parent list for a __index fallback. + * + * Only fallbacks are checked; extensible getters are intentionally ignored because they + * have already been searched by try_call_parent_index_extensibles. + */ +template +inline std::optional try_call_parent_index_fallbacks(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + + lua_rawgetp_x(L, -1, getIndexFallbackKey()); // Stack: mt, parent list, parent mt, ifb | nil + if (lua_iscfunction(L, -1)) + { + lua_pushvalue(L, 1); // Stack: mt, parent list, parent mt, ifb, arg1 + lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, ifb, arg1, arg2 + lua_call(L, 2, 1); // Stack: mt, parent list, parent mt, result + + if (! lua_isnoneornil(L, -1)) + { + lua_remove(L, -2); // Stack: mt, parent list, result + lua_remove(L, -2); // Stack: mt, result + lua_remove(L, -2); // Stack: result + return 1; + } + + lua_pop(L, 1); // Stack: mt, parent list, parent mt + } + else + { + lua_pop(L, 1); // Stack: mt, parent list, parent mt + } + + lua_pop(L, 1); // Stack: mt, parent list + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +template +inline int index_metamethod(lua_State* L) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 6, detail::error_lua_stack_overflow); +#endif + + LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name + + lua_getmetatable(L, 1); // Stack: class/const table (mt) + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + // Protect internal meta methods + const char* key = get_string_key(L, 2); + if (key != nullptr && is_metamethod(key)) + { + lua_pushnil(L); + return 1; + } + + for (;;) + { + const Options options = get_class_options(L, -1); // Stack: mt + + // For static __index: the static fallback takes priority over registered static + // property getters so that a user-defined static __index fallback can shadow + // static properties. + // For instance __index with allowOverridingMethods: the instance fallback takes + // priority over class-table Lua methods, enabling Lua-side method overrides via + // __newindex. + if constexpr (IsObject) + { + if (options.test(extensibleClass | allowOverridingMethods)) + { + if (auto result = try_call_index_fallback(L)) + return *result; + } + } + else + { + if (auto result = try_call_static_index_fallback(L)) + return *result; + } + + // Search into self or metatable + if (lua_istable(L, 1)) + { + if constexpr (IsObject) + lua_pushvalue(L, 1); // Stack: mt, self + else + push_class_or_const_table(L, -1); // Stack: mt, cl | co + + if (lua_istable(L, -1)) + { + lua_pushvalue(L, 2); // Stack: mt, self | cl | co, field name + lua_rawget(L, -2); // Stack: mt, self | cl | co, field | nil + lua_remove(L, -2); // Stack: mt, field | nil + if (! lua_isnil(L, -1)) // Stack: mt, field + { + lua_remove(L, -2); // Stack: field + return 1; + } + } + + lua_pop(L, 1); // Stack: mt + } + + lua_pushvalue(L, 2); // Stack: mt, field name + lua_rawget(L, -2); // Stack: mt, field | nil + if (! lua_isnil(L, -1)) // Stack: mt, field + { + lua_remove(L, -2); // Stack: field + return 1; + } + + LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil + lua_pop(L, 1); // Stack: mt + + // Repeat the lookup in the index extensible, for method overrides + if (options.test(extensibleClass | allowOverridingMethods)) + { + if (auto result = try_call_index_extensible(L)) + return *result; + } + + // Try in the propget key + lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, propget table (pg) + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + lua_pushvalue(L, 2); // Stack: mt, pg, field name + lua_rawget(L, -2); // Stack: mt, pg, getter | nil + lua_remove(L, -2); // Stack: mt, getter | nil + + if (lua_iscfunction(L, -1)) // Stack: mt, getter + { + lua_remove(L, -2); // Stack: getter + lua_pushvalue(L, 1); // Stack: getter, table | userdata + lua_call(L, 1, 1); // Stack: value + return 1; + } + + LUABRIDGE_ASSERT(lua_isnil(L, -1)); // Stack: mt, nil + lua_pop(L, 1); // Stack: mt + + if constexpr (IsObject) + { + if (auto result = try_call_instance_static_index(L, -1)) + return *result; + } + + // It may mean that the field may be in const table and it's constness violation. + + // Search flattened parent list in declaration-order DFS. + lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil + + if (lua_istable(L, -1)) + { + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + + lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, field name + lua_rawget(L, -2); // Stack: mt, parent list, parent mt, field | nil + if (! lua_isnil(L, -1)) + { + lua_remove(L, -2); // Stack: mt, parent list, field + lua_remove(L, -2); // Stack: mt, field + lua_remove(L, -2); // Stack: field + return 1; + } + lua_pop(L, 1); // Stack: mt, parent list, parent mt + + lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, parent list, parent mt, pg | nil + if (lua_istable(L, -1)) + { + lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, pg, field name + lua_rawget(L, -2); // Stack: mt, parent list, parent mt, pg, getter | nil + lua_remove(L, -2); // Stack: mt, parent list, parent mt, getter | nil + if (lua_iscfunction(L, -1)) + { + lua_remove(L, -2); // Stack: mt, parent list, getter + lua_remove(L, -2); // Stack: mt, getter + lua_remove(L, -2); // Stack: getter + lua_pushvalue(L, 1); // Stack: getter, table | userdata + lua_call(L, 1, 1); // Stack: value + return 1; + } + + lua_pop(L, 1); // Stack: mt, parent list, parent mt + } + else + { + lua_pop(L, 1); // Stack: mt, parent list, parent mt + } + + lua_pop(L, 1); // Stack: mt, parent list + } + } + + lua_pop(L, 2); // Stack: - + break; + } + + lua_getmetatable(L, 1); // Stack: class/const table (mt) + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + const Options options = get_class_options(L, -1); // Stack: mt + if (options.test(extensibleClass | ~allowOverridingMethods)) + { + if (auto result = try_call_index_extensible(L)) + return *result; + } + + // Before consulting any __index fallback, scan the entire parent hierarchy for a + // matching extensible getter. This ensures that a registered getter anywhere in the + // inheritance chain always takes priority over a __index fallback defined at a + // narrower scope (e.g. an intermediate or leaf class). + if (auto result = try_call_parent_index_extensibles(L)) + return *result; + + if constexpr (IsObject) + { + if (auto result = try_call_index_fallback(L)) + return *result; + } + else + { + if (auto result = try_call_static_index_fallback(L)) + return *result; + } + + if (auto result = try_call_parent_index_fallbacks(L)) + return *result; + + lua_pop(L, 1); // Stack: - + lua_pushnil(L); + return 1; + + // no return +} + +template +inline int index_metamethod_simple(lua_State* L) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 3, detail::error_lua_stack_overflow); +#endif + + LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); + + // Fast path for simple userdata objects: when registration provides propget and + // method tables as closure upvalues, avoid metatable/pointer-key lookups. + if constexpr (IsObject) + { + if (lua_isuserdata(L, 1)) + { + const char* key = get_string_key(L, 2); + + const auto rawlookup = [L](int tableIndex) + { + lua_pushvalue(L, 2); + lua_rawget(L, tableIndex); + }; + + rawlookup(lua_upvalueindex(1)); // Stack: getter | nil + if (lua_iscfunction(L, -1)) + { + lua_pushvalue(L, 1); // Stack: getter, self + lua_call(L, 1, 1); // Stack: value + return 1; + } + + lua_pop(L, 1); + + if (key != nullptr && is_metamethod(key)) + { + lua_pushnil(L); + return 1; + } + + rawlookup(lua_upvalueindex(2)); // Stack: value | nil + if (! lua_isnil(L, -1)) + return 1; + + lua_pop(L, 1); + + if (auto result = try_call_instance_static_index(L, lua_upvalueindex(2))) + return *result; + + lua_pushnil(L); + return 1; + } + } + else + { + // Fast path for simple static tables: capture propget/class/metatable as upvalues. + if (lua_istable(L, 1)) + { + const char* key = get_string_key(L, 2); + + const auto rawlookup = [L](int tableIndex) + { + lua_pushvalue(L, 2); + lua_rawget(L, tableIndex); + }; + + if (key != nullptr && is_metamethod(key)) + { + lua_pushnil(L); + return 1; + } + + if (lua_istable(L, lua_upvalueindex(2))) + { + rawlookup(lua_upvalueindex(2)); // Stack: value | nil + if (! lua_isnil(L, -1)) + return 1; + + lua_pop(L, 1); + } + + rawlookup(lua_upvalueindex(3)); // Stack: value | nil + if (! lua_isnil(L, -1)) + return 1; + + lua_pop(L, 1); + + rawlookup(lua_upvalueindex(1)); // Stack: getter | nil + if (lua_iscfunction(L, -1)) + { + lua_pushvalue(L, 1); // Stack: getter, self + lua_call(L, 1, 1); // Stack: value + return 1; + } + + lua_pop(L, 1); + lua_pushnil(L); + return 1; + } + } + + lua_getmetatable(L, 1); // Stack: mt + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + const char* key = get_string_key(L, 2); + + const auto rawlookup = [L]() + { + lua_pushvalue(L, 2); + lua_rawget(L, -2); + }; + + // For userdata instance property access, checking propget first avoids an always-miss lookup + // in the class table for common property keys. + if (lua_isuserdata(L, 1)) + { + lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, pg + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + rawlookup(); // Stack: mt, pg, getter | nil + lua_remove(L, -2); // Stack: mt, getter | nil + + if (lua_iscfunction(L, -1)) + { + lua_remove(L, -2); // Stack: getter + lua_pushvalue(L, 1); // Stack: getter, self + lua_call(L, 1, 1); // Stack: value + return 1; + } + + lua_pop(L, 1); // Stack: mt + } + if (key != nullptr && is_metamethod(key)) + { + lua_pushnil(L); + return 1; + } + + if (lua_istable(L, 1)) + { + if constexpr (IsObject) + lua_pushvalue(L, 1); // Stack: mt, self + else + push_class_or_const_table(L, -1); // Stack: mt, cl | co + + if (lua_istable(L, -1)) + { + rawlookup(); // Stack: mt, self | cl | co, value | nil + lua_remove(L, -2); // Stack: mt, value | nil + if (! lua_isnil(L, -1)) + { + lua_remove(L, -2); // Stack: value + return 1; + } + } + + lua_pop(L, 1); // Stack: mt + } + + rawlookup(); // Stack: mt, value | nil + if (! lua_isnil(L, -1)) + { + lua_remove(L, -2); // Stack: value + return 1; + } + + lua_pop(L, 1); // Stack: mt + + lua_rawgetp_x(L, -1, getPropgetKey()); // Stack: mt, pg + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + rawlookup(); // Stack: mt, pg, getter | nil + lua_remove(L, -2); // Stack: mt, getter | nil + + if (lua_iscfunction(L, -1)) + { + lua_remove(L, -2); // Stack: getter + lua_pushvalue(L, 1); // Stack: getter, self + lua_call(L, 1, 1); // Stack: value + return 1; + } + + lua_pop(L, 2); // Stack: - + lua_pushnil(L); + return 1; +} + +//================================================================================================= +/** + * @brief __newindex metamethod for non-static members. + * + * Retrieves properties from propset tables. + */ + +inline std::optional try_call_newindex_fallback(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); // Stack: mt, nifb (may be nil) + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + lua_pushvalue(L, 1); // stack: mt, nifb, arg1 + lua_pushvalue(L, 2); // stack: mt, nifb, arg1, arg2 + lua_pushvalue(L, 3); // stack: mt, nifb, arg1, arg2, arg3 + lua_call(L, 3, 0); // stack: mt + + return 0; +} + +inline std::optional try_call_static_newindex_fallback(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getStaticNewIndexFallbackKey()); // Stack: mt, nifb (may be nil) + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + lua_pushvalue(L, 2); // stack: mt, nifb, arg1 (key only, no self for static) + lua_pushvalue(L, 3); // stack: mt, nifb, arg1, arg2 (value) + lua_call(L, 2, 0); // stack: mt + + return 0; +} + +inline std::optional try_call_newindex_extensible(lua_State* L, const char* key) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + // For Lua function values (instance methods added from Lua), capture the originating class + // table so the method is stored there rather than in a traversed-to parent class table. + // This prevents e.g. `function Derived:init()` from polluting `Base`'s method table. + // + // For non-function values (static properties like `Class.prop = val`), the original + // traversal-end behaviour is preserved: storing them in the nearest class table that + // already has a matching key (or the topmost base if none exists). This keeps static + // properties out of the derived class table, which is also used for instance extensible + // method lookup, avoiding unintended shadowing of per-instance properties accessed via an + // index-fallback metamethod registered on a non-extensible base class. + const bool value_is_function = lua_isfunction(L, 3); + + if (value_is_function) + { + // Capture the original (most-derived) class table — always write here. + push_class_or_const_table(L, -1); // Stack: mt, orig_ct | nil + if (! lua_istable(L, -1)) // Stack: mt, nil + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + // Stack: mt, orig_ct + + const int mtIndex = lua_absindex(L, -2); + const int origClassTableIndex = lua_absindex(L, -1); + const auto process_metatable = [=](int candidateMtIndex) + { + push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return false; + } + + lua_pushvalue(L, 2); // Stack: ..., candidate_ct, field name + lua_rawget(L, -2); // Stack: ..., candidate_ct, field | nil + + if (lua_isnil(L, -1)) + { + lua_pop(L, 2); // Stack: ... + return false; + } + + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 2); // Stack: ... + return true; + } + + const Options options = get_class_options(L, -2); + if (! options.test(allowOverridingMethods)) + raise_lua_error(L, "immutable member '%s'", get_key_name_for_error(L, 2)); + + if (key != nullptr) + rawset_super_method(L, origClassTableIndex, key); // Stack: ..., candidate_ct + lua_pop(L, 1); // Stack: ... + return true; + }; + + (void) process_metatable(mtIndex); + + lua_rawgetp_x(L, mtIndex, getParentKey()); // Stack: mt, orig_ct, parent list | nil + if (lua_istable(L, -1)) + { + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, orig_ct, parent list, parent mt + if (lua_istable(L, -1)) + { + if (process_metatable(lua_absindex(L, -1))) + { + lua_pop(L, 1); // Stack: mt, orig_ct, parent list + break; + } + } + + lua_pop(L, 1); // Stack: mt, orig_ct, parent list + } + } + + lua_pop(L, 1); // Stack: mt, orig_ct + + // Stack: mt, orig_ct — write to the original (most-derived) class table. + lua_getmetatable(L, -1); // Stack: mt, orig_ct, orig_ct_mt + lua_pushvalue(L, 3); // Stack: mt, orig_ct, orig_ct_mt, arg3 + lua_pushvalue(L, 2); // Stack: mt, orig_ct, orig_ct_mt, arg3, field name + lua_insert(L, -2); // Stack: mt, orig_ct, orig_ct_mt, field name, arg3 + lua_rawset(L, -3); // Stack: mt, orig_ct, orig_ct_mt + lua_pop(L, 2); // Stack: mt + return 0; + } + + // Non-function value (static property): use original traversal-end write location. + const int rootMetatableIndex = lua_absindex(L, -1); + lua_pushvalue(L, rootMetatableIndex); // Stack: mt, target mt + const int targetMetatableIndex = lua_absindex(L, -1); + + const auto process_metatable = [=](int candidateMtIndex) + { + push_class_or_const_table(L, candidateMtIndex); // Stack: ..., candidate_ct | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return false; + } + + lua_pushvalue(L, 2); // Stack: ..., candidate_ct, field name + lua_rawget(L, -2); // Stack: ..., candidate_ct, field | nil + + if (lua_isnil(L, -1)) + { + lua_pop(L, 2); // Stack: ... + return false; + } + + lua_pushvalue(L, candidateMtIndex); + lua_replace(L, targetMetatableIndex); + + if (! lua_iscfunction(L, -1)) + { + lua_pop(L, 2); // Stack: ... + return true; + } + + const Options options = get_class_options(L, -2); + if (! options.test(allowOverridingMethods)) + raise_lua_error(L, "immutable member '%s'", get_key_name_for_error(L, 2)); + + if (key != nullptr) + rawset_super_method(L, -2, key); // Stack: ..., candidate_ct + lua_pop(L, 1); // Stack: ... + return true; + }; + + (void) process_metatable(rootMetatableIndex); + + lua_rawgetp_x(L, rootMetatableIndex, getParentKey()); // Stack: mt, target mt, parent list | nil + if (lua_istable(L, -1)) + { + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, target mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + + // Preserve old traversal-end behavior when no member exists in the chain. + lua_pushvalue(L, -1); + lua_replace(L, targetMetatableIndex); + + if (process_metatable(lua_absindex(L, -1))) + { + lua_pop(L, 1); // Stack: mt, target mt, parent list + break; + } + + lua_pop(L, 1); // Stack: mt, target mt, parent list + } + } + + lua_pop(L, 1); // Stack: mt, target mt + + push_class_or_const_table(L, targetMetatableIndex); // Stack: mt, target mt, ct | nil + if (! lua_istable(L, -1)) // Stack: mt, target mt, nil + { + lua_pop(L, 2); // Stack: mt + return std::nullopt; + } + + lua_getmetatable(L, -1); // Stack: mt, target mt, ct, ct_mt + lua_pushvalue(L, 3); // Stack: mt, target mt, ct, ct_mt, arg3 + lua_pushvalue(L, 2); // Stack: mt, target mt, ct, ct_mt, arg3, field name + lua_insert(L, -2); // Stack: mt, target mt, ct, ct_mt, field name, arg3 + lua_rawset(L, -3); // Stack: mt, target mt, ct, ct_mt + lua_pop(L, 3); // Stack: mt + return 0; +} + +/** + * @brief Scan the flattened parent list for a property setter matching the key (stack[2]). + * + * Only setters are checked; __newindex fallbacks are intentionally ignored so that a + * registered property anywhere in the hierarchy always takes priority over any fallback. + */ +template +inline std::optional try_call_parent_newindex_setters(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + + lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, parent list, parent mt, ps | nil + if (lua_istable(L, -1)) + { + lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, ps, field name + lua_rawget(L, -2); // Stack: mt, parent list, parent mt, ps, setter | nil + lua_remove(L, -2); // Stack: mt, parent list, parent mt, setter | nil + + if (lua_iscfunction(L, -1)) + { + lua_remove(L, -2); // Stack: mt, parent list, setter + lua_remove(L, -2); // Stack: mt, setter + lua_remove(L, -2); // Stack: setter + + if constexpr (IsObject) + lua_pushvalue(L, 1); // Stack: setter, table | userdata + lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value + lua_call(L, IsObject ? 2 : 1, 0); // Stack: - + return 0; + } + + lua_pop(L, 1); // Stack: mt, parent list, parent mt + } + else + { + lua_pop(L, 1); // Stack: mt, parent list, parent mt + } + + lua_pop(L, 1); // Stack: mt, parent list + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +/** + * @brief Scan the flattened parent list for a __newindex fallback. + * + * Only fallbacks are checked; property setters are intentionally ignored because they + * have already been searched by try_call_parent_newindex_setters. + */ +template +inline std::optional try_call_parent_newindex_fallbacks(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: mt + + lua_rawgetp_x(L, -1, getParentKey()); // Stack: mt, parent list | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); // Stack: mt + return std::nullopt; + } + + const int parentListIndex = lua_absindex(L, -1); + const int parentCount = get_length(L, parentListIndex); + + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, parentListIndex, i); // Stack: mt, parent list, parent mt + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + continue; + } + + lua_rawgetp_x(L, -1, getNewIndexFallbackKey()); // Stack: mt, parent list, parent mt, nifb | nil + if (lua_iscfunction(L, -1)) + { + lua_pushvalue(L, 1); // Stack: mt, parent list, parent mt, nifb, arg1 + lua_pushvalue(L, 2); // Stack: mt, parent list, parent mt, nifb, arg1, arg2 + lua_pushvalue(L, 3); // Stack: mt, parent list, parent mt, nifb, arg1, arg2, arg3 + lua_call(L, 3, 0); // Stack: mt, parent list, parent mt + + lua_pop(L, 3); // Stack: - + return 0; + } + + lua_pop(L, 2); // Stack: mt, parent list + } + + lua_pop(L, 1); // Stack: mt + return std::nullopt; +} + +template +inline int newindex_metamethod(lua_State* L) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 6, detail::error_lua_stack_overflow); +#endif + + LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name, new value + + lua_getmetatable(L, 1); // Stack: metatable (mt) + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + const char* key = get_string_key(L, 2); + + const Options options = get_class_options(L, -1); + + // Try in the property set table on the current class first. + lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) | nil + if (! lua_istable(L, -1)) + raise_lua_error(L, "no member named '%s'", get_key_name_for_error(L, 2)); + + lua_pushvalue(L, 2); // Stack: mt, ps, field name + lua_rawget(L, -2); // Stack: mt, ps, setter | nil + lua_remove(L, -2); // Stack: mt, setter | nil + + if (lua_iscfunction(L, -1)) // Stack: mt, setter + { + lua_remove(L, -2); // Stack: setter + if constexpr (IsObject) + lua_pushvalue(L, 1); // Stack: setter, table | userdata + lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value + lua_call(L, IsObject ? 2 : 1, 0); // Stack: - + return 0; + } + + lua_pop(L, 1); // Stack: mt + + // Before consulting any __newindex fallback, scan the entire parent hierarchy for a + // matching property setter. This ensures that a registered property anywhere in the + // inheritance chain always takes priority over a __newindex fallback defined at a + // narrower scope (e.g. an intermediate or leaf class). + if (auto result = try_call_parent_newindex_setters(L)) + return *result; + + if constexpr (IsObject) + { + if (auto result = try_call_instance_static_newindex(L, -1)) + return *result; + } + + // Before consulting any __newindex fallback, scan the entire parent hierarchy for a + // matching property setter. This ensures that a registered property anywhere in the + // inheritance chain always takes priority over a __newindex fallback defined at a + // narrower scope (e.g. an intermediate or leaf class). + if (auto result = try_call_parent_newindex_setters(L)) + return *result; + + if constexpr (IsObject) + { + if (auto result = try_call_instance_static_newindex(L, -1)) + return *result; + } + + // Before consulting any __newindex fallback, scan the entire parent hierarchy for a + // matching property setter. This ensures that a registered property anywhere in the + // inheritance chain always takes priority over a __newindex fallback defined at a + // narrower scope (e.g. an intermediate or leaf class). + if (auto result = try_call_parent_newindex_setters(L)) + return *result; + + if constexpr (IsObject) + { + if (auto result = try_call_newindex_fallback(L)) + return *result; + } + else + { + if (auto result = try_call_static_newindex_fallback(L)) + return *result; + + if (options.test(extensibleClass)) + { + // For static extensible writes of plain values, store directly on the class table. + // Function values still go through try_call_newindex_extensible to preserve super_* wiring. + if (! lua_isfunction(L, 3)) + { + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + lua_rawset(L, 1); + return 0; + } + + if (auto result = try_call_newindex_extensible(L, key)) + return *result; + } + } + + if (auto result = try_call_parent_newindex_fallbacks(L)) + return *result; + + if constexpr (IsObject) + { + // Parent metamethods should win; extensible storage is the final fallback. + if (options.test(extensibleClass)) + { + if (auto result = try_call_newindex_extensible(L, key)) + return *result; + } + } + + lua_pop(L, 1); // Stack: - + raise_lua_error(L, "no writable member '%s'", get_key_name_for_error(L, 2)); +} + +template +inline int newindex_metamethod_simple(lua_State* L) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 3, detail::error_lua_stack_overflow); +#endif + + LUABRIDGE_ASSERT(lua_istable(L, 1) || lua_isuserdata(L, 1)); + + // Fast path for simple userdata objects: propset table is captured as upvalue. + if constexpr (IsObject) + { + if (lua_isuserdata(L, 1)) + { + if (! lua_istable(L, lua_upvalueindex(1))) + raise_lua_error(L, "no writable member '%s'", get_key_name_for_error(L, 2)); + + lua_pushvalue(L, 2); // Stack: key + lua_rawget(L, lua_upvalueindex(1)); // Stack: setter | nil + + if (lua_iscfunction(L, -1)) + { + lua_pushvalue(L, 1); // Stack: setter, self + lua_pushvalue(L, 3); // Stack: setter, self, value + lua_call(L, 2, 0); + return 0; + } + + lua_pop(L, 1); + lua_getmetatable(L, 1); // Stack: mt + LUABRIDGE_ASSERT(lua_istable(L, -1)); + if (auto result = try_call_instance_static_newindex(L, -1)) + return *result; + lua_pop(L, 1); + + luaL_error(L, "no writable member '%s'", get_key_name_for_error(L, 2)); + } + } + else + { + // Fast path for simple static tables: propset table is captured as upvalue. + if (lua_istable(L, 1)) + { + if (! lua_istable(L, lua_upvalueindex(1))) + raise_lua_error(L, "no writable member '%s'", get_key_name_for_error(L, 2)); + + lua_pushvalue(L, 2); // Stack: key + lua_rawget(L, lua_upvalueindex(1)); // Stack: setter | nil + + if (lua_iscfunction(L, -1)) + { + lua_pushvalue(L, 3); // Stack: setter, value + lua_call(L, 1, 0); + return 0; + } + + raise_lua_error(L, "no writable member '%s'", get_key_name_for_error(L, 2)); + } + } + + lua_getmetatable(L, 1); // Stack: mt + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + lua_rawgetp_x(L, -1, getPropsetKey()); // Stack: mt, ps | nil + if (! lua_istable(L, -1)) + raise_lua_error(L, "no member named '%s'", get_key_name_for_error(L, 2)); + + lua_pushvalue(L, 2); // Stack: mt, ps, key + lua_rawget(L, -2); // Stack: mt, ps, setter | nil + lua_remove(L, -2); // Stack: mt, setter | nil + + if (lua_iscfunction(L, -1)) + { + lua_remove(L, -2); // Stack: setter + if constexpr (IsObject) + lua_pushvalue(L, 1); // Stack: setter, self + lua_pushvalue(L, 3); // Stack: setter, self, value + lua_call(L, IsObject ? 2 : 1, 0); + return 0; + } + + raise_lua_error(L, "no writable member '%s'", get_key_name_for_error(L, 2)); +} + +//================================================================================================= +/** + * @brief lua_CFunction to report an error writing to a read-only value. + * + * The name of the variable is in the first upvalue. + */ +[[noreturn]] inline int read_only_error(lua_State* L) +{ + raise_lua_error(L, "'%s' is read-only", lua_tostring(L, lua_upvalueindex(1))); +} + +//================================================================================================= +/** + * @brief __tostring metamethod for a class. + */ +template +int tostring_metamethod(lua_State* L) +{ + const void* ptr = lua_topointer(L, 1); + + lua_getmetatable(L, -1); // Stack: metatable (mt) + lua_rawgetp_x(L, -1, getTypeKey()); // Stack: mt, classname (cn) + lua_remove(L, -2); // Stack: cn + + std::stringstream ss; + ss << ": 0x" << std::hex << reinterpret_cast(const_cast(ptr)); + lua_pushstring(L, ss.str().c_str()); // Stack: cn, address string (astr) + lua_concat(L, 2); // Stack: astr + + return 1; +} + +//================================================================================================= +/** + * @brief __destruct metamethod for a class. + */ +template +int destruct_metamethod(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_isuserdata(L, 1)); // Stack: userdata (ud) + const auto top = lua_gettop(L); + + const int result = lua_getmetatable(L, 1); // Stack: ud, object metatable (ot) | nothing + if (result == 0) + return 0; + + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: ud, ot + + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ud, ot, registry metatable (rt) | nil + if (lua_istable(L, -1)) // Stack: ud, ot, rt + { + rawgetfield(L, -1, "__destruct"); // Stack: ud, ot, rt, ud, function | nil + if (lua_isfunction(L, -1)) + { + lua_pushvalue(L, 1); // Stack: ud, ot, rt, function, ud + lua_pcall(L, 1, 0, 0); // Stack: ud, ot, rt + } + } + + lua_settop(L, top); // Stack: ud + return 0; +} + +//================================================================================================= +/** + * @brief __gc metamethod for a class. + */ +template +int gc_metamethod(lua_State* L) +{ + destruct_metamethod(L); + + Userdata* ud = Userdata::getExact(L, 1); + LUABRIDGE_ASSERT(ud); + + ud->~Userdata(); + + return 0; +} + +//================================================================================================= + +template +struct property_getter; + +/** + * @brief lua_CFunction to get a variable. + * + * This is used for global variables or class static data members. The pointer to the data is in the first upvalue. + */ +template +struct property_getter +{ + static int call(lua_State* L) + { + LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); + + T* ptr = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(ptr != nullptr); + + auto result = Stack::push(L, *ptr); + if (! result) + raise_lua_error(L, "%s", result.error_cstr()); + + return 1; + } +}; + +/** + * @brief lua_CFunction to get a class data member. + * + * The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack. + */ +template +struct property_getter +{ + static int call(lua_State* L) + { + auto c = Userdata::get(L, 1, true); + if (! c) + raise_lua_error(L, "%s", c.error_cstr()); + + T C::** mp = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + + Result result; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + result = Stack::push(L, (*c)->**mp); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + if (! result) + raise_lua_error(L, "%s", result.error_cstr()); + + return 1; + } +}; + +/** + * @brief Helper function to push a property getter on a table at a specific index. + */ +inline void add_property_getter(lua_State* L, const char* name, int tableIndex) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 2, detail::error_lua_stack_overflow); +#endif + + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, tableIndex)); + LUABRIDGE_ASSERT(lua_iscfunction(L, -1)); // Stack: getter + + lua_rawgetp_x(L, tableIndex, getPropgetKey()); // Stack: getter, propget table (pg) + lua_pushvalue(L, -2); // Stack: getter, pg, getter + rawsetfield(L, -2, name); // Stack: getter, pg + lua_pop(L, 2); // Stack: - +} + +//================================================================================================= + +template +struct property_setter; + +/** + * @brief lua_CFunction to set a variable. + * + * This is used for global variables or class static data members. The pointer to the data is in the first upvalue. + */ +template +struct property_setter +{ + static int call(lua_State* L) + { + LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); + + T* ptr = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(ptr != nullptr); + + auto result = Stack::get(L, 1); + if (! result) + raise_lua_error(L, "%s", result.error_cstr()); + + *ptr = std::move(*result); + + return 0; + } +}; + +/** + * @brief lua_CFunction to set a class data member. + * + * The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack. + */ +template +struct property_setter +{ + static int call(lua_State* L) + { + auto c = Userdata::get(L, 1, false); + if (! c) + raise_lua_error(L, "%s", c.error_cstr()); + + T C::** mp = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + auto result = Stack::get(L, 2); + if (! result) + raise_lua_error(L, "%s", result.error_cstr()); + + (*c)->** mp = std::move(*result); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + return 0; + } +}; + +/** + * @brief Helper function to push a property setter on a table at a specific index. + */ +inline void add_property_setter(lua_State* L, const char* name, int tableIndex) +{ +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(L, 2, detail::error_lua_stack_overflow); +#endif + + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, tableIndex)); + LUABRIDGE_ASSERT(lua_iscfunction(L, -1)); // Stack: setter + + lua_rawgetp_x(L, tableIndex, getPropsetKey()); // Stack: setter, propset table (ps) + lua_pushvalue(L, -2); // Stack: setter, ps, setter + rawsetfield(L, -2, name); // Stack: setter, ps + lua_pop(L, 2); // Stack: - +} + +//================================================================================================= +/** + * @brief Function generator. + */ +template +decltype(auto) invoke_callable_from_stack(lua_State* L, F&& func) +{ + 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 std::apply( + std::forward(func), + std::tuple_cat(std::tuple(ptr), make_arguments_list(L))); +} + +template +struct function +{ + template + static int call(lua_State* L, F&& func) + { + Result result; + int numResults = 1; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + if constexpr (detail::is_tuple_v) + { + numResults = static_cast(std::tuple_size_v); + result = detail::push_tuple(L, invoke_callable_from_stack(L, std::forward(func))); + } + else + { + result = Stack::push(L, invoke_callable_from_stack(L, std::forward(func))); + } +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + if (! result) + raise_lua_error(L, "%s", result.error_cstr()); + + return numResults; + } + + template + static int call(lua_State* L, T* ptr, F&& func) + { + Result result; + int numResults = 1; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + if constexpr (detail::is_tuple_v) + { + numResults = static_cast(std::tuple_size_v); + result = detail::push_tuple(L, invoke_member_callable_from_stack(L, ptr, std::forward(func))); + } + else + { + result = Stack::push(L, invoke_member_callable_from_stack(L, ptr, std::forward(func))); + } +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + if (! result) + raise_lua_error(L, "%s", result.error_cstr()); + + return numResults; + } +}; + +template +struct function, ArgsPack, Start> : function +{ + template + static int call(lua_State* L, F&& func) + { +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + invoke_callable_from_stack(L, std::forward(func)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + return 0; + } + + template + static int call(lua_State* L, T* ptr, F&& func) + { +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + invoke_member_callable_from_stack(L, ptr, std::forward(func)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + return 0; + } +}; + +template +struct function +{ + template + static int call(lua_State* L, F&& func) + { +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + invoke_callable_from_stack(L, std::forward(func)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + return 0; + } + + template + static int call(lua_State* L, T* ptr, F&& func) + { +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + invoke_member_callable_from_stack(L, ptr, std::forward(func)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + return 0; + } +}; + +template +TypeResult get_member_object(lua_State* L, bool canBeConst) +{ + const int selfIndex = lua_absindex(L, 1); + + if (lua_getmetatable(L, selfIndex)) + { + if (lua_type(L, lua_upvalueindex(2)) == LUA_TTABLE && lua_rawequal(L, -1, lua_upvalueindex(2))) + { + lua_pop(L, 1); + return Userdata::getExactPointer(L, selfIndex); + } + + if (canBeConst && lua_type(L, lua_upvalueindex(3)) == LUA_TTABLE && lua_rawequal(L, -1, lua_upvalueindex(3))) + { + lua_pop(L, 1); + return Userdata::getExactPointer(L, selfIndex); + } + + lua_pop(L, 1); + } + + return Userdata::get(L, selfIndex, canBeConst); +} + +//================================================================================================= +/** + * @brief lua_CFunction to call a class member function with a return value. + * + * The member function pointer is in the first upvalue. The class userdata object is at the top of the Lua stack. + */ +template +int invoke_member_function(lua_State* L) +{ + using FnTraits = function_traits; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + + auto ptr = get_member_object(L, false); + if (! ptr) + raise_lua_error(L, "%s", ptr.error_cstr()); + + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(func != nullptr); + + return function::call(L, *ptr, func); +} + +template +int invoke_const_member_function(lua_State* L) +{ + using FnTraits = function_traits; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + + auto ptr = get_member_object(L, true); + if (! ptr) + raise_lua_error(L, "%s", ptr.error_cstr()); + + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(func != nullptr); + + return function::call(L, *ptr, func); +} + +//================================================================================================= +/** + * @brief lua_CFunction to call a class member lua_CFunction. + * + * The member function pointer is in the first upvalue. The object userdata ('this') value is at top ot the Lua stack. + */ +template +int invoke_member_cfunction(lua_State* L) +{ + using F = int (T::*)(lua_State* L); + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + + auto t = Userdata::get(L, 1, false); + if (! t) + raise_lua_error(L, "%s", t.error_cstr()); + + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(func != nullptr); + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + return ((*t)->*func)(L); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } + + return 0; +#endif +} + +template +int invoke_const_member_cfunction(lua_State* L) +{ + using F = int (T::*)(lua_State * L) const; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + + auto t = Userdata::get(L, 1, true); + if (! t) + raise_lua_error(L, "%s", t.error_cstr()); + + const F& func = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(func != nullptr); + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + return ((*t)->*func)(L); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } + + return 0; +#endif +} + +//================================================================================================= +/** + * @brief lua_CFunction to call on a object via function pointer. + * + * The proxy function pointer (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack. + */ +template +int invoke_proxy_function(lua_State* L) +{ + using FnTraits = function_traits; + + LUABRIDGE_ASSERT(lua_islightuserdata(L, lua_upvalueindex(1))); + + auto func = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); + LUABRIDGE_ASSERT(func != nullptr); + + return function::call(L, func); +} + +//================================================================================================= +/** + * @brief lua_CFunction to call on a object via functor (lambda wrapped in a std::function). + * + * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack. + */ +template +int invoke_proxy_functor(lua_State* L) +{ + using FnTraits = function_traits>; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + + auto& func = *align>(lua_touserdata(L, lua_upvalueindex(1))); + + return function::call(L, func); +} + +//================================================================================================= +/** + * @brief lua_CFunction to call safely by trapping exceptions + */ +#if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS +inline int invoke_safe_cfunction(lua_State* L) +{ + LUABRIDGE_ASSERT(lua_iscfunction(L, lua_upvalueindex(1))); + + auto func = lua_tocfunction(L, lua_upvalueindex(1)); + + try + { + return func(L); + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } + + return 0; +} +#endif + +//================================================================================================= +/** + * @brief lua_CFunction to call on a object constructor via functor (lambda wrapped in a std::function). + * + * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object will be pushed at the top of the Lua stack. + */ +template +int invoke_proxy_constructor(lua_State* L) +{ + using FnTraits = function_traits; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + + auto& func = *align(lua_touserdata(L, lua_upvalueindex(1))); + + function::call(L, func); + + return 1; +} + +//================================================================================================= +/** + * @brief lua_CFunction to call on a object constructor via functor (lambda wrapped in a std::function). + * + * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object will be pushed at the top of the Lua stack. + */ +template +int invoke_proxy_destructor(lua_State* L) +{ + using FnTraits = function_traits; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + + auto& func = *align(lua_touserdata(L, lua_upvalueindex(1))); + + function::call(L, func); + + return 1; +} + +//================================================================================================= +/** + * @brief C++ storage for a single overload entry: arity and optional type checker. + * + * Stored inside an OverloadSet which is kept as a Lua full userdata (auto-GC'd). + */ +struct OverloadEntry +{ + using TypeChecker = bool (*)(lua_State*, int start); + + int arity; // -1 for variadic (lua_CFunction): always attempt + TypeChecker checker; // nullptr for variadic: skip type pre-checking +}; + +/** + * @brief C++ storage for all overloads of a function. + * + * Stored as a Lua full userdata so it is GC'd automatically when the closure is collected. + * The actual function closures are stored separately in a flat Lua table (upvalue 2). + */ +struct OverloadSet +{ + std::vector entries; +}; + +/** + * @brief Check a single argument type, skipping lua_State* (auto-injected, not on the Lua stack). + */ +template +bool overload_check_one_arg(lua_State* L, int& idx) +{ + if constexpr (std::is_pointer_v && + std::is_same_v>, lua_State>) + { + return true; // lua_State* is auto-injected by LuaBridge, not a Lua-visible argument + } + else + { + return Stack::isInstance(L, idx++); + } +} + +template +bool overload_check_args_impl(lua_State* L, int start, std::index_sequence) +{ + [[maybe_unused]] int idx = start; + return (overload_check_one_arg>(L, idx) && ...); +} + +template +bool overload_check_args(lua_State* L, int start) +{ + return overload_check_args_impl(L, start, + std::make_index_sequence>{}); +} + +/** + * @brief Type checker instantiable as an OverloadEntry::TypeChecker function pointer. + * + * Checks that the Lua stack arguments starting at @p start match the types in @tparam ArgsPack, + * using Stack::isInstance without raising errors. lua_State* arguments are skipped. + */ +template +bool overload_type_checker(lua_State* L, int start) +{ + return overload_check_args(L, start); +} + +/** + * @brief Type checker for reversed proxy functions (binary operators with swapped operands). + * + * Checks all arguments from absolute stack index 1, ignoring @p start, because the class object + * is not the first operand on the stack (eg. `3 * vec`). + */ +template +bool reversed_overload_type_checker(lua_State* L, int) +{ + return overload_check_args(L, 1); +} + +//================================================================================================= +/** + * @brief lua_CFunction to resolve an invocation between several overloads. + * + * upvalue[1] = OverloadSet full userdata — C++ vector of {arity, type_checker} per overload. + * upvalue[2] = flat Lua table {[1]=func1, [2]=func2, ...} — the actual function closures. + * + * Dispatch: + * 1. Arity check in C++ (no Lua call). + * 2. Type check via Stack::isInstance in C++ (no pcall) — skips clearly mismatched overloads. + * 3. Only calls lua_pcall for type-matched candidates, eliminating failed pcalls for type mismatches. + */ +template +inline int try_overload_functions(lua_State* L) +{ + const int nargs = lua_gettop(L); + const int effective_args = nargs - (Member ? 1 : 0); + const int start_arg = Member ? 2 : 1; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + auto* overload_set = align(lua_touserdata(L, lua_upvalueindex(1))); + + // push flat functions table (upvalue 2) + lua_pushvalue(L, lua_upvalueindex(2)); + LUABRIDGE_ASSERT(lua_istable(L, -1)); + const int idx_funcs = nargs + 1; + + // create table to hold error messages + lua_createtable(L, static_cast(overload_set->entries.size()), 0); + const int idx_errors = nargs + 2; + int nerrors = 0; + + for (int i = 0; i < static_cast(overload_set->entries.size()); ++i) + { + const auto& entry = overload_set->entries[i]; + + // fast arity check (C++, no Lua calls) + if (entry.arity >= 0 && entry.arity != effective_args) + { + lua_pushfstring(L, "Skipped overload #%d with unmatched arity of %d instead of %d", i, entry.arity, effective_args); + lua_rawseti(L, idx_errors, ++nerrors); + continue; + } + + // fast type check (C++, no pcall) — avoids expensive pcall for clearly mismatched types + if (entry.checker != nullptr && !entry.checker(L, start_arg)) + { + lua_pushfstring(L, "Skipped overload #%d with unmatched argument types", i); + lua_rawseti(L, idx_errors, ++nerrors); + continue; + } + + // O(1) function lookup from flat table + lua_rawgeti(L, idx_funcs, i + 1); + LUABRIDGE_ASSERT(lua_isfunction(L, -1)); + + // push arguments + for (int j = 1; j <= nargs; ++j) + lua_pushvalue(L, j); + + // call f, this pops the function and its args, pushes result(s) + const int err = lua_pcall(L, nargs, LUA_MULTRET, 0); + if (err == LUABRIDGE_LUA_OK) + { + // 2 extra items on stack below results: idx_funcs, idx_errors + return lua_gettop(L) - nargs - 2; + } + else if (err == LUA_ERRRUN) + { + // store error message and try next overload + lua_rawseti(L, idx_errors, ++nerrors); + } + else + { + lua_error_x(L); // critical error: rethrow + } + } + + lua_Debug debug; + lua_getstack_info_x(L, 0, "n", &debug); + lua_pushfstring(L, "All %d overloads of %s returned an error:", nerrors, debug.name); + + // Concatenate error messages of each overload + for (int i = 1; i <= nerrors; ++i) + { + lua_pushfstring(L, "\n%d: ", i); + lua_rawgeti(L, idx_errors, i); + } + lua_concat(L, nerrors * 2 + 1); + + const char* message = lua_tostring(L, -1); + raise_lua_error(L, "%s", message ? message : ""); +} + +//================================================================================================= + +// Lua CFunction +inline void push_function(lua_State* L, lua_CFunction fp, const char* debugname) +{ +#if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS + lua_pushcfunction_x(L, fp, debugname); + lua_pushcclosure_x(L, invoke_safe_cfunction, debugname, 1); +#else + lua_pushcfunction_x(L, fp, debugname); +#endif +} + +// Generic function pointer +template +inline void push_function(lua_State* L, ReturnType (*fp)(Params...), const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +inline void push_function(lua_State* L, ReturnType (*fp)(Params...) noexcept, const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +// Callable object (lambdas) +template && !std::is_pointer_v && !std::is_member_function_pointer_v>> +inline void push_function(lua_State* L, F&& f, const char* debugname) +{ + lua_newuserdata_aligned(L, std::forward(f)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); +} + +//================================================================================================= +// Lua CFunction +template +void push_member_function(lua_State* L, lua_CFunction fp, const char* debugname) +{ +#if LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING && LUABRIDGE_HAS_EXCEPTIONS + lua_pushcfunction_x(L, fp, debugname); + lua_pushcclosure_x(L, invoke_safe_cfunction, debugname, 1); +#else + lua_pushcfunction_x(L, fp, debugname); +#endif +} + +// Generic function pointer +template +void push_member_function(lua_State* L, ReturnType (*fp)(T*, Params...), const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_member_function(lua_State* L, ReturnType (*fp)(T&, Params...), const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_member_function(lua_State* L, ReturnType (*fp)(T*, Params...) noexcept, const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_member_function(lua_State* L, ReturnType (*fp)(T&, Params...) noexcept, const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_member_function(lua_State* L, ReturnType (*fp)(const T*, Params...), const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_member_function(lua_State* L, ReturnType (*fp)(const T&, Params...), const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_member_function(lua_State* L, ReturnType (*fp)(const T*, Params...) noexcept, const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_member_function(lua_State* L, ReturnType (*fp)(const T&, Params...) noexcept, const char* debugname) +{ + using FnType = decltype(fp); + + lua_pushlightuserdata(L, reinterpret_cast(fp)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +// Callable object (lambdas) +template && + std::is_object_v && + !std::is_pointer_v && + !std::is_member_function_pointer_v>> +void push_member_function(lua_State* L, F&& f, const char* debugname) +{ + static_assert(std::is_same_v>>> || + is_reversed_proxy_function_v); + + lua_newuserdata_aligned(L, std::forward(f)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); +} + +// Non const member function pointer +template +void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...), const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using F = decltype(mfp); + + new (lua_newuserdata_x(L, sizeof(F))) F(mfp); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + lua_pushcclosure_x(L, &invoke_member_function, debugname, 2); +} + +template +void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) noexcept, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using F = decltype(mfp); + + new (lua_newuserdata_x(L, sizeof(F))) F(mfp); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + lua_pushcclosure_x(L, &invoke_member_function, debugname, 2); +} + +// Const member function pointer +template +void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) const, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using F = decltype(mfp); + + new (lua_newuserdata_x(L, sizeof(F))) F(mfp); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); + lua_pushcclosure_x(L, &detail::invoke_const_member_function, debugname, 3); +} + +template +void push_member_function(lua_State* L, ReturnType (U::*mfp)(Params...) const noexcept, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using F = decltype(mfp); + + new (lua_newuserdata_x(L, sizeof(F))) F(mfp); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); + lua_pushcclosure_x(L, &detail::invoke_const_member_function, debugname, 3); +} + +// Non const member Lua CFunction pointer +template +void push_member_function(lua_State* L, int (U::*mfp)(lua_State*), const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using F = decltype(mfp); + + new (lua_newuserdata_x(L, sizeof(F))) F(mfp); + lua_pushcclosure_x(L, &invoke_member_cfunction, debugname, 1); +} + +// Const member Lua CFunction pointer +template +void push_member_function(lua_State* L, int (U::*mfp)(lua_State*) const, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using F = decltype(mfp); + + new (lua_newuserdata_x(L, sizeof(F))) F(mfp); + lua_pushcclosure_x(L, &invoke_const_member_cfunction, debugname, 1); +} + +//================================================================================================= +/** + * @brief + */ +template || !detail::is_callable_v)>> +void push_property_getter(lua_State* L, const T* value, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(const_cast(value))); + lua_pushcclosure_x(L, &property_getter::call, debugname, 1); +} + +template +void push_property_getter(lua_State* L, T (*getter)(), const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_property_getter(lua_State* L, T (*getter)() noexcept, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template >> +void push_property_getter(lua_State* L, T getter, const char* debugname) +{ + if constexpr (std::is_same_v) + { + lua_pushcfunction_x(L, getter, debugname); + } + else + { + lua_newuserdata_aligned(L, std::move(getter)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); + } +} + +//================================================================================================= +/** + * @brief + */ +template +void push_class_property_getter(lua_State* L, T (U::*value), const char* debugname) +{ + using MemberValue = decltype(value); + + new (lua_newuserdata_x(L, sizeof(MemberValue))) MemberValue(value); + lua_pushcclosure_x(L, &property_getter::call, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (B::*getter)() const, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using GetType = decltype(getter); + + new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); + lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (B::*getter)() const noexcept, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using GetType = decltype(getter); + + new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); + lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using GetType = decltype(getter); + + new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); + lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (B::*getter)(lua_State*) const noexcept, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using GetType = decltype(getter); + + new (lua_newuserdata_x(L, sizeof(GetType))) GetType(getter); + lua_pushcclosure_x(L, &invoke_const_member_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C*), const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C&), const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C*, lua_State*), const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C&, lua_State*), const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C*) noexcept, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C&) noexcept, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C*, lua_State*) noexcept, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, T (*getter)(const C&, lua_State*) noexcept, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(getter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_getter(lua_State* L, lua_CFunction getter, const char* debugname) +{ + lua_pushcfunction_x(L, getter, debugname); +} + +template && detail::is_callable_v>> +void push_class_property_getter(lua_State* L, T getter, const char* debugname) +{ + using FirstArg = detail::function_argument_t<0, T>; + static_assert(std::is_same_v>>, C>); + + lua_newuserdata_aligned(L, std::move(getter)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); +} + +//================================================================================================= +/** + * @brief + */ +template || !detail::is_callable_v)>> +void push_property_setter(lua_State* L, T* value, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(value)); + lua_pushcclosure_x(L, &property_setter::call, debugname, 1); +} + +template +void push_property_setter(lua_State* L, void (*setter)(T), const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &detail::invoke_proxy_function, debugname, 1); +} + +template +void push_property_setter(lua_State* L, void (*setter)(T) noexcept, const char* debugname) +{ + lua_pushlightuserdata(L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &detail::invoke_proxy_function, debugname, 1); +} + +template >> +void push_property_setter(lua_State* L, T setter, const char* debugname) +{ + if constexpr (std::is_same_v) + { + lua_pushcfunction_x(L, setter, debugname); + } + else + { + lua_newuserdata_aligned(L, std::move(setter)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); + } +} + +//================================================================================================= +/** + * @brief + */ +template +void push_class_property_setter(lua_State* L, T U::*value, const char* debugname) +{ + using MemberValue = decltype(value); + + new (lua_newuserdata_x(L, sizeof(MemberValue))) MemberValue(value); + lua_pushcclosure_x(L, &property_setter::call, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T), const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using SetType = decltype(setter); + + new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); + lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T) noexcept, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using SetType = decltype(setter); + + new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); + lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*), const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using SetType = decltype(setter); + + new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); + lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (B::*setter)(T, lua_State*) noexcept, const char* debugname) +{ + static_assert(std::is_same_v || std::is_base_of_v); + + using SetType = decltype(setter); + + new (lua_newuserdata_x(L, sizeof(SetType))) SetType(setter); + lua_pushcclosure_x(L, &invoke_member_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C*, T), const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C&, T), const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C*, T, lua_State*), const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C&, T, lua_State*), const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C*, T) noexcept, const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C&, T) noexcept, const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C*, T, lua_State*) noexcept, const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, void (*setter)(C&, T, lua_State*) noexcept, const char* debugname) +{ + lua_pushlightuserdata( L, reinterpret_cast(setter)); + lua_pushcclosure_x(L, &invoke_proxy_function, debugname, 1); +} + +template +void push_class_property_setter(lua_State* L, lua_CFunction setter, const char* debugname) +{ + lua_pushcfunction_x(L, setter, debugname); +} + +template && detail::is_callable_v>> +void push_class_property_setter(lua_State* L, T setter, const char* debugname) +{ + using FirstArg = detail::function_argument_t<0, T>; + static_assert(std::is_same_v>, C>); + + lua_newuserdata_aligned(L, std::move(setter)); + lua_pushcclosure_x(L, &invoke_proxy_functor, debugname, 1); +} + +//================================================================================================= +/** + * @brief + */ +inline void push_property_readonly(lua_State* L, const char* debugname) +{ + lua_pushstring(L, debugname); + lua_pushcclosure_x(L, &detail::read_only_error, debugname, 1); +} + +//================================================================================================= +/** + * @brief Constructor generators. + * + * These templates call operator new with the contents of a type/value list passed to the constructor. Two versions of call() are provided. + * One performs a regular new, the other performs a placement new. + */ +template +struct constructor +{ + static T* construct(const Args& args) + { + auto alloc = [](auto&&... args) { return new T(std::forward(args)...); }; + + return std::apply(alloc, args); + } + + static T* construct(void* ptr, const Args& args) + { + auto alloc = [ptr](auto&&... args) { return new (ptr) T(std::forward(args)...); }; + + return std::apply(alloc, args); + } +}; + +//================================================================================================= +/** + * @brief Placement constructor generators. + */ +template +struct placement_constructor +{ + template + static T* construct(void* ptr, F&& func, const Args& args) + { + auto alloc = [ptr, func = std::forward(func)](auto&&... args) { return func(ptr, std::forward(args)...); }; + + return std::apply(alloc, args); + } +}; + +//================================================================================================= +/** + * @brief Container allocator generators. + */ +template +struct container_constructor +{ + template + static C construct(F&& func, const Args& args) + { + auto alloc = [func = std::forward(func)](auto&&... args) { return func(std::forward(args)...); }; + + return std::apply(alloc, args); + } +}; + +//================================================================================================= +/** + * @brief External allocator generators. + */ +template +struct external_constructor +{ + template + static T* construct(F&& func, const Args& args) + { + auto alloc = [func = std::forward(func)](auto&&... args) { return func(std::forward(args)...); }; + + return std::apply(alloc, args); + } +}; + +//================================================================================================= +/** + * @brief lua_CFunction to construct a class object wrapped in a container. + */ +template +int constructor_container_proxy(lua_State* L) +{ + using T = typename ContainerTraits::Type; + + T* object = nullptr; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + object = constructor::construct(make_arguments_list(L)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + auto result = UserdataSharedHelper::push(L, object); + if (! result) + raise_lua_error(L, "%s", result.error_cstr()); + + return 1; +} + +/** + * @brief lua_CFunction to construct a class object in-place in the userdata. + */ +template +int constructor_placement_proxy(lua_State* L) +{ + auto args = make_arguments_list(L); + + std::error_code ec; + auto* value = UserdataValue::place(L, ec); + if (! value) + raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + + constructor::construct(value->getObject(), std::move(args)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + value->commit(); + + return 1; +} + +//================================================================================================= +/** + * @brief Constructor forwarder. + */ +template +struct constructor_forwarder +{ + explicit constructor_forwarder(F f) + : m_func(std::move(f)) + { + } + + T* operator()(lua_State* L) + { + using FnTraits = function_traits; + using FnArgs = remove_first_type_t; + + auto args = make_arguments_list(L); + + std::error_code ec; + auto* value = UserdataValue::place(L, ec); + if (! value) + raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); + + T* object = nullptr; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + object = placement_constructor::construct(value->getObject(), m_func, std::move(args)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + value->commit(); + + return object; + } + +private: + F m_func; +}; + +//================================================================================================= +/** + * @brief Constructor forwarder. + */ +template +struct destructor_forwarder +{ + explicit destructor_forwarder(F f) + : m_func(std::move(f)) + { + } + + void operator()(lua_State* L) + { + auto value = Userdata::get(L, -1, false); + if (! value) + raise_lua_error(L, "%s", value.error_cstr()); + + std::invoke(m_func, *value); + } + +private: + F m_func; +}; + +//================================================================================================= +/** + * @brief Constructor forwarder. + */ +template +struct factory_forwarder +{ + explicit factory_forwarder(Alloc alloc, Dealloc dealloc) + : m_alloc(std::move(alloc)) + , m_dealloc(std::move(dealloc)) + { + } + + T* operator()(lua_State* L) + { + using FnTraits = function_traits; + using FnArgs = typename FnTraits::argument_types; + + T* object = nullptr; + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { +#endif + object = external_constructor::construct(m_alloc, make_arguments_list(L)); + +#if LUABRIDGE_HAS_EXCEPTIONS + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } +#endif + + std::error_code ec; + auto* value = UserdataValueExternal::place(L, object, m_dealloc, ec); + if (! value) + raise_lua_error(L, "%s", detail::ErrorCategory::errorString(ec.value())); + + return object; + } + +private: + Alloc m_alloc; + Dealloc m_dealloc; +}; + +//================================================================================================= +/** + * @brief Container forwarder. + */ +template +struct container_forwarder +{ + explicit container_forwarder(F f) + : m_func(std::move(f)) + { + } + + C operator()(lua_State* L) + { + using FnTraits = function_traits; + using FnArgs = typename FnTraits::argument_types; + + 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 + 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 + + if (! result) + { + std::destroy_at(object); + raise_lua_error(L, "%s", result.error_cstr()); + } + + C ret = std::move(*object); + std::destroy_at(object); + return ret; + } + +private: + F m_func; +}; + +} // namespace detail +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/ClassInfo.h b/external/include/LuaBridge3/LuaBridge/detail/ClassInfo.h new file mode 100644 index 0000000000..82e4899928 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/ClassInfo.h @@ -0,0 +1,259 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2020, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" + +#include +#include +#include + +#if defined __clang__ || defined __GNUC__ +#define LUABRIDGE_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#define LUABRIDGE_PRETTY_FUNCTION_PREFIX '=' +#define LUABRIDGE_PRETTY_FUNCTION_SUFFIX ']' +#elif defined _MSC_VER +#define LUABRIDGE_PRETTY_FUNCTION __FUNCSIG__ +#define LUABRIDGE_PRETTY_FUNCTION_PREFIX '<' +#define LUABRIDGE_PRETTY_FUNCTION_SUFFIX '>' +#endif + +namespace luabridge { +namespace detail { + +[[nodiscard]] constexpr auto fnv1a(const char* s, std::size_t count) noexcept +{ + uint32_t seed = 2166136261u; + + for (std::size_t i = 0; i < count; ++i) + seed = static_cast(static_cast(seed ^ static_cast(*s++)) * 16777619u); + + if constexpr (sizeof(void*) == 8) + return static_cast(seed); + else + return seed; +} + +template +[[nodiscard]] static constexpr auto typeName(T* = nullptr) noexcept +{ + constexpr std::string_view prettyName{ LUABRIDGE_PRETTY_FUNCTION }; + + constexpr auto first = prettyName.find_first_not_of(' ', prettyName.find_first_of(LUABRIDGE_PRETTY_FUNCTION_PREFIX) + 1); + + return prettyName.substr(first, prettyName.find_last_of(LUABRIDGE_PRETTY_FUNCTION_SUFFIX) - first); +} + +template ().find_first_of('.')> +[[nodiscard]] static constexpr auto typeHash(T* = nullptr) noexcept +{ + constexpr auto stripped = typeName(); + + return fnv1a(stripped.data(), stripped.size()); +} + +//================================================================================================= +/** + * @brief A unique key for the exceptions in the registry. + */ +[[nodiscard]] inline void* getExceptionsKey() noexcept +{ + return reinterpret_cast(0xc7); +} + +//================================================================================================= +/** + * @brief A unique key for a type name in a metatable. + */ +[[nodiscard]] inline const void* getTypeKey() noexcept +{ + return reinterpret_cast(0x71); +} + +//================================================================================================= +/** + * @brief The key of a const table in another metatable. + */ +[[nodiscard]] inline const void* getConstKey() noexcept +{ + return reinterpret_cast(0xc07); +} + +//================================================================================================= +/** + * @brief The key of a class table in another metatable. + */ +[[nodiscard]] inline const void* getClassKey() noexcept +{ + return reinterpret_cast(0xc1a); +} + +//================================================================================================= +/** + * @brief The key of a class options table in another metatable. + */ +[[nodiscard]] inline const void* getClassOptionsKey() noexcept +{ + return reinterpret_cast(0xc2b); +} + +//================================================================================================= +/** + * @brief The key of a type identity tag in class/const metatables. + */ +[[nodiscard]] inline const void* getTypeIdentityKey() noexcept +{ + return reinterpret_cast(0xc2c); +} + +//================================================================================================= +/** + * @brief The key of a propget table in another metatable. + */ +[[nodiscard]] inline const void* getPropgetKey() noexcept +{ + return reinterpret_cast(0x6e7); +} + +//================================================================================================= +/** + * @brief The key of a propset table in another metatable. + */ +[[nodiscard]] inline const void* getPropsetKey() noexcept +{ + return reinterpret_cast(0x5e7); +} + +//================================================================================================= +/** + * @brief The key of a static table in another metatable. + */ +[[nodiscard]] inline const void* getStaticKey() noexcept +{ + return reinterpret_cast(0x57a); +} + +//================================================================================================= +/** + * @brief The key of a parent table in another metatable. + */ +[[nodiscard]] inline const void* getParentKey() noexcept +{ + return reinterpret_cast(0xdad); +} + +//================================================================================================= +/** + * @brief The key of a cast offset table in a derived class metatable. + * + * Maps base class registry keys to byte offsets for pointer adjustment when converting + * a derived class pointer to a base class pointer in multiple inheritance scenarios. + */ +[[nodiscard]] inline const void* getCastTableKey() noexcept +{ + return reinterpret_cast(0xca57); +} + +//================================================================================================= +/** + * The key of the index fall back in another metatable. + */ +[[nodiscard]] inline const void* getIndexFallbackKey() +{ + return reinterpret_cast(0x81ca); +} + +[[nodiscard]] inline const void* getIndexExtensibleKey() +{ + return reinterpret_cast(0x81cb); +} + +//================================================================================================= +/** + * The key of the new index fall back in another metatable. + */ +[[nodiscard]] inline const void* getNewIndexFallbackKey() +{ + return reinterpret_cast(0x8107); +} + +[[nodiscard]] inline const void* getNewIndexExtensibleKey() +{ + return reinterpret_cast(0x8108); +} + +//================================================================================================= +/** + * @brief The key of a ConverterRegistry userdata in a class metatable. + */ +[[nodiscard]] inline const void* getConvertersKey() noexcept +{ + return reinterpret_cast(0xc0de); +} + +//================================================================================================= +/** + * The key of the static index fall back in another metatable. + */ +[[nodiscard]] inline const void* getStaticIndexFallbackKey() +{ + return reinterpret_cast(0x81cc); +} + +//================================================================================================= +/** + * The key of the static new index fall back in another metatable. + */ +[[nodiscard]] inline const void* getStaticNewIndexFallbackKey() +{ + return reinterpret_cast(0x8109); +} + +//================================================================================================= +/** + * @brief Get the key for the static table in the Lua registry. + * + * The static table holds the static data members, static properties, and static member functions for a class. + */ +template +[[nodiscard]] const void* getStaticRegistryKey() noexcept +{ + static auto value = typeHash(); + + return reinterpret_cast(value); +} + +//================================================================================================= +/** + * @brief Get the key for the class table in the Lua registry. + * + * The class table holds the data members, properties, and member functions of a class. Read-only data and properties, and const + * member functions are also placed here (to save a lookup in the const table). + */ +template +[[nodiscard]] const void* getClassRegistryKey() noexcept +{ + static auto value = typeHash() ^ 1; + + return reinterpret_cast(value); +} + +//================================================================================================= +/** + * @brief Get the key for the const table in the Lua registry. + * + * The const table holds read-only data members and properties, and const member functions of a class. + */ +template +[[nodiscard]] const void* getConstRegistryKey() noexcept +{ + static auto value = typeHash() ^ 2; + + return reinterpret_cast(value); +} +} // namespace detail +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Config.h b/external/include/LuaBridge3/LuaBridge/detail/Config.h new file mode 100644 index 0000000000..e5e7592075 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Config.h @@ -0,0 +1,272 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2020, Dmitry Tarakanov +// Copyright 2019, George Tokmaji +// SPDX-License-Identifier: MIT + +#pragma once + +#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 + +#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) +#define LUABRIDGE_ON_LUAJIT 1 +#elif defined(RAVI_OPTION_STRING2) +#define LUABRIDGE_ON_RAVI 1 +#elif defined(LUA_VERSION_NUM) +#define LUABRIDGE_ON_LUA 1 +#else +#error "Lua headers must be included prior to LuaBridge ones" +#endif + +#if !defined(LUABRIDGE_HAS_EXCEPTIONS) +#if defined(_MSC_VER) +#if _CPPUNWIND || _HAS_EXCEPTIONS +#define LUABRIDGE_HAS_EXCEPTIONS 1 +#else +#define LUABRIDGE_HAS_EXCEPTIONS 0 +#endif +#elif defined(__clang__) +#if __EXCEPTIONS && __has_feature(cxx_exceptions) +#define LUABRIDGE_HAS_EXCEPTIONS 1 +#else +#define LUABRIDGE_HAS_EXCEPTIONS 0 +#endif +#elif defined(__GNUC__) +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) +#define LUABRIDGE_HAS_EXCEPTIONS 1 +#else +#define LUABRIDGE_HAS_EXCEPTIONS 0 +#endif +#endif +#endif + +#if LUABRIDGE_HAS_EXCEPTIONS +#define LUABRIDGE_IF_EXCEPTIONS(...) __VA_ARGS__ +#define LUABRIDGE_IF_NO_EXCEPTIONS(...) +#else +#define LUABRIDGE_IF_EXCEPTIONS(...) +#define LUABRIDGE_IF_NO_EXCEPTIONS(...) __VA_ARGS__ +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define LUABRIDGE_NO_SANITIZE(x) __attribute__((no_sanitize(x))) +#else +#define LUABRIDGE_NO_SANITIZE(x) +#endif + +#if defined(__OBJC__) +#define LUABRIDGE_ON_OBJECTIVE_C 1 +#endif + +/** + * @brief Enable safe stack checks to avoid lua stack overflow when pushing values on the stack. + * + * @note Default is enabled. + */ +#if !defined(LUABRIDGE_SAFE_STACK_CHECKS) +#define LUABRIDGE_SAFE_STACK_CHECKS 1 +#endif + +/** + * @brief Enable strict stack conversions to enforce exact type matching when getting values from the stack. + * + * When enabled: + * - `Stack::get` only accepts `LUA_TBOOLEAN` (nil is not convertible to bool). + * - Integer `Stack` specializations only accept Lua integer values (not floats with integer representation, on Lua 5.3+). + * - `Stack::get` only accepts `LUA_TSTRING` (numbers are not coerced to strings). + * + * When disabled (default), a more permissive conversion is used: + * - `Stack::get` accepts `LUA_TBOOLEAN` and `LUA_TNIL` (nil converts to false). + * - Integer `Stack` specializations accept any `LUA_TNUMBER` that can be represented as the target integer type. + * - `Stack::get` accepts `LUA_TSTRING` and `LUA_TNUMBER` (numbers are coerced to strings). + * + * @note Default is disabled. + */ +#if !defined(LUABRIDGE_STRICT_STACK_CONVERSIONS) +#define LUABRIDGE_STRICT_STACK_CONVERSIONS 0 +#endif + +/** + * @brief Enable safe exception handling when lua is compiled as `C` and exceptions raise during execution of registered `lua_CFunction`. + * + * This is a problem that manifests when exceptions are leaking a CFunction when lua is compiled as `C` because the library will then longjmp + * instead of correctly unwinding the exception into C++ land. If you have exceptions enabled and are compiling lua as `C` and you are getting random + * crashes when invoking CFunctions that throw, you have two options: or you catch exceptions in your CFunction and raise a `lua_error` instead + * or you enable this macro, which will add a safe indirection doing exceptions catching and raising when invoking your registered CFunction. + * + * @warning When enabled, some performance degradation is to be expected when invoking registered `lua_CFunction` through the library. + * + * @note Default is disabled, can only be enabled when `LUABRIDGE_HAS_EXCEPTIONS` is 1. + */ +#if !defined(LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING) +#define LUABRIDGE_SAFE_LUA_C_EXCEPTION_HANDLING 0 +#endif + +/** + * @brief Control raising when an unregistered class is used. + * + * @note Default is enabled when exceptions are enabled, disabled otherwise. + */ +#if !defined(LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE) +#if LUABRIDGE_HAS_EXCEPTIONS +#define LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE 1 +#else +#define LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE 0 +#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)) +#else +#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 map library support. + * + * Requires C++23 and the flat_map header to be available. + * Define LUABRIDGE_DISABLE_CXX23_FLAT_MAP to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX23_FLAT_MAP) +#if !defined(LUABRIDGE_DISABLE_CXX23_FLAT_MAP) && LUABRIDGE_CXX23_OR_GREATER && __has_include() && defined(__cpp_lib_flat_map) +#define LUABRIDGE_HAS_CXX23_FLAT_MAP 1 +#else +#define LUABRIDGE_HAS_CXX23_FLAT_MAP 0 +#endif +#endif + +/** + * @brief Enable C++23 flat set library support. + * + * Requires C++23 and the flat_set header to be available. + * Define LUABRIDGE_DISABLE_CXX23_FLAT_SET to force-disable even when available. + */ +#if !defined(LUABRIDGE_HAS_CXX23_FLAT_SET) +#if !defined(LUABRIDGE_DISABLE_CXX23_FLAT_SET) && LUABRIDGE_CXX23_OR_GREATER && __has_include() && defined(__cpp_lib_flat_set) +#define LUABRIDGE_HAS_CXX23_FLAT_SET 1 +#else +#define LUABRIDGE_HAS_CXX23_FLAT_SET 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/external/include/LuaBridge3/LuaBridge/detail/Converter.h b/external/include/LuaBridge3/LuaBridge/detail/Converter.h new file mode 100644 index 0000000000..f2bafcf4cc --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Converter.h @@ -0,0 +1,183 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "ClassInfo.h" +#include "Errors.h" +#include "LuaHelpers.h" +#include "Result.h" + +#include +#include +#include +#include +#include + +namespace luabridge { + +// Forward declaration. +template +struct Stack; + +//================================================================================================= +/** + * @brief Opt-in trait for enabling custom type converters for type T. + * + * Specialize with enabled = true so that Stack::get consults the metatable + * converter registry as a Phase 3 fallback after Phase 1 (exact match) and + * Phase 2 (inheritance) both fail. + * + * Example: + * template <> struct luabridge::StackConversion { static constexpr bool enabled = true; }; + */ +template +struct StackConversion +{ + static constexpr bool enabled = false; +}; + +//================================================================================================= +/** + * @brief User-defined conversion hook from source type From to target type To. + * + * Specialize this template for each (To, From) pair and provide: + * static To convert(const From& from); + * + * Example: + * template <> + * struct luabridge::StackConverter { + * static Vec3 convert(const glm::vec3& v) { return {v.x, v.y, v.z}; } + * }; + */ +template +struct StackConverter; + +//================================================================================================= + +namespace detail { + +struct ConverterRegistry +{ + std::unordered_map converters; +}; + +inline ConverterRegistry* getOrCreateConverterRegistry(lua_State* L, int metatableIdx) +{ + const int absIdx = lua_absindex(L, metatableIdx); + + lua_rawgetp_x(L, absIdx, getConvertersKey()); + if (lua_isuserdata(L, -1) && !lua_islightuserdata(L, -1)) + { + auto* reg = align(lua_touserdata(L, -1)); + lua_pop(L, 1); + return reg; + } + lua_pop(L, 1); // pop nil or unexpected value + + // Create ConverterRegistry as an aligned Lua full userdata with automatic __gc + lua_newuserdata_aligned(L); + auto* reg = align(lua_touserdata(L, -1)); + + // Store the userdata in the class metatable + lua_pushvalue(L, -1); // dup userdata + lua_rawsetp_x(L, absIdx, getConvertersKey()); // store, pops dup + lua_pop(L, 1); // pop the original userdata + + return reg; +} + +template +class ConverterConstRef +{ +public: + explicit ConverterConstRef(const T& value) noexcept + : m_ref(std::addressof(value)) + { + } + + explicit ConverterConstRef(T&& value) noexcept(std::is_nothrow_move_constructible_v) + : m_value(std::move(value)) + , m_ref(std::addressof(*m_value)) + { + } + + ConverterConstRef(ConverterConstRef&& other) noexcept(std::is_nothrow_move_constructible_v) + : m_value(std::move(other.m_value)) + , m_ref(m_value.has_value() ? std::addressof(*m_value) : other.m_ref) + { + } + + ConverterConstRef(const ConverterConstRef& other) + : m_value(other.m_value) + , m_ref(m_value.has_value() ? std::addressof(*m_value) : other.m_ref) + { + } + + ConverterConstRef& operator=(ConverterConstRef&& other) noexcept(std::is_nothrow_move_assignable_v) + { + m_value = std::move(other.m_value); + m_ref = m_value.has_value() ? std::addressof(*m_value) : other.m_ref; + return *this; + } + + ConverterConstRef& operator=(const ConverterConstRef& other) + { + m_value = other.m_value; + m_ref = m_value.has_value() ? std::addressof(*m_value) : other.m_ref; + return *this; + } + + operator const T&() const noexcept + { + return *m_ref; + } + +private: + std::optional m_value; + const T* m_ref = nullptr; +}; + +template +TypeResult tryConvertFromRegisteredConverter(lua_State* L, int index) +{ + using FnType = TypeResult(*)(lua_State*, int); + + if (! lua_getmetatable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + lua_rawgetp_x(L, -1, detail::getConvertersKey()); + if (lua_isuserdata(L, -1) && !lua_islightuserdata(L, -1)) + { + auto* reg = align(lua_touserdata(L, -1)); + lua_pop(L, 2); // registry userdata + metatable + + auto it = reg->converters.find(detail::getClassRegistryKey()); + if (it != reg->converters.end() && it->second) + { + const auto* fn = static_cast(it->second); + return (*fn)(L, index); + } + } + else + { + lua_pop(L, 2); // nil/other + metatable + } + + return makeErrorCode(ErrorCode::InvalidTypeCast); +} + +template +TypeResult convertFromStack(lua_State* L, int index) +{ + auto result = detail::Userdata::get(L, index, true); + + if (!result || !*result) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return StackConverter::convert(**result); +} + +} // namespace detail +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Coroutine.h b/external/include/LuaBridge3/LuaBridge/detail/Coroutine.h new file mode 100644 index 0000000000..2afb92909d --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Coroutine.h @@ -0,0 +1,492 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "CFunctions.h" +#include "Errors.h" +#include "LuaHelpers.h" +#include "Stack.h" + +#if LUABRIDGE_HAS_CXX20_COROUTINES + +#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." +#endif +#else + +#include +#include +#include +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief A C++20 coroutine type callable from Lua. + * + * Register instances via Namespace::addCoroutine(). When called from Lua, the coroutine body + * runs until the first co_yield (which yields a value back to Lua) or co_return (which + * returns a final value). Subsequent Lua resumes continue the body from the last suspension point. + * + * @tparam R The type yielded/returned by the coroutine. May be void. + * + * Example: + * @code + * luabridge::getGlobalNamespace(L) + * .addCoroutine("range", [](int start, int stop) -> luabridge::CppCoroutine { + * for (int i = start; i < stop; ++i) + * co_yield i; + * co_return -1; + * }); + * @endcode + * + * @note Requires Lua 5.2+ (lua_yieldk). Not supported on Lua 5.1, LuaJIT, or Luau. + * @note Not thread-safe. Must be driven from a single OS thread. + */ +template +struct CppCoroutine +{ + struct promise_type + { + lua_State* L = nullptr; + int nresults = 0; + bool is_done = false; + std::exception_ptr exception; + + std::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + + void unhandled_exception() noexcept + { + exception = std::current_exception(); + } + + std::suspend_always yield_value(const R& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, value); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + return {}; + } + + std::suspend_always yield_value(R&& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, std::move(value)); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + return {}; + } + + void return_value(const R& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, value); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + is_done = true; + } + + void return_value(R&& value) + { + nresults = 0; + if (L) + { + auto result = Stack::push(L, std::move(value)); + if (result) + nresults = 1; + else + exception = std::make_exception_ptr(std::system_error(result.error())); + } + is_done = true; + } + + CppCoroutine get_return_object() + { + return CppCoroutine{ std::coroutine_handle::from_promise(*this) }; + } + }; + + std::coroutine_handle handle; + + explicit CppCoroutine(std::coroutine_handle h) noexcept + : handle(h) + { + } + + CppCoroutine(CppCoroutine&& other) noexcept + : handle(std::exchange(other.handle, {})) + { + } + + CppCoroutine(const CppCoroutine&) = delete; + CppCoroutine& operator=(const CppCoroutine&) = delete; + ~CppCoroutine() = default; +}; + +//================================================================================================= +/** + * @brief Specialisation for void-returning coroutines. + */ +template <> +struct CppCoroutine +{ + struct promise_type + { + lua_State* L = nullptr; + int nresults = 0; + bool is_done = false; + std::exception_ptr exception; + + std::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + + void unhandled_exception() noexcept + { + exception = std::current_exception(); + } + + void return_void() + { + nresults = 0; + is_done = true; + } + + CppCoroutine get_return_object() + { + return CppCoroutine{ std::coroutine_handle::from_promise(*this) }; + } + }; + + std::coroutine_handle handle; + + explicit CppCoroutine(std::coroutine_handle h) noexcept + : handle(h) + { + } + + CppCoroutine(CppCoroutine&& other) noexcept + : handle(std::exchange(other.handle, {})) + { + } + + CppCoroutine(const CppCoroutine&) = delete; + CppCoroutine& operator=(const CppCoroutine&) = delete; + ~CppCoroutine() = default; +}; + +//================================================================================================= +/** + * @brief An awaitable wrapper around a Lua coroutine thread. + * + * Use inside a CppCoroutine body to synchronously resume a child Lua thread and obtain + * the number of values it left on its stack (either from yield or return). + * + * @note Runs the Lua thread synchronously (no external event loop required). + */ +class LuaCoroutine +{ +public: + LuaCoroutine(lua_State* thread, lua_State* from = nullptr) noexcept + : m_thread(thread) + , m_from(from) + { + } + + bool await_ready() noexcept + { + m_status = lua_resume_x(m_thread, m_from, 0, &m_nresults); + return true; // Always ready: runs synchronously + } + + void await_suspend(std::coroutine_handle<>) noexcept + { + // Never called because await_ready always returns true + } + + /** + * @returns {status, nresults} where status is LUA_OK or LUA_YIELD, + * and nresults is the number of values on the thread's stack. + */ + std::pair await_resume() noexcept + { + return { m_status, m_nresults }; + } + +private: + lua_State* m_thread; + lua_State* m_from; + int m_status = LUABRIDGE_LUA_OK; + int m_nresults = 0; +}; + +//================================================================================================= +namespace detail { + +/** + * @brief Trait: is T a CppCoroutine specialisation? + */ +template +struct is_cpp_coroutine : std::false_type +{ +}; + +template +struct is_cpp_coroutine> : std::true_type +{ +}; + +/** + * @brief Trait: does callable F return a CppCoroutine? + */ +template +struct is_cpp_coroutine_factory : std::false_type +{ +}; + +template +struct is_cpp_coroutine_factory>::result_type>> + : is_cpp_coroutine>::result_type> +{ +}; + +template +inline constexpr bool is_cpp_coroutine_factory_v = is_cpp_coroutine_factory::value; + +//================================================================================================= +/** + * @brief RAII frame for a suspended CppCoroutine, stored as a Lua full userdata. + * + * Kept alive on the Lua thread's own stack (not in the registry) so that abandoning the + * coroutine — i.e. letting the Lua thread be collected by the GC — automatically triggers + * the __gc metamethod, which calls the destructor and destroys the coroutine handle. + */ +template +struct CppCoroutineFrame +{ + using HandleType = std::coroutine_handle; + + HandleType handle; + + explicit CppCoroutineFrame(HandleType h) noexcept + : handle(h) + { + } + + CppCoroutineFrame(const CppCoroutineFrame&) = delete; + CppCoroutineFrame& operator=(const CppCoroutineFrame&) = delete; + + ~CppCoroutineFrame() + { + if (handle && !handle.done()) + handle.destroy(); + } +}; + +//================================================================================================= +// Version-portable yield helpers. +// +// Lua 5.2: lua_yieldk ctx is int; continuation signature is (lua_State*, int) — ctx +// retrieved inside via lua_getctx(). +// Lua 5.3+: lua_yieldk ctx is lua_KContext; continuation signature is +// (lua_State*, int, lua_KContext) — ctx passed directly. + +// Forward declarations +template int coroutine_continuation_body(lua_State* L, int frame_abs_idx); + +#if LUA_VERSION_NUM < 503 +// Lua 5.2: lua_yieldk takes lua_CFunction (int(*)(lua_State*)) as continuation. +// The context is recovered inside via lua_getctx(). +template +int coroutine_continuation(lua_State* L) +{ + int frame_abs_idx = 0; + lua_getctx(L, &frame_abs_idx); + return coroutine_continuation_body(L, frame_abs_idx); +} + +template +int do_yield(lua_State* L, int nresults, int frame_abs_idx) +{ + return lua_yieldk(L, nresults, frame_abs_idx, &coroutine_continuation); +} +#else +// Lua 5.3+: continuation receives lua_KContext directly. +template +int coroutine_continuation(lua_State* L, int /*status*/, lua_KContext ctx) +{ + return coroutine_continuation_body(L, static_cast(ctx)); +} + +template +int do_yield(lua_State* L, int nresults, int frame_abs_idx) +{ + return lua_yieldk(L, nresults, static_cast(frame_abs_idx), &coroutine_continuation); +} +#endif + +//================================================================================================= +/** + * @brief Raises a Lua error from a stored C++ exception (or a generic message). + * Removes the frame userdata from the stack before raising so GC can collect it. + */ +[[noreturn]] inline void raise_from_exception(lua_State* L, int frame_abs_idx, [[maybe_unused]] std::exception_ptr ex) +{ + lua_settop(L, frame_abs_idx - 1); // pop frame (and any value above it) — GC will collect it + +#if LUABRIDGE_HAS_EXCEPTIONS + try + { + std::rethrow_exception(ex); + } + catch (const std::exception& e) + { + raise_lua_error(L, "%s", e.what()); + } + catch (...) + { +#endif + + raise_lua_error(L, "unknown exception in C++ coroutine"); + +#if LUABRIDGE_HAS_EXCEPTIONS + } +#endif +} + +//================================================================================================= +/** + * @brief Common body for the coroutine continuation: resumes the C++ coroutine handle + * and either yields again or returns the final result. + * + * @param frame_abs_idx Absolute stack index where the CppCoroutineFrame userdata lives. + * Any resume arguments pushed above it are discarded first. + */ +template +int coroutine_continuation_body(lua_State* L, int frame_abs_idx) +{ + using CoroType = typename function_traits>::result_type; + using FrameType = CppCoroutineFrame; + + // Discard resume arguments pushed above the frame (we don't expose them to C++ yet) + lua_settop(L, frame_abs_idx); + + // Recover the frame from its stable stack position + auto* frame = align(lua_touserdata(L, frame_abs_idx)); + + // Resume the C++ coroutine body; yield_value/return_value will push at frame_abs_idx+1 + frame->handle.resume(); + + auto& promise = frame->handle.promise(); + + if (promise.exception) + raise_from_exception(L, frame_abs_idx, promise.exception); + + if (promise.is_done) + { + if (promise.nresults == 1) + lua_replace(L, frame_abs_idx); // swap return value into frame slot; pops frame userdata + else + lua_settop(L, frame_abs_idx - 1); // void: remove frame entirely + return promise.nresults; + } + + // yield_value pushed one value above the frame; yield it, keeping frame below + return do_yield(L, promise.nresults, frame_abs_idx); +} + +//================================================================================================= +/** + * @brief lua_CFunction entry point for a registered CppCoroutine factory. + * + * Upvalue 1: the factory functor F (as aligned full userdata). + * + * The CppCoroutineFrame userdata is left on the Lua thread's own stack (not in the registry). + * This means an abandoned coroutine is naturally cleaned up when the Lua thread is GC'd. + */ +template +int invoke_coroutine_entry(lua_State* L) +{ + using FnTraits = function_traits>; + using ArgsPack = typename FnTraits::argument_types; + using CoroType = typename FnTraits::result_type; + using FrameType = CppCoroutineFrame; + + LUABRIDGE_ASSERT(isfulluserdata(L, lua_upvalueindex(1))); + auto& factory = *align(lua_touserdata(L, lua_upvalueindex(1))); + + // Invoke the factory to create the coroutine object. + // The coroutine body does not run yet (initial_suspend returns suspend_always). + auto coro = invoke_callable_from_stack(L, factory); + + // Push the frame as a Lua full userdata and remember its absolute stack position. + // It is NOT pinned in the registry; keeping it on the thread's stack means GC will + // collect it (via __gc) when the Lua thread is abandoned. + lua_newuserdata_aligned(L, std::move(coro.handle)); + coro.handle = {}; // ownership transferred to frame + + int frame_abs_idx = lua_gettop(L); + auto* frame = align(lua_touserdata(L, frame_abs_idx)); + + // Give the promise access to the Lua state so yield_value/return_value can push values + frame->handle.promise().L = L; + + // First resume: runs the body to the first co_yield or co_return + frame->handle.resume(); + + auto& promise = frame->handle.promise(); + + if (promise.exception) + raise_from_exception(L, frame_abs_idx, promise.exception); + + if (promise.is_done) + { + if (promise.nresults == 1) + lua_replace(L, frame_abs_idx); // swap return value into frame slot + else + lua_settop(L, frame_abs_idx - 1); // void: remove frame + return promise.nresults; + } + + // yield_value pushed one value above the frame; yield it, keeping frame below + return do_yield(L, promise.nresults, frame_abs_idx); +} + +//================================================================================================= +/** + * @brief Pushes a CppCoroutine factory as a Lua closure onto the stack. + */ +template >> +inline void push_coroutine_function(lua_State* L, F&& f, const char* debugname) +{ + using FDecay = std::decay_t; + lua_newuserdata_aligned(L, std::forward(f)); + lua_pushcclosure_x(L, &invoke_coroutine_entry, debugname, 1); +} + +} // namespace detail +} // namespace luabridge + +#endif // !Lua 5.1 / LuaJIT / Luau +#endif // LUABRIDGE_HAS_CXX20_COROUTINES diff --git a/external/include/LuaBridge3/LuaBridge/detail/Enum.h b/external/include/LuaBridge3/LuaBridge/detail/Enum.h new file mode 100644 index 0000000000..b782712cfc --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Enum.h @@ -0,0 +1,67 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2023, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "Errors.h" +#include "LuaHelpers.h" +#include "Stack.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief LuaBridge enum wrapper for enums as integers. + * + * An enum exposed with this class will just be decayed to lua as integer. It's responsibility of + * the developer to make sure that a lua integer could be converted back to C++. Failing to validate a lua + * integer before converting to the corresponding C++ enum value could lead to a C++ enum that has no defined value. + * + * For improved security, specify which values the enum will have, so runtime validation could be performed. + */ +template +struct Enum +{ + static_assert(std::is_enum_v); + + using Type = std::underlying_type_t; + + [[nodiscard]] static Result push(lua_State* L, T value) + { + return Stack::push(L, static_cast(value)); + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + const auto result = Stack::get(L, index); + if (! result) + return result.error(); + + if constexpr (sizeof...(Values) > 0) + { + constexpr Type values[] = { static_cast(Values)... }; + for (std::size_t i = 0; i < sizeof...(Values); ++i) + { + if (values[i] == *result) + return static_cast(*result); + } + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + else + { + return static_cast(*result); + } + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TNUMBER; + } +}; + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Errors.h b/external/include/LuaBridge3/LuaBridge/detail/Errors.h new file mode 100644 index 0000000000..a9f091a01a --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Errors.h @@ -0,0 +1,123 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2021, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" + +#include + +namespace luabridge { + +//================================================================================================= +namespace detail { + +static inline constexpr char error_lua_stack_overflow[] = "stack overflow"; + +} // namespace detail + +//================================================================================================= +/** + * @brief LuaBridge error codes. + */ +enum class ErrorCode +{ + ClassNotRegistered = 1, + + LuaStackOverflow, + + LuaFunctionCallFailed, + + IntegerDoesntFitIntoLuaInteger, + + FloatingPointDoesntFitIntoLuaNumber, + + InvalidTypeCast, + + InvalidTableSizeInCast, + + CoroutineYieldFromNonCoroutine, + + CoroutineAlreadyDone +}; + +//================================================================================================= +namespace detail { +struct ErrorCategory : std::error_category +{ + const char* name() const noexcept override + { + return "luabridge"; + } + + std::string message(int ev) const override + { + return errorString(ev); + } + + static const char* errorString(int ev) noexcept + { + switch (static_cast(ev)) + { + case ErrorCode::ClassNotRegistered: + return "The class is not registered in LuaBridge"; + + case ErrorCode::LuaStackOverflow: + return "The lua stack has overflow"; + + case ErrorCode::LuaFunctionCallFailed: + return "The lua function invocation raised an error"; + + case ErrorCode::IntegerDoesntFitIntoLuaInteger: + return "The native integer can't fit inside a lua integer"; + + case ErrorCode::FloatingPointDoesntFitIntoLuaNumber: + return "The native floating point can't fit inside a lua number"; + + case ErrorCode::InvalidTypeCast: + return "The lua object can't be cast to desired type"; + + case ErrorCode::InvalidTableSizeInCast: + return "The lua table has different size than expected"; + + case ErrorCode::CoroutineYieldFromNonCoroutine: + return "Cannot yield from a non-coroutine Lua state"; + + case ErrorCode::CoroutineAlreadyDone: + return "The Lua coroutine has already finished execution"; + + default: + return "Unknown error"; + } + } + + static const ErrorCategory& getInstance() noexcept + { + static ErrorCategory category; + return category; + } +}; +} // namespace detail + +//================================================================================================= +/** + * @brief Construct an error code from the error enum. + */ +inline std::error_code makeErrorCode(ErrorCode e) +{ + return { static_cast(e), detail::ErrorCategory::getInstance() }; +} + +/** + * @brief Supports std::error_code construction. + */ +inline std::error_code make_error_code(ErrorCode e) +{ + return { static_cast(e), detail::ErrorCategory::getInstance() }; +} +} // namespace luabridge + +namespace std { +template <> struct is_error_code_enum : true_type {}; +} // namespace std diff --git a/external/include/LuaBridge3/LuaBridge/detail/Expected.h b/external/include/LuaBridge3/LuaBridge/detail/Expected.h new file mode 100644 index 0000000000..6eb7544070 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Expected.h @@ -0,0 +1,1632 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2022, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" + +#include +#include +#include +#include + +#if LUABRIDGE_HAS_EXCEPTIONS +#include +#endif + +namespace luabridge { +namespace detail { +using std::swap; + +template +T* construct_at(T* ptr, Args&&... args) noexcept(std::is_nothrow_constructible::value) +{ + return static_cast(::new (const_cast(static_cast(ptr))) T(std::forward(args)...)); +} + +template +struct is_swappable_with_impl : std::false_type +{ +}; + +template +struct is_swappable_with_impl(), std::declval()))>> + : std::true_type +{ +}; + +template +struct is_nothrow_swappable_with_impl +{ + static constexpr bool value = noexcept(swap(std::declval(), std::declval())) && noexcept(swap(std::declval(), std::declval())); + + using type = std::bool_constant; +}; + +template +struct is_swappable_with + : std::conjunction< + is_swappable_with_impl, std::add_lvalue_reference_t>, + is_swappable_with_impl, std::add_lvalue_reference_t>>::type +{ +}; + +template +struct is_nothrow_swappable_with + : std::conjunction, is_nothrow_swappable_with_impl>::type +{ +}; + +template +struct is_nothrow_swappable + : std::is_nothrow_swappable_with, std::add_lvalue_reference_t> +{ +}; + +template +struct has_member_message : std::false_type +{ +}; + +template +struct has_member_message().message())>> : std::true_type +{ +}; + +template +inline static constexpr bool has_member_message_v = has_member_message::value; +} // namespace detail + +template +class Expected; + +struct UnexpectType +{ + constexpr UnexpectType() = default; +}; + +static constexpr auto unexpect = UnexpectType(); + +namespace detail { +template , bool = (std::is_void_v || std::is_trivial_v) && std::is_trivial_v> +union expected_storage +{ +public: + template >> + constexpr expected_storage() noexcept + : value_() + { + } + + template + constexpr explicit expected_storage(std::in_place_t, Args&&... args) noexcept + : value_(std::forward(args)...) + { + } + + template + constexpr explicit expected_storage(UnexpectType, Args&&... args) noexcept + : error_(std::forward(args)...) + { + } + + ~expected_storage() = default; + + constexpr const T& value() const noexcept + { + return value_; + } + + constexpr T& value() noexcept + { + return value_; + } + + constexpr const E& error() const noexcept + { + return error_; + } + + constexpr E& error() noexcept + { + return error_; + } + +private: + T value_; + E error_; +}; + +template +union expected_storage +{ +public: + constexpr expected_storage() noexcept + : dummy_(0) + { + } + + template + constexpr explicit expected_storage(UnexpectType, Args&&... args) noexcept + : error_(std::forward(args)...) + { + } + + ~expected_storage() = default; + + constexpr const E& error() const noexcept + { + return error_; + } + + constexpr E& error() noexcept + { + return error_; + } + +private: + char dummy_; + E error_; +}; + +template +union expected_storage +{ +public: + constexpr expected_storage() noexcept(std::is_nothrow_default_constructible_v) + : value_() + { + } + + template + constexpr explicit expected_storage(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : value_(std::forward(args)...) + { + } + + template + constexpr explicit expected_storage(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : error_(std::forward(args)...) + { + } + + ~expected_storage() + { + } + + constexpr const T& value() const noexcept + { + return value_; + } + + constexpr T& value() noexcept + { + return value_; + } + + constexpr const E& error() const noexcept + { + return error_; + } + + constexpr E& error() noexcept + { + return error_; + } + +private: + T value_; + E error_; +}; + +template +union expected_storage +{ +public: + constexpr explicit expected_storage() noexcept + : dummy_(0) + { + } + + template + constexpr explicit expected_storage(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : value_(std::forward(args)...) + { + } + + template + constexpr explicit expected_storage(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : error_(std::forward(args)...) + { + } + + ~expected_storage() + { + } + + constexpr const T& value() const noexcept + { + return value_; + } + + constexpr T& value() noexcept + { + return value_; + } + + constexpr const E& error() const noexcept + { + return error_; + } + + constexpr E& error() noexcept + { + return error_; + } + +private: + char dummy_; + T value_; + E error_; +}; + +template +union expected_storage +{ +public: + constexpr expected_storage() noexcept + : dummy_(0) + { + } + + template + constexpr explicit expected_storage(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : error_(std::forward(args)...) + { + } + + ~expected_storage() = default; + + constexpr const E& error() const noexcept + { + return error_; + } + + constexpr E& error() noexcept + { + return error_; + } + +private: + char dummy_; + E error_; +}; + +template +class expected_base_trivial +{ + using this_type = expected_base_trivial; + +protected: + using storage_type = expected_storage; + + constexpr expected_base_trivial() noexcept + : valid_(true) + { + } + + template + constexpr expected_base_trivial(std::in_place_t, Args&&... args) noexcept + : storage_(std::in_place, std::forward(args)...) + , valid_(true) + { + } + + template + constexpr expected_base_trivial(UnexpectType, Args&&... args) noexcept + : storage_(unexpect, std::forward(args)...) + , valid_(false) + { + } + + expected_base_trivial(const expected_base_trivial& other) noexcept + { + if (other.valid_) + { + construct(std::in_place, other.value()); + } + else + { + construct(unexpect, other.error()); + } + } + + expected_base_trivial(expected_base_trivial&& other) noexcept + { + if (other.valid_) + { + construct(std::in_place, std::move(other.value())); + } + else + { + construct(unexpect, std::move(other.error())); + } + } + + ~expected_base_trivial() noexcept = default; + + constexpr const T& value() const noexcept + { + return storage_.value(); + } + + constexpr T& value() noexcept + { + return storage_.value(); + } + + constexpr const E& error() const noexcept + { + return storage_.error(); + } + + constexpr E& error() noexcept + { + return storage_.error(); + } + + constexpr const T* valuePtr() const noexcept + { + return std::addressof(value()); + } + + constexpr T* valuePtr() noexcept + { + return std::addressof(value()); + } + + constexpr const E* errorPtr() const noexcept + { + return std::addressof(error()); + } + + constexpr E* errorPtr() noexcept + { + return std::addressof(error()); + } + + constexpr bool valid() const noexcept + { + return valid_; + } + + template + inline T& construct(std::in_place_t, Args&&... args) noexcept + { + valid_ = true; + return *detail::construct_at(valuePtr(), std::forward(args)...); + } + + template + inline E& construct(UnexpectType, Args&&... args) noexcept + { + valid_ = false; + return *detail::construct_at(errorPtr(), std::forward(args)...); + } + + inline void destroy() noexcept + { + } + +private: + storage_type storage_; + bool valid_; +}; + +template +class expected_base_non_trivial +{ + using this_type = expected_base_non_trivial; + +protected: + using storage_type = expected_storage; + + constexpr expected_base_non_trivial() noexcept(std::is_nothrow_default_constructible_v) + : valid_(true) + { + } + + template + constexpr expected_base_non_trivial(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(std::in_place, std::forward(args)...) + , valid_(true) + { + } + + template + constexpr expected_base_non_trivial(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(unexpect, std::forward(args)...) + , valid_(false) + { + } + + expected_base_non_trivial(const expected_base_non_trivial& other) + { + if (other.valid_) + { + construct(std::in_place, other.value()); + } + else + { + construct(unexpect, other.error()); + } + } + + expected_base_non_trivial(expected_base_non_trivial&& other) noexcept + { + if (other.valid_) + { + construct(std::in_place, std::move(other.value())); + } + else + { + construct(unexpect, std::move(other.error())); + } + } + + ~expected_base_non_trivial() + { + destroy(); + } + + constexpr const T& value() const noexcept + { + return storage_.value(); + } + + constexpr T& value() noexcept + { + return storage_.value(); + } + + constexpr const E& error() const noexcept + { + return storage_.error(); + } + + constexpr E& error() noexcept + { + return storage_.error(); + } + + constexpr const T* valuePtr() const noexcept + { + return std::addressof(value()); + } + + constexpr T* valuePtr() noexcept + { + return std::addressof(value()); + } + + constexpr const E* errorPtr() const noexcept + { + return std::addressof(error()); + } + + constexpr E* errorPtr() noexcept + { + return std::addressof(error()); + } + + constexpr bool valid() const noexcept + { + return valid_; + } + + template + inline T& construct(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = true; + return *detail::construct_at(valuePtr(), std::forward(args)...); + } + + template + inline E& construct(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = false; + return *detail::construct_at(errorPtr(), std::forward(args)...); + } + + inline void destroy() noexcept(std::is_nothrow_destructible_v&& std::is_nothrow_destructible_v) + { + if (valid_) + { + std::destroy_at(valuePtr()); + } + else + { + std::destroy_at(errorPtr()); + } + } + +private: + storage_type storage_; + bool valid_; +}; + +template +class expected_base_non_trivial +{ + using this_type = expected_base_non_trivial; + +protected: + using storage_type = expected_storage; + + constexpr expected_base_non_trivial() noexcept(std::is_nothrow_default_constructible_v) + : valid_(true) + { + } + + template + constexpr expected_base_non_trivial(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(std::in_place, std::forward(args)...) + , valid_(true) + { + } + + template + constexpr expected_base_non_trivial(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(unexpect, std::forward(args)...) + , valid_(false) + { + } + + expected_base_non_trivial(const expected_base_non_trivial& other) = delete; + + expected_base_non_trivial(expected_base_non_trivial&& other) noexcept + { + if (other.valid_) + { + construct(std::in_place, std::move(other.value())); + } + else + { + construct(unexpect, std::move(other.error())); + } + } + + ~expected_base_non_trivial() + { + destroy(); + } + + constexpr const T& value() const noexcept + { + return storage_.value(); + } + + constexpr T& value() noexcept + { + return storage_.value(); + } + + constexpr const E& error() const noexcept + { + return storage_.error(); + } + + constexpr E& error() noexcept + { + return storage_.error(); + } + + constexpr const T* valuePtr() const noexcept + { + return std::addressof(value()); + } + + constexpr T* valuePtr() noexcept + { + return std::addressof(value()); + } + + constexpr const E* errorPtr() const noexcept + { + return std::addressof(error()); + } + + constexpr E* errorPtr() noexcept + { + return std::addressof(error()); + } + + constexpr bool valid() const noexcept + { + return valid_; + } + + template + inline T& construct(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = true; + return *detail::construct_at(valuePtr(), std::forward(args)...); + } + + template + inline E& construct(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = false; + return *detail::construct_at(errorPtr(), std::forward(args)...); + } + + inline void destroy() noexcept(std::is_nothrow_destructible_v&& std::is_nothrow_destructible_v) + { + if (valid_) + { + std::destroy_at(valuePtr()); + } + else + { + std::destroy_at(errorPtr()); + } + } + +private: + storage_type storage_; + bool valid_; +}; + +template +class expected_base_non_trivial +{ + using this_type = expected_base_non_trivial; + +protected: + using storage_type = expected_storage; + + template >> + constexpr expected_base_non_trivial() noexcept(std::is_nothrow_default_constructible_v) + : valid_(true) + { + } + + template + constexpr expected_base_non_trivial(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(std::in_place, std::forward(args)...) + , valid_(true) + { + } + + template + constexpr expected_base_non_trivial(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(unexpect, std::forward(args)...) + , valid_(false) + { + } + + expected_base_non_trivial(const expected_base_non_trivial& other) + { + if (other.valid_) + { + construct(std::in_place, other.value()); + } + else + { + construct(unexpect, other.error()); + } + } + + expected_base_non_trivial(expected_base_non_trivial&& other) = delete; + + ~expected_base_non_trivial() + { + destroy(); + } + + constexpr const T& value() const noexcept + { + return storage_.value(); + } + + constexpr T& value() noexcept + { + return storage_.value(); + } + + constexpr const E& error() const noexcept + { + return storage_.error(); + } + + constexpr E& error() noexcept + { + return storage_.error(); + } + + constexpr const T* valuePtr() const noexcept + { + return std::addressof(value()); + } + + constexpr T* valuePtr() noexcept + { + return std::addressof(value()); + } + + constexpr const E* errorPtr() const noexcept + { + return std::addressof(error()); + } + + constexpr E* errorPtr() noexcept + { + return std::addressof(error()); + } + + constexpr bool valid() const noexcept + { + return valid_; + } + + template + inline T& construct(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = true; + return *detail::construct_at(valuePtr(), std::forward(args)...); + } + + template + inline E& construct(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = false; + return *detail::construct_at(errorPtr(), std::forward(args)...); + } + + inline void destroy() noexcept(std::is_nothrow_destructible_v&& std::is_nothrow_destructible_v) + { + if (valid_) + { + std::destroy_at(valuePtr()); + } + else + { + std::destroy_at(errorPtr()); + } + } + +private: + storage_type storage_; + bool valid_; +}; + +template +class expected_base_non_trivial +{ + using this_type = expected_base_non_trivial; + +protected: + using storage_type = expected_storage; + + template >> + constexpr expected_base_non_trivial() noexcept(std::is_nothrow_default_constructible_v) + : valid_(true) + { + } + + template + constexpr expected_base_non_trivial(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(std::in_place, std::forward(args)...) + , valid_(true) + { + } + + template + constexpr expected_base_non_trivial(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : storage_(unexpect, std::forward(args)...) + , valid_(false) + { + } + + expected_base_non_trivial(const expected_base_non_trivial& other) = delete; + + expected_base_non_trivial(expected_base_non_trivial&& other) = delete; + + ~expected_base_non_trivial() + { + destroy(); + } + + constexpr const T& value() const noexcept + { + return storage_.value(); + } + + constexpr T& value() noexcept + { + return storage_.value(); + } + + constexpr const E& error() const noexcept + { + return storage_.error(); + } + + constexpr E& error() noexcept + { + return storage_.error(); + } + + constexpr const T* valuePtr() const noexcept + { + return std::addressof(value()); + } + + constexpr T* valuePtr() noexcept + { + return std::addressof(value()); + } + + constexpr const E* errorPtr() const noexcept + { + return std::addressof(error()); + } + + constexpr E* errorPtr() noexcept + { + return std::addressof(error()); + } + + constexpr bool valid() const noexcept + { + return valid_; + } + + template + inline T& construct(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = true; + return *detail::construct_at(valuePtr(), std::forward(args)...); + } + + template + inline E& construct(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + { + valid_ = false; + return *detail::construct_at(errorPtr(), std::forward(args)...); + } + + inline void destroy() noexcept(std::is_nothrow_destructible_v&& std::is_nothrow_destructible_v) + { + if (valid_) + { + std::destroy_at(valuePtr()); + } + else + { + std::destroy_at(errorPtr()); + } + } + +private: + storage_type storage_; + bool valid_; +}; + +template +using expected_base = std::conditional_t< + (std::is_void_v || std::is_trivially_destructible_v) && std::is_trivially_destructible_v, + expected_base_trivial, + expected_base_non_trivial>; + +} // namespace detail + +template +class Unexpected +{ + static_assert(!std::is_reference_v && !std::is_void_v, "Unexpected type can't be a reference or void"); + +public: + Unexpected() = delete; + + constexpr explicit Unexpected(E&& e) noexcept(std::is_nothrow_move_constructible_v) + : error_(std::move(e)) + { + } + + constexpr explicit Unexpected(const E& e) noexcept(std::is_nothrow_copy_constructible_v) + : error_(e) + { + } + + constexpr const E& value() const& noexcept + { + return error_; + } + + constexpr E& value() & noexcept + { + return error_; + } + + constexpr const E&& value() const&& noexcept + { + return std::move(error_); + } + + constexpr E&& value() && noexcept + { + return std::move(error_); + } + +private: + E error_; +}; + +template +constexpr bool operator==(const Unexpected& lhs, const Unexpected& rhs) noexcept +{ + return lhs.value() == rhs.value(); +} + +template +constexpr bool operator!=(const Unexpected& lhs, const Unexpected& rhs) noexcept +{ + return lhs.value() != rhs.value(); +} + +template +constexpr inline Unexpected> makeUnexpected(E&& error) noexcept(std::is_nothrow_constructible_v>, E>) +{ + return Unexpected>{ std::forward(error) }; +} + +#if LUABRIDGE_HAS_EXCEPTIONS +template +class BadExpectedAccess; + +template <> +class BadExpectedAccess : public std::runtime_error +{ + template + friend class BadExpectedAccess; + + BadExpectedAccess(std::in_place_t) noexcept + : std::runtime_error("Bad access to expected value") + { + } + +public: + BadExpectedAccess() noexcept + : BadExpectedAccess(std::in_place) + { + } + + explicit BadExpectedAccess(const std::string& message) noexcept + : std::runtime_error(message) + { + } +}; + +template +class BadExpectedAccess : public std::runtime_error +{ +public: + explicit BadExpectedAccess(E error) noexcept(std::is_nothrow_constructible_v) + : std::runtime_error([](const E& error) + { + if constexpr (detail::has_member_message_v) + return error.message(); + else + return "Bad access to expected value"; + }(error)) + , error_(std::move(error)) + { + } + + const E& error() const& noexcept + { + return error_; + } + + E& error() & noexcept + { + return error_; + } + + E&& error() && noexcept + { + return std::move(error_); + } + +private: + E error_; +}; +#endif + +namespace detail { +template +inline void throw_bad_expected_access_or_abort([[maybe_unused]] E e) +{ +#if LUABRIDGE_HAS_EXCEPTIONS + throw BadExpectedAccess(std::move(e)); +#else + std::abort(); +#endif +} +} // namespace detail + +template +struct is_expected : std::false_type +{ +}; + +template +struct is_expected> : std::true_type +{ +}; +template +struct is_unexpected : std::false_type +{ +}; + +template +struct is_unexpected> : std::true_type +{ +}; + +template +class Expected : public detail::expected_base, std::is_move_constructible_v> +{ + static_assert(!std::is_reference_v && !std::is_void_v, "Unexpected type can't be a reference or void"); + + using base_type = detail::expected_base, std::is_move_constructible_v>; + using this_type = Expected; + +public: + using value_type = T; + + using error_type = E; + + using unexpected_type = Unexpected; + + template + struct rebind + { + using type = Expected; + }; + + template >> + constexpr Expected() noexcept(std::is_nothrow_default_constructible_v) + : base_type() + { + } + + constexpr Expected(const Expected& other) noexcept(std::is_nothrow_copy_constructible_v) = default; + + constexpr Expected(Expected&& other) noexcept(std::is_nothrow_move_constructible_v) = default; + + template + Expected(const Expected& other) + { + if (other.hasValue()) + { + this->construct(std::in_place, other.value()); + } + else + { + this->construct(unexpect, other.error()); + } + } + + template + Expected(Expected&& other) + { + if (other.hasValue()) + { + this->construct(std::in_place, std::move(other.value())); + } + else + { + this->construct(unexpect, std::move(other.error())); + } + } + + template && std::is_constructible_v && !std::is_same_v, std::in_place_t> && !is_expected>::value && !is_unexpected>::value, int> = 0> + constexpr Expected(U&& value) noexcept(std::is_nothrow_constructible_v) + : base_type(std::in_place, std::forward(value)) + { + } + + template + constexpr explicit Expected(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : base_type(std::in_place, std::forward(args)...) + { + } + + template + constexpr explicit Expected(std::in_place_t, std::initializer_list ilist, Args&&... args) noexcept(std::is_nothrow_constructible_v, Args...>) + : base_type(std::in_place, ilist, std::forward(args)...) + { + } + + template + constexpr Expected(const Unexpected& u) noexcept(std::is_nothrow_constructible_v) + : base_type(unexpect, u.value()) + { + } + + template + constexpr Expected(Unexpected&& u) noexcept(std::is_nothrow_constructible_v) + : base_type(unexpect, std::move(u.value())) + { + } + + template + constexpr explicit Expected(UnexpectType, Args&&... args) noexcept(std::is_nothrow_constructible_v) + : base_type(unexpect, std::forward(args)...) + { + } + + template + constexpr explicit Expected(UnexpectType, std::initializer_list ilist, Args&&... args) noexcept(std::is_nothrow_constructible_v, Args...>) + : base_type(unexpect, ilist, std::forward(args)...) + { + } + + Expected& operator=(const Expected& other) + { + if (other.hasValue()) + { + assign(std::in_place, other.value()); + } + else + { + assign(unexpect, other.error()); + } + + return *this; + } + + Expected& operator=(Expected&& other) noexcept + { + if (other.hasValue()) + { + assign(std::in_place, std::move(other.value())); + } + else + { + assign(unexpect, std::move(other.error())); + } + + return *this; + } + + template >::value && !is_unexpected>::value, int> = 0> + Expected& operator=(U&& value) + { + assign(std::in_place, std::forward(value)); + return *this; + } + + template + Expected& operator=(const Unexpected& u) + { + assign(unexpect, u.value()); + return *this; + } + + template + Expected& operator=(Unexpected&& u) + { + assign(unexpect, std::move(u.value())); + return *this; + } + + template + T& emplace(Args&&... args) noexcept(noexcept(std::declval().assign(std::in_place, std::forward(args)...))) + { + return assign(std::in_place, std::forward(args)...); + } + + template + T& emplace(std::initializer_list ilist, Args&&... args) noexcept(noexcept(std::declval().assign(std::in_place, ilist, std::forward(args)...))) + { + return assign(std::in_place, ilist, std::forward(args)...); + } + + void swap(Expected& other) noexcept(detail::is_nothrow_swappable::value && detail::is_nothrow_swappable::value) + { + using std::swap; + + if (hasValue()) + { + if (other.hasValue()) + { + swap(value(), other.value()); + } + else + { + E error = std::move(other.error()); + other.assign(std::in_place, std::move(value())); + assign(unexpect, std::move(error)); + } + } + else + { + if (other.hasValue()) + { + other.swap(*this); + } + else + { + swap(error(), other.error()); + } + } + } + + constexpr const T* operator->() const + { + return base_type::valuePtr(); + } + + constexpr T* operator->() + { + return base_type::valuePtr(); + } + + constexpr const T& operator*() const& + { + return value(); + } + + constexpr T& operator*() & + { + return value(); + } + + constexpr const T&& operator*() const&& + { + return std::move(value()); + } + + constexpr T&& operator*() && + { + return std::move(value()); + } + + constexpr explicit operator bool() const noexcept + { + return hasValue(); + } + + constexpr bool hasValue() const noexcept + { + return base_type::valid(); + } + + constexpr const T& value() const& LUABRIDGE_IF_NO_EXCEPTIONS(noexcept) + { + if (!hasValue()) + detail::throw_bad_expected_access_or_abort(std::move(error())); + + return base_type::value(); + } + + constexpr T& value() & LUABRIDGE_IF_NO_EXCEPTIONS(noexcept) + { + if (!hasValue()) + detail::throw_bad_expected_access_or_abort(std::move(error())); + + return base_type::value(); + } + + constexpr const T&& value() const&& LUABRIDGE_IF_NO_EXCEPTIONS(noexcept) + { + if (!hasValue()) + detail::throw_bad_expected_access_or_abort(std::move(error())); + + return std::move(base_type::value()); + } + + constexpr T&& value() && LUABRIDGE_IF_NO_EXCEPTIONS(noexcept) + { + if (!hasValue()) + detail::throw_bad_expected_access_or_abort(std::move(error())); + + return std::move(base_type::value()); + } + + constexpr const E& error() const& noexcept + { + return base_type::error(); + } + + constexpr E& error() & noexcept + { + return base_type::error(); + } + + constexpr const E&& error() const&& noexcept + { + return std::move(base_type::error()); + } + + constexpr E&& error() && noexcept + { + return std::move(base_type::error()); + } + + template + constexpr T valueOr(U&& defaultValue) const& + { + return hasValue() ? value() : static_cast(std::forward(defaultValue)); + } + + template + T valueOr(U&& defaultValue) && + { + return hasValue() ? std::move(value()) : static_cast(std::forward(defaultValue)); + } + +private: + template + auto assign(Tag tag, Args&&... args) noexcept(noexcept(std::declval().destroy()) && noexcept(std::declval().construct(tag, std::forward(args)...))) + -> decltype(std::declval().construct(tag, std::forward(args)...)) + { + this->destroy(); + + return this->construct(tag, std::forward(args)...); + } +}; + +template +class Expected : public detail::expected_base, std::is_move_constructible_v> +{ + static_assert(!std::is_reference_v && !std::is_void_v, "Unexpected type can't be a reference or void"); + + using base_type = detail::expected_base, std::is_move_constructible_v>; + using this_type = Expected; + +public: + using value_type = void; + + using error_type = E; + + using unexpected_type = Unexpected; + + template + struct rebind + { + using type = Expected; + }; + + constexpr Expected() = default; + + constexpr Expected(const Expected& other) = default; + + constexpr Expected(Expected&& other) = default; + + template + Expected(const Expected& other) + { + if (other.hasValue()) + { + this->valid_ = true; + } + else + { + this->construct(unexpect, other.error()); + } + } + + template + Expected(Expected&& other) + { + if (other.hasValue()) + { + this->valid_ = true; + } + else + { + this->construct(unexpect, std::move(other.error())); + } + } + + template + constexpr Expected(const Unexpected& u) + : base_type(unexpect, u.value()) + { + } + + template + constexpr Expected(Unexpected&& u) + : base_type(unexpect, std::move(u.value())) + { + } + + template + constexpr explicit Expected(UnexpectType, Args&&... args) + : base_type(unexpect, std::forward(args)...) + { + } + + template + constexpr explicit Expected(UnexpectType, std::initializer_list ilist, Args&&... args) + : base_type(unexpect, ilist, std::forward(args)...) + { + } + + Expected& operator=(const Expected& other) + { + if (other.hasValue()) + { + assign(std::in_place); + } + else + { + assign(unexpect, other.error()); + } + + return *this; + } + + Expected& operator=(Expected&& other) + { + if (other.hasValue()) + { + assign(std::in_place); + } + else + { + assign(unexpect, std::move(other.error())); + } + + return *this; + } + + template + Expected& operator=(const Unexpected& u) + { + assign(unexpect, u.value()); + return *this; + } + + template + Expected& operator=(Unexpected&& u) + { + assign(unexpect, std::move(u.value())); + return *this; + } + + void swap(Expected& other) noexcept(detail::is_nothrow_swappable::value) + { + using std::swap; + + if (hasValue()) + { + if (!other.hasValue()) + { + assign(unexpect, std::move(other.error())); + other.assign(std::in_place); + } + } + else + { + if (other.hasValue()) + { + other.swap(*this); + } + else + { + swap(error(), other.error()); + } + } + } + + constexpr explicit operator bool() const noexcept + { + return hasValue(); + } + + constexpr bool hasValue() const noexcept + { + return base_type::valid(); + } + + constexpr const E& error() const& noexcept + { + return base_type::error(); + } + + constexpr E& error() & noexcept + { + return base_type::error(); + } + + constexpr const E&& error() const&& noexcept + { + return std::move(base_type::error()); + } + + constexpr E&& error() && noexcept + { + return std::move(base_type::error()); + } + +private: + template + void assign(Tag tag, Args&&... args) noexcept(noexcept(std::declval().destroy()) && noexcept(std::declval().construct(tag, std::forward(args)...))) + { + this->destroy(); + this->construct(tag, std::forward(args)...); + } +}; + +template +constexpr bool operator==(const Expected& lhs, const Expected& rhs) +{ + return (lhs && rhs) ? *lhs == *rhs : ((!lhs && !rhs) ? lhs.error() == rhs.error() : false); +} + +template +constexpr bool operator==(const Expected& lhs, const Expected& rhs) +{ + return (lhs && rhs) ? true : ((!lhs && !rhs) ? lhs.error() == rhs.error() : false); +} + +template +constexpr bool operator!=(const Expected& lhs, const Expected& rhs) +{ + return !(lhs == rhs); +} + +template +constexpr bool operator==(const Expected& lhs, const T& rhs) +{ + return lhs ? *lhs == rhs : false; +} + +template +constexpr bool operator==(const T& lhs, const Expected& rhs) +{ + return rhs == lhs; +} + +template +constexpr bool operator!=(const Expected& lhs, const T& rhs) +{ + return !(lhs == rhs); +} + +template +constexpr bool operator!=(const T& lhs, const Expected& rhs) +{ + return rhs != lhs; +} + +template +constexpr bool operator==(const Expected& lhs, const Unexpected& rhs) +{ + return lhs ? false : lhs.error() == rhs.value(); +} + +template +constexpr bool operator==(const Unexpected& lhs, const Expected& rhs) +{ + return rhs == lhs; +} + +template +constexpr bool operator!=(const Expected& lhs, const Unexpected& rhs) +{ + return !(lhs == rhs); +} + +template +constexpr bool operator!=(const Unexpected& lhs, const Expected& rhs) +{ + return rhs != lhs; +} +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/FlagSet.h b/external/include/LuaBridge3/LuaBridge/detail/FlagSet.h new file mode 100644 index 0000000000..91b4667697 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/FlagSet.h @@ -0,0 +1,124 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2023, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" + +#include +#include +#include + +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; +}; + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/FuncTraits.h b/external/include/LuaBridge3/LuaBridge/detail/FuncTraits.h new file mode 100644 index 0000000000..8eb6d10407 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/FuncTraits.h @@ -0,0 +1,895 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2020, Dmitry Tarakanov +// Copyright 2019, George Tokmaji +// Copyright 2012, Vinnie Falco +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" + +#include +#include + +namespace luabridge { +namespace detail { + +//================================================================================================= +/** + * @brief Invokes undefined behavior when an unreachable part of the code is reached. + * + * An implementation may use this to optimize impossible code branches away (typically, in optimized builds) or to trap them to prevent + * further execution (typically, in debug builds). + */ +[[noreturn]] inline void unreachable() +{ +#if defined(__GNUC__) // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif +} + +//================================================================================================= +/** + * @brief Provides the member typedef type which is the type referred to by T with its topmost cv-qualifiers removed. + */ +template< class T > +struct remove_cvref +{ + typedef std::remove_cv_t> type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +//================================================================================================= +/** + * @brief Generic function traits. + * + * @tparam IsMember True if the function is a member function pointer. + * @tparam IsConst True if the function is const. + * @tparam R Return type of the function. + * @tparam Args Arguments types as variadic parameter pack. + */ +template +struct function_traits_base +{ + using result_type = R; + + using argument_types = std::tuple; + + static constexpr auto arity = sizeof...(Args); + + static constexpr auto is_member = IsMember; + + static constexpr auto is_const = IsConst; +}; + +template +struct function_traits_impl; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +#if defined(_MSC_VER) && defined(_M_IX86) // Windows: WINAPI (a.k.a. __stdcall) function pointers (32bit only). +inline static constexpr bool is_stdcall_default_calling_convention = std::is_same_v; +inline static constexpr bool is_fastcall_default_calling_convention = std::is_same_v; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; + +template +struct function_traits_impl : function_traits_base +{ +}; +#endif + +template +struct has_call_operator : std::false_type +{ +}; + +template +struct has_call_operator> : std::true_type +{ +}; + +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 +{ +}; + +template +struct functor_traits_impl && !is_move_only_function_v>> : function_traits_impl +{ +}; + +template +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) + * + * @tparam F Callable object. + */ +template +struct function_traits : std::conditional_t, + detail::functor_traits_impl, + detail::function_traits_impl> +{ +}; + +template , class = void> +struct has_function_traits : std::false_type +{ +}; + +template +struct has_function_traits::result_type, typename function_traits::argument_types>> : std::true_type +{ +}; + +template +inline static constexpr bool has_function_traits_v = has_function_traits::value; + +//================================================================================================= +/** + * @brief Deduces the argument type of a callble object or void in case it has no argument. + * + * @tparam I Argument index. + * @tparam F Callable object. + */ +template +struct function_argument_or_void +{ + using type = void; +}; + +template +struct function_argument_or_void::argument_types>>> +{ + using type = std::tuple_element_t::argument_types>; +}; + +template +using function_argument_or_void_t = typename function_argument_or_void::type; + +//================================================================================================= +/** + * @brief Deduces the return type of a callble object. + * + * @tparam F Callable object. + */ +template +using function_result_t = typename function_traits::result_type; + +/** + * @brief Deduces the argument type of a callble object. + * + * @tparam I Argument index. + * @tparam F Callable object. + */ +template +using function_argument_t = std::tuple_element_t::argument_types>; + +/** + * @brief Deduces the arguments type of a callble object. + * + * @tparam F Callable object. + */ +template +using function_arguments_t = typename function_traits::argument_types; + +/** + * @brief An integral constant expression that gives the number of arguments accepted by the callable object. + * + * @tparam F Callable object. + */ +template +static constexpr std::size_t function_arity_v = function_traits::arity; + +/** + * @brief An boolean constant expression that checks if the callable object is a member function. + * + * @tparam F Callable object. + */ +template +static constexpr bool function_is_member_v = function_traits::is_member; + +/** + * @brief An boolean constant expression that checks if the callable object is const. + * + * @tparam F Callable object. + */ +template +static constexpr bool function_is_const_v = function_traits::is_const; + +//================================================================================================= +/** + * @brief Detect if we T is a callable object. + * + * @tparam T Potentially callable object. + */ +template +struct is_callable +{ + static constexpr bool value = false; +}; + +template +struct is_callable> +{ + static constexpr bool value = true; +}; + +template +struct is_callable && !has_call_operator_v && has_function_traits_v>> +{ + static constexpr bool value = true; +}; + +template +struct is_callable && std::is_function_v>>> +{ + static constexpr bool value = true; +}; + +template +struct is_callable>> +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_callable_v = is_callable::value; + +//================================================================================================= +/** + * @brief Detect if we T is a const member function pointer. + * + * @tparam T Potentially const member function pointer. + */ +template +struct is_const_member_function_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = true; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_function_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_const_member_function_pointer_v = is_const_member_function_pointer::value; + +//================================================================================================= +/** + * @brief Detect if T is a lua cfunction pointer. + * + * @tparam T Potentially lua cfunction pointer. + */ +template +struct is_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template <> +struct is_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_cfunction_pointer_v = is_cfunction_pointer::value; + +//================================================================================================= +/** + * @brief Detect if T is a member lua cfunction pointer. + * + * @tparam T Potentially member lua cfunction pointer. + */ +template +struct is_member_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_member_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +struct is_member_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_member_cfunction_pointer_v = is_member_cfunction_pointer::value; + +/** + * @brief Detect if T is a const member lua cfunction pointer. + * + * @tparam T Potentially const member lua cfunction pointer. + */ +template +struct is_const_member_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_cfunction_pointer +{ + static constexpr bool value = false; +}; + +template +struct is_const_member_cfunction_pointer +{ + static constexpr bool value = true; +}; + +template +inline static constexpr bool is_const_member_cfunction_pointer_v = is_const_member_cfunction_pointer::value; + +//================================================================================================= +/** + * @brief Detect if T is a member or non member lua cfunction pointer. + * + * @tparam T Potentially member or non member lua cfunction pointer. + */ +template +inline static constexpr bool is_any_cfunction_pointer_v = is_cfunction_pointer_v || is_member_cfunction_pointer_v; + +//================================================================================================= +/** + * @brief A constexpr check for proxy_member functions. + * + * @tparam T Type where the callable should be able to operate. + * @tparam F Callable object. + */ +template +inline static constexpr bool is_proxy_member_function_v = + !std::is_member_function_pointer_v && + std::is_same_v>>>; + +template +inline static constexpr bool is_const_proxy_function_v = + is_proxy_member_function_v && + std::is_const_v>>>; + +//================================================================================================= +/** + * @brief A constexpr check for reversed proxy member functions (binary operators). + * + * A reversed proxy function is a binary callable where T is the second argument instead of the + * first, as needed by binary operator metamethods invoked with swapped operands (eg. `3 * vec`). + * + * @tparam T Type where the callable should be able to operate. + * @tparam F Callable object. + */ +template +inline static constexpr bool is_reversed_proxy_function_v = + !std::is_member_function_pointer_v && + !is_proxy_member_function_v && + function_arity_v == 2 && + std::is_same_v>>>; + +template +inline static constexpr bool is_const_reversed_proxy_function_v = + is_reversed_proxy_function_v && + std::is_const_v>>>; + +//================================================================================================= +/** + * @brief An integral constant expression that gives the number of arguments excluding one type (usually used with lua_State*) accepted by the callable object. + * + * @tparam F Callable object. + */ +template +struct function_arity_excluding +{ +}; + +template < class... Ts, class ExclusionType> +struct function_arity_excluding, ExclusionType> + : std::integral_constant, ExclusionType> ? 0 : 1))> +{ +}; + +template +inline static constexpr std::size_t function_arity_excluding_v = function_arity_excluding, ExclusionType>::value; + +/** + * @brief An integral constant expression that gives the number of arguments excluding one type (usually used with lua_State*) accepted by the callable object. + * + * @tparam F Callable object. + */ +template +struct member_function_arity_excluding +{ +}; + +template +struct member_function_arity_excluding, ExclusionType, std::enable_if_t>> + : std::integral_constant, ExclusionType> ? 0 : 1))> +{ +}; + +template +struct member_function_arity_excluding, ExclusionType, std::enable_if_t>> + : std::integral_constant, ExclusionType> ? 0 : 1)) - 1> +{ +}; + +template +inline static constexpr std::size_t member_function_arity_excluding_v = member_function_arity_excluding, ExclusionType>::value; + +//================================================================================================= +/** + * @brief Detectors for const and non const functions in packs and counting them. + */ +template +static constexpr bool is_const_function = + detail::is_const_member_function_pointer_v || + (detail::function_arity_v > 0 && detail::is_const_proxy_function_v) || + detail::is_const_reversed_proxy_function_v; + +template +inline static constexpr std::size_t const_functions_count = (0 + ... + (is_const_function ? 1 : 0)); + +template +inline static constexpr std::size_t non_const_functions_count = (0 + ... + (is_const_function ? 0 : 1)); + +//================================================================================================= +/** + * @brief Simple make_tuple alternative that doesn't decay the types. + * + * @tparam Types Argument types that will compose the tuple. + */ +template +constexpr auto tupleize(Types&&... types) +{ + return std::tuple(std::forward(types)...); +} + +//================================================================================================= +/** + * @brief Remove first type from tuple. + */ +template +struct remove_first_type +{ +}; + +template +struct remove_first_type> +{ + using type = std::tuple; +}; + +template +using remove_first_type_t = typename remove_first_type::type; + +//================================================================================================= +/** + * @brief Drop the first N types from a tuple. + */ +template +struct tuple_drop_first +{ + using type = typename tuple_drop_first>::type; +}; + +template +struct tuple_drop_first<0, Tuple> +{ + using type = Tuple; +}; + +template +using tuple_drop_first_t = typename tuple_drop_first::type; + +//================================================================================================= +/** + * @brief Prepend a type to a tuple. + */ +template +struct tuple_prepend; + +template +struct tuple_prepend> +{ + using type = std::tuple; +}; + +template +using tuple_prepend_t = typename tuple_prepend::type; + +//================================================================================================= +/** + * @brief Take only the first N types from a tuple (uses an accumulator to avoid ambiguity). + */ +template > +struct tuple_take_first_impl +{ + using type = Accum; +}; + +template +struct tuple_take_first_impl, std::tuple> +{ + using type = typename tuple_take_first_impl, std::tuple>::type; +}; + +template +struct tuple_take_first_impl<0, std::tuple, std::tuple> +{ + using type = std::tuple; +}; + +template +using tuple_take_first_t = typename tuple_take_first_impl::type; + +//================================================================================================= +/** + * @brief Extracts the class type from a member function pointer. + */ +template +struct member_function_class; + +template +struct member_function_class { using type = C; }; + +template +struct member_function_class { using type = const C; }; + +template +struct member_function_class { using type = C; }; + +template +struct member_function_class { using type = const C; }; + +template +using member_function_class_t = typename member_function_class::type; + +//================================================================================================= +/** + * @brief Computes the leading argument tuple for bind_back: for member function pointers, + * prepends ClassType* to the explicit remaining args; for all other callables, returns + * the explicit remaining args unchanged. + */ +template +struct bind_back_leading_impl +{ + using type = ExplicitRemaining; +}; + +template +struct bind_back_leading_impl +{ + using type = tuple_prepend_t*, ExplicitRemaining>; +}; + +template +using bind_back_leading_t = + typename bind_back_leading_impl>::type; + +//================================================================================================= +/** + * @brief Internal storage for luabridge::bind_front — exposes a non-template operator() so that + * function_traits can statically resolve result_type and argument_types. + */ +template +struct bind_front_wrapper; + +template +struct bind_front_wrapper, Fn, BoundArgs...> +{ + Fn fn_; + std::tuple bound_; + + template + bind_front_wrapper(F&& f, BA&&... ba) + : fn_(std::forward(f)), bound_(std::forward(ba)...) + { + } + + R operator()(Remaining... args) const + { + return std::apply([&](const auto&... ba) { return std::invoke(fn_, ba..., args...); }, bound_); + } +}; + +} // namespace detail + +//================================================================================================= +/** + * @brief Drop-in replacement for std::bind_front with statically introspectable argument types. + * + * std::bind_front returns an object whose operator() is a template, so its argument and result + * types cannot be resolved at compile time without an explicit std::function cast. This + * wrapper stores the callable and its leading bound arguments, then exposes a concrete + * non-template operator() whose parameter types are derived directly from the underlying + * callable's signature. + * + * For member function pointers the implicit object argument consumed by std::invoke is not + * counted as part of the remaining (Lua-visible) parameter list, matching std::bind_front + * semantics. + * + * @tparam F Callable type (function pointer, member function pointer, functor). + * @tparam BoundArgs Leading argument types to bind. + * @param f The callable to wrap. + * @param args Leading arguments forwarded into the wrapper by value. + * @return A callable object whose operator() accepts the remaining (unbound) arguments. + */ +template +auto bind_front(F&& f, BoundArgs&&... args) +{ + using Fn = std::decay_t; + using FnTraits = detail::function_traits; + + static constexpr std::size_t skip = std::is_member_function_pointer_v ? 1u : 0u; + static constexpr std::size_t num_effective_bound = sizeof...(BoundArgs) - skip; + + using remaining = detail::tuple_drop_first_t; + using R = typename FnTraits::result_type; + + return detail::bind_front_wrapper...>( + std::forward(f), std::forward(args)...); +} + +//================================================================================================= +namespace detail { + +/** + * @brief Internal storage for luabridge::bind_back — exposes a non-template operator() so that + * function_traits can statically resolve result_type and argument_types. + * + * LeadingArgsTuple is the tuple of arguments that the caller must provide; BoundArgs are + * the trailing arguments captured at bind time. For member function pointers, the class + * pointer is included as the first element of LeadingArgsTuple. + */ +template +struct bind_back_wrapper; + +template +struct bind_back_wrapper, Fn, BoundArgs...> +{ + Fn fn_; + std::tuple bound_; + + template + bind_back_wrapper(F&& f, BA&&... ba) + : fn_(std::forward(f)), bound_(std::forward(ba)...) + { + } + + R operator()(Leading... args) const + { + return std::apply([&](const auto&... ba) { return std::invoke(fn_, args..., ba...); }, bound_); + } +}; + +} // namespace detail + +//================================================================================================= +/** + * @brief Drop-in replacement for std::bind_back with statically introspectable argument types. + * + * Stores the callable and its trailing bound arguments, then exposes a concrete non-template + * operator() whose parameter types are derived directly from the underlying callable's signature. + * This lets LuaBridge register the result with addFunction / addStaticFunction without any extra + * annotation. + * + * For member function pointers the class pointer is automatically prepended to the remaining + * (Lua-visible) parameter list so that LuaBridge can dispatch it as a proxy member function. + * + * @tparam F Callable type (function pointer, member function pointer, functor). + * @tparam BoundArgs Trailing argument types to bind. + * @param f The callable to wrap. + * @param args Trailing arguments forwarded into the wrapper by value. + * @return A callable object whose operator() accepts the remaining (leading) arguments. + */ +template +auto bind_back(F&& f, BoundArgs&&... args) +{ + using Fn = std::decay_t; + using FnTraits = detail::function_traits; + + static constexpr std::size_t num_explicit = FnTraits::arity; + static constexpr std::size_t num_bound = sizeof...(BoundArgs); + static constexpr std::size_t num_remaining = num_explicit - num_bound; + + using explicit_remaining = detail::tuple_take_first_t; + using leading = detail::bind_back_leading_t; + using R = typename FnTraits::result_type; + + return detail::bind_back_wrapper...>( + std::forward(f), std::forward(args)...); +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Globals.h b/external/include/LuaBridge3/LuaBridge/detail/Globals.h new file mode 100644 index 0000000000..d1fc0db6a1 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Globals.h @@ -0,0 +1,77 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2023, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "Stack.h" + +#include +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Get a global value from the lua_State. + * + * @note This works on any type specialized by `Stack`, including `LuaRef` and its table proxies. +*/ +template +TypeResult getGlobal(lua_State* L, const char* name) +{ + lua_getglobal(L, name); + + auto result = luabridge::Stack::get(L, -1); + + lua_pop(L, 1); + + return result; +} + +//================================================================================================= +/** + * @brief Try to get a field from a global table without creating a LuaRef. + * + * This is a fast-path helper for optional lookup patterns. It invokes normal Lua field access + * on the table, including metamethods, and returns std::nullopt when the global is not a table + * or the field cannot be converted to the requested type. + */ +template +std::optional tryGetGlobalField(lua_State* L, const char* globalName, const char* fieldName) +{ + const StackRestore stackRestore(L); + + lua_getglobal(L, globalName); + if (! lua_istable(L, -1)) + return std::nullopt; + + lua_getfield(L, -1, fieldName); + + auto result = Stack>::get(L, -1); + if (! result) + return std::nullopt; + + return *result; +} + +//================================================================================================= +/** + * @brief Set a global value in the lua_State. + * + * @note This works on any type specialized by `Stack`, including `LuaRef` and its table proxies. + */ +template +bool setGlobal(lua_State* L, T&& t, const char* name) +{ + if (auto result = push(L, std::forward(t))) + { + lua_setglobal(L, name); + return true; + } + + return false; +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Invoke.h b/external/include/LuaBridge3/LuaBridge/detail/Invoke.h new file mode 100644 index 0000000000..8638f343fe --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Invoke.h @@ -0,0 +1,263 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2021, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "Errors.h" +#include "Stack.h" +#include "LuaRef.h" +#include "LuaException.h" + +#include +#include +#include +#include + +namespace luabridge { + +//================================================================================================= +namespace detail { + +template +bool is_handler_valid(const F& f) noexcept +{ + if constexpr (std::is_pointer_v>) + return f != nullptr; + else if constexpr (std::is_constructible_v>) + return static_cast(f); + else + return true; +} + +template +TypeResult decode_tuple_result(lua_State* L, int first_result_index, std::index_sequence) +{ + auto results = std::make_tuple( + Stack>::get(L, first_result_index + static_cast(Indices))...); + + std::error_code ec; + + const bool ok = + (([&]() + { + const auto& element = std::get(results); + if (! element) + { + ec = element.error(); + return false; + } + return true; + }()) + && ...); + + if (! ok) + return ec; + + return Tuple{ std::move(*std::get(results))... }; +} + +template +TypeResult decode_call_result(lua_State* L, int first_result_index, int num_returned_values) +{ + if constexpr (std::is_same_v || std::is_same_v>) + { + if (num_returned_values != 0) + return makeErrorCode(ErrorCode::InvalidTableSizeInCast); + + return {}; + } + else if constexpr (is_tuple_v) + { + constexpr auto expected_size = static_cast(std::tuple_size_v); + if (num_returned_values != expected_size) + return makeErrorCode(ErrorCode::InvalidTableSizeInCast); + + return decode_tuple_result(L, first_result_index, std::make_index_sequence{}); + } + else + { + if (num_returned_values < 1) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return Stack::get(L, first_result_index); + } +} + +} // namespace detail + +//================================================================================================= +/** + * @brief Safely call Lua code and decode the return values to R. + */ +template +TypeResult callWithHandler(const Ref& object, F&& errorHandler, Args&&... args) +{ + static_assert(std::is_same_v, detail::remove_cvref_t> || std::is_invocable_r_v); + + static constexpr bool isValidHandler = + !std::is_same_v, detail::remove_cvref_t>; + + lua_State* L = object.state(); + const StackRestore stackRestore(L); + const int initialTop = lua_gettop(L); + + bool hasHandler = false; + if constexpr (isValidHandler) + { + hasHandler = detail::is_handler_valid(errorHandler); + if (hasHandler) + detail::push_function(L, std::forward(errorHandler), ""); + } + + object.push(); + + { + const auto [result, index] = detail::push_arguments(L, std::forward_as_tuple(args...)); + if (! result) + return result.error(); + } + + const int messageHandlerIndex = hasHandler ? (initialTop + 1) : 0; + const int code = lua_pcall(L, sizeof...(Args), LUA_MULTRET, messageHandlerIndex); + if (code != LUABRIDGE_LUA_OK) + { + auto ec = makeErrorCode(ErrorCode::LuaFunctionCallFailed); + +#if LUABRIDGE_HAS_EXCEPTIONS + if constexpr (! isValidHandler) + { + if (LuaException::areExceptionsEnabled(L)) + LuaException::raise(L, ec); + } +#endif + + lua_pop(L, 1); + return ec; + } + + if (hasHandler) + lua_remove(L, initialTop + 1); + + const int firstResultIndex = initialTop + 1; + const int numReturnedValues = lua_gettop(L) - initialTop; + return detail::decode_call_result(L, firstResultIndex, numReturnedValues); +} + +template +TypeResult callWithHandler(const Ref& object, F&& errorHandler, Args&&... args) +{ + return callWithHandler(object, std::forward(errorHandler), std::forward(args)...); +} + +template +TypeResult call(const Ref& object, Args&&... args) +{ + return callWithHandler(object, std::ignore, std::forward(args)...); +} + +template +class LuaFunction; + +template +class LuaFunction +{ +public: + LuaFunction() = default; + + explicit LuaFunction(const LuaRef& function) + : m_function(function) + { + } + + explicit LuaFunction(LuaRef&& function) + : m_function(std::move(function)) + { + } + + [[nodiscard]] TypeResult operator()(Args... args) const + { + return call(std::forward(args)...); + } + + [[nodiscard]] TypeResult call(Args... args) const + { + return luabridge::call(m_function, std::forward(args)...); + } + + template + [[nodiscard]] TypeResult callWithHandler(F&& errorHandler, Args... args) const + { + return luabridge::callWithHandler(m_function, std::forward(errorHandler), std::forward(args)...); + } + + [[nodiscard]] bool isValid() const + { + return m_function.isCallable(); + } + + [[nodiscard]] const LuaRef& ref() const + { + return m_function; + } + +private: + LuaRef m_function; +}; + +//============================================================================================= +/** + * @brief Wrapper for `lua_pcall` that throws if exceptions are enabled. + */ +inline int pcall(lua_State* L, int nargs = 0, int nresults = 0, int msgh = 0) +{ + const int code = lua_pcall(L, nargs, nresults, msgh); + +#if LUABRIDGE_HAS_EXCEPTIONS + if (code != LUABRIDGE_LUA_OK && LuaException::areExceptionsEnabled(L)) + LuaException::raise(L, makeErrorCode(ErrorCode::LuaFunctionCallFailed)); +#endif + + return code; +} + +//============================================================================================= +template +template +TypeResult LuaRefBase::call(Args&&... args) const +{ + return luabridge::call(impl(), std::forward(args)...); +} + +template +template +TypeResult LuaRefBase::operator()(Args&&... args) const +{ + return call(std::forward(args)...); +} + +template +template +TypeResult LuaRefBase::callWithHandler(F&& errorHandler, Args&&... args) const +{ + return luabridge::callWithHandler(impl(), std::forward(errorHandler), std::forward(args)...); +} + +template +template +TypeResult LuaRefBase::callWithHandler(F&& errorHandler, Args&&... args) const +{ + return callWithHandler(std::forward(errorHandler), std::forward(args)...); +} + +template +template +LuaFunction LuaRefBase::callable() const +{ + const StackRestore stackRestore(m_L); + impl().push(m_L); + return LuaFunction(LuaRef::fromStack(m_L)); +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Iterator.h b/external/include/LuaBridge3/LuaBridge/detail/Iterator.h new file mode 100644 index 0000000000..698510929f --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Iterator.h @@ -0,0 +1,256 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2018, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// SPDX-License-Identifier: MIT + +#pragma once + +#include "LuaRef.h" + +#include + +#if LUABRIDGE_HAS_CXX20_RANGES +#include +#include +#endif + +namespace luabridge { + +//================================================================================================= +/** + * @brief Iterator class to allow table iteration. + * + * @see Range class. + */ +class Iterator +{ +public: + explicit Iterator(const LuaRef& table, bool isEnd = false) + : m_L(table.state()) + , m_table(table) + , m_key(table.state()) // m_key is nil + , m_value(table.state()) // m_value is nil + { + if (! isEnd) + { + next(); // get the first (key, value) pair from table + } + } + +#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. + * + * @return A Lua state. + */ + lua_State* state() const noexcept + { + return m_L; + } + + /** + * @brief Dereference the iterator. + * + * @return A key-value pair for a current table entry. + */ + std::pair operator*() const + { + return std::make_pair(m_key, m_value); + } + + /** + * @brief Return the value referred by the iterator. + * + * @return A value for the current table entry. + */ + LuaRef operator->() const + { + return m_value; + } + + /** + * @brief Compare two iterators. + * + * @param rhs Another iterator. + * + * @return True if iterators point to the same entry of the same table, false otherwise. + */ + bool operator!=(const Iterator& rhs) const + { + LUABRIDGE_ASSERT(m_L == rhs.m_L); + + return ! m_table.rawequal(rhs.m_table) || ! m_key.rawequal(rhs.m_key); + } + + /** + * @brief Move the iterator to the next table entry. + * + * @return This iterator. + */ + Iterator& operator++() + { + if (isNil()) + { + // if the iterator reaches the end, do nothing + return *this; + } + else + { + next(); + return *this; + } + } + + /** + * @brief Check if the iterator points after the last table entry. + * + * @return True if there are no more table entries to iterate, false otherwise. + */ + bool isNil() const noexcept + { + return m_key.isNil(); + } + + /** + * @brief Return the key for the current table entry. + * + * @return A reference to the entry key. + */ + LuaRef key() const + { + return m_key; + } + + /** + * @brief Return the key for the current table entry. + * + * @return A reference to the entry value. + */ + LuaRef value() const + { + return m_value; + } + +private: + // Don't use postfix increment, it is less efficient + Iterator operator++(int); + + void next() + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 2)) + { + m_key = LuaNil(); + m_value = LuaNil(); + return; + } +#endif + + m_table.push(); + m_key.push(); + + if (lua_next(m_L, -2)) + { + m_value.pop(); + m_key.pop(); + } + else + { + m_key = LuaNil(); + m_value = LuaNil(); + } + + lua_pop(m_L, 1); + } + + lua_State* m_L = nullptr; + LuaRef m_table; + LuaRef m_key; + LuaRef m_value; +}; + +//================================================================================================= +/** + * @brief Range class taking two table iterators. + */ +class Range +{ +public: + Range(const Iterator& begin, const Iterator& end) + : m_begin(begin) + , m_end(end) + { + } + + const Iterator& begin() const noexcept + { + return m_begin; + } + + const Iterator& end() const noexcept + { + return m_end; + } + +private: + Iterator m_begin; + Iterator m_end; +}; + +//================================================================================================= +/** + * @brief Return a range for the Lua table reference. + * + * @return A range suitable for range-based for statement. + */ +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/external/include/LuaBridge3/LuaBridge/detail/LuaException.h b/external/include/LuaBridge3/LuaBridge/detail/LuaException.h new file mode 100644 index 0000000000..f70d5c3484 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/LuaException.h @@ -0,0 +1,188 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2021, kunitoki +// Copyright 2012, Vinnie Falco +// Copyright 2008, Nigel Atkinson +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" + +#include "ClassInfo.h" +#include "LuaHelpers.h" + +#include +#include +#include + +namespace luabridge { + +//================================================================================================ +class LuaException : public std::exception +{ +public: + //============================================================================================= + /** + * @brief Construct a LuaException from a LuaBridge error code. + */ + LuaException(lua_State* L, std::error_code code) + : m_L(L) + , m_code(code) + , m_what(code.message()) + { + } + + ~LuaException() noexcept override + { + } + + //============================================================================================= + /** + * @brief Return the error message. + */ + const char* what() const noexcept override + { + return m_what.c_str(); + } + + //============================================================================================= + /** + * @brief Throw an exception or raises a luaerror when exceptions are disabled. + */ + [[noreturn]] static void raise(lua_State* L, std::error_code code) + { + LUABRIDGE_ASSERT(areExceptionsEnabled(L)); + +#if LUABRIDGE_HAS_EXCEPTIONS + throw LuaException(L, code, FromLua{}); +#else + unused(L, code); + + std::abort(); +#endif + } + + //============================================================================================= + /** + * @brief Check if exceptions are enabled. + */ + static bool areExceptionsEnabled(lua_State* L) noexcept + { + lua_pushlightuserdata(L, detail::getExceptionsKey()); + lua_gettable(L, LUA_REGISTRYINDEX); + + const bool enabled = lua_isboolean(L, -1) ? static_cast(lua_toboolean(L, -1)) : false; + lua_pop(L, 1); + + return enabled; + } + + /** + * @brief Initializes error handling. + * + * Subsequent Lua errors are translated to C++ exceptions, or logging and abort if exceptions are disabled. + */ + static void enableExceptions(lua_State* L) noexcept + { + lua_pushlightuserdata(L, detail::getExceptionsKey()); + lua_pushboolean(L, true); + lua_settable(L, LUA_REGISTRYINDEX); + +#if LUABRIDGE_HAS_EXCEPTIONS && LUABRIDGE_ON_LUAJIT + lua_pushlightuserdata(L, (void*)luajitWrapperCallback); + luaJIT_setmode(L, -1, LUAJIT_MODE_WRAPCFUNC | LUAJIT_MODE_ON); + lua_pop(L, 1); +#endif + +#if LUABRIDGE_ON_LUAU + auto callbacks = lua_callbacks(L); + callbacks->panic = +[](lua_State* L, int) { panicHandlerCallback(L); }; +#else + lua_atpanic(L, panicHandlerCallback); +#endif + } + + //============================================================================================= + /** + * @brief Retrieve the lua_State associated with the exception. + * + * @return A Lua state. + */ + lua_State* state() const { return m_L; } + +private: + struct FromLua {}; + + LuaException(lua_State* L, std::error_code code, FromLua) + : m_L(L) + , m_code(code) + { + whatFromStack(); + } + + void whatFromStack() + { + std::stringstream ss; + + const char* errorText = nullptr; + + if (lua_gettop(m_L) > 0) + { + errorText = lua_tostring(m_L, -1); + lua_pop(m_L, 1); + } + + ss << (errorText ? errorText : "Unknown error") << " (code=" << m_code.message() << ")"; + + m_what = std::move(ss).str(); + } + + static int panicHandlerCallback(lua_State* L) + { +#if LUABRIDGE_HAS_EXCEPTIONS + throw LuaException(L, makeErrorCode(ErrorCode::LuaFunctionCallFailed), FromLua{}); +#else + unused(L); + + std::abort(); +#endif + } + +#if LUABRIDGE_HAS_EXCEPTIONS && LUABRIDGE_ON_LUAJIT + static int luajitWrapperCallback(lua_State* L, lua_CFunction f) + { + try + { + return f(L); + } + catch (const std::exception& e) + { + lua_pushstring(L, e.what()); + lua_error_x(L); + } + } +#endif + + lua_State* m_L = nullptr; + std::error_code m_code; + std::string m_what; +}; + +//================================================================================================= +/** + * @brief Initializes error handling using C++ exceptions. + * + * Subsequent Lua errors are translated to C++ exceptions. It aborts the application if called when no exceptions. + */ +inline void enableExceptions(lua_State* L) noexcept +{ +#if LUABRIDGE_HAS_EXCEPTIONS + LuaException::enableExceptions(L); +#else + unused(L); + + LUABRIDGE_ASSERT(false); // Never call this function when exceptions are not enabled. +#endif +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/LuaHelpers.h b/external/include/LuaBridge3/LuaBridge/detail/LuaHelpers.h new file mode 100644 index 0000000000..98d6f7a442 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/LuaHelpers.h @@ -0,0 +1,793 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2012, Vinnie Falco +// Copyright 2007, Nathan Reed +// SPDX-License-Identifier: MIT + +#pragma once + +#include "FuncTraits.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace luabridge { + +/** + * @brief Helper for unused vars. + */ +template +constexpr void unused(Args&&...) +{ +} + +// These are for Lua versions prior to 5.2.0. +#if LUA_VERSION_NUM < 502 +using lua_Unsigned = std::make_unsigned_t; + +#if ! LUABRIDGE_ON_LUAU +inline int lua_absindex(lua_State* L, int idx) +{ + if (idx > LUA_REGISTRYINDEX && idx < 0) + return lua_gettop(L) + idx + 1; + else + return idx; +} +#endif + +#define LUA_OPEQ 1 +#define LUA_OPLT 2 +#define LUA_OPLE 3 + +inline int lua_compare(lua_State* L, int idx1, int idx2, int op) +{ + switch (op) + { + case LUA_OPEQ: + return lua_equal(L, idx1, idx2); + + case LUA_OPLT: + return lua_lessthan(L, idx1, idx2); + + case LUA_OPLE: + return lua_equal(L, idx1, idx2) || lua_lessthan(L, idx1, idx2); + + default: + return 0; + } +} + +#if ! LUABRIDGE_ON_LUAJIT +inline void* luaL_testudata(lua_State* L, int ud, const char* tname) +{ + void* p = lua_touserdata(L, ud); + if (p == nullptr) + return nullptr; + + if (! lua_getmetatable(L, ud)) + return nullptr; + + luaL_getmetatable(L, tname); + if (! lua_rawequal(L, -1, -2)) + p = nullptr; + + lua_pop(L, 2); + return p; +} +#endif + +inline int get_length(lua_State* L, int idx) +{ + return static_cast(lua_objlen(L, idx)); +} +#else // LUA_VERSION_NUM >= 502 +inline int get_length(lua_State* L, int idx) +{ + return static_cast(lua_rawlen(L, idx)); +} +#endif // LUA_VERSION_NUM < 502 + +// These functions and defines are for Luau. +#if LUABRIDGE_ON_LUAU +inline int luaL_ref(lua_State* L, int idx) +{ + LUABRIDGE_ASSERT(idx == LUA_REGISTRYINDEX); + + const int ref = lua_ref(L, -1); + + lua_pop(L, 1); + + return ref; +} + +inline void luaL_unref(lua_State* L, int idx, int ref) +{ + unused(idx); + + lua_unref(L, ref); +} + +/** + * @brief Trampolines adapting a `void(void*)` destructor to the signature expected by `lua_newuserdatadtor`. + * + * Older Luau versions take `void (*)(void*)`, newer ones take `lua_Destructor` (`void (*)(lua_State*, void*)`). + * Overload resolution against the parameter type of `lua_newuserdatadtor` picks the right one. + */ +template +inline void luau_userdata_dtor(void* x) +{ + Dtor(x); +} + +template +inline void luau_userdata_dtor(lua_State*, void* x) +{ + Dtor(x); +} + +template +inline void* lua_newuserdatadtor_x(lua_State* L, size_t sz) +{ + return lua_newuserdatadtor(L, sz, &luau_userdata_dtor); +} + +template +inline void luau_destroy_userdata(void* x) +{ + T* object = static_cast(x); + object->~T(); +} + +template +inline void* lua_newuserdata_x(lua_State* L, size_t sz) +{ + return lua_newuserdatadtor_x<&luau_destroy_userdata>(L, sz); +} + +inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn, const char* debugname) +{ + lua_pushcfunction(L, fn, debugname); +} + +inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debugname, int n) +{ + lua_pushcclosure(L, fn, debugname, n); +} + +[[noreturn]] inline void lua_error_x(lua_State* L) +{ + lua_error(L); + + detail::unreachable(); +} + +inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) +{ + return lua_getinfo(L, level, "nlS", ar); +} + +inline int lua_getstack_info_x(lua_State* L, int level, const char* what, lua_Debug* ar) +{ + return lua_getinfo(L, level, what, ar); +} + +inline int lua_rawgetp_x(lua_State* L, int idx, void* p) +{ + return lua_rawgetp(L, idx, p); +} + +inline void lua_rawsetp_x(lua_State* L, int idx, void* p) +{ + lua_rawsetp(L, idx, p); +} + +#else +using ::luaL_ref; +using ::luaL_unref; + +template +inline void* lua_newuserdata_x(lua_State* L, size_t sz) +{ + return lua_newuserdata(L, sz); +} + +inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn, const char* debugname) +{ + unused(debugname); + + lua_pushcfunction(L, fn); +} + +inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, const char* debugname, int n) +{ + unused(debugname); + + lua_pushcclosure(L, fn, n); +} + +[[noreturn]] inline void lua_error_x(lua_State* L) +{ + lua_error(L); + + detail::unreachable(); +} + +inline int lua_getstack_x(lua_State* L, int level, lua_Debug* ar) +{ + return lua_getstack(L, level, ar); +} + +inline int lua_getstack_info_x(lua_State* L, int level, const char* what, lua_Debug* ar) +{ + lua_getstack(L, level, ar); + return lua_getinfo(L, what, ar); +} + +inline int lua_rawgetp_x(lua_State* L, int idx, void* p) +{ +#if LUA_VERSION_NUM < 503 + idx = lua_absindex(L, idx); + luaL_checkstack(L, 1, "not enough stack slots"); + lua_pushlightuserdata(L, p); + lua_rawget(L, idx); + return lua_type(L, -1); +#else + return lua_rawgetp(L, idx, p); +#endif +} + +inline void lua_rawsetp_x(lua_State* L, int idx, void* p) +{ +#if LUA_VERSION_NUM < 503 + idx = lua_absindex(L, idx); + luaL_checkstack(L, 1, "not enough stack slots"); + lua_pushlightuserdata(L, p); + lua_insert(L, -2); + lua_rawset(L, idx); +#else + lua_rawsetp(L, idx, p); +#endif +} + +#endif // LUABRIDGE_ON_LUAU + +// These are for Lua versions prior to 5.5.0. +#if LUA_VERSION_NUM < 505 +inline lua_State* lua_newstate_x(lua_Alloc f, void* ud, [[maybe_unused]] unsigned seed) +{ + return lua_newstate(f, ud); +} +#else +inline lua_State* lua_newstate_x(lua_Alloc f, void* ud, unsigned seed) +{ + return lua_newstate(f, ud, seed); +} +#endif + +// These are for Lua versions prior to 5.3.0. +#if LUA_VERSION_NUM < 503 +inline lua_Number to_numberx(lua_State* L, int idx, int* isnum) +{ + lua_Number n = lua_tonumber(L, idx); + + if (isnum) + *isnum = (n != 0 || lua_isnumber(L, idx)); + + return n; +} + +inline lua_Integer to_integerx(lua_State* L, int idx, int* isnum) +{ + int ok = 0; + lua_Number n = to_numberx(L, idx, &ok); + + if (ok) + { + if (n < static_cast(std::numeric_limits::min()) || + n >= -static_cast(std::numeric_limits::min())) + { + if (isnum) + *isnum = 0; + return 0; + } + + const auto int_n = static_cast(n); + if (n == static_cast(int_n)) + { + if (isnum) + *isnum = 1; + + return int_n; + } + } + + if (isnum) + *isnum = 0; + + return 0; +} +#endif // LUA_VERSION_NUM < 503 + +inline int lua_rawgetp_x(lua_State* L, int idx, const void* p) +{ + return lua_rawgetp_x(L, idx, const_cast(p)); +} + +inline void lua_rawsetp_x(lua_State* L, int idx, const void* p) +{ + lua_rawsetp_x(L, idx, const_cast(p)); +} + +#ifndef LUA_OK +#define LUABRIDGE_LUA_OK 0 +#else +#define LUABRIDGE_LUA_OK LUA_OK +#endif + +/** + * @brief Helper to throw or return an error code. + */ +template +std::error_code throw_or_error_code(ErrorType error) +{ +#if LUABRIDGE_HAS_EXCEPTIONS + throw T(makeErrorCode(error).message().c_str()); +#else + return makeErrorCode(error); +#endif +} + +template +std::error_code throw_or_error_code(lua_State* L, ErrorType error) +{ +#if LUABRIDGE_HAS_EXCEPTIONS + throw T(L, makeErrorCode(error)); +#else + return unused(L), makeErrorCode(error); +#endif +} + +/** + * @brief Helper to throw or LUABRIDGE_ASSERT. + */ +template +void throw_or_assert(Args&&... args) +{ +#if LUABRIDGE_HAS_EXCEPTIONS + throw T(std::forward(args)...); +#else + unused(std::forward(args)...); + LUABRIDGE_ASSERT(false); +#endif +} + +/** + * @brief Helper to set unsigned. + */ +template +void pushunsigned(lua_State* L, T value) +{ + static_assert(std::is_unsigned_v); + + lua_pushinteger(L, static_cast(value)); +} + +/** + * @brief Helper to convert to integer. + */ +inline lua_Number tonumber(lua_State* L, int idx, int* isnum) +{ +#if ! LUABRIDGE_ON_LUAU && LUA_VERSION_NUM > 502 + return lua_tonumberx(L, idx, isnum); +#else + return to_numberx(L, idx, isnum); +#endif +} + +/** + * @brief Helper to convert to integer. + */ +inline lua_Integer tointeger(lua_State* L, int idx, int* isnum) +{ +#if ! LUABRIDGE_ON_LUAU && LUA_VERSION_NUM > 502 + return lua_tointegerx(L, idx, isnum); +#else + return to_integerx(L, idx, isnum); +#endif +} + +/** + * @brief Register main thread, only supported on 5.1. + */ +inline constexpr char main_thread_name[] = "__luabridge_main_thread"; + +inline void register_main_thread(lua_State* threadL) +{ +#if LUA_VERSION_NUM < 502 + if (threadL == nullptr) + lua_pushnil(threadL); + else + lua_pushthread(threadL); + + lua_setglobal(threadL, main_thread_name); +#else + unused(threadL); +#endif +} + +/** + * @brief Get main thread, not supported on 5.1. + */ +inline lua_State* main_thread(lua_State* threadL) +{ +#if LUA_VERSION_NUM < 502 + lua_getglobal(threadL, main_thread_name); + if (lua_isthread(threadL, -1)) + { + auto L = lua_tothread(threadL, -1); + lua_pop(threadL, 1); + return L; + } + LUABRIDGE_ASSERT(false); // Have you forgot to call luabridge::registerMainThread ? + lua_pop(threadL, 1); + return threadL; +#else + lua_rawgeti(threadL, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD); + lua_State* L = lua_tothread(threadL, -1); + lua_pop(threadL, 1); + return L; +#endif +} + +/** + * @brief Get a table value, bypassing metamethods. + */ +inline int rawgetfield(lua_State* L, int index, const char* key) +{ + LUABRIDGE_ASSERT(lua_istable(L, index)); + index = lua_absindex(L, index); + lua_pushstring(L, key); +#if LUA_VERSION_NUM <= 502 + lua_rawget(L, index); + return lua_type(L, -1); +#else + return lua_rawget(L, index); +#endif +} + +/** + * @brief Set a table value, bypassing metamethods. + */ +inline void rawsetfield(lua_State* L, int index, const char* key) +{ + LUABRIDGE_ASSERT(lua_istable(L, index)); + index = lua_absindex(L, index); + lua_pushstring(L, key); + lua_insert(L, -2); + lua_rawset(L, index); +} + +/** + * @brief Returns true if the value is a full userdata (not light). + */ +[[nodiscard]] inline bool isfulluserdata(lua_State* L, int index) +{ + return lua_isuserdata(L, index) && !lua_islightuserdata(L, index); +} + +/** + * @brief Test lua_State objects for global equality. + * + * This can determine if two different lua_State objects really point + * to the same global state, such as when using coroutines. + * + * @note This is used for assertions. + */ +[[nodiscard]] inline bool equalstates(lua_State* L1, lua_State* L2) +{ + return lua_topointer(L1, LUA_REGISTRYINDEX) == lua_topointer(L2, LUA_REGISTRYINDEX); +} + +/** + * @brief Return the size of lua table, even if not a sequence { 1=x, 2=y, 3=... }. + */ +[[nodiscard]] inline int table_length(lua_State* L, int index) +{ + LUABRIDGE_ASSERT(lua_istable(L, index)); + + int items_count = 0; + + lua_pushnil(L); + while (lua_next(L, index) != 0) + { + ++items_count; + + lua_pop(L, 1); + } + + return items_count; +} + +/** + * @brief Return an aligned pointer of type T. + */ +template +[[nodiscard]] T* align(void* ptr) noexcept +{ + const auto address = reinterpret_cast(ptr); + + const auto offset = address % alignof(T); + const auto aligned_address = (offset == 0) ? address : (address + alignof(T) - offset); + + return reinterpret_cast(aligned_address); +} + +/** + * @brief Return if a pointer of type T is aligned. + */ +template , int> = 0> +[[nodiscard]] bool is_aligned(T address) noexcept +{ + static_assert(Alignment > 0u); + + return (reinterpret_cast(address) & (Alignment - 1u)) == 0u; +} + +/** + * @brief Return the space needed to align the type T on an unaligned address. + */ +template +[[nodiscard]] constexpr size_t maximum_space_needed_to_align() noexcept +{ + return sizeof(T) + alignof(T) - 1; +} + +/** + * @brief Deallocate lua userdata taking into account alignment. + */ +template +int lua_deleteuserdata_aligned(lua_State* L) +{ + LUABRIDGE_ASSERT(isfulluserdata(L, 1)); + + T* aligned = align(lua_touserdata(L, 1)); + aligned->~T(); + + return 0; +} + +#if LUABRIDGE_ON_LUAU +/** + * @brief Destroy luau userdata taking into account alignment. + */ +template +void luau_destroy_userdata_aligned(void* x) +{ + T* aligned = align(x); + aligned->~T(); +} +#endif + +/** + * @brief Allocate lua userdata taking into account alignment. + * + * Using this instead of lua_newuserdata directly prevents alignment warnings on 64bits platforms. + */ +template +void* lua_newuserdata_aligned(lua_State* L, Args&&... args) +{ + using U = std::remove_reference_t; + +#if LUABRIDGE_ON_LUAU + void* pointer = lua_newuserdatadtor_x<&luau_destroy_userdata_aligned>(L, maximum_space_needed_to_align()); +#else + void* pointer = lua_newuserdata_x(L, maximum_space_needed_to_align()); + + lua_newtable(L); + lua_pushcfunction_x(L, &lua_deleteuserdata_aligned, ""); + rawsetfield(L, -2, "__gc"); + lua_setmetatable(L, -2); +#endif + + U* aligned = align(pointer); + + new (aligned) U(std::forward(args)...); + + return pointer; +} + +/** + * @brief Safe error able to walk backwards for error reporting correctly. + */ +[[noreturn]] inline void raise_lua_error(lua_State* L, const char* fmt, ...) +{ + va_list argp; + va_start(argp, fmt); + lua_pushvfstring(L, fmt, argp); + va_end(argp); + + const char* message = lua_tostring(L, -1); + if (message != nullptr) + { + if (auto str = std::string_view(message); !str.empty() && str[0] == '[') + lua_error_x(L); + } + + bool pushed_error = false; + for (int level = 1; level <= 2; ++level) + { + lua_Debug ar; + +#if LUABRIDGE_ON_LUAU + if (lua_getinfo(L, level, "sl", &ar) == 0) + continue; +#else + if (lua_getstack(L, level, &ar) == 0 || lua_getinfo(L, "Sl", &ar) == 0) + continue; +#endif + + if (ar.currentline <= 0) + continue; + + lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); + pushed_error = true; + + break; + } + + if (! pushed_error) + lua_pushliteral(L, ""); + + lua_pushvalue(L, -2); + lua_remove(L, -3); + lua_concat(L, 2); + + lua_error_x(L); +} + +/** + * @brief Checks if the value on the stack is a number type and can fit into the corresponding c++ integral type.. + */ +template +constexpr bool is_integral_representable_by(T value) +{ + constexpr bool same_signedness = (std::is_unsigned_v && std::is_unsigned_v) + || (!std::is_unsigned_v && !std::is_unsigned_v); + + if constexpr (sizeof(T) == sizeof(U)) + { + if constexpr (same_signedness) + { + return true; + } + else if constexpr (std::is_unsigned_v) + { + return value <= static_cast((std::numeric_limits::max)()); + } + else + { + return value >= static_cast((std::numeric_limits::min)()) + && static_cast(value) <= (std::numeric_limits::max)(); + } + } + else if constexpr (sizeof(T) < sizeof(U)) + { + return static_cast(value) >= (std::numeric_limits::min)() + && static_cast(value) <= (std::numeric_limits::max)(); + } + else if constexpr (std::is_unsigned_v) + { + return value <= static_cast((std::numeric_limits::max)()); + } + else + { + return value >= static_cast((std::numeric_limits::min)()) + && value <= static_cast((std::numeric_limits::max)()); + } +} + +template +bool is_integral_representable_by(lua_State* L, int index) +{ + int isValid = 0; + + const auto value = tointeger(L, index, &isValid); + + return isValid ? is_integral_representable_by(value) : false; +} + +/** + * @brief Checks if the value on the stack is a number type and can fit into the corresponding c++ numerical type.. + */ +template +bool is_floating_point_representable_by(T value) +{ + if constexpr (sizeof(T) == sizeof(U)) + { + return true; + } + else if constexpr (sizeof(T) < sizeof(U)) + { + if (std::isnan(value) || std::isinf(value)) + return true; + + return static_cast(value) >= -(std::numeric_limits::max)() + && static_cast(value) <= (std::numeric_limits::max)(); + } + else + { + if (std::isnan(value) || std::isinf(value)) + return true; + + return value >= static_cast(-(std::numeric_limits::max)()) + && value <= static_cast((std::numeric_limits::max)()); + } +} + +template +bool is_floating_point_representable_by(lua_State* L, int index) +{ + int isValid = 0; + + const auto value = tonumber(L, index, &isValid); + + return isValid ? is_floating_point_representable_by(value) : false; +} + +/** + * @brief Portable wrapper for lua_resume that normalises calling convention differences + * across Lua 5.1/LuaJIT (no from, no nresults), 5.2-5.3 (from but no nresults), and 5.4+ (from + nresults). + * + * @param L The coroutine thread to resume. + * @param from The thread doing the resuming (may be nullptr on older Lua). + * @param nargs Number of arguments on L's stack to pass to the resumed function. + * @param nresults Output: number of values on L's stack after resume (yielded or returned). + * For Lua 5.4+, filled directly by lua_resume. For older versions, computed via lua_gettop. + * @returns LUA_OK, LUA_YIELD, or an error code. + */ +inline int lua_resume_x(lua_State* L, lua_State* from, int nargs, int* nresults = nullptr) +{ +#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 + unused(from); + int status = lua_resume(L, nargs); + if (nresults) + *nresults = lua_gettop(L); + return status; +#elif LUABRIDGE_ON_LUAU || LUABRIDGE_ON_RAVI || LUA_VERSION_NUM < 504 + int status = lua_resume(L, from, nargs); + if (nresults) + *nresults = lua_gettop(L); + return status; +#else + int nr = 0; + int status = lua_resume(L, from, nargs, &nr); + if (nresults) + *nresults = nr; + return status; +#endif +} + +/** + * @brief Returns true if the currently running C function can yield via lua_yieldk. + * + * Returns false on Lua 5.1, LuaJIT, and Luau where lua_yieldk is unavailable. + */ +inline bool lua_isyieldable_x(lua_State* L) +{ +#if LUABRIDGE_ON_LUAJIT || LUA_VERSION_NUM == 501 || LUABRIDGE_ON_LUAU + unused(L); + return false; +#elif LUA_VERSION_NUM < 503 + unused(L); + return true; // lua_yieldk exists in 5.2; assume yieldable when reached +#else + return lua_isyieldable(L) != 0; +#endif +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/LuaRef.h b/external/include/LuaBridge3/LuaBridge/detail/LuaRef.h new file mode 100644 index 0000000000..2c2f59ce8e --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/LuaRef.h @@ -0,0 +1,2099 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2019, George Tokmaji +// Copyright 2018, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// Copyright 2008, Nigel Atkinson +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "Errors.h" +#include "Expected.h" +#include "Stack.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace luabridge { + +template +class LuaFunction; + +//================================================================================================= +/** + * @brief Type tag for representing LUA_TNIL. + * + * Construct one of these using `LuaNil ()` to represent a Lua nil. This is faster than creating a reference in the registry to nil. + * Example: + * + * @code + * LuaRef t (LuaRef::createTable (L)); + * ... + * t ["k"] = LuaNil (); // assign nil + * @endcode + */ +struct LuaNil +{ +}; + +/** + * @brief Stack specialization for LuaNil. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const LuaNil&) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TNIL; + } +}; + +//================================================================================================= +/** + * @brief Base class for Lua variables and table item reference classes. + */ +template +class LuaRefBase +{ +protected: + friend struct Stack; + + //============================================================================================= + /** + * @brief Type tag for stack construction. + */ + struct FromStack + { + }; + + LuaRefBase(lua_State* L) noexcept + : m_L(L) + { + LUABRIDGE_ASSERT(L != nullptr); + } + + //============================================================================================= + /** + * @brief Create a reference to this reference. + * + * @returns An index in the Lua registry. + */ + int createRef() const + { + impl().push(m_L); + + return luaL_ref(m_L, LUA_REGISTRYINDEX); + } + +public: + //============================================================================================= + /** + * @brief Convert to a string using lua_tostring function. + * + * @returns A string representation of the referred Lua value. + */ + std::string tostring() const + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 2)) + return {}; +#endif + + const StackRestore stackRestore(m_L); + + lua_getglobal(m_L, "tostring"); + + impl().push(m_L); + + lua_call(m_L, 1, 1); + + const char* str = lua_tostring(m_L, -1); + return str != nullptr ? str : ""; + } + + //============================================================================================= + /** + * @brief Print a text description of the value to a stream. + * + * This is used for diagnostics. + * + * @param os An output stream. + */ + void print(std::ostream& os) const + { + switch (type()) + { + case LUA_TNONE: + case LUA_TNIL: + os << "nil"; + break; + + case LUA_TNUMBER: + os << unsafe_cast(); + break; + + case LUA_TBOOLEAN: + os << (unsafe_cast() ? "true" : "false"); + break; + + case LUA_TSTRING: + os << '"' << unsafe_cast() << '"'; + break; + + case LUA_TTABLE: + case LUA_TFUNCTION: + case LUA_TTHREAD: + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: + os << tostring(); + break; + + default: + os << "unknown"; + break; + } + } + + //============================================================================================= + /** + * @brief Insert a Lua value or table item reference to a stream. + * + * @param os An output stream. + * @param ref A Lua reference. + * + * @returns The output stream. + */ + friend std::ostream& operator<<(std::ostream& os, const LuaRefBase& ref) + { + ref.print(os); + return os; + } + + //============================================================================================= + /** + * @brief Retrieve the lua_State associated with the reference. + * + * @returns A Lua state. + */ + lua_State* state() const + { + return m_L; + } + + //============================================================================================= + /** + * @brief Return the Lua type of the referred value. + * + * This invokes lua_type(). + * + * @returns The type of the referred value. + * + * @see lua_type() + */ + int type() const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + return lua_type(m_L, -1); + } + + /** + * @brief Indicate whether it is a nil reference. + * + * @returns True if this is a nil reference, false otherwise. + */ + bool isNil() const { return type() == LUA_TNIL; } + + /** + * @brief Indicate whether it is a reference to a boolean. + * + * @returns True if it is a reference to a boolean, false otherwise. + */ + bool isBool() const { return type() == LUA_TBOOLEAN; } + + /** + * @brief Indicate whether it is a reference to a number. + * + * @returns True if it is a reference to a number, false otherwise. + */ + bool isNumber() const { return type() == LUA_TNUMBER; } + + /** + * @brief Indicate whether it is a reference to a string. + * + * @returns True if it is a reference to a string, false otherwise. + */ + bool isString() const { return type() == LUA_TSTRING; } + + /** + * @brief Indicate whether it is a reference to a table. + * + * @returns True if it is a reference to a table, false otherwise. + */ + bool isTable() const { return type() == LUA_TTABLE; } + + /** + * @brief Indicate whether it is a reference to a function. + * + * @returns True if it is a reference to a function, false otherwise. + */ + bool isFunction() const { return type() == LUA_TFUNCTION; } + + /** + * @brief Indicate whether it is a reference to a full userdata. + * + * @returns True if it is a reference to a full userdata, false otherwise. + */ + bool isUserdata() const { return type() == LUA_TUSERDATA; } + + /** + * @brief Indicate whether it is a reference to a lua thread (coroutine). + * + * @returns True if it is a reference to a lua thread, false otherwise. + */ + bool isThread() const { return type() == LUA_TTHREAD; } + + /** + * @brief Indicate whether it is a reference to a light userdata. + * + * @returns True if it is a reference to a light userdata, false otherwise. + */ + bool isLightUserdata() const { return type() == LUA_TLIGHTUSERDATA; } + + /** + * @brief Indicate whether it is a callable. + * + * @returns True if it is a callable, false otherwise. + */ + bool isCallable() const + { + if (isFunction()) + return true; + + auto metatable = getMetatable(); + return metatable.isTable() && metatable["__call"].isFunction(); + } + + /** + * @brief Get the name of the class, only if it is a C++ registered class via the library. + * + * @returns An optional string containing the name used to register the class with `beginClass`, nullopt in case it's not a registered class. + */ + std::optional getClassName() const + { + if (! isUserdata()) + return std::nullopt; + + const StackRestore stackRestore(m_L); + + impl().push(m_L); + if (! lua_getmetatable(m_L, -1)) + return std::nullopt; + + lua_rawgetp_x(m_L, -1, detail::getTypeKey()); + if (lua_isstring(m_L, -1)) + return lua_tostring(m_L, -1); + + return std::nullopt; + } + + //============================================================================================= + /** + * @brief Perform a safe explicit conversion to the type T. + * + * @returns An expected holding a value of the type T converted from this reference or an error code. + */ + template + TypeResult cast() const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + return Stack::get(m_L, -1); + } + + /** + * @brief Perform an unsafe explicit conversion to the type T. + * + * @returns A value of the type T converted from this reference. + */ + template + T unsafe_cast() const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + return *Stack::get(m_L, -1); + } + + //============================================================================================= + /** + * @brief Indicate if this reference is convertible to the type T. + * + * @returns True if the referred value is convertible to the type T, false otherwise. + */ + template + bool isInstance() const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + return Stack::isInstance(m_L, -1); + } + + //============================================================================================= + /** + * @brief Type cast operator. + * + * This operator calls cast and always dereference the returned expected instance, resulting in exceptions being thrown if the + * exceptions are enabled, or otherwise we'll enter the UB land (and a likely crash down the line). + * + * @returns A value of the type T converted from this reference. + */ + template + operator T() const + { + return cast().value(); + } + + //============================================================================================= + /** + * @brief Get the metatable for the LuaRef. + * + * @returns A LuaRef holding the metatable of the lua object. + */ + LuaRef getMetatable() const + { + if (isNil()) + return LuaRef(m_L); + + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + if (! lua_getmetatable(m_L, -1)) + return LuaRef(m_L); + + return LuaRef::fromStack(m_L); + } + + //============================================================================================= + /** + * @brief Compare this reference with a specified value using lua_compare(). + * + * This invokes metamethods. + * + * @param rhs A value to compare with. + * + * @returns True if the referred value is equal to the specified one. + */ + template + bool operator==(const T& rhs) const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + if (! Stack::push(m_L, rhs)) + return false; + + return lua_compare(m_L, -2, -1, LUA_OPEQ) == 1; + } + + /** + * @brief Compare this reference with a specified value using lua_compare(). + * + * This invokes metamethods. + * + * @param rhs A value to compare with. + * + * @returns True if the referred value is not equal to the specified one. + */ + template + bool operator!=(const T& rhs) const + { + return !(*this == rhs); + } + + /** + * @brief Compare this reference with a specified value using lua_compare(). + * + * This invokes metamethods. + * + * @param rhs A value to compare with. + * + * @returns True if the referred value is less than the specified one. + */ + template + bool operator<(const T& rhs) const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + if (! Stack::push(m_L, rhs)) + return false; + + const int lhsType = lua_type(m_L, -2); + const int rhsType = lua_type(m_L, -1); + if (lhsType != rhsType) + return lhsType < rhsType; + + return lua_compare(m_L, -2, -1, LUA_OPLT) == 1; + } + + /** + * @brief Compare this reference with a specified value using lua_compare(). + * + * This invokes metamethods. + * + * @param rhs A value to compare with. + * + * @returns True if the referred value is less than or equal to the specified one. + */ + template + bool operator<=(const T& rhs) const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + if (! Stack::push(m_L, rhs)) + return false; + + const int lhsType = lua_type(m_L, -2); + const int rhsType = lua_type(m_L, -1); + if (lhsType != rhsType) + return lhsType <= rhsType; + + return lua_compare(m_L, -2, -1, LUA_OPLE) == 1; + } + + /** + * @brief Compare this reference with a specified value using lua_compare(). + * + * This invokes metamethods. + * + * @param rhs A value to compare with. + * + * @returns True if the referred value is greater than the specified one. + */ + template + bool operator>(const T& rhs) const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + if (! Stack::push(m_L, rhs)) + return false; + + const int lhsType = lua_type(m_L, -2); + const int rhsType = lua_type(m_L, -1); + if (lhsType != rhsType) + return lhsType > rhsType; + + return lua_compare(m_L, -1, -2, LUA_OPLT) == 1; + } + + /** + * @brief Compare this reference with a specified value using lua_compare(). + * + * This invokes metamethods. + * + * @param rhs A value to compare with. + * + * @returns True if the referred value is greater than or equal to the specified one. + */ + template + bool operator>=(const T& rhs) const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + if (! Stack::push(m_L, rhs)) + return false; + + const int lhsType = lua_type(m_L, -2); + const int rhsType = lua_type(m_L, -1); + if (lhsType != rhsType) + return lhsType >= rhsType; + + return lua_compare(m_L, -1, -2, LUA_OPLE) == 1; + } + + /** + * @brief Compare this reference with a specified value using lua_compare(). + * + * This does not invoke metamethods. + * + * @param rhs A value to compare with. + * + * @returns True if the referred value is equal to the specified one. + */ + template + [[nodiscard]] bool rawequal(const T& v) const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + if (! Stack::push(m_L, v)) + return false; + + return lua_rawequal(m_L, -1, -2) == 1; + } + + //============================================================================================= + /** + * @brief Return the length of a referred array. + * + * This is identical to applying the Lua # operator. + * + * @returns The length of the referred array. + */ + [[nodiscard]] int length() const + { + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + return get_length(m_L, -1); + } + + //============================================================================================= + /** + * @brief Append one or more values to a referred table. + * + * If the table is a sequence this will add more elements to it. + * + * @param vs Values to append. + * + * @returns True if all values were successfully appended. + */ + template + [[nodiscard]] bool append(const Ts&... vs) const + { + static_assert(sizeof...(vs) > 0); + + const StackRestore stackRestore(m_L); + + impl().push(m_L); + + int index = get_length(m_L, -1) + 1; + + auto appendOne = [&](const auto& v) -> bool + { + if (! Stack>::push(m_L, v)) + return false; + + lua_rawseti(m_L, -2, index++); + return true; + }; + + return (appendOne(vs) && ...); + } + + //============================================================================================= + /** + * @brief Call Lua code and decode the first return value to R. + * + * For tuple types, each tuple element is decoded from a distinct Lua return value. + */ + template + TypeResult call(Args&&... args) const; + + /** + * @brief Call Lua code assuming no return values. + */ + template + TypeResult operator()(Args&&... args) const; + + /** + * @brief Call Lua code with an explicit error handler and decode the return type. + */ + template + TypeResult callWithHandler(F&& errorHandler, Args&&... args) const; + + /** + * @brief Call Lua code with an explicit error handler and no return values. + */ + template + TypeResult callWithHandler(F&& errorHandler, Args&&... args) const; + + /** + * @brief Build a typed callable wrapper from this Lua object. + */ + template + LuaFunction callable() const; + +protected: + lua_State* m_L = nullptr; + +private: + const Impl& impl() const { return static_cast(*this); } + + Impl& impl() { return static_cast(*this); } +}; + +//================================================================================================= +/** + * @brief Lightweight reference to a Lua object. + * + * The reference is maintained for the lifetime of the C++ object. + */ +class LuaRef : public LuaRefBase +{ + //============================================================================================= + /** + * @brief A proxy for representing table values. + */ + class TableItem : public LuaRefBase + { + friend class LuaRef; + + struct AdoptTableRef + { + }; + + struct ChainTableRef + { + }; + + public: + // Table items own the table they reference so stored proxies remain valid after the parent LuaRef changes. + // Rvalue indexing can adopt the parent table ref and defer one chained lookup to avoid extra registry churn. + //========================================================================================= + /** + * @brief Construct a TableItem from a table value. + * + * The table is in the registry, and the key is at the top of the stack. + * The key is popped off the stack. + * + * @param L A lua state. + * @param tableRef The index of a table in the Lua registry. + */ + TableItem(lua_State* L, int tableRef) + : LuaRefBase(L) + , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX)) + , m_ownsTableRef(true) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(m_L, 1, detail::error_lua_stack_overflow); +#endif + + lua_rawgeti(m_L, LUA_REGISTRYINDEX, tableRef); + m_tableRef = luaL_ref(L, LUA_REGISTRYINDEX); + } + + template + TableItem(lua_State* L, int tableRef, const char (&key)[N]) + : LuaRefBase(L) + , m_keyLiteral(key) + , m_ownsTableRef(true) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(m_L, 1, detail::error_lua_stack_overflow); +#endif + + lua_rawgeti(m_L, LUA_REGISTRYINDEX, tableRef); + m_tableRef = luaL_ref(L, LUA_REGISTRYINDEX); + } + + TableItem(lua_State* L, int tableRef, AdoptTableRef) + : LuaRefBase(L) + , m_tableRef(tableRef) + , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX)) + , m_ownsTableRef(true) + { + } + + template + TableItem(lua_State* L, int tableRef, AdoptTableRef, const char (&key)[N]) + : LuaRefBase(L) + , m_tableRef(tableRef) + , m_keyLiteral(key) + , m_ownsTableRef(true) + { + } + + TableItem(TableItem&& table, ChainTableRef) + : LuaRefBase(table.m_L) + , m_tableRef(std::exchange(table.m_tableRef, LUA_NOREF)) + , m_keyRef(luaL_ref(m_L, LUA_REGISTRYINDEX)) + , m_parentKeyRef(std::exchange(table.m_keyRef, LUA_NOREF)) + , m_parentKeyLiteral(std::exchange(table.m_keyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(table.m_ownsTableRef, false)) + { + } + + template + TableItem(TableItem&& table, ChainTableRef, const char (&key)[N]) + : LuaRefBase(table.m_L) + , m_tableRef(std::exchange(table.m_tableRef, LUA_NOREF)) + , m_parentKeyRef(std::exchange(table.m_keyRef, LUA_NOREF)) + , m_keyLiteral(key) + , m_parentKeyLiteral(std::exchange(table.m_keyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(table.m_ownsTableRef, false)) + { + } + + //========================================================================================= + /** + * @brief Create a TableItem via copy constructor. + * + * It is best to avoid code paths that invoke this, because it creates an extra temporary Lua reference. Typically this is done by + * passing the TableItem parameter as a `const` reference. + * + * @param other Another Lua table item reference. + */ + TableItem(const TableItem& other) + : LuaRefBase(other.m_L) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 1)) + return; +#endif + + other.pushTable(m_L); + m_tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + m_ownsTableRef = true; + + m_keyLiteral = other.m_keyLiteral; + if (other.m_keyRef != LUA_NOREF) + { + other.pushKey(m_L); + m_keyRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + } + } + + //========================================================================================= + /** + * @brief Destroy the proxy. + * + * This does not destroy the table value. + */ + ~TableItem() + { + if (m_keyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_keyRef); + + if (m_parentKeyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_parentKeyRef); + + if (m_ownsTableRef && m_tableRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_tableRef); + } + + //========================================================================================= + /** + * @brief Indicate whether this TableItem proxy is in a valid state. + * + * @returns True if the table reference is valid, false otherwise. + */ + bool isValid() const { return m_tableRef != LUA_NOREF; } + + //========================================================================================= + /** + * @brief Copy assignment operator (copy-and-swap idiom). + * + * Makes this TableItem refer to the same table slot as `other`. + * + * @param other Another Lua table item reference. + * + * @returns This reference. + */ + TableItem& operator=(const TableItem& other) + { + if (this == &other) + return *this; + TableItem tmp(other); + swap(tmp); + return *this; + } + + //========================================================================================= + /** + * @brief Move constructor. + * + * Transfers ownership of the table and key refs from `other` to this. + * + * @param other Another Lua table item reference (moved-from). + */ + TableItem(TableItem&& other) noexcept + : LuaRefBase(other.m_L) + , m_tableRef(std::exchange(other.m_tableRef, LUA_NOREF)) + , m_keyRef(std::exchange(other.m_keyRef, LUA_NOREF)) + , m_parentKeyRef(std::exchange(other.m_parentKeyRef, LUA_NOREF)) + , m_keyLiteral(std::exchange(other.m_keyLiteral, nullptr)) + , m_parentKeyLiteral(std::exchange(other.m_parentKeyLiteral, nullptr)) + , m_ownsTableRef(std::exchange(other.m_ownsTableRef, false)) + { + } + + //========================================================================================= + /** + * @brief Move assignment operator. + * + * Releases this TableItem's refs and takes ownership of `other`'s refs. + * + * @param other Another Lua table item reference (moved-from). + * + * @returns This reference. + */ + TableItem& operator=(TableItem&& other) noexcept + { + if (this == &other) + return *this; + + if (m_keyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_keyRef); + if (m_parentKeyRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_parentKeyRef); + if (m_ownsTableRef && m_tableRef != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_tableRef); + + m_L = other.m_L; + m_tableRef = std::exchange(other.m_tableRef, LUA_NOREF); + m_keyRef = std::exchange(other.m_keyRef, LUA_NOREF); + m_parentKeyRef = std::exchange(other.m_parentKeyRef, LUA_NOREF); + m_keyLiteral = std::exchange(other.m_keyLiteral, nullptr); + m_parentKeyLiteral = std::exchange(other.m_parentKeyLiteral, nullptr); + m_ownsTableRef = std::exchange(other.m_ownsTableRef, false); + + return *this; + } + + //========================================================================================= + /** + * @brief Assign a new value to this table key. + * + * This may invoke metamethods. + * + * @tparam T The type of a value to assign. + * + * @param v A value to assign. + * + * @returns This reference. + */ + template + TableItem& operator=(const T& v) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 3)) + return *this; +#endif + + const StackRestore stackRestore(m_L); + + pushTable(m_L); + if (m_keyLiteral != nullptr) + { + if (! Stack::push(m_L, v)) + return *this; + + lua_setfield(m_L, -2, m_keyLiteral); + } + else + { + pushKey(m_L); + + if (! Stack::push(m_L, v)) + return *this; + + lua_settable(m_L, -3); + } + + return *this; + } + + //========================================================================================= + /** + * @brief Assign a new value to this table key. + * + * The assignment is raw, no metamethods are invoked. + * + * @tparam T The type of a value to assign. + * + * @param v A value to assign. + * + * @returns This reference. + */ + template + TableItem& rawset(const T& v) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 3)) + return *this; +#endif + + const StackRestore stackRestore(m_L); + + pushTable(m_L); + if (m_keyLiteral != nullptr) + { + lua_pushstring(m_L, m_keyLiteral); + + if (! Stack::push(m_L, v)) + return *this; + + lua_rawset(m_L, -3); + } + else + { + pushKey(m_L); + + if (! Stack::push(m_L, v)) + return *this; + + lua_rawset(m_L, -3); + } + + return *this; + } + + //========================================================================================= + /** + * @brief Push the value onto the Lua stack. + */ + void push() const + { + push(m_L); + } + + void push(lua_State* L) const + { + LUABRIDGE_ASSERT(equalstates(L, m_L)); + +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return; +#endif + + pushTable(L); + + if (m_keyLiteral != nullptr) + { + lua_getfield(L, -1, m_keyLiteral); + } + else + { + pushKey(L); + lua_gettable(L, -2); + } + + lua_remove(L, -2); // remove the table + } + + //========================================================================================= + /** + * @brief Access a table value using a key. + * + * This invokes metamethods. + * + * @tparam T The type of a key. + * + * @param key A key value. + * + * @returns A Lua table item reference. + */ + template + TableItem operator[](const T& key) const& + { + const StackRestore stackRestore(m_L); + + if (! Stack::push(m_L, key)) + lua_pushnil(m_L); + + return materializedChildFromStackKey(); + } + + template + TableItem operator[](const T& key) && + { + if (canChain()) + { + if (! Stack::push(m_L, key)) + lua_pushnil(m_L); + + return TableItem(std::move(*this), ChainTableRef{}); + } + + const StackRestore stackRestore(m_L); + + if (! Stack::push(m_L, key)) + lua_pushnil(m_L); + + return materializedChildFromStackKey(); + } + + template + TableItem operator[](const char (&key)[N]) const& + { + const StackRestore stackRestore(m_L); + + push(m_L); + + const auto tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + + return TableItem(m_L, tableRef, AdoptTableRef{}, key); + } + + template + TableItem operator[](const char (&key)[N]) && + { + if (canChain()) + return TableItem(std::move(*this), ChainTableRef{}, key); + + const StackRestore stackRestore(m_L); + + push(m_L); + + const auto tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + + return TableItem(m_L, tableRef, AdoptTableRef{}, key); + } + + //========================================================================================= + /** + * @brief Access a table value using a key. + * + * The operation is raw, metamethods are not invoked. The result is passed by value and may not be modified. + * + * @tparam T The type of a key. + * + * @param key A key value. + * + * @returns A Lua value reference. + */ + template + LuaRef rawget(const T& key) const + { + return LuaRef(*this).rawget(key); + } + + //========================================================================================= + /** + * @brief Get a field from the table item value without metamethods in an unsafe fast path. + * + * @param key A field key. + * + * @returns The converted value. + */ + template + T unsafeRawgetField(const char* key) const + { +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(m_L, 3, detail::error_lua_stack_overflow); +#endif + + push(m_L); + lua_pushstring(m_L, key); + lua_rawget(m_L, -2); + + auto result = Stack::get(m_L, -1); + lua_pop(m_L, 2); + + return result.value(); + } + + //========================================================================================= + /** + * @brief Set a field on the table item value without metamethods in an unsafe fast path. + * + * @param key A field key. + * @param value A value to assign. + */ + template + void unsafeRawsetField(const char* key, T&& value) const + { +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(m_L, 3, detail::error_lua_stack_overflow); +#endif + + push(m_L); + lua_pushstring(m_L, key); + [[maybe_unused]] const auto pushed = Stack>::push(m_L, std::forward(value)); + LUABRIDGE_ASSERT(static_cast(pushed)); + + lua_rawset(m_L, -3); + lua_pop(m_L, 1); + } + + private: + void swap(TableItem& other) noexcept + { + using std::swap; + swap(m_L, other.m_L); + swap(m_tableRef, other.m_tableRef); + swap(m_keyRef, other.m_keyRef); + swap(m_parentKeyRef, other.m_parentKeyRef); + swap(m_keyLiteral, other.m_keyLiteral); + swap(m_parentKeyLiteral, other.m_parentKeyLiteral); + swap(m_ownsTableRef, other.m_ownsTableRef); + } + + bool hasParentKey() const + { + return m_parentKeyLiteral != nullptr || m_parentKeyRef != LUA_NOREF; + } + + bool canChain() const + { + return m_ownsTableRef && ! hasParentKey(); + } + + TableItem materializedChildFromStackKey() const + { + push(m_L); + lua_insert(m_L, -2); + + lua_pushvalue(m_L, -2); + const auto tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX); + lua_remove(m_L, -2); + + return TableItem(m_L, tableRef, AdoptTableRef{}); + } + + void pushTable(lua_State* L) const + { + if (m_tableRef == LUA_REFNIL) + lua_pushnil(L); + else + lua_rawgeti(L, LUA_REGISTRYINDEX, m_tableRef); + + if (m_parentKeyLiteral != nullptr) + { + lua_getfield(L, -1, m_parentKeyLiteral); + lua_remove(L, -2); + } + else if (m_parentKeyRef != LUA_NOREF) + { + pushRef(L, m_parentKeyRef); + lua_gettable(L, -2); + lua_remove(L, -2); + } + } + + void pushKey(lua_State* L) const + { + pushRef(L, m_keyRef); + } + + void pushRef(lua_State* L, int ref) const + { + if (ref == LUA_REFNIL) + lua_pushnil(L); + else + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + } + + int m_tableRef = LUA_NOREF; + int m_keyRef = LUA_NOREF; + int m_parentKeyRef = LUA_NOREF; + const char* m_keyLiteral = nullptr; + const char* m_parentKeyLiteral = nullptr; + bool m_ownsTableRef = false; + }; + + friend struct Stack; + friend struct Stack; + + //========================================================================================= + /** + * @brief Create a reference to an object at the top of the Lua stack and pop it. + * + * This constructor is private and not invoked directly. Instead, use the `fromStack` function. + * + * @param L A Lua state. + * + * @note The object is popped. + */ + LuaRef(lua_State* L, FromStack) noexcept + : LuaRefBase(L) + , m_ref(luaL_ref(m_L, LUA_REGISTRYINDEX)) + { + } + + //========================================================================================= + /** + * @brief Create a reference to an object on the Lua stack. + * + * This constructor is private and not invoked directly. Instead, use the `fromStack` function. + * + * @param L A Lua state. + * + * @param index The index of the value on the Lua stack. + * + * @note The object is not popped. + */ + LuaRef(lua_State* L, int index, FromStack) + : LuaRefBase(L) + , m_ref(LUA_NOREF) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 1)) + return; +#endif + + lua_pushvalue(m_L, index); + m_ref = luaL_ref(m_L, LUA_REGISTRYINDEX); + } + + LuaRef(std::nullptr_t) noexcept = delete; + +public: + //============================================================================================= + /** + * @brief Create an invalid reference that will be treated as nil. + * + * The Lua reference may be assigned later. + * + * @param L A Lua state. + */ + LuaRef(lua_State* L) noexcept + : LuaRefBase(L) + , m_ref(LUA_NOREF) + { + } + + //============================================================================================= + /** + * @brief Push a value onto a Lua stack and return a reference to it. + * + * @param L A Lua state. + * @param v A value to push. + */ + template + LuaRef(lua_State* L, const T& v) + : LuaRefBase(L) + , m_ref(LUA_NOREF) + { + if (! Stack::push(m_L, v)) + return; + + m_ref = luaL_ref(m_L, LUA_REGISTRYINDEX); + } + + //============================================================================================= + /** + * @brief Create a reference to a table item. + * + * @param v A table item reference. + */ + LuaRef(const TableItem& v) + : LuaRefBase(v.state()) + , m_ref(v.createRef()) + { + } + + //============================================================================================= + /** + * @brief Create a new reference to an existing Lua value. + * + * @param other An existing reference. + */ + LuaRef(const LuaRef& other) + : LuaRefBase(other.m_L) + , m_ref(other.createRef()) + { + } + + //============================================================================================= + /** + * @brief Move a reference to an existing Lua value. + * + * @param other An existing reference. + */ + LuaRef(LuaRef&& other) noexcept + : LuaRefBase(other.m_L) + , m_ref(std::exchange(other.m_ref, LUA_NOREF)) + { + } + + //============================================================================================= + /** + * @brief Destroy a reference. + * + * The corresponding Lua registry reference will be released. + * + * @note If the state refers to a thread, it is the responsibility of the caller to ensure that the thread still exists when the LuaRef is destroyed. + */ + ~LuaRef() + { + if (m_ref != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_ref); + } + + //============================================================================================= + /** + * @brief Return a reference to the top Lua stack item and pop it. + * + * The stack item is popped. + * + * @param L A Lua state. + * + * @returns A reference to a value that was on the top of the Lua stack. + */ + static LuaRef fromStack(lua_State* L) + { + return LuaRef(L, FromStack()); + } + + //============================================================================================= + /** + * @brief Return a reference to a Lua stack item with a specified index. + * + * The stack item is not removed. + * + * @param L A Lua state. + * @param index An index in the Lua stack. + * + * @returns A reference to a value in a Lua stack. + */ + static LuaRef fromStack(lua_State* L, int index) + { + return LuaRef(L, index, FromStack()); + } + + //============================================================================================= + /** + * @brief Create a new empty table on the top of a Lua stack and return a reference to it. + * + * @param L A Lua state. + * + * @returns A reference to the newly created table. + * + * @see luabridge::newTable() + */ + static LuaRef newTable(lua_State* L) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return { L }; +#endif + + lua_newtable(L); + return LuaRef(L, FromStack()); + } + + //============================================================================================= + /** + * @brief Create a new function on the top of a Lua stack and return a reference to it. + * + * @param L A Lua state. + * @param func The c++ function to map to lua. + * @param debugname Optional debug name (used only by Luau). + * + * @returns A reference to the newly created function. + * + * @see luabridge::newFunction() + */ + template + static LuaRef newFunction(lua_State* L, F&& func, const char* debugname = "") + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return { L }; +#endif + + detail::push_function(L, std::forward(func), debugname); + return LuaRef(L, FromStack()); + } + + //============================================================================================= + /** + * @brief Return a reference to a named global Lua variable. + * + * @param L A Lua state. + * @param name The name of a global variable. + * + * @returns A reference to the Lua variable. + * + * @see luabridge::getGlobal() + */ + static LuaRef getGlobal(lua_State* L, const char* name) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return { L }; +#endif + + lua_getglobal(L, name); + return LuaRef(L, FromStack()); + } + + //============================================================================================= + /** + * @brief Indicate whether it is an invalid reference. + * + * @returns True if this is an invalid reference, false otherwise. + */ + bool isValid() const { return m_ref != LUA_NOREF; } + + //============================================================================================= + /** + * @brief Assign another LuaRef to this LuaRef. + * + * @param rhs A reference to assign from. + * + * @returns This reference. + */ + LuaRef& operator=(const LuaRef& rhs) + { + LuaRef ref(rhs); + swap(ref); + return *this; + } + + //============================================================================================= + /** + * @brief Move assign another LuaRef to this LuaRef. + * + * @param rhs A reference to assign from. + * + * @returns This reference. + */ + LuaRef& operator=(LuaRef&& rhs) noexcept + { + if (m_ref != LUA_NOREF) + luaL_unref(m_L, LUA_REGISTRYINDEX, m_ref); + + m_L = rhs.m_L; + m_ref = std::exchange(rhs.m_ref, LUA_NOREF); + + return *this; + } + + //============================================================================================= + /** + * @brief Assign a table item reference. + * + * @param rhs A table item reference. + * + * @returns This reference. + */ + LuaRef& operator=(const LuaRef::TableItem& rhs) + { + LuaRef ref(rhs); + swap(ref); + return *this; + } + + //============================================================================================= + /** + * @brief Assign nil to this reference. + * + * @returns This reference. + */ + LuaRef& operator=(const LuaNil&) + { + LuaRef ref(m_L); + swap(ref); + return *this; + } + + //============================================================================================= + /** + * @brief Assign a different value to this reference. + * + * @param rhs A value to assign. + * + * @returns This reference. + */ + template + LuaRef& operator=(const T& rhs) + { + LuaRef ref(m_L, rhs); + swap(ref); + return *this; + } + + //============================================================================================= + /** + * @brief Place the object onto the Lua stack. + */ + void push() const + { + push(m_L); + } + + void push(lua_State* L) const + { + LUABRIDGE_ASSERT(equalstates(L, m_L)); + +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return; +#endif + + lua_rawgeti(L, LUA_REGISTRYINDEX, m_ref); + } + + //============================================================================================= + /** + * @brief Pop the top of Lua stack and assign the ref to m_ref + */ + void pop() + { + pop(m_L); + } + + void pop(lua_State* L) + { + LUABRIDGE_ASSERT(equalstates(L, m_L)); + + if (m_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, m_ref); + + m_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + + //============================================================================================= + /** + * @brief Move the reference to a separate coroutine (lua_State). + */ + void moveTo(lua_State* newL) + { + push(); // push value onto m_L + + lua_xmove(m_L, newL, 1); // move value from m_L to newL (pops m_L, pushes newL) + + const int oldRef = m_ref; + lua_State* const oldL = m_L; + + m_L = newL; + m_ref = luaL_ref(newL, LUA_REGISTRYINDEX); // register on newL (pops from newL's stack) + + luaL_unref(oldL, LUA_REGISTRYINDEX, oldRef); // release old registry entry + } + + //============================================================================================= + /** + * @brief Access a table value using a key. + * + * This invokes metamethods. + * + * @param key A key in the table. + * + * @returns A reference to the table item. + */ + template + TableItem operator[](const T& key) const& + { + if (! Stack::push(m_L, key)) + { + lua_pushnil(m_L); + return TableItem(m_L, m_ref); + } + + return TableItem(m_L, m_ref); + } + + template + TableItem operator[](const T& key) && + { + if (! Stack::push(m_L, key)) + { + lua_pushnil(m_L); + return TableItem(m_L, m_ref); + } + + return TableItem(m_L, std::exchange(m_ref, LUA_NOREF), typename TableItem::AdoptTableRef{}); + } + + template + TableItem operator[](const char (&key)[N]) const& + { + return TableItem(m_L, m_ref, key); + } + + template + TableItem operator[](const char (&key)[N]) && + { + return TableItem(m_L, std::exchange(m_ref, LUA_NOREF), typename TableItem::AdoptTableRef{}, key); + } + + //============================================================================================= + /** + * @brief Access a table value using a key. + * + * The operation is raw, metamethods are not invoked. The result is passed by value and may not be modified. + * + * @param key A key in the table. + * + * @returns A reference to the table item. + */ + template + [[nodiscard]] LuaRef rawget(const T& key) const + { + const StackRestore stackRestore(m_L); + + push(m_L); + + if (! Stack::push(m_L, key)) + return LuaRef(m_L); + + lua_rawget(m_L, -2); + return LuaRef(m_L, FromStack()); + } + + + //============================================================================================= + /** + * @brief Get a table field by C-string key and convert to T. + * + * This invokes metamethods. + * + * @param key A field key. + * + * @returns A converted value or an error. + */ + template + [[nodiscard]] TypeResult getField(const char* key) const + { + const StackRestore stackRestore(m_L); + + push(m_L); + lua_getfield(m_L, -1, key); + + return Stack::get(m_L, -1); + } + + //============================================================================================= + /** + * @brief Try to get a table field by C-string key and convert to T. + * + * This invokes metamethods and returns std::nullopt when the referred value is not a table + * or the field cannot be converted to the requested type. + */ + template + [[nodiscard]] std::optional tryGetField(const char* key) const + { + const StackRestore stackRestore(m_L); + + push(m_L); + if (! lua_istable(m_L, -1)) + return std::nullopt; + + lua_getfield(m_L, -1, key); + + auto result = Stack>::get(m_L, -1); + if (! result) + return std::nullopt; + + return *result; + } + + //============================================================================================= + /** + * @brief Set a table field by C-string key. + * + * This invokes metamethods. + * + * @param key A field key. + * @param value A value to assign. + * + * @returns True if value push succeeded, false otherwise. + */ + template + [[nodiscard]] bool setField(const char* key, T&& value) const + { + const StackRestore stackRestore(m_L); + + push(m_L); + + if (! Stack>::push(m_L, std::forward(value))) + return false; + + lua_setfield(m_L, -2, key); + return true; + } + + //============================================================================================= + /** + * @brief Get a table field by C-string key without metamethods. + * + * @param key A field key. + * + * @returns A converted value or an error. + */ + template + [[nodiscard]] TypeResult rawgetField(const char* key) const + { + const StackRestore stackRestore(m_L); + + push(m_L); + lua_pushstring(m_L, key); + lua_rawget(m_L, -2); + + return Stack::get(m_L, -1); + } + + //============================================================================================= + /** + * @brief Set a table field by C-string key without metamethods. + * + * @param key A field key. + * @param value A value to assign. + * + * @returns True if key/value push succeeded, false otherwise. + */ + template + [[nodiscard]] bool rawsetField(const char* key, T&& value) const + { + const StackRestore stackRestore(m_L); + + push(m_L); + lua_pushstring(m_L, key); + + if (! Stack>::push(m_L, std::forward(value))) + return false; + + lua_rawset(m_L, -3); + return true; + } + + //============================================================================================= + /** + * @brief Get a table field by C-string key without metamethods in an unsafe fast path. + * + * This helper is intended for hot loops where stack conversion is known to succeed. + * + * @param key A field key. + * + * @returns The converted value. + */ + template + T unsafeRawgetField(const char* key) const + { +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(m_L, 3, detail::error_lua_stack_overflow); +#endif + + push(m_L); + lua_pushstring(m_L, key); + lua_rawget(m_L, -2); + + auto result = Stack::get(m_L, -1); + lua_pop(m_L, 2); + + return result.value(); + } + + //============================================================================================= + /** + * @brief Set a table field by C-string key without metamethods in an unsafe fast path. + * + * @param key A field key. + * @param value A value to assign. + */ + template + void unsafeRawsetField(const char* key, T&& value) const + { +#if LUABRIDGE_SAFE_STACK_CHECKS + luaL_checkstack(m_L, 3, detail::error_lua_stack_overflow); +#endif + + push(m_L); + lua_pushstring(m_L, key); + [[maybe_unused]] const auto pushed = Stack>::push(m_L, std::forward(value)); + LUABRIDGE_ASSERT(static_cast(pushed)); + + lua_rawset(m_L, -3); + lua_pop(m_L, 1); + } + + //============================================================================================= + /** + * @brief Get the Lua pointer of the referenced value. + */ + const void* getPointer() const + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 3)) + return nullptr; +#endif + + lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_ref); + + if (lua_isuserdata(m_L, -1) && lua_getmetatable(m_L, -1)) + { + const void* key = static_cast(detail::getTypeKey()); + lua_pushlightuserdata(m_L, const_cast(key)); + lua_rawget(m_L, -2); + const bool isLuaBridgeType = lua_isstring(m_L, -1); + lua_pop(m_L, 2); + + if (isLuaBridgeType) + { + const auto* ud = static_cast(lua_touserdata(m_L, -1)); + const void* ptr = ud->getPointer(); + lua_pop(m_L, 1); + return ptr; + } + } + + const void* ptr = lua_topointer(m_L, -1); + lua_pop(m_L, 1); + + return ptr; + } + + //============================================================================================= + /** + * @brief Get the unique hash of a LuaRef. + */ + [[nodiscard]] std::size_t hash() const + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(m_L, 1)) + return 0; +#endif + + lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_ref); + + const int t = lua_type(m_L, -1); + + std::size_t value; + switch (t) + { + case LUA_TNONE: + case LUA_TNIL: + value = std::hash{}(nullptr); + break; + + case LUA_TBOOLEAN: + value = std::hash{}(lua_toboolean(m_L, -1) != 0); + break; + + case LUA_TNUMBER: + value = std::hash{}(lua_tonumber(m_L, -1)); + break; + + case LUA_TSTRING: + { + std::size_t len = 0; + const char* s = lua_tolstring(m_L, -1, &len); + value = std::hash{}(std::string_view(s, len)); + break; + } + + case LUA_TTABLE: + case LUA_TFUNCTION: + case LUA_TTHREAD: + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: + default: + value = reinterpret_cast(lua_topointer(m_L, -1)); + break; + } + + lua_pop(m_L, 1); + + const std::size_t seed = std::hash{}(t == LUA_TNONE ? LUA_TNIL : t); + return value + 0x9e3779b9u + (seed << 6) + (seed >> 2); + } + +private: + void swap(LuaRef& other) noexcept + { + using std::swap; + + swap(m_L, other.m_L); + swap(m_ref, other.m_ref); + } + + int m_ref = LUA_NOREF; +}; + +//================================================================================================= +/** + * @brief Equality between type T and LuaRef. + */ +template +auto operator==(const T& lhs, const LuaRef& rhs) + -> std::enable_if_t && !std::is_same_v>, bool> +{ + return rhs == lhs; +} + +/** + * @brief Inequality between type T and LuaRef. + */ +template +auto operator!=(const T& lhs, const LuaRef& rhs) + -> std::enable_if_t && !std::is_same_v>, bool> +{ + return !(rhs == lhs); +} + +/** + * @brief Less than between type T and LuaRef. + */ +template +auto operator<(const T& lhs, const LuaRef& rhs) + -> std::enable_if_t && !std::is_same_v>, bool> +{ + return rhs > lhs; +} + +/** + * @brief Less than equal between type T and LuaRef. + */ +template +auto operator<=(const T& lhs, const LuaRef& rhs) + -> std::enable_if_t && !std::is_same_v>, bool> +{ + return rhs >= lhs; +} + +/** + * @brief Greater than between type T and LuaRef. + */ +template +auto operator>(const T& lhs, const LuaRef& rhs) + -> std::enable_if_t && !std::is_same_v>, bool> +{ + return rhs < lhs; +} + +/** + * @brief Greater than equal between type T and LuaRef. + */ +template +auto operator>=(const T& lhs, const LuaRef& rhs) + -> std::enable_if_t && !std::is_same_v>, bool> +{ + return rhs <= lhs; +} + +//================================================================================================= +/** + * @brief Stack specialization for `LuaRef`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const LuaRef& v) + { + v.push(L); + + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + return LuaRef::fromStack(L, index); + } + + [[nodiscard]] static bool isInstance(lua_State*, int) + { + return true; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `TableItem`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const LuaRef::TableItem& v) + { + v.push(L); + + return {}; + } +}; + +//================================================================================================= +/** + * @brief Create a reference to a new, empty table. + * + * This is a syntactic abbreviation for LuaRef::newTable (). + */ +[[nodiscard]] inline LuaRef newTable(lua_State* L) +{ + return LuaRef::newTable(L); +} + +//================================================================================================= +/** + * @brief Create a reference to a new function. + * + * This is a syntactic abbreviation for LuaRef::newFunction (). + */ +template +[[nodiscard]] inline LuaRef newFunction(lua_State* L, F&& func) +{ + return LuaRef::newFunction(L, std::forward(func)); +} + +//================================================================================================= +/** + * @brief Create a reference to a value in the global table. + * + * This is a syntactic abbreviation for LuaRef::getGlobal (). + */ +[[nodiscard]] inline LuaRef getGlobal(lua_State* L, const char* name) +{ + return LuaRef::getGlobal(L, name); +} + +//================================================================================================= +/** + * @brief C++ like cast syntax, safe. + */ +template +[[nodiscard]] TypeResult cast(const LuaRef& ref) +{ + return ref.cast(); +} + +/** + * @brief C++ like cast syntax, unsafe. + */ +template +[[nodiscard]] T unsafe_cast(const LuaRef& ref) +{ + return ref.unsafe_cast(); +} +} // namespace luabridge + +namespace std { +template <> +struct hash +{ + std::size_t operator()(const luabridge::LuaRef& x) const + { + return x.hash(); + } +}; +} // namespace std diff --git a/external/include/LuaBridge3/LuaBridge/detail/Namespace.h b/external/include/LuaBridge3/LuaBridge/detail/Namespace.h new file mode 100644 index 0000000000..6d59cde01f --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Namespace.h @@ -0,0 +1,2206 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2019, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// Copyright 2007, Nathan Reed +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "ClassInfo.h" +#include "FlagSet.h" +#include "LuaHelpers.h" +#include "LuaException.h" +#include "Options.h" +#include "TypeTraits.h" + +#include +#include +#include +#include +#include +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Base for class and namespace registration. + * + * Maintains Lua stack in the proper state. Once beginNamespace, beginClass or deriveClass is called the parent object upon its destruction + * may no longer clear the Lua stack. + * + * Then endNamespace or endClass is called, a new parent is created and the child transfers the responsibility for clearing stack to it. + * + * So there can be maximum one "active" registrar object. + */ +namespace detail { + +struct BaseClassInfo +{ + const void* staticKey; + const void* classKey; + lua_Integer castOffset; +}; + +template +lua_Integer computeCastOffset() noexcept +{ + static_assert(std::is_base_of_v); + + alignas(Derived) std::byte buf[sizeof(Derived)] = {}; + + auto* derived = reinterpret_cast(buf); + auto* base = static_cast(derived); // implicit upcast, purely pointer arithmetic + + return static_cast(reinterpret_cast(base) - reinterpret_cast(derived)); +} + +class Registrar +{ +protected: + Registrar(lua_State* L) + : L(L) + , m_stackSize(0) + { + } + + Registrar(lua_State* L, int skipStackPops) + : L(L) + , m_stackSize(0) + , m_skipStackPops(skipStackPops) + { + } + + Registrar(const Registrar& rhs) = delete; + + Registrar(Registrar&& rhs) + : L(rhs.L) + , m_stackSize(std::exchange(rhs.m_stackSize, 0)) + , m_skipStackPops(std::exchange(rhs.m_skipStackPops, 0)) + { + } + + Registrar& operator=(const Registrar& rhs) = delete; + + Registrar& operator=(Registrar&& rhs) + { + m_stackSize = std::exchange(rhs.m_stackSize, 0); + m_skipStackPops = std::exchange(rhs.m_skipStackPops, 0); + + return *this; + } + + ~Registrar() + { + const int popsCount = m_stackSize - m_skipStackPops; + if (popsCount > 0) + { + LUABRIDGE_ASSERT(popsCount <= lua_gettop(L)); + + lua_pop(L, popsCount); + } + } + + void assertIsActive() const + { + if (m_stackSize == 0) + { + throw_or_assert("Unable to continue registration"); + } + } + + lua_State* const L = nullptr; + int m_stackSize = 0; + int m_skipStackPops = 0; +}; + +} // namespace detail + +//================================================================================================= +/** + * @brief Provides C++ to Lua registration capabilities. + * + * This class is not instantiated directly, call `getGlobalNamespace` to start the registration process. + */ +class Namespace : public detail::Registrar +{ + //============================================================================================= +#if 0 + /** + * @brief Error reporting. + * + * This function looks handy, why aren't we using it? + */ + static int luaError(lua_State* L, std::string message) + { + LUABRIDGE_ASSERT(lua_isstring(L, lua_upvalueindex(1))); + std::string s; + + // Get information on the caller's caller to format the message, + // so the error appears to originate from the Lua source. + lua_Debug ar; + + int result = lua_getstack(L, 2, &ar); + if (result != 0) + { + lua_getinfo(L, "Sl", &ar); + s = ar.short_src; + if (ar.currentline != -1) + { + // poor mans int to string to avoid . + lua_pushnumber(L, ar.currentline); + s = s + ":" + lua_tostring(L, -1) + ": "; + lua_pop(L, 1); + } + } + + s = s + message; + + luaL_error(L, "%s", s.c_str()); + } +#endif + + //============================================================================================= + /** + * @brief Factored base to reduce template instantiations. + */ + class ClassBase : public detail::Registrar + { + public: + explicit ClassBase(const char* name, Namespace parent) + : Registrar(std::move(parent)) + , className(name == nullptr ? "" : name) + { + } + + using Registrar::operator=; + + protected: + static void appendParentList(lua_State* L, int parentsIndex, int visitedIndex, int baseMetatableIndex) + { + parentsIndex = lua_absindex(L, parentsIndex); + visitedIndex = lua_absindex(L, visitedIndex); + baseMetatableIndex = lua_absindex(L, baseMetatableIndex); + + LUABRIDGE_ASSERT(lua_istable(L, parentsIndex)); + LUABRIDGE_ASSERT(lua_istable(L, visitedIndex)); + LUABRIDGE_ASSERT(lua_istable(L, baseMetatableIndex)); + + const auto appendUnique = [=](int metatableIndex) + { + metatableIndex = lua_absindex(L, metatableIndex); + + if (! lua_istable(L, metatableIndex)) + return; + + auto* metatablePtr = const_cast(lua_topointer(L, metatableIndex)); + LUABRIDGE_ASSERT(metatablePtr != nullptr); + + lua_pushlightuserdata(L, metatablePtr); + lua_rawget(L, visitedIndex); + const bool alreadyVisited = ! lua_isnil(L, -1); + lua_pop(L, 1); + + if (alreadyVisited) + return; + + lua_pushlightuserdata(L, metatablePtr); + lua_pushboolean(L, 1); + lua_rawset(L, visitedIndex); + + lua_pushvalue(L, metatableIndex); + lua_rawseti(L, parentsIndex, static_cast(static_cast(get_length(L, parentsIndex)) + 1)); + }; + + appendUnique(baseMetatableIndex); + + lua_rawgetp_x(L, baseMetatableIndex, detail::getParentKey()); // Stack: ..., parent list | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 1); + return; + } + + const int parentListIndex = lua_absindex(L, -1); + const int count = get_length(L, parentListIndex); + + for (int i = 1; i <= count; ++i) + { + lua_rawgeti(L, parentListIndex, i); + appendUnique(-1); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + void setObjectMetaMethods(int tableIndex, bool simple) + { + tableIndex = lua_absindex(L, tableIndex); + + if (simple) + { + lua_rawgetp_x(L, tableIndex, detail::getPropgetKey()); // Stack: ..., pg | nil + lua_pushvalue(L, tableIndex); // Stack: ..., pg | nil, mt + lua_pushcclosure_x(L, &detail::index_metamethod_simple, "__index", 2); + rawsetfield(L, tableIndex, "__index"); + + lua_rawgetp_x(L, tableIndex, detail::getPropsetKey()); // Stack: ..., ps | nil + lua_pushcclosure_x(L, &detail::newindex_metamethod_simple, "__newindex", 1); + rawsetfield(L, tableIndex, "__newindex"); + } + else + { + lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); + rawsetfield(L, tableIndex, "__index"); + + lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); + rawsetfield(L, tableIndex, "__newindex"); + } + } + + void setStaticMetaMethods(int tableIndex, bool simple) + { + tableIndex = lua_absindex(L, tableIndex); + + if (simple) + { + lua_rawgetp_x(L, tableIndex, detail::getPropgetKey()); // Stack: ..., pg | nil + lua_rawgetp_x(L, tableIndex, detail::getClassKey()); // Stack: ..., pg | nil, cl | nil + lua_pushvalue(L, tableIndex); // Stack: ..., pg | nil, cl | nil, mt + lua_pushcclosure_x(L, &detail::index_metamethod_simple, "__index", 3); + rawsetfield(L, tableIndex, "__index"); + + lua_rawgetp_x(L, tableIndex, detail::getPropsetKey()); // Stack: ..., ps | nil + lua_pushcclosure_x(L, &detail::newindex_metamethod_simple, "__newindex", 1); + rawsetfield(L, tableIndex, "__newindex"); + } + else + { + lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); + rawsetfield(L, tableIndex, "__index"); + + lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); + rawsetfield(L, tableIndex, "__newindex"); + } + } + + //========================================================================================= + /** + * @brief Create the const table. + */ + void createConstTable(const char* name, bool trueConst, Options options) + { + LUABRIDGE_ASSERT(name != nullptr); + + std::string type_name = std::string(trueConst ? "const " : "") + name; + + // Stack: namespace table (ns) + + lua_newtable(L); // Stack: ns, const table (co) + lua_pushvalue(L, -1); // Stack: ns, co, co + lua_setmetatable(L, -2); // co.__metatable = co. Stack: ns, co + + pushunsigned(L, options.toUnderlying()); // Stack: ns, co, options + lua_rawsetp_x(L, -2, detail::getClassOptionsKey()); // co [classOptionsKey] = options. Stack: ns, co + + lua_pushstring(L, type_name.c_str()); // Stack: ns, co, name + lua_rawsetp_x(L, -2, detail::getTypeKey()); // co [typeKey] = name. Stack: ns, co + + lua_newtable(L); // Stack: ns, co, propget table (pg) + lua_rawsetp_x(L, -2, detail::getPropgetKey()); // Stack: ns, co + + setObjectMetaMethods(-1, ! options.test(extensibleClass)); // Stack: ns, co + + if (! options.test(visibleMetatables)) + { + lua_pushboolean(L, 0); + rawsetfield(L, -2, "__metatable"); + } + } + + //========================================================================================= + /** + * @brief Create the class table. + * + * The Lua stack should have the const table on top. + */ + void createClassTable(const char* name, Options options) + { + LUABRIDGE_ASSERT(name != nullptr); + + // Stack: namespace table (ns), const table (co) + + // Class table is the same as const table except the propset table + createConstTable(name, false, options); // Stack: ns, co, cl + + lua_newtable(L); // Stack: ns, co, cl, propset table (ps) + lua_rawsetp_x(L, -2, detail::getPropsetKey()); // cl [propsetKey] = ps. Stack: ns, co, cl + + lua_pushvalue(L, -2); // Stack: ns, co, cl, co + lua_rawsetp_x(L, -2, detail::getConstKey()); // cl [constKey] = co. Stack: ns, co, cl + + lua_pushvalue(L, -1); // Stack: ns, co, cl, cl + lua_rawsetp_x(L, -3, detail::getClassKey()); // co [classKey] = cl. Stack: ns, co, cl + + if (! options.test(extensibleClass)) + setObjectMetaMethods(-1, true); + } + + //========================================================================================= + /** + * @brief Create the static table. + */ + void createStaticTable(const char* name, Options options) + { + LUABRIDGE_ASSERT(name != nullptr); + + // Stack: namespace table (ns), const table (co), class table (cl) + + lua_newtable(L); // Stack: ns, co, cl, static table (st) + lua_newtable(L); // Stack: ns, co, cl, st, static metatable (mt) + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, mt, mt + lua_setmetatable(L, -3); // st.__metatable = mt. Stack: ns, co, cl, st, mt + lua_insert(L, -2); // Stack: ns, co, cl, st, mt, st + rawsetfield(L, -5, name); // ns [name] = st. Stack: ns, co, cl, st, mt + + pushunsigned(L, options.toUnderlying()); // Stack: ns, co, cl, st, mt, options + lua_rawsetp_x(L, -2, detail::getClassOptionsKey()); // st [classOptionsKey] = options. Stack: ns, co, cl, st, mt + + lua_newtable(L); // Stack: ns, co, cl, st, proget table (pg) + lua_rawsetp_x(L, -2, detail::getPropgetKey()); // st [propgetKey] = pg. Stack: ns, co, cl, st + + lua_newtable(L); // Stack: ns, co, cl, st, propset table (ps) + lua_rawsetp_x(L, -2, detail::getPropsetKey()); // st [propsetKey] = pg. Stack: ns, co, cl, st + + lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl + lua_rawsetp_x(L, -2, detail::getClassKey()); // st [classKey] = cl. Stack: ns, co, cl, st + + setStaticMetaMethods(-1, ! options.test(extensibleClass)); + + if (! options.test(visibleMetatables)) + { + lua_pushboolean(L, 0); + rawsetfield(L, -2, "__metatable"); + } + } + + //========================================================================================= + /** + * @brief Asserts on stack state. + */ + void assertStackState() const + { + // Stack: const table (co), class table (cl), static table (st) + LUABRIDGE_ASSERT(lua_istable(L, -3)); + LUABRIDGE_ASSERT(lua_istable(L, -2)); + LUABRIDGE_ASSERT(lua_istable(L, -1)); + } + + //========================================================================================= + const char* className = ""; + }; + + //============================================================================================= + /** + * @brief Provides a class registration in a lua_State. + * + * After construction the Lua stack holds these objects: + * -1 static table + * -2 class table + * -3 const table + * -4 enclosing namespace table + */ + template + class Class : public ClassBase + { + public: + //========================================================================================= + + /** + * @brief Register a new class or add to an existing class registration. + * + * @param name The new class name. + * @param parent A parent namespace object. + * @param options Class options. + */ + Class(const char* name, Namespace parent, Options options) + : ClassBase(name, std::move(parent)) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + rawgetfield(L, -1, name); // Stack: ns, static table (st) | nil + + if (lua_isnil(L, -1)) // Stack: ns, nil + { + lua_pop(L, 1); // Stack: ns + + createConstTable(name, true, options); // Stack: ns, const table (co) + ++m_stackSize; +#if !defined(LUABRIDGE_ON_LUAU) + lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, function + rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co +#endif + lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); + rawsetfield(L, -2, "__tostring"); + + createClassTable(name, options); // Stack: ns, co, class table (cl) + ++m_stackSize; + + lua_pushlightuserdata(L, const_cast(detail::getConstRegistryKey())); // Stack: ns, co, cl, id + lua_rawsetp_x(L, -3, detail::getTypeIdentityKey()); // co[typeIdentityKey] = const id. Stack: ns, co, cl + lua_pushlightuserdata(L, const_cast(detail::getClassRegistryKey())); // Stack: ns, co, cl, id + lua_rawsetp_x(L, -2, detail::getTypeIdentityKey()); // cl[typeIdentityKey] = class id. Stack: ns, co, cl + +#if !defined(LUABRIDGE_ON_LUAU) + lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, cl, function + rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl +#endif + + lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); + rawsetfield(L, -2, "__tostring"); + + createStaticTable(name, options); // Stack: ns, co, cl, st + ++m_stackSize; + + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp_x(L, -2, detail::getStaticKey()); // cl [staticKey] = st. Stack: ns, co, cl, st + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp_x(L, -3, detail::getStaticKey()); // co [staticKey] = st. Stack: ns, co, cl, st + + // Map T back to its tables. + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getStaticRegistryKey()); // Stack: ns, co, cl, st + lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl + lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, cl, st + lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co + lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, co, cl, st + } + else + { + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: ns, vst + ++m_stackSize; + + lua_getmetatable(L, -1); // Stack: ns, vst, st + lua_insert(L, -2); // Stack: ns, st, vst + lua_pop(L, 1); // Stack: ns, st + + // Map T back from its stored tables + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, st, co + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Class was previously registered as table or namespace ? + lua_insert(L, -2); // Stack: ns, co, st + ++m_stackSize; + + lua_rawgetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, st, cl + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Class was previously registered as table or namespace ? + lua_insert(L, -2); // Stack: ns, co, cl, st + ++m_stackSize; + } + } + + //========================================================================================= + /** + * @brief Derive a new class. + * + * @param name The class name. + * @param parent A parent namespace object. + * @param staticKey Key where the class is stored. + */ + Class(const char* name, Namespace parent, std::initializer_list bases, Options options) + : ClassBase(name, std::move(parent)) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + createConstTable(name, true, options); // Stack: ns, const table (co) + ++m_stackSize; +#if !defined(LUABRIDGE_ON_LUAU) + lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, function + rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co +#endif + lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); + rawsetfield(L, -2, "__tostring"); + + createClassTable(name, options); // Stack: ns, co, class table (cl) + ++m_stackSize; + + lua_pushlightuserdata(L, const_cast(detail::getConstRegistryKey())); // Stack: ns, co, cl, id + lua_rawsetp_x(L, -3, detail::getTypeIdentityKey()); // co[typeIdentityKey] = const id. Stack: ns, co, cl + lua_pushlightuserdata(L, const_cast(detail::getClassRegistryKey())); // Stack: ns, co, cl, id + lua_rawsetp_x(L, -2, detail::getTypeIdentityKey()); // cl[typeIdentityKey] = class id. Stack: ns, co, cl + +#if !defined(LUABRIDGE_ON_LUAU) + lua_pushcfunction_x(L, &detail::gc_metamethod, "__gc"); // Stack: ns, co, cl, function + rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl +#endif + lua_pushcfunction_x(L, &detail::tostring_metamethod, "__tostring"); + rawsetfield(L, -2, "__tostring"); + + createStaticTable(name, options); // Stack: ns, co, cl, st + ++m_stackSize; + + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp_x(L, -2, detail::getStaticKey()); // cl [staticKey] = st. Stack: ns, co, cl, st + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp_x(L, -3, detail::getStaticKey()); // co [staticKey] = st. Stack: ns, co, cl, st + + const int coIndex = lua_absindex(L, -3); + const int clIndex = lua_absindex(L, -2); + const int stIndex = lua_absindex(L, -1); + + lua_newtable(L); // Stack: ns, co, cl, st, cl parents + const int clParentsIndex = lua_absindex(L, -1); + lua_newtable(L); // Stack: ns, co, cl, st, cl parents, cast table + const int castTableIndex = lua_absindex(L, -1); + lua_newtable(L); // Stack: ns, co, cl, st, cl parents, cast table, visited + const int visitedIndex = lua_absindex(L, -1); + + for (const detail::BaseClassInfo& base : bases) + { + lua_rawgetp_x(L, LUA_REGISTRYINDEX, base.staticKey); // Stack: ..., visited, base st | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 4); // pop (nil), visited, cast table, cl parents. Stack: ns, co, cl, st + + throw_or_assert("Base class is not registered"); +#if !LUABRIDGE_HAS_EXCEPTIONS + return; +#endif + } + + lua_rawgetp_x(L, -1, detail::getClassKey()); // Stack: ..., visited, base st, base cl | nil + if (! lua_istable(L, -1)) + { + lua_pop(L, 5); // pop (nil), base st, visited, cast table, cl parents. Stack: ns, co, cl, st + + throw_or_assert("Base class is not registered"); +#if !LUABRIDGE_HAS_EXCEPTIONS + return; +#endif + } + + appendParentList(L, clParentsIndex, visitedIndex, -1); + + // Store the direct pointer adjustment offset for this base class + lua_pushlightuserdata(L, const_cast(base.classKey)); + lua_pushinteger(L, base.castOffset); + lua_rawset(L, castTableIndex); + + // Compose and propagate ancestor offsets from the base's own cast table. + // offset(T → ancestor) = offset(T → base) + offset(base → ancestor) + lua_rawgetp_x(L, -1, detail::getCastTableKey()); // Stack: ..., base st, base cl, base cast table | nil + if (lua_istable(L, -1)) + { + lua_pushnil(L); + while (lua_next(L, -2) != 0) + { + // Stack: ..., base cast table, ancestor key, ancestor offset + const lua_Integer ancestorOffset = lua_tointeger(L, -1); + + lua_pushvalue(L, -2); // duplicate ancestor key to check presence + lua_rawget(L, castTableIndex); + const bool alreadyPresent = ! lua_isnil(L, -1); + lua_pop(L, 1); + + if (! alreadyPresent) + { + lua_pushvalue(L, -2); // push ancestor key + lua_pushinteger(L, base.castOffset + ancestorOffset); + lua_rawset(L, castTableIndex); + } + + lua_pop(L, 1); // pop ancestor offset; ancestor key stays for next() + } + lua_pop(L, 1); // pop base cast table + } + else + { + lua_pop(L, 1); // pop nil + } + + lua_pop(L, 2); // pop base cl and base st. Stack: ..., cast table, visited + } + + lua_pop(L, 1); // pop visited. Stack: ns, co, cl, st, cl parents, cast table + + // Store the cast table in both class and const metatables + lua_pushvalue(L, castTableIndex); + lua_rawsetp_x(L, clIndex, detail::getCastTableKey()); // cl[castTableKey] = cast table + lua_pushvalue(L, castTableIndex); + lua_rawsetp_x(L, coIndex, detail::getCastTableKey()); // co[castTableKey] = cast table + lua_pop(L, 1); // pop cast table. Stack: ns, co, cl, st, cl parents + + lua_createtable(L, get_length(L, clParentsIndex), 0); // Stack: ns, co, cl, st, cl parents, co parents + const int coParentsIndex = lua_absindex(L, -1); + lua_createtable(L, get_length(L, clParentsIndex), 0); // Stack: ns, co, cl, st, cl parents, co parents, st parents + const int stParentsIndex = lua_absindex(L, -1); + + const int parentCount = get_length(L, clParentsIndex); + for (int i = 1; i <= parentCount; ++i) + { + lua_rawgeti(L, clParentsIndex, i); // Stack: ..., st parents, parent cl + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + lua_rawgetp_x(L, -1, detail::getConstKey()); // Stack: ..., parent cl, parent co + LUABRIDGE_ASSERT(lua_istable(L, -1)); + lua_rawseti(L, coParentsIndex, i); // Stack: ..., parent cl + + lua_rawgetp_x(L, -1, detail::getStaticKey()); // Stack: ..., parent cl, parent st + LUABRIDGE_ASSERT(lua_istable(L, -1)); + lua_rawseti(L, stParentsIndex, i); // Stack: ..., parent cl + + lua_pop(L, 1); // Stack: ..., st parents + } + + lua_pushvalue(L, coParentsIndex); + lua_rawsetp_x(L, coIndex, detail::getParentKey()); + + lua_pushvalue(L, clParentsIndex); + lua_rawsetp_x(L, clIndex, detail::getParentKey()); + + lua_pushvalue(L, stParentsIndex); + lua_rawsetp_x(L, stIndex, detail::getParentKey()); + + lua_pop(L, 3); // Stack: ns, co, cl, st + + setObjectMetaMethods(-3, false); // co + setObjectMetaMethods(-2, false); // cl + setStaticMetaMethods(-1, false); // st + + lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st + lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getStaticRegistryKey()); // Stack: ns, co, cl, st + lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl + lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey()); // Stack: ns, co, cl, st + lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co + lua_rawsetp_x(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey()); // Stack: ns, co, cl, st + } + + //========================================================================================= + /** + * @brief Continue registration in the enclosing namespace. + * + * @returns A parent registration object. + */ + Namespace endClass() + { + LUABRIDGE_ASSERT(m_stackSize > 3); + + m_stackSize -= 3; + lua_pop(L, 3); + + return Namespace(std::move(*this)); + } + + //========================================================================================= + /** + * @brief Add or replace a static property. + */ + template + Class& addStaticProperty(const char* name, Getter get, bool) = delete; + + template + Class& addStaticProperty(const char* name, Getter get) + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + detail::push_property_getter(L, std::move(get), name); // Stack: co, cl, st, function + detail::add_property_getter(L, name, -2); // Stack: co, cl, st + + detail::push_property_readonly(L, name); // Stack: co, cl, st, function + detail::add_property_setter(L, name, -2); // Stack: co, cl, st + + return *this; + } + + template + Class& addStaticProperty(const char* name, Getter get, Setter set) + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + detail::push_property_getter(L, std::move(get), name); // Stack: co, cl, st, function + detail::add_property_getter(L, name, -2); // Stack: co, cl, st + + detail::push_property_setter(L, std::move(set), name); // Stack: co, cl, st, function + detail::add_property_setter(L, name, -2); // Stack: co, cl, st + + return *this; + } + + template + Class& addStaticPropertyReadWrite(const char* name, Field member) + { + return addStaticProperty(name, member, member); + } + + //========================================================================================= + /** + * @brief Add or replace a single static function or multiple overloaded functions. + * + * @param name The overload name. + * @param functions A single or set of static functions that will be invoked. + * + * @returns This class registration object. + */ + template + auto addStaticFunction(const char* name, Functions... functions) + -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + detail::push_function(L, std::move(functions), name); + + } (), ...); + } + else + { + // upvalue 1: OverloadSet (C++ struct with arity + type checker per overload) + 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) + { + 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); + + } (), ...); + + // upvalue 2: flat table of function closures indexed 1..N + lua_createtable(L, static_cast(sizeof...(Functions)), 0); + + int idx = 1; + + ([&] + { + detail::push_function(L, std::move(functions), name); + lua_rawseti(L, -2, idx++); + + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + } + + rawsetfield(L, -2, name); + + return *this; + } + + //========================================================================================= + /** + * @brief Add a static index metamethod function fallback that is triggered when no result is found in static functions, properties or any other static members. + * + * Let the user define a fallback index (__index) metamethod for the static class table. + */ + template + auto addStaticIndexMetaMethod(Function function) + -> std::enable_if_t + && std::is_invocable_v, Class&> + { + using FnType = decltype(function); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__index", 1); // Stack: co, cl, st, function + lua_rawsetp_x(L, -2, detail::getStaticIndexFallbackKey()); + setStaticMetaMethods(-1, false); + + return *this; + } + + Class& addStaticIndexMetaMethod(LuaRef (*idxf)(const LuaRef&, lua_State*)) + { + using FnType = decltype(idxf); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr + lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__index", 1); // Stack: co, cl, st, function + lua_rawsetp_x(L, -2, detail::getStaticIndexFallbackKey()); + setStaticMetaMethods(-1, false); + + return *this; + } + + //========================================================================================= + /** + * @brief Add a static new index metamethod function fallback that is triggered when no result is found in static functions, properties or any other static members. + * + * Let the user define a fallback new index (__newindex) metamethod for the static class table. + */ + template + auto addStaticNewIndexMetaMethod(Function function) + -> std::enable_if_t + && std::is_invocable_v, Class&> + { + using FnType = decltype(function); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__newindex", 1); // Stack: co, cl, st, function + lua_rawsetp_x(L, -2, detail::getStaticNewIndexFallbackKey()); + setStaticMetaMethods(-1, false); + + return *this; + } + + Class& addStaticNewIndexMetaMethod(LuaRef (*idxf)(const LuaRef&, const LuaRef&, lua_State*)) + { + using FnType = decltype(idxf); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr + lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__newindex", 1); // Stack: co, cl, st, function + lua_rawsetp_x(L, -2, detail::getStaticNewIndexFallbackKey()); + setStaticMetaMethods(-1, false); + + return *this; + } + + //========================================================================================= + /** + * @brief Add or replace a property member, by constructible by std::function. + */ + template + Class& addProperty(const char* name, Getter getter, bool) = delete; + + template + Class& addProperty(const char* name, Getter getter) + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + detail::push_class_property_getter(L, std::move(getter), name); // Stack: co, cl, st, getter + lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter + detail::add_property_getter(L, name, -4); // Stack: co, cl, st, getter + detail::add_property_getter(L, name, -4); // Stack: co, cl, st + + detail::push_property_readonly(L, name); // Stack: co, cl, st, function + detail::add_property_setter(L, name, -3); // Stack: co, cl, st + + return *this; + } + + template + Class& addProperty(const char* name, Getter getter, Setter setter) + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + detail::push_class_property_getter(L, std::move(getter), name); // Stack: co, cl, st, getter + lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter + detail::add_property_getter(L, name, -4); // Stack: co, cl, st, getter + detail::add_property_getter(L, name, -4); // Stack: co, cl, st + + detail::push_class_property_setter(L, std::move(setter), name); // Stack: co, cl, st, setter + detail::add_property_setter(L, name, -3); // Stack: co, cl, st + + return *this; + } + + template + Class& addPropertyReadWrite(const char* name, Field T::*member) + { + return addProperty(name, member, member); + } + + //========================================================================================= + /** + * @brief Add or replace a function that can operate on the class. + * + * @param name The function or overloaded functions name. + * @param functions A single or set of functions that will be invoked. + * + * @returns This class registration object. + */ + template + auto addFunction(const char* name, Functions... functions) + -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + if (name == std::string_view("__gc")) + { + throw_or_assert("__gc metamethod registration is forbidden"); +#if !LUABRIDGE_HAS_EXCEPTIONS + return *this; +#endif + } + + if constexpr ((detail::is_reversed_proxy_function_v || ...)) + { + if (!detail::is_binary_operator_metamethod(name)) + { + throw_or_assert("reversed functions are only allowed on binary operator metamethods"); + return *this; + } + } + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + detail::push_member_function(L, std::move(functions), name); + + } (), ...); + + if constexpr (detail::const_functions_count == 1) + { + lua_pushvalue(L, -1); // Stack: co, cl, st, function, function + rawsetfield(L, -4, name); // Stack: co, cl, st, function + rawsetfield(L, -4, name); // Stack: co, cl, st + } + else + { + rawsetfield(L, -3, name); // Stack: co, cl, st + } + } + else + { + // create new closure of const try_overload_functions with new table + if constexpr (detail::const_functions_count > 0) + { + // upvalue 1: OverloadSet + auto* overload_set_const_unaligned = lua_newuserdata_aligned(L); + auto* overload_set_const = align(overload_set_const_unaligned); + + ([&] + { + if (!detail::is_const_function) + return; + + detail::OverloadEntry entry; + if constexpr (detail::is_any_cfunction_pointer_v) + { + entry.arity = -1; + entry.checker = nullptr; + } + else if constexpr (detail::is_proxy_member_function_v) + { + using ArgsPack = detail::remove_first_type_t>; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + } + else if constexpr (detail::is_reversed_proxy_function_v) + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::member_function_arity_excluding_v) - 1; + entry.checker = &detail::reversed_overload_type_checker; + } + else + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + } + overload_set_const->entries.push_back(entry); + + } (), ...); + + LUABRIDGE_ASSERT(!overload_set_const->entries.empty()); + + // upvalue 2: flat table of function closures + lua_createtable(L, static_cast(detail::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); + lua_pushvalue(L, -1); // Stack: co, cl, st, function, function + rawsetfield(L, -4, name); // Stack: co, cl, st, function + rawsetfield(L, -4, name); // Stack: co, cl, st + } + + // create new closure of non const try_overload_functions with new table + if constexpr (detail::non_const_functions_count > 0) + { + // upvalue 1: OverloadSet + auto* overload_set_nonconst_unaligned = lua_newuserdata_aligned(L); + auto* overload_set_nonconst = align(overload_set_nonconst_unaligned); + + ([&] + { + if (detail::is_const_function) + return; + + detail::OverloadEntry entry; + if constexpr (detail::is_any_cfunction_pointer_v) + { + entry.arity = -1; + entry.checker = nullptr; + } + else if constexpr (detail::is_proxy_member_function_v) + { + using ArgsPack = detail::remove_first_type_t>; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + } + else if constexpr (detail::is_reversed_proxy_function_v) + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::member_function_arity_excluding_v) - 1; + entry.checker = &detail::reversed_overload_type_checker; + } + else + { + using ArgsPack = detail::function_arguments_t; + entry.arity = static_cast(detail::member_function_arity_excluding_v); + entry.checker = &detail::overload_type_checker; + } + overload_set_nonconst->entries.push_back(entry); + + } (), ...); + + LUABRIDGE_ASSERT(!overload_set_nonconst->entries.empty()); + + // upvalue 2: flat table of function closures + 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); // Stack: co, cl, st + } + } + + return *this; + } + +#if LUABRIDGE_HAS_CXX20_COROUTINES + //========================================================================================= + /** + * @brief Add a C++20 coroutine as a static function to this class. + * + * The factory must be a callable returning CppCoroutine. When Lua calls the registered + * function, a new C++ coroutine is created and run; co_yield sends values to the Lua caller + * and co_return sends the final return value. + * + * @param name The function name to register. + * @param factory A callable returning CppCoroutine. + * + * @returns This class registration object. + */ + template + auto addStaticCoroutine(const char* name, F factory) + -> std::enable_if_t, Class&> + { + LUABRIDGE_ASSERT(name != nullptr); + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + detail::push_coroutine_function(L, std::move(factory), name); + rawsetfield(L, -2, name); // Stack: co, cl, st (into st) + + return *this; + } + + //========================================================================================= + /** + * @brief Add a C++20 coroutine as a member function to this class. + * + * The factory must be a callable whose first argument is T* or const T*, followed by any + * additional arguments, and returning CppCoroutine. When Lua calls the method on an + * object, a new C++ coroutine is created with the object as the first argument; co_yield + * sends values to the Lua caller and co_return sends the final return value. + * + * If the factory takes const T* as the first argument it is registered as a const method + * (accessible on both const and non-const objects); otherwise it is registered as a + * non-const method (accessible on non-const objects only). + * + * @param name The method name to register. + * @param factory A callable taking T* or const T* as the first argument (the object + * pointer), plus optional further arguments, returning CppCoroutine. + * + * @returns This class registration object. + */ + 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(); // Stack: const table (co), class table (cl), static table (st) + + detail::push_coroutine_function(L, std::move(factory), name); + + if constexpr (detail::is_const_function) + { + lua_pushvalue(L, -1); // Stack: co, cl, st, func, func + rawsetfield(L, -4, name); // Stack: co, cl, st, func (sets in cl) + rawsetfield(L, -4, name); // Stack: co, cl, st (sets in co) + } + else + { + rawsetfield(L, -3, name); // Stack: co, cl, st (sets in cl) + } + + return *this; + } +#endif // LUABRIDGE_HAS_CXX20_COROUTINES + + //========================================================================================= + /** + * @brief Add or replace a primary Constructor. + * + * The primary Constructor is invoked when calling the class type table like a function. + * + * The template parameter should be a function pointer type that matches the desired Constructor (since you can't take the + * address of a Constructor and pass it as an argument). + */ + template + auto addConstructor() + -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> + { + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + lua_pushcclosure_x(L, &detail::constructor_placement_proxy>, className, 0); + + } (), ...); + } + else + { + // upvalue 1: OverloadSet + 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); + + } (), ...); + + // upvalue 2: flat table of function closures + 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; + } + + //========================================================================================= + /** + * @brief Add or replace a placement constructor. + * + * The primary placement constructor is invoked when calling the class type table like a function. + * + * The provider of the Function argument is responsible of doing placement new of the type T over the void* pointer provided to + * the method as first argument. + */ + 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(); // Stack: const table (co), class table (cl), static table (st) + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + using F = detail::constructor_forwarder; + + lua_newuserdata_aligned(L, F(std::move(functions))); // Stack: co, cl, st, upvalue + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function + + } (), ...); + } + else + { + // upvalue 1: OverloadSet + 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) + { + entry.arity = -1; + entry.checker = nullptr; + } + else + { + // skip void* first arg (placement new destination, not a Lua argument) + 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); + + } (), ...); + + // upvalue 2: flat table of function closures + lua_createtable(L, static_cast(sizeof...(Functions)), 0); + + int idx = 1; + + ([&] + { + using F = detail::constructor_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"); // Stack: co, cl, st + + return *this; + } + + //========================================================================================= + /** + * @brief Add or replace a primary Constructor when the type is used from an intrusive container C. + */ + template + auto addConstructorFrom() + -> std::enable_if_t<(sizeof...(Functions) > 0), Class&> + { + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + lua_pushcclosure_x(L, &detail::constructor_container_proxy>, className, 0); + + } (), ...); + } + else + { + // upvalue 1: OverloadSet + 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); + + } (), ...); + + // upvalue 2: flat table of function closures + 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; + } + + //========================================================================================= + /** + * @brief Add or replace a primary Constructor when the type is used from an intrusive container C. + * + * The provider of the Function argument is responsible of constructing the container C forwarding arguments in the callable Functions passed in. + */ + 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(); // Stack: const table (co), class table (cl), static table (st) + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + using F = detail::container_forwarder; + + lua_newuserdata_aligned(L, F(std::move(functions))); // Stack: co, cl, st, upvalue + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function + + } (), ...); + } + else + { + // upvalue 1: OverloadSet + 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) + { + 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); + + } (), ...); + + // upvalue 2: flat table of function closures + lua_createtable(L, static_cast(sizeof...(Functions)), 0); + + int idx = 1; + + ([&] + { + 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"); // Stack: co, cl, st + + 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(); // Stack: const table (co), class table (cl), static table (st) + + using F = detail::destructor_forwarder; + + lua_newuserdata_aligned(L, F(std::move(function))); // Stack: co, cl, st, upvalue + lua_pushcclosure_x(L, &detail::invoke_proxy_destructor, className, 1); // Stack: co, cl, st, function + + rawsetfield(L, -3, "__destruct"); // Stack: co, cl, st + + return *this; + } + + //========================================================================================= + /** + * @brief Add or replace a factory. + * + * The primary Constructor is invoked when calling the class type table like a function. + * + * The template parameter should be a function pointer type that matches the desired Constructor (since you can't take the + * address of a Constructor and pass it as an argument). + */ + template + Class& addFactory(Allocator allocator, Deallocator deallocator) + { + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + using F = detail::factory_forwarder; + + lua_newuserdata_aligned(L, F(std::move(allocator), std::move(deallocator))); // Stack: co, cl, st, upvalue + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, className, 1); // Stack: co, cl, st, function + rawsetfield(L, -2, "__call"); // Stack: co, cl, st + + return *this; + } + + //========================================================================================= + /** + * @brief Add an index metamethod function fallback that is triggered when no result is found in functions, properties or any other members. + * + * Let the user define a fallback index (__index) metamethod at its level. + */ + template + auto addIndexMetaMethod(Function function) + -> std::enable_if_t + && std::is_invocable_v, Class&> + { + using FnType = decltype(function); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__index", 1); // Stack: co, cl, st, function + 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(); // Stack: const table (co), class table (cl), static table (st) + + lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr + lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__index", 1); // Stack: co, cl, st, function + 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(); // Stack: const table (co), class table (cl), static table (st) + + 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; + } + + //========================================================================================= + /** + * @brief Add an insert index metamethod function fallback that is triggered when no result is found in functions, properties or any other members. + * + * Let the user define a fallback insert index (___newindex) metamethod at its level. + */ + template + auto addNewIndexMetaMethod(Function function) + -> std::enable_if_t + && std::is_invocable_v, Class&> + { + using FnType = decltype(function); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + lua_newuserdata_aligned(L, std::move(function)); // Stack: co, cl, st, function userdata (ud) + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, "__newindex", 1); // Stack: co, cl, st, function + lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); + setObjectMetaMethods(-2, false); + + return *this; + } + + Class& addNewIndexMetaMethod(LuaRef (*idxf)(T&, const LuaRef&, const LuaRef&, lua_State*)) + { + using FnType = decltype(idxf); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + lua_pushlightuserdata(L, reinterpret_cast(idxf)); // Stack: co, cl, st, function ptr + lua_pushcclosure_x(L, &detail::invoke_proxy_function, "__newindex", 1); // Stack: co, cl, st, function + lua_rawsetp_x(L, -3, detail::getNewIndexFallbackKey()); + setObjectMetaMethods(-2, false); + + return *this; + } + + Class& addNewIndexMetaMethod(LuaRef (T::* idxf)(const LuaRef&, const LuaRef&, lua_State*)) + { + using MemFnPtr = decltype(idxf); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + 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); + + return *this; + } + + //========================================================================================= + /** + * @brief Register a custom type converter from this class (T) to a target type (To). + * + * Stores a function pointer in the FROM class's (T's) Lua metatable under + * getConvertersKey() → getClassRegistryKey(), for both the class table and + * the const table. Phase 3 in Stack::get reads it back during extraction. + * + * Requirements: + * - StackConversion::enabled must be true (user specializes to opt in). + * - StackConverter must provide: static To convert(const T&). + * + * @tparam To Target type. Stack and Stack gain Phase 3 fallback. + */ + template + Class& addConverter() + { + static_assert(StackConversion::enabled, + "Specialize StackConversion with enabled=true before calling addConverter()"); + + using FnType = TypeResult(*)(lua_State*, int); + static const FnType fn = &detail::convertFromStack; + + // Stack during Class methods: ns, co, cl, st + // Store into both cl (-2) and co (-3) so both mutable and const userdatas work. + detail::getOrCreateConverterRegistry(L, lua_absindex(L, -2))->converters[detail::getClassRegistryKey()] = &fn; // cl + detail::getOrCreateConverterRegistry(L, lua_absindex(L, -3))->converters[detail::getClassRegistryKey()] = &fn; // co + + return *this; + } + }; + + class Table : public detail::Registrar + { + public: + explicit Table(const char* name, Namespace parent) + : Registrar(std::move(parent)) + { + lua_newtable(L); // Stack: ns, table (tb) + lua_pushvalue(L, -1); // Stack: ns, tb, tb + rawsetfield(L, -3, name); + ++m_stackSize; + + lua_newtable(L); // Stack: ns, table (tb) + lua_pushvalue(L, -1); // Stack: ns, tb, tb + lua_setmetatable(L, -3); // tb.__metatable = tb. Stack: ns, tb + ++m_stackSize; + } + + using Registrar::operator=; + + template + Table& addFunction(const char* name, Function function) + { + using FnType = decltype(function); + + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + lua_newuserdata_aligned(L, std::move(function)); // Stack: ns, function userdata (ud) + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); // Stack: ns, function + rawsetfield(L, -3, name); // Stack: ns + + return *this; + } + + template + Table& addMetaFunction(const char* name, Function function) + { + using FnType = decltype(function); + + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + lua_newuserdata_aligned(L, std::move(function)); // Stack: ns, function userdata (ud) + lua_pushcclosure_x(L, &detail::invoke_proxy_functor, name, 1); // Stack: ns, function + rawsetfield(L, -2, name); // Stack: ns + + return *this; + } + + Namespace endTable() + { + LUABRIDGE_ASSERT(m_stackSize > 2); + + m_stackSize -= 2; + lua_pop(L, 2); + + return Namespace(std::move(*this)); + } + }; + +private: + struct FromStack {}; + + //============================================================================================= + /** + * @brief Open the global namespace for registrations. + * + * @param L A Lua state. + */ + explicit Namespace(lua_State* L) + : Registrar(L) + { + lua_getglobal(L, "_G"); + + ++m_stackSize; + } + + //============================================================================================= + /** + * @brief Open the a namespace for registrations from a table on top of the stack. + * + * @param L A Lua state. + */ + Namespace(lua_State* L, Options options, FromStack) + : Registrar(L, 1) + { + LUABRIDGE_ASSERT(lua_istable(L, -1)); + + lua_pushvalue(L, -1); // Stack: ns, ns + + // ns.__metatable = ns + lua_setmetatable(L, -2); // Stack: ns + + // ns.__index = index_static_metamethod + lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); + rawsetfield(L, -2, "__index"); // Stack: ns + + // ns.__newindex = newindex_static_metamethod + //lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); + //rawsetfield(L, -2, "__newindex"); // Stack: pns, ns + + lua_newtable(L); // Stack: ns, mt, propget table (pg) + lua_rawsetp_x(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: ns + + lua_newtable(L); // Stack: ns, mt, propset table (ps) + lua_rawsetp_x(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: ns + + if (! options.test(visibleMetatables)) + { + lua_pushboolean(L, 0); + rawsetfield(L, -2, "__metatable"); + } + + ++m_stackSize; + } + + //============================================================================================= + /** + * @brief Open a namespace for registrations. + * + * The namespace is created if it doesn't already exist. + * + * @param name The namespace name. + * @param parent The parent namespace object. + * + * @pre The parent namespace is at the top of the Lua stack. + */ + Namespace(const char* name, Namespace parent, Options options) + : Registrar(std::move(parent)) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: parent namespace (pns) + + rawgetfield(L, -1, name); // Stack: pns, namespace (ns) | nil + + if (lua_isnil(L, -1)) // Stack: pns, nil + { + lua_pop(L, 1); // Stack: pns + + lua_newtable(L); // Stack: pns, ns + lua_pushvalue(L, -1); // Stack: pns, ns, mt + + // ns.__metatable = ns + lua_setmetatable(L, -2); // Stack: pns, ns + + // ns.__index = index_static_metamethod + lua_pushcfunction_x(L, &detail::index_metamethod, "__index"); + rawsetfield(L, -2, "__index"); // Stack: pns, ns + + // ns.__newindex = newindex_static_metamethod + lua_pushcfunction_x(L, &detail::newindex_metamethod, "__newindex"); + rawsetfield(L, -2, "__newindex"); // Stack: pns, ns + + lua_newtable(L); // Stack: pns, ns, propget table (pg) + lua_rawsetp_x(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: pns, ns + + lua_newtable(L); // Stack: pns, ns, propset table (ps) + lua_rawsetp_x(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: pns, ns + + if (! options.test(visibleMetatables)) + { + lua_pushboolean(L, 0); + rawsetfield(L, -2, "__metatable"); + } + + // pns [name] = ns + lua_pushvalue(L, -1); // Stack: pns, ns, ns + rawsetfield(L, -3, name); // Stack: pns, ns + } + + ++m_stackSize; + } + + //============================================================================================= + /** + * @brief Close the class and continue the namespace registrations. + * + * @param child A child class registration object. + */ + explicit Namespace(ClassBase child) + : Registrar(std::move(child)) + { + } + + explicit Namespace(Table child) + : Registrar(std::move(child)) + { + } + + using Registrar::operator=; + +public: + //============================================================================================= + /** + * @brief Retrieve the global namespace. + * + * It is recommended to put your namespace inside the global namespace, and then add your classes and functions to it, rather than + * adding many classes and functions directly to the global namespace. + * + * @param L A Lua state. + * + * @returns A namespace registration object. + */ + static Namespace getGlobalNamespace(lua_State* L) + { + return Namespace(L); + } + + /** + * @brief Retrieve the namespace on top of the stack. + * + * You should have a table on top of the stack before calling this function. It will then use the table there as destination for registrations. + * + * @param L A Lua state. + * + * @returns A namespace registration object. + */ + static Namespace getNamespaceFromStack(lua_State* L, Options options = defaultOptions) + { + return Namespace(L, options, FromStack{}); + } + + //============================================================================================= + /** + * @brief Open a new or existing namespace for registrations. + * + * @param name The namespace name. + * + * @returns A namespace registration object. + */ + Namespace beginNamespace(const char* name, Options options = defaultOptions) + { + assertIsActive(); + return Namespace(name, std::move(*this), options); + } + + //============================================================================================= + /** + * @brief Continue namespace registration in the parent. + * + * Do not use this on the global namespace. + * + * @returns A parent namespace registration object. + */ + Namespace endNamespace() + { + if (m_stackSize == 1) + { + throw_or_assert("endNamespace() called on global namespace"); + +#if !LUABRIDGE_HAS_EXCEPTIONS + return Namespace(std::move(*this)); +#endif + } + + LUABRIDGE_ASSERT(m_stackSize > 1); + --m_stackSize; + lua_pop(L, 1); + + return Namespace(std::move(*this)); + } + + //============================================================================================= + /** + * @brief Add or replace a variable that will be added in the namespace by copy of the passed value. + * + * @param name The variable name. + * @param value A value object. + * + * @returns This namespace registration object. + */ + template + Namespace& addVariable(const char* name, const T& value) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + if constexpr (std::is_enum_v) + { + using U = std::underlying_type_t; + + 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()); + } + + rawsetfield(L, -2, name); // Stack: ns + + return *this; + } + + //============================================================================================= + /** + * @brief Add or replace a readonly property. + * + * @param name The property name. + * @param get A pointer to a property getter function. + * + * @returns This namespace registration object. + */ + template + Namespace& addProperty(const char* name, Getter getter) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + if (! checkTableHasPropertyGetter()) + { + throw_or_assert("addProperty() called on global namespace"); + +#if !LUABRIDGE_HAS_EXCEPTIONS + return *this; +#endif + } + + detail::push_property_getter(L, std::move(getter), name); // Stack: ns, getter + detail::add_property_getter(L, name, -2); // Stack: ns, getter + + detail::push_property_readonly(L, name); // Stack: ns, function + detail::add_property_setter(L, name, -2); // Stack: ns + + return *this; + } + + /** + * @brief Add or replace a mutable property. + * + * @param name The property name. + * @param get A pointer to a property getter function. + * @param set A pointer to a property setter function. + * + * @returns This namespace registration object. + */ + template + Namespace& addProperty(const char* name, Getter getter, Setter setter) + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + if (! checkTableHasPropertyGetter()) + { + throw_or_assert("addProperty() called on global namespace"); + +#if !LUABRIDGE_HAS_EXCEPTIONS + return *this; +#endif + } + + detail::push_property_getter(L, std::move(getter), name); // Stack: ns, getter + detail::add_property_getter(L, name, -2); // Stack: ns + + detail::push_property_setter(L, std::move(setter), name); // Stack: ns, setter + detail::add_property_setter(L, name, -2); // Stack: ns + + return *this; + } + + //============================================================================================= + /** + * @brief Add or replace a single function or multiple overloaded functions. + * + * @param name The overload name. + * @param functions A single or set of functions that will be invoked. + * + * @returns This namespace registration object. + */ + 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)); // Stack: namespace table (ns) + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + detail::push_function(L, std::move(functions), name); + + } (), ...); + } + else + { + // upvalue 1: OverloadSet (C++ struct with arity + type checker per overload) + 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) + { + 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); + + } (), ...); + + // upvalue 2: flat table of function closures indexed 1..N + lua_createtable(L, static_cast(sizeof...(Functions)), 0); + + int idx = 1; + + ([&] + { + detail::push_function(L, std::move(functions), name); + lua_rawseti(L, -2, idx++); + + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, name, 2); + } + + rawsetfield(L, -2, name); + + return *this; + } + +#if LUABRIDGE_HAS_CXX20_COROUTINES + //============================================================================================= + /** + * @brief Add a C++20 coroutine function to this namespace. + * + * The factory must be a callable returning CppCoroutine. When Lua calls the registered + * function, a new C++ coroutine is created and run; co_yield sends values to the Lua caller + * and co_return sends the final return value. + * + * @param name The function name to register. + * @param factory A callable returning CppCoroutine. + * + * @returns This namespace registration object. + */ + template + auto addCoroutine(const char* name, F factory) + -> std::enable_if_t, Namespace&> + { + LUABRIDGE_ASSERT(name != nullptr); + LUABRIDGE_ASSERT(lua_istable(L, -1)); // Stack: namespace table (ns) + + detail::push_coroutine_function(L, std::move(factory), name); + rawsetfield(L, -2, name); + + return *this; + } +#endif // LUABRIDGE_HAS_CXX20_COROUTINES + + //============================================================================================= + /** + * @brief Open a new or existing table as namespace for registrations. + * + * @param name The table name. + * + * @returns A table registration object. + */ + Table beginTable(const char* name) + { + assertIsActive(); + return Table(name, std::move(*this)); + } + + //============================================================================================= + /** + * @brief Open a new or existing class for registrations. + * + * @param name The class name. + * @param options The class options. + * + * @returns A class registration object. + */ + template + Class beginClass(const char* name, Options options = defaultOptions) + { + assertIsActive(); + return Class(name, std::move(*this), options); + } + + //============================================================================================= + /** + * @brief Derive a new class for registrations. + * + * Call deriveClass() only once. To continue registrations for the class later, use beginClass(). + * + * @param name The class name. + * @param options The class options. + * + * @returns A class registration object. + */ + 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"); + + 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: + /** + * @brief Checks if the table has a property getter metatable. + */ + bool checkTableHasPropertyGetter() const + { + if (m_stackSize == 1 && lua_istable(L, -1)) + { + lua_rawgetp_x(L, -1, detail::getPropgetKey()); + const bool propertyGetterTableIsValid = lua_istable(L, -1); + lua_pop(L, 1); + + return propertyGetterTableIsValid; + } + + return true; + } +}; + +//================================================================================================= +/** + * @brief Retrieve the global namespace. + * + * It is recommended to put your namespace inside the global namespace, and then add your classes and functions to it, rather than adding + * many classes and functions directly to the global namespace. + * + * @param L A Lua state. + * + * @returns A namespace registration object. + */ +inline Namespace getGlobalNamespace(lua_State* L) +{ + return Namespace::getGlobalNamespace(L); +} + +//================================================================================================= +/** + * @brief Retrieve the namespace on top of the stack. + * + * You should have a table on top of the stack before calling this function. It will then use the table there as destination for registrations. + * + * @param L A Lua state. + * + * @returns A namespace registration object. + */ +inline Namespace getNamespaceFromStack(lua_State* L) +{ + return Namespace::getNamespaceFromStack(L); +} + +//================================================================================================= +/** + * @brief Registers main thread. + * + * This is a backward compatibility mitigation for lua 5.1 not supporting LUA_RIDX_MAINTHREAD. + * + * @param L The main Lua state that will be registered as main thread. + * + * @returns A namespace registration object. + */ +inline void registerMainThread(lua_State* L) +{ + register_main_thread(L); +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Options.h b/external/include/LuaBridge3/LuaBridge/detail/Options.h new file mode 100644 index 0000000000..63a8267bf8 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Options.h @@ -0,0 +1,50 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2023, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "FlagSet.h" + +#include + +namespace luabridge { + +//================================================================================================= +namespace detail { +struct OptionExtensibleClass; +struct OptionAllowOverridingMethods; +struct OptionVisibleMetatables; +} // namespace Detail + +/** + * @brief Options for the library. + */ +using Options = FlagSet; + +/** + * @brief Set of default options. + * + * This setting means all options are not enabled. + */ +static inline constexpr Options defaultOptions = Options(); + +/** + * @brief Enable extensible C++ classes when registering them. + */ +static inline constexpr Options extensibleClass = Options::Value(); + +/** + * @brief Allow to be able to override methods from lua in extensible C++ classes. + */ +static inline constexpr Options allowOverridingMethods = Options::Value(); + +/** + * @brief Specify if metatables are visible for namespaces and classes. + */ +static inline constexpr Options visibleMetatables = Options::Value(); + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Overload.h b/external/include/LuaBridge3/LuaBridge/detail/Overload.h new file mode 100644 index 0000000000..54bff00064 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Overload.h @@ -0,0 +1,84 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2020, Dmitry Tarakanov +// Copyright 2019, George Tokmaji +// Copyright 2012, Vinnie Falco +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "Errors.h" +#include "Stack.h" +#include "TypeTraits.h" +#include "Userdata.h" + +#include +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Overloaded objects. + */ +template +struct NonConstOverload +{ + template + constexpr auto operator()(R (T::*ptr)(Args...)) const noexcept -> decltype(ptr) + { + return ptr; + } + + template + static constexpr auto with(R (T::*ptr)(Args...)) noexcept -> decltype(ptr) + { + return ptr; + } +}; + +template +struct ConstOverload +{ + template + constexpr auto operator()(R (T::*ptr)(Args...) const) const noexcept -> decltype(ptr) + { + return ptr; + } + + template + static constexpr auto with(R (T::*ptr)(Args...) const) noexcept -> decltype(ptr) + { + return ptr; + } +}; + +template +struct Overload : ConstOverload, NonConstOverload +{ + using ConstOverload::operator(); + using NonConstOverload::operator(); + + template + constexpr auto operator()(R (*ptr)(Args...)) const noexcept -> decltype(ptr) + { + return ptr; + } + + template + static constexpr auto with(R (T::*ptr)(Args...)) noexcept -> decltype(ptr) + { + return ptr; + } +}; + +//================================================================================================= +/** + * @brief Overload resolution. + */ +template [[maybe_unused]] constexpr Overload overload = {}; +template [[maybe_unused]] constexpr ConstOverload constOverload = {}; +template [[maybe_unused]] constexpr NonConstOverload nonConstOverload = {}; + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/Result.h b/external/include/LuaBridge3/LuaBridge/detail/Result.h new file mode 100644 index 0000000000..6f59c331e8 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Result.h @@ -0,0 +1,257 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2022, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Errors.h" + +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Simple result class containing a result. + */ +struct Result +{ + Result() noexcept = default; + + Result(std::error_code ec) noexcept + : m_ec(ec) + { + } + + Result(const Result&) noexcept = default; + Result(Result&&) noexcept = default; + Result& operator=(const Result&) noexcept = default; + Result& operator=(Result&&) noexcept = default; + + explicit operator bool() const noexcept + { + return !m_ec; + } + + std::error_code error() const noexcept + { + return m_ec; + } + + const char* error_cstr() const noexcept + { + return detail::ErrorCategory::errorString(m_ec.value()); + } + + operator std::error_code() const noexcept + { + return m_ec; + } + + std::string message() const + { + return m_ec.message(); + } + +#if LUABRIDGE_HAS_EXCEPTIONS + void throw_on_error() const + { + if (m_ec) + throw std::system_error(m_ec); + } +#endif + +private: + std::error_code m_ec; +}; + +//================================================================================================= +/** + * @brief Simple result class containing or a type T or an error code. + */ +template +struct TypeResult +{ + TypeResult() noexcept = default; + + template && !std::is_same_v, std::error_code>>> + TypeResult(U&& value) noexcept + : m_value(std::in_place, std::forward(value)) + { + } + + TypeResult(std::error_code ec) noexcept + : m_value(makeUnexpected(ec)) + { + } + + TypeResult(const TypeResult&) = default; + TypeResult(TypeResult&&) = default; + TypeResult& operator=(const TypeResult&) = default; + TypeResult& operator=(TypeResult&&) = default; + + explicit operator bool() const noexcept + { + return m_value.hasValue(); + } + + const T& value() const + { + return m_value.value(); + } + + T& operator*() & + { + return m_value.value(); + } + + T operator*() && + { + return std::move(m_value.value()); + } + + const T& operator*() const& + { + return m_value.value(); + } + + T operator*() const&& + { + 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& + { + return m_value.valueOr(std::forward(defaultValue)); + } + + template + T valueOr(U&& defaultValue) && + { + return m_value.valueOr(std::forward(defaultValue)); + } + + std::error_code error() const + { + return m_value.error(); + } + + const char* error_cstr() const noexcept + { + return detail::ErrorCategory::errorString(m_value.error().value()); + } + + operator std::error_code() const + { + return m_value.error(); + } + + std::string message() const + { + return m_value.error().message(); + } + +#if LUABRIDGE_HAS_EXCEPTIONS + void throw_on_error() const + { + if (! m_value.hasValue()) + throw std::system_error(m_value.error()); + } +#endif + +private: + Expected m_value; +}; + +template <> +struct TypeResult +{ + TypeResult() noexcept = default; + + TypeResult(std::error_code ec) noexcept + : m_ec(ec) + { + } + + TypeResult(const TypeResult&) noexcept = default; + TypeResult(TypeResult&&) noexcept = default; + TypeResult& operator=(const TypeResult&) noexcept = default; + TypeResult& operator=(TypeResult&&) noexcept = default; + + explicit operator bool() const noexcept + { + return ! m_ec; + } + + void value() const noexcept + { + } + + std::error_code error() const noexcept + { + return m_ec; + } + + const char* error_cstr() const noexcept + { + return detail::ErrorCategory::errorString(m_ec.value()); + } + + operator std::error_code() const noexcept + { + return m_ec; + } + + std::string message() const + { + return m_ec.message(); + } + +#if LUABRIDGE_HAS_EXCEPTIONS + void throw_on_error() const + { + if (m_ec) + throw std::system_error(m_ec); + } +#endif + +private: + std::error_code m_ec; +}; + +template +inline bool operator==(const TypeResult& lhs, const U& rhs) noexcept +{ + return lhs ? *lhs == rhs : false; +} + +template +inline bool operator==(const U& lhs, const TypeResult& rhs) noexcept +{ + return rhs == lhs; +} + +template +inline bool operator!=(const TypeResult& lhs, const U& rhs) noexcept +{ + return !(lhs == rhs); +} + +template +inline bool operator!=(const U& lhs, const TypeResult& rhs) noexcept +{ + return !(rhs == lhs); +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/ScopeGuard.h b/external/include/LuaBridge3/LuaBridge/detail/ScopeGuard.h new file mode 100644 index 0000000000..057fb79fb0 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/ScopeGuard.h @@ -0,0 +1,47 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2021, kunitoki +// Copyright 2012, Vinnie Falco +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" +#include "Stack.h" + +namespace luabridge::detail { + +//================================================================================================= +/** + * @brief Scope guard. + */ +template +class ScopeGuard +{ +public: + template + explicit ScopeGuard(V&& v) + : m_func(std::forward(v)) + , m_shouldRun(true) + { + } + + ~ScopeGuard() + { + if (m_shouldRun) + m_func(); + } + + void reset() noexcept + { + m_shouldRun = false; + } + +private: + F m_func; + bool m_shouldRun; +}; + +template +ScopeGuard(F&&) -> ScopeGuard; + +} // namespace luabridge::detail diff --git a/external/include/LuaBridge3/LuaBridge/detail/Stack.h b/external/include/LuaBridge3/LuaBridge/detail/Stack.h new file mode 100644 index 0000000000..94517c9e5e --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/Stack.h @@ -0,0 +1,1726 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2019, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// Copyright 2007, Nathan Reed +// SPDX-License-Identifier: MIT + +#pragma once + +#include "LuaHelpers.h" +#include "Errors.h" +#include "Expected.h" +#include "Result.h" +#include "Userdata.h" +#include "Converter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LUABRIDGE_HAS_CXX17_FILESYSTEM +#include +#endif + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack restorer. + */ +class StackRestore final +{ +public: + StackRestore(lua_State* L) + : m_L(L) + , m_stackTop(lua_gettop(L)) + { + } + + ~StackRestore() + { + if (m_doRestoreStack) + lua_settop(m_L, m_stackTop); + } + + void reset() + { + m_doRestoreStack = false; + } + +private: + lua_State* const m_L = nullptr; + int m_stackTop = 0; + bool m_doRestoreStack = true; +}; + +//================================================================================================= +/** + * @brief Lua stack traits for C++ types. + * + * @tparam T A C++ type. + */ +template +struct Stack; + +//================================================================================================= +/** + * @brief Specialization for void type. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State*) + { + return {}; + } +}; + +//================================================================================================= +/** + * @brief Specialization for nullptr_t. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, std::nullptr_t) + { +#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) + { + if (! lua_isnil(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return nullptr; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_isnil(L, index); + } +}; + +//================================================================================================= +/** + * @brief Receive the lua_State* as an argument. + */ +template <> +struct Stack +{ + [[nodiscard]] static TypeResult get(lua_State* L, int) + { + return L; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for a lua_CFunction. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, lua_CFunction f) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushcfunction_x(L, f, ""); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (! lua_iscfunction(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return lua_tocfunction(L, index); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_iscfunction(L, index) != 0; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `bool`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, bool value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushboolean(L, value ? 1 : 0); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { +#if LUABRIDGE_STRICT_STACK_CONVERSIONS + if (lua_type(L, index) != LUA_TBOOLEAN) + return makeErrorCode(ErrorCode::InvalidTypeCast); +#endif + + return lua_toboolean(L, index) ? true : false; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_isboolean(L, index); + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `std::byte`. + */ +template <> +struct Stack +{ + static_assert(sizeof(std::byte) < sizeof(lua_Integer)); + + [[nodiscard]] static Result push(lua_State* L, std::byte value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + pushunsigned(L, std::to_integer>(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `char`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, char value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlstring(L, &value, 1); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TSTRING) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + std::size_t length = 0; + const char* str = lua_tolstring(L, index, &length); + + if (str == nullptr || length != 1) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return str[0]; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TSTRING) + { + std::size_t len = 0; + lua_tolstring(L, index, &len); + return len == 1; + } + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `int8_t`. + */ +#if !defined(__BORLANDC__) +template <> +struct Stack +{ + static_assert(sizeof(int8_t) < sizeof(lua_Integer)); + + [[nodiscard]] static Result push(lua_State* L, int8_t value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushinteger(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; +#endif + +//================================================================================================= +/** + * @brief Stack specialization for `unsigned char`. + */ +template <> +struct Stack +{ + static_assert(sizeof(unsigned char) < sizeof(lua_Integer)); + + [[nodiscard]] static Result push(lua_State* L, unsigned char value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + pushunsigned(L, value); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `short`. + */ +template <> +struct Stack +{ + static_assert(sizeof(short) < sizeof(lua_Integer)); + + [[nodiscard]] static Result push(lua_State* L, short value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushinteger(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `unsigned short`. + */ +template <> +struct Stack +{ + static_assert(sizeof(unsigned short) < sizeof(lua_Integer)); + + [[nodiscard]] static Result push(lua_State* L, unsigned short value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + pushunsigned(L, value); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `int`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, int value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + lua_pushinteger(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `unsigned int`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, unsigned int value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + pushunsigned(L, value); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `long`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, long value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + lua_pushinteger(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `unsigned long`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, unsigned long value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + pushunsigned(L, value); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `long long`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, long long value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + lua_pushinteger(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `unsigned long long`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, unsigned long long value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + pushunsigned(L, value); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by(L, index); + + return false; + } +}; + +#if 0 // defined(__SIZEOF_INT128__) +//================================================================================================= +/** + * @brief Stack specialization for `__int128_t`. + */ +template <> +struct Stack<__int128_t> +{ + [[nodiscard]] static Result push(lua_State* L, __int128_t value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + lua_pushinteger(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult<__int128_t> get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by<__int128_t>(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast<__int128_t>(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by<__int128_t>(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `__uint128_t`. + */ +template <> +struct Stack<__uint128_t> +{ + [[nodiscard]] static Result push(lua_State* L, __uint128_t value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_integral_representable_by(value)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + lua_pushinteger(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult<__uint128_t> get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_integral_representable_by<__uint128_t>(L, index)) + return makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger); + + return static_cast<__uint128_t>(lua_tointeger(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_integral_representable_by<__uint128_t>(L, index); + + return false; + } +}; +#endif + +//================================================================================================= +/** + * @brief Stack specialization for `float`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, float value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_floating_point_representable_by(value)) + return makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber); + + lua_pushnumber(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_floating_point_representable_by(L, index)) + return makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber); + + return static_cast(lua_tonumber(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_floating_point_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `double`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, double value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_floating_point_representable_by(value)) + return makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber); + + lua_pushnumber(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_floating_point_representable_by(L, index)) + return makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber); + + return static_cast(lua_tonumber(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_floating_point_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `long double`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, long double value) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (! is_floating_point_representable_by(value)) + return makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber); + + lua_pushnumber(L, static_cast(value)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_type(L, index) != LUA_TNUMBER) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (! is_floating_point_representable_by(L, index)) + return makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber); + + return static_cast(lua_tonumber(L, index)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + if (lua_type(L, index) == LUA_TNUMBER) + return is_floating_point_representable_by(L, index); + + return false; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `const char*`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const char* str) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + if (str != nullptr) + lua_pushstring(L, str); + else + lua_pushnil(L); + + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + const bool isNil = lua_isnil(L, index); + + if (!isNil && lua_type(L, index) != LUA_TSTRING) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (isNil) + return nullptr; + + std::size_t length = 0; + const char* str = lua_tolstring(L, index, &length); + if (str == nullptr) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return str; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_isnil(L, index) || lua_type(L, index) == LUA_TSTRING; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `std::string_view`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, std::string_view str) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlstring(L, str.data(), str.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 length = 0; + const char* str = lua_tolstring(L, index, &length); + if (str == nullptr) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return std::string_view{ str, length }; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TSTRING; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `std::string`. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const std::string& str) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlstring(L, str.data(), str.size()); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + std::size_t length = 0; + const char* str = nullptr; + + if (lua_type(L, index) == LUA_TSTRING) + { + str = lua_tolstring(L, index, &length); + } +#if !LUABRIDGE_STRICT_STACK_CONVERSIONS + else + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + // Lua reference manual: + // If the value is a number, then lua_tolstring also changes the actual value in the stack + // to a string. (This change confuses lua_next when lua_tolstring is applied to keys during + // a table traversal) + lua_pushvalue(L, index); + str = lua_tolstring(L, -1, &length); + lua_pop(L, 1); + } +#endif + + if (str == nullptr) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + return std::string{ str, length }; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TSTRING; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `std::optional`. + */ +template +struct Stack> +{ + using Type = std::optional; + + [[nodiscard]] static Result push(lua_State* L, const Type& value) + { + if (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 std::nullopt; + + auto result = Stack::get(L, index); + if (! result) + return result.error(); + + return std::make_optional(std::move(*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 `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`. + */ +template +struct Stack> +{ + [[nodiscard]] static Result push(lua_State* L, const std::pair& t) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, 2, 0); + + auto result1 = push_element<0>(L, t); + if (! result1) + return result1; + + auto result2 = push_element<1>(L, t); + if (! result2) + return result2; + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult> get(lua_State* L, int index) + { + const StackRestore stackRestore(L); + + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (get_length(L, index) != 2) + return makeErrorCode(ErrorCode::InvalidTableSizeInCast); + + std::pair value; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + auto result1 = pop_element<0>(L, absIndex, value); + if (! result1) + return result1.error(); + + auto result2 = pop_element<1>(L, absIndex, value); + if (! result2) + return result2.error(); + + return value; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TTABLE && get_length(L, index) == 2; + } + +private: + template + static Result push_element(lua_State* L, const std::pair& p) + { + static_assert(Index < 2); + + using T = std::tuple_element_t>; + + lua_pushinteger(L, static_cast(Index + 1)); + + auto result = Stack::push(L, std::get(p)); + if (! result) + return result; + + lua_settable(L, -3); + + return {}; + } + + template + static Result pop_element(lua_State* L, int absIndex, std::pair& p) + { + static_assert(Index < 2); + + using T = std::tuple_element_t>; + + if (lua_next(L, absIndex) == 0) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto result = Stack::get(L, -1); + if (! result) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + std::get(p) = std::move(*result); + lua_pop(L, 1); + + return {}; + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `std::tuple`. + */ +template +struct Stack> +{ + [[nodiscard]] static Result push(lua_State* L, const std::tuple& t) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 3)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(Size), 0); + + auto result = push_element(L, t); + if (! result) + return result; + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static TypeResult> get(lua_State* L, int index) + { + const StackRestore stackRestore(L); + + if (!lua_istable(L, index)) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + if (get_length(L, index) != static_cast(Size)) + return makeErrorCode(ErrorCode::InvalidTableSizeInCast); + + std::tuple value; + + int absIndex = lua_absindex(L, index); + lua_pushnil(L); + + auto result = pop_element(L, absIndex, value); + if (! result) + return result.error(); + + return value; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TTABLE && get_length(L, index) == static_cast(Size); + } + +private: + static constexpr std::size_t Size = std::tuple_size_v>; + + template + static auto push_element(lua_State*, const std::tuple&) + -> std::enable_if_t + { + return {}; + } + + template + static auto push_element(lua_State* L, const std::tuple& t) + -> std::enable_if_t + { + using T = std::tuple_element_t>; + + lua_pushinteger(L, static_cast(Index + 1)); + + auto result = Stack::push(L, std::get(t)); + if (! result) + return result; + + lua_settable(L, -3); + + return push_element(L, t); + } + + template + static auto pop_element(lua_State*, int, std::tuple&) + -> std::enable_if_t + { + return {}; + } + + template + static auto pop_element(lua_State* L, int absIndex, std::tuple& t) + -> std::enable_if_t + { + using T = std::tuple_element_t>; + + if (lua_next(L, absIndex) == 0) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + auto result = Stack::get(L, -1); + if (! result) + return makeErrorCode(ErrorCode::InvalidTypeCast); + + std::get(t) = std::move(*result); + lua_pop(L, 1); + + return pop_element(L, absIndex, t); + } +}; + +//================================================================================================= +/** + * @brief Stack specialization for `T[N]`. + */ +template +struct Stack +{ + static_assert(N > 0, "Unsupported zero sized array"); + + [[nodiscard]] static Result push(lua_State* L, const T (&value)[N]) + { + if constexpr (std::is_same_v) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlstring(L, value, N - 1); + return {}; + } + +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 2)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + StackRestore stackRestore(L); + + lua_createtable(L, static_cast(N), 0); + + for (std::size_t i = 0; i < N; ++i) + { + lua_pushinteger(L, static_cast(i + 1)); + + auto result = Stack::push(L, value[i]); + if (! result) + return result; + + lua_settable(L, -3); + } + + stackRestore.reset(); + return {}; + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_type(L, index) == LUA_TTABLE && get_length(L, index) == static_cast(N); + } +}; + +//================================================================================================= +/** + * @brief Specialization for `void*` and `const void*` type. + */ +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, void* ptr) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlightuserdata(L, ptr); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_isnil(L, index)) + return nullptr; + + if (lua_islightuserdata(L, index)) + return lua_touserdata(L, index); + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_islightuserdata(L, index) || lua_isnil(L, index); + } +}; + +template <> +struct Stack +{ + [[nodiscard]] static Result push(lua_State* L, const void* ptr) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushlightuserdata(L, const_cast(ptr)); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_isnil(L, index)) + return nullptr; + + if (lua_islightuserdata(L, index)) + return lua_touserdata(L, index); + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_islightuserdata(L, index) || lua_isnil(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 + +//================================================================================================= + +namespace detail { + +template +struct StackOpSelector +{ + using ReturnType = TypeResult; + + static Result push(lua_State* L, T* value) + { + return value ? Stack::push(L, *value) : Stack::push(L, nullptr); + } + + static ReturnType get(lua_State* L, int index) { return Stack::get(L, index); } + + template + static bool isInstance(lua_State* L, int index) { return Stack::isInstance(L, index); } +}; + +template +struct StackOpSelector +{ + using ReturnType = TypeResult; + + static Result push(lua_State* L, const T* value) + { + return value ? Stack::push(L, *value) : Stack::push(L, nullptr); + } + + static ReturnType get(lua_State* L, int index) { return Stack::get(L, index); } + + template + static bool isInstance(lua_State* L, int index) { return Stack::isInstance(L, index); } +}; + +template +struct StackOpSelector +{ + using ReturnType = TypeResult; + + static Result push(lua_State* L, T& value) { return Stack::push(L, value); } + + static ReturnType get(lua_State* L, int index) { return Stack::get(L, index); } + + template + static bool isInstance(lua_State* L, int index) { return Stack::isInstance(L, index); } +}; + +template +struct StackOpSelector +{ + using ReturnType = TypeResult; + + static Result push(lua_State* L, const T& value) { return Stack::push(L, value); } + + static ReturnType get(lua_State* L, int index) { return Stack::get(L, index); } + + template + static bool isInstance(lua_State* L, int index) { return Stack::isInstance(L, index); } +}; + +} // namespace detail + +template +struct Stack +{ + using Helper = detail::StackOpSelector::value>; + using ReturnType = typename Helper::ReturnType; + + [[nodiscard]] static Result push(lua_State* L, T* value) { return Helper::push(L, value); } + + [[nodiscard]] static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::isInstance(L, index); } +}; + +template +struct Stack +{ + using Helper = detail::StackOpSelector::value>; + using ReturnType = typename Helper::ReturnType; + + [[nodiscard]] static Result push(lua_State* L, const T* value) { return Helper::push(L, value); } + + [[nodiscard]] static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::isInstance(L, index); } +}; + +template +struct Stack && !std::is_const_v>> +{ + using Helper = detail::StackOpSelector::value>; + using ReturnType = typename Helper::ReturnType; + + [[nodiscard]] static Result push(lua_State* L, T& value) { return Helper::push(L, value); } + + [[nodiscard]] static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::isInstance(L, index); } +}; + +template +struct Stack && !StackConversion::enabled>> +{ + using Helper = detail::StackOpSelector::value>; + using ReturnType = typename Helper::ReturnType; + + [[nodiscard]] static Result push(lua_State* L, const T& value) { return Helper::push(L, value); } + + [[nodiscard]] static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::isInstance(L, index); } +}; + +template +struct Stack::enabled && !std::is_array_v>> +{ + using ReturnType = TypeResult>; + + [[nodiscard]] static Result push(lua_State* L, const T& value) + { + return Stack::push(L, value); + } + + [[nodiscard]] static ReturnType get(lua_State* L, int index) + { + if (detail::Userdata::isInstance(L, index)) + { + auto result = detail::Userdata::get(L, index, true); + if (!result) + return result.error(); + + if (T* ptr = *result) + return detail::ConverterConstRef(*ptr); + + return detail::getNilBadArgError(L, index); + } + + auto converted = detail::tryConvertFromRegisteredConverter(L, index); + if (!converted) + return converted.error(); + + return detail::ConverterConstRef(std::move(*converted)); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return Stack::isInstance(L, index); + } +}; + +template +struct Stack::enabled>> +{ + using IsUserdata = void; + using ReturnType = TypeResult; + + [[nodiscard]] static Result push(lua_State* L, const T& value) + { + return detail::StackHelper::value>::push(L, value); + } + + [[nodiscard]] static Result push(lua_State* L, T&& value) + { + return detail::StackHelper::value>::push(L, std::move(value)); + } + + [[nodiscard]] static ReturnType get(lua_State* L, int index) + { + if (detail::Userdata::isInstance(L, index)) + { + auto result = detail::Userdata::get(L, index, true); + if (result) + { + if (T* ptr = *result) + return *ptr; + } + return result.error(); + } + + return detail::tryConvertFromRegisteredConverter(L, index); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return detail::Userdata::isInstance(L, index); + } +}; + +//================================================================================================= +/** + * @brief Push an object onto the Lua stack. + */ +template +[[nodiscard]] Result push(lua_State* L, const T& t) +{ + return Stack::push(L, t); +} + +//================================================================================================= +/** + * @brief Get an object from the Lua stack. + */ +template +[[nodiscard]] TypeResult get(lua_State* L, int index) +{ + return Stack::get(L, index); +} + +//================================================================================================= +/** + * @brief Check whether an object on the Lua stack is of type T. + */ +template +[[nodiscard]] bool isInstance(lua_State* L, int index) +{ + return Stack::isInstance(L, index); +} + +} // namespace luabridge diff --git a/external/include/LuaBridge3/LuaBridge/detail/TypeTraits.h b/external/include/LuaBridge3/LuaBridge/detail/TypeTraits.h new file mode 100644 index 0000000000..58f895af31 --- /dev/null +++ b/external/include/LuaBridge3/LuaBridge/detail/TypeTraits.h @@ -0,0 +1,159 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2020, kunitoki +// Copyright 2019, Dmitry Tarakanov +// Copyright 2012, Vinnie Falco +// SPDX-License-Identifier: MIT + +#pragma once + +#include "Config.h" + +#include +#include + +namespace luabridge { +namespace detail { +template