diff --git a/data/lua/core.lua b/data/lua/core.lua index aca080c0455..053d5bc1057 100644 --- a/data/lua/core.lua +++ b/data/lua/core.lua @@ -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]========] diff --git a/src/scripting/lua_common.cpp b/src/scripting/lua_common.cpp index 1794c3dc3aa..dd6319f61a5 100644 --- a/src/scripting/lua_common.cpp +++ b/src/scripting/lua_common.cpp @@ -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)) { diff --git a/src/scripting/lua_kernel_base.cpp b/src/scripting/lua_kernel_base.cpp index 4103212e87e..4154d34df55 100644 --- a/src/scripting/lua_kernel_base.cpp +++ b/src/scripting/lua_kernel_base.cpp @@ -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 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 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[]= {