///////////////////////////////////////////////////////// // // // 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, "arguments", "documentation") { // ... // } // // 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 "eris.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; class LuaUpvalue : public LuaSlot { public: LuaUpvalue(int n) { index_ = lua_upvalueindex(n); } }; class LuaNilMarker {}; extern LuaNilMarker LuaNil; class LuaNewTableMarker {}; extern LuaNewTableMarker LuaNewTable; using LuaDeleterFn = void (*)(void *); using LuaTypeTag = lua_CFunction; template int LuaTypeTagValue(lua_State *L) { return 0; } // Lua table types. These deliberately do not overlap // with lua type values. // #define LUA_TT_GENERAL 16 #define LUA_TT_REGISTRY 17 #define LUA_TT_GLOBALENV 18 #define LUA_TT_TANGIBLE 19 #define LUA_TT_TANGIBLEMETA 20 #define LUA_TT_DEADTANGIBLE 21 #define LUA_TT_GLOBALDB 22 #define LUA_TT_CLASS 23 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; void pop_any_value(lua_Number &s) const; void pop_any_value(std::string &s) const; // 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() { } 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 to function"); } assign_slots(argpos_, varpos_, retpos_, stackslots...); clear_frame(); } ~LuaStack() {}; int result(); public: lua_State *state() const { return L_; } 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; } bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s) != 0; } bool issortablekey(LuaSlot s) const; 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); } bool ckboolean(LuaSlot s) const; lua_Integer ckinteger(LuaSlot s) const; int ckint(LuaSlot s) const; lua_Number cknumber(LuaSlot s) const; std::string ckstring(LuaSlot s) const; lua_State *ckthread(LuaSlot s) const; void clearmetatable(LuaSlot tab) const; void setmetatable(LuaSlot tab, LuaSlot mt) const; bool getmetatable(LuaSlot mt, LuaSlot tab) const; void checknometa(LuaSlot index) const; void newtable(LuaSlot target) const; void createtable(LuaSlot target, int narr, int nrec) const; lua_State *newthread(LuaSlot target) const; void getglobaltable(LuaSlot gltab) const; void makesubtable(LuaSlot sub, LuaSlot tab, const char *name) const; void cleartable(LuaSlot tab, bool clearmeta) const; int rawlen(LuaSlot val) const; int next(LuaSlot tab, LuaSlot key, LuaSlot value) const; void getclass(LuaSlot tab, LuaSlot name) const; void makeclass(LuaSlot tab, LuaSlot name) const; void makeclass(LuaSlot tab, const char *name) const; void makeclass(LuaSlot tab, const std::string &name) const; std::string classname(LuaSlot tab) const; int64_t tanid(LuaSlot tab) const; // There's no 'isclass' operator, but 'classname' will return empty // string if tab is not a valid class. void movesortablekey(LuaSlot val, LuaStack &other, LuaSlot otherslot); bool rawequal(LuaSlot v1, LuaSlot v2) const { return lua_rawequal(L_, v1, v2); } bool rawequal(LuaSlot v1, const char *name) const { push_any_value(name); bool result = lua_rawequal(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 rawget(RT &target, LuaSlot tab, int key) const { lua_rawgeti(L_, tab, key); 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, int key, VT value) const { push_any_value(value); lua_rawseti(L_, tab, key); } // Lua flagbits manipulation: Table types. int gettabletype(LuaSlot tab) const; void settabletype(LuaSlot tab, int t) const; // If slot is a table, returns the LUA_TT_XXX table type. // If slot is not a table, returns the LUA_TXXX general type. int xtype(LuaSlot slot) const; // Lua flagbits manipulation: visited bit. bool getvisited(LuaSlot tab) const; void setvisited(LuaSlot tab, bool visited) const; }; class LuaFunctionReg { private: const char *name_; const char *args_; const char *docs_; lua_CFunction func_; LuaFunctionReg *next_; static LuaFunctionReg *LuaFunctionRegistry; public: using List = std::vector; LuaFunctionReg(const char *name, const char *args, const char *docs, lua_CFunction f); static List all(); static const LuaFunctionReg *lookup(lua_CFunction fn); const char *get_name() const { return name_; } const char *get_args() const { return args_; } const char *get_docs() const { return docs_; } lua_CFunction get_func() const { return func_; } void set_func(lua_CFunction fn) { func_ = fn; } }; #define LuaDefine(name, args, docs) \ int lfn_##name(lua_State *L); \ LuaFunctionReg reg_##name(#name, args, docs, lfn_##name); \ int lfn_##name(lua_State *L) #define LuaDefineBuiltin(name, args, docs) \ LuaFunctionReg reg_##name(#name, args, docs, nullptr); #define LuaStringify(x) #x #define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); } #define LuaAssertStrEq(L, x, y) { std::string _s1_ = (x); std::string _s2_ = (y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); } #endif // LUASTACK_HPP