// https://github.com/vinniefalco/LuaBridge // Copyright 2019, Dmitry Tarakanov // Copyright 2012, Vinnie Falco // SPDX-License-Identifier: MIT #pragma once #include #include #include namespace luabridge { namespace detail { // We use a structure so we can define everything in the header. // struct CFunc { static void addGetter(lua_State* L, const char* name, int tableIndex) { assert(lua_istable(L, tableIndex)); assert(lua_iscfunction(L, -1)); // Stack: getter lua_rawgetp(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: - } static void addSetter(lua_State* L, const char* name, int tableIndex) { assert(lua_istable(L, tableIndex)); assert(lua_iscfunction(L, -1)); // Stack: setter lua_rawgetp(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: - } //---------------------------------------------------------------------------- /** __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. */ static int indexMetaMethod(lua_State* L) { 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) assert(lua_istable(L, -1)); for (;;) { lua_pushvalue(L, 2); // Stack: mt, field name lua_rawget(L, -2); // Stack: mt, field | nil if (lua_iscfunction(L, -1)) // Stack: mt, field { lua_remove(L, -2); // Stack: field return 1; } assert(lua_isnil(L, -1)); // Stack: mt, nil lua_pop(L, 1); // Stack: mt lua_rawgetp(L, -1, getPropgetKey()); // Stack: mt, propget table (pg) 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; } assert(lua_isnil(L, -1)); // Stack: mt, nil lua_pop(L, 1); // Stack: mt // It may mean that the field may be in const table and it's constness violation. // Don't check that, just return nil // Repeat the lookup in the parent metafield, // or return nil if the field doesn't exist. lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil if (lua_isnil(L, -1)) // Stack: mt, nil { lua_remove(L, -2); // Stack: nil return 1; } // Removethe metatable and repeat the search in the parent one. assert(lua_istable(L, -1)); // Stack: mt, parent mt lua_remove(L, -2); // Stack: parent mt } // no return } //---------------------------------------------------------------------------- /** __newindex metamethod for namespace or class static members. Retrieves properties from propset tables. */ static int newindexStaticMetaMethod(lua_State* L) { return newindexMetaMethod(L, false); } //---------------------------------------------------------------------------- /** __newindex metamethod for non-static members. Retrieves properties from propset tables. */ static int newindexObjectMetaMethod(lua_State* L) { return newindexMetaMethod(L, true); } static int newindexMetaMethod(lua_State* L, bool pushSelf) { 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) assert(lua_istable(L, -1)); for (;;) { lua_rawgetp(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) | nil if (lua_isnil(L, -1)) // Stack: mt, nil { lua_pop(L, 2); // Stack: - return luaL_error(L, "No member named '%s'", lua_tostring(L, 2)); } assert(lua_istable(L, -1)); 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 (pushSelf) { lua_pushvalue(L, 1); // Stack: setter, table | userdata } lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value lua_call(L, pushSelf ? 2 : 1, 0); // Stack: - return 0; } assert(lua_isnil(L, -1)); // Stack: mt, nil lua_pop(L, 1); // Stack: mt lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil if (lua_isnil(L, -1)) // Stack: mt, nil { lua_pop(L, 1); // Stack: - return luaL_error(L, "No writable member '%s'", lua_tostring(L, 2)); } assert(lua_istable(L, -1)); // Stack: mt, parent mt lua_remove(L, -2); // Stack: parent mt // Repeat the search in the parent } // no return } //---------------------------------------------------------------------------- /** lua_CFunction to report an error writing to a read-only value. The name of the variable is in the first upvalue. */ static int readOnlyError(lua_State* L) { std::string s; s = s + "'" + lua_tostring(L, lua_upvalueindex(1)) + "' is read-only"; return luaL_error(L, s.c_str()); } //---------------------------------------------------------------------------- /** 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 static int getVariable(lua_State* L) { assert(lua_islightuserdata(L, lua_upvalueindex(1))); T const* ptr = static_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(ptr != 0); Stack::push(L, *ptr); return 1; } //---------------------------------------------------------------------------- /** 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 static int setVariable(lua_State* L) { assert(lua_islightuserdata(L, lua_upvalueindex(1))); T* ptr = static_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(ptr != 0); *ptr = Stack::get(L, 1); return 0; } //---------------------------------------------------------------------------- /** lua_CFunction to call a function with a return value. This is used for global functions, global properties, class static methods, and class static properties. The function pointer (lightuserdata) in the first upvalue. */ template struct Call { typedef typename FuncTraits::Params Params; typedef typename FuncTraits::ReturnType ReturnType; static int f(lua_State* L) { assert(lua_islightuserdata(L, lua_upvalueindex(1))); FnPtr fnptr = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(fnptr != 0); return Invoke::run(L, fnptr); } }; //---------------------------------------------------------------------------- /** 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 struct CallMember { typedef typename FuncTraits::ClassType T; typedef typename FuncTraits::Params Params; typedef typename FuncTraits::ReturnType ReturnType; static int f(lua_State* L) { assert(isfulluserdata(L, lua_upvalueindex(1))); T* const t = Userdata::get(L, 1, false); MemFnPtr const& fnptr = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(fnptr != 0); return Invoke::run(L, t, fnptr); } }; template struct CallConstMember { typedef typename FuncTraits::ClassType T; typedef typename FuncTraits::Params Params; typedef typename FuncTraits::ReturnType ReturnType; static int f(lua_State* L) { assert(isfulluserdata(L, lua_upvalueindex(1))); T const* const t = Userdata::get(L, 1, true); MemFnPtr const& fnptr = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(fnptr != 0); return Invoke::run(L, t, fnptr); } }; //-------------------------------------------------------------------------- /** 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 struct CallMemberCFunction { static int f(lua_State* L) { assert(isfulluserdata(L, lua_upvalueindex(1))); typedef int (T::*MFP)(lua_State * L); T* const t = Userdata::get(L, 1, false); MFP const& fnptr = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(fnptr != 0); return (t->*fnptr)(L); } }; template struct CallConstMemberCFunction { static int f(lua_State* L) { assert(isfulluserdata(L, lua_upvalueindex(1))); typedef int (T::*MFP)(lua_State * L); T const* const t = Userdata::get(L, 1, true); MFP const& fnptr = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(fnptr != 0); return (t->*fnptr)(L); } }; //-------------------------------------------------------------------------- /** lua_CFunction to call on a object. The proxy function pointer (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack. */ template struct CallProxyFunction { using Params = typename FuncTraits::Params; using ReturnType = typename FuncTraits::ReturnType; static int f(lua_State* L) { assert(lua_islightuserdata(L, lua_upvalueindex(1))); auto fnptr = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); assert(fnptr != 0); return Invoke::run(L, fnptr); } }; template struct CallProxyFunctor { using Params = typename FuncTraits::Params; using ReturnType = typename FuncTraits::ReturnType; static int f(lua_State* L) { assert(isfulluserdata(L, lua_upvalueindex(1))); Functor& fn = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); return Invoke::run(L, fn); } }; //-------------------------------------------------------------------------- // SFINAE Helpers template struct CallMemberFunctionHelper { static void add(lua_State* L, char const* name, MemFnPtr mf) { new (lua_newuserdata(L, sizeof(MemFnPtr))) MemFnPtr(mf); lua_pushcclosure(L, &CallConstMember::f, 1); lua_pushvalue(L, -1); rawsetfield(L, -5, name); // const table rawsetfield(L, -3, name); // class table } }; template struct CallMemberFunctionHelper { static void add(lua_State* L, char const* name, MemFnPtr mf) { new (lua_newuserdata(L, sizeof(MemFnPtr))) MemFnPtr(mf); lua_pushcclosure(L, &CallMember::f, 1); rawsetfield(L, -3, name); // class table } }; //-------------------------------------------------------------------------- /** __gc metamethod for a class. */ template static int gcMetaMethod(lua_State* L) { Userdata* const ud = Userdata::getExact(L, 1); ud->~Userdata(); return 0; } /** __gc metamethod for an arbitrary class. */ template static int gcMetaMethodAny(lua_State* L) { assert(isfulluserdata(L, 1)); T* t = static_cast(lua_touserdata(L, 1)); t->~T(); return 0; } //-------------------------------------------------------------------------- /** 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 static int getProperty(lua_State* L) { C* const c = Userdata::get(L, 1, true); T C::** mp = static_cast(lua_touserdata(L, lua_upvalueindex(1))); try { Stack::push(L, c->**mp); } catch (const std::exception& e) { luaL_error(L, e.what()); } return 1; } //-------------------------------------------------------------------------- /** 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 static int setProperty(lua_State* L) { C* const c = Userdata::get(L, 1, false); T C::** mp = static_cast(lua_touserdata(L, lua_upvalueindex(1))); try { c->** mp = Stack::get(L, 2); } catch (const std::exception& e) { luaL_error(L, e.what()); } return 0; } }; } // namespace detail } // namespace luabridge