#include "luastack.hpp" #include #include #include #include "wrap-string.hpp" #include "wrap-set.hpp" #include "wrap-sstream.hpp" #include "util.hpp" LuaSpecial LuaRegistry(LUA_REGISTRYINDEX); LuaNilMarker LuaNil; LuaNewTableMarker LuaNewTable; static LuaToken token_error("error"); static LuaToken token_found("found"); LuaFunctionReg::LuaFunctionReg(const char *n, const char *a, const char *d, bool s, lua_CFunction f) { name_ = n; args_ = a; docs_ = d; func_ = f; sandbox_ = s; next_ = All; All = this; } LuaConstantReg::LuaConstantReg(const char *n, const char *d, LuaToken tokenvalue, lua_Number numbervalue) { name_ = n; docs_ = d; tokenvalue_ = tokenvalue; numbervalue_ = numbervalue; next_ = All; All = this; } const LuaFunctionReg *LuaFunctionReg::lookup(lua_CFunction fn) { for (const LuaFunctionReg *r = All; r != 0; r = r->next_) { if (r->func_ == fn) { return r; } } return nullptr; } LuaFunctionReg *LuaFunctionReg::All; LuaConstantReg *LuaConstantReg::All; eng::string LuaToken::str() const { static const char encoding[] = "\0_0123456789abcdefghijklmnopqrstuvwxyz"; uint64_t n = (uint64_t)value; char buffer[13] = {}; for (int i = 11; i >= 0; i--) { buffer[i] = encoding[n % 38]; n /= 38; } return eng::string(buffer); } static int panicf(lua_State *L) { const char *p = lua_tostring(L, -1); fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", p); fflush(stderr); exit(1); } // An allocator for lua states that uses system malloc and system free. static void *l_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { if (nsize == 0) { free(ptr); return NULL; } else { return realloc(ptr, nsize); } } lua_State *LuaCoreStack::newstate (lua_Alloc allocf) { if (allocf == nullptr) allocf = l_alloc; lua_State *L = lua_newstate(allocf, NULL); assert(L != nullptr); lua_atpanic(L, &panicf); // We want all states to have a classes table and // a tangibles table so that LS.makeclass and LS.maketan // work out-of-the-box. lua_pushstring(L, "classes"); lua_newtable(L); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushstring(L, "classnames"); lua_newtable(L); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushstring(L, "tangibles"); lua_newtable(L); lua_rawset(L, LUA_REGISTRYINDEX); return L; } void LuaCoreStack::argerr(const char *nm, const char *tp) const { luaL_error(L_, "'%s' should be %s", nm, tp); } std::optional LuaCoreStack::tryboolean(LuaSlot s) const { if (lua_type(L_, s) == LUA_TBOOLEAN) { return lua_toboolean(L_, s); } return std::nullopt; } std::optional LuaCoreStack::tryinteger(LuaSlot s) const { if (lua_type(L_, s) == LUA_TNUMBER) { lua_Number result = lua_tonumber(L_, s); if (lua_Integer(result) == result) { return lua_Integer(result); } } return std::nullopt; } std::optional LuaCoreStack::tryint(LuaSlot s) const { if (lua_type(L_, s) == LUA_TNUMBER) { lua_Number result = lua_tonumber(L_, s); if (int(result) == result) { return int(result); } } return std::nullopt; } std::optional LuaCoreStack::trynumber(LuaSlot s) const { if (lua_type(L_, s) == LUA_TNUMBER) { return lua_tonumber(L_, s); } return std::nullopt; } std::optional LuaCoreStack::trystring(LuaSlot s) const { if (lua_type(L_, s) == LUA_TSTRING) { size_t len; const char *str = lua_tolstring(L_, s, &len); return eng::string(str, len); } return std::nullopt; } std::optional LuaCoreStack::trystringview(LuaSlot s) const { if (lua_type(L_, s) == LUA_TSTRING) { size_t len; const char *str = lua_tolstring(L_, s, &len); return std::string_view(str, len); } return std::nullopt; } std::optional LuaCoreStack::trythread(LuaSlot s) const { if (lua_type(L_, s) == LUA_TTHREAD) { return lua_tothread(L_, s); } return std::nullopt; } std::optional LuaCoreStack::trytoken(LuaSlot s) const { if (lua_type(L_, s) == LUA_TLIGHTUSERDATA) { return LuaToken(lua_touserdata(L_, s)); } return std::nullopt; } std::optional LuaCoreStack::tryxyz(LuaSlot s) const { if (lua_istable(L_, s) && (lua_nkeys(L_, s) == 3)) { int top = lua_gettop(L_); lua_rawgeti(L_, s, 3); lua_rawgeti(L_, s, 2); lua_rawgeti(L_, s, 1); if ((lua_type(L_, -1)==LUA_TNUMBER) && (lua_type(L_, -2)==LUA_TNUMBER) && (lua_type(L_, -3)==LUA_TNUMBER)) { util::DXYZ result; result.x = lua_tonumber(L_, -1); result.y = lua_tonumber(L_, -2); result.z = lua_tonumber(L_, -3); lua_settop(L_, top); return result; } lua_settop(L_, top); } return std::nullopt; } bool LuaCoreStack::trytable(LuaSlot s) const { return lua_istable(L_, s); } bool LuaCoreStack::trynil(LuaSlot s) const { return lua_isnil(L_, s); } bool LuaCoreStack::tryfunction(LuaSlot s) const { return lua_isfunction(L_, s); } bool LuaCoreStack::trycfunction(LuaSlot s) const { return lua_iscfunction(L_, s); } bool LuaCoreStack::trytangible(LuaSlot s) const { return (lua_istable(L_, s) && gettabletype(s) == LUA_TT_TANGIBLE); } bool LuaCoreStack::ckboolean(LuaSlot s, const char *argname) const { auto result = tryboolean(s); if (!result) argerr(argname, "boolean"); return *result; } lua_Integer LuaCoreStack::ckinteger(LuaSlot s, const char *argname) const { auto result = tryinteger(s); if (!result) argerr(argname, "integer"); return *result; } int LuaCoreStack::ckint(LuaSlot s, const char *argname) const { auto result = tryint(s); if (!result) argerr(argname, "int"); return *result; } lua_Number LuaCoreStack::cknumber(LuaSlot s, const char *argname) const { auto result = trynumber(s); if (!result) argerr(argname, "number"); return *result; } eng::string LuaCoreStack::ckstring(LuaSlot s, const char *argname) const { auto result = trystring(s); if (!result) argerr(argname, "string"); return *result; } std::string_view LuaCoreStack::ckstringview(LuaSlot s, const char *argname) const { auto result = trystringview(s); if (!result) argerr(argname, "string"); return *result; } lua_State * LuaCoreStack::ckthread(LuaSlot s, const char *argname) const { auto result = trythread(s); if (!result) argerr(argname, "thread"); return *result; } LuaToken LuaCoreStack::cktoken(LuaSlot s, const char *argname) const { auto result = trytoken(s); if (!result) argerr(argname, "token"); return *result; } util::DXYZ LuaCoreStack::ckxyz(LuaSlot s, const char *argname) const { auto result = tryxyz(s); if (!result) argerr(argname, "xyz"); return *result; } void LuaCoreStack::cktable(LuaSlot s, const char *argname) const { if (!trytable(s)) argerr(argname, "table"); } void LuaCoreStack::cknil(LuaSlot s, const char *argname) const { if (!trynil(s)) argerr(argname, "nil"); } void LuaCoreStack::ckfunction(LuaSlot s, const char *argname) const { if (!tryfunction(s)) argerr(argname, "function"); } void LuaCoreStack::ckcfunction(LuaSlot s, const char *argname) const { if (!trycfunction(s)) argerr(argname, "c-function"); } void LuaCoreStack::cktangible(LuaSlot s, const char *argname) const { if (!trytangible(s)) argerr(argname, "tangible"); } void LuaCoreStack::clearmetatable(LuaSlot tab) const { lua_pushnil(L_); lua_setmetatable(L_, tab); } void LuaCoreStack::setmetatable(LuaSlot tab, LuaSlot mt) const { lua_pushvalue(L_, mt); lua_setmetatable(L_, tab); } bool LuaCoreStack::getmetatable(LuaSlot mt, LuaSlot tab) const { if (lua_getmetatable(L_, tab)) { lua_replace(L_, mt); return true; } else { lua_pushnil(L_); lua_replace(L_, mt); return false; } } bool LuaCoreStack::next(LuaSlot tab, LuaSlot key, LuaSlot value) const { lua_pushvalue(L_, key); int ret = lua_next(L_, tab); if (ret != 0) { lua_replace(L_, value); lua_replace(L_, key); } return (ret != 0); } eng::string LuaCoreStack::load(LuaSlot result, std::string_view code, std::string_view context) { // We interpret "=x" as syntactic sugar for "return x" eng::string expanded; if (sv::has_prefix(code, "=")) { expanded = eng::string("return ") + eng::string(code.substr(1)); code = expanded; } if (sv::has_prefix(code, "/")) { set(result, "slash command"); return "slash command"; } if (sv::is_whitespace(code)) { set(result, "white space"); return "white space"; } eng::string fullcontext = eng::string("=") + eng::string(context); luaL_loadbuffer(L_, code.data(), code.size(), fullcontext.c_str()); int type = lua_type(L_, -1); if (type == LUA_TFUNCTION) { // compiler returned a closure. lua_replace(L_, result.index()); return ""; } else if (type == LUA_TSTRING) { // compiler returned an error message. size_t len; const char *str = lua_tolstring(L_, -1, &len); eng::string message(str, len); lua_pop(L_, 1); if (sv::has_suffix(message, "near ")) { message = "truncated lua"; } if (message.empty()) message = "unknown compiler error"; set(result, message); return message; } else { assert(false && "lua compiler didn't return a closure, but didn't return an error message either"); } } void LuaCoreStack::getglobaltable(LuaSlot target) const { lua_pushglobaltable(L_); lua_replace(L_, target); } void LuaCoreStack::newtable(LuaSlot target) const { lua_newtable(L_); lua_replace(L_, target); } void LuaCoreStack::createtable(LuaSlot target, int narr, int nrec) const { lua_createtable(L_, narr, nrec); lua_replace(L_, target); } lua_State *LuaCoreStack::newthread(LuaSlot target) const { lua_State *result = lua_newthread(L_); lua_replace(L_, target); return result; } void LuaCoreStack::getclassinfo(LuaSlot classtab, eng::string &classname, eng::string &error, LuaSlot input) const { LuaVar lookup, cname; LuaExtStack LS(L_, lookup, cname); // Step 1: Resolve input to a class table. int xt = xtype(input); if (xt == LUA_TSTRING) { LS.rawget(lookup, LuaRegistry, "classes"); LS.rawget(classtab, lookup, input); } else if (xt == LUA_TT_TANGIBLE) { if (LS.getmetatable(lookup, input)) { LS.rawget(classtab, lookup, "__index"); } else { LS.set(classtab, LuaNil); } } else if (xt == LUA_TT_CLASS) { LS.set(classtab, input); } else if (xt == LUA_TT_GENERAL) { LS.getmetatable(classtab, input); } else { LS.set(classtab, LuaNil); } // Step 2: Validate classtab and get classname, or generate error. // A class table is only valid if it's registered in classnames. if (xtype(classtab) == LUA_TT_CLASS) { LS.rawget(lookup, LuaRegistry, "classnames"); LS.rawget(cname, lookup, classtab); auto name = LS.trystring(cname); if (name) { classname = *name; error = ""; return; } } // Step 3: We didn't find a valid classtab and classname. // Clear the classtab and classname return-values, then // figure out what went wrong and generate an error message. LS.set(classtab, LuaNil); classname = ""; if (xt == LUA_TSTRING) { eng::string s = LS.ckstring(input); if (!sv::is_lua_classname(s)) { error = "invalid class name: " + s; } else { error = "class does not exist: " + s; } } else if (xt == LUA_TT_TANGIBLE) { error = "tangible has no valid class"; } else if (xt == LUA_TT_GENERAL) { error = "table has no valid class"; } else if (xt == LUA_TT_CLASS) { error = "class is no longer valid"; } else { error = "expected a string, class, tangible, or table"; } } eng::string LuaCoreStack::classname(LuaSlot input) const { LuaVar tab; LuaExtStack LS(L_, tab); eng::string name, error; getclassinfo(tab, name, error, input); return name; } eng::string LuaCoreStack::getclass(LuaSlot classtab, LuaSlot input) const { eng::string name, error; getclassinfo(classtab, name, error, input); return error; } eng::string LuaCoreStack::getclass(LuaSlot classtab, std::string_view classname) const { LuaVar input; LuaExtStack LS(L_, input); LS.set(input, classname); eng::string name, error; getclassinfo(classtab, name, error, input); return error; } void LuaCoreStack::makeclass(LuaSlot classtab, LuaSlot classname) const { LuaVar classes, classnames, globtab, cname; LuaExtStack LS(L_, classes, classnames, globtab, cname); // Validate the class name. assert(LS.isstring(classname)); assert(sv::is_lua_classname(LS.ckstring(classname))); // Fetch the classes table from the registry. LS.rawget(classes, LuaRegistry, "classes"); assert(LS.istable(classes)); // Fetch the classnames table from the registry. LS.rawget(classnames, LuaRegistry, "classnames"); assert(LS.istable(classnames)); // Fetch the global environment from the registry. LS.getglobaltable(globtab); // Get the classtab from the classes table. LS.rawget(classtab, classes, classname); // Make a new table if necessary. if (!LS.istable(classtab)) { LS.newtable(classtab); LS.rawset(classes, classname, classtab); LS.rawset(classnames, classtab, classname); } // Put the table into the global environment. LS.rawset(globtab, classname, classtab); // Repair the special fields. LS.settabletype(classtab, LUA_TT_CLASS); LS.rawset(classtab, "__index", classtab); } void LuaCoreStack::makeclass(LuaSlot tab, std::string_view name) const { push_any_value(name); LuaSpecial classname(lua_gettop(L_)); makeclass(tab, classname); lua_pop(L_, 1); } void LuaCoreStack::maketan(LuaSlot tab, int64_t id) const { assert(validpositiveinteger(id)); LuaVar tangibles, metatab; LuaExtStack LS(L_, tangibles, metatab); // Try to get the existing tangible. LS.rawget(tangibles, LuaRegistry, "tangibles"); LS.rawget(tab, tangibles, id); // If we succeeded, return it. if (LS.istable(tab)) { return; } // Create the tangible's database and metatable. LS.set(tab, LuaNewTable); LS.set(metatab, LuaNewTable); LS.setmetatable(tab, metatab); // Mark the tangible using the tabletype field. LS.settabletype(tab, LUA_TT_TANGIBLE); LS.settabletype(metatab, LUA_TT_TANGIBLEMETA); // Store the tangible ID and lock the metatable. LS.rawset(metatab, "id", id); LS.rawset(metatab, "__metatable", false); // Store the database into the tangibles table. LS.rawset(tangibles, id, tab); } bool LuaCoreStack::tanblank(LuaSlot tab) const { bool result = true; if (istable(tab) && gettabletype(tab) == LUA_TT_TANGIBLE) { if (lua_getmetatable(L_, tab.index())) { lua_pushstring(L_, "threads"); lua_rawget(L_, -2); if (lua_type(L_, -1) == LUA_TTABLE) { result = false; } lua_pop(L_, 2); } } return result; } int64_t LuaCoreStack::tanid(LuaSlot tab) const { int64_t result = 0; if (istable(tab) && gettabletype(tab) == LUA_TT_TANGIBLE) { if (lua_getmetatable(L_, tab.index())) { lua_pushstring(L_, "id"); lua_rawget(L_, -2); if (lua_type(L_, -1) == LUA_TNUMBER) { result = int64_t(lua_tonumber(L_, -1)); } lua_pop(L_, 2); } } return result; } bool LuaCoreStack::tangetclass(LuaSlot classobj, LuaSlot tab) { if (istable(tab) && (gettabletype(tab) == LUA_TT_TANGIBLE) && lua_getmetatable(L_, tab.index())) { lua_pushstring(L_, "__index"); lua_rawget(L_, -2); lua_replace(L_, classobj); lua_pop(L_, 1); if (istable(classobj) && (gettabletype(classobj) == LUA_TT_CLASS)) { return true; } } set(classobj, LuaNil); return false; } bool LuaCoreStack::issortablekey(LuaSlot s) const { int type = lua_type(L_, s); return (type == LUA_TBOOLEAN) || (type == LUA_TNUMBER) || (type == LUA_TSTRING) || (type == LUA_TLIGHTUSERDATA); } void LuaCoreStack::movesortablekey(LuaSlot key, LuaCoreStack &otherstack, LuaSlot otherslot) { int type = lua_type(L_, key); switch (type) { case LUA_TBOOLEAN: lua_pushboolean(otherstack.L_, lua_toboolean(L_, key)); lua_replace(otherstack.L_, otherslot); break; case LUA_TNUMBER: lua_pushnumber(otherstack.L_, lua_tonumber(L_, key)); lua_replace(otherstack.L_, otherslot); break; case LUA_TSTRING: { size_t len; const char *str = lua_tolstring(L_, key, &len); lua_pushlstring(otherstack.L_, str, len); lua_replace(otherstack.L_, otherslot); break; } case LUA_TLIGHTUSERDATA: lua_pushlightuserdata(otherstack.L_, lua_touserdata(L_, key)); lua_replace(otherstack.L_, otherslot); break; default: assert(false && "movesortablekey: not a sortable key"); } } void LuaCoreStack::cleartable(LuaSlot tab, bool clearmeta) const { assert(istable(tab)); lua_pushnil(L_); while (lua_next(L_, tab.index()) != 0) { lua_pop(L_, 1); // Pop the old value. lua_pushvalue(L_, -1); // Clone the key lua_pushnil(L_); // Push the new value. lua_rawset(L_, tab.index()); } if (clearmeta) { lua_pushnil(L_); lua_setmetatable(L_, tab.index()); } } int LuaCoreStack::rawlen(LuaSlot obj) const { return lua_rawlen(L_, obj.index()); } int LuaCoreStack::nkeys(LuaSlot obj) const { return lua_nkeys(L_, obj.index()); } int LuaCoreStack::gettabletype(LuaSlot tab) const { uint16_t bits = lua_getflagbits(L_, tab.index()); return LUA_TT_GENERAL + (bits & 0x000F); } void LuaCoreStack::settabletype(LuaSlot tab, int t) const { assert((t >= LUA_TT_GENERAL) && (t <= LUA_TT_CLASS)); int offset = (t - LUA_TT_GENERAL); lua_modflagbits(L_, tab.index(), 0x000F, offset); } int LuaCoreStack::xtype(LuaSlot slot) const { int t = lua_type(L_, slot); if (t != LUA_TTABLE) return t; uint16_t bits = lua_getflagbits(L_, slot); return LUA_TT_GENERAL + (bits & 0x000F); } bool LuaCoreStack::getvisited(LuaSlot tab) const { uint16_t bits = lua_getflagbits(L_, tab.index()); return (bits & 0x0010); } void LuaCoreStack::setvisited(LuaSlot tab, bool visited) const { lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0); } static int tailcall_continuation(lua_State *L) { int base; lua_getctx(L, &base); return lua_gettop(L) - base; } int LuaDefStack::tailcall_internal(bool passup, int base, int nargs) { lua_callk(L_, nargs, passup ? LUA_MULTRET : 0, base, tailcall_continuation); return lua_gettop(L_) - base; } LuaKeywordParser::LuaKeywordParser(const LuaCoreStack &LS0, LuaSlot slot) : keytab(slot.index()), LS(LS0.state()) { lua_State *L = LS0.state(); lua_pushnil(L); found.index_ = lua_gettop(L); lua_pushnil(L); error.index_ = lua_gettop(L); lua_pushnil(L); key.index_ = lua_gettop(L); lua_pushnil(L); val.index_ = lua_gettop(L); istable = LS.istable(keytab); if (istable) { LS.rawget(found, keytab, token_found); if (!LS.istable(found)) { LS.set(found, LuaNewTable); LS.rawset(keytab, token_found, found); } } } bool LuaKeywordParser::optional(LuaSlot out, std::string_view kw) { if (!istable) { LS.set(out, LuaNil); return false; } LS.rawget(out, keytab, kw); if (!LS.isnil(out)) { LS.rawset(found, kw, true); return true; } else { return false; } }; bool LuaKeywordParser::required(LuaSlot out, std::string_view kw) { if (!istable) { LS.set(out, LuaNil); return false; } LS.rawget(out, keytab, kw); if (!LS.isnil(out)) { LS.rawset(found, kw, true); return true; } else { LS.rawget(error, keytab, token_error); if (!LS.isstring(error)) { LS.rawset(keytab, token_error, util::ss("required keyword argument not present: ", kw)); } return false; } }; eng::string LuaKeywordParser::check() { if (!istable) { return "keyword arguments must be a table"; } LS.rawget(error, keytab, token_error); auto str = LS.trystring(error); if (str.has_value()) return str.value(); return ""; } eng::string LuaKeywordParser::final_check() { if (!istable) { return "keyword arguments must be a table"; } LS.rawget(error, keytab, token_error); auto str = LS.trystring(error); if (str.has_value()) return str.value(); LS.set(key, LuaNil); while (LS.next(keytab, key, val)) { if (LS.istoken(key)) { continue; } auto kw = LS.trystringview(key); if (!kw.has_value()) { return "keyword arguments include a non-string key"; } LS.rawget(val, found, key); if (!LS.rawequal(val, true)) { return util::ss("unrecognized keyword argument: ", kw.value()); } } return ""; } void LuaKeywordParser::check_throw() { eng::string err = check(); if (!err.empty()) { luaL_error(LS.state(), "%s", err.c_str()); } } void LuaKeywordParser::final_check_throw() { eng::string err = final_check(); if (!err.empty()) { luaL_error(LS.state(), "%s", err.c_str()); } } const char *LuaByteReader::lua_reader(lua_State *L, void *ud, size_t *size) { LuaByteReader *reader = (LuaByteReader*)ud; *size = reader->size_; const char *data = reader->data_; reader->data_ = 0; reader->size_ = 0; return data; } static const char *kwdoc = "|Parse lua keyword arguments." "|" "|The keywords module is used to help parse keyword arguments" "|for functions. The error handling is strong, it throws useful" "|error messages when the caller passes in incorrect arguments." "|This is a typical example of how it is used:" "|" "| function drawrect(args)" "| local x1,y1 = keywords.required(args, 'x1', 'y1')" "| local x2,y2 = keywords.required(args, 'x2', 'y2')" "| local color = keywords.optional(args, 'color')" "| keywords.finalcheckthrow(args)" "| ..." "| end" "|" "|The function above expects four required keyword arguments and one" "|optional one. The example code above handles all the following" "|error cases:" "|" "| * That 'args' actually is a table." "| * That the required keywords x1,y1,x2,y2 are all present." "| * That the table doesn't contain extraneous unknown keywords." "|" "|The functions keywords.required and keywords.optional don't throw" "|lua errors. Instead, they store status information in the keyword" "|table itself. Using the status information, the function" "|keywords.finalcheckthrow will throw an error if anything went wrong" "|along the way." "|" "|You can check errors when you're not finished parsing using the" "|function keywords.checkthrow() instead of keywords.finalcheckthrow()." "|Unlike finalcheckthrow, this function doesn't verify the absence of" "|extraneous keywords, which can only be done at the end." "|" "|If you don't want to throw errors at all, you can use" "|keywords.finalcheck() instead of keywords.finalcheckthrow()." "|This returns the error message, rather than throwing it." "|"; LuaDefine(keywords_optional, "table, keyword, keyword, keyword...", kwdoc) { LuaArg table; LuaExtraArgs keywords; LuaDefStack LS(L, table, keywords); LuaKeywordParser KP(LS, table); if (keywords.size() < 1) { luaL_error(L, "expected at least one keyword"); return 0; } for (int i = 0; i < keywords.size(); i++) { eng::string kw = LS.ckstring(keywords[i], "keyword"); KP.optional(keywords[i], kw); } // Return the results without using LS.result, because it doesn't // support multiple return values. lua_settop(L, keywords[keywords.size() - 1].index()); return keywords.size(); } LuaDefine(keywords_required, "table, keyword, keyword, keyword...", kwdoc) { LuaArg table; LuaExtraArgs keywords; LuaDefStack LS(L, table, keywords); LuaKeywordParser KP(LS, table); if (keywords.size() < 1) { luaL_error(L, "expected at least one keyword"); return 0; } for (int i = 0; i < keywords.size(); i++) { eng::string kw = LS.ckstring(keywords[i], "keyword"); KP.required(keywords[i], kw); } // Return the results without using LS.result, because it doesn't // support multiple return values. lua_settop(L, keywords[keywords.size() - 1].index()); return keywords.size(); } LuaDefine(keywords_check, "table", kwdoc) { LuaArg table; LuaRet result; LuaDefStack LS(L, table, result); LuaKeywordParser KP(LS, table); eng::string err = KP.check(); if (!err.empty()) { LS.set(result, err); } return LS.result(); } LuaDefine(keywords_finalcheck, "table", kwdoc) { LuaArg table; LuaRet result; LuaDefStack LS(L, table, result); LuaKeywordParser KP(LS, table); eng::string err = KP.final_check(); if (!err.empty()) { LS.set(result, err); } return LS.result(); } LuaDefine(keywords_checkthrow, "table", kwdoc) { LuaArg table; LuaDefStack LS(L, table); LuaKeywordParser KP(LS, table); KP.check_throw(); return LS.result(); } LuaDefine(keywords_finalcheckthrow, "table", kwdoc) { LuaArg table; LuaDefStack LS(L, table); LuaKeywordParser KP(LS, table); KP.final_check_throw(); return LS.result(); } LuaDefine(unittests_token, "", "Unit tests for LuaToken encoding") { // Test round-trip encoding for various strings. LuaAssertStrEq(L, LuaToken("a").str(), "a"); LuaAssertStrEq(L, LuaToken("z").str(), "z"); LuaAssertStrEq(L, LuaToken("0").str(), "0"); LuaAssertStrEq(L, LuaToken("9").str(), "9"); LuaAssertStrEq(L, LuaToken("null").str(), "null"); LuaAssertStrEq(L, LuaToken("hello").str(), "hello"); LuaAssertStrEq(L, LuaToken("zzzzzzzzzzzz").str(), "zzzzzzzzzzzz"); LuaAssertStrEq(L, LuaToken("a0").str(), "a0"); LuaAssertStrEq(L, LuaToken("0a").str(), "0a"); LuaAssertStrEq(L, LuaToken("000000000000").str(), "000000000000"); LuaAssertStrEq(L, LuaToken("foo_bar").str(), "foo_bar"); LuaAssertStrEq(L, LuaToken("_").str(), "_"); LuaAssertStrEq(L, LuaToken("a_b").str(), "a_b"); // Test that empty/invalid strings produce the empty token. LuaAssert(L, LuaToken(std::string_view("")).empty()); LuaAssert(L, LuaToken(std::string_view("hello world")).empty()); LuaAssert(L, LuaToken(std::string_view("aaaaaaaaaaaaa")).empty()); // 13 chars // Test that numeric ordering matches lexicographic ordering. LuaAssert(L, LuaToken("a").value > LuaToken("0").value); LuaAssert(L, LuaToken("b").value > LuaToken("a").value); LuaAssert(L, LuaToken("aa").value > LuaToken("a").value); LuaAssert(L, LuaToken("ab").value > LuaToken("aa").value); LuaAssert(L, LuaToken("b").value > LuaToken("az").value); LuaAssert(L, LuaToken("ba").value > LuaToken("az").value); LuaAssert(L, LuaToken("hello").value > LuaToken("hell").value); LuaAssert(L, LuaToken("a0").value > LuaToken("a").value); LuaAssert(L, LuaToken("a").value != LuaToken("a0").value); LuaAssert(L, LuaToken("0").value > LuaToken("_").value); LuaAssert(L, LuaToken("a").value > LuaToken("9").value); LuaAssert(L, LuaToken("a_b").value > LuaToken("a_a").value); LuaAssert(L, LuaToken("foo_bar").value != LuaToken("foobar").value); return 0; }