From 2d531b28b30afa4520c0174682c45c91ff60c40b Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 20 Jan 2025 18:54:05 -0500 Subject: [PATCH] Reworking the keyword parser, also fixed some dynamic linking issues --- luprex/Makefile | 2 +- luprex/cpp/core/drivenengine.cpp | 12 ++-- luprex/cpp/core/http.cpp | 46 ++++++------- luprex/cpp/core/luastack.cpp | 106 +++++++++++++++++++++-------- luprex/cpp/core/luastack.hpp | 82 +++++++++++++++------- luprex/cpp/core/planemap.cpp | 16 +++-- luprex/cpp/core/pprint.cpp | 9 +-- luprex/cpp/core/world-accessor.cpp | 82 +++++++++++++++------- luprex/cpp/drv/driver-linux.cpp | 6 +- 9 files changed, 243 insertions(+), 118 deletions(-) diff --git a/luprex/Makefile b/luprex/Makefile index 02fef99c..2dd2e7b4 100644 --- a/luprex/Makefile +++ b/luprex/Makefile @@ -115,7 +115,7 @@ ifeq "$(OS)" "Linux" LUPREXSTATIC_EXE=luprexstatic COMPILE=g++ -Wall $(OPT) -std=c++17 -fvisibility=hidden -c -MMD -fPIC -o LINKDLL=g++ -Wall $(OPT) -std=c++17 -export-dynamic -Wl,--no-allow-shlib-undefined -Wl,-z,defs -shared -o - LINKEXE=g++ -Wall $(OPT) -std=c++17 -o + LINKEXE=g++ -Wall $(OPT) -std=c++17 -export-dynamic -o MAKEDEPS=true OPENSSL_INCLUDE=-I./ext/openssl-3.0.1/inc LUA_FLAGS=-DLUA_USE_APICHECK -DLUA_USE_POSIX diff --git a/luprex/cpp/core/drivenengine.cpp b/luprex/cpp/core/drivenengine.cpp index 6da6d905..1c5ab0a2 100644 --- a/luprex/cpp/core/drivenengine.cpp +++ b/luprex/cpp/core/drivenengine.cpp @@ -927,12 +927,6 @@ static void replaycore_step(EngineWrapper *w) { ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// -#if defined(__linux__) - #define DLLEXPORT __attribute__((visibility("default"))) -#elif defined(_WIN32) - #define DLLEXPORT __declspec(dllexport) -#endif - static void init_engine_wrapper_helper(EngineWrapper *w) { static bool called_initializer; assert(DrivenEngineInitializerReg::func != nullptr); @@ -973,6 +967,12 @@ static void init_engine_wrapper_helper(EngineWrapper *w) { w->release = release; }; +#if defined(__linux__) + #define DLLEXPORT __attribute__((visibility("default"))) +#elif defined(_WIN32) + #define DLLEXPORT __declspec(dllexport) +#endif + extern "C" { DLLEXPORT void init_engine_wrapper(EngineWrapper *w) { init_engine_wrapper_helper(w); diff --git a/luprex/cpp/core/http.cpp b/luprex/cpp/core/http.cpp index 35c6eb83..cd068129 100644 --- a/luprex/cpp/core/http.cpp +++ b/luprex/cpp/core/http.cpp @@ -744,51 +744,51 @@ void HttpClientRequest::set_defaults() { void HttpClientRequest::configure(LuaKeywordParser &kp) { LuaVar val; LuaExtStack LS(kp.state(), val); - if (kp.parse(val, "method")) { + if (kp.optional(val, "method")) { set_method(LS, val); } - if (kp.parse(val, "host")) { + if (kp.optional(val, "host")) { set_host(LS, val); } - if (kp.parse(val, "port")) { + if (kp.optional(val, "port")) { set_port(LS, val); } - if (kp.parse(val, "path")) { + if (kp.optional(val, "path")) { set_path(LS, val); } - if (kp.parse(val, "params")) { + if (kp.optional(val, "params")) { set_params(LS, val); } - if (kp.parse(val, "url")) { + if (kp.optional(val, "url")) { set_url(LS, val); } - if (kp.parse(val, "verifycertificate")) { + if (kp.optional(val, "verifycertificate")) { set_verify_certificate(LS, val); } - if (kp.parse(val, "mimetype")) { + if (kp.optional(val, "mimetype")) { set_mime_type(LS, val); } - if (kp.parse(val, "content")) { + if (kp.optional(val, "content")) { set_content(LS, val); } - if (kp.parse(val, "html")) { + if (kp.optional(val, "html")) { set_content(LS, val); set_mime_type("text/html"); } - if (kp.parse(val, "text")) { + if (kp.optional(val, "text")) { set_content(LS, val); set_mime_type("text/plain"); } - if (kp.parse(val, "json")) { + if (kp.optional(val, "json")) { set_content(LS, val); set_mime_type("application/json"); } - if (kp.parse(val, "bytes")) { + if (kp.optional(val, "bytes")) { set_content(LS, val); set_mime_type("application/octet-stream"); } - if (kp.parse(val, "jsonvalue")) { + if (kp.optional(val, "jsonvalue")) { set_jsonvalue(LS, val); } } @@ -1044,35 +1044,35 @@ void HttpServerResponse::set_jsonvalue(LuaCoreStack &LS, LuaSlot val) { void HttpServerResponse::configure(LuaKeywordParser &kp) { LuaVar val; LuaExtStack LS(kp.state(), val); - if (kp.parse(val, "status")) { + if (kp.optional(val, "status")) { set_status(LS, val); } - if (kp.parse(val, "maxage")) { + if (kp.optional(val, "maxage")) { set_max_age(LS, val); } - if (kp.parse(val, "mimetype")) { + if (kp.optional(val, "mimetype")) { set_mime_type(LS, val); } - if (kp.parse(val, "content")) { + if (kp.optional(val, "content")) { set_content(LS, val); } - if (kp.parse(val, "html")) { + if (kp.optional(val, "html")) { set_content(LS, val); set_mime_type("text/html"); } - if (kp.parse(val, "text")) { + if (kp.optional(val, "text")) { set_content(LS, val); set_mime_type("text/plain"); } - if (kp.parse(val, "json")) { + if (kp.optional(val, "json")) { set_content(LS, val); set_mime_type("application/json"); } - if (kp.parse(val, "bytes")) { + if (kp.optional(val, "bytes")) { set_content(LS, val); set_mime_type("application/octet-stream"); } - if (kp.parse(val, "jsonvalue")) { + if (kp.optional(val, "jsonvalue")) { set_jsonvalue(LS, val); } } diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index cdb78d53..30709e6e 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -10,6 +10,9 @@ 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; @@ -650,52 +653,101 @@ void LuaCoreStack::guard_nopredict(const char *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_); +LuaKeywordParser::LuaKeywordParser(const LuaCoreStack &LS0, LuaSlot slot) + : keytab(slot.index()), LS(LS0.state(), found, error, key, val) { + 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::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); +bool LuaKeywordParser::optional(LuaSlot out, const char *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; } }; -eng::string LuaKeywordParser::final_check() { - if (not_table_) { - return "expected a keyword table"; +bool LuaKeywordParser::required(LuaSlot out, const char *kw) { + if (!istable) { + LS.set(out, LuaNil); + return false; } - 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"; + + 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)); } - 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 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(L_, "%s", err.c_str()); + luaL_error(LS.state(), "%s", err.c_str()); } } diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp index 1eed44ad..8212bc11 100644 --- a/luprex/cpp/core/luastack.hpp +++ b/luprex/cpp/core/luastack.hpp @@ -1201,49 +1201,83 @@ public: // // * It makes sure the keyword table actually is a table. // -// * It makes sure that you didn't put an unrecognized keyword -// into the keyword table. Unrecognized keywords are defined -// as keywords that are never checked using 'parse'. +// * It makes sure that all required keywords are present. // // * It makes sure that you didn't put anything that isn't a -// keyword into the keyword table. +// 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 { - struct cmp_char { - bool operator () (const char *s1, const char *s2) const { - return strcmp(s1, s2) < 0; - }; - }; private: - bool not_table_; - lua_State *L_; - int slot_; - eng::set parsed_; + LuaVar found, error, key, val; + LuaSpecial keytab; + LuaExtStack LS; + bool istable; void init(const lua_State *L, int slot); public: - // If the slot is not a table, sets the not_table - // flag and creates a dummy table in the slot. - LuaKeywordParser(lua_State *L, int slot); - LuaKeywordParser(const LuaCoreStack &LS, LuaSlot slot) : LuaKeywordParser(LS.state(), slot.index()) {} + LuaKeywordParser(const LuaCoreStack &LS, LuaSlot slot); - // Fetch a value from the table. This never throws. - // Return true if the value is non-nil. - bool parse(LuaSlot slot, const char *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 and returns false. + bool optional(LuaSlot slot, const char *kw); - // Check if there were any errors. If so, return an - // error message. + // 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, const char *kw); + + // Check if there are any errors so far. 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. 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(); - // Check if there are any errors. If so, throw a lua error. + // 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 L_; } + lua_State *state() const { return LS.state(); } }; + + + //////////////////////////////////////////////////////////////////// // // Lua Byte Reader diff --git a/luprex/cpp/core/planemap.cpp b/luprex/cpp/core/planemap.cpp index fb6ebb46..9e4d1252 100644 --- a/luprex/cpp/core/planemap.cpp +++ b/luprex/cpp/core/planemap.cpp @@ -834,18 +834,20 @@ void PlaneScan::configure(LuaKeywordParser &kp) { bool have_shape = false; bool have_near = false; - if (kp.parse(val, "plane")) { + kp.check_throw(); + + if (kp.optional(val, "plane")) { plane_ = LS.ckstring(val, "plane"); have_plane = true; } - if (kp.parse(val, "center")) { + if (kp.optional(val, "center")) { util::DXYZ xyz = LS.ckxyz(val, "center"); center_ = xyz; have_center = true; } - if (kp.parse(val, "radius")) { + if (kp.optional(val, "radius")) { auto simple = LS.trynumber(val); if (simple) { radius_ = *simple; @@ -862,7 +864,7 @@ void PlaneScan::configure(LuaKeywordParser &kp) { } } - if (kp.parse(val, "shape")) { + if (kp.optional(val, "shape")) { eng::string shape = LS.ckstring(val, "shape"); if (shape == "box") { shape_ = BOX; @@ -876,7 +878,7 @@ void PlaneScan::configure(LuaKeywordParser &kp) { have_shape = true; } - if (kp.parse(val, "near")) { + if (kp.optional(val, "near")) { int64_t id = LS.tanid(val); if (id == 0) { luaL_error(L, "scan configuration: 'near' must be a tangible"); @@ -893,14 +895,14 @@ void PlaneScan::configure(LuaKeywordParser &kp) { have_near = true; } - if (kp.parse(val, "include")) { + if (kp.optional(val, "include")) { if (!have_near) { luaL_error(L, "scan configuration: 'include' specified without 'near'"); } include_near_ = LS.ckboolean(val, "include"); } - if (kp.parse(val, "wholeplane")) { + if (kp.optional(val, "wholeplane")) { if (have_plane || have_center || have_radius || have_shape) { luaL_error(L, "scan configuration: do not specify plane, center, shape, or radius with 'wholeplane'"); } diff --git a/luprex/cpp/core/pprint.cpp b/luprex/cpp/core/pprint.cpp index 73817f02..3999ec21 100644 --- a/luprex/cpp/core/pprint.cpp +++ b/luprex/cpp/core/pprint.cpp @@ -243,13 +243,14 @@ public: void PrettyPrintOptions::parse(LuaKeywordParser &kp) { LuaVar option; LuaExtStack LS(kp.state(), option); - if (kp.parse(option, "indent")) { + kp.check_throw(); + if (kp.optional(option, "indent")) { indent = LS.ckboolean(option); } - if (kp.parse(option, "level")) { + if (kp.optional(option, "level")) { level = LS.ckint(option); } - if (kp.parse(option, "expand")) { + if (kp.optional(option, "expand")) { expand = LS.ckboolean(option); } } @@ -306,7 +307,7 @@ LuaDefine(string_pprintx, "options", PrettyPrintOptions options; LuaKeywordParser kp(LS, loptions); options.parse(kp); - if (!kp.parse(value, "value")) { + if (!kp.optional(value, "value")) { LS.set(value, LuaNil); } kp.final_check_throw(); diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index 7f0260a4..0690c52b 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -183,7 +183,7 @@ LuaDefine(tangible_animate, "tan,options,config", bool replace = false; if (!LS.isnil(options)) { LuaKeywordParser kp(LS, options); - if (kp.parse(option, "replace")) { + if (kp.optional(option, "replace")) { replace = LS.ckboolean(option); } kp.final_check_throw(); @@ -274,27 +274,48 @@ LuaDefine(tangible_delete, "tan", return LS.result(); } -LuaDefine(tangible_build, "config", +LuaDefine(tangible_build, "config", "|Build a new tangible object." "|" - "|The config table must contain: class,animstate." - "|" ){ + "|The configuration table must contain the keyword 'class', which" + "|must be the name of a class created with 'makeclass'. It may also" + "|contain the following values:" + "|" + "| bp - the unreal blueprint, defaults to class name." + "| plane - the plane, defaults to actor.plane" + "| xyz - the xyz coordinate, defaults to actor.xyz" + "| facing - the rotation, defaults to actor.facing" + "|" + "|Tangible.build will create an initial animstate containing only" + "|bp, plane, xyz, and facing." + "|" + "|After creating the tangible and setting up the initial animation" + "|state, build will call the constructor for the tangible." + "|The constructor must have this prototype:" + "|" + "| function myclass.init(place, actor, config)" + "|" + "|The configuration table passed to tangible.build is then passed" + "|directly to the init function. The constructor should use the keywords" + "|module to parse the keyword arguments." + "|" + "|The constructor is not allowed to block." + ){ LuaArg config; - LuaVar classname, classtab, mt, animstate; + LuaVar classname, classtab, bp, plane, xyz, facing, mt; LuaRet database; - LuaDefStack LS(L, config, classname, classtab, database, mt, animstate); + LuaDefStack LS(L, config, classname, classtab, bp, plane, xyz, facing, mt, database); + World *w = World::fetch_global_pointer(L); + LuaKeywordParser kp(LS, config); + kp.required(classname, "class"); + kp.optional(bp, "bp"); + kp.optional(plane, "plane"); + kp.optional(xyz, "xyz"); + kp.optional(facing, "facing"); + kp.check_throw(); - // Get the keyword arguments. - if (!kp.parse(classname, "class")) { - luaL_error(L, "You must specify a class for the tangible"); - } - if (!kp.parse(animstate, "animstate")) { - luaL_error(L, "You must specify an animstate table"); - } - kp.final_check_throw(); - - // Find the class. + // Verify the class. eng::string err = LS.getclass(classtab, classname); if (err != "") { luaL_error(L, "%s", err.c_str()); @@ -302,19 +323,29 @@ LuaDefine(tangible_build, "config", // Calculate the initial animation state. AnimState state; - err = state.from_lua(LS, animstate, true, false); - if (err != "") { - luaL_error(L, "%s", err.c_str()); + if (!LS.isnil(bp)) { + state.set_string("bp", LS.ckstring(bp)); + } else { + state.set_string("bp", LS.ckstring(classname)); } - if (!state.contains("xyz") || !state.contains("plane")) { - luaL_error(L, "You must specify both xyz and plane in animstate"); + if (!LS.isnil(plane)) { + state.set_string("plane", LS.ckstring(plane)); } - err = state.add_defaults(nullptr); + if (!LS.isnil(xyz)) { + state.set_dxyz("xyz", LS.ckxyz(xyz)); + } + if (!LS.isnil(facing)) { + state.set_number("facing", LS.cknumber(facing)); + } + + // Add default values from the actor. Set persistent flags. + Tangible *actor = w->tangible_get(w->lthread_actor_id_); + AnimState actorstate = actor->anim_queue_.get_final_persistent(); + err = state.add_defaults(&actorstate); if (err != "") { luaL_error(L, "%s", err.c_str()); } - World *w = World::fetch_global_pointer(L); int64_t new_id = w->alloc_id_predictable(); Tangible *tan = w->tangible_make(LS, database, new_id); @@ -322,9 +353,12 @@ LuaDefine(tangible_build, "config", LS.getmetatable(mt, database); LS.rawset(mt, "__index", classtab); + // Initialize the animstate of the new tangible. tan->anim_queue_.clear(state); tan->update_plane_item(); + // TODO: call the constructor. + return LS.result(); } @@ -839,7 +873,7 @@ LuaDefine(pprintx, "options", PrettyPrintOptions options; LuaKeywordParser kp(LS, loptions); options.parse(kp); - if (!kp.parse(value, "value")) { + if (!kp.optional(value, "value")) { LS.set(value, LuaNil); } kp.final_check_throw(); diff --git a/luprex/cpp/drv/driver-linux.cpp b/luprex/cpp/drv/driver-linux.cpp index 23cbba86..cc087ca0 100644 --- a/luprex/cpp/drv/driver-linux.cpp +++ b/luprex/cpp/drv/driver-linux.cpp @@ -238,9 +238,11 @@ static std::u32string console_read() { static void call_init_engine_wrapper(const std::filesystem::path &luprexroot, EngineWrapper *w) { using InitFn = void (*)(EngineWrapper *); - InitFn initfn = (InitFn)dlsym(nullptr, "init_engine_wrapper"); + void *exe_handle = dlopen(nullptr, RTLD_NOW | RTLD_LOCAL); + InitFn initfn = (InitFn)dlsym(exe_handle, "init_engine_wrapper"); if (initfn == nullptr) { - std::string path = luprexroot / "build/linux/luprexlib.so"; + fprintf(stderr, "loading luprexlib.so...\n"); + std::string path = luprexroot / "build/Linux/luprexlib.so"; void *dll_handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); assert(dll_handle != nullptr); initfn = (InitFn)dlsym(dll_handle, "init_engine_wrapper");