// 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); } template inline void* lua_newuserdata_x(lua_State* L, size_t sz) { return lua_newuserdatadtor(L, sz, [](void* x) { T* object = static_cast(x); object->~T(); }); } 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); } 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; } /** * @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(L, maximum_space_needed_to_align(), [](void* x) { U* aligned = align(x); aligned->~U(); }); #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