Add stringx module that exposes Wesnoth's split/join functions to Lua

The built-in string module is set as the metatable index for the stringx module, and the stringx module is set as the metatable index for strings, meaning all string and stringx methods are available through (''):method_name.

This also enables direct indexing of strings to get individual characters (ie ('str')[1] and the like), which package.lua already assumed even though it didn't work. Negative indices are supported to, and index from the end of the srring.

New functions:
- stringx.split
- stringx.parenthetical_split
- stringx.map_split
- stringx.escaped_split
- stringx.quoted_split
- stringx.anim_split
- stringx.join
- stringx.join_map
This commit is contained in:
Celtic Minstrel 2019-12-01 20:24:40 -05:00
parent 86f66a5825
commit aa5ac6af29
3 changed files with 235 additions and 4 deletions

View file

@ -16,6 +16,25 @@ function gui.confirm(title, msg)
return gui.show_prompt(title, msg, "yes_no", true)
end
--[========[Additional string support functions]========]
function stringx.escaped_split(str, sep, esc)
esc = esc or '\\'
return stringx.split(str, sep, {escape = esc})
end
function stringx.quoted_split(str, sep, left, right)
right = right or left
if left == nil and right == nil then
left = '('
right = ')'
end
return stringx.split(str, sep, {quote_left = left, quote_right = right})
end
function stringx.anim_split(str, sep)
return stringx.split(str, sep, {expand_anim = true});
end
--[========[Config Manipulation Functions]========]

View file

@ -958,10 +958,7 @@ bool luaW_checkvariable(lua_State *L, variable_access_create& v, int n)
bool luaW_tableget(lua_State *L, int index, const char* key)
{
if(index < 0) {
//with the next lua_pushstring negative indicies will no longer be correct.
--index;
}
index = lua_absindex(L, index);
lua_pushstring(L, key);
lua_gettable(L, index);
if(lua_isnoneornil(L, -1)) {

View file

@ -440,6 +440,196 @@ static int intf_format_list(lua_State* L)
return 1;
}
static int impl_str_index(lua_State* L)
{
if(lua_type(L, 2) == LUA_TSTRING) {
// return stringx[key]
lua_getglobal(L, "stringx");
lua_pushvalue(L, 2);
lua_gettable(L, -2);
return 1;
} else if(lua_type(L, 2) == LUA_TNUMBER) {
// return string.sub(str, key, key)
luaW_getglobal(L, "string", "sub");
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_pushvalue(L, 2);
lua_call(L, 3, 1);
return 1;
}
return 0;
}
static int intf_str_split(lua_State* L)
{
enum {BASIC, ESCAPED, PAREN, ANIM} type = BASIC;
const std::string& str = luaL_checkstring(L, 1);
const std::string& sep = luaL_optstring(L, 2, ",");
std::string left, right;
int flags = utils::REMOVE_EMPTY | utils::STRIP_SPACES;
if(lua_istable(L, 3)) {
flags = 0;
if(luaW_table_get_def(L, 3, "remove_empty", true)) {
flags |= utils::REMOVE_EMPTY;
}
if(luaW_table_get_def(L, 3, "strip_spaces", true)) {
flags |= utils::STRIP_SPACES;
}
bool anim = luaW_table_get_def(L, 3, "expand_anim", false);
if(luaW_tableget(L, 3, "escape")) {
if(anim) {
return luaL_error(L, "escape and expand_anim options are incompatible!");
}
type = ESCAPED;
left = luaL_checkstring(L, -1);
if(left.size() != 1) {
return luaL_error(L, "escape must be a single character");
}
} else if(luaW_tableget(L, 3, "quote")) {
left = right = luaL_checkstring(L, -1);
if(anim) {
type = ANIM;
left.push_back('[');
right.push_back(']');
} else type = PAREN;
} else if(luaW_tableget(L, 3, "quote_left") && luaW_tableget(L, 3, "quote_right")) {
left = luaL_checkstring(L, -2);
right = luaL_checkstring(L, -1);
if(anim) {
if(left.find_first_of("[]") != std::string::npos || right.find_first_of("[]") != std::string::npos) {
return luaL_error(L, "left and right cannot include square brackets [] if expand_anim is enabled");
}
type = ANIM;
left.push_back('[');
right.push_back(']');
} else type = PAREN;
} else if(anim) {
type = ANIM;
left = "([";
right = ")]";
}
if(type != ESCAPED && left.size() != right.size()) {
return luaL_error(L, "left and right need to be strings of the same length");
}
}
switch(type) {
case BASIC:
lua_push(L, utils::split(str, sep[0], flags));
break;
case ESCAPED:
lua_push(L, utils::quoted_split(str, sep[0], flags, left[0]));
break;
case PAREN:
lua_push(L, utils::parenthetical_split(str, sep[0], left, right, flags));
break;
case ANIM:
lua_push(L, utils::square_parenthetical_split(str, sep[0], left, right, flags));
break;
}
return 1;
}
static int intf_str_paren_split(lua_State* L)
{
const std::string& str = luaL_checkstring(L, 1);
const std::string& left = luaL_optstring(L, 2, "(");
const std::string& right = luaL_optstring(L, 3, ")");
if(left.size() != right.size()) {
return luaL_error(L, "left and right need to be strings of the same length");
}
bool strip_spaces = luaL_opt(L, luaW_toboolean, 4, true);
lua_push(L, utils::parenthetical_split(str, 0, left, right, strip_spaces ? utils::STRIP_SPACES : 0));
return 1;
}
static int intf_str_map_split(lua_State* L)
{
const std::string& str = luaL_checkstring(L, 1);
const std::string& sep = luaL_optstring(L, 2, ",");
const std::string& kv = luaL_optstring(L, 3, ":");
std::string dflt;
if(sep.size() != 1) {
return luaL_error(L, "separator must be a single character");
}
if(kv.size() != 1) {
return luaL_error(L, "key_value_separator must be a single character");
}
int flags = utils::REMOVE_EMPTY | utils::STRIP_SPACES;
if(lua_istable(L, 4)) {
flags = 0;
if(luaW_table_get_def(L, 4, "remove_empty", true)) {
flags |= utils::REMOVE_EMPTY;
}
if(luaW_table_get_def(L, 4, "strip_spaces", true)) {
flags |= utils::STRIP_SPACES;
}
if(luaW_tableget(L, 4, "default")) {
dflt = luaL_checkstring(L, -1);
}
}
lua_push(L, utils::map_split(str, sep[0], kv[0], flags, dflt));
return 1;
}
static int intf_str_join(lua_State* L) {
// Support both join(list, [sep]) and join(sep, list)
// The latter form means sep:join(list) also works.
std::string sep;
int list_idx;
if(lua_istable(L, 1)) {
list_idx = 1;
sep = luaL_optstring(L, 2, ",");
} else if(lua_istable(L, 2)) {
sep = luaL_checkstring(L, 1);
list_idx = 2;
} else return luaL_error(L, "invalid arguments to join, should have map and separator");
std::vector<std::string> pieces;
for(int i = 1; i <= luaL_len(L, list_idx); i++) {
lua_getglobal(L, "tostring");
lua_geti(L, list_idx, i);
lua_call(L, 1, 1);
pieces.push_back(luaL_checkstring(L, -1));
}
lua_push(L, utils::join(pieces, sep));
return 1;
}
static int intf_str_join_map(lua_State* L) {
// Support join_map(map, [sep], [kv_sep]), join_map(sep, map, [kv_sep]), and join_map(sep, kv_sep, map)
// The latter forms mean sep:join_map(kv_sep, map) and sep:join_map(map) also work.
// If only one separator is given in the first form, it will be sep, not kv_sep
std::string sep, kv;
int map_idx;
if(lua_istable(L, 1)) {
map_idx = 1;
sep = luaL_optstring(L, 2, ",");
kv = luaL_optstring(L, 3, ":");
} else if(lua_istable(L, 2)) {
sep = luaL_checkstring(L, 1);
map_idx = 2;
kv = luaL_optstring(L, 3, ":");
} else if(lua_istable(L, 3)) {
sep = luaL_checkstring(L, 1);
kv = luaL_checkstring(L, 2);
map_idx = 3;
} else return luaL_error(L, "invalid arguments to join_map, should have map, separator, and key_value_separator");
std::map<std::string, std::string> pieces;
for(lua_pushnil(L); lua_next(L, map_idx); /*pop in loop body*/) {
int key_idx = lua_absindex(L, -2), val_idx = lua_absindex(L, -1);
lua_getglobal(L, "tostring");
lua_pushvalue(L, key_idx);
lua_call(L, 1, 1);
std::string& val = pieces[luaL_checkstring(L, -1)];
lua_getglobal(L, "tostring");
lua_pushvalue(L, val_idx);
lua_call(L, 1, 1);
val = luaL_checkstring(L, -1);
lua_settop(L, key_idx);
}
lua_push(L, utils::join_map(pieces, sep, kv));
return 1;
}
static int intf_get_language(lua_State* L)
{
lua_push(L, get_language().localename);
@ -675,6 +865,31 @@ lua_kernel_base::lua_kernel_base()
luaL_setfuncs(L, callbacks, 0);
//lua_cpp::set_functions(L, cpp_callbacks, 0);
lua_setglobal(L, "wesnoth");
cmd_log_ << "Adding stringx module...\n";
static luaL_Reg const str_callbacks[] = {
{ "split", &intf_str_split },
{ "parenthetical_split", &intf_str_paren_split },
{ "map_split", &intf_str_map_split },
{ "join", &intf_str_join },
{ "join_map", &intf_str_join_map },
{ nullptr, nullptr },
};
lua_newtable(L);
luaL_setfuncs(L, str_callbacks, 0);
// Set the stringx metatable to index the string module
lua_createtable(L, 0, 1);
lua_getglobal(L, "string");
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
lua_setglobal(L, "stringx");
// Set the metatable of strings to index the stringx module instead of the string module
lua_pushliteral(L, "");
lua_getmetatable(L, -1);
lua_pushcfunction(L, &impl_str_index);
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
cmd_log_ << "Adding wml module...\n";
static luaL_Reg const wml_callbacks[]= {