Implement the 'keywords' lua module

This commit is contained in:
2025-01-20 21:11:59 -05:00
parent 2d531b28b3
commit 485caee05d
3 changed files with 136 additions and 9 deletions

View File

@@ -653,6 +653,7 @@ void LuaCoreStack::guard_nopredict(const char *fn) {
} }
} }
LuaKeywordParser::LuaKeywordParser(const LuaCoreStack &LS0, LuaSlot slot) LuaKeywordParser::LuaKeywordParser(const LuaCoreStack &LS0, LuaSlot slot)
: keytab(slot.index()), LS(LS0.state(), found, error, key, val) { : keytab(slot.index()), LS(LS0.state(), found, error, key, val) {
istable = LS.istable(keytab); 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) { if (!istable) {
LS.set(out, LuaNil); LS.set(out, LuaNil);
return false; 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) { if (!istable) {
LS.set(out, LuaNil); LS.set(out, LuaNil);
return false; return false;
@@ -759,3 +760,127 @@ const char *LuaByteReader::lua_reader(lua_State *L, void *ud, size_t *size) {
reader->size_ = 0; reader->size_ = 0;
return data; 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();
}

View File

@@ -518,10 +518,8 @@ private:
int size_; int size_;
public: public:
LuaExtraArgs() { LuaExtraArgs() : index_(0), size_(0) {}
index_ = 0; LuaExtraArgs(int i, int s) : index_(i), size_(s) {}
size_ = 0;
}
LuaSpecial operator[] (int n) const { return LuaSpecial(index_ + n); } LuaSpecial operator[] (int n) const { return LuaSpecial(index_ + n); }
int size() const { return size_; } int size() const { return size_; }
@@ -1186,7 +1184,9 @@ public:
~LuaExtStack() { ~LuaExtStack() {
if (!lua_isthrowing(L_)) { 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 // 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, // keyword is added to the [FOUND] set, the value is returned in slot,
// and returns true. Otherwise, sets slot to nil and returns false. // 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 // 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, // keyword is added to the [FOUND] set, the value is returned in slot,
// and returns true. Otherwise, sets slot to nil, returns false, and // and returns true. Otherwise, sets slot to nil, returns false, and
// stores an [ERROR] report in the keyword table. // 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 // Check if there are any errors so far. If any error has been
// detected, returns an error message, otherwise, returns empty // detected, returns an error message, otherwise, returns empty

View File

@@ -173,6 +173,8 @@ public:
tabify(level + 1); tabify(level + 1);
if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) { if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) {
(*output_) << LS.ckstring(key); (*output_) << LS.ckstring(key);
} else if (LS.istoken(key)) {
atomic_print(LUA_TLIGHTUSERDATA, key, false);
} else { } else {
(*output_) << "["; (*output_) << "[";
pprint_r(level + 1, false, key); pprint_r(level + 1, false, key);