235 lines
6.8 KiB
C++
235 lines
6.8 KiB
C++
|
|
#include "wrap-string.hpp"
|
||
|
|
|
||
|
|
#include "keywords.hpp"
|
||
|
|
#include "util.hpp"
|
||
|
|
|
||
|
|
static LuaToken token_error("error");
|
||
|
|
static LuaToken token_found("found");
|
||
|
|
|
||
|
|
LuaKeywordParser::LuaKeywordParser(const LuaCoreStack &LS0, LuaSlot slot)
|
||
|
|
: keytab(slot.index()), LS(LS0.state()) {
|
||
|
|
lua_State *L = LS0.state();
|
||
|
|
lua_pushnil(L); found.index_ = lua_gettop(L);
|
||
|
|
lua_pushnil(L); error.index_ = lua_gettop(L);
|
||
|
|
lua_pushnil(L); key.index_ = lua_gettop(L);
|
||
|
|
lua_pushnil(L); val.index_ = lua_gettop(L);
|
||
|
|
|
||
|
|
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::optional(LuaSlot out, std::string_view 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;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
bool LuaKeywordParser::required(LuaSlot out, std::string_view 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 {
|
||
|
|
LS.rawget(error, keytab, token_error);
|
||
|
|
if (!LS.isstring(error)) {
|
||
|
|
LS.rawset(keytab, token_error, util::ss("required keyword argument not present: ", kw));
|
||
|
|
}
|
||
|
|
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(LS.state(), "%s", err.c_str());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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();
|
||
|
|
}
|