This commit is contained in:
2025-02-05 13:14:42 -05:00
124 changed files with 1229 additions and 731 deletions

View File

@@ -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;
@@ -655,52 +658,119 @@ 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_);
static int tailcall_continuation(lua_State *L)
{
int base;
lua_getctx(L, &base);
return lua_gettop(L) - base;
}
int LuaDefStack::tailcall_internal(bool passup, int base, int nargs) {
lua_callk(L_, nargs, passup ? LUA_MULTRET : 0, base, tailcall_continuation);
return lua_gettop(L_) - base;
}
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::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, 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;
}
};
eng::string LuaKeywordParser::final_check() {
if (not_table_) {
return "expected a keyword table";
bool LuaKeywordParser::required(LuaSlot out, std::string_view 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());
}
}
@@ -712,3 +782,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();
}