#include "luastack.hpp" #include #include #include #include "wrap-string.hpp" #include "wrap-set.hpp" #include "wrap-sstream.hpp" LuaSpecial LuaRegistry(LUA_REGISTRYINDEX); LuaNilMarker LuaNil; LuaNewTableMarker LuaNewTable; 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 { uint64_t token = (uint64_t)value; char buffer[9]; for (int i = 0; i < 8; i++) { unsigned char c = token; buffer[7-i] = c; token >>= 8; } buffer[8] = 0; 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, "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); } bool LuaCoreStack::isinteger(LuaSlot s) const { if (lua_type(L_, s) == LUA_TNUMBER) { lua_Number result = lua_tonumber(L_, s); if (lua_Integer(result) == result) return true; } return false; } bool LuaCoreStack::isint(LuaSlot s) const { if (lua_type(L_, s) == LUA_TNUMBER) { lua_Number result = lua_tonumber(L_, s); if (int(result) == result) return true; } return false; } bool LuaCoreStack::ckboolean(LuaSlot s) const { checkboolean(s, "value"); return lua_toboolean(L_, s) ? true:false; } lua_Integer LuaCoreStack::ckinteger(LuaSlot s) const { checknumber(s, "value"); luaL_checktype(L_, s, LUA_TNUMBER); lua_Number result = lua_tonumber(L_, s); lua_Integer iresult(result); if (iresult != result) { luaL_error(L_, "not a valid integer"); return 0; } return iresult; } int LuaCoreStack::ckint(LuaSlot s) const { checknumber(s, "value"); lua_Number result = lua_tonumber(L_, s); int iresult(result); if (iresult != result) { luaL_error(L_, "not a valid int"); return 0; } return iresult; } lua_Number LuaCoreStack::cknumber(LuaSlot s) const { checknumber(s, "value"); return lua_tonumber(L_, s); } eng::string LuaCoreStack::ckstring(LuaSlot s) const { checkstring(s, "value"); size_t len; const char *str = lua_tolstring(L_, s, &len); return eng::string(str, len); } std::string_view LuaCoreStack::ckstringview(LuaSlot s) const { checkstring(s, "value"); size_t len; const char *str = lua_tolstring(L_, s, &len); return std::string_view(str, len); } lua_State *LuaCoreStack::ckthread(LuaSlot s) const { checkthread(s, "value"); return lua_tothread(L_, s); } LuaToken LuaCoreStack::cktoken(LuaSlot s) const { checktoken(s, "value"); return LuaToken(lua_touserdata(L_, s)); } 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; } } int 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; } 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; } bool LuaCoreStack::validclassname(std::string_view cname) { if (cname.empty()) return false; if (cname == "_G") return false; return true; } bool LuaCoreStack::validclassname(LuaSlot slot) const { if (!isstring(slot)) return false; return validclassname(ckstring(slot)); } eng::string LuaCoreStack::classname(LuaSlot tab) const { eng::string result; if ((istable(tab)) && (gettabletype(tab) == LUA_TT_CLASS)) { LuaVar classes, name, dup; LuaExtStack LS(L_, classes, name, dup); // Get the classes table from the registry. LS.rawget(classes, LuaRegistry, "classes"); // Try the efficient approach: get the class name from // the class, and confirm it by checking the classes table. LS.rawget(name, tab, "__class"); if (LS.isstring(name)) { LS.rawget(dup, classes, name); if (LS.rawequal(dup, tab)) { return LS.ckstring(name); } } // Do it the brute force way: scan the classes table. LS.set(name, LuaNil); while (LS.next(classes, name, dup)) { if (LS.rawequal(dup, tab)) { return LS.ckstring(name); } } } return ""; } eng::string LuaCoreStack::getclass(LuaSlot classtab, LuaSlot classname) const { lua_checkstack(L_, 20); LuaVar globtab, cname; LuaExtStack LS(L_, globtab, cname); LS.getglobaltable(globtab); if (LS.isstring(classname)) { if (!validclassname(LS.ckstring(classname))) { eng::string err = "invalid class name: " + LS.ckstring(classname); return err; } LS.rawget(classtab, globtab, classname); if (!LS.istable(classtab)) { eng::string err = "not a class: " + LS.ckstring(classname); return err; } LS.rawget(cname, classtab, "__class"); if (!LS.rawequal(cname, classname)) { eng::string err = "not a valid class: " + LS.ckstring(classname); return err; } return ""; } else if (LS.istable(classname)) { LS.rawget(cname, classname, "__class"); if (!LS.isstring(cname)) { eng::string err = "table is not a class."; return err; } if (!validclassname(LS.ckstring(cname))) { eng::string err = "invalid class name: " + LS.ckstring(cname); return err; } LS.rawget(classtab, globtab, cname); if (!LS.rawequal(classtab, classname)) { eng::string err = "not a valid class: " + LS.ckstring(cname); return err; } return ""; } else { eng::string err = "getclass expects a string or a classtab"; return err; } } eng::string LuaCoreStack::getclass(LuaSlot tab, std::string_view name) const { push_any_value(name); LuaSpecial classname(lua_gettop(L_)); eng::string err = getclass(tab, classname); lua_pop(L_, 1); return err; } void LuaCoreStack::makeclass(LuaSlot classtab, LuaSlot classname) const { LuaVar classes, globtab, cname; LuaExtStack LS(L_, classes, globtab, cname); // Validate the class name. assert(LS.validclassname(classname)); // Fetch the classes table from the registry. LS.rawget(classes, LuaRegistry, "classes"); assert(LS.istable(classes)); // 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); } // 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, "__class", classname); 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 { 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::issortablekey(LuaSlot s) const { int type = lua_type(L_, s); return (type == LUA_TBOOLEAN) || (type == LUA_TNUMBER) || (type == LUA_TSTRING); } 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; } 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); } WorldType LuaCoreStack::world_type() const { lua_pushstring(L_, "worldtype"); lua_rawget(L_, LUA_REGISTRYINDEX); lua_Integer n = lua_tointeger(L_, -1); lua_pop(L_, 1); assert(n != 0); return (WorldType)n; } void LuaCoreStack::guard_nopredict(const char *fn) { if (lua_isyieldable(L_)) { if (!is_authoritative()) { lua_yield(L_, 0); luaL_error(L_, "unexplained nopredict failure in %s", fn); } } } LuaKeywordParser::LuaKeywordParser(lua_State *L, int slot) { L_ = L; slot_ = slot; not_table_ = !lua_istable(L_, slot_); if (not_table_) { lua_newtable(L_); lua_replace(L_, slot_); } } bool LuaKeywordParser::parse(LuaSlot out, const char *kw) { lua_pushstring(L_, kw); lua_rawget(L_, slot_); lua_replace(L_, out.index()); if (!lua_isnil(L_, out.index())) { parsed_.insert(kw); return true; } else { return false; } }; eng::string LuaKeywordParser::final_check() { if (not_table_) { return "expected a keyword table"; } if (lua_nkeys(L_, slot_) != int(parsed_.size())) { lua_pushnil(L_); while (lua_next(L_, slot_) != 0) { lua_pop(L_, 1); // Don't need the value. if (!lua_isstring(L_, -1)) { return "keyword table contains non-string key"; } const char *kw = lua_tostring(L_, -1); if (parsed_.find(kw) == parsed_.end()) { eng::ostringstream oss; oss << "keyword " << kw << " not known"; return oss.str(); } } assert(false && "should never get here in check_unparsed_keywords"); } return ""; } void LuaKeywordParser::final_check_throw() { eng::string err = final_check(); if (!err.empty()) { luaL_error(L_, "%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; }