///////////////////////////////////////////////////////// // // // LuaOldStack // // 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, "LuaOldStack." 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 LuaOldStack, 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. // LuaOldStack 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 LuaOldStack 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 LuaOldStack 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 LuaOldStack 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 // LuaOldStack add anything to the stack, or pop anything from the // stack. // // Class LuaOldStack 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 LuaOldStack will // automatically convert it. In general, class LuaOldStack can // convert lua_Integer, lua_Number, eng::string, bool, and LuaNil. // // On output, LuaOldStack 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. // // ///////////////////////////////////////////////////////// // // // LuaOldStack type checking // // LuaOldStack contains accessors for type checking. These include: // // bool LuaOldStack::isnumber(LuaSlot s) // bool LuaOldStack::isinteger(LuaSlot s) // bool LuaOldStack::isstring(LuaSlot s) // etc... // // And it also contains operations that throw errors: // // void LuaOldStack::checknumber(LuaSlot s) // void LuaOldStack::checkinteger(LuaSlot s) // void LuaOldStack::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 LuaOldStack::ckinteger(LuaSlot s) // lua_Number LuaOldStack::cknumber(LuaSlot s) // eng::string LuaOldStack::ckstring(LuaSlot s) // lua_State *LuaOldStack::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 LuaOldStack_HPP #define LuaOldStack_HPP #include "wrap-string.hpp" #include "wrap-set.hpp" #include #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 LuaCoreStack; friend class LuaOldStack; friend class LuaExtStack; }; 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. // enum LuaTableType { LUA_TT_GENERAL = LUA_NUMTAGS, LUA_TT_REGISTRY, LUA_TT_GLOBALENV, LUA_TT_TANGIBLE, LUA_TT_TANGIBLEMETA, LUA_TT_CLASS, LUA_TT_SENTINEL }; // World types enum. enum WorldType { WORLD_TYPE_MASTER = 1, WORLD_TYPE_PREDICTIVE = 2, }; // 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 LuaCoreStack : public eng::nevernew { protected: lua_State *L_; 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() { } void argerr(const char *arg, const char *tp) const; public: lua_State *state() const { return L_; } // 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); 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; // Create a tangible, or look up an existing tangible. // If the tangible doesn't exist yet, this creates a tangible stub. // It is possible to use World::tangible_make to transform a tangible // stub into a full blown tangible, and World::tangible_delete to turn // a full-blown tangible back into a stub. A stub doesn't have a // class or a thread table. void maketan(LuaSlot tab, int64_t id) const; // Return true if a tangible is empty (deleted or not yet created). bool tanblank(LuaSlot tab) const; // Get the ID of a tangible. 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, LuaCoreStack &other, LuaSlot otherslot); bool rawequal(LuaSlot v1, LuaSlot v2) const { return lua_rawequal(L_, v1, v2); } template bool rawequal(LuaSlot v1, VT value) const { push_any_value(value); 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 the world type (from the registry). WorldType world_type() const; // World types that are authoritative. static bool is_authoritative(WorldType t) { return (t == WORLD_TYPE_MASTER); } bool is_authoritative() { return is_authoritative(world_type()); } // Stop execution of this thread if in a nonauth model, // and if the thread is not a probe. void guard_nopredict(const char *fn); // 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); } }; class LuaOldStack : public LuaCoreStack { private: int narg_; int ngap_; int nvar_; int nret_; int argpos_; int gappos_; int varpos_; int retpos_; int rettop_; int finaltop_; private: 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) {} 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() { narg_ = NARG; nret_ = NRET; nvar_ = NVAR; ngap_ = NRET - NVAR - NARG; if (ngap_ < 0) ngap_ = 0; int argtop = lua_gettop(L_); argpos_ = argtop + 1 - NARG; gappos_ = argpos_ + NARG; varpos_ = gappos_ + ngap_; retpos_ = varpos_ + NVAR; rettop_ = retpos_ + NRET - 1; finaltop_ = argpos_ + NRET - 1; } public: template LuaOldStack(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...); lua_settop(L_, varpos_ - 1); for (int i = 0; i < nvar_ + nret_; i++) { lua_pushnil(L_); } } int result() { lua_settop(L_, rettop_); int i = finaltop_; for (int j = 0; j < nret_; j++) { lua_replace(L_, i); i -= 1; } lua_settop(L_, finaltop_); return nret_; } ~LuaOldStack() {}; }; class LuaDefStack : public LuaCoreStack { private: int nret_; int narg_; int nvar_; template void assign_slots(int retp, int argp, int varp, LuaRet &v, SS & ... stackslots) { v.index_ = retp; assign_slots(retp+1, argp, varp, stackslots...); } template void assign_slots(int retp, int argp, int varp, LuaArg &v, SS & ... stackslots) { v.index_ = argp; assign_slots(retp, argp + 1, varp, stackslots...); } template void assign_slots(int retp, int argp, int varp, LuaVar &v, SS & ... stackslots) { v.index_ = varp; assign_slots(retp, argp, varp+1, stackslots...); } void assign_slots(int retp, int argp, int varp) {} template void count_slots(LuaRet &v, SS & ... stackslots) { count_slots(stackslots...); } 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() { nret_ = NRET; narg_ = NARG; nvar_ = NVAR; } public: template LuaDefStack(lua_State *L, SS & ... stackslots) { L_ = L; count_slots<0, 0, 0>(stackslots...); if (lua_gettop(L_) != narg_) { luaL_error(L_, "function expects exactly %d arguments", narg_); } int tot = narg_ + nvar_ + nret_; lua_checkstack(L, tot + 20); for (int i = 0; i < nret_; i ++) { lua_pushnil(L_); lua_insert(L_, i + 1); } for (int i = 0; i < nvar_; i++) { lua_pushnil(L_); } assign_slots(1, 1 + nret_, 1 + nret_ + narg_, stackslots...); } ~LuaDefStack() { if (!lua_isthrowing(L_)) { lua_settop(L_, nret_); }; } }; class LuaExtStack : public LuaCoreStack { private: int oldtop_; int nvar_; template void assign_slots(int varp, LuaVar &v, SS & ... stackslots) { v.index_ = varp; assign_slots(varp+1, stackslots...); } void assign_slots(int varp) {} template void count_slots(LuaVar &v, SS & ... stackslots) { count_slots(stackslots...); } template void count_slots() { nvar_ = NVAR; } public: template LuaExtStack(lua_State *L, SS & ... stackslots) { L_ = L; count_slots<0>(stackslots...); lua_checkstack(L_, nvar_ + 20); oldtop_ = lua_gettop(L_); for (int i = 0; i < nvar_; i++) { lua_pushnil(L_); } assign_slots(oldtop_ + 1, stackslots...); } template LuaExtStack(const LuaCoreStack &LS0, SS & ... stackslots) { L_ = LS0.state(); count_slots<0>(stackslots...); lua_checkstack(L_, nvar_ + 20); oldtop_ = lua_gettop(L_); for (int i = 0; i < nvar_; i++) { lua_pushnil(L_); } assign_slots(oldtop_ + 1, stackslots...); } ~LuaExtStack() { if (!lua_isthrowing(L_)) { lua_settop(L_, oldtop_); } } void forcediscard() { lua_settop(L_, oldtop_); } }; // 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 LuaCoreStack &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) \ LuaToken ltoken_##name(tvalue); \ LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0); #define LuaNumberConstant(name, nvalue, docs) \ lua_Number lnumber_##name(nvalue); \ 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 // LuaOldStack_HPP