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