// Copyright satoren // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once #include #include #include #include #include "kaguya/config.hpp" #include "kaguya/push_any.hpp" #include "kaguya/native_function.hpp" #include "kaguya/lua_ref_function.hpp" #define KAGUYA_PROPERTY_PREFIX "_prop_" namespace kaguya { #define KAGUYA_PP_STRUCT_TDEF_REP(N) KAGUYA_PP_CAT(class A, N) = void #define KAGUYA_PP_STRUCT_TEMPLATE_DEF_REPEAT(N) \ KAGUYA_PP_REPEAT_ARG(N, KAGUYA_PP_STRUCT_TDEF_REP) template struct MultipleBase {}; #undef KAGUYA_PP_STRUCT_TDEF_REP #undef KAGUYA_PP_STRUCT_TEMPLATE_DEF_REPEAT } namespace kaguya { struct LuaCodeChunk { LuaCodeChunk(const std::string &src, const std::string &name = "") : code_(src), chunk_name_(name) {} LuaCodeChunk(const char *src, const char *name = "") : code_(src), chunk_name_(name ? name : "") {} std::string code_; std::string chunk_name_; }; /// @ingroup lua_type_traits /// @brief lua_type_traits for LuaCodeChunk template <> struct lua_type_traits { static int push(lua_State *state, const LuaCodeChunk &ref) { int status = luaL_loadbuffer( state, ref.code_.c_str(), ref.code_.size(), ref.chunk_name_.empty() ? ref.code_.c_str() : ref.chunk_name_.c_str()); if (!except::checkErrorAndThrow(status, state)) { return 0; } return 1; } }; struct LuaCodeChunkExecute : LuaCodeChunk { LuaCodeChunkExecute(const std::string &src, const std::string &name = "") : LuaCodeChunk(src, name) {} LuaCodeChunkExecute(const char *src, const char *name = "") : LuaCodeChunk(src, name) {} }; typedef LuaCodeChunkExecute LuaCodeChunkResult; /// @ingroup lua_type_traits /// @brief lua_type_traits for LuaCodeChunkResult template <> struct lua_type_traits { static int push(lua_State *state, const LuaCodeChunkExecute &ref) { int status = luaL_loadbuffer( state, ref.code_.c_str(), ref.code_.size(), ref.chunk_name_.empty() ? ref.code_.c_str() : ref.chunk_name_.c_str()); if (!except::checkErrorAndThrow(status, state)) { return 0; } status = lua_pcall_wrap(state, 0, 1); if (!except::checkErrorAndThrow(status, state)) { return 0; } return 1; } }; namespace Metatable { typedef std::map PropMapType; typedef std::map MemberMapType; inline bool is_property_key(const char *keyname) { return keyname && strncmp(keyname, KAGUYA_PROPERTY_PREFIX, sizeof(KAGUYA_PROPERTY_PREFIX) - 1) != 0; } inline int property_index_function(lua_State *L) { // Lua // local arg = {...};local metatable = arg[1]; // return function(table, index) // if string.find(index,KAGUYA_PROPERTY_PREFIX)~=0 then // local propfun = metatable[KAGUYA_PROPERTY_PREFIX ..index]; // if propfun then return propfun(table) end // end // return metatable[index] // end static const int table = 1; static const int key = 2; static const int metatable = lua_upvalueindex(1); const char *strkey = lua_tostring(L, key); if (lua_type(L, 1) == LUA_TUSERDATA && is_property_key(strkey)) { int type = lua_getfield_rtype( L, metatable, (KAGUYA_PROPERTY_PREFIX + std::string(strkey)).c_str()); if (type == LUA_TFUNCTION) { lua_pushvalue(L, table); lua_call(L, 1, 1); return 1; } } lua_pushvalue(L, key); lua_gettable(L, metatable); return 1; } inline int property_newindex_function(lua_State *L) { // Lua // local arg = {...};local metatable = arg[1]; // return function(table, index, value) // if type(table) == 'userdata' then // if string.find(index,KAGUYA_PROPERTY_PREFIX)~=0 then // local propfun = metatable[KAGUYA_PROPERTY_PREFIX..index]; // if propfun then return propfun(table,value) end // end // end // rawset(table,index,value) // end static const int table = 1; static const int key = 2; static const int value = 3; static const int metatable = lua_upvalueindex(1); const char *strkey = lua_tostring(L, 2); if (lua_type(L, 1) == LUA_TUSERDATA && is_property_key(strkey)) { int type = lua_getfield_rtype( L, metatable, (KAGUYA_PROPERTY_PREFIX + std::string(strkey)).c_str()); if (type == LUA_TFUNCTION) { lua_pushvalue(L, table); lua_pushvalue(L, value); lua_call(L, 2, 0); return 0; } } lua_pushvalue(L, key); lua_pushvalue(L, value); lua_rawset(L, table); return 0; } inline int multiple_base_index_function(lua_State *L) { // Lua // local arg = {...};local metabases = arg[1]; // return function(t, k) // for i = 1,#metabases do // local v = metabases[i][k] // if v then // t[k] = v // return v end // end // end static const int table = 1; static const int key = 2; static const int metabases = lua_upvalueindex(1); lua_pushnil(L); while (lua_next(L, metabases) != 0) { if (lua_type(L, -1) == LUA_TTABLE) { lua_pushvalue(L, key); int type = lua_gettable_rtype(L, -2); if (type != LUA_TNIL) { lua_pushvalue(L, key); lua_pushvalue(L, -2); lua_settable(L, table); return 1; } } lua_settop(L, 3); // pop value } return 0; } inline int call_constructor_function(lua_State *L) { // function(t,...) return t.new(...) end lua_getfield(L, 1, "new"); lua_replace(L, 1); lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); } inline void get_call_constructor_metatable(lua_State *L) { static int key = 0; int ttype = lua_rawgetp_rtype(L, LUA_REGISTRYINDEX, &key); if (ttype != LUA_TTABLE) { lua_pop(L, 1); lua_createtable(L, 0, 1); lua_pushstring(L, "__call"); lua_pushcfunction(L, &call_constructor_function); lua_rawset(L, -3); lua_pushvalue(L, -1); lua_rawsetp(L, LUA_REGISTRYINDEX, &key); } } inline void setMembers(lua_State *state, int metatable_index, const MemberMapType &member_map, const PropMapType &property_map) { for (MemberMapType::const_iterator it = member_map.begin(); it != member_map.end(); ++it) { util::one_push(state, it->first); util::one_push(state, it->second); lua_rawset(state, metatable_index); } for (PropMapType::const_iterator it = property_map.begin(); it != property_map.end(); ++it) { util::one_push(state, KAGUYA_PROPERTY_PREFIX + it->first); util::one_push(state, it->second); lua_rawset(state, metatable_index); } } inline void setPropertyIndexMetamethod(lua_State *state, int metatable_index) { lua_pushstring(state, "__index"); lua_pushvalue(state, metatable_index); lua_pushcclosure(state, &property_index_function, 1); lua_rawset(state, metatable_index); } inline void setPropertyNewIndexMetamethod(lua_State *state, int metatable_index) { lua_pushstring(state, "__newindex"); lua_pushvalue(state, metatable_index); lua_pushcclosure(state, &property_newindex_function, 1); lua_rawset(state, metatable_index); } inline void setMultipleBase(lua_State *state, int metatable_index, int metabase_array_index) { lua_createtable(state, 0, 1); int newmetaindex = lua_gettop(state); lua_pushstring(state, "__index"); lua_pushvalue(state, metabase_array_index); // bind metabase_array to // multiple_base_index_function lua_pushcclosure(state, &multiple_base_index_function, 1); lua_rawset(state, newmetaindex); // newmeta["__index"] = multiple_base_index_function lua_setmetatable(state, metatable_index); // metatable.setMetatable(newmeta); } } /// class binding interface. template class UserdataMetatable { public: UserdataMetatable() { addStaticFunction("__gc", &class_userdata::destructor); KAGUYA_STATIC_ASSERT(is_registerable::value || !traits::is_std_vector::value, "std::vector is binding to lua-table by default.If " "you wants register for std::vector yourself," "please define KAGUYA_NO_STD_VECTOR_TO_TABLE"); KAGUYA_STATIC_ASSERT(is_registerable::value || !traits::is_std_map::value, "std::map is binding to lua-table by default.If you " "wants register for std::map yourself," "please define KAGUYA_NO_STD_MAP_TO_TABLE"); // can not register push specialized class KAGUYA_STATIC_ASSERT(is_registerable::value, "Can not register specialized of type conversion " "class. e.g. std::tuple"); } LuaTable createMatatable(lua_State *state) const { util::ScopedSavedStack save(state); if (!class_userdata::newmetatable(state)) { except::OtherError(state, typeid(class_type *).name() + std::string(" is already registered")); return LuaTable(); } int metatable_index = lua_gettop(state); Metatable::setMembers(state, metatable_index, member_map_, property_map_); if (!traits::is_same::value || !property_map_.empty()) // if base class has property and derived class // hasnt property. need property access // metamethod { if (member_map_.count("__index") == 0) { Metatable::setPropertyIndexMetamethod(state, metatable_index); } if (member_map_.count("__newindex") == 0) { Metatable::setPropertyNewIndexMetamethod(state, metatable_index); } } else { if (member_map_.count("__index") == 0) { lua_pushstring(state, "__index"); lua_pushvalue(state, metatable_index); lua_rawset(state, metatable_index); } } set_base_metatable(state, metatable_index, types::typetag()); if (lua_getmetatable(state, metatable_index)) // get base_metatable { lua_pushstring(state, "__call"); lua_pushcfunction(state, &Metatable::call_constructor_function); lua_rawset(state, -3); // base_metatable["__call"] = // Metatable::call_constructor_function } else { Metatable::get_call_constructor_metatable(state); lua_setmetatable(state, metatable_index); } return LuaStackRef(state, metatable_index); } #if KAGUYA_USE_CPP11 template UserdataMetatable &setConstructors() { addOverloadedFunctions( "new", typename ConstructorFunction::type()...); return *this; } #else #define KAGUYA_SET_CON_TYPE_DEF(N) \ typename ConstructorFunction::type() #define KAGUYA_SET_CON_FN_DEF(N) \ template \ inline UserdataMetatable &setConstructors() { \ addOverloadedFunctions("new", \ KAGUYA_PP_REPEAT_ARG(N, KAGUYA_SET_CON_TYPE_DEF)); \ return *this; \ } KAGUYA_PP_REPEAT_DEF(KAGUYA_FUNCTION_MAX_OVERLOADS, KAGUYA_SET_CON_FN_DEF) #undef KAGUYA_SET_CON_FN_DEF #undef KAGUYA_SET_CON_TYPE_DEF #endif /// @brief add member property with getter function.(experimental) /// @param name function name for lua /// @param mem bind member data template UserdataMetatable &addProperty(const char *name, Ret class_type::*mem) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(kaguya::function(mem)); return *this; } /// @brief add member property with getter function.(experimental) /// @param name function name for lua /// @param getter getter function template UserdataMetatable &addProperty(const char *name, GetType (class_type::*getter)() const) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(kaguya::function(getter)); return *this; } /// @brief add member property with setter, getter functions.(experimental) /// @param name function name for lua /// @param getter getter function /// @param setter setter function template UserdataMetatable &addProperty(const char *name, GetType (*getter)(const class_type *)) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(function(getter)); return *this; } /// @brief add member property with setter, getter functions.(experimental) /// @param name function name for lua /// @param getter getter function /// @param setter setter function template UserdataMetatable &addProperty(const char *name, GetType (*getter)(const class_type &)) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(function(getter)); return *this; } /// @brief add member property with setter, getter functions.(experimental) /// @param name function name for lua /// @param getter getter function /// @param setter setter function template UserdataMetatable &addProperty(const char *name, GetType (class_type::*getter)() const, void (class_type::*setter)(SetType)) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(overload(getter, setter)); return *this; } /// @brief add member property with external setter, getter /// functions.(experimental) /// @param name function name for lua /// @param getter getter function /// @param setter setter function template UserdataMetatable &addProperty(const char *name, GetType (*getter)(const class_type *), void (*setter)(class_type *, SetType)) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(overload(getter, setter)); return *this; } /// @brief add member property with external setter, getter /// functions.(experimental) /// @param name function name for lua /// @param getter getter function /// @param setter setter function template UserdataMetatable &addProperty(const char *name, GetType (*getter)(const class_type &), void (*setter)(class_type &, SetType)) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(overload(getter, setter)); return *this; } /// @brief add member property with getter function.(experimental) /// @param name function name for lua /// @param getter getter function template UserdataMetatable &addPropertyAny(const char *name, GetterType getter) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(function(getter)); return *this; } /// @brief add member property with setter, getter functions.(experimental) /// @param name function name for lua /// @param getter getter function /// @param setter setter function template UserdataMetatable &addPropertyAny(const char *name, GetterType getter, SetterType setter) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } property_map_[name] = AnyDataPusher(overload(getter, setter)); return *this; } /// @brief add non member function /// @param name function name for lua /// @param f function template UserdataMetatable &addStaticFunction(const char *name, Fun f) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } member_map_[name] = AnyDataPusher(kaguya::function(f)); return *this; } #if KAGUYA_USE_CPP11 /// @brief assign overloaded from functions. /// @param name name for lua /// @param f functions template UserdataMetatable &addOverloadedFunctions(const char *name, Funcs... f) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } member_map_[name] = AnyDataPusher(overload(f...)); return *this; } /// @brief assign data by argument value. /// @param name name for lua /// @param d data template UserdataMetatable &addStaticField(const char *name, Data &&d) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } member_map_[name] = AnyDataPusher(std::forward(d)); return *this; } #else #define KAGUYA_ADD_OVERLOAD_FUNCTION_DEF(N) \ template \ inline UserdataMetatable &addOverloadedFunctions( \ const char *name, KAGUYA_PP_ARG_CR_DEF_REPEAT(N)) { \ if (has_key(name)) { \ throw KaguyaException(std::string(name) + " is already registered."); \ return *this; \ } \ member_map_[name] = \ AnyDataPusher(kaguya::overload(KAGUYA_PP_ARG_REPEAT(N))); \ return *this; \ } KAGUYA_PP_REPEAT_DEF(KAGUYA_FUNCTION_MAX_OVERLOADS, KAGUYA_ADD_OVERLOAD_FUNCTION_DEF) #undef KAGUYA_ADD_OVERLOAD_FUNCTION_DEF /// @brief assign data by argument value. /// @param name name for lua /// @param d data template UserdataMetatable &addStaticField(const char *name, const Data &d) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered."); return *this; } member_map_[name] = AnyDataPusher(d); return *this; } #endif #if defined(_MSC_VER) && _MSC_VER <= 1800 // can not write Ret class_type::* f on MSC++2013 template UserdataMetatable &addFunction(const char *name, Fun f) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered. To " "overload a function, use " "addOverloadedFunctions"); return *this; } member_map_[name] = AnyDataPusher(kaguya::function(f)); return *this; } #else /// @brief assign function /// @param name name for lua /// @param f pointer to member function. template UserdataMetatable &addFunction(const char *name, Ret class_type::*f) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered. To " "overload a function, use " "addOverloadedFunctions"); return *this; } member_map_[name] = AnyDataPusher(kaguya::function(f)); return *this; } #endif /// @brief assign function /// @param name name for lua /// @param f member function object. UserdataMetatable &addFunction(const char *name, PolymorphicMemberInvoker f) { if (has_key(name)) { throw KaguyaException(std::string(name) + " is already registered. To " "overload a function, use " "addOverloadedFunctions"); return *this; } member_map_[name] = AnyDataPusher(kaguya::function(f)); return *this; } private: void set_base_metatable(lua_State *, int, types::typetag) const {} template void set_base_metatable(lua_State *state, int metatable_index, types::typetag) const { class_userdata::get_metatable(state); lua_setmetatable(state, metatable_index); // metatable.setMetatable(newmeta); PointerConverter &pconverter = PointerConverter::get(state); pconverter.add_type_conversion(); } #if KAGUYA_USE_CPP11 template void metatables(lua_State *state, int metabase_array_index, PointerConverter &pvtreg, types::typetag >) const { class_userdata::get_metatable(state); lua_rawseti(state, metabase_array_index, lua_rawlen(state, metabase_array_index) + 1); pvtreg.add_type_conversion(); } template void metatables(lua_State *state, int metabase_array_index, PointerConverter &pvtreg, types::typetag >) const { class_userdata::get_metatable(state); lua_rawseti(state, metabase_array_index, lua_rawlen(state, metabase_array_index) + 1); pvtreg.add_type_conversion(); metatables(state, metabase_array_index, pvtreg, types::typetag >()); } template void set_base_metatable(lua_State *state, int metatable_index, types::typetag > metatypes) const { PointerConverter &pconverter = PointerConverter::get(state); lua_createtable(state, sizeof...(Bases), 0); int metabase_array_index = lua_gettop(state); metatables(state, metabase_array_index, pconverter, metatypes); Metatable::setMultipleBase(state, metatable_index, metabase_array_index); } #else #define KAGUYA_GET_BASE_METATABLE(N) \ class_userdata::get_metatable(state); \ lua_rawseti(state, metabase_array_index, \ lua_rawlen(state, metabase_array_index) + 1); \ pconverter.add_type_conversion(); #define KAGUYA_MULTIPLE_INHERITANCE_SETBASE_DEF(N) \ template \ void set_base_metatable( \ lua_State *state, int metatable_index, \ types::typetag >) const { \ PointerConverter &pconverter = PointerConverter::get(state); \ lua_createtable(state, N, 0); \ int metabase_array_index = lua_gettop(state); \ KAGUYA_PP_REPEAT(N, KAGUYA_GET_BASE_METATABLE); \ Metatable::setMultipleBase(state, metatable_index, metabase_array_index); \ } KAGUYA_PP_REPEAT_DEF(KAGUYA_CLASS_MAX_BASE_CLASSES, KAGUYA_MULTIPLE_INHERITANCE_SETBASE_DEF) #undef KAGUYA_MULTIPLE_INHERITANCE_SETBASE_DEF #undef KAGUYA_GET_BASE_METATABLE #endif bool has_key(const std::string &key) { if (member_map_.count(key) > 0) { return true; } if (property_map_.count(key) > 0) { return true; } std::string propkey = KAGUYA_PROPERTY_PREFIX + key; if (member_map_.count(propkey) > 0) //_prop_keyname is reserved for property { return true; } return false; } Metatable::PropMapType property_map_; Metatable::MemberMapType member_map_; }; /// @ingroup lua_type_traits /// @brief lua_type_traits for UserdataMetatable template struct lua_type_traits > { typedef const UserdataMetatable &push_type; static int push(lua_State *l, push_type ref) { return ref.createMatatable(l).push(l); } }; }