1 2local helper = wesnoth.require "helper" 3local utils = {vwriter = {}} 4 5function utils.trim(s) 6 -- use (f(a)) to get first argument 7 return (tostring(s):gsub("^%s*(.-)%s*$", "%1")) 8end 9 10function utils.split(s) 11 return tostring(s or ""):gmatch("[^%s,][^,]*") 12end 13 14function utils.check_key(val, key, tag, convert_spaces) 15 if not val then return nil end 16 if convert_spaces then 17 val = tostring(val):gsub(' ', '_') 18 end 19 if not val:match('^[a-zA-Z0-9_]+$') then 20 helper.wml_error("Invalid " .. key .. "= in [" .. tag .. "]") 21 end 22 return val 23end 24 25function utils.vwriter.init(cfg, default_variable) 26 local variable = cfg.variable or default_variable 27 local is_explicit_index = string.sub(variable, string.len(variable)) == "]" 28 local mode = cfg.mode or "always_clear" 29 local index = 0 30 if is_explicit_index then 31 -- explicit indexes behave always like "replace" 32 elseif mode == "append" then 33 index = wml.variables[variable .. ".length"] 34 elseif mode ~= "replace" then 35 wml.variables[variable] = nil 36 end 37 return { 38 variable = variable, 39 is_explicit_index = is_explicit_index, 40 index = index, 41 } 42end 43 44function utils.vwriter.write(self, container) 45 if self.is_explicit_index then 46 wml.variables[self.variable] = container 47 else 48 wml.variables[string.format("%s[%u]", self.variable, self.index)] = container 49 end 50 self.index = self.index + 1 51end 52 53function utils.get_sides(cfg, key_name, filter_name) 54 key_name = key_name or "side" 55 filter_name = filter_name or "filter_side" 56 local filter = wml.get_child(cfg, filter_name) 57 if filter then 58 if cfg[key_name] then 59 wesnoth.log('warn', "ignoring duplicate side filter information (inline side=)") 60 end 61 return wesnoth.get_sides(filter) 62 else 63 return wesnoth.get_sides{side = cfg[key_name]} 64 end 65end 66 67function utils.optional_side_filter(cfg, key_name, filter_name) 68 key_name = key_name or "side" 69 filter_name = filter_name or "filter_side" 70 if cfg[key_name] == nil and wml.get_child(cfg, filter_name) == nil then 71 return true 72 end 73 local sides = utils.get_sides(cfg, key_name, filter_name) 74 for index,side in ipairs(sides) do 75 if side.controller == "human" and side.is_local then 76 return true 77 end 78 end 79 return false 80end 81 82local current_exit = "none" 83local scope_stack = { 84 push = table.insert, 85 pop = table.remove, 86} 87 88--[[ Possible exit types: 89 - none - ordinary execution 90 - break - exiting a loop scope 91 - return - immediate termination (exit all scopes) 92 - continue - jumping to the end of a loop scope 93]] 94function utils.set_exiting(exit_type) 95 current_exit = exit_type 96end 97 98--[[ Possible scope types: 99 - plain - ordinary scope, no special features; eg [command] or [event] 100 - conditional - scope that's executing because of a condition, eg [then] or [else] 101 - switch - scope that's part of a switch statement, eg [case] or [else] 102 - loop - scope that's part of a loop, eg [do] 103Currently, only "loop" has any special effects. ]] 104function utils.handle_event_commands(cfg, scope_type) 105 -- The WML might be modifying the currently executed WML by mixing 106 -- [insert_tag] with [set_variables] and [clear_variable], so we 107 -- have to be careful not to get confused by tags vanishing during 108 -- the execution, hence the manual handling of [insert_tag]. 109 scope_type = scope_type or "plain" 110 scope_stack:push(scope_type) 111 local cmds = wml.shallow_literal(cfg) 112 for i = 1,#cmds do 113 local v = cmds[i] 114 local cmd = v[1] 115 local arg = v[2] 116 local insert_from 117 if cmd == "insert_tag" then 118 cmd = arg.name 119 local from = arg.variable or 120 helper.wml_error("[insert_tag] found with no variable= field") 121 122 arg = wml.variables[from] 123 if type(arg) ~= "table" then 124 -- Corner case: A missing variable is replaced 125 -- by an empty container rather than being ignored. 126 arg = {} 127 elseif string.sub(from, -1) ~= ']' then 128 insert_from = from 129 end 130 arg = wml.tovconfig(arg) 131 end 132 if not string.find(cmd, "^filter") then 133 cmd = wesnoth.wml_actions[cmd] or 134 helper.wml_error(string.format("[%s] not supported", cmd)) 135 if insert_from then 136 local j = 0 137 repeat 138 cmd(arg) 139 if current_exit ~= "none" then break end 140 j = j + 1 141 if j >= wml.variables[insert_from .. ".length"] then break end 142 arg = wml.tovconfig(wml.variables[string.format("%s[%d]", insert_from, j)]) 143 until false 144 else 145 cmd(arg) 146 end 147 end 148 if current_exit ~= "none" then break end 149 end 150 scope_stack:pop() 151 if #scope_stack == 0 then 152 if current_exit == "continue" and scope_type ~= "loop" then 153 helper.wml_error("[continue] found outside a loop scope!") 154 end 155 current_exit = "none" 156 end 157 return current_exit 158end 159 160-- Splits the string argument on commas, excepting those commas that occur 161-- within paired parentheses. The result is returned as a (non-empty) table. 162-- (The table might have a single entry that is an empty string, though.) 163-- Spaces around splitting commas are stripped (as in the C++ version). 164-- Empty strings are not removed (unlike the C++ version). 165function utils.parenthetical_split(str) 166 local t = {""} 167 -- To simplify some logic, end the string with paired parentheses. 168 local formatted = (str or "") .. ",()" 169 170 -- Isolate paired parentheses. 171 for prefix,paren in string.gmatch(formatted, "(.-)(%b())") do 172 -- Separate on commas 173 for comma,text in string.gmatch(prefix, "(,?)([^,]*)") do 174 if comma == "" then 175 -- We are continuing the last string found. 176 t[#t] = t[#t] .. text 177 else 178 -- We are starting the next string. 179 -- (Now that we know the last string is complete, 180 -- strip leading and trailing spaces from it.) 181 t[#t] = string.match(t[#t], "^%s*(.-)%s*$") 182 table.insert(t, text) 183 end 184 end 185 -- Add the parenthetical part to the last string found. 186 t[#t] = t[#t] .. paren 187 end 188 -- Remove the empty parentheses we had added to the end. 189 table.remove(t) 190 return t 191end 192 193--note: when using these, make sure that nothing can throw over the call to end_var_scope 194function utils.start_var_scope(name) 195 local var = wml.array_access.get(name) --containers and arrays 196 if #var == 0 then var = wml.variables[name] end --scalars (and nil/empty) 197 wml.variables[name] = nil 198 return var 199end 200 201function utils.end_var_scope(name, var) 202 wml.variables[name] = nil 203 if type(var) == "table" then 204 wml.array_access.set(name, var) 205 else 206 wml.variables[name] = var 207 end 208end 209 210return utils 211