// Copyright (c) 2014 Hao Fei // this file is part of luamm // // 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. #ifndef LUAMM_HPP #define LUAMM_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace luamm { // utility classes and functions namespace detail { class AutoPopper { lua_State* state; int n; public: AutoPopper(lua_State* st, int n = 1) : state(st), n(n) {} ~AutoPopper() { if (n > 0) lua_pop(state, n); } }; inline void cleanup(lua_State* state, int index) { if (state && index == lua_gettop(state)) { lua_pop(state, 1); } } // type trait distinguishes value(primitive) and non-value lua types // value == 0, iff non-value type, Closure, Table etc // value == 1, iff value type Number, string, boolean, Nil etc template struct StackVariable { enum { value = 0 }; }; template struct AutoCleaner { template static void clean(const Container& _, const Key& k) {} }; template<> struct AutoCleaner { template static void clean(lua_State* state, int index) { if (lua_gettop(state) == index && !StackVariable::value) { lua_pop(state, 1); } } }; } /* make aliases for lua types */ typedef lua_Number Number; typedef lua_CFunction CFunction; /* primitive lua typs */ class Nil {}; /* Base class for errors */ struct RuntimeError : std::runtime_error { RuntimeError(const std::string& s) : std::runtime_error(s) {} RuntimeError() : std::runtime_error("") {} }; /* defines a static member funcion push() for each type of variable * that permitted to be passed into lua runtime environmrnt */ template struct VarPusher; /* defines a static member function get() for read a lua variable * uniquely identified by key of type Key into elem of type Elem */ template struct KeyGetter; /* similar to KeyGetter, but set elem instead of getting */ template struct KeySetter; /* return the lua type of variable indexed by key */ template struct KeyTyper; template struct VarProxy; /* router class for read/write/update a lua variable */ template class Variant { friend struct VarProxy>; private: Key index; Container state; public: Variant(Container st, const Key& i) : index(i), state(st) {} template T to() const { return KeyGetter::get(state, index); } template operator T() && { T o = to(); detail::AutoCleaner::template clean(state, index); return o; } template operator T() & { return to(); } template Variant& operator=(const T& var) { KeySetter::set(state, index, var); return *this; } template T convert(T* ) { return this->operator T(); } int type() { return KeyTyper::type(state, index); } bool isnum() { return type() == LUA_TNUMBER; } bool istab() { return type() == LUA_TTABLE; } bool isnil() { return type() == LUA_TNIL; } bool isbool() { return type() == LUA_TBOOLEAN; } bool isstr() { return type() == LUA_TSTRING; } bool isfun() { return type() == LUA_TFUNCTION; } bool isuserdata() { return type() == LUA_TUSERDATA; } bool isthread() { return type() == LUA_TTHREAD; } bool islight() { return type() == LUA_TLIGHTUSERDATA; } bool iscfun() { try { KeyGetter::get(state, index); } catch (RuntimeError e) { return false; } return true; } }; struct Table { lua_State* state; int index; Table(lua_State* st, int i); ~Table(); template Variant::type> operator[](const T& k); void setmetatable(const Table& metatab); Table getmetatable(); int length() { return lua_rawlen(state, index); } bool operator==(const Table& o) const { return o.state == state && lua_compare(state, index, o.index, LUA_OPEQ); } bool operator!=(const Table& o) const { return !this->operator==(o); } Table(Table&& o) : state(o.state), index(o.index) { o.state = nullptr; o.index = 0; } bool hasmetatable() { return lua_getmetatable(state, index) ? true : false; } private: Table(const Table&); }; namespace detail { /* interface that lua variable types which can have corresponding metatable */ template struct HasMetaTable { void setmetatable(const Table& metatab); void setmetatable(const std::string& regkey); void checkmetatable(const std::string& regkey); Table getmetatable(); bool hasmetatable() { Sub* this_ = static_cast(this); return lua_getmetatable(this_->state, this_->index)?true:false; } }; } /* a closure in lua is a tuple of * #1, a function pointer who follows the lua cfunction signature * #2, the number of upvalues */ struct CClosure { int index; CFunction func; public: CClosure(CFunction func, int index = 0) : index(index), func(func) {} }; struct UserData : detail::HasMetaTable { lua_State* state; int index; UserData(lua_State* st, int index) : state(st), index(lua_absindex(st, index)) {} UserData(UserData&& o) : state(o.state), index(o.index) { o.state = nullptr; o.index = 0; } UserData(const UserData&) = delete; template T& to() { void *p = lua_touserdata(state, index); return *static_cast(p); } ~UserData(); }; namespace detail {template<>struct StackVariable { enum{value=1};};} struct VarBase { lua_State *state; VarBase(lua_State *st) : state(st) {} template VarProxy& to() { return static_cast&>(*this); } }; template<> struct VarProxy : VarBase { bool push(CClosure c) { for (auto i = 0; i < c.index; i++) { lua_pushnil(state); } lua_pushcclosure(state, c.func, c.index); return true; } }; // light userdata is just a void* pointer template<> struct VarProxy : VarBase { bool push(void* l) { lua_pushlightuserdata(state, l); return true; } void *get(int index, bool& success) { auto p = lua_touserdata(state, index); if (p) { success = true; } return p; } enum { tid = LUA_TLIGHTUSERDATA }; }; template<> struct VarProxy : VarBase { bool push(const UserData& l) { lua_pushnil(state); lua_copy(state, l.index, -1); return true; } UserData get(int index, bool& success) { success = true; return UserData(state, index); } enum { tid = LUA_TUSERDATA }; }; template<> struct VarProxy : VarBase { CFunction get(int index, bool& success) { auto p = lua_tocfunction(state, index); if (p) success = true; return p; } enum { tid = LUA_TFUNCTION }; }; template<> struct VarProxy : VarBase { bool push(const std::string& v) { return lua_pushstring(state, v.c_str()) ? true : false; } }; template<> struct VarProxy : VarBase { const char *get(int index, bool& success) { const char * r = lua_tostring(state, index); success = r ? true : false; return r; } enum { tid = LUA_TSTRING}; }; template<> struct VarProxy : VarBase { bool push(Number num) { lua_pushnumber(state, num); return true; } Number get(int index, bool& success) { int isnum; Number r = lua_tonumberx(state, index, &isnum); success = isnum ? true : false; return r; } enum { tid = LUA_TNUMBER }; }; namespace detail { /* all in/out lua variable types that expected to be converted to/from * automatically (as a function arguments or return value) * should be placed in this mpl vector. */ typedef boost::mpl::vector< std::string, const char*, Number, CClosure, Table, UserData, Nil > varproxies; struct PlaceHolder {}; /* decide which type in varproxies provide the matching proxy for typename * Var */ template struct PredPush { typedef struct { char _[2]; } two; typedef char one; template static one test( decltype( std::declval>().push(std::declval()))); template static two test(...); typedef boost::mpl::bool_(true)) == 1> type; }; template struct PredGet { typedef struct { char _[2]; } two; typedef char one; template static one test( typename std::conditional< std::is_convertible< decltype( VarBase(static_cast(nullptr)) .to

().get( static_cast(2), std::declval() ) ), V >::value, void *, double >::type ); template static two test(...); typedef boost::mpl::bool_(nullptr)) == 1> type; }; template class Pred, typename T> struct SelectImpl { typedef typename boost::mpl::fold< detail::varproxies, detail::PlaceHolder, // _1 is state, _2 is current item boost::mpl::if_, boost::mpl::_2, // select current item boost::mpl::_1> // keep last item >::type type; }; template struct Guard { bool status; Guard() : status(false) {} ~Guard() { if (!status) throw Exception(); } }; } template<> struct VarProxy : VarBase { bool push(Nil _) { lua_pushnil(state); return true; } enum { tid = LUA_TNIL}; }; template<> struct VarProxy : VarBase { bool push(bool b) { lua_pushboolean(state, b); return true; } bool get(int index, bool& success) { success = true; return lua_toboolean(state, index) ? true : false; } enum { tid = LUA_TBOOLEAN}; }; struct VarPushError : RuntimeError { VarPushError() : RuntimeError("") {} }; namespace detail { /* generate a tuple to simulate multiple return value in c++*/ template, typename... Args> struct GenTuple { typedef typename GenTuple::type type; }; template struct GenTuple<0, Arg, Args...> { typedef std::tuple type; }; } struct Closure; struct ReturnProxy { Closure *self; int nargs; ReturnProxy(Closure *self, int nargs) : self(self), nargs(nargs) {} ReturnProxy(const ReturnProxy&) = delete; ReturnProxy(ReturnProxy&& o) : self(o.self), nargs(o.nargs) { o.self = nullptr; } ReturnProxy& call(int nresults); template operator T() &&; template operator std::tuple() &&; ~ReturnProxy() { if (self) { call(0); } } }; template struct TieProxy { std::tuple data; TieProxy(std::tuple arg) : data(arg) {} void operator=(int a) { std::get<0>(data) = a; } TieProxy(const TieProxy&) = delete; TieProxy(TieProxy&& o) : data(std::move(o.data)) {} void operator=(ReturnProxy&& retproxy); }; template TieProxy tie(Types&... args) { return TieProxy(std::tie(args...)); } /* a callable representing a lua variable exitsing in the stack, * either a c function or a lua function */ struct Closure : public detail::HasMetaTable { lua_State* state; int index; Closure(lua_State* st, int index) : state(st), index(lua_absindex(st, index)) {} Variant operator[](int n) { return Variant(this, n); } ~Closure(); Closure(Closure&& o) : state(o.state), index(o.index) { o.state = nullptr; o.index = 0; } template struct Rvals { typedef typename std::conditional auto cleanable variant Variant<>, typename detail::GenTuple::type >::type >::type type; }; template typename Rvals::type __return__(); template ReturnProxy operator()(Args&& ... args) { return call(std::forward(args)...); } template ReturnProxy call(Args&&... args) { // push the function to be called lua_pushnil(state); lua_copy(state, index, -1); // passing arguments return __call__<0, Args...>(std::forward(args)...); } template ReturnProxy __call__(T&& a, Args&&... args) { { detail::Guard gd; gd.status = VarPusher::push(state, std::forward(a)); } return this->__call__(std::forward(args)...); } template ReturnProxy __call__() { return ReturnProxy(this, count); } private: Closure(const Closure&); }; inline ReturnProxy& ReturnProxy::call(int nresults) { auto i = lua_pcall(self->state, nargs, nresults, 0); if (i != LUA_OK) { throw RuntimeError(lua_tostring(self->state, -1)); } self = nullptr; return *this; } template ReturnProxy::operator T() && { auto self = this->self; call(1); return Variant<>(self->state, lua_gettop(self->state)); } template<> inline void Closure::__return__<0>() {} template<> inline typename Closure::Rvals<1>::type Closure::__return__<1>() { // auto cleanable variant return Closure::Rvals<1>::type(state, lua_gettop(state)); } #ifndef LUAMM_MAX_RETVALUES #define LUAMM_MAX_RETVALUES 15 #endif #define LUAMM_X(n) Variant<>(state, -n)BOOST_PP_COMMA_IF(BOOST_PP_SUB(n,1)) #define LUAMM_Y(a, b, c) LUAMM_X(BOOST_PP_SUB(c, b)) #define LUAMM_ARGS(n) BOOST_PP_REPEAT(n, LUAMM_Y, n) #define LUAMM_RET(_a, n, _b) template<>\ inline typename Closure::Rvals::type Closure::__return__() {\ return std::make_tuple(LUAMM_ARGS(n)); \ } BOOST_PP_REPEAT_FROM_TO(2, LUAMM_MAX_RETVALUES, LUAMM_RET,) #undef LUAMM_X #undef LUAMM_Y #undef LUAMM_ARGS #undef LUAMM_RET template void TieProxy::operator=(ReturnProxy&& retproxy) { auto self = retproxy.self; retproxy.call(sizeof...(Args)); data = self->__return__(); } template ReturnProxy::operator std::tuple() && { auto self = this->self; call(sizeof...(Args)); std::tuple tup; tup = self->__return__(); return tup; } namespace detail { template<> struct StackVariable { enum { value = 1 }; }; } template<> struct VarProxy : VarBase { bool push(const Closure& c) { lua_pushnil(state); lua_copy(state, c.index, -1); return true; } Closure get(int index, bool& success) { if (lua_isfunction(state, index)) { success = true; return Closure(state, index); } else { return Closure(nullptr, 0); } } enum { tid = LUA_TFUNCTION }; }; namespace detail { template<> struct StackVariable { enum { value = 1 }; }; } struct KeyGetError : RuntimeError { KeyGetError() : RuntimeError("") {} }; struct KeyPutError : RuntimeError { KeyPutError() : RuntimeError("") {} }; struct VarGetError : RuntimeError { VarGetError() : RuntimeError("") {} }; template<> struct VarProxy
: VarBase { bool push(const Table& tb) { lua_pushnil(state); lua_copy(state, tb.index, -1); return true; } Table get(int index, bool& success) { if (!lua_istable(state, index)) { throw RuntimeError("is not a table"); } success = true; return Table(state, index); } enum { tid = LUA_TTABLE }; }; namespace detail { template struct VarDispatcher { template static char testget( decltype(&VarProxy::get) ); template static double testget( ... ); template static char testpush( decltype(&VarProxy::push) ); template static double testpush( ... ); enum { use_indirect_get = sizeof(testget(nullptr)) != 1 }; enum { use_indirect_push = sizeof(testpush(nullptr)) != 1 }; }; } template struct VarPusher { typedef typename std::conditional< detail::VarDispatcher::use_indirect_push, typename detail::SelectImpl::type, T >::type type; static bool push(lua_State* st, const T& v) { static_assert( ! std::is_same::value, "no push() implmentation can be selected for T" ); return VarBase(st).to().push(v); } }; template struct VarGetter { typedef typename std::conditional< detail::VarDispatcher::use_indirect_get, typename detail::SelectImpl::type, T >::type type; static T get(lua_State* st, int index, bool& success) { static_assert( ! std::is_same::value, "no get() implmentation can be selected for T" ); return VarBase(st).to().get(index, success); } }; template struct KeyGetter { static Var get(Closure* cl, int key) { auto p = lua_getupvalue(cl->state, cl->index, key); if (!p) { throw VarGetError(); } detail::Guard gd; detail::AutoPopper ap(cl->state, 1 - detail::StackVariable::value); return VarGetter::get(cl->state, -1, gd.status); } }; template<> struct KeyTyper { static int type(Closure* cl, int key) { auto p = lua_getupvalue(cl->state, cl->index, key); if (!p) { throw VarGetError(); } detail::AutoPopper ap(cl->state); return lua_type(cl->state, -1); } }; template struct KeySetter { static void set(Closure* cl, int key, const Var& nv) { { detail::Guard gd; gd.status = VarPusher::push(cl->state, nv); } if (!lua_setupvalue(cl->state, cl->index, key)) { lua_pop(cl->state, 1); throw RuntimeError("cannot set upvalue"); } } }; // stack + position template struct KeyGetter { static Var get(lua_State* container, int key) { detail::Guard gd; return VarGetter::get(container, key, gd.status); } }; template struct KeySetter { static void set(lua_State* container, int key, const Var& nv) { { detail::Guard gd; gd.status = VarPusher::push(container, nv); } detail::AutoPopper ap(container); lua_copy(container, -1, key); } }; template<> struct KeyTyper { static int type(lua_State* st, int i) { return lua_type(st, i); } }; // auto clean variant if it is not a stack variable(which handle life // crycle by itself, this process is conservative: // that if not on top, do noting // global variable key template struct KeyGetter { static Var get(lua_State* container, const std::string& key) { lua_getglobal(container, key.c_str()); detail::Guard gd; detail::AutoPopper ap(container, 1 - detail::StackVariable::value); return VarGetter::get(container, -1, gd.status); } }; template<> struct KeyTyper { static int type(lua_State* st, const std::string& k) { lua_getglobal(st, k.c_str()); detail::AutoPopper ap(st); return lua_type(st, -1); } }; template struct KeySetter { static void set(lua_State* container, const std::string& key, const Var& nv) { { detail::Guard gd; gd.status = VarPusher::push(container, nv); } lua_setglobal(container, key.c_str()); } }; template struct KeyTyper { static int type(Table *t, const Key& k) { { detail::Guard gd; gd.status = VarPusher::push(t->state, k); } lua_gettable(t->state, t->index); detail::AutoPopper ap(t->state); return lua_type(t->state, -1); } }; template struct KeyGetter { static Var get(Table* container, const Key& key) { { // push key detail::Guard gd; gd.status = VarPusher::push(container->state, key); } // access table lua_gettable(container->state, container->index); // return reteieved value detail::Guard gd; detail::AutoPopper ap(container->state, 1 - detail::StackVariable::value); return VarGetter::get(container->state, -1, gd.status); } }; template struct KeySetter { static void set(Table *container, const Key& key, const Var& nv) { { // push key detail::Guard gd; gd.status = VarPusher::push(container->state, key); } { // push key detail::Guard gd; gd.status = VarPusher::push(container->state, nv); } // access table lua_settable(container->state, container->index); } }; class State; template class Class_ { friend State; Class_(const std::string& name, State& state); std::string name; State& state; Table mod; Table mtab; std::uintptr_t uuid; bool hasReadAttribute{false}; bool hasWriteAttribute{false}; public: enum Attributes { Read = 1, Write = 2 }; template typename std::enable_if< std::is_member_function_pointer::value, Class_&>::type def(const std::string& method, T method_pointer); template typename std::enable_if< !std::is_member_function_pointer::value, Class_&>::type def(const std::string& method, T callable); // enable custom constructor template Class_& init(); template Class_& attribute(const std::string& name, T Class::*mp, unsigned perm = Read | Write); void setupAccessor(); operator Table() && { setupAccessor(); return std::move(mod); } Table getmetatable(); }; /* wrap an existing lua_State */ class State { protected: lua_State *ptr_; public: class Scope { State *state; int origtop; public: Scope(State* st) : state(st), origtop(st->top()) {} ~Scope() { state->settop(origtop); } }; Scope newScope() { return Scope(this); } State(lua_State *p); lua_State* ptr(); void pop(int n=1) { lua_pop(ptr(), n); } template Variant<> push(const T& value) { VarPusher::push(ptr(), value); return Variant<>(ptr(),-1); } Table newTable(int narray = 0, int nother = 0) { lua_createtable(ptr(), 0, 0); return Table(ptr(), top()); } template UserData newUserData(Args&& ... args) { void * buf = lua_newuserdata(ptr(), sizeof(T)); new (buf) T(std::forward(args)...); return UserData(ptr(), -1); } Variant<> operator[](int pos) { return Variant<>(ptr(), pos); } Variant operator[](const std::string& key) { return Variant(ptr(), key); } int top() { return lua_gettop(ptr()); } Variant<> gettop() { return this->operator[](top()); } void openlibs() { luaL_openlibs(ptr()); } void debug() { Table debug = open(luaopen_debug); (Closure(debug["debug"]))(); } Table open(int (*lib)(lua_State*)) { Closure loader(push(lib)); Table mod = loader(); lua_copy(ptr(), mod.index, loader.index); loader.index = 0; return this->operator[](-2); } template void error(const T& t) { push(t); lua_error(ptr()); } Table registry() { return this->operator[](LUA_REGISTRYINDEX); } Closure newFunc(const std::string& str) { auto code = luaL_loadstring(ptr(), str.c_str()); if (code != LUA_OK) { std::string msg = this->operator[](-1); pop(); throw RuntimeError(msg); } else { return Closure(ptr(), -1); } } Closure newFile(const std::string& filename) { auto code = luaL_loadfile(ptr(), filename.c_str()); if (code != LUA_OK) { std::string msg = this->operator[](-1); pop(); throw RuntimeError(msg); } else { return Closure(ptr(), -1); } } State& operator=(State&& o) { ptr_ = o.ptr_; o.ptr_ = nullptr; return *this; } template Closure newCallable(F func, int extra_upvalues = 0); State(const State& o); int upvalue(int i) { return lua_upvalueindex(i); } const char *typerepr(int tid) { return lua_typename(ptr(), tid); } void allocate(int i) { lua_pushnil(ptr()); lua_insert(ptr(), i); } void settop(int i) { lua_settop(ptr(), i); } bool onstack(int index) { return index > LUAI_FIRSTPSEUDOIDX; } template Class_ class_(const std::string& name) { return Class_(name, *this); } virtual ~State() {} }; namespace detail { template struct MemberFunctionWrapper { typedef typename Data::first MemberFuncPtr; typedef typename Data::second ThisType; MemberFuncPtr p; MemberFunctionWrapper(MemberFuncPtr p) : p(p) {} decltype( (std::declval().*p)(std::declval()...) ) operator()(State& st, UserData&& self, Args... args) { auto& hidden_this = self.to(); return (hidden_this.*p)(std::forward(args)...); } }; template class T, typename Data, typename ParaList, int size = boost::mpl::size::value, typename... Args> struct ParameterListTransformer { typedef typename ParameterListTransformer::type, size - 1, Args..., typename boost::mpl::at_c::type>::type type; }; template class T, typename Data, typename ParaList, typename... Args> struct ParameterListTransformer { typedef T type; }; } // end namespace detail template<> struct VarProxy> : VarBase { static bool push(const Variant<>& var) { lua_pushnil(var.state); lua_copy(var.state, var.index, -1); return true; } }; struct ClassAccessorHelper { static int getter(lua_State* _) { State st(_); UserData ud = st[1]; Table mtab = ud.getmetatable(); lua_pushnil(_); lua_copy(_, 2, -1); lua_gettable(_, mtab.index); if (st[-1].isnil()) { if (!st[2].isstr()) { return 0; } const char *key = st[2]; st.push(std::string("get_") + key); lua_gettable(_, mtab.index); if (st[-1].isfun()) { Closure getter = st[-1]; getter(ud).call(1); return 1; } else { return 0; } } else { return 1; } } static int setter(lua_State* _) { State st(_); if (!st[2].isstr()) return 0; UserData ud = st[1]; const char *key = st[2]; Table mtab = ud.getmetatable(); auto setter_candidate = mtab[std::string("set_") + key]; if (!setter_candidate.isfun()) return 0; Closure setter = setter_candidate; setter(ud, st[3]); return 0; } }; template void Class_::setupAccessor() { if (!hasReadAttribute && !hasWriteAttribute) return; if (hasReadAttribute) { mtab["__index"] = Closure( state.push(CClosure(ClassAccessorHelper::getter))); } if (hasWriteAttribute) { mtab["__newindex"] = Closure( state.push(CClosure(ClassAccessorHelper::setter))); } } template template Class_& Class_::attribute(const std::string& name, T Class::*mp, unsigned perm) { if (perm & Read) { hasReadAttribute = true; mtab[std::string("get_") + name] = state.newCallable([mp](UserData&& ud) { auto& ref = ud.to(); return ref.*mp; }); } if (perm & Write) { hasWriteAttribute = true; mtab[std::string("set_") + name] = state.newCallable( [mp](UserData&& ud, const T& val) { auto& ref = ud.to(); ref.*mp = val; return std::move(ud); } ); } return *this; } template template typename std::enable_if< std::is_member_function_pointer::value, Class_&>::type Class_::def(const std::string& method, T method_ptr) { typedef typename boost::function_types::parameter_types::type full_para_t; typedef typename std::remove_reference< typename boost::mpl::at_c::type>::type this_t; typedef typename boost::mpl::pop_front::type para_t; typedef typename detail::ParameterListTransformer< detail::MemberFunctionWrapper, boost::mpl::pair, para_t>::type Wrapper; mtab[method] = state.newCallable(Wrapper(method_ptr)); return *this; } template template typename std::enable_if< !std::is_member_function_pointer::value, Class_&>::type Class_::def(const std::string& method, T callable) { mtab[method] = state.newCallable(callable); return *this; } template template Class_& Class_::init() { std::string mkey = std::to_string(uuid); Table constructor = state.newTable(); constructor["__call"] = state.newCallable( [mkey](State& st, Table&& tab, Args&&... args) { UserData ud = st.newUserData(std::forward(args)...); ud.setmetatable(mkey); return ud; } ); constructor["__metatable"] = Nil(); mod.setmetatable(constructor); return *this; } template Class_::Class_(const std::string& name, State& state) : name(name), state(state), mod(state.newTable()), mtab(state.newTable()), uuid(reinterpret_cast(this)) { // initialize metatable mod["className"] = name; mtab["__metatable"] = Nil(); mtab["__index"] = mtab; state.registry()[std::to_string(uuid)] = mtab; } inline State::State(const State& o) : ptr_(o.ptr_) {} class NewState : public State { public: NewState(); virtual ~NewState(); }; inline State::State(lua_State *p) : ptr_(p) { } inline lua_State* State::ptr() { return ptr_; } /* State with a new lua_State */ inline NewState::NewState() : State(luaL_newstate()) { } inline NewState::~NewState() { lua_close(ptr()); } inline Table::Table(lua_State* st, int i) : state(st), index(lua_absindex(st,i)) {} inline Table::~Table() { detail::cleanup(state, index); } inline UserData::~UserData() { detail::cleanup(state, index); } inline Closure::~Closure() { detail::cleanup(state, index); } template Variant::type> Table::operator[](const T& k) { return Variant::type>(this, k); } inline void Table::setmetatable(const Table& metatab) { if (!VarPusher
::push(state, metatab)) { throw RuntimeError("cannot push metatable"); } lua_setmetatable(state, index); } struct NoMetatableError : public RuntimeError { NoMetatableError() : RuntimeError("no metatable exists") {} }; inline Table Table::getmetatable() { if (!lua_getmetatable(state, index)) { throw NoMetatableError(); } return Table(state, -1); } template void detail::HasMetaTable::setmetatable(const Table& metatab) { Sub* p = static_cast(this); { detail::Guard gd; gd.status = VarPusher
::push(p->state, metatab); } lua_setmetatable(p->state, p->index); } template void detail::HasMetaTable::setmetatable(const std::string& registry_entry) { Sub* p = static_cast(this); lua_pushnil(p->state); lua_copy(p->state, p->index, -1); luaL_setmetatable(p->state, registry_entry.c_str()); } template void detail::HasMetaTable::checkmetatable(const std::string& registry_entry) { Sub* p = static_cast(this); State st(p->state); try { Table mtab = getmetatable(); Table target_mtab = st.registry()[registry_entry]; if (target_mtab != mtab) { st.error(std::string("expect a userdata (") + registry_entry + ")"); } } catch (NoMetatableError& e) { st.error(std::string("userdata has no metatable, expect") + registry_entry); } } template Table detail::HasMetaTable::getmetatable() { Sub* p = static_cast(this); if (!lua_getmetatable(p->state, p->index)) { throw NoMetatableError(); } return Table(p->state, -1); } template struct CallLambda; template struct TypeChecker { typedef typename boost::mpl::at_c::type Elem; typedef VarProxy::type> Getter; static_assert(Getter::tid >= 0, "not a valid type"); static void check(State& st, int offset) { TypeChecker::check(st, offset); int rtid = st[n+offset].type(); if (rtid != Getter::tid) { st.error(std::string("bad argument#") + std::to_string(n) + " (" + st.typerepr(Getter::tid) + " expected, got " + st.typerepr(rtid) + ")"); } } }; template struct IsSingleReturnValue { enum { value = !std::is_same::type, detail::PlaceHolder>::value }; }; template struct SingleReturn { static void collect(State& st, T&& ret) { st[1] = ret; } enum { value = 1 }; }; template struct MultiReturnUnpack { static void unpack(State& st, Tuple&& ret) { st[n+1] = std::move(std::get(ret)); MultiReturnUnpack::unpack(st, std::forward(ret)); } }; template struct MultiReturnUnpack { static void unpack(State& st, Tuple&& ret) {} }; template struct MultiReturn { enum { value = std::tuple_size::value }; static void collect(State& st, T&& ret) { MultiReturnUnpack::unpack(st, std::forward(ret)); } }; template struct ReturnValue { typedef typename std::conditional::value, SingleReturn, MultiReturn>::type type; enum { value = type::value }; static void collect(State& st, T&& ret) { type::collect(st, std::forward(ret)); } }; template<> struct ReturnValue { enum { value = 0 }; }; template struct TypeChecker { static void check(State& st, int offset) {} }; template struct ToLambda { typedef decltype(&F::operator()) lambda_t; typedef typename boost::function_types::parameter_types< lambda_t>::type fullpara_t; // paramter list, include the leading State typedef typename boost::mpl::pop_front::type para_t; }; template struct ToLambda { static_assert(std::is_function::value, "F* should be a function pointer"); typedef F lambda_t; typedef typename boost::function_types::parameter_types< lambda_t>::type para_t; }; template struct IsCanonicalCallable { enum { value = std::is_same::para_t, 0>::type>::value }; }; namespace detail { template struct CanonicalWrapper { Callable callable; CanonicalWrapper(Callable callable) : callable(callable) { } typename boost::function_types::result_type< typename ToLambda::lambda_t>::type operator()(State&, Args&&... args) { return callable(std::forward(args)...); } }; } template struct CanonicalCallable { typedef typename ToLambda::para_t para_t; typedef typename detail::ParameterListTransformer::type type; }; // callables SHOULD carry State& as its first argument, if not, we would // add one through this template template struct ToCanonicalCallable { typedef typename std::conditional::value, Callable, typename CanonicalCallable::type>::type type; }; template struct CallableCall { typedef typename ToLambda::para_t para_t; typedef typename boost::function_types::result_type< typename ToLambda::lambda_t>::type result_t; typedef ReturnValue RetType; typedef boost::mpl::size nargs_t; struct NoRet { static void call(C& c, State& st) { CallLambda::call(c, st, RetType::value); } }; struct HasRet { static void call(C& c, State& st) { RetType::collect(st, CallLambda::call(c, st, RetType::value)); } }; typedef typename std::conditional::type Ret; static void call(C c, lua_State* st) { State state(st); // type checking TypeChecker::value - 1> ::check(state, RetType::value); Ret::call(c, state); } }; templatestruct CallLambda { static typename CallableCall::result_t call(C& func, State& st, int offset) { return func(st); }}; // special case, c function has no argument #ifndef LUAMM_LAMBDA_PARANUMBER #define LUAMM_LAMBDA_PARANUMBER 15 #endif #define LUAMM_ARGPACK(n) st[n + offset - 1] #define LUAMM_ARGLIST(z, n, _) LUAMM_ARGPACK(BOOST_PP_ADD(n, 2)), #define LUAMM_ARG(n) BOOST_PP_REPEAT(BOOST_PP_SUB(n, 1), LUAMM_ARGLIST, ) LUAMM_ARGPACK(BOOST_PP_INC(n)) #define LUAMM_TEMPL(_a, n, _b) templatestruct CallLambda {\ static typename CallableCall::result_t call(C& func, State& st, int offset) {\ return func(st, LUAMM_ARG(BOOST_PP_SUB(n,1))); }}; BOOST_PP_REPEAT_FROM_TO(2, LUAMM_LAMBDA_PARANUMBER, LUAMM_TEMPL,) #undef LUAMM_ARGPACK #undef LUAMM_ARGLIST #undef LUAMM_ARG #undef LUAMM_TEMPL #undef LUAMM_LAMBDA_PARANUMBER typedef std::function lua_Lambda; namespace detail { struct NewCallableHelper { static int luamm_cclosure(lua_State* _) { State st(_); UserData ud = st[st.upvalue(1)]; auto& lambda = ud.to(); return lambda(_); } static int luamm_cleanup(lua_State* _) { State st(_); UserData ud = st[1]; auto& lambda = ud.to(); lambda.~lua_Lambda(); return 0; } }; } template Closure State::newCallable(F func, int extra_upvalues) { typename ToCanonicalCallable::type canonical_callable(func); typedef ReturnValue::result_t> RetType; lua_Lambda lambda = [canonical_callable](lua_State* st) -> int { // allocate a slot for return value State lua(st); const int rets = RetType::value; // shift +1 to allocate slot for return value for (auto i = 1; i <= rets; i++) { lua_pushboolean(st, 1); lua_insert(st, i); } try { CallableCall::call(canonical_callable, st); } catch (std::exception& e) { lua.error(e.what()); } // leave return value one the stack, wipe out other things lua_settop(st, rets); return rets; }; push(CClosure(detail::NewCallableHelper::luamm_cclosure, 1 + extra_upvalues)); Closure cl = this->operator[](-1); UserData ud = newUserData(lambda); { Table reg = registry(); auto gctab = reg["LUAMM_COMMON_GC"]; if (!gctab.istab()) { Table mtab = newTable(); mtab["__gc"] = CClosure(detail::NewCallableHelper::luamm_cleanup); gctab = mtab; } Table mtab = gctab; ud.setmetatable(mtab); } cl[1] = ud; return cl; } } // end namespace #define LUAMM_MODULE(name, state) extern "C" int luaopen_##name(lua_State *state) #define LUAMM_MODULE_RETURN(state, tab) state[1] = tab; state.settop(1); return 1; #endif