#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; 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, "classnames"); lua_newtable(L); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushstring(L, "tangibles"); lua_newtable(L); lua_rawset(L, LUA_REGISTRYINDEX); lua_pushstring(L, "worldtype"); lua_pushnumber(L, WORLD_TYPE_MASTER); 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); } 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; } eng::string LuaCoreStack::classname(LuaSlot input) const { LuaVar lookup, classtab, classname; LuaExtStack LS(L_, lookup, classtab, classname); int xt = xtype(input); if (xt == LUA_TSTRING) { LS.rawget(lookup, LuaRegistry, "classes"); LS.rawget(classtab, lookup, input); if (xtype(classtab) != LUA_TT_CLASS) { return ""; } return LS.ckstring(input); } else if (xt == LUA_TT_TANGIBLE) { if (!LS.getmetatable(lookup, input)) { return ""; } LS.rawget(classtab, lookup, "__index"); if (xtype(classtab) != LUA_TT_CLASS) { return ""; } LS.rawget(lookup, LuaRegistry, "classnames"); LS.rawget(classname, lookup, classtab); if (!LS.isstring(classname)) { return ""; } return LS.ckstring(classname); } else if (xt == LUA_TT_CLASS) { LS.rawget(lookup, LuaRegistry, "classnames"); LS.rawget(classname, lookup, input); if (!LS.isstring(classname)) { return ""; } return LS.ckstring(classname); } else { return ""; } } static eng::string nonexistentclass(eng::string name) { eng::string err; if (!sv::is_lua_classname(name)) { err = "invalid class name: " + name; } else { err = "not a class: " + name; } return err; } eng::string LuaCoreStack::getclass(LuaSlot classtab, LuaSlot input) const { LuaVar lookup; LuaExtStack LS(L_, lookup); int xt = xtype(input); if (xt == LUA_TSTRING) { LS.rawget(lookup, LuaRegistry, "classes"); LS.rawget(classtab, lookup, input); if (xtype(classtab) != LUA_TT_CLASS) { eng::string classname = LS.ckstring(input); return nonexistentclass(LS.ckstring(input)); } return ""; } else if (xt == LUA_TT_TANGIBLE) { if (!LS.getmetatable(lookup, input)) { eng::string err = "inactive tangible has no class"; return err; } LS.rawget(classtab, lookup, "__index"); if (xtype(classtab) != LUA_TT_CLASS) { eng::string err = "tangible has invalid class"; } return ""; } else if (xt == LUA_TT_CLASS) { LS.set(classtab, input); return ""; } else { eng::string err = "getclass must be passed a string, class, or tangible"; return err; } } eng::string LuaCoreStack::getclass(LuaSlot classtab, std::string_view classname) const { LuaVar lookup; LuaExtStack LS(L_, lookup); LS.rawget(lookup, LuaRegistry, "classes"); LS.rawget(classtab, lookup, classname); if (xtype(classtab) != LUA_TT_CLASS) { return nonexistentclass(eng::string(classname)); } return ""; } 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); } 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::get_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::set_world_type(WorldType t) const { lua_pushstring(L_, "worldtype"); lua_pushnumber(L_, (int)t); lua_rawset(L_, LUA_REGISTRYINDEX); } 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"; } 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(); } } 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; }