///////////////////////////////////////////////////////// // // // LUASTACK // // The standard lua C API asks you to work with a stack machine. You're supposed // to manually push and pop values on the lua stack. I find this difficult, I // find it hard to remember what stack position contains what value. // // To make it easier, I've created this module, "LuaStack." This module // creates the illusion that you're working with local variables that contain // lua values. // // Of course, this is all using the lua stack under the covers. Lua // local variables are actually just lua stack addresses. But that's // all kept fairly well hidden. When you use Lua local variables, and // the accessors inside class LuaStack, it appears that you're // manipulating data using local variables instead of using a stack. // For people like me, that's easier to think about. // // Here's an example. // // let's say you have a function that takes two arguments // ARG1 and ARG2, has a single return value RET1, and needs two local // variables LOC1 and LOC2. We would declare it like this: // // int myfunc(lua_State *L) { // // LuaArg arg1, arg2; // Declare local variables to hold the arguments. // LuaRet ret1; // Declare local variables to hold the return values. // LuaVar loc1, loc2, loc3; // Declare local variables for other purposes. // // // Assign every local var a stack index. // LuaStack LS(L, arg1, arg2, ret1, loc1, loc2, loc3); // // // manipulate the data in the lua local variables... // LS.rawget(loc1, arg1, arg2); // ... etc ... // } // // Class LuaArg, LuaRet, and LuaVar are all lua local variables. // The luastack constructor assigns each one of them a position on // the lua stack. It also makes sure that the arguments are in // the LuaArg variables, and it makes sure that the LuaRet values // are the only thing left on the stack at return time. // // Class LuaStack provides a complete catalog of accessors // like 'rawget' - roughly speaking, it provides equivalents to // every major accessor in the lua API. However, the accessors // provided by LuaStack take input and output from lua locals, not // from the stack. For example, consider this: // // LS.rawget(value, tab, key); // // In the above, value, tab, and key should be lua local variables. // This does a rawget on 'table', with the specified 'key', and // stores the result in 'value'. Nothing is added to or removed // from the lua stack. In general, none of the accessors in class // LuaStack add anything to the stack, or pop anything from the // stack. // // Class LuaStack can also do automatic type conversions. For // example, suppose you do this: // // LS.rawget(value, tab, key); // // Nominally, you would expect value, tab, and key to be lua local // variables. But if you pass a std::string for key, then LuaStack will // automatically convert it. In general, class LuaStack can // convert lua_Integer, lua_Number, std::string, bool, and LuaNil. // // On output, LuaStack can convert lua_Integers, lua_Numbers, and // std::strings. In this case, strict type checking is done. If // there is a type mismatch, a lua error is thrown. // // You can use the operator 'set' to assign a value to a lua local // variable: // // LS.set(val1, val2); // // This is actually a copy operation that copies from one lua local // variable to another. But using type conversions, it can also be // used to assign arbitrary values to lua local variables, or to // get values from lua local variables. // // Passing LuaNewTable as an input will cause a new table to be // created before calling the specified operation. // // ///////////////////////////////////////////////////////// // // // LuaStack type checking // // LuaStack contains accessors for type checking. These include: // // bool LuaStack::isnumber(LuaSlot s) // bool LuaStack::isinteger(LuaSlot s) // bool LuaStack::isstring(LuaSlot s) // etc... // // And it also contains operations that throw errors: // // void LuaStack::checknumber(LuaSlot s) // void LuaStack::checkinteger(LuaSlot s) // void LuaStack::checkstring(LuaSlot s) // etc... // // These are different from the lua builtins in that they are strict. // For example, 'isnumber' only returns true if the value in the // lua local variable is already a number. No conversions are done. // // These functions do checking and also conversion at the same time: // // lua_Integer LuaStack::ckinteger(LuaSlot s) // lua_Number LuaStack::cknumber(LuaSlot s) // std::string LuaStack::ckstring(LuaSlot s) // lua_State *LuaStack::ckthread(LuaSlot s) // // Like the other operations, they are strict. // // // LUADEFINE // // LuaDefine is a macro that defines a C function which is // exposed to lua. It creates a global registry of functions // created with LuaDefine. You use it like so: // // LuaDefine(function_name, "modebits") { // ... // } // // This macroexpands into a function definition and a function // registration. The function definition looks like this: // // int function_name(lua_State *L) { // ... // } // // The macro expansion generates this function definition, but it // also generates a "registration object" whose constructor puts // this function into a global registry of lua-callable C functions. // This global registry is later used to inject these C functions // into the lua intepreter. The mode is a string that contain // the following characters: // // c - create a class, and put a function into it. // f - create a global function not inside a class. // // ///////////////////////////////////////////////////////// #ifndef LUASTACK_HPP #define LUASTACK_HPP extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "luajit.h" } #include #include class LuaSlot { protected: int index_; private: inline operator int() const { return index_; } public: LuaSlot() { index_ = 0; } int index() const { return index_; } friend class LuaStack; }; class LuaArg : public LuaSlot {}; class LuaRet : public LuaSlot {}; class LuaVar : public LuaSlot {}; class LuaSpecial : public LuaSlot { public: LuaSpecial(int n) { index_ = n; } }; extern LuaSpecial LuaRegistry; extern LuaSpecial LuaGlobals; class LuaUpvalue : public LuaSlot { public: LuaUpvalue(int n) { index_ = lua_upvalueindex(n); } }; class LuaNilMarker {}; extern LuaNilMarker LuaNil; class LuaNewTableMarker {}; extern LuaNewTableMarker LuaNewTable; class LuaDiscardMarker {}; extern LuaDiscardMarker LuaDiscard; struct LuaAcceptNilNumber { lua_Number v; }; struct LuaAcceptNilInteger { lua_Integer v; }; struct LuaAcceptNilString { std::string v; }; inline LuaAcceptNilNumber &LuaAcceptNil(lua_Number &x) { return *(LuaAcceptNilNumber *)(&x); } inline LuaAcceptNilInteger &LuaAcceptNil(lua_Integer &x) { return *(LuaAcceptNilInteger *)(&x); } inline LuaAcceptNilString &LuaAcceptNil(std::string &x) { return *(LuaAcceptNilString *)(&x); } using LuaDeleterFn = void (*)(void *); using LuaTypeTag = lua_CFunction; template int LuaTypeTagValue(lua_State *L) { return 0; } class LuaStack { private: int narg_; int ngap_; int nvar_; int nret_; int argpos_; int gappos_; int varpos_; int retpos_; int rettop_; int finaltop_; lua_State *L_; template void count_slots(LuaArg &v, SS & ... stackslots) { count_slots(stackslots...); } template void count_slots(LuaVar &v, SS & ... stackslots) { count_slots(stackslots...); } template void count_slots(LuaRet &v, SS & ... stackslots) { count_slots(stackslots...); } template void count_slots() { count_slots_finalize(NARG, NVAR, NRET); } void count_slots_finalize(int narg, int nvar, int nret); template void assign_slots(int argp, int varp, int retp, LuaArg &v, SS & ... stackslots) { v.index_ = argp; assign_slots(argp + 1, varp, retp, stackslots...); } template void assign_slots(int argp, int varp, int retp, LuaVar &v, SS & ... stackslots) { v.index_ = varp; assign_slots(argp, varp+1, retp, stackslots...); } template void assign_slots(int argp, int varp, int retp, LuaRet &v, SS & ... stackslots) { v.index_ = retp; assign_slots(argp, varp, retp+1, stackslots...); } void assign_slots(int argp, int varp, int retp) {} void clear_frame(); private: // Push any value on the stack, by type. void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); } void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); } void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); } void push_any_value(const std::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } void push_any_value(const char *s) const { lua_pushstring(L_, s); } void push_any_value(float s) const { lua_pushnumber(L_, s); } void push_any_value(double s) const { lua_pushnumber(L_, s); } void push_any_value(int s) const { lua_pushinteger(L_, s); } void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); } void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); } void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); } // Pop any value off the stack, by type. void pop_any_value(LuaSlot &s) const { lua_replace(L_, s); } void pop_any_value(lua_Integer &s) const { s = luaL_ckinteger(L_, -1); lua_pop(L_, 1); } void pop_any_value(lua_Number &s) const { s = luaL_cknumber(L_, -1); lua_pop(L_, 1); } void pop_any_value(std::string &s) const; void pop_any_value(LuaAcceptNilNumber &s) const; void pop_any_value(LuaAcceptNilInteger &s) const; void pop_any_value(LuaAcceptNilString &s) const; void pop_any_value(LuaDiscardMarker &s) const { lua_pop(L_, 1); } // Push multiple values on the stack, in order, by type. template void push_any_values(T0 arg0, T... args) { push_any_value(arg0); push_any_values(args...); } void push_any_values() { } // Call the CFunction, pushing and popping arguments appropriately. template void call_cfunction(int otop, LuaSlot s, T... args) { call_cfunction(otop, args...); pop_any_value(s); } template void call_cfunction(int otop, lua_Integer &s, T... args) { call_cfunction(otop, args...); pop_any_value(s); } template void call_cfunction(int otop, lua_Number &s, T... args) { call_cfunction(otop, args...); pop_any_value(s); } template void call_cfunction(int otop, std::string &s, T... args) { call_cfunction(otop, args...); pop_any_value(s); } template void call_cfunction(int otop, LuaDiscardMarker &s, T... args) { call_cfunction(otop, args...); pop_any_value(s); } template void call_cfunction(int otop, lua_CFunction fn, T... args) { push_any_values(args...); int nret = fn(L_); check_nret(NRET, otop, nret); } // Check number of return values: xpected number of return values, // original stack top, and number of declared return values. void check_nret(int xnret, int otop, int nret) const; // Tagged pointers: we expect all userdata to be tagged pointers. // This starts with a void pointer, then a type tag that identifies the // underlying C++ type, then a deleter function. In addition, there will // be a metatable that contains the type name as a string and a collect // function. struct TaggedPointer { void *ptr; LuaTypeTag tag; LuaDeleterFn del; }; static int collect_tagged_pointer(lua_State *L); void make_tagged_pointer(LuaSlot target, void *ptr, LuaTypeTag tag, LuaDeleterFn del); void clear_tagged_pointer(LuaSlot target); template static void delete_pointer(void *p) { delete (T*)p; } static void do_not_delete(void *p) { } public: template LuaStack(lua_State *L, SS & ... stackslots) { L_ = L; count_slots<0, 0, 0>(stackslots...); if (lua_gettop(L) < narg_) { luaL_error(L, "not enough arguments on stack"); } assign_slots(argpos_, varpos_, retpos_, stackslots...); clear_frame(); } ~LuaStack() {}; int result(); public: int type(LuaSlot s) const { return lua_type(L_, s); } void checktype(LuaSlot s, int type) const { luaL_checktype(L_, s, type); } bool istable(LuaSlot s) const { return lua_type(L_, s) == LUA_TTABLE; } bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } bool isfunction(LuaSlot s) const { return lua_type(L_, s) == LUA_TFUNCTION; } bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; } bool isnil(LuaSlot s) const { return lua_type(L_, s) == LUA_TNIL; } void checktable(LuaSlot index) const { checktype(index, LUA_TTABLE); } void checkstring(LuaSlot index) const { checktype(index, LUA_TSTRING); } void checknumber(LuaSlot index) const { checktype(index, LUA_TNUMBER); } void checkthread(LuaSlot index) const { checktype(index, LUA_TTHREAD); } void checkfunction(LuaSlot index) const { checktype(index, LUA_TFUNCTION); } void checkboolean(LuaSlot index) const { checktype(index, LUA_TBOOLEAN); } void checknil(LuaSlot index) const { checktype(index, LUA_TNIL); } lua_Integer ckinteger(LuaSlot s) const { return luaL_ckinteger(L_, s); } double cknumber(LuaSlot s) const { return luaL_cknumber(L_, s); } std::string ckstring(LuaSlot s) const; lua_State *ckthread(LuaSlot s) const { return luaL_ckthread(L_, s); } void clearmetatable(LuaSlot tab) const; void setmetatable(LuaSlot tab, LuaSlot mt) const; void getmetatable(LuaSlot mt, LuaSlot tab) const; void checknometa(LuaSlot index) const; void newtable(LuaSlot target) const; void makesubtable(LuaSlot sub, LuaSlot tab, const char *name) const; void setlightuserdata(LuaSlot target, void *p) const; template void newpointer(LuaSlot target, T *ptr, bool autodel) { make_tagged_pointer(target, ptr, LuaTypeTagValue, autodel ? delete_pointer : do_not_delete); } template T *touserdata(LuaSlot target) { TaggedPointer *tp = (TaggedPointer*)lua_touserdata(L_, target); LuaTypeTag tag = LuaTypeTagValue; if ((tp == 0) || (tp->tag != tag)) return 0; return (T*)(tp->ptr); } template T *ckuserdata(LuaSlot target) { TaggedPointer *tp = (TaggedPointer*)lua_touserdata(L_, target); LuaTypeTag tag = LuaTypeTagValue; if ((tp == 0) || (tp->tag != tag) || (tp->ptr==0)) { luaL_error(L_, "wrong userdata type"); } return (T*)(tp->ptr); } void clearuserdata(LuaSlot target) { clear_tagged_pointer(target); } int next(LuaSlot tab, LuaSlot key, LuaSlot value) const; void makeclass(LuaSlot tab, LuaSlot name) const; void makeclass(LuaSlot tab, const char *name) const { push_any_value(name); LuaSpecial classname(lua_gettop(L_)); makeclass(tab, classname); lua_pop(L_, 1); } bool equal(LuaSlot v1, LuaSlot v2) const { return lua_equal(L_, v1, v2); } bool equal(LuaSlot v1, const char *name) const { push_any_value(name); bool result = lua_equal(L_, v1, -1); lua_pop(L_, 1); return result; } template void set(T1 &target, T2 value) const { push_any_value(value); pop_any_value(target); } template void gettable(RT &target, LuaSlot tab, KT key) const { push_any_value(key); lua_gettable(L_, tab); pop_any_value(target); } template void rawget(RT &target, LuaSlot tab, KT key) const { push_any_value(key); lua_rawget(L_, tab); pop_any_value(target); } template void rawset(LuaSlot tab, KT key, VT value) const { push_any_value(key); push_any_value(value); lua_rawset(L_, tab); } // template // void rawset(LuaSlot tab, const char *field, VT value) const { // push_any_value(value); // lua_rawset(L_, tab, field); // } // template // void rawget(RT &target, LuaSlot tab, const char *field) const { // lua_rawget(L_, tab, field); // pop_any_value(target); // } // Call invokes any C function. It pushes the arguments on the stack, // calls the cfunction, verifies that the number of return values is as // expected, and pops the return values into LuaVars. template void call(T&... args) { call_cfunction<0>(lua_gettop(L_), args...); } static void register_all_userdata(lua_State *L); }; class LuaFunctionReg { private: const char *mode_; const char *name_; lua_CFunction func_; LuaFunctionReg *next_; static LuaFunctionReg *LuaFunctionRegistry; public: using List = std::vector; LuaFunctionReg(const char *mode, const char *n, lua_CFunction f); static List all(); const char *get_mode() const { return mode_; } const char *get_name() const { return name_; } lua_CFunction get_func() const { return func_; } }; #define LuaDefine(name, mode) \ int name(lua_State *L); \ LuaFunctionReg reg_##name(mode, #name, name); \ int name(lua_State *L) #define LuaDefineType(name) \ LuaFunctionReg regt_##name("t", #name, LuaTypeTagValue) #define LuaStringify(x) #x #define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); } #endif // LUASTACK_HPP