diff --git a/luprex/core/TODO b/luprex/core/TODO index 111c2258..e93dc485 100644 --- a/luprex/core/TODO +++ b/luprex/core/TODO @@ -2,8 +2,6 @@ Calling out to external servers. Support ANSI escape sequences on output. -Make math.random do something predictable. - Do something about std::cerr && std::cout once and for all. Fix math.random diff --git a/luprex/core/cpp/globaldb.cpp b/luprex/core/cpp/globaldb.cpp index c85aa72f..d90367f1 100644 --- a/luprex/core/cpp/globaldb.cpp +++ b/luprex/core/cpp/globaldb.cpp @@ -1,7 +1,7 @@ #include "luastack.hpp" #include "globaldb.hpp" -LuaDefine(global_once, "string", "for a given string, returns true exactly once") { +LuaDefine(global_once, "name", "for a given string, returns true exactly once") { LuaArg name; LuaRet flag; LuaVar oncedb, val; @@ -14,7 +14,7 @@ LuaDefine(global_once, "string", "for a given string, returns true exactly once" return LS.result(); } - LS.checkstring(name); + LS.checkstring(name, "name"); LS.rawget(val, oncedb, name); if (!LS.isnil(val)) { LS.set(flag, false); @@ -35,7 +35,7 @@ LuaDefine(global_clearonce, "name", "reset the specified once-flag") { if (!LS.istable(oncedb)) { return LS.result(); } - LS.checkstring(name); + LS.checkstring(name, "name"); LS.rawset(oncedb, name, LuaNil); return LS.result(); } @@ -45,7 +45,7 @@ LuaDefine(global_table, "globalname", "get a table where global data can be stor LuaRet globaltab; LuaVar globaldb; LuaStack LS(L, globalname, globaltab, globaldb); - LS.checkstring(globalname); + LS.checkstring(globalname, "globalname"); // Get a pointer to the globaldb. LS.rawget(globaldb, LuaRegistry, "globaldb"); diff --git a/luprex/core/cpp/luastack.cpp b/luprex/core/cpp/luastack.cpp index c938fa4b..7ba295e9 100644 --- a/luprex/core/cpp/luastack.cpp +++ b/luprex/core/cpp/luastack.cpp @@ -3,6 +3,8 @@ #include #include #include +#include "wrap-string.hpp" +#include "wrap-set.hpp" LuaSpecial LuaRegistry(LUA_REGISTRYINDEX); LuaNilMarker LuaNil; @@ -76,6 +78,10 @@ lua_State *LuaStack::newstate (lua_Alloc allocf) { return L; } +void LuaStack::argerr(const char *nm, const char *tp) const { + luaL_error(L_, "'%s' should be %s", nm, tp); +} + bool LuaStack::isinteger(LuaSlot s) const { if (lua_type(L_, s) == LUA_TNUMBER) { lua_Number result = lua_tonumber(L_, s); @@ -93,11 +99,12 @@ bool LuaStack::isint(LuaSlot s) const { } bool LuaStack::ckboolean(LuaSlot s) const { - luaL_checktype(L_, s, LUA_TBOOLEAN); + checkboolean(s, "value"); return lua_toboolean(L_, s) ? true:false; } lua_Integer LuaStack::ckinteger(LuaSlot s) const { + checknumber(s, "value"); luaL_checktype(L_, s, LUA_TNUMBER); lua_Number result = lua_tonumber(L_, s); lua_Integer iresult(result); @@ -109,7 +116,7 @@ lua_Integer LuaStack::ckinteger(LuaSlot s) const { } int LuaStack::ckint(LuaSlot s) const { - luaL_checktype(L_, s, LUA_TNUMBER); + checknumber(s, "value"); lua_Number result = lua_tonumber(L_, s); int iresult(result); if (iresult != result) { @@ -120,31 +127,31 @@ int LuaStack::ckint(LuaSlot s) const { } lua_Number LuaStack::cknumber(LuaSlot s) const { - luaL_checktype(L_, s, LUA_TNUMBER); + checknumber(s, "value"); return lua_tonumber(L_, s); } eng::string LuaStack::ckstring(LuaSlot s) const { - luaL_checktype(L_, s, LUA_TSTRING); + checkstring(s, "value"); size_t len; const char *str = lua_tolstring(L_, s, &len); return eng::string(str, len); } std::string_view LuaStack::ckstringview(LuaSlot s) const { - luaL_checktype(L_, s, LUA_TSTRING); + checkstring(s, "value"); size_t len; const char *str = lua_tolstring(L_, s, &len); return std::string_view(str, len); } lua_State *LuaStack::ckthread(LuaSlot s) const { - luaL_checktype(L_, s, LUA_TTHREAD); + checkthread(s, "value"); return lua_tothread(L_, s); } LuaToken LuaStack::cktoken(LuaSlot s) const { - luaL_checktype(L_, s, LUA_TLIGHTUSERDATA); + checktoken(s, "value"); return LuaToken(lua_touserdata(L_, s)); } @@ -457,3 +464,41 @@ bool LuaStack::getvisited(LuaSlot tab) const { void LuaStack::setvisited(LuaSlot tab, bool visited) const { lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0); } + +LuaKeywordParser::LuaKeywordParser(lua_State *L, int slot) { + L_ = L; + slot_ = slot; + if (!lua_istable(L_, slot_)) { + luaL_error(L_, "expected an argument which is a table full of keywords"); + } +} + +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; + } +}; + +void LuaKeywordParser::check_unparsed_keywords() { + 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)) { + luaL_error(L_, "keyword table contains non-string key"); + } + const char *kw = lua_tostring(L_, -1); + if (parsed_.find(kw) == parsed_.end()) { + luaL_error(L_, "keyword %s not known", kw); + } + } + assert(false && "should never get here in check_unparsed_keywords"); + } +} + diff --git a/luprex/core/cpp/planemap.cpp b/luprex/core/cpp/planemap.cpp index cfefce3e..ae04f10c 100644 --- a/luprex/core/cpp/planemap.cpp +++ b/luprex/core/cpp/planemap.cpp @@ -827,90 +827,61 @@ void PlaneMap::untrack_all() { } } -eng::string PlaneScan::configure(const LuaStack &LS0, LuaSlot config) { +void PlaneScan::configure(LuaKeywordParser &kp) { + lua_State *L = kp.state(); LuaVar val, vx, vy, vz; - LuaStack LS(LS0.state(), val, vx, vy, vz); - - if (!LS.istable(config)) { - return "scan configuration is not a table"; - } + LuaStack LS(L, val, vx, vy, vz); bool have_plane = false; bool have_center = false; bool have_radius = false; bool have_shape = false; bool have_near = false; - int parameters = 0; - LS.rawget(val, config, "plane"); - if (!LS.isnil(val)) { - if (!LS.isstring(val)) { - return "scan configuration: 'plane' must be a string"; - } + if (kp.parse(val, "plane")) { + LS.checkstring(val, "plane"); plane_ = LS.ckstring(val); have_plane = true; - parameters += 1; } - LS.rawget(vx, config, "centerx"); - LS.rawget(vy, config, "centery"); - LS.rawget(vz, config, "centerz"); + kp.parse(vx, "centerx"); + kp.parse(vy, "centery"); + kp.parse(vz, "centerz"); if ((!LS.isnil(vx)) || (!LS.isnil(vy)) || (!LS.isnil(vz))) { - if (!LS.isnumber(vx)) { - return "scan configuration: 'centerx' must be a number"; - } - if (!LS.isnumber(vy)) { - return "scan configuration: 'centery' must be a number"; - } - if (!LS.isnumber(vz)) { - return "scan configuration: 'centerz' must be a number"; - } + LS.checknumber(vx, "centerx"); + LS.checknumber(vy, "centery"); + LS.checknumber(vz, "centerz"); center_.x = LS.cknumber(vx); center_.y = LS.cknumber(vy); center_.z = LS.cknumber(vz); have_center = true; - parameters += 3; } - LS.rawget(val, config, "radius"); - if (!LS.isnil(val)) { - if (!LS.isnumber(val)) { - return "scan configuration: 'radius' must be a number"; - } + if (kp.parse(val, "radius")) { + LS.checknumber(val, "radius"); radius_.x = LS.cknumber(val); radius_.y = radius_.z = radius_.x; have_radius = true; - parameters += 1; } - LS.rawget(vx, config, "radiusx"); - LS.rawget(vy, config, "radiusy"); - LS.rawget(vz, config, "radiusz"); + kp.parse(vx, "radiusx"); + kp.parse(vy, "radiusy"); + kp.parse(vz, "radiusz"); if ((!LS.isnil(vx)) || (!LS.isnil(vy)) || (!LS.isnil(vz))) { - if (!LS.isnumber(vx)) { - return "scan configuration: 'radiusx' must be a number"; - } - if (!LS.isnumber(vy)) { - return "scan configuration: 'radiusy' must be a number"; - } - if (!LS.isnumber(vz)) { - return "scan configuration: 'radiusz' must be a number"; - } + LS.checknumber(vx, "radiusx"); + LS.checknumber(vy, "radiusy"); + LS.checknumber(vz, "radiusz"); if (have_radius) { - return "scan configuration: specified both 'radius' and 'radiusx'"; + luaL_error(L, "scan configuration: specified both 'radius' and 'radiusx'"); } radius_.x = LS.cknumber(vx); radius_.y = LS.cknumber(vy); radius_.z = LS.cknumber(vz); have_radius = true; - parameters += 3; } - LS.rawget(val, config, "shape"); - if (!LS.isnil(val)) { - if (!LS.isstring(val)) { - return "scan configuration: 'shape' must be a string"; - } + if (kp.parse(val, "shape")) { + LS.checkstring(val, "shape"); eng::string shape = LS.ckstring(val); if (shape == "box") { shape_ = BOX; @@ -919,50 +890,40 @@ eng::string PlaneScan::configure(const LuaStack &LS0, LuaSlot config) { } else if (shape == "cylinder") { shape_ = CYLINDER; } else { - return util::ss("scan configuration: unknown shape ", shape); + luaL_error(L, "scan configuration: unknown shape %s", shape.c_str()); } have_shape = true; - parameters += 1; } - LS.rawget(val, config, "near"); - if (!LS.isnil(val)) { + if (kp.parse(val, "near")) { int64_t id = LS.tanid(val); if (id == 0) { - return "scan configuration: 'near' must be a tangible"; + luaL_error(L, "scan configuration: 'near' must be a tangible"); } if (have_center) { - return "scan configuration: specified both 'center' and 'near'"; + luaL_error(L, "scan configuration: specified both 'center' and 'near'"); } if (have_plane) { - return "scan configuration: specified both 'plane' and 'near'"; + luaL_error(L, "scan configuration: specified both 'plane' and 'near'"); } near_ = id; have_center = true; have_plane = true; have_near = true; - parameters += 1; } - LS.rawget(val, config, "include"); - if (!LS.isnil(val)) { - if (!LS.isboolean(val)) { - return "scan configuration: 'include' must be a boolean"; - } + if (kp.parse(val, "include")) { + LS.checkboolean(val, "include"); if (!have_near) { - return "scan configuration: 'include' specified without 'near'"; + luaL_error(L, "scan configuration: 'include' specified without 'near'"); } include_near_ = LS.ckboolean(val); - parameters += 1; } - LS.rawget(val, config, "wholeplane"); - if (!LS.isnil(val)) { - if (!LS.isstring(val)) { - return "scan configuration: 'wholeplane' must be a string"; - } + if (kp.parse(val, "wholeplane")) { + LS.checkstring(val, "wholeplane"); if (have_plane || have_center || have_radius || have_shape) { - return "scan configuration: do not specify plane, center, shape, or radius with 'wholeplane'"; + luaL_error(L, "scan configuration: do not specify plane, center, shape, or radius with 'wholeplane'"); } plane_ = LS.ckstring(val); set_whole_plane(); @@ -970,26 +931,20 @@ eng::string PlaneScan::configure(const LuaStack &LS0, LuaSlot config) { have_center = true; have_radius = true; have_shape = true; - parameters += 1; - } - - if (lua_nkeys(LS.state(), config.index()) != parameters) { - return "scan configuration: unrecognized parameters in table"; } if (!have_plane) { - return "scan configuration: did not specify plane"; + luaL_error(L, "scan configuration: did not specify plane"); } if (!have_radius) { - return "scan configuration: did not specify radius"; + luaL_error(L, "scan configuration: did not specify radius"); } if (!have_center) { - return "scan configuration: did not specify center"; + luaL_error(L, "scan configuration: did not specify center"); } if (!have_shape) { shape_ = SPHERE; // default value. } - return ""; } eng::string PlaneScan::debug_string() const { diff --git a/luprex/core/cpp/table.cpp b/luprex/core/cpp/table.cpp index 05cb235d..cc722c35 100644 --- a/luprex/core/cpp/table.cpp +++ b/luprex/core/cpp/table.cpp @@ -8,8 +8,8 @@ bool table_equal(LuaStack &LS, LuaSlot t1, LuaSlot t2) { lua_State *L = LS.state(); int top = lua_gettop(L); - LS.checktable(t1); - LS.checktable(t2); + LS.checktable(t1, "table1"); + LS.checktable(t2, "table2"); int nkeys1 = lua_nkeys(L, t1.index()); int nkeys2 = lua_nkeys(L, t2.index()); if (nkeys1 != nkeys2) return false; @@ -122,7 +122,7 @@ LuaDefine(table_clear, "table,metaflag", "clear all keys, and optionally the met LuaArg tab, clearmeta; LuaVar metatable, metafield; LuaStack LS(L, tab, clearmeta, metatable, metafield); - LS.checktable(tab); + LS.checktable(tab, "table"); if (LS.ckboolean(clearmeta)) { LS.getmetatable(metatable, tab); if (LS.istable(metatable)) { @@ -417,9 +417,9 @@ LuaDefine(deque_size, "deque", "return the number of items in the deque") { LuaArg deque; LuaRet size; LuaStack LS(L, deque, size); - LS.checktable(deque); + LS.checktable(deque, "deque"); LS.rawget(size, deque, DEQUE_FILL); - LS.checknumber(size); + LS.checknumber(size, "deque size"); return LS.result(); } diff --git a/luprex/core/cpp/world-accessor.cpp b/luprex/core/cpp/world-accessor.cpp index c9772913..c7413dce 100644 --- a/luprex/core/cpp/world-accessor.cpp +++ b/luprex/core/cpp/world-accessor.cpp @@ -138,7 +138,7 @@ LuaDefine(tangible_build, "configtable", LuaRet database; LuaStack LS(L, config, classname, classtab, database, mt); - LS.checktable(config); + LS.checktable(config, "config"); // Get the class of the new tangible. LS.rawget(classname, config, "class"); eng::string err = LS.getclass(classtab, classname); @@ -346,8 +346,13 @@ LuaDefine(tangible_find, "config", "|" "| wholeplane: the name of the plane to scan in its entirety" "|" - "|It is valid to use 0.0 as the radius. If you do so, then only" - "|objects at the exact center you specify will be found." + "|It is valid to use 0.0 as a radius. For example, you could" + "|use shape='cylinder' and radiusz=0.0 to scan a flat" + "|circular area." + "|" + "|It is also valid to use math.huge (infinity) as a radius. For" + "|example, you could use shape='cylinder' and radiusz=math.huge" + "|to scan an infinitely tall cylinder." "|" "|If you are making a 2D game, it works fine to set all object Z" "|coordinates to zero, and then do searches with centerz=0.0" @@ -356,11 +361,11 @@ LuaDefine(tangible_find, "config", LuaArg config; LuaRet result; LuaStack LS(L, config, result); + LuaKeywordParser kw(LS, config); PlaneScan scan; - eng::string error = scan.configure(LS, config); - if (!error.empty()) { - luaL_error(L, "%s", error.c_str()); - } + scan.configure(kw); + kw.check_unparsed_keywords(); + // When the configure routine sees the 'near' flag, it stores the tangible // ID, but not the center and plane, because doing so would require it to // know about world models. We have to handle center and plane for 'near' diff --git a/luprex/core/cpp/world-difftab.cpp b/luprex/core/cpp/world-difftab.cpp index 59f0c10f..d7410895 100644 --- a/luprex/core/cpp/world-difftab.cpp +++ b/luprex/core/cpp/world-difftab.cpp @@ -463,10 +463,10 @@ LuaDefine(table_diffcompare, "mtnmap,mtab,stnmap,stab", "for unit testing only") LuaVar tthread; LuaStack MLS(L, mtnmap, mtab, mstnmap, mstab, dbgstring, tthread); // Check the arguments. - MLS.checktable(mtnmap); - MLS.checktable(mstnmap); - MLS.checktable(mtab); - MLS.checktable(mstab); + MLS.checktable(mtnmap, "mtnmap"); + MLS.checktable(mstnmap, "mstnmap"); + MLS.checktable(mtab, "mtab"); + MLS.checktable(mstab, "mstab"); // Create a temporary thread to be the 'synch model'. We'll use the // existing thread as the 'master model'. Move two tables to the synch thread. @@ -493,9 +493,9 @@ LuaDefine(table_diffapply, "mtnmap,mtab,mstab", "for unit testing only") { LuaVar tthread, tangibles, mntmap, key, val; LuaStack MLS(L, mtnmap, mtab, mstab, eql, eqlstr, rtab, tthread, tangibles, mntmap, key, val); // Check the arguments. - MLS.checktable(mtnmap); - MLS.checktable(mtab); - MLS.checktable(mstab); + MLS.checktable(mtnmap, "mtnmap"); + MLS.checktable(mtab, "mtab"); + MLS.checktable(mstab, "mstab"); // Get the tangibles map. MLS.rawget(tangibles, LuaRegistry, "tangibles");