From 485caee05d05a38f0567578ce79223c48f56ee00 Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 20 Jan 2025 21:11:59 -0500 Subject: [PATCH] Implement the 'keywords' lua module --- luprex/cpp/core/luastack.cpp | 129 ++++++++++++++++++++++++++++++++++- luprex/cpp/core/luastack.hpp | 14 ++-- luprex/cpp/core/pprint.cpp | 2 + 3 files changed, 136 insertions(+), 9 deletions(-) diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index 30709e6e..3134e67c 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -653,6 +653,7 @@ void LuaCoreStack::guard_nopredict(const char *fn) { } } + LuaKeywordParser::LuaKeywordParser(const LuaCoreStack &LS0, LuaSlot slot) : keytab(slot.index()), LS(LS0.state(), found, error, key, val) { istable = LS.istable(keytab); @@ -665,7 +666,7 @@ LuaKeywordParser::LuaKeywordParser(const LuaCoreStack &LS0, LuaSlot slot) } } -bool LuaKeywordParser::optional(LuaSlot out, const char *kw) { +bool LuaKeywordParser::optional(LuaSlot out, std::string_view kw) { if (!istable) { LS.set(out, LuaNil); return false; @@ -680,7 +681,7 @@ bool LuaKeywordParser::optional(LuaSlot out, const char *kw) { } }; -bool LuaKeywordParser::required(LuaSlot out, const char *kw) { +bool LuaKeywordParser::required(LuaSlot out, std::string_view kw) { if (!istable) { LS.set(out, LuaNil); return false; @@ -759,3 +760,127 @@ const char *LuaByteReader::lua_reader(lua_State *L, void *ud, size_t *size) { 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(); +} + diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp index 8212bc11..e1bca9d2 100644 --- a/luprex/cpp/core/luastack.hpp +++ b/luprex/cpp/core/luastack.hpp @@ -518,10 +518,8 @@ private: int size_; public: - LuaExtraArgs() { - index_ = 0; - size_ = 0; - } + LuaExtraArgs() : index_(0), size_(0) {} + LuaExtraArgs(int i, int s) : index_(i), size_(s) {} LuaSpecial operator[] (int n) const { return LuaSpecial(index_ + n); } int size() const { return size_; } @@ -1186,7 +1184,9 @@ public: ~LuaExtStack() { if (!lua_isthrowing(L_)) { - lua_settop(L_, oldtop_); + if (lua_gettop(L_) > oldtop_) { + lua_settop(L_, oldtop_); + } } } }; @@ -1247,13 +1247,13 @@ public: // 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); + bool optional(LuaSlot slot, std::string_view 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, returns false, and // stores an [ERROR] report in the keyword table. - bool required(LuaSlot slot, const char *kw); + bool required(LuaSlot slot, std::string_view kw); // Check if there are any errors so far. If any error has been // detected, returns an error message, otherwise, returns empty diff --git a/luprex/cpp/core/pprint.cpp b/luprex/cpp/core/pprint.cpp index 3999ec21..3f9016d5 100644 --- a/luprex/cpp/core/pprint.cpp +++ b/luprex/cpp/core/pprint.cpp @@ -173,6 +173,8 @@ public: tabify(level + 1); if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) { (*output_) << LS.ckstring(key); + } else if (LS.istoken(key)) { + atomic_print(LUA_TLIGHTUSERDATA, key, false); } else { (*output_) << "["; pprint_r(level + 1, false, key);