///////////////////////////////////////////////////////// // // // GENERAL CONCEPT // // The original lua C API asks you to work with a stack machine. When you // use the original API, you're manually pushing and popping in every // line of code. I find it hard to remember what stack position contains // what value. This wrapper is designed to alleviate that problem. // // However, the lua garbage collector demands that we keep all lua values // on the lua stack. I can't change that. I can create a wrapper, but I // still have to keep all lua values on the lua stack. So, here's how // this wrapper works: // // At the beginning of any C++ lua function, we allocate enough space on // the lua stack to hold all the arguments, all the return values, and all // the temporary values that we need. We give each of the stack slots // that we allocate a human-readable name. From that point forward, // we don't push or pop anything on the lua stack. Instead, we do all our // lua value manipulation using the stack slots that we allocated and named // at the beginning of the function. // // ///////////////////////////////////////////////////////// // // LUAARG, LUARET, LUAVAR, and LUADEFSTACK // // The best way to explain this wrapper is with an example. This // function, 'table_removevalue', takes a table and a 'removethis' // value. It searches the table for any (key,val) pairs // where val==removethis, and it removes those pairs. // // int table_removevalue(lua_State *L) { // LuaArg table, removethis; // LuaRet returnvalue; // LuaVar key, val; // LuaDefStack LS(L, table, removethis, returnvalue, key, val); // LS.set(key, LuaNil); // while (LS.next(table, key, val)) { // if (LS.rawequal(val, removethis)) { // LS.rawset(table, key, LuaNil); // } // } // LS.set(returnvalue, table); // return LS.result(); // } // // Now I'll explain this function. First, you have some declarations for // LuaArg (lua function arguments), LuaRet (lua function return values), and // LuaVar (lua local variables). These are small structs each containing // an 'int' indicating an absolute position on the lua stack. These are all // collectively called lua stack slots. Class LuaArg, LuaRet, and // LuaVar all derive from a base class LuaSlot. // // At construction time, all the LuaSlots are initialized to zero, which // means they aren't ready to use yet. They don't point to anywhere on the // lua stack. Remember, lua numbers everything starting at 1, so zero is // not a valid lua stack position. // // After the LuaSlot declarations, you have the LuaDefStack constructor. // This takes the lua stack as a parameter, and then all the LuaSlots. // This allocates space on the lua stack for all the LuaSlots. When it's // done, the lua stack will contain exactly five values, one for each of the // five LuaSlots. The LuaSlot structs will contain the stack positions of // these values. The LuaRet and LuaVar stack slots will be initialized to nil. // The LuaArg stack slots will be initialized with the function arguments. // After the LuaDefStack constructor, you are ready to do lua calculations. // // Next, you have the loop that iterates over the table. To iterate over // a table in lua, you initialize 'key' to nil, then you call the 'next' // operator to get the next (key,val) pair. The 'next' operator will return // false if there is no next pair. For each (key,val) pair in the table, // we check if the val is equal to the thing we want to remove, and if so, // we change the table entry to nil. After the loop, we set the returnvalue // slot to the table that was passed in. // // The last line, return LS.result(), is a piece of boilerplate, every lua // C function must end with this. This function removes everything but the // return values from the stack, and returns the number of return values. // // ///////////////////////////////////////////////////////// // // CLASS LUAEXTSTACK. // // Class LuaDefStack, which I showed above, is meant to be used at the // top level of a C++ lua function. // // Class LuaExtStack is meant to be used when you want to allocate // some MORE stack slots halfway through a C++ lua function. // Class LuaExtStack is particularly useful when you want to write // a recursive implementation. Typically you would put LuaDefStack // in the top-level function, and LuaExtStack in the recursive // implementation. // // Class LuaExtStack differs in two ways: first of all, it doesn't allow // LuaArg and LuaRet slots, only LuaVar. Second, it has a destructor that // automatically puts the stack back the way it was when it was constructed. // // You might wonder why class LuaDefStack doesn't have a destructor that // cleans up the lua stack. It's because of return values: it can't remove // everything from the stack, because it has to leave the return values // on the stack. // // I call these two classes the 'LuaStack' classes. When I say that // the LuaStack classes do something, I mean that both LuaDefStack // and LuaExtStack do that thing. // // ///////////////////////////////////////////////////////// // // THE LIBRARY OF BUILTIN OPERATORS // // The LuaStack classes provide a whole library of methods to operate // on lua values. Roughly speaking, there is one function in LuaStack // for every function in the raw lua API, and they are similarly named. // // However, the functions in the raw lua API push and pop values on the // lua stack. The equivalent functions in LuaStack take inputs from // LuaSlots, and store their outputs into other LuaSlots. Nothing is // pushed or popped. // // To get the complete list, you will have to scroll down to class // LuaCoreStack, below. All the prototypes are there, and they are all // documented. // // ///////////////////////////////////////////////////////// // // AUTOMATIC TYPE CONVERSION // // In some cases, LuaStack can do automatic conversions of C++ values into // lua values. This is supported in any function where it makes sense. // One function that supports automatic conversion is 'LuaStack::set': // // LS.set(val1, val2); // // This is actually a copy operation that copies from one lua local variable to // another. But using auto conversions, it can also be used to assign C++ // values to lua slots. These are all valid: // // LS.set(value, 1); // int and int64_t can be converted. // LS.set(value, "banana"); // char*, string, and string_view can be converted. // LS.set(value, true); // bool can be converted. // LS.set(value, 2.39); // float and double can be converted. // LS.set(value, LuaNil); // This special token stores nil. // // Automatic conversion is generally allowed in any context where it would // make sense. For example, these are all valid: // // LS.rawget(value, mytable, 1); // LS.rawget(value, mytable, "banana"); // LS.rawget(value, mytable, true); // LS.rawget(value, mytable, 2.39); // LS.rawget(value, mytable, LuaNil); // // There's also the 'LuaNewTable' constant. This is handled by the // auto-conversion system, but it's not really a conversion: // // LS.set(value, LuaNewTable); // // This will cause a new table to be created, and stored in value. // // ///////////////////////////////////////////////////////// // // API INTEROPERABILITY // // This wrapper can intentionally be mixed with the standard, raw lua API. // You still have an explicit handle to the lua stack, and you can get the // stack addresses out of the LuaSlot variables using LuaSlot::index. So // if there's some tricky thing you can't do with this wrapper, you can always // fall back to the raw API for just the section of code that you need to. // // I also sometimes use the raw lua API for code that is doing truly // unusual things, that aren't easy to do with this wrapper. // ///////////////////////////////////////////////////////// // // // ON RAWGET AND METAMETHODS // // The raw lua API provides functions like lua_gettab and lua_equal // which automatically call metamethods. I do not think it is wise to // habitually use functions like that, because some of the code we write // is not executed in a protected context. That means that a metamethod // that generates an error would bring down the whole system rather than // just stopping a script thread. It also means that a metamethod could // return an invalid piece of data, corrupting a system data structure // rather than just a script data structure. // // Because there are so many contexts where it is just not safe to call // metamethods, I've made it deliberately difficult to call metamethods. // Our API includes 'rawget' which doesn't call metamethods, // but it omits 'gettab' which does. // // If someday we actually need metamethod support, we can do that, // but I'll have to write a safe wrapper for them. I know how to do that, // but it's a lot of work and I'm not going to bother unless it's needed. // // ///////////////////////////////////////////////////////// // // LUA TABLE TYPES // // We have modified the lua interpreter to allow us to assign // table subtypes to different tables. Most lua tables are // marked as 'general' tables. If you create a table using the // lua newtable operator, you'll get a general table. // // Aside from general tables, we have special table types for the // lua registry, for lua global environment tables, for tangibles, // for tangible metatables, and for classes. These tables get // handled specially in various parts of the code. // // ///////////////////////////////////////////////////////// // // LIGHTUSERDATA AND CLASS LUATOKEN // // Before I tell you what tokens are, I'm going to tell you what problem // I was trying to solve that led me to create tokens. // // I was trying to write a JSON to LUA converter. That's mostly // straightforward. Json tables are very similar to lua tables. // For example, consider this JSON: // // { // "name": "John Smith", // "alive": true, // "address": { // "city": "New York", // "state": "NY", // }, // "spouse": null // } // // That converts very straightforwardly to a lua table: // // { // name = "John Smith", // alive = true, // address = { // city = "New York", // state = "NY", // }, // spouse = nil // } // // There's only one problem here: that "spouse" line doesn't really work. // Setting a key to nil in lua is the same as not setting that key at all. // That's not correct. Instead of storing json null as lua nil, // we could store json null as the string "null". But that would be make it // impossible to parse and store the literal string "null". That's not correct // either. // // Lua has a datatype called 'lightuserdata'. A lightuserdata holds an // int64. That gives me an option: I can store json null as a // lightuserdata. When we see this lightuserdata value, we would know // we have a json null. // // So that finally brings me to what a "token" is. A token is a lightuserdata // containing a short string encoded as a fixed-width base37 number. Tokens // may only contain the characters a-z and 0-9, and can be up to 12 characters // long (since 37^12 fits in 64 bits). In effect, it's a short string, but it's // a string that's distinguishable from a normal lua string. It doesn't have // the same type as a lua string (it shows up as a lightuserdata). // The purpose of tokens is to represent special unique values, like json null. // // To make working with tokens easy, I've created a C++ struct 'LuaToken'. // It stores an int64. You can construct a LuaToken in two different ways: // // LuaToken(0x3D5E30BCAF2EF663) // LuaToken("null") // // Those are equivalent. The second form is just as fast as the first, // because of C++ constexpr magic. You can use our automatic type conversion // system to automatically convert C++ LuaToken structs into lua lightuserdata // values, like this: // // LS.set(value, LuaToken("null")) // // When our pretty-printer is printing out lua data structures, and it // encounters a lightuserdata, it prints it out as a token, ie, as a short // string, but using brackets instead of quotation marks. // ///////////////////////////////////////////////////////// // // LUA CLASSES // // This module adds the concept of a 'class' to lua. The function // LuaStack::makeclass creates a class. This function is exposed to lua. // For example, you could create a lua class to manipulate deques: // // makeclass("deque") // // This creates a new table, which you will find in the global // environment under the name 'deque'. The class is a table for // storing functions related to deques. You can now add lua functions // to the class: // // function deque.insertr(...) // end // // If you call makeclass a second time with the same classname, this // is a no-op. This is useful because if you have two sourcefiles that // both add functions to class 'deque', they can both makeclass 'deque', // and no conflict will occur. // // Class tables have a distinct LuaTableType: LUA_TT_CLASS. That // way, it is easy to tell that the table is intended as a class. // Class tables are treated uniquely by our engine in several ways. // // A class also has a field "__index" which points to itself. // C.__index = C. That makes it possible to use the class as a // metatable, and any attempt to look up a name in the table // that fails will then attempt to look up that name in the class. // // ///////////////////////////////////////////////////////// // // LUADEFINE // // LuaDefine is a macro that defines a C function which is exposed to lua. // In addition to actually defining the C function, it also creates a global // registry of all such functions. The registry is used to actually // know which functions to inject into lua. // // Here is an example of a typical LuaDefine: // // LuaDefine(table_removevalue, "table, removethis", // "|This function removes a specified value from a table." // "|", // "|Iterates over all the (key, val) pairs in a table and" // "|removes the ones where val == removethis." // ) { // LuaArg table, removethis; // LuaRet returnvalue; // LuaVar key, val; // LuaDefStack LS(L, table, removethis, returnvalue, key, val); // LS.set(key, LuaNil); // while (LS.next(table, key, val)) { // if (LS.rawequal(val, removethis)) { // LS.rawset(table, key, LuaNil); // } // } // LS.set(returnvalue, table); // return LS.result(); // } // // So you might notice that this is the same example function from // earlier, but this time with LuaDefine. If you omit the LuaDefine // and write the function as it was shown earlier, you will get a // function that can be called from C++, and which works fine when // called from C++, but it won't be visible from lua. // // The example function above will show up in lua as 'table.removevalue'. // This lua function name is derived straightforwardly from the C++ // function name. // // Note that both of the string parameters to LuaDefine are part of the // function documentation, neither has any effect on how the lua function // behaves. The function documentation goes into the registry and from // there is accessible through the lua documentation system. // ///////////////////////////////////////////////////////// #ifndef LUASTACK_HPP #define LUASTACK_HPP #include "util.hpp" #include "wrap-string.hpp" #include "wrap-set.hpp" #include #include #include #include #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "eris.h" //////////////////////////////////////////////////////////////////// // // LUA TABLE TYPES // //////////////////////////////////////////////////////////////////// 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 }; //////////////////////////////////////////////////////////////////// // // LuaToken // //////////////////////////////////////////////////////////////////// struct LuaToken { private: // Encode a token string as a fixed-width base37 number. // Each character is mapped to a digit 1-36 (0 means "no character"), // and the result is: CH0*37^11 + CH1*37^10 + ... + CH11*37^0. // This fixed-width encoding ensures that numeric ordering matches // lexicographic ordering of the original strings. // // WARNING: The Lua lexer in llex.c contains a duplicate of this // encoding logic (in the '@' token literal case). If you change // the encoding here, you must update llex.c to match. // Returns zero if the string is empty, too long, or contains // invalid characters. // static constexpr uint64_t parse(std::string_view str) { if (str.size() > 12) return 0; if (str.empty()) return 0; uint64_t result = 0; for (int i = 0; i < int(str.size()); i++) { char c = str[i]; uint64_t digit = 0; if ((c >= '0') && (c <= '9')) { digit = uint64_t(c - '0') + 1; } else if ((c >= 'a') && (c <= 'z')) { digit = uint64_t(c - 'a') + 11; } else if ((c >= 'A') && (c <= 'Z')) { digit = uint64_t(c - 'A') + 11; } else { return 0; } result = result * 37 + digit; } // Pad remaining positions with zeros (no character). for (int i = int(str.size()); i < 12; i++) { result = result * 37; } return result; } public: uint64_t value; // Get rid of the default constructors. // template LuaToken(T arg) = delete; // Construct a token from a string. // // If the string is not a valid token, then this // initializes the token to the empty token (zero) // LuaToken(std::string_view s) : value(parse(s)) {} LuaToken(const eng::string &s) : value(parse(s)) {} // Construct a token from a compile-time constant string. // // It appears that the code below throws an exception if the // string not parseable. But in reality, since this function is // consteval (evaluated at compile time), the error is // generated during the compilation. // consteval LuaToken(const char *s) : value(parse(s)) { if (empty()) throw "cannot parse token"; } // Construct a token from an int64. // LuaToken(uint64_t v) : value(v) {} // Construct a token from a void pointer. // LuaToken(void *v) : value((uint64_t)v) {} // Default constructor: construct the empty token. // LuaToken() : value(0) {} // Assignment operator. void operator =(const LuaToken &other) { value = other.value; } // Empty: return true if the token is zero. // constexpr bool empty() const { return value == 0; } // Compare two tokens for equality. // bool operator ==(const LuaToken &other) const { return value == other.value; } // Convert the token to a void pointer. // void *voidvalue() const { return (void*)value; } // Convert the token to a string. // // The conversion to string consists of expressing the value // in base 36. // eng::string str() const; }; //////////////////////////////////////////////////////////////////// // // LuaSlots. // //////////////////////////////////////////////////////////////////// class LuaSlot : public eng::nevernew { protected: int index_; constexpr LuaSlot(int n) : index_(n) {} public: // LuaSlots are normally constructed without arguments. // They are uninitialized until the LuaStack constructor runs. // constexpr LuaSlot() : index_(0) {} // You can fetch the stack index from the LuaSlot. // inline int index() const { return index_; } private: // Our code can fetch the stack index using an implicit conversion. // inline operator int() const { return index_; } friend class LuaCoreStack; friend class LuaDefStack; friend class LuaExtStack; friend class LuaKeywordParser; }; class LuaArg : public LuaSlot {}; class LuaRet : public LuaSlot {}; class LuaVar : public LuaSlot {}; class LuaSpecial : public LuaSlot { public: constexpr LuaSpecial(int n) : LuaSlot(n) {} }; extern LuaSpecial LuaRegistry; //////////////////////////////////////////////////////////////////// // // LuaExtraArgs // //////////////////////////////////////////////////////////////////// class LuaExtraArgs { private: int index_; int size_; public: LuaExtraArgs() : index_(0), size_(0) {} LuaExtraArgs(int i, int s) : index_(i), size_(s) {} LuaSpecial operator[] (int n) const { return LuaSpecial(index_ + n); } int size() const { return size_; } friend class LuaCoreStack; friend class LuaDefStack; }; //////////////////////////////////////////////////////////////////// // // LuaNil and LuaNewTable // //////////////////////////////////////////////////////////////////// class LuaNilMarker {}; extern LuaNilMarker LuaNil; class LuaNewTableMarker {}; extern LuaNewTableMarker LuaNewTable; //////////////////////////////////////////////////////////////////// // // LuaCoreStack // // This is the common base class for LuaDefStack and LuaExtStack. // You should use one of those classes in your code, not this class. // However, this class is where all the interesting operators on lua // local variables resides. // //////////////////////////////////////////////////////////////////// class LuaCoreStack : public eng::nevernew { protected: lua_State *L_; public: // Constructor. You should almost never use this, instead, // you should construct a LuaDefStack or a LuaExtStack. // LuaCoreStack(lua_State *L) : L_(L) {} // Get the raw pointer to the lua_State. // lua_State *state() const { return L_; } // Turn a Lua value into a C++ value, if possible. // // If the lua value doesn't match the desired type, then these return // an empty optional. The ones that return bool only verify // the value's type, they don't actually fetch the value. // std::optional tryboolean(LuaSlot s) const; std::optional tryinteger(LuaSlot s) const; std::optional tryint(LuaSlot s) const; std::optional trynumber(LuaSlot s) const; std::optional trystring(LuaSlot s) const; std::optional trystringview(LuaSlot s) const; std::optional trythread(LuaSlot s) const; std::optional trytoken(LuaSlot s) const; std::optional tryxyz(LuaSlot s) const; bool trytable(LuaSlot s) const; bool trynil(LuaSlot s) const; bool tryfunction(LuaSlot s) const; bool trycfunction(LuaSlot s) const; bool trytangible(LuaSlot s) const; // Turn a lua value into a C++ value, or throw an error. // // If the lua value doesn't match the desired type, // then these throw a lua error. It is invalid to use these // outside of a protected context. The argname is used // for making a nice error message. // bool ckboolean(LuaSlot s, const char *argname = "value") const; lua_Integer ckinteger(LuaSlot s, const char *argname = "value") const; int ckint(LuaSlot s, const char *argname = "value") const; lua_Number cknumber(LuaSlot s, const char *argname = "value") const; eng::string ckstring(LuaSlot s, const char *argname = "value") const; std::string_view ckstringview(LuaSlot s, const char *argname = "value") const; lua_State * ckthread(LuaSlot s, const char *argname = "value") const; LuaToken cktoken(LuaSlot s, const char *argname = "value") const; util::DXYZ ckxyz(LuaSlot s, const char *argname = "value") const; void cktable(LuaSlot s, const char *argname = "value") const; void cknil(LuaSlot s, const char *argname = "value") const; void ckfunction(LuaSlot s, const char *argname = "value") const; void ckcfunction(LuaSlot s, const char *argname = "value") const; void cktangible(LuaSlot s, const char *argname = "value") const; // Check if a lua value can be converted to C++. // // These functions check if a value can be converted // to a C++ value. They don't actually return the C++ value. // It is more efficient to use the 'try' or 'ck' functions above if // you also want the value. // bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; } bool isinteger(LuaSlot s) const { return bool(tryinteger(s)); } bool isint(LuaSlot s) const { return bool(tryint(s)); } bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } bool isstringview(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } bool istoken(LuaSlot s) const { return lua_type(L_, s) == LUA_TLIGHTUSERDATA; } bool isxyz(LuaSlot s) const { return bool(tryxyz(s)); } bool istable(LuaSlot s) const { return lua_istable(L_, s); } bool isnil(LuaSlot s) const { return lua_isnil(L_, s); } bool isfunction(LuaSlot s) const { return lua_isfunction(L_, s); } bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s); } bool istangible(LuaSlot s) const { return bool(trytangible(s)); } // Create a new interpreter using the specified allocator. // // Typically, the allocator used would be eng::l_alloc. // You can also pass nullptr to use a default allocator based // on malloc. // static lua_State *newstate (lua_Alloc allocf); // Create a new thread. // // The target parameter is an output parameter, this will contain // the new thread. Also returns a C++ pointer to the thread. Remember // that the C++ pointer by itself doesn't prevent garbage collection, // you must keep the thread in the LuaSlot or in some other lua data // structure to prevent it from getting garbage collected. // lua_State *newthread(LuaSlot target) const; // Get the type of a LuaSlot. // // Returns one of the standard lua type tags. These include: // // LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, // LUA_TTHREAD, LUA_TLIGHTUSERDATA, LUA_TUSERDATA. // int type(LuaSlot s) const { return lua_type(L_, s); } const char *typestr(LuaSlot s) const { return lua_typename(L_, lua_type(L_, s)); } // Get the extended type of a LuaSlot. // // If the variable contains a table, returns one of the LuaTableType // constants. Search for this enum above. If it is not a table, // returns one of the standard lua type tags. See the 'type' // method above. // int xtype(LuaSlot slot) const; // Get the table type of a lua table. // // Tab must contain a lua table. Returns a value from enum LuaTableType. // int gettabletype(LuaSlot tab) const; // Set the table type of a lua table. // // Tab must contain a lua table. T must be a value from enum LuaTableType. // void settabletype(LuaSlot tab, int t) const; // Get the length of a lua string. // // Techically, you can also use this on tables, but it's not recommended. // Instead, use 'nkeys' below. The semantics of rawlen on tables // is just plain weird: see lua documentation if you are curious. // int rawlen(LuaSlot val) const; // Get the number of key/value pairs in a lua table. // // This works on any table, even tables where the keys aren't integers. // int nkeys(LuaSlot tab) const; // Get the metatable of a table. // // Tab must be a table. The metatable of tab is stored in mt. // bool getmetatable(LuaSlot mt, LuaSlot tab) const; // Set the metatable of a table. // // Tab must be a table. Mt must be a table or nil. // void setmetatable(LuaSlot tab, LuaSlot mt) const; // Remove the metatable from a table. // // Tab must be a table. The metatable, if any, is removed from tab. // void clearmetatable(LuaSlot tab) const; // Create a new table. // // The new table is stored in target. // void newtable(LuaSlot target) const; // Create a new table with a storage hint. // // The new table is stored in target. The new table has space // pre-allocated for narr array elements and nrec non-array elements. // void createtable(LuaSlot target, int narr, int nrec) const; // Get the global environment table. // // The global environment table is stored in gltab. // void getglobaltable(LuaSlot gltab) const; // Delete everything in a table. // // Tab must be a table. Removes all (key,val) pairs from tab. // If clearmeta is true, then the metatable is also removed from tab. // void cleartable(LuaSlot tab, bool clearmeta) const; // Iterate over the key,val pairs in a table. // // Before the iteration begins, you should initialize 'key' to nil. // Then, you should call 'next' to fetch the next key,val pair in // the table. You can keep calling 'next' to obtain successive key,val // pairs until 'next' returns false. // // Do not alter 'key' during the iteration, if you do, then the 'next' // function will return the wrong next-value. // bool next(LuaSlot tab, LuaSlot key, LuaSlot val) const; // Compile lua code. // // If the code contains a syntax error, then the result variable // is set to the error message, and the error message is returned. // // If the code is valid, then the result variable is set to a // closure, and an empty string is returned. // // If a syntax error occurs, the error message may contain the // token . If so, the problem is an incomplete expression. // eng::string load(LuaSlot result, std::string_view code, std::string_view context); // Return true if the int64 can be stored losslessly in a lua_Number. // // Lua numbers are actually double-precision floating point. double // can hold integers losslessly as long as they're small enough to // fit within the double's mantissa. The mantissa of an IEEE double // is big enough to hold a 53-bit integer. // static bool validinteger(int64_t value) { return (value <= MAXINT) && (value >= -MAXINT); } // Return true if the int64 can be stored losslessly and is positive. // // This returns true if the number is a validinteger (see above), and is // a positive number. // static bool validpositiveinteger(int64_t value) { return (value <= MAXINT) && (value >= 1); } // Get the class name given a class table. // // Return the class name if x is a valid class table. 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 for a class by class-name. // // 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. // // Creates a new class, unless the class already exists. Stores the // class in the global environment table. This routine assert-fails if the // parameter is not a valid classname. // 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. // A stub tangible is an empty table with a metatable containing the // tangible's ID. Nothing else is present in the stub. It is up to // the World module to transform stubs into full-blown tangibles. // Assert-fails if the tangible ID is not a validpositiveinteger. // void maketan(LuaSlot tab, int64_t id) const; // Return true if a tangible is empty, ie, a stub. // bool tanblank(LuaSlot tab) const; // Get the ID of a tangible. // // This works on both full-blown tangibles and stubs. If tab // is not a valid tangible, returns zero. // int64_t tanid(LuaSlot tab) const; // Get the class of a tangible. // // If the tangible has been assigned a class, then puts the class // table into classobj and returns true. Otherwise, sets classobj // to nil and returns false. // bool tangetclass(LuaSlot classobj, LuaSlot tan); // Assign a lua variable. // // Copies value into target. The 'value' parameter can be a LuaSlot or // any lua-convertible C++ type. // template void set(LuaSlot target, VT value) const { push_any_value(value); lua_replace(L_, target); } // Return true if two values are equal. // // Checks if the two values are equal. Note that in lua, if two strings // contain the same text, then they're equal. The 'value' parameter // can be a LuaSlot or any lua-convertible C++ type. // // This could possibly be faster if we were to write some specializations // for strings, numbers, and bools. // 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; } // Return true if val1 is less than val2. // // This is NOT the same as the lua_lessthan function. This is a more // general function that can compare any two lua objects. // // Numbers are compared in the obvious numeric manner. // Strings are compared alphabetically. // Booleans are compared with false being less than true. // Tables are all considered equal to other tables. // Functions are all considered equal to other functions. // Coroutines are all considered equal to other coroutines. // // Numbers are less than strings. // Strings are less than booleans. // Booleans are less than functions. // Functions are less than coroutines. // Coroutines are less than tables. // // Does not call metamethods. // bool genlt(LuaSlot val1, LuaSlot val2) const { return lua_genlt(L_, val1, val2); } // Return true if the value is a sortable key. // // Sortable keys are: strings, booleans, and numbers. // These three types can be meaningfully compared using genlt, // above. They also can be meaningfully transferred from lua to C++ // and back without losing anything. // bool issortablekey(LuaSlot s) const; // Move a sortable key to another lua environment. // // This is used when you've created two lua interpreters and you // want to move data from one to the other. The only kinds of data // you can move are strings, booleans, and numbers. // void movesortablekey(LuaSlot val, LuaCoreStack &other, LuaSlot otherslot); // Fetch a value from a table. // // Fetches the specified key from the table tab, and stores the // result in target. The key parameter can be a LuaSlot or any lua- // convertible C++ value. // template void rawget(LuaSlot target, LuaSlot tab, KT key) const { push_any_value(key); lua_rawget(L_, tab); lua_replace(L_, target); } // Store a value in a table. // // Sets the table entry for key to value. The key and val // parameters can be LuaSlots, or they can be any lua-convertible // C++ value. // template void rawset(LuaSlot tab, KT key, VT value) const { push_any_value(key); push_any_value(value); lua_rawset(L_, tab); } // Get the 'visited' bit from a lua table. // bool getvisited(LuaSlot tab) const; // Set the 'visited' bit in a lua table. // void setvisited(LuaSlot tab, bool visited) const; // Store the world type in the registry. // // This just stores the enum value in the registry key "worldtype". // void set_world_type(WorldType t) const; // Return the world type from the registry. // // This just fetches the enum value from the registry key "worldtype". // WorldType get_world_type() const; // Return true if this world is authoritative. // // This fetches the enum value from the registry key "worldtype", // then it checks if the world type is authoritative. // bool is_authoritative() { return util::is_authoritative(get_world_type()); } // Yield this thread with zero if in a nonauth model, and not a probe. // // The function name is just used for generating better error messages. // void guard_nopredict(const char *fn); // 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; // Template Specializations. // // These are all specializations of functions that are defined above. // These are typically here purely to make the functions above faster. // void set(LuaSlot target, LuaSlot value) const { lua_copy(L_, value, target); } bool rawequal(LuaSlot v1, LuaSlot v2) const { return lua_rawequal(L_, v1, v2); } void rawget(LuaSlot target, LuaSlot tab, int key) const { lua_rawgeti(L_, tab, key); lua_replace(L_, target); } template void rawset(LuaSlot tab, int key, VT value) const { push_any_value(value); lua_rawseti(L_, tab, key); } protected: // Assign slots: this is used by the LuaDefStack and LuaExtStack // constructors to assign stack indices to LuaSlots. // template inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaRet &v, SS & ... stackslots) { v.index_ = retp; vassign_slots(retp+1, argp, varp, extrap, extrac, stackslots...); } template inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaArg &v, SS & ... stackslots) { v.index_ = argp; vassign_slots(retp, argp+1, varp, extrap, extrac, stackslots...); } template inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaVar &v, SS & ... stackslots) { v.index_ = varp; vassign_slots(retp, argp, varp+1, extrap, extrac, stackslots...); } template inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaExtraArgs &v, SS & ... stackslots) { v.index_ = extrap; v.size_ = extrac; vassign_slots(retp, argp, varp, extrap, extrac, stackslots...); } inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac) {} // Push any value on the stack, by type. // inline void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); } inline void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); } inline void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); } inline void push_any_value(const eng::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } inline void push_any_value(std::string_view s) const { lua_pushlstring(L_, s.data(), s.size()); } inline void push_any_value(const char *s) const { lua_pushstring(L_, s); } inline void push_any_value(float s) const { lua_pushnumber(L_, s); } inline void push_any_value(double s) const { lua_pushnumber(L_, s); } inline void push_any_value(int s) const { lua_pushinteger(L_, s); } inline void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); } inline void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); } inline void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); } inline 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() { } // Throw a lua error message void argerr(const char *arg, const char *tp) const; }; //////////////////////////////////////////////////////////////////// // // Argument Counting Templates. // // These are internal functions used by LuaDefStack and LuaExtStack // to help with the processing of constructor arguments. // //////////////////////////////////////////////////////////////////// struct LuaArgCounts { int nret; int narg; int nvar; int nextra; constexpr LuaArgCounts(int nr, int na, int nv, int ne) : nret(nr), narg(na), nvar(nv), nextra(ne) {} constexpr LuaArgCounts operator +(LuaArgCounts b) const { return LuaArgCounts(nret + b.nret, narg + b.narg, nvar + b.nvar, nextra + b.nextra); } }; template struct LuaCountArgs; template<> struct LuaCountArgs<> { static constexpr LuaArgCounts value = LuaArgCounts(0,0,0,0); }; template struct LuaCountArgs { static constexpr LuaArgCounts value = LuaArgCounts(1, 0, 0, 0) + LuaCountArgs::value; }; template struct LuaCountArgs { static constexpr LuaArgCounts value = LuaArgCounts(0, 1, 0, 0) + LuaCountArgs::value; }; template struct LuaCountArgs { static constexpr LuaArgCounts value = LuaArgCounts(0, 0, 1, 0) + LuaCountArgs::value; }; template struct LuaCountArgs { static constexpr LuaArgCounts value = LuaArgCounts(0, 0, 0, 1) + LuaCountArgs::value; }; //////////////////////////////////////////////////////////////////// // // LuaDefStack // // This version of LuaStack should only be used inside a LuaDefine. It can // assign stack slots to LuaArg, LuaRet, LuaVar, and LuaExtraArgs. It // arranges for the arguments to be in the LuaArg variables, and it arranges for // the LuaRet variables to be returned. It also makes sure that the function // has the correct number of arguments. // // At the end of the LuaDefine function, you're supposed to return LS.result(). // LS.result causes the allocated stack slots to be freed except for the LuaRet // values, which have to stay on the stack in order to pass them back as return // values. LS.result returns the number of LuaRet variables left on the stack. // // If you terminate a LuaDefine by calling lua_error or lua_yield, then // obviously, you don't get a chance to call LS.result. That's not a problem. // The lua interpreter will clean up after an error or yield. // // Implementation note: LuaDefStack doesn't have a destructor to deallocate // stack slots. That's deliberate: you shouldn't expect this class to clean up // its stack frame, because after all, it has to leave return values on the // stack. It would be deceptive to put a destructor, which then doesn't // actually clean up anyway. Better to just let it be known that this class // doesn't clean up its stack frame. // //////////////////////////////////////////////////////////////////// class LuaDefStack : public LuaCoreStack { private: int nret_; public: template inline LuaDefStack(lua_State *L, SS & ... stackslots) : LuaCoreStack(L) { constexpr LuaArgCounts counts = LuaCountArgs::value; int nargs = lua_gettop(L); if (counts.nextra == 0) { if (nargs != counts.narg) { luaL_error(L_, "function expects exactly %d arguments", counts.narg); } } else { if (nargs < counts.narg) { luaL_error(L_, "function expects at least %d arguments", counts.narg); } } lua_checkstack(L, counts.nret + counts.nvar + 20); lua_insert_frame(L, counts.nret + counts.nvar); vassign_slots(1, 1 + counts.nret + counts.nvar, 1 + counts.nret, 1 + counts.nret + counts.nvar + counts.narg, nargs - counts.narg, stackslots...); nret_ = counts.nret; } int result() { lua_settop(L_, nret_); return nret_; } // Tail-call into lua. // // This is meant to be used as follows: return LS.tailcall(passup, func, arg, arg...) // // If passup is true, the return value to our caller consists of our // LuaRet arguments concatenated to the return values from the tail-call. // If passup is false, the return value to our caller consists solely // of our LuaRet arguments. // template int tailcall(bool passup, LuaSlot func, T... args) { lua_checkstack(L_, nret_ + 20); int base = lua_gettop(L_); for (int i = 1; i <= nret_; i++) { lua_pushvalue(L_, i); } push_any_value(func); int argbase = lua_gettop(L_); push_any_values(args...); int nargs = lua_gettop(L_) - argbase; return tailcall_internal(passup, base, nargs); } ~LuaDefStack() { } private: int tailcall_internal(bool passup, int base, int nargs); }; //////////////////////////////////////////////////////////////////// // // LuaExtStack // // This version of LuaStack is meant to be used in any context where // you want to assign stack slots to some LuaVars, and then you want // to automatically deallocate those LuaVars when the LuaExtStack // goes out of scope. // // Unlike LuaDefStack, this version of LuaStack is meant to fully // deallocate its stack frame when it goes out of scope, so it does // have a destructor to do that. There is a special case in the // destructor: if lua is throwing an error, the destructor leaves // the stack alone, in order to preserve the error message that's // on the stack. After an error throw, the lua interpreter will // clean up the stack. // //////////////////////////////////////////////////////////////////// class LuaExtStack : public LuaCoreStack { private: int oldtop_; public: template LuaExtStack(lua_State *L, SS & ... stackslots) : LuaCoreStack(L) { constexpr LuaArgCounts counts = LuaCountArgs::value; static_assert(counts.narg == 0, "LuaExtStack only allows LuaVar, not LuaArg"); static_assert(counts.nret == 0, "LuaExtStack only allows LuaVar, not LuaRet"); static_assert(counts.nextra == 0, "LuaExtStack only allows LuaVar, not LuaExtraArgs"); lua_checkstack(L_, counts.nvar + 20); oldtop_ = lua_gettop(L_); for (int i = 0; i < counts.nvar; i++) { lua_pushnil(L_); } vassign_slots(0, 0, oldtop_ + 1, 0, 0, stackslots...); } template LuaExtStack(const LuaCoreStack &LS0, SS & ... stackslots) : LuaCoreStack(LS0.state(), stackslots...) {} int oldtop() const { return oldtop_; } ~LuaExtStack() { if (!lua_isthrowing(L_)) { if (lua_gettop(L_) > oldtop_) { lua_settop(L_, oldtop_); } } } }; //////////////////////////////////////////////////////////////////// // // LuaKeywordParser // // This is a helper class to help parse tables full of keywords. // It is meant to make it easier to write LuaDefine functions that // accept keyword arguments. It helps with the following tasks: // // * It makes sure the keyword table actually is a table. // // * It makes sure that all required keywords are present. // // * It makes sure that you didn't put anything that isn't a // known keyword into the keyword table. // // This module adds two fields to the table: // // [ERROR] - stores an error message, initially nil. // [FOUND] - the set of keywords successfully parsed. // // If at any time, this module detects an error, it doesn't throw. // Instead, it stores an error report in the table key [ERROR]. // Later, you can check for an error string using the function // final_check or final_check_throw. If an error is // detected when there is already an error report in the table, // the error report is not overwritten, so therefore, the error // reported is always the first error detected. // // When this module finds a keyword in the table, it adds the keyword // to the [FOUND] set of all keywords successfully parsed. // // If the keyword table that you pass in isn't a table at all, // then the keyword-fetching functions will always return false. // Later, when you call 'check', an appropriate error will be // generated. // // The lua module 'keywords' contains the same functions as this // C++ class. You can write code where a C++ function does some // of the parsing, and the lua code does the rest of the parsing. // //////////////////////////////////////////////////////////////////// class LuaKeywordParser { private: LuaVar found, error, key, val; LuaSpecial keytab; LuaCoreStack LS; bool istable; void init(const lua_State *L, int slot); public: LuaKeywordParser(const LuaCoreStack &LS, LuaSlot slot); // Fetch the value of the keyword. If the keyword is found, then the // keyword is added to the [FOUND] set, the value is returned in slot, // and returns true. Otherwise, sets slot to nil and returns false. bool optional(LuaSlot slot, std::string_view kw); // Fetch the value of the keyword. If the keyword is found, then the // keyword is added to the [FOUND] set, the value is returned in slot, // and returns true. Otherwise, sets slot to nil, returns false, and // stores an [ERROR] report in the keyword table. bool required(LuaSlot slot, std::string_view kw); // Check if there are any errors so far, by checking for an [ERROR] // report in the keyword table. If any error has been // detected, returns an error message, otherwise, returns empty // string. eng::string check(); // Check if there are any errors so far, by checking for an [ERROR] // report in the keyword table. Also check that all keyword // arguments present in the table are in the [FOUND] set. If there are // any errors, returns an error message, otherwise returns empty string. eng::string final_check(); // If check() returns an error, throws the error using luaL_error. void check_throw(); // If final_check() returns an error, throws the error using luaL_error. void final_check_throw(); // Fetch the state pointer. lua_State *state() const { return LS.state(); } }; //////////////////////////////////////////////////////////////////// // // Lua Byte Reader // // Converts a block of bytes in RAM into a lua_reader. // //////////////////////////////////////////////////////////////////// class LuaByteReader { private: const char *data_; int64_t size_; public: LuaByteReader(const char *d, int64_t s) : data_(d), size_(s) {} void *lua_reader_userdata() { return this; } static const char *lua_reader(lua_State *L, void *ud, size_t *size); }; //////////////////////////////////////////////////////////////////// // // The Lua Constant Registry // //////////////////////////////////////////////////////////////////// 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_; } }; //////////////////////////////////////////////////////////////////// // // The Lua Function Registry // //////////////////////////////////////////////////////////////////// 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; } }; //////////////////////////////////////////////////////////////////// // // LuaDefine and friends. // //////////////////////////////////////////////////////////////////// #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); \ const char *lfnarg_##name = args; \ const char *lfndoc_##name = docs; \ LuaFunctionReg reg_##name(#name, lfnarg_##name, lfndoc_##name, false, lfn_##name); \ int lfn_##name(lua_State *L) #define LuaDefineAlias(name1, name2) \ LuaFunctionReg reg_##name1(#name1, lfnarg_##name2, lfndoc_##name2, false, lfn_##name2); \ #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