///////////////////////////////////////////////////////// // // // 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 eng::string for key, then LuaStack will // automatically convert it. In general, class LuaStack can // convert lua_Integer, lua_Number, eng::string, bool, and LuaNil. // // On output, LuaStack can convert lua_Integers, lua_Numbers, and // eng::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) // eng::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 #include "wrap-string.hpp" #include "wrap-set.hpp" #include extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "eris.h" } class LuaSlot : public eng::nevernew { 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 // We use lightuserdata to store 'tokens': short // strings of 8 characters or less. These tokens // are useful as unique markers. The 8 characters // are packed into a uint64. struct LuaToken { private: static constexpr uint64_t literal_to_token(const char *str) { uint64_t result = 0; for (int i = 0; i < 8; i++) { unsigned char c = *str; result = (result << 8) + c; if (*str) str++; } return result; } public: uint64_t value; template LuaToken(T arg) = delete; constexpr LuaToken(const char *str) : value(literal_to_token(str)) {} LuaToken(uint64_t v) : value(v) {} LuaToken(void *v) : value((uint64_t)v) {} LuaToken() : value(0) {} bool empty() const { return value == 0; } bool operator ==(const LuaToken &other) const { return value == other.value; } void *voidvalue() const { return (void*)value; } eng::string str() const; }; class LuaStack : public eng::nevernew { 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 eng::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } void push_any_value(std::string_view s) const { lua_pushlstring(L_, s.data(), 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); } void push_any_value(LuaToken token) const { lua_pushlightuserdata(L_, (void*)(token.value)); } // 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) { } void argerr(const char *arg, const char *tp) const; 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: // This is the largest integer that can be stored in a lua_Number. // In other words, any 53-bit number can be stored. static const int64_t MAXINT = 0x001FFFFFFFFFFFFF; static lua_State *newstate (lua_Alloc allocf); 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 isinteger(LuaSlot s) const; bool isint(LuaSlot s) const; 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 iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s) != 0; } 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 istoken(LuaSlot s) const { return lua_islightuserdata(L_, s) != 0; } void checktable(LuaSlot s, const char *n) const { if (!istable(s)) argerr(n, "table"); } void checkstring(LuaSlot s, const char *n) const { if (!isstring(s)) argerr(n, "string"); } void checknumber(LuaSlot s, const char *n) const { if (!isnumber(s)) argerr(n, "number"); } void checkinteger(LuaSlot s, const char *n) const { if (!isinteger(s)) argerr(n, "integer"); } void checkint(LuaSlot s, const char *n) const { if (!isint(s)) argerr(n, "int"); } void checkthread(LuaSlot s, const char *n) const { if (!isthread(s)) argerr(n, "thread"); } void checkfunction(LuaSlot s, const char *n) const { if (!isfunction(s)) argerr(n, "function"); } void checkcfunction(LuaSlot s, const char *n) const { if (!iscfunction(s)) argerr(n, "cfunction"); } void checkboolean(LuaSlot s, const char *n) const { if (!isboolean(s)) argerr(n, "boolean"); } void checknil(LuaSlot s, const char *n) const { if (!isnil(s)) argerr(n, "nil"); } void checktoken(LuaSlot s, const char *n) const { if (!istoken(s)) argerr(n, "token"); } bool ckboolean(LuaSlot s) const; lua_Integer ckinteger(LuaSlot s) const; int ckint(LuaSlot s) const; lua_Number cknumber(LuaSlot s) const; eng::string ckstring(LuaSlot s) const; std::string_view ckstringview(LuaSlot s) const; lua_State *ckthread(LuaSlot s) const; LuaToken cktoken(LuaSlot s) const; void clearmetatable(LuaSlot tab) const; void setmetatable(LuaSlot tab, LuaSlot mt) const; bool getmetatable(LuaSlot mt, LuaSlot tab) 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 cleartable(LuaSlot tab, bool clearmeta) const; int rawlen(LuaSlot val) const; int next(LuaSlot tab, LuaSlot key, LuaSlot value) const; // Return true if the classname is legal. bool validclassname(LuaSlot value) const; static bool validclassname(std::string_view cname); // Return the class name if x is a valid classtab. // Otherwise, returns empty string. If nonempty, the // result is guaranteed to be a validclassname. // This can also function as an "isclass" operator. eng::string classname(LuaSlot x) const; // Look up a class. // If there is a problem, returns an error message. // There are lots of error conditions, including such things // as no such class, corrupted class, classname invalid, etc. eng::string getclass(LuaSlot tab, LuaSlot name) const; eng::string getclass(LuaSlot tab, std::string_view name) const; // Create a class, or look up an existing class. // WARNING: this routine assert-fails if the parameter is not // a valid classname. Check the classname before calling this! void makeclass(LuaSlot tab, LuaSlot name) const; void makeclass(LuaSlot tab, std::string_view name) const; // Get the ID of a tangible. It's a little weird to put this in // this module. int64_t tanid(LuaSlot tab) const; // Return true if the value is a sortable key (string, number, or boolean). bool issortablekey(LuaSlot s) const; // Move a sortable key (string, number, or boolean) from one lua // environment to another lua environment. WARNING: this assert-fails // if the value is not a sortable key. 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(LuaSlot target, VT value) const { push_any_value(value); lua_replace(L_, target); } template void rawget(LuaSlot target, LuaSlot tab, KT key) const { push_any_value(key); lua_rawget(L_, tab); lua_replace(L_, target); } void rawget(LuaSlot target, LuaSlot tab, int key) const { lua_rawgeti(L_, tab, key); lua_replace(L_, 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; // Return true if the int64 value can be stored as a lua number. static bool int64_storable(int64_t v) { return (v <= MAXINT) && (v >= -MAXINT); } }; // This is a helper class to help parse tables full of keywords. class LuaKeywordParser { struct cmp_char { bool operator () (const char *s1, const char *s2) const { return strcmp(s1, s2) < 0; }; }; private: bool not_table_; lua_State *L_; int slot_; eng::set parsed_; void init(const lua_State *L, int slot); public: // If the slot is not a table, sets the not_table // flag and creates a dummy table in the slot. LuaKeywordParser(lua_State *L, int slot); LuaKeywordParser(const LuaStack &LS, LuaSlot slot) : LuaKeywordParser(LS.state(), slot.index()) {} // Fetch a value from the table. This never throws. // Return true if the value is non-nil. bool parse(LuaSlot slot, const char *kw); // Check if there were any errors. If so, return an // error message. eng::string final_check(); // Check if there are any errors. If so, throw a lua error. void final_check_throw(); // Fetch the state pointer. lua_State *state() const { return L_; } }; class LuaConstantReg : public eng::nevernew { private: const char *name_; const char *docs_; LuaToken tokenvalue_; lua_Number numbervalue_; LuaConstantReg *next_; public: static LuaConstantReg *All; LuaConstantReg(const char *name, const char *docs, LuaToken tokenvalue, lua_Number numbervalue); const char *get_name() const { return name_; } const char *get_docs() const { return docs_; } LuaToken get_tokenvalue() const { return tokenvalue_; } lua_Number get_numbervalue() const { return numbervalue_; } LuaConstantReg *next() const { return next_; } }; class LuaFunctionReg : public eng::nevernew { private: const char *name_; const char *args_; const char *docs_; bool sandbox_; lua_CFunction func_; LuaFunctionReg *next_; public: static LuaFunctionReg *All; LuaFunctionReg(const char *name, const char *args, const char *docs, bool sand, lua_CFunction f); 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_; } bool get_sandbox() const { return sandbox_; } LuaFunctionReg *next() const { return next_; } void set_func(lua_CFunction fn) { func_ = fn; } }; #define LuaTokenConstant(name, tvalue, docs) \ LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0); #define LuaNumberConstant(name, nvalue, docs) \ LuaConstantReg reg_##name(#name, docs, LuaToken(), nvalue); #define LuaDefine(name, args, docs) \ int lfn_##name(lua_State *L); \ LuaFunctionReg reg_##name(#name, args, docs, false, lfn_##name); \ int lfn_##name(lua_State *L) #define LuaSandbox(name, args, docs) \ int lfn_##name(lua_State *L); \ LuaFunctionReg reg_##name(#name, args, docs, true, lfn_##name); \ int lfn_##name(lua_State *L) #define LuaDefineBuiltin(name, args, docs) \ LuaFunctionReg reg_##name(#name, args, docs, false, nullptr); #define LuaSandboxBuiltin(name, args, docs) \ LuaFunctionReg reg_##name(#name, args, docs, true, 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) { eng::string _s1_(x); eng::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